42 lines
1.5 KiB
TypeScript
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());
|
|
}
|