vignette/apps/web/src/lib/theme.ts
2026-07-15 21:31:30 +09:00

42 lines
1.5 KiB
TypeScript

export type AppTheme = "light" | "dark";
export const THEME_KEY = "vignette.theme";
const themeListeners = new Set<() => void>();
export function readInitialTheme(): AppTheme {
try {
const saved = localStorage.getItem(THEME_KEY);
if (saved === "light" || saved === "dark") return saved;
} catch {
/* localStorage 접근 불가 환경에서는 기본값 사용 */
}
// 이번 시각 검증은 어두운 훈련 화면을 기본 표면으로 삼는다. 사용자가 바꾸면 저장값을 우선한다.
return "dark";
}
export function applyTheme(theme: AppTheme) {
const changed = document.documentElement.getAttribute("data-theme") !== theme;
document.documentElement.setAttribute("data-theme", theme);
try {
localStorage.setItem(THEME_KEY, theme);
} catch {
/* 저장 실패는 렌더링을 막지 않는다. */
}
if (changed) themeListeners.forEach((listener) => listener());
}
/** React와 비 React 진입점이 함께 쓰는 현재 적용 테마 snapshot. */
export function readAppliedTheme(): AppTheme {
const applied = document.documentElement.getAttribute("data-theme");
return applied === "light" || applied === "dark" ? applied : readInitialTheme();
}
/** 앱 전역 테마 store 구독. 테마 상태의 유일한 알림 경로다. */
export function subscribeTheme(listener: () => void) {
themeListeners.add(listener);
return () => themeListeners.delete(listener);
}
export function initTheme() {
applyTheme(readInitialTheme());
}