import { useCallback, useEffect, useRef, useState } from "react"; import { Palette, Check } from "lucide-react"; import { BUILTIN_THEMES, useTheme } from "@/themes"; import { useI18n } from "@/i18n"; import { cn } from "@/lib/utils"; /** * Compact theme picker mounted next to the language switcher in the header. * Each dropdown row shows a 3-stop swatch (background / midground / warm * glow) so users can preview the palette before committing. User-defined * themes from `~/.hermes/dashboard-themes/*.yaml` that aren't in * `BUILTIN_THEMES` render without swatches and apply the default palette. */ export function ThemeSwitcher() { const { themeName, availableThemes, setTheme } = useTheme(); const { t } = useI18n(); const [open, setOpen] = useState(false); const wrapperRef = useRef(null); const close = useCallback(() => setOpen(false), []); useEffect(() => { if (!open) return; const onMouseDown = (e: MouseEvent) => { if (wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) { close(); } }; const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") close(); }; document.addEventListener("mousedown", onMouseDown); document.addEventListener("keydown", onKey); return () => { document.removeEventListener("mousedown", onMouseDown); document.removeEventListener("keydown", onKey); }; }, [open, close]); const current = availableThemes.find((th) => th.name === themeName); const label = current?.label ?? themeName; return (
{open && (
{t.theme?.title ?? "Theme"}
{availableThemes.map((th) => { const isActive = th.name === themeName; const preset = BUILTIN_THEMES[th.name]; return ( ); })}
)}
); } function ThemeSwatch({ theme }: { theme: string }) { const preset = BUILTIN_THEMES[theme]; if (!preset) return ; const { background, midground, warmGlow } = preset.palette; return (
); } function PlaceholderSwatch() { return (
); }