64 lines
1.7 KiB
TypeScript
64 lines
1.7 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useEffect, useState } from "react";
|
|
|
|
export type ThemeMode = "light" | "dark";
|
|
export type AccentColor = "sky" | "indigo" | "violet" | "emerald" | "rose" | "amber";
|
|
|
|
export interface ThemePreferences {
|
|
mode: ThemeMode;
|
|
accent: AccentColor;
|
|
}
|
|
|
|
const STORAGE_KEY = "planarchy_theme";
|
|
const DEFAULT: ThemePreferences = { mode: "light", accent: "sky" };
|
|
|
|
function readStorage(): ThemePreferences {
|
|
if (typeof window === "undefined") return DEFAULT;
|
|
try {
|
|
const raw = localStorage.getItem(STORAGE_KEY);
|
|
if (!raw) return DEFAULT;
|
|
return { ...DEFAULT, ...(JSON.parse(raw) as Partial<ThemePreferences>) };
|
|
} catch {
|
|
return DEFAULT;
|
|
}
|
|
}
|
|
|
|
function applyTheme(prefs: ThemePreferences) {
|
|
const html = document.documentElement;
|
|
if (prefs.mode === "dark") html.classList.add("dark");
|
|
else html.classList.remove("dark");
|
|
html.setAttribute("data-accent", prefs.accent);
|
|
}
|
|
|
|
export function useTheme() {
|
|
const [prefs, setPrefs] = useState<ThemePreferences>(DEFAULT);
|
|
|
|
// Read from storage on mount
|
|
useEffect(() => {
|
|
const stored = readStorage();
|
|
setPrefs(stored);
|
|
applyTheme(stored);
|
|
}, []);
|
|
|
|
const setMode = useCallback((mode: ThemeMode) => {
|
|
setPrefs((prev) => {
|
|
const next = { ...prev, mode };
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
|
|
applyTheme(next);
|
|
return next;
|
|
});
|
|
}, []);
|
|
|
|
const setAccent = useCallback((accent: AccentColor) => {
|
|
setPrefs((prev) => {
|
|
const next = { ...prev, accent };
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
|
|
applyTheme(next);
|
|
return next;
|
|
});
|
|
}, []);
|
|
|
|
return { prefs, setMode, setAccent };
|
|
}
|