From 28dad7373f598368a71dcf4ca385b22cab00d561 Mon Sep 17 00:00:00 2001 From: codytseng Date: Sat, 18 Oct 2025 23:18:44 +0800 Subject: [PATCH] feat: support primary color customization --- src/components/Sidebar/index.tsx | 7 +- src/constants.ts | 239 ++++++++++++++++++ src/i18n/locales/ar.ts | 21 +- src/i18n/locales/de.ts | 21 +- src/i18n/locales/en.ts | 21 +- src/i18n/locales/es.ts | 21 +- src/i18n/locales/fa.ts | 21 +- src/i18n/locales/fr.ts | 21 +- src/i18n/locales/hi.ts | 21 +- src/i18n/locales/it.ts | 21 +- src/i18n/locales/ja.ts | 21 +- src/i18n/locales/ko.ts | 21 +- src/i18n/locales/pl.ts | 21 +- src/i18n/locales/pt-BR.ts | 21 +- src/i18n/locales/pt-PT.ts | 21 +- src/i18n/locales/ru.ts | 21 +- src/i18n/locales/th.ts | 21 +- src/i18n/locales/zh.ts | 21 +- .../AppearanceSettingsPage/index.tsx | 34 ++- src/providers/ThemeProvider.tsx | 55 ++-- src/services/local-storage.service.ts | 16 +- 21 files changed, 644 insertions(+), 43 deletions(-) diff --git a/src/components/Sidebar/index.tsx b/src/components/Sidebar/index.tsx index 77db53eb..5398153a 100644 --- a/src/components/Sidebar/index.tsx +++ b/src/components/Sidebar/index.tsx @@ -2,6 +2,7 @@ import Icon from '@/assets/Icon' import Logo from '@/assets/Logo' import { cn } from '@/lib/utils' import { useScreenSize } from '@/providers/ScreenSizeProvider' +import { useTheme } from '@/providers/ThemeProvider' import { useUserPreferences } from '@/providers/UserPreferencesProvider' import { ChevronsLeft, ChevronsRight } from 'lucide-react' import AccountButton from './AccountButton' @@ -15,6 +16,7 @@ import SettingsButton from './SettingsButton' export default function PrimaryPageSidebar() { const { isSmallScreen } = useScreenSize() + const { themeSetting } = useTheme() const { sidebarCollapse, updateSidebarCollapse } = useUserPreferences() if (isSmallScreen) return null @@ -46,7 +48,10 @@ export default function PrimaryPageSidebar() { + ))} + + ) diff --git a/src/providers/ThemeProvider.tsx b/src/providers/ThemeProvider.tsx index 6ee05fc8..76d121d1 100644 --- a/src/providers/ThemeProvider.tsx +++ b/src/providers/ThemeProvider.tsx @@ -1,36 +1,36 @@ +import { PRIMARY_COLORS, StorageKey, TPrimaryColor } from '@/constants' import storage from '@/services/local-storage.service' import { TTheme, TThemeSetting } from '@/types' import { createContext, useContext, useEffect, useState } from 'react' type ThemeProviderState = { themeSetting: TThemeSetting - setThemeSetting: (themeSetting: TThemeSetting) => Promise -} - -function getSystemTheme() { - return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' + setThemeSetting: (themeSetting: TThemeSetting) => void + primaryColor: TPrimaryColor + setPrimaryColor: (color: TPrimaryColor) => void } const ThemeProviderContext = createContext(undefined) +const updateCSSVariables = (color: TPrimaryColor, currentTheme: TTheme) => { + const root = window.document.documentElement + const colorConfig = PRIMARY_COLORS[color] ?? PRIMARY_COLORS.DEFAULT + + const config = currentTheme === 'light' ? colorConfig.light : colorConfig.dark + + root.style.setProperty('--primary', config.primary) + root.style.setProperty('--primary-hover', config['primary-hover']) + root.style.setProperty('--primary-foreground', config['primary-foreground']) +} + export function ThemeProvider({ children }: { children: React.ReactNode }) { const [themeSetting, setThemeSetting] = useState( - (localStorage.getItem('themeSetting') as TThemeSetting | null) ?? 'system' + (localStorage.getItem(StorageKey.THEME_SETTING) as TThemeSetting) ?? 'system' ) const [theme, setTheme] = useState('light') - - useEffect(() => { - const init = async () => { - const themeSetting = storage.getThemeSetting() - if (themeSetting === 'system') { - setTheme(getSystemTheme()) - return - } - setTheme(themeSetting) - } - - init() - }, []) + const [primaryColor, setPrimaryColor] = useState( + (localStorage.getItem(StorageKey.PRIMARY_COLOR) as TPrimaryColor) ?? 'DEFAULT' + ) useEffect(() => { if (themeSetting !== 'system') { @@ -65,16 +65,27 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) { updateTheme() }, [theme]) - const updateThemeSetting = async (themeSetting: TThemeSetting) => { + useEffect(() => { + updateCSSVariables(primaryColor, theme) + }, [theme, primaryColor]) + + const updateThemeSetting = (themeSetting: TThemeSetting) => { storage.setThemeSetting(themeSetting) setThemeSetting(themeSetting) } + const updatePrimaryColor = (color: TPrimaryColor) => { + storage.setPrimaryColor(color) + setPrimaryColor(color) + } + return ( {children} diff --git a/src/services/local-storage.service.ts b/src/services/local-storage.service.ts index 2522b1dc..58195fc7 100644 --- a/src/services/local-storage.service.ts +++ b/src/services/local-storage.service.ts @@ -4,7 +4,8 @@ import { MEDIA_AUTO_LOAD_POLICY, NOTIFICATION_LIST_STYLE, SUPPORTED_KINDS, - StorageKey + StorageKey, + TPrimaryColor } from '@/constants' import { isSameAccount } from '@/lib/account' import { randomString } from '@/lib/random' @@ -49,6 +50,7 @@ class LocalStorageService { private mediaAutoLoadPolicy: TMediaAutoLoadPolicy = MEDIA_AUTO_LOAD_POLICY.ALWAYS private shownCreateWalletGuideToastPubkeys: Set = new Set() private sidebarCollapse: boolean = false + private primaryColor: TPrimaryColor = 'DEFAULT' constructor() { if (!LocalStorageService.instance) { @@ -196,6 +198,9 @@ class LocalStorageService { this.sidebarCollapse = window.localStorage.getItem(StorageKey.SIDEBAR_COLLAPSE) === 'true' + this.primaryColor = + (window.localStorage.getItem(StorageKey.PRIMARY_COLOR) as TPrimaryColor) ?? 'DEFAULT' + // Clean up deprecated data window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP) window.localStorage.removeItem(StorageKey.ACCOUNT_FOLLOW_LIST_EVENT_MAP) @@ -488,6 +493,15 @@ class LocalStorageService { this.sidebarCollapse = collapse window.localStorage.setItem(StorageKey.SIDEBAR_COLLAPSE, collapse.toString()) } + + getPrimaryColor() { + return this.primaryColor + } + + setPrimaryColor(color: TPrimaryColor) { + this.primaryColor = color + window.localStorage.setItem(StorageKey.PRIMARY_COLOR, color) + } } const instance = new LocalStorageService()