/**
* MobileMenu.tsx
* ---------------------------------------------------------------------------
* React island that powers the hamburger menu on small screens. Renders the
* same two-group nav structure as the desktop bar (imported via a data prop so
* the source of truth stays in site.ts).
*
* Accessibility: the button is aria-expanded/aria-controls wired to the panel,
* Escape closes, clicking a link closes, and focus is returned to the toggle.
* Animations respect prefers-reduced-motion (Framer Motion reads the media query
* automatically when we pass the transition).
*/
import { useEffect, useRef, useState } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
import BrandIcon from '@components/brand/BrandIcon';
export interface MobileNavItem {
label: string;
href: string;
icon: string;
}
export interface MobileNavGroup {
items: MobileNavItem[];
}
interface Props {
groups: MobileNavGroup[];
/** Current path for active link highlighting. */
currentPath: string;
}
// Patiun brand icons, resolved by the same names the nav config uses.
function Icon({ name, className }: { name: string; className?: string }) {
return ;
}
function isActive(href: string, path: string): boolean {
if (href === '/') return path === '/';
return path === href || path.startsWith(href + '/');
}
export default function MobileMenu({ groups, currentPath }: Props) {
const [open, setOpen] = useState(false);
const buttonRef = useRef(null);
// Escape closes the menu; lock body scroll while open.
useEffect(() => {
if (!open) return;
const onKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
setOpen(false);
buttonRef.current?.focus();
}
};
document.addEventListener('keydown', onKey);
document.body.style.overflow = 'hidden';
return () => {
document.removeEventListener('keydown', onKey);
document.body.style.overflow = '';
};
}, [open]);
return (