import { create } from 'zustand'; /** * 主题偏好。 * * 三态而不是「开/关」:`system` 是有意义的第三个值,不是 light 的别名。 * 只给开关的话,用户在白天设成浅色之后,晚上系统切深色时应用不会跟着变 —— * 而那恰恰是大多数人想要的默认行为。 */ export type ThemePref = 'light' | 'dark' | 'system'; /** 实际生效的主题(system 解析之后的结果)。 */ export type ResolvedTheme = 'light' | 'dark'; const STORAGE_KEY = 'agentmail.theme'; const DARK_QUERY = '(prefers-color-scheme: dark)'; function readStored(): ThemePref { try { const v = localStorage.getItem(STORAGE_KEY); if (v === 'light' || v === 'dark' || v === 'system') return v; } catch { // 隐私模式下 localStorage 抛异常。跟随系统是最安全的退路 —— // 硬编码 light 会让深色偏好的用户每次开页面都被闪一下白屏 } return 'system'; } function systemPrefersDark(): boolean { if (typeof window === 'undefined' || !window.matchMedia) return false; return window.matchMedia(DARK_QUERY).matches; } export function resolveTheme(pref: ThemePref): ResolvedTheme { if (pref === 'system') return systemPrefersDark() ? 'dark' : 'light'; return pref; } /** * 把主题写进 DOM。 * * 类名挂在 `` 而不是 `
`:tailwind 的 darkMode:'class' 默认 * 从根元素找,而且 `` 上的 background-color 才管得到 overscroll * 露出的那一片。 */ function apply(resolved: ResolvedTheme) { if (typeof document === 'undefined') return; const root = document.documentElement; root.classList.toggle('dark', resolved === 'dark'); // 让浏览器把滚动条、表单控件、autofill 背景一并切换。 // 不设的话深色页面上会出现一条浅色滚动条与白底的自动填充输入框。 root.style.colorScheme = resolved; } interface ThemeState { pref: ThemePref; resolved: ResolvedTheme; setPref: (p: ThemePref) => void; /** 在 light / dark 间直接翻转(顶栏那个按钮用)。 */ toggle: () => void; } export const useThemeStore = create