// — a single header icon-button + popover holding the // client-local overrides swarm-ui currently has (theme, reduced // motion). Mirrors `LinksMenu`'s shape (icon button, popover, close on // outside-click/Escape) — same "quiet chrome, not another nav item" // affordance, not a coincidence: the operator, when asked how big this // settings surface should be, said "small thing somewhere" since there // are only two overrides right now — a full `/settings` route + nav // entry would be over-building for two selects. Grows into a real page // only if the setting count outgrows a popover; nothing here assumes // it can't. import { useEffect, useRef, useState } from 'preact/hooks'; import { useThemeOverride, type ThemeOverride } from '../lib/theme-apply.js'; import { useMotionOverride, type MotionOverride } from '../lib/motion-apply.js'; import './SettingsMenu.css'; const THEME_OPTIONS: ThemeOverride[] = ['system', 'light', 'dark']; const MOTION_OPTIONS: MotionOverride[] = ['system', 'allow', 'reduce']; export function SettingsMenu() { const [open, setOpen] = useState(false); const rootRef = useRef(null); const [theme, setTheme] = useThemeOverride(); const [motion, setMotion] = useMotionOverride(); // Close on an outside click or Escape — only listens while open, same // pattern (and same rationale) as `LinksMenu`. useEffect(() => { if (!open) return; function onPointerDown(e: MouseEvent) { if (rootRef.current && !rootRef.current.contains(e.target as Node)) setOpen(false); } function onKeyDown(e: KeyboardEvent) { if (e.key === 'Escape') setOpen(false); } document.addEventListener('pointerdown', onPointerDown); document.addEventListener('keydown', onKeyDown); return () => { document.removeEventListener('pointerdown', onPointerDown); document.removeEventListener('keydown', onKeyDown); }; }, [open]); return (
{open ? ( ) : null}
); }