"use client"; import { useEffect, useRef, useState } from "react"; import { AnimatePresence, motion } from "motion/react"; import { Palette } from "../lib/tokens"; import { LANGS, Lang, t, useLang } from "../lib/i18n"; import { IconBtn } from "./ui"; import { Icon } from "./M3Node"; const EASE = [0.2, 0, 0, 1] as const; /** An icon button with a small menu anchored to it. `side` is where the menu opens. */ export function Popover({ p, icon, title, side = "down", size = 40, children, }: { p: Palette; icon: string; title: string; side?: "down" | "right"; size?: number; children: (close: () => void) => React.ReactNode; }) { const [open, setOpen] = useState(false); const ref = useRef(null); useEffect(() => { if (!open) return; const onDown = (e: PointerEvent) => { if (!ref.current?.contains(e.target as Node)) setOpen(false); }; const onKey = (e: KeyboardEvent) => e.key === "Escape" && setOpen(false); window.addEventListener("pointerdown", onDown); window.addEventListener("keydown", onKey); return () => { window.removeEventListener("pointerdown", onDown); window.removeEventListener("keydown", onKey); }; }, [open]); const anchor: React.CSSProperties = side === "down" ? { top: size + 8, right: 0, transformOrigin: "top right" } : { left: size + 8, bottom: 0, transformOrigin: "bottom left" }; const hidden = side === "down" ? { opacity: 0, y: -6, scale: 0.96 } : { opacity: 0, x: -6, scale: 0.96 }; return (
setOpen((o) => !o)} title={title} size={size} /> {open && ( {children(() => setOpen(false))} )}
); } export function LangMenu({ p, onLang, side, size }: { p: Palette; onLang: (l: Lang) => void; side?: "down" | "right"; size?: number }) { const lang = useLang(); return ( {(close) => (
{LANGS.map((l) => { const on = l.key === lang; return ( ); })}
)}
); }