From 0466e78233b4a3e64278fa8536b58eb280573888 Mon Sep 17 00:00:00 2001 From: heaven Date: Sat, 25 Apr 2026 13:36:40 +0300 Subject: [PATCH] Simplify theme settings to a single dropdown with System, Light, and Dark options. --- public/locales/en.json | 8 +- public/locales/ru.json | 8 +- src/app/features/settings/general/General.tsx | 235 ++++-------------- src/app/hooks/useTheme.ts | 39 +-- src/app/state/settings.ts | 4 - src/colors.css.ts | 137 ---------- src/index.css | 3 +- 7 files changed, 62 insertions(+), 372 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index 78183faf..706d15e0 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -92,12 +92,10 @@ "general_title": "General", "appearance": "Appearance", - "system_theme": "System Theme", - "system_theme_desc": "Choose between light and dark theme based on system preference.", - "light_theme": "Light Theme:", - "dark_theme": "Dark Theme:", + "system_theme": "System", + "theme_light": "Light", + "theme_dark": "Dark", "theme": "Theme", - "theme_desc": "Theme to use when system theme is not enabled.", "monochrome_mode": "Monochrome Mode", "twitter_emoji": "Twitter Emoji", "page_zoom": "Page Zoom", diff --git a/public/locales/ru.json b/public/locales/ru.json index 759dcd33..f378479f 100644 --- a/public/locales/ru.json +++ b/public/locales/ru.json @@ -92,12 +92,10 @@ "general_title": "Общие", "appearance": "Внешний вид", - "system_theme": "Системная тема", - "system_theme_desc": "Выбор между светлой и тёмной темой на основе системных настроек.", - "light_theme": "Светлая тема:", - "dark_theme": "Тёмная тема:", + "system_theme": "Системная", + "theme_light": "Светлая", + "theme_dark": "Тёмная", "theme": "Тема", - "theme_desc": "Тема при отключённой системной теме.", "monochrome_mode": "Монохромный режим", "twitter_emoji": "Эмодзи Twitter", "page_zoom": "Масштаб страницы", diff --git a/src/app/features/settings/general/General.tsx b/src/app/features/settings/general/General.tsx index a775f605..5c5a547c 100644 --- a/src/app/features/settings/general/General.tsx +++ b/src/app/features/settings/general/General.tsx @@ -11,7 +11,6 @@ import { as, Box, Button, - Chip, config, Header, Icon, @@ -40,10 +39,6 @@ import { isMacOS } from '../../../utils/user-agent'; import { DarkTheme, LightTheme, - Theme, - ThemeKind, - useSystemThemeKind, - useThemeNames, useThemes, } from '../../../hooks/useTheme'; import { stopPropagation } from '../../../utils/keyboard'; @@ -52,48 +47,42 @@ import { useMessageSpacingItems } from '../../../hooks/useMessageSpacing'; import { useDateFormatItems } from '../../../hooks/useDateFormat'; import { SequenceCardStyle } from '../styles.css'; -type ThemeSelectorProps = { - themeNames: Record; - themes: Theme[]; - selected: Theme; - onSelect: (theme: Theme) => void; -}; -const ThemeSelector = as<'div', ThemeSelectorProps>( - ({ themeNames, themes, selected, onSelect, ...props }, ref) => ( - - - {themes.map((theme) => ( - onSelect(theme)} - > - {themeNames[theme.id] ?? theme.id} - - ))} - - - ) -); +const SYSTEM_THEME_ID = 'system'; -function SelectTheme({ disabled }: { disabled?: boolean }) { +const THEME_I18N_KEYS: Record = { + [LightTheme.id]: 'Settings.theme_light', + [DarkTheme.id]: 'Settings.theme_dark', +}; + +function ThemeSelect() { + const { t } = useTranslation(); const themes = useThemes(); - const themeNames = useThemeNames(); + const [systemTheme, setSystemTheme] = useSetting(settingsAtom, 'useSystemTheme'); const [themeId, setThemeId] = useSetting(settingsAtom, 'themeId'); const [menuCords, setMenuCords] = useState(); - const selectedTheme = themes.find((theme) => theme.id === themeId) ?? LightTheme; - const handleThemeMenu: MouseEventHandler = (evt) => { + const themeName = (id: string) => t(THEME_I18N_KEYS[id] ?? id); + + const currentLabel = systemTheme + ? t('Settings.system_theme') + : themeName(themeId ?? LightTheme.id); + + const handleOpenMenu: MouseEventHandler = (evt) => { setMenuCords(evt.currentTarget.getBoundingClientRect()); }; - const handleThemeSelect = (theme: Theme) => { - setThemeId(theme.id); + const handleSelect = (id: string) => { + if (id === SYSTEM_THEME_ID) { + setSystemTheme(true); + } else { + setSystemTheme(false); + setThemeId(id); + } setMenuCords(undefined); }; + const selectedId = systemTheme ? SYSTEM_THEME_ID : (themeId ?? LightTheme.id); + return ( <> - + + + handleSelect(SYSTEM_THEME_ID)} + > + {t('Settings.system_theme')} + + {themes.map((theme) => ( + handleSelect(theme.id)} + > + {themeName(theme.id)} + + ))} + + } /> @@ -139,128 +144,6 @@ function SelectTheme({ disabled }: { disabled?: boolean }) { ); } -function SystemThemePreferences() { - const { t } = useTranslation(); - const themeKind = useSystemThemeKind(); - const themeNames = useThemeNames(); - const themes = useThemes(); - const [lightThemeId, setLightThemeId] = useSetting(settingsAtom, 'lightThemeId'); - const [darkThemeId, setDarkThemeId] = useSetting(settingsAtom, 'darkThemeId'); - - const lightThemes = themes.filter((theme) => theme.kind === ThemeKind.Light); - const darkThemes = themes.filter((theme) => theme.kind === ThemeKind.Dark); - - const selectedLightTheme = lightThemes.find((theme) => theme.id === lightThemeId) ?? LightTheme; - const selectedDarkTheme = darkThemes.find((theme) => theme.id === darkThemeId) ?? DarkTheme; - - const [ltCords, setLTCords] = useState(); - const [dtCords, setDTCords] = useState(); - - const handleLightThemeMenu: MouseEventHandler = (evt) => { - setLTCords(evt.currentTarget.getBoundingClientRect()); - }; - const handleDarkThemeMenu: MouseEventHandler = (evt) => { - setDTCords(evt.currentTarget.getBoundingClientRect()); - }; - - const handleLightThemeSelect = (theme: Theme) => { - setLightThemeId(theme.id); - setLTCords(undefined); - }; - - const handleDarkThemeSelect = (theme: Theme) => { - setDarkThemeId(theme.id); - setDTCords(undefined); - }; - - return ( - - } - onClick={handleLightThemeMenu} - > - {themeNames[selectedLightTheme.id] ?? selectedLightTheme.id} - - } - /> - setLTCords(undefined), - clickOutsideDeactivates: true, - isKeyForward: (evt: KeyboardEvent) => - evt.key === 'ArrowDown' || evt.key === 'ArrowRight', - isKeyBackward: (evt: KeyboardEvent) => - evt.key === 'ArrowUp' || evt.key === 'ArrowLeft', - escapeDeactivates: stopPropagation, - }} - > - - - } - /> - } - onClick={handleDarkThemeMenu} - > - {themeNames[selectedDarkTheme.id] ?? selectedDarkTheme.id} - - } - /> - setDTCords(undefined), - clickOutsideDeactivates: true, - isKeyForward: (evt: KeyboardEvent) => - evt.key === 'ArrowDown' || evt.key === 'ArrowRight', - isKeyBackward: (evt: KeyboardEvent) => - evt.key === 'ArrowUp' || evt.key === 'ArrowLeft', - escapeDeactivates: stopPropagation, - }} - > - - - } - /> - - ); -} - function PageZoomInput() { const [pageZoom, setPageZoom] = useSetting(settingsAtom, 'pageZoom'); const [currentZoom, setCurrentZoom] = useState(`${pageZoom}`); @@ -307,32 +190,16 @@ function PageZoomInput() { function Appearance() { const { t } = useTranslation(); - const [systemTheme, setSystemTheme] = useSetting(settingsAtom, 'useSystemTheme'); const [monochromeMode, setMonochromeMode] = useSetting(settingsAtom, 'monochromeMode'); const [twitterEmoji, setTwitterEmoji] = useSetting(settingsAtom, 'twitterEmoji'); return ( {t('Settings.appearance')} - - } - /> - {systemTheme && } - - } + after={} /> diff --git a/src/app/hooks/useTheme.ts b/src/app/hooks/useTheme.ts index cdbb9dba..4eab41a1 100644 --- a/src/app/hooks/useTheme.ts +++ b/src/app/hooks/useTheme.ts @@ -1,7 +1,7 @@ import { lightTheme } from 'folds'; import { createContext, useContext, useEffect, useMemo, useState } from 'react'; import { onDarkFontWeight, onLightFontWeight } from '../../config.css'; -import { butterTheme, darkTheme, silverTheme } from '../../colors.css'; +import { darkTheme } from '../../colors.css'; import { settingsAtom } from '../state/settings'; import { useSetting } from '../state/hooks/settings'; @@ -22,39 +22,18 @@ export const LightTheme: Theme = { classNames: [lightTheme, onLightFontWeight, 'prism-light'], }; -export const SilverTheme: Theme = { - id: 'silver-theme', - kind: ThemeKind.Light, - classNames: ['silver-theme', silverTheme, onLightFontWeight, 'prism-light'], -}; export const DarkTheme: Theme = { id: 'dark-theme', kind: ThemeKind.Dark, classNames: ['dark-theme', darkTheme, onDarkFontWeight, 'prism-dark'], }; -export const ButterTheme: Theme = { - id: 'butter-theme', - kind: ThemeKind.Dark, - classNames: ['butter-theme', butterTheme, onDarkFontWeight, 'prism-dark'], -}; export const useThemes = (): Theme[] => { - const themes: Theme[] = useMemo(() => [LightTheme, SilverTheme, DarkTheme, ButterTheme], []); + const themes: Theme[] = useMemo(() => [LightTheme, DarkTheme], []); return themes; }; -export const useThemeNames = (): Record => - useMemo( - () => ({ - [LightTheme.id]: 'Light', - [SilverTheme.id]: 'Silver', - [DarkTheme.id]: 'Dark', - [ButterTheme.id]: 'Butter', - }), - [] - ); - export const useSystemThemeKind = (): ThemeKind => { const darkModeQueryList = useMemo(() => window.matchMedia('(prefers-color-scheme: dark)'), []); const [themeKind, setThemeKind] = useState( @@ -77,24 +56,14 @@ export const useSystemThemeKind = (): ThemeKind => { export const useActiveTheme = (): Theme => { const systemThemeKind = useSystemThemeKind(); - const themes = useThemes(); const [systemTheme] = useSetting(settingsAtom, 'useSystemTheme'); const [themeId] = useSetting(settingsAtom, 'themeId'); - const [lightThemeId] = useSetting(settingsAtom, 'lightThemeId'); - const [darkThemeId] = useSetting(settingsAtom, 'darkThemeId'); if (!systemTheme) { - const selectedTheme = themes.find((theme) => theme.id === themeId) ?? LightTheme; - - return selectedTheme; + return themeId === DarkTheme.id ? DarkTheme : LightTheme; } - const selectedTheme = - systemThemeKind === ThemeKind.Dark - ? themes.find((theme) => theme.id === darkThemeId) ?? DarkTheme - : themes.find((theme) => theme.id === lightThemeId) ?? LightTheme; - - return selectedTheme; + return systemThemeKind === ThemeKind.Dark ? DarkTheme : LightTheme; }; const ThemeContext = createContext(null); diff --git a/src/app/state/settings.ts b/src/app/state/settings.ts index 3503762e..08ee294b 100644 --- a/src/app/state/settings.ts +++ b/src/app/state/settings.ts @@ -18,8 +18,6 @@ export enum MessageLayout { export interface Settings { themeId?: string; useSystemTheme: boolean; - lightThemeId?: string; - darkThemeId?: string; monochromeMode?: boolean; isMarkdown: boolean; editorToolbar: boolean; @@ -51,8 +49,6 @@ export interface Settings { const defaultSettings: Settings = { themeId: undefined, useSystemTheme: true, - lightThemeId: undefined, - darkThemeId: undefined, monochromeMode: false, isMarkdown: true, editorToolbar: false, diff --git a/src/colors.css.ts b/src/colors.css.ts index e03e5a66..c3a23cdd 100644 --- a/src/colors.css.ts +++ b/src/colors.css.ts @@ -5,103 +5,6 @@ import { color } from 'folds'; const navDark = '#121314'; // левая панель (навигация) const contentDark = '#0d0d0e'; // правая часть (контент/чат) -export const silverTheme = createTheme(color, { - Background: { - Container: '#DEDEDE', - ContainerHover: '#D3D3D3', - ContainerActive: '#C7C7C7', - ContainerLine: '#BBBBBB', - OnContainer: '#000000', - }, - - Surface: { - Container: '#EAEAEA', - ContainerHover: '#DEDEDE', - ContainerActive: '#D3D3D3', - ContainerLine: '#C7C7C7', - OnContainer: '#000000', - }, - - SurfaceVariant: { - Container: '#DEDEDE', - ContainerHover: '#D3D3D3', - ContainerActive: '#C7C7C7', - ContainerLine: '#BBBBBB', - OnContainer: '#000000', - }, - - Primary: { - Main: '#1245A8', - MainHover: '#103E97', - MainActive: '#0F3B8F', - MainLine: '#0E3786', - OnMain: '#FFFFFF', - Container: '#C4D0E9', - ContainerHover: '#B8C7E5', - ContainerActive: '#ACBEE1', - ContainerLine: '#A0B5DC', - OnContainer: '#0D3076', - }, - - Secondary: { - Main: '#000000', - MainHover: '#171717', - MainActive: '#232323', - MainLine: '#2F2F2F', - OnMain: '#EAEAEA', - Container: '#C7C7C7', - ContainerHover: '#BBBBBB', - ContainerActive: '#AFAFAF', - ContainerLine: '#A4A4A4', - OnContainer: '#0C0C0C', - }, - - Success: { - Main: '#017343', - MainHover: '#01683C', - MainActive: '#016239', - MainLine: '#015C36', - OnMain: '#FFFFFF', - Container: '#BFDCD0', - ContainerHover: '#B3D5C7', - ContainerActive: '#A6CEBD', - ContainerLine: '#99C7B4', - OnContainer: '#01512F', - }, - - Warning: { - Main: '#864300', - MainHover: '#793C00', - MainActive: '#723900', - MainLine: '#6B3600', - OnMain: '#FFFFFF', - Container: '#E1D0BF', - ContainerHover: '#DBC7B2', - ContainerActive: '#D5BDA6', - ContainerLine: '#CFB499', - OnContainer: '#5E2F00', - }, - - Critical: { - Main: '#9D0F0F', - MainHover: '#8D0E0E', - MainActive: '#850D0D', - MainLine: '#7E0C0C', - OnMain: '#FFFFFF', - Container: '#E7C3C3', - ContainerHover: '#E2B7B7', - ContainerActive: '#DDABAB', - ContainerLine: '#D89F9F', - OnContainer: '#6E0B0B', - }, - - Other: { - FocusRing: 'rgba(0 0 0 / 50%)', - Shadow: 'rgba(0 0 0 / 20%)', - Overlay: 'rgba(0 0 0 / 50%)', - }, -}); - const darkThemeData = { Background: { Container: navDark, @@ -200,43 +103,3 @@ const darkThemeData = { }; export const darkTheme = createTheme(color, darkThemeData); - -export const butterTheme = createTheme(color, { - ...darkThemeData, - Background: { - Container: '#1A1916', - ContainerHover: '#262621', - ContainerActive: '#33322C', - ContainerLine: '#403F38', - OnContainer: '#FFFBDE', - }, - - Surface: { - Container: '#262621', - ContainerHover: '#33322C', - ContainerActive: '#403F38', - ContainerLine: '#4D4B43', - OnContainer: '#FFFBDE', - }, - - SurfaceVariant: { - Container: '#33322C', - ContainerHover: '#403F38', - ContainerActive: '#4D4B43', - ContainerLine: '#59584E', - OnContainer: '#FFFBDE', - }, - - Secondary: { - Main: '#FFFBDE', - MainHover: '#E5E2C8', - MainActive: '#D9D5BD', - MainLine: '#CCC9B2', - OnMain: '#1A1916', - Container: '#403F38', - ContainerHover: '#4D4B43', - ContainerActive: '#59584E', - ContainerLine: '#666459', - OnContainer: '#F2EED3', - }, -}); diff --git a/src/index.css b/src/index.css index cedeaff7..c5f6a6f9 100644 --- a/src/index.css +++ b/src/index.css @@ -22,8 +22,7 @@ --font-secondary: 'InterVariable', var(--font-emoji), sans-serif; } -.dark-theme, -.butter-theme { +.dark-theme { --tc-link: hsl(213deg 100% 80%); --mx-uc-1: hsl(208, 100%, 75%);