- useTheme: localStorage 기반 다크/라이트 테마 전환 - data-theme 속성으로 CSS 변수 자동 전환 - Topbar에 Light/Dark 토글 버튼 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
48 lines
1.0 KiB
TypeScript
48 lines
1.0 KiB
TypeScript
import { useState, useEffect, useCallback } from 'react';
|
|
|
|
type Theme = 'dark' | 'light';
|
|
|
|
const STORAGE_KEY = 'wing:theme';
|
|
const DEFAULT_THEME: Theme = 'dark';
|
|
|
|
function readTheme(): Theme {
|
|
try {
|
|
const raw = localStorage.getItem(STORAGE_KEY);
|
|
if (raw === 'light' || raw === 'dark') return raw;
|
|
} catch {
|
|
// storage unavailable
|
|
}
|
|
return DEFAULT_THEME;
|
|
}
|
|
|
|
function applyTheme(theme: Theme) {
|
|
document.documentElement.dataset.theme = theme;
|
|
}
|
|
|
|
export function useTheme() {
|
|
const [theme, setThemeState] = useState<Theme>(() => {
|
|
const t = readTheme();
|
|
applyTheme(t);
|
|
return t;
|
|
});
|
|
|
|
useEffect(() => {
|
|
applyTheme(theme);
|
|
}, [theme]);
|
|
|
|
const setTheme = useCallback((t: Theme) => {
|
|
setThemeState(t);
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY, t);
|
|
} catch {
|
|
// quota exceeded or unavailable
|
|
}
|
|
}, []);
|
|
|
|
const toggleTheme = useCallback(() => {
|
|
setTheme(theme === 'dark' ? 'light' : 'dark');
|
|
}, [theme, setTheme]);
|
|
|
|
return { theme, setTheme, toggleTheme } as const;
|
|
}
|