58 lines
1.6 KiB
TypeScript
58 lines
1.6 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect, useCallback } from 'react';
|
|
|
|
type Theme = 'light' | 'dark' | 'system';
|
|
|
|
function getSystemTheme(): 'light' | 'dark' {
|
|
if (typeof window === 'undefined') return 'light';
|
|
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
}
|
|
|
|
function applyTheme(theme: Theme) {
|
|
const resolved = theme === 'system' ? getSystemTheme() : theme;
|
|
const root = document.documentElement;
|
|
if (resolved === 'dark') {
|
|
root.classList.add('dark');
|
|
} else {
|
|
root.classList.remove('dark');
|
|
}
|
|
}
|
|
|
|
export function useTheme() {
|
|
const [theme, setThemeState] = useState<Theme>('system');
|
|
|
|
// Read from localStorage on mount
|
|
useEffect(() => {
|
|
const stored = localStorage.getItem('simo-theme') as Theme | null;
|
|
const initial = stored ?? 'system';
|
|
setThemeState(initial);
|
|
applyTheme(initial);
|
|
}, []);
|
|
|
|
// Listen for system theme changes when in 'system' mode
|
|
useEffect(() => {
|
|
const mq = window.matchMedia('(prefers-color-scheme: dark)');
|
|
const handler = () => {
|
|
if (theme === 'system') applyTheme('system');
|
|
};
|
|
mq.addEventListener('change', handler);
|
|
return () => mq.removeEventListener('change', handler);
|
|
}, [theme]);
|
|
|
|
const setTheme = useCallback((t: Theme) => {
|
|
setThemeState(t);
|
|
localStorage.setItem('simo-theme', t);
|
|
applyTheme(t);
|
|
}, []);
|
|
|
|
const cycle = useCallback(() => {
|
|
const order: Theme[] = ['light', 'dark', 'system'];
|
|
const idx = order.indexOf(theme);
|
|
const next = order[(idx + 1) % order.length];
|
|
setTheme(next);
|
|
}, [theme, setTheme]);
|
|
|
|
return { theme, setTheme, cycle };
|
|
}
|