"use client"; import { useEffect, useId, useRef, useState, type KeyboardEvent, type MouseEvent } from "react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; export type MorphMenuItem = { label: string; href: string }; type MorphMenuProps = { items: MorphMenuItem[]; /** Called on click. Call e.preventDefault() to stop the browser following the link. */ onSelect?: (item: MorphMenuItem, e: MouseEvent) => void; /** Width of the open panel in pixels. */ width?: number; /** Size of the closed button in pixels. */ size?: number; /** Which side the panel grows from. */ align?: "left" | "right"; /** Colour of the hover marker. */ accent?: string; /** Name read out by screen readers. */ label?: string; /** Look of the button / panel. */ className?: string; }; /* MorphMenu: the round menu button itself stretches into the menu panel, and the lines turn into a cross. */ export function MorphMenu({ items, onSelect, width = 240, size = 48, align = "right", accent = "#a78bfa", label = "Menu", className = "border border-white/10 bg-[#161229] text-white shadow-2xl shadow-black/50", }: MorphMenuProps) { const reduce = useReducedMotion(); const uid = useId(); const wrap = useRef(null); const button = useRef(null); const [open, setOpen] = useState(false); const spring = reduce ? { duration: 0 } : { type: "spring" as const, stiffness: 380, damping: 32 }; // Close on a click outside. useEffect(() => { if (!open) return; const away = (e: PointerEvent) => { if (!wrap.current?.contains(e.target as Node)) setOpen(false); }; document.addEventListener("pointerdown", away); return () => document.removeEventListener("pointerdown", away); }, [open]); const close = () => { setOpen(false); button.current?.focus(); }; const onKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape" && open) close(); }; return (
{open && (

{label}

)}
); }