diff --git a/dev/index.html b/dev/index.html index e73afd4..f53dd68 100644 --- a/dev/index.html +++ b/dev/index.html @@ -14,24 +14,16 @@ THEME: "", }; try { - // 1. 尝试读取用户存储的偏好 - const storedTheme = localStorage.getItem("x-theme"); - let theme = storedTheme; // 如果有存储,直接用 - - // 2. 如果没有存储,则检测系统偏好 - if (!storedTheme) { - const prefersDark = window.matchMedia( - "(prefers-color-scheme: dark)" - ).matches; - theme = prefersDark ? "dark" : "light"; - window.THEME = theme; + const storedTheme = String(localStorage.getItem("x-theme:v1") ?? ""); + let theme = storedTheme; + if (storedTheme) { + window.GLOBAL_CONFIG.THEME = storedTheme; + } else { + theme = window.matchMedia("(prefers-color-scheme: dark)").matches + ? "dark" + : "light"; } - - // 3. 将主题应用到根元素 document.documentElement.setAttribute("data-theme", theme); - - // 4. (可选)如果想让后续 JS 也能读到,可以同步存入 localStorage,但建议让用户主动选择时才存,避免覆盖“未选择”状态 - // 如果不想存储系统偏好,则不执行 localStorage.setItem } catch (e) { console.error(e); } diff --git a/src/Components/Theme/components/index.tsx b/src/Components/Theme/components/index.tsx index 91a6b0b..af75cbf 100644 --- a/src/Components/Theme/components/index.tsx +++ b/src/Components/Theme/components/index.tsx @@ -1,4 +1,11 @@ -import { type FC, useCallback, useState } from "react"; +import { + type CSSProperties, + type FC, + type MouseEvent, + useCallback, + useEffect, + useState, +} from "react"; import { isBrightOklch, toOklchString, @@ -7,39 +14,78 @@ import { WindowConfig } from "@/Components/WindowConfig/components/index.ts"; import { themes } from "./constants.ts"; import styles from "./index.module.scss"; +const setThemeAttr = (id: string) => { + document.documentElement.setAttribute("data-theme", id); +}; + +const getSystemTheme = () => + window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; + export const Theme: FC = () => { - const [theme, setTheme] = useState(WindowConfig.THEME); - const handleActiveTheme = useCallback( - (e: React.MouseEvent) => { - const { id } = e.currentTarget.dataset; - if (!id) { - return; - } - setTheme(id); - document.documentElement.setAttribute("data-theme", id); - localStorage.setItem("x-theme", id); - }, - [], - ); + // 假设 WindowConfig.THEME 为空或 'system' 代表跟随系统 + const [theme, setTheme] = useState(WindowConfig.THEME || "system"); + const [systemTheme, setSystemTheme] = useState(getSystemTheme); + + // 1. 监听系统主题变化:保持其纯粹性,不依赖外部 theme 状态 + useEffect(() => { + const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); + const handler = (e: MediaQueryListEvent) => { + setSystemTheme(e.matches ? "dark" : "light"); + }; + + mediaQuery.addEventListener("change", handler); + return () => mediaQuery.removeEventListener("change", handler); + }, []); + + // 2. 统一处理 DOM 属性的副作用:当选择的主题或系统主题改变时触发 + useEffect(() => { + if (theme === "system" || !theme) { + setThemeAttr(systemTheme); + } else { + setThemeAttr(theme); + } + }, [theme, systemTheme]); + + const handleActiveTheme = useCallback((e: MouseEvent) => { + const { id } = e.currentTarget.dataset; + if (!id) { + return; + } + + setTheme(id); + localStorage.setItem("x-theme:v1", id); + }, []); + + // 计算当前真正生效的激活 ID + const currentActiveId = theme === "system" || !theme ? systemTheme : theme; + return (
- {themes.map(({ id, name, color }) => ( - - ))} + {themes.map(({ id, name, color }) => { + // 如果你的 themes 列表中包含一个 id 为 "system" 的按钮, + // 那么 isActive 直接对比 id === theme 即可。 + // 如果这里只展示具体颜色按钮,则对比当前真正生效的 active 属性: + const isActive = id === theme || + (theme === "system" && id === currentActiveId); + + return ( + + ); + })}
); };