mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 22:06:38 +08:00
108 lines
2.5 KiB
TypeScript
108 lines
2.5 KiB
TypeScript
'use client';
|
|
|
|
import {
|
|
createContext,
|
|
type ReactNode,
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useMemo,
|
|
useState,
|
|
} from 'react';
|
|
|
|
import {
|
|
isThemeMode,
|
|
resolveTheme,
|
|
THEME_MEDIA_QUERY,
|
|
THEME_STORAGE_KEY,
|
|
type ResolvedTheme,
|
|
type ThemeMode,
|
|
} from '@/lib/theme/theme';
|
|
|
|
interface ThemeContextValue {
|
|
themeMode: ThemeMode;
|
|
resolvedTheme: ResolvedTheme;
|
|
setThemeMode: (mode: ThemeMode) => void;
|
|
}
|
|
|
|
const ThemeContext = createContext<ThemeContextValue | null>(null);
|
|
|
|
interface ThemeProviderProps {
|
|
children: ReactNode;
|
|
}
|
|
|
|
function getSystemPreference() {
|
|
return window.matchMedia(THEME_MEDIA_QUERY).matches;
|
|
}
|
|
|
|
function applyTheme(mode: ThemeMode) {
|
|
const resolvedTheme = resolveTheme(mode, getSystemPreference());
|
|
const root = document.documentElement;
|
|
|
|
root.dataset.themeMode = mode;
|
|
root.dataset.theme = resolvedTheme;
|
|
root.style.colorScheme = resolvedTheme;
|
|
window.localStorage.setItem(THEME_STORAGE_KEY, mode);
|
|
|
|
return resolvedTheme;
|
|
}
|
|
|
|
export function ThemeProvider({ children }: ThemeProviderProps) {
|
|
const [themeMode, setThemeModeState] = useState<ThemeMode>('system');
|
|
const [resolvedTheme, setResolvedTheme] = useState<ResolvedTheme>('dark');
|
|
|
|
useEffect(() => {
|
|
const root = document.documentElement;
|
|
const domThemeMode = root.dataset.themeMode;
|
|
const nextThemeMode = isThemeMode(domThemeMode) ? domThemeMode : 'system';
|
|
const nextResolvedTheme = applyTheme(nextThemeMode);
|
|
|
|
setThemeModeState(nextThemeMode);
|
|
setResolvedTheme(nextResolvedTheme);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const mediaQuery = window.matchMedia(THEME_MEDIA_QUERY);
|
|
|
|
const handleChange = () => {
|
|
if (themeMode !== 'system') {
|
|
return;
|
|
}
|
|
|
|
setResolvedTheme(applyTheme('system'));
|
|
};
|
|
|
|
mediaQuery.addEventListener('change', handleChange);
|
|
|
|
return () => {
|
|
mediaQuery.removeEventListener('change', handleChange);
|
|
};
|
|
}, [themeMode]);
|
|
|
|
const setThemeMode = useCallback((mode: ThemeMode) => {
|
|
setThemeModeState(mode);
|
|
setResolvedTheme(applyTheme(mode));
|
|
}, []);
|
|
|
|
const value = useMemo(
|
|
() => ({
|
|
themeMode,
|
|
resolvedTheme,
|
|
setThemeMode,
|
|
}),
|
|
[resolvedTheme, setThemeMode, themeMode],
|
|
);
|
|
|
|
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
|
|
}
|
|
|
|
export function useTheme() {
|
|
const context = useContext(ThemeContext);
|
|
|
|
if (!context) {
|
|
throw new Error('useTheme must be used within ThemeProvider');
|
|
}
|
|
|
|
return context;
|
|
}
|