'use client'; import { ChevronDown } from 'lucide-react'; import { useEffect, useState } from 'react'; import { cn } from '@sport/ui'; import { Link } from '@/i18n/navigation'; export interface MegaMenuColumn { readonly heading: string; readonly links: readonly { readonly label: string; readonly href: string }[]; } export interface MegaMenuEntry { readonly id: string; readonly label: string; readonly href: string; readonly columns: readonly MegaMenuColumn[]; } /** * Desktop navigation with a full-width drop panel. * * Hand-built rather than Radix NavigationMenu. The panel here is a plain hover * region with no roving focus and no collision logic to get wrong, and it is * the most brand-visible element in the chrome — the animation, the full-bleed * black panel, the column rhythm. Radix would supply behaviour this does not * need and constrain the markup that carries the identity. * * What it still has to get right, and does: * - Opens on hover *and* on keyboard focus, so it is reachable without a mouse. * - Closes on Escape and on focus leaving the group. * - The top-level item stays a real link, so a sport is one click away rather * than requiring the panel. */ export function MegaMenu({ entries, className, }: { entries: readonly MegaMenuEntry[]; className?: string; }) { const [openId, setOpenId] = useState(null); // Escape is bound to the document rather than to a wrapper's `onKeyDown`: // the panel is dismissible from anywhere while it is open, and hanging key // handlers off a non-interactive container would be lying about what that // container is. useEffect(() => { if (!openId) return; const onKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') setOpenId(null); }; document.addEventListener('keydown', onKeyDown); return () => document.removeEventListener('keydown', onKeyDown); }, [openId]); return (
setOpenId(null)}> {entries.map((entry) => { const open = openId === entry.id; const hasPanel = entry.columns.length > 0; return (
setOpenId(entry.id)} onFocus={() => setOpenId(entry.id)} onBlur={(event) => { // Only close when focus leaves the whole group, otherwise tabbing // from the trigger into the panel would shut it immediately. if (!event.currentTarget.contains(event.relatedTarget)) setOpenId(null); }} > {entry.label} {hasPanel ? ( ) : null} {/* `border-y`, not `border-t`. On the homepage the panel is black over a black hero, and without a bottom edge the two surfaces merge — the menu reads as text floating on the hero rather than as a panel in front of it. */} {hasPanel && open ? (
{entry.columns.map((column) => (

{column.heading}

    {column.links.map((link) => (
  • setOpenId(null)} className="text-ink-300 hover:text-volt-500 text-sm transition-colors" > {link.label}
  • ))}
))}
) : null}
); })}
); }