"use client"; import { AnimatePresence, motion } from "motion/react"; import Image from "next/image"; import { type ReactNode, useCallback, useEffect, useId, useRef, useState, } from "react"; import { mobileMenuVariants } from "@/lib/motion"; interface MobileNavProps { children: ReactNode; menuLabel?: string; closeLabel?: string; brandLabel?: string; } export function MobileNav({ children, menuLabel = "Open menu", closeLabel = "Close menu", brandLabel = "", }: MobileNavProps) { const [open, setOpen] = useState(false); const detailsRef = useRef(null); const menuRef = useRef(null); const toggleRef = useRef(null); const prevFocusRef = useRef(null); const menuId = useId(); const handleEscape = useCallback( (e: KeyboardEvent) => { if (e.key === "Escape" && open) { setOpen(false); (prevFocusRef.current ?? toggleRef.current)?.focus(); } }, [open], ); useEffect(() => { document.addEventListener("keydown", handleEscape); return () => document.removeEventListener("keydown", handleEscape); }, [handleEscape]); // Initial focus, scroll-lock, click-outside close, focus restore. useEffect(() => { if (!open) return; prevFocusRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null; menuRef.current ?.querySelector( 'a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])', ) ?.focus(); const prevOverflow = document.body.style.overflow; document.body.style.overflow = "hidden"; function handlePointerDown(e: PointerEvent) { if ( menuRef.current && !menuRef.current.contains(e.target as Node) && !toggleRef.current?.contains(e.target as Node) ) { setOpen(false); } } document.addEventListener("pointerdown", handlePointerDown); return () => { document.body.style.overflow = prevOverflow; document.removeEventListener("pointerdown", handlePointerDown); (prevFocusRef.current ?? toggleRef.current)?.focus(); }; }, [open]); useEffect(() => { if (!open) return; const menu = menuRef.current; if (!menu) return; const focusableSelector = 'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])'; function handleTab(e: KeyboardEvent) { if (e.key !== "Tab" || !menu) return; const focusable = Array.from( menu.querySelectorAll(focusableSelector), ); if (focusable.length === 0) return; const first = focusable[0]; const last = focusable[focusable.length - 1]; if (e.shiftKey) { if (document.activeElement === first) { e.preventDefault(); last.focus(); } } else { if (document.activeElement === last) { e.preventDefault(); first.focus(); } } } menu.addEventListener("keydown", handleTab); return () => menu.removeEventListener("keydown", handleTab); }, [open]); return (
{open && (
{brandLabel} {closeLabel}
{children}
)}
); }