SPB Git

Repository Public MIT

mobile-menu.tsx 1.7 KB 40 lines tsx

1"use client";2import Link from "next/link";3import { Menu } from "lucide-react";4import { usePathname } from "next/navigation";5import { useEffect, useRef } from "react";67/** Native <details> menu (works without JavaScript); with JavaScript it closes on navigation, Escape and outside clicks. */8export function MobileMenu({ links }: { links: { href: string; label: string }[] }) {9  const ref = useRef<HTMLDetailsElement>(null);10  const pathname = usePathname();11  useEffect(() => {12    ref.current?.removeAttribute("open");13  }, [pathname]);14  useEffect(() => {15    const close = (e: Event) => {16      if (e instanceof KeyboardEvent ? e.key === "Escape" : !ref.current?.contains(e.target as Node)) ref.current?.removeAttribute("open");17    };18    document.addEventListener("click", close);19    document.addEventListener("keydown", close);20    return () => {21      document.removeEventListener("click", close);22      document.removeEventListener("keydown", close);23    };24  }, []);25  return (26    <details ref={ref} className="group relative md:hidden">27      <summary className="grid h-9 w-9 cursor-pointer list-none place-items-center rounded-lg border border-line text-muted [&::-webkit-details-marker]:hidden" aria-label="Menu">28        <Menu size={16} aria-hidden />29      </summary>30      <nav aria-label="Mobile" className="absolute right-0 top-11 z-50 w-56 rounded-xl border border-line-strong bg-graphite p-2 [box-shadow:var(--shadow-pop)]">31        {links.map((n) => (32          <Link key={n.href} href={n.href} onClick={() => ref.current?.removeAttribute("open")} className="block rounded-lg px-3 py-2.5 text-sm text-text hover:bg-raised">33            {n.label}34          </Link>35        ))}36      </nav>37    </details>38  );39}40
Files main
Menu

Theme