51 lines
1.5 KiB
TypeScript
51 lines
1.5 KiB
TypeScript
import { useState, useEffect, useCallback, type ReactNode } from 'react';
|
|
import {
|
|
DEFAULT_THEME_MODE,
|
|
THEME_STORAGE_KEY,
|
|
ThemeContext,
|
|
type ThemeMode,
|
|
} from './themeContext';
|
|
|
|
export type { ThemeMode } from './themeContext';
|
|
|
|
export function ThemeProvider({ children }: { children: ReactNode }) {
|
|
const [mode, setModeState] = useState<ThemeMode>(() => {
|
|
const saved = localStorage.getItem(THEME_STORAGE_KEY);
|
|
return (saved as ThemeMode) || DEFAULT_THEME_MODE;
|
|
});
|
|
|
|
// 监听系统主题变化
|
|
const [systemIsDark, setSystemIsDark] = useState(() =>
|
|
window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
);
|
|
|
|
useEffect(() => {
|
|
const mql = window.matchMedia('(prefers-color-scheme: dark)');
|
|
const handler = (e: MediaQueryListEvent) => setSystemIsDark(e.matches);
|
|
mql.addEventListener('change', handler);
|
|
return () => mql.removeEventListener('change', handler);
|
|
}, []);
|
|
|
|
const isDark = mode === 'dark' || (mode === 'system' && systemIsDark);
|
|
|
|
const setMode = useCallback((newMode: ThemeMode) => {
|
|
setModeState(newMode);
|
|
localStorage.setItem(THEME_STORAGE_KEY, newMode);
|
|
}, []);
|
|
|
|
const toggle = useCallback(() => {
|
|
setMode(isDark ? 'light' : 'dark');
|
|
}, [isDark, setMode]);
|
|
|
|
// 同步到 HTML root,方便 CSS 使用
|
|
useEffect(() => {
|
|
document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');
|
|
}, [isDark]);
|
|
|
|
return (
|
|
<ThemeContext.Provider value={{ mode, isDark, setMode, toggle }}>
|
|
{children}
|
|
</ThemeContext.Provider>
|
|
);
|
|
}
|