"use client"; import { useEffect, useId, useRef, useState, type FocusEvent, type KeyboardEvent, type MouseEvent, type ReactNode } from "react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; export type MegaCanvasLink = { label: string; href: string; note?: string }; export type MegaCanvasSection = { label: string; links: MegaCanvasLink[]; /** Optional block on the right of the panel, e.g. a promo card. */ feature?: ReactNode; }; type MegaCanvasProps = { sections: MegaCanvasSection[]; /** Called on click. Call e.preventDefault() to stop the browser following the link. */ onSelect?: (link: MegaCanvasLink, e: MouseEvent) => void; /** Panel width in pixels. It shrinks to fit narrow spaces. */ width?: number; /** Milliseconds to wait before closing after the pointer leaves. */ closeDelay?: number; /** Colour of the note text and hover marker. */ accent?: string; /** Name read out by screen readers. */ label?: string; /** Bar look. */ className?: string; /** Panel look. */ panelClassName?: string; }; /* MegaCanvas: one big panel serves every menu. It resizes to each section and the content slides in from the side you moved toward. */ export function MegaCanvas({ sections, onSelect, width = 560, closeDelay = 160, accent = "#a78bfa", label = "Main", className = "rounded-full border border-white/10 bg-white/5 p-1 text-sm text-white backdrop-blur-md", panelClassName = "rounded-2xl border border-white/10 bg-[#141026]/95 text-white shadow-2xl shadow-black/50 backdrop-blur-xl", }: MegaCanvasProps) { const reduce = useReducedMotion(); const uid = useId(); const nav = useRef(null); const panel = useRef(null); const timer = useRef>(undefined); const [open, setOpen] = useState(null); // +1 when moving right, -1 when moving left: content slides in from that side. const [dir, setDir] = useState(0); const focusPanel = useRef(false); const pointer = useRef(""); useEffect(() => () => clearTimeout(timer.current), []); // After a click / Enter, move focus into the panel. useEffect(() => { if (!focusPanel.current || open === null) return; focusPanel.current = false; // Only the incoming section: the old one is still fading out. panel.current?.querySelector(`[data-mega-section="${open}"] a[href]`)?.focus(); }, [open]); const show = (i: number) => { clearTimeout(timer.current); setDir(open === null ? 0 : Math.sign(i - open)); setOpen(i); }; const hideSoon = () => { clearTimeout(timer.current); timer.current = setTimeout(() => setOpen(null), closeDelay); }; const closeTo = (i: number | null) => { setOpen(null); if (i !== null) nav.current?.querySelectorAll("[data-mega-trigger]")[i]?.focus(); }; const onKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape" && open !== null) closeTo(open); }; // Close once focus leaves the whole menu. const onBlur = (e: FocusEvent) => { if (!nav.current?.contains(e.relatedTarget as Node)) setOpen(null); }; const section = open === null ? null : sections[open]; return ( ); }