optimize theme logic

This commit is contained in:
Km.Van
2026-07-10 22:57:30 +08:00
parent 6a482603b0
commit ee3bbc55fd
2 changed files with 85 additions and 47 deletions
+8 -16
View File
@@ -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);
}
+77 -31
View File
@@ -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<string>(WindowConfig.THEME);
const handleActiveTheme = useCallback(
(e: React.MouseEvent<HTMLButtonElement>) => {
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<string>(WindowConfig.THEME || "system");
const [systemTheme, setSystemTheme] = useState<string>(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<HTMLButtonElement>) => {
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 (
<div className={styles.main}>
{themes.map(({ id, name, color }) => (
<button
className={styles.button}
data-active={id === theme || undefined}
data-id={id}
data-is-dark={!isBrightOklch(color) || undefined}
key={id}
onClick={handleActiveTheme}
style={{
"--x-theme-button-bg": toOklchString(color),
} as React.CSSProperties}
title={name}
type="button"
>
{name}
</button>
))}
{themes.map(({ id, name, color }) => {
// 如果你的 themes 列表中包含一个 id 为 "system" 的按钮,
// 那么 isActive 直接对比 id === theme 即可。
// 如果这里只展示具体颜色按钮,则对比当前真正生效的 active 属性:
const isActive = id === theme ||
(theme === "system" && id === currentActiveId);
return (
<button
className={styles.button}
data-active={isActive || undefined}
data-id={id}
data-is-dark={!isBrightOklch(color) || undefined}
key={id}
onClick={handleActiveTheme}
style={{
"--x-theme-button-bg": toOklchString(color),
} as CSSProperties}
title={name}
type="button"
>
{name}
</button>
);
})}
</div>
);
};