diff --git a/desktop/src/features/settings/ui/SettingsPanels.tsx b/desktop/src/features/settings/ui/SettingsPanels.tsx index 00dfa437d3..200aab20d3 100644 --- a/desktop/src/features/settings/ui/SettingsPanels.tsx +++ b/desktop/src/features/settings/ui/SettingsPanels.tsx @@ -359,6 +359,8 @@ function HorizontalScrollWithFade({ ); } +type AppearanceMode = "system" | "light" | "dark"; + function ThemeSettingsCard() { const { setTheme, @@ -366,44 +368,63 @@ function ThemeSettingsCard() { isDark, accentColor, setAccentColor, + followSystem, setFollowSystem, } = useTheme(); const previewVarsByTheme = useThemePreviewVars(); const { pairedLight, lightOnly, darkOnly } = useThemeCategories(); + // Determine the active mode from current state + const activeMode: AppearanceMode = followSystem + ? "system" + : isDark + ? "dark" + : "light"; + + const [selectedMode, setSelectedMode] = useState(activeMode); + const [expanded, setExpanded] = useState(false); + const getVars = (name: SyntaxThemeName) => withAccentPreviewVars( previewVarsByTheme[name] ?? getThemeFallbackPreviewVars(name), accentColor, ); + // All light themes (paired light + light-only) + const allLightThemes = useMemo( + () => [...pairedLight, ...lightOnly], + [pairedLight, lightOnly], + ); + + // All dark themes (paired dark + dark-only) + const allDarkThemes = useMemo(() => { + const pairedDark = pairedLight + .map((l) => getThemePair(l)) + .filter(Boolean) as SyntaxThemeName[]; + return [...pairedDark, ...darkOnly]; + }, [pairedLight, darkOnly]); + + const handleModeSelect = (mode: AppearanceMode) => { + setSelectedMode(mode); + setExpanded(false); + }; + + const handleSelectTheme = (name: SyntaxThemeName) => { + setTheme(name); + if (selectedMode === "system") { + setFollowSystem(true); + } else { + setFollowSystem(false); + } + }; + /** Check if a paired theme (by its light member) is the active selection */ const isPairActive = (lightName: SyntaxThemeName) => { const darkName = getThemePair(lightName); return selectedThemeName === lightName || selectedThemeName === darkName; }; - const handleSelectPair = (lightName: SyntaxThemeName) => { - // Selecting a paired theme auto-enables follow-system - setTheme(lightName); - setFollowSystem(true); - }; - - const handleSelectSingle = (name: SyntaxThemeName) => { - setTheme(name); - // Single themes don't follow system — they're always one mode - setFollowSystem(false); - }; - - const [expandedSections, setExpandedSections] = useState< - Record - >({}); - - const toggleSection = (key: string) => { - setExpandedSections((prev) => ({ ...prev, [key]: !prev[key] })); - }; - return (
- {/* Section 1: Paired themes (follow system) */} + {/* Mode selector: System / Light / Dark */} +
+ {( + [ + { mode: "system" as const, label: "System" }, + { mode: "light" as const, label: "Light" }, + { mode: "dark" as const, label: "Dark" }, + ] as const + ).map(({ mode, label }) => ( + + ))} +
+ + {/* Theme list based on selected mode */}
-

- Adapts to system -

- {pairedLight.length > 4 && ( +

Themes

+ {((selectedMode === "system" && pairedLight.length > 4) || + (selectedMode === "light" && allLightThemes.length > 4) || + (selectedMode === "dark" && allDarkThemes.length > 4)) && ( )}
- - {pairedLight.map((lightName) => { - const darkName = getThemePair(lightName); - if (!darkName) return null; - return ( - handleSelectPair(lightName)} + + + {selectedMode === "system" && + pairedLight.map((lightName) => { + const darkName = getThemePair(lightName); + if (!darkName) return null; + return ( + handleSelectTheme(lightName)} + /> + ); + })} + {selectedMode === "light" && + allLightThemes.map((name) => ( + handleSelectTheme(name)} + vars={getVars(name)} /> - ); - })} + ))} + {selectedMode === "dark" && + allDarkThemes.map((name) => ( + handleSelectTheme(name)} + vars={getVars(name)} + /> + ))}
- {/* Section 2: Light-only themes */} - {lightOnly.length > 0 && ( -
-
-

Light

- {lightOnly.length > 4 && ( - - )} -
- - {lightOnly.map((name) => ( - handleSelectSingle(name)} - vars={getVars(name)} - /> - ))} - -
- )} - - {/* Section 3: Dark-only themes */} - {darkOnly.length > 0 && ( -
-
-

Dark

- {darkOnly.length > 4 && ( - - )} -
- - {darkOnly.map((name) => ( - handleSelectSingle(name)} - vars={getVars(name)} - /> - ))} - -
- )} - {/* Accent color picker */}

Accent color