"use client"; import { ArrowUpRight, ChevronDown, ChevronUp } from "lucide-react"; import Link from "next/link"; import Image from "next/image"; import Search from "@/components/SearchBox"; import Anchor from "@/components/anchor"; import { Separator } from "@/components/ui/separator"; import docuConfig from "@/docu.json"; import GitHubButton from "@/components/Github"; import { Button } from "@/components/ui/button"; import { useState, useCallback, useRef, useEffect } from "react"; import { ModeToggle } from "@/components/ThemeToggle"; interface NavbarProps { id?: string; } export function Navbar({ id }: NavbarProps) { const [isMenuOpen, setIsMenuOpen] = useState(false); const navRef = useRef(null); const menuRef = useRef(null); const toggleMenu = useCallback(() => { setIsMenuOpen((prev) => !prev); }, []); // Close menu when the user clicks/taps anywhere outside the navbar, or presses Escape useEffect(() => { if (!isMenuOpen) return; const handleClickOutside = (event: MouseEvent) => { if (navRef.current && !navRef.current.contains(event.target as Node)) { setIsMenuOpen(false); } }; const handleKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape") setIsMenuOpen(false); }; document.addEventListener("mousedown", handleClickOutside); document.addEventListener("keydown", handleKeyDown); return () => { document.removeEventListener("mousedown", handleClickOutside); document.removeEventListener("keydown", handleKeyDown); }; }, [isMenuOpen]); // Focus trap: keep Tab within the mobile menu when open useEffect(() => { if (!isMenuOpen || !menuRef.current) return; const menu = menuRef.current; const focusableSelector = 'a[href], button, input, textarea, select, [tabindex]:not([tabindex="-1"])'; const handleTrap = (e: KeyboardEvent) => { if (e.key !== "Tab") return; const focusables = menu.querySelectorAll(focusableSelector); if (focusables.length === 0) return; const first = focusables[0]; const last = focusables[focusables.length - 1]; if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } }; // Move focus into the menu const focusables = menu.querySelectorAll(focusableSelector); if (focusables.length > 0) focusables[0].focus(); menu.addEventListener("keydown", handleTrap); return () => menu.removeEventListener("keydown", handleTrap); }, [isMenuOpen]); return (
); } export function Logo() { const { navbar } = docuConfig; return (
{navbar.logo.alt}

{navbar.logoText}

); } // Desktop NavMenu — horizontal list export function NavMenu() { const { navbar } = docuConfig; return ( <> {navbar?.menu?.map((item) => { const isExternal = item.href.startsWith("http"); return ( {item.title} {isExternal && } ); })} ); } // Mobile Collapsible NavMenu — vertical list items function NavMenuCollapsible({ onItemClick }: { onItemClick: () => void }) { const { navbar } = docuConfig; return ( <> {navbar?.menu?.map((item) => { const isExternal = item.href.startsWith("http"); return (
  • {item.title} {isExternal && }
  • ); })} ); }