60 lines
1.6 KiB
TypeScript
60 lines
1.6 KiB
TypeScript
|
|
import { useCallback, useEffect, useState } from 'react';
|
||
|
|
|
||
|
|
export type Theme = 'light' | 'dark' | 'system';
|
||
|
|
|
||
|
|
const STORAGE_KEY = 'mimic-theme';
|
||
|
|
|
||
|
|
function resolveTheme(theme: Theme): 'light' | 'dark' {
|
||
|
|
if (theme === 'system') {
|
||
|
|
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||
|
|
}
|
||
|
|
return theme;
|
||
|
|
}
|
||
|
|
|
||
|
|
function applyTheme(theme: Theme) {
|
||
|
|
const resolved = resolveTheme(theme);
|
||
|
|
document.documentElement.classList.toggle('dark', resolved === 'dark');
|
||
|
|
}
|
||
|
|
|
||
|
|
function readStoredTheme(): Theme {
|
||
|
|
try {
|
||
|
|
const stored = localStorage.getItem(STORAGE_KEY);
|
||
|
|
if (stored === 'light' || stored === 'dark' || stored === 'system') return stored;
|
||
|
|
} catch {
|
||
|
|
// localStorage unavailable
|
||
|
|
}
|
||
|
|
return 'system';
|
||
|
|
}
|
||
|
|
|
||
|
|
export function useTheme() {
|
||
|
|
const [theme, setThemeState] = useState<Theme>(readStoredTheme);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
applyTheme(theme);
|
||
|
|
}, [theme]);
|
||
|
|
|
||
|
|
// Track system preference changes when theme === 'system'
|
||
|
|
useEffect(() => {
|
||
|
|
if (theme !== 'system') return;
|
||
|
|
const mq = window.matchMedia('(prefers-color-scheme: dark)');
|
||
|
|
const handler = () => applyTheme('system');
|
||
|
|
mq.addEventListener('change', handler);
|
||
|
|
return () => mq.removeEventListener('change', handler);
|
||
|
|
}, [theme]);
|
||
|
|
|
||
|
|
const setTheme = useCallback((next: Theme) => {
|
||
|
|
try {
|
||
|
|
localStorage.setItem(STORAGE_KEY, next);
|
||
|
|
} catch {
|
||
|
|
// ignore
|
||
|
|
}
|
||
|
|
setThemeState(next);
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
const cycleTheme = useCallback(() => {
|
||
|
|
setTheme(theme === 'light' ? 'dark' : theme === 'dark' ? 'system' : 'light');
|
||
|
|
}, [theme, setTheme]);
|
||
|
|
|
||
|
|
return { theme, setTheme, cycleTheme };
|
||
|
|
}
|