From a04f818ffb8627a5c3d594edb466d1b8e45e3015 Mon Sep 17 00:00:00 2001 From: Charles Zhao Date: Fri, 29 Apr 2022 21:01:28 +0800 Subject: [PATCH] feat(console): integrate dark mode settings --- packages/console/src/App.module.scss | 21 +++ packages/console/src/App.tsx | 27 ++- .../components/Topbar/index.module.scss | 6 +- .../AppContent/components/Topbar/index.tsx | 4 +- .../components/AppContent/index.module.scss | 10 -- .../src/components/AppContent/index.tsx | 17 +- .../src/components/Drawer/index.module.scss | 2 +- packages/console/src/consts/index.ts | 4 + packages/console/src/icons/Logo.tsx | 160 ++++++++++++++++++ .../components/GuideModal/index.module.scss | 2 +- packages/console/src/pages/Settings/index.tsx | 2 + packages/console/src/scss/_colors.scss | 72 ++++---- packages/console/src/scss/normalized.scss | 1 + packages/console/src/scss/table.module.scss | 6 +- packages/phrases/src/locales/en.ts | 2 +- .../schemas/src/foundations/jsonb-types.ts | 6 +- 16 files changed, 265 insertions(+), 77 deletions(-) create mode 100644 packages/console/src/App.module.scss create mode 100644 packages/console/src/consts/index.ts create mode 100644 packages/console/src/icons/Logo.tsx diff --git a/packages/console/src/App.module.scss b/packages/console/src/App.module.scss new file mode 100644 index 000000000..553484ee1 --- /dev/null +++ b/packages/console/src/App.module.scss @@ -0,0 +1,21 @@ +@use '@/scss/colors' as colors; + +.light { + @include colors.light-theme; +} + +.dark { + @include colors.dark-theme; +} + +@media (prefers-color-scheme: light) { + body { + @include colors.light-theme; + } +} + +@media (prefers-color-scheme: dark) { + body { + @include colors.dark-theme; + } +} diff --git a/packages/console/src/App.tsx b/packages/console/src/App.tsx index 2008f03bf..02ef697d7 100644 --- a/packages/console/src/App.tsx +++ b/packages/console/src/App.tsx @@ -1,13 +1,16 @@ import { LogtoProvider } from '@logto/react'; +import { AppearanceMode, Setting } from '@logto/schemas'; import React, { useEffect } from 'react'; import { BrowserRouter, Navigate, Route, Routes, useLocation, useNavigate } from 'react-router-dom'; -import { SWRConfig } from 'swr'; +import useSWR, { SWRConfig } from 'swr'; import './scss/normalized.scss'; +import * as styles from './App.module.scss'; import AppContent from './components/AppContent'; import { getPath, sections } from './components/AppContent/components/Sidebar'; import Toast from './components/Toast'; -import { logtoApiResource } from './consts/api'; +import { themeStorageKey, logtoApiResource } from './consts'; +import { RequestError } from './hooks/use-api'; import useSwrFetcher from './hooks/use-swr-fetcher'; import initI18n from './i18n/init'; import ApiResourceDetails from './pages/ApiResourceDetails'; @@ -26,11 +29,29 @@ import Users from './pages/Users'; const isBasenameNeeded = process.env.NODE_ENV !== 'development' || process.env.PORT === '5002'; void initI18n(); +const defaultTheme = localStorage.getItem(themeStorageKey) ?? AppearanceMode.SyncWithSystem; const Main = () => { const location = useLocation(); const navigate = useNavigate(); const fetcher = useSwrFetcher(); + const { data } = useSWR('/api/settings'); + + useEffect(() => { + const theme = data?.adminConsole.appearanceMode ?? defaultTheme; + const isFollowSystem = theme === AppearanceMode.SyncWithSystem; + const className = styles[theme] ?? ''; + + if (!isFollowSystem) { + document.body.classList.add(className); + } + + return () => { + if (!isFollowSystem) { + document.body.classList.remove(className); + } + }; + }, [data?.adminConsole.appearanceMode]); useEffect(() => { if (location.pathname === '/') { @@ -43,7 +64,7 @@ const Main = () => { } /> - }> + }> } /> } /> diff --git a/packages/console/src/components/AppContent/components/Topbar/index.module.scss b/packages/console/src/components/AppContent/components/Topbar/index.module.scss index 2d39111fc..251746f30 100644 --- a/packages/console/src/components/AppContent/components/Topbar/index.module.scss +++ b/packages/console/src/components/AppContent/components/Topbar/index.module.scss @@ -7,8 +7,10 @@ display: flex; align-items: center; - img { - height: 32px; + .logo { + width: 94px; + height: 30px; + fill: var(--color-text); } .line { diff --git a/packages/console/src/components/AppContent/components/Topbar/index.tsx b/packages/console/src/components/AppContent/components/Topbar/index.tsx index 56ab68bcc..cf03db23c 100644 --- a/packages/console/src/components/AppContent/components/Topbar/index.tsx +++ b/packages/console/src/components/AppContent/components/Topbar/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import logo from '@/assets/images/logo.svg'; +import Logo from '@/icons/Logo'; import * as styles from './index.module.scss'; @@ -10,7 +10,7 @@ const Topbar = () => { return (
- +
{t('admin_console.title')}
diff --git a/packages/console/src/components/AppContent/index.module.scss b/packages/console/src/components/AppContent/index.module.scss index 94e58caee..8bee59453 100644 --- a/packages/console/src/components/AppContent/index.module.scss +++ b/packages/console/src/components/AppContent/index.module.scss @@ -1,4 +1,3 @@ -@use '@/scss/colors' as colors; @use '@/scss/underscore' as _; .app { @@ -20,12 +19,3 @@ overflow-y: auto; } } - -.light { - @include colors.light-theme; -} - -// Currently not in use -.dark { - @include colors.dark-theme; -} diff --git a/packages/console/src/components/AppContent/index.tsx b/packages/console/src/components/AppContent/index.tsx index 8d2a1d086..d8ee2e776 100644 --- a/packages/console/src/components/AppContent/index.tsx +++ b/packages/console/src/components/AppContent/index.tsx @@ -8,25 +8,10 @@ import Sidebar from './components/Sidebar'; import Topbar from './components/Topbar'; import * as styles from './index.module.scss'; -type Theme = 'light' | 'dark'; - -type Props = { - theme: Theme; -}; - -const AppContent = ({ theme }: Props) => { +const AppContent = () => { const { isAuthenticated, signIn } = useLogto(); const href = useHref('/callback'); - useEffect(() => { - const className = styles[theme] ?? ''; - document.body.classList.add(className); - - return () => { - document.body.classList.remove(className); - }; - }, [theme]); - useEffect(() => { if (!isAuthenticated) { void signIn(new URL(href, window.location.origin).toString()); diff --git a/packages/console/src/components/Drawer/index.module.scss b/packages/console/src/components/Drawer/index.module.scss index e7786a2ba..8e1ad9259 100644 --- a/packages/console/src/components/Drawer/index.module.scss +++ b/packages/console/src/components/Drawer/index.module.scss @@ -7,7 +7,7 @@ right: 0; bottom: 0; outline: none; - background: var(--color-on-primary); + background: var(--color-layer-1); padding: _.unit(6); overflow-y: auto; diff --git a/packages/console/src/consts/index.ts b/packages/console/src/consts/index.ts new file mode 100644 index 000000000..430d0a0ae --- /dev/null +++ b/packages/console/src/consts/index.ts @@ -0,0 +1,4 @@ +export * from './applications'; +export * from './api'; + +export const themeStorageKey = 'adminConsoleTheme'; diff --git a/packages/console/src/icons/Logo.tsx b/packages/console/src/icons/Logo.tsx new file mode 100644 index 000000000..046db7b35 --- /dev/null +++ b/packages/console/src/icons/Logo.tsx @@ -0,0 +1,160 @@ +import React, { SVGProps } from 'react'; + +const Logo = ({ ...props }: SVGProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +export default Logo; diff --git a/packages/console/src/pages/Connectors/components/GuideModal/index.module.scss b/packages/console/src/pages/Connectors/components/GuideModal/index.module.scss index b5df4c088..8b31751ff 100644 --- a/packages/console/src/pages/Connectors/components/GuideModal/index.module.scss +++ b/packages/console/src/pages/Connectors/components/GuideModal/index.module.scss @@ -9,7 +9,7 @@ .header { display: flex; align-items: center; - background-color: var(--color-on-primary); + background-color: var(--color-layer-1); height: 64px; padding: 0 _.unit(21) 0 _.unit(2); diff --git a/packages/console/src/pages/Settings/index.tsx b/packages/console/src/pages/Settings/index.tsx index fc6bdd671..3634e51c9 100644 --- a/packages/console/src/pages/Settings/index.tsx +++ b/packages/console/src/pages/Settings/index.tsx @@ -14,6 +14,7 @@ import FormField from '@/components/FormField'; import Select from '@/components/Select'; import TabNav, { TabNavLink } from '@/components/TabNav'; import TextInput from '@/components/TextInput'; +import { themeStorageKey } from '@/consts'; import useApi, { RequestError } from '@/hooks/use-api'; import * as detailsStyles from '@/scss/details.module.scss'; @@ -50,6 +51,7 @@ const Settings = () => { }) .json(); void mutate(updatedData); + localStorage.setItem(themeStorageKey, updatedData.adminConsole.appearanceMode); toast.success(t('settings.saved')); }); diff --git a/packages/console/src/scss/_colors.scss b/packages/console/src/scss/_colors.scss index 1291e3959..98f302a5d 100644 --- a/packages/console/src/scss/_colors.scss +++ b/packages/console/src/scss/_colors.scss @@ -111,11 +111,11 @@ --color-background: var(--color-neutral-99); --color-on-background: var(--color-neutral-10); --color-surface: var(--color-neutral-99); - --color-surface-1: #ecebf6; // N99 + 2% NV50 + 5% P40 - --color-surface-2: #e8e6f6; // N99 + 2% NV50 + 8% P40 - --color-surface-3: #e3e0f6; // N99 + 2% NV50 + 11% P40 - --color-surface-4: #e2def6; // N99 + 2% NV50 + 12% P40 - --color-surface-5: #dcd6f5; // N99 + 2% NV50 + 16% P40 + --color-surface-1: #ecebf6; // Neutral-99 + 2% Neutral-Variant-50 + 5% Primary-40 + --color-surface-2: #e8e6f6; // Neutral-99 + 2% Neutral-Variant-50 + 8% Primary-40 + --color-surface-3: #e3e0f6; // Neutral-99 + 2% Neutral-Variant-50 + 11% Primary-40 + --color-surface-4: #dfdaf5; // Neutral-99 + 2% Neutral-Variant-50 + 14% Primary-40 + --color-surface-5: #dcd6f5; // Neutral-99 + 2% Neutral-Variant-50 + 16% Primary-40 --color-on-surface: var(--color-neutral-10); --color-surface-variant: var(--color-neutral-variant-90); --color-on-surface-variant: var(--color-neutral-variant-30); @@ -134,19 +134,20 @@ --color-layer-1: var(--color-all-100); --color-layer-2: var(--color-neutral-95); --color-inverse-on-surface: var(--color-neutral-95); - --color-hover: rgba(25, 28, 29, 8%); // 8% N10 - --color-pressed: rgba(25, 28, 29, 12%); // 12% N10 - --color-focused: rgba(25, 28, 29, 16%); // 16% N10 - --color-hover-variant: rgba(93, 52, 242, 8%); // 8% P40 - --color-pressed-variant: rgba(93, 52, 242, 12%); // 12% P40 - --color-focused-variant: rgba(93, 52, 242, 16%); // 16% P40 - --shadow-light-s1: 0 4px 8px rgba(66, 41, 159, 8%); - --shadow-light-s2: 0 4px 12px rgba(66, 41, 159, 12%); - --shadow-light-s2-reversed: 0 -4px 12px rgba(66, 41, 159, 12%); - --shadow-light-s3: 0 4px 16px rgba(66, 41, 159, 16%); + --color-hover: rgba(25, 28, 29, 8%); // 8% Neutral-10 + --color-pressed: rgba(25, 28, 29, 12%); // 12% Neutral-10 + --color-focused: rgba(25, 28, 29, 16%); // 16% Neutral-10 + --color-hover-variant: rgba(93, 52, 242, 8%); // 8% Primary-40 + --color-pressed-variant: rgba(93, 52, 242, 12%); // 12% Primary-40 + --color-focused-variant: rgba(93, 52, 242, 16%); // 16% Primary-40 - // Client specific variables - --color-danger-focused: rgba(186, 27, 27, 16%); // 16% ER40 + // Shadows + --shadow-1: 0 4px 8px rgba(66, 41, 159, 8%); + --shadow-2: 0 4px 12px rgba(66, 41, 159, 12%); + --shadow-3: 0 4px 16px rgba(66, 41, 159, 16%); + + // Client specific variables (not available in design system) + --color-danger-focused: rgba(186, 27, 27, 16%); // 16% Error-40 } @mixin dark-theme { @@ -262,11 +263,11 @@ --color-background: var(--color-neutral-99); --color-on-background: var(--color-neutral-10); --color-surface: var(--color-neutral-99); - --color-surface-1: #25272b; // N99 + 2% NV50 + 5% P40 - --color-surface-2: #2a2c32; // N99 + 2% NV50 + 8% P40 - --color-surface-3: #2f3039; // N99 + 2% NV50 + 11% P40 - --color-surface-4: #31323b; // N99 + 2% NV50 + 12% P40 - --color-surface-5: #383844; // N99 + 2% NV50 + 16% P40 + --color-surface-1: #25272b; // Neutral-99 + 2% Neutral-Variant-50 + 5% Primary-40 + --color-surface-2: #2a2c32; // Neutral-99 + 2% Neutral-Variant-50 + 8% Primary-40 + --color-surface-3: #2f3039; // Neutral-99 + 2% Neutral-Variant-50 + 11% Primary-40 + --color-surface-4: #34353f; // Neutral-99 + 2% Neutral-Variant-50 + 14% Primary-40 + --color-surface-5: #383844; // Neutral-99 + 2% Neutral-Variant-50 + 16% Primary-40 --color-on-surface: var(--color-neutral-10); --color-surface-variant: var(--color-neutral-variant-90); --color-on-surface-variant: var(--color-neutral-variant-30); @@ -281,21 +282,22 @@ --color-border: var(--color-neutral-80); --color-divider: var(--color-neutral-90); --color-disabled: var(--color-neutral-80); - --color-base: #1e2124; // N99 + 3% P40 + --color-base: var(--color-surface); --color-layer-1: var(--color-surface-2); --color-layer-2: var(--color-surface-3); --color-inverse-on-surface: var(--color-neutral-95); - --color-hover: rgba(247, 248, 248, 8%); // 8% N10 - --color-pressed: rgba(247, 248, 248, 12%); // 12% N10 - --color-focused: rgba(247, 248, 248, 16%); // 16% N10 - --color-hover-variant: rgba(202, 190, 255, 8%); // 8% P40 - --color-pressed-variant: rgba(202, 190, 255, 12%); // 12% P40 - --color-focused-variant: rgba(202, 190, 255, 16%); // 16% P40 - --shadow-light-s1: 0 4px 8px rgba(66, 41, 159, 8%); - --shadow-light-s2: 0 4px 12px rgba(66, 41, 159, 12%); - --shadow-light-s2-reversed: 0 -4px -12px rgba(66, 41, 159, 12%); - --shadow-light-s3: 0 4px 16px rgba(66, 41, 159, 16%); + --color-hover: rgba(247, 248, 248, 8%); // 8% Neutral-10 + --color-pressed: rgba(247, 248, 248, 12%); // 12% Neutral-10 + --color-focused: rgba(247, 248, 248, 16%); // 16% Neutral-10 + --color-hover-variant: rgba(202, 190, 255, 8%); // 8% Primary-40 + --color-pressed-variant: rgba(202, 190, 255, 12%); // 12% Primary-40 + --color-focused-variant: rgba(202, 190, 255, 16%); // 16% Primary-40 - // Client specific variables - --color-danger-focused: rgba(255, 180, 169, 16%); // 16% ER40 + // Shadows + --shadow-1: 0 4px 8px rgba(66, 41, 159, 8%); + --shadow-2: 0 4px 12px rgba(66, 41, 159, 12%); + --shadow-3: 0 4px 16px rgba(66, 41, 159, 16%); + + // Client specific variables (not available in design system) + --color-danger-focused: rgba(255, 180, 169, 16%); // 16% Error-40 } diff --git a/packages/console/src/scss/normalized.scss b/packages/console/src/scss/normalized.scss index e81c03ba0..20b60f660 100644 --- a/packages/console/src/scss/normalized.scss +++ b/packages/console/src/scss/normalized.scss @@ -6,6 +6,7 @@ body { padding: 0; font-family: sans-serif; background: var(--color-base); + color: var(--color-text); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: auto; } diff --git a/packages/console/src/scss/table.module.scss b/packages/console/src/scss/table.module.scss index 6a193b2ec..e245ac7d1 100644 --- a/packages/console/src/scss/table.module.scss +++ b/packages/console/src/scss/table.module.scss @@ -1,7 +1,7 @@ @use '@/scss/underscore' as _; .base { - box-shadow: var(--shadow-light-s1); + box-shadow: var(--shadow-1); } tr.clickable { @@ -16,7 +16,7 @@ tr.clickable { overflow-y: auto; border: 1px solid var(--color-neutral-90); border-radius: _.unit(2); - box-shadow: var(--shadow-light-s1); + box-shadow: var(--shadow-1); table { border: none; @@ -27,7 +27,7 @@ tr.clickable { top: 0; th { - background: var(--color-on-primary); + background: var(--color-layer-1); } } } diff --git a/packages/phrases/src/locales/en.ts b/packages/phrases/src/locales/en.ts index 771f0be30..d3e9cfc59 100644 --- a/packages/phrases/src/locales/en.ts +++ b/packages/phrases/src/locales/en.ts @@ -415,7 +415,7 @@ const translation = { }, }, settings: { - title: 'settings', + title: 'Settings', description: 'Global settings and others', tabs: { general: 'General', diff --git a/packages/schemas/src/foundations/jsonb-types.ts b/packages/schemas/src/foundations/jsonb-types.ts index 89146e7c3..37c3780a2 100644 --- a/packages/schemas/src/foundations/jsonb-types.ts +++ b/packages/schemas/src/foundations/jsonb-types.ts @@ -135,9 +135,9 @@ export type ConnectorIds = z.infer; */ export enum AppearanceMode { - SyncWithSystem = 'SyncWithSystem', - LightMode = 'LightMode', - DarkMode = 'DarkMode', + SyncWithSystem = 'system', + LightMode = 'light', + DarkMode = 'dark', } export const adminConsoleConfigGuard = z.object({