From 98026d4c0bbaf3d1ef3cbfbd08e89a4cad146162 Mon Sep 17 00:00:00 2001 From: Local Admin Date: Mon, 26 Jan 2026 22:07:16 +0000 Subject: [PATCH] feat(ui): add solarized light and dark themes --- .../components/MemoContent/MermaidBlock.tsx | 2 +- web/src/components/ThemeSelect.tsx | 2 + web/src/themes/solarized-dark.css | 103 ++++++++++++++++++ web/src/themes/solarized-light.css | 103 ++++++++++++++++++ web/src/types/modules/setting.d.ts | 2 +- web/src/utils/theme.ts | 17 ++- 6 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 web/src/themes/solarized-dark.css create mode 100644 web/src/themes/solarized-light.css diff --git a/web/src/components/MemoContent/MermaidBlock.tsx b/web/src/components/MemoContent/MermaidBlock.tsx index 48ec20bb1..cbd210e42 100644 --- a/web/src/components/MemoContent/MermaidBlock.tsx +++ b/web/src/components/MemoContent/MermaidBlock.tsx @@ -11,7 +11,7 @@ interface MermaidBlockProps { } const getMermaidTheme = (appTheme: string): "default" | "dark" => { - return appTheme === "default-dark" ? "dark" : "default"; + return appTheme.includes("dark") ? "dark" : "default"; }; export const MermaidBlock = ({ children, className }: MermaidBlockProps) => { diff --git a/web/src/components/ThemeSelect.tsx b/web/src/components/ThemeSelect.tsx index 7b9b4bbf1..db34e3614 100644 --- a/web/src/components/ThemeSelect.tsx +++ b/web/src/components/ThemeSelect.tsx @@ -15,6 +15,8 @@ const THEME_ICONS: Record = { midnight: , paper: , whitewall: , + "solarized-light": , + "solarized-dark": , }; const ThemeSelect = ({ value, onValueChange, className }: ThemeSelectProps = {}) => { diff --git a/web/src/themes/solarized-dark.css b/web/src/themes/solarized-dark.css new file mode 100644 index 000000000..8de6adbc5 --- /dev/null +++ b/web/src/themes/solarized-dark.css @@ -0,0 +1,103 @@ +:root { + --background: #002b36; + --foreground: #839496; + --card: #073642; + --card-foreground: #839496; + --popover: #073642; + --popover-foreground: #839496; + --primary: #268bd2; + --primary-foreground: #002b36; + --secondary: #073642; + --secondary-foreground: #93a1a1; + --muted: #073642; + --muted-foreground: #93a1a1; + --accent: #2aa198; + --accent-foreground: #002b36; + --destructive: #dc322f; + --destructive-foreground: #fdf6e3; + --border: #0b3d48; + --input: #0b3d48; + --ring: #268bd2; + --chart-1: #b58900; + --chart-2: #2aa198; + --chart-3: #268bd2; + --chart-4: #6c71c4; + --chart-5: #859900; + --sidebar: #00212a; + --sidebar-foreground: #93a1a1; + --sidebar-primary: #268bd2; + --sidebar-primary-foreground: #002b36; + --sidebar-accent: #2aa198; + --sidebar-accent-foreground: #002b36; + --sidebar-border: #0b3d48; + --sidebar-ring: #268bd2; + --font-sans: + ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + --radius: 0.5rem; + --shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05); + --shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05); + --shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1); + --shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1); + --shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 2px 4px -1px hsl(0 0% 0% / 0.1); + --shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 4px 6px -1px hsl(0 0% 0% / 0.1); + --shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 8px 10px -1px hsl(0 0% 0% / 0.1); + --shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25); + --tracking-normal: 0em; + --spacing: 0.25rem; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); + + --font-sans: var(--font-sans); + --font-mono: var(--font-mono); + --font-serif: var(--font-serif); + + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + + --shadow-2xs: var(--shadow-2xs); + --shadow-xs: var(--shadow-xs); + --shadow-sm: var(--shadow-sm); + --shadow: var(--shadow); + --shadow-md: var(--shadow-md); + --shadow-lg: var(--shadow-lg); + --shadow-xl: var(--shadow-xl); + --shadow-2xl: var(--shadow-2xl); +} diff --git a/web/src/themes/solarized-light.css b/web/src/themes/solarized-light.css new file mode 100644 index 000000000..84b4ab85f --- /dev/null +++ b/web/src/themes/solarized-light.css @@ -0,0 +1,103 @@ +:root { + --background: #fdf6e3; + --foreground: #657b83; + --card: #eee8d5; + --card-foreground: #657b83; + --popover: #eee8d5; + --popover-foreground: #657b83; + --primary: #268bd2; + --primary-foreground: #fdf6e3; + --secondary: #eee8d5; + --secondary-foreground: #586e75; + --muted: #eee8d5; + --muted-foreground: #586e75; + --accent: #2aa198; + --accent-foreground: #fdf6e3; + --destructive: #dc322f; + --destructive-foreground: #fdf6e3; + --border: #e4ddc5; + --input: #e4ddc5; + --ring: #268bd2; + --chart-1: #b58900; + --chart-2: #2aa198; + --chart-3: #268bd2; + --chart-4: #6c71c4; + --chart-5: #859900; + --sidebar: #f6f0da; + --sidebar-foreground: #586e75; + --sidebar-primary: #268bd2; + --sidebar-primary-foreground: #fdf6e3; + --sidebar-accent: #2aa198; + --sidebar-accent-foreground: #fdf6e3; + --sidebar-border: #e4ddc5; + --sidebar-ring: #268bd2; + --font-sans: + ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + --radius: 0.5rem; + --shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05); + --shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05); + --shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1); + --shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1); + --shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 2px 4px -1px hsl(0 0% 0% / 0.1); + --shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 4px 6px -1px hsl(0 0% 0% / 0.1); + --shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 8px 10px -1px hsl(0 0% 0% / 0.1); + --shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25); + --tracking-normal: 0em; + --spacing: 0.25rem; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); + + --font-sans: var(--font-sans); + --font-mono: var(--font-mono); + --font-serif: var(--font-serif); + + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + + --shadow-2xs: var(--shadow-2xs); + --shadow-xs: var(--shadow-xs); + --shadow-sm: var(--shadow-sm); + --shadow: var(--shadow); + --shadow-md: var(--shadow-md); + --shadow-lg: var(--shadow-lg); + --shadow-xl: var(--shadow-xl); + --shadow-2xl: var(--shadow-2xl); +} diff --git a/web/src/types/modules/setting.d.ts b/web/src/types/modules/setting.d.ts index 64e9bd2fe..6b557cfe6 100644 --- a/web/src/types/modules/setting.d.ts +++ b/web/src/types/modules/setting.d.ts @@ -1 +1 @@ -type Theme = "system" | "default" | "default-dark" | "midnight" | "paper" | "whitewall"; +type Theme = "system" | "default" | "default-dark" | "midnight" | "paper" | "whitewall" | "solarized-light" | "solarized-dark"; diff --git a/web/src/utils/theme.ts b/web/src/utils/theme.ts index 4b10bec68..8688a0e23 100644 --- a/web/src/utils/theme.ts +++ b/web/src/utils/theme.ts @@ -1,13 +1,24 @@ import defaultDarkThemeContent from "../themes/default-dark.css?raw"; import midnightThemeContent from "../themes/midnight.css?raw"; import paperThemeContent from "../themes/paper.css?raw"; +import solarizedDarkThemeContent from "../themes/solarized-dark.css?raw"; +import solarizedLightThemeContent from "../themes/solarized-light.css?raw"; import whitewallThemeContent from "../themes/whitewall.css?raw"; // ============================================================================ // Types and Constants // ============================================================================ -const VALID_THEMES = ["system", "default", "default-dark", "midnight", "paper", "whitewall"] as const; +const VALID_THEMES = [ + "system", + "default", + "default-dark", + "midnight", + "paper", + "whitewall", + "solarized-light", + "solarized-dark", +] as const; export type Theme = (typeof VALID_THEMES)[number]; export type ResolvedTheme = Exclude; @@ -25,6 +36,8 @@ const THEME_CONTENT: Record = { "default-dark": defaultDarkThemeContent, midnight: midnightThemeContent, paper: paperThemeContent, + "solarized-light": solarizedLightThemeContent, + "solarized-dark": solarizedDarkThemeContent, whitewall: whitewallThemeContent, }; @@ -35,6 +48,8 @@ export const THEME_OPTIONS: ThemeOption[] = [ { value: "midnight", label: "Midnight" }, { value: "paper", label: "Paper" }, { value: "whitewall", label: "Whitewall" }, + { value: "solarized-light", label: "Solarized Light" }, + { value: "solarized-dark", label: "Solarized Dark" }, ]; // ============================================================================