SPB Git

spb/autoscholar

Public
2.9 KB · 96 lines tsx
1"use client";2import Link from "next/link";3import { usePathname } from "next/navigation";4import { useEffect, useState } from "react";5import { IconClose, IconMenu } from "@/components/Icons";67/** Admin navigation: vertical list on desktop; on mobile a compact button opening a drawer. */8export function AdminNav({9  items,10  mobileOnly = false,11}: {12  items: Array<[string, string]>;13  mobileOnly?: boolean;14}) {15  const pathname = usePathname();16  const [open, setOpen] = useState(false);17  // biome-ignore lint/correctness/useExhaustiveDependencies: close on navigation18  useEffect(() => setOpen(false), [pathname]);19  useEffect(() => {20    if (!open) return;21    const onKey = (e: KeyboardEvent) => e.key === "Escape" && setOpen(false);22    document.addEventListener("keydown", onKey);23    const prev = document.body.style.overflow;24    document.body.style.overflow = "hidden";25    return () => {26      document.removeEventListener("keydown", onKey);27      document.body.style.overflow = prev;28    };29  }, [open]);30  const isCurrent = (href: string) => (href === "/admin" ? pathname === "/admin" : pathname.startsWith(href));31  const list = (32    <nav aria-label="Admin" className="flex flex-col gap-0.5 px-2 pb-2">33      {items.map(([href, label]) => (34        <Link35          key={href}36          href={href}37          aria-current={isCurrent(href) ? "page" : undefined}38          className="nav-link flex min-h-10 items-center text-sm md:min-h-9"39        >40          {label}41        </Link>42      ))}43    </nav>44  );45  if (!mobileOnly) return list;46  return (47    <div className="md:hidden">48      <button49        type="button"50        className="btn-icon"51        aria-label={open ? "Close admin menu" : "Open admin menu"}52        aria-expanded={open}53        aria-controls="admin-drawer"54        onClick={() => setOpen((v) => !v)}55      >56        {open ? <IconClose /> : <IconMenu />}57      </button>58      {open && (59        <>60          <button61            type="button"62            className="drawer-backdrop"63            aria-label="Close admin menu"64            onClick={() => setOpen(false)}65          />66          <div67            id="admin-drawer"68            role="dialog"69            aria-modal="true"70            aria-label="Admin navigation"71            className="drawer-panel"72          >73            <div className="flex items-center justify-between px-2">74              <span className="eyebrow">Admin</span>75              <button76                type="button"77                className="btn-icon"78                aria-label="Close admin menu"79                onClick={() => setOpen(false)}80              >81                <IconClose />82              </button>83            </div>84            <div className="mt-3">{list}</div>85            <div className="mt-4 px-2">86              <Link href="/" className="text-xs">87                ← Public site88              </Link>89            </div>90          </div>91        </>92      )}93    </div>94  );95}96
Files · main
Menu

Theme