Files
OpenFlare/atsf_server/web/components/providers/theme-provider.tsx
T

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;
}