import { useCallback, useEffect, useRef, useState } from "react"; import { useTheme, PRESETS } from "../themes/index.ts"; import type { HeadingFont } from "../themes/index.ts"; import { useI18n } from "../contexts/I18nContext"; export function ThemePicker() { const { config, preset, setPreset, setAccent, setHeadingFont } = useTheme(); const [open, setOpen] = useState(false); const ref = useRef(null); const { t } = useI18n(); // Close on outside click useEffect(() => { if (!open) return; function handleClick(e: MouseEvent) { if (ref.current && !ref.current.contains(e.target as Node)) { setOpen(false); } } document.addEventListener("mousedown", handleClick); return () => document.removeEventListener("mousedown", handleClick); }, [open]); // Close on Escape useEffect(() => { if (!open) return; function handleKey(e: KeyboardEvent) { if (e.key === "Escape") setOpen(false); } document.addEventListener("keydown", handleKey); return () => document.removeEventListener("keydown", handleKey); }, [open]); const handlePreset = useCallback( (id: string) => { setPreset(id as Parameters[0]); }, [setPreset], ); return (
{open && (
{/* Presets */}
{t.themePicker.theme}
{PRESETS.map((p) => ( ))}
{/* Accent swatches */}
{t.themePicker.accentColor}
{preset.accentSwatches.map((swatch) => (
{/* Heading font */}
{t.themePicker.headingFont}
{([ { id: "serif" as HeadingFont, label: t.themePicker.serif, sample: "Aa" }, { id: "sans" as HeadingFont, label: t.themePicker.sans, sample: "Aa" }, { id: "mono" as HeadingFont, label: t.themePicker.mono, sample: "Aa" }, ]).map((opt) => ( ))}
)}
); }