TypeScript 97.5%
SQL 1.4%
Python 0.8%
1"use client";2import * as React from "react";3import Link from "next/link";4import { usePathname } from "next/navigation";5import { ChevronRight, ExternalLink, Menu, X } from "lucide-react";6import { Button } from "@/components/ui/button";7import { cn } from "@/lib/utils";8import { DOCS_NAV, type DocsNavLink } from "./nav-config";910function NavLinks({ onNavigate, className }: { onNavigate?: () => void; className?: string }) {11 const pathname = usePathname();12 return (13 <nav aria-label="Documentation" className={cn("text-[13.5px]", className)}>14 {DOCS_NAV.map((section) => (15 <div key={section.title} className="mb-6 last:mb-0">16 <div className="mb-1.5 px-2 text-[11.5px] font-semibold uppercase tracking-wide text-fg-subtle">{section.title}</div>17 <ul className="space-y-px">18 {section.links.map((l) => (19 <li key={l.href}>20 <NavItem link={l} active={pathname === l.href} onNavigate={onNavigate} />21 </li>22 ))}23 </ul>24 </div>25 ))}26 </nav>27 );28}2930function NavItem({ link, active, onNavigate }: { link: DocsNavLink; active: boolean; onNavigate?: () => void }) {31 return (32 <Link33 href={link.href}34 onClick={onNavigate}35 aria-current={active ? "page" : undefined}36 className={cn(37 "flex items-center justify-between gap-2 rounded-md px-2 py-1.5 transition-colors",38 active ? "bg-accent-soft font-medium text-accent" : "text-fg-muted hover:bg-bg-muted hover:text-fg",39 )}40 >41 <span className="truncate">{link.title}</span>42 {link.badge ? <span className="shrink-0 rounded-full bg-warning-soft px-1.5 py-px text-[10.5px] font-medium text-warning">{link.badge}</span> : null}43 {link.external ? <ExternalLink className="size-3.5 shrink-0 text-fg-subtle" aria-hidden /> : null}44 </Link>45 );46}4748/** Desktop sidebar: sticky under the marketing header. */49export function DocsSidebar({ className }: { className?: string }) {50 return (51 <aside className={cn("sticky top-14 hidden max-h-[calc(100dvh-3.5rem)] overflow-y-auto py-8 pr-4 scrollbar-thin lg:block", className)}>52 <NavLinks />53 </aside>54 );55}5657/** Mobile: compact bar with the current page and a drawer with the full navigation. */58export function DocsMobileNav() {59 const [open, setOpen] = React.useState(false);60 const pathname = usePathname();61 const current = React.useMemo(() => DOCS_NAV.flatMap((s) => s.links.map((l) => ({ ...l, section: s.title }))).find((l) => l.href === pathname), [pathname]);6263 React.useEffect(() => {64 if (!open) return;65 const onKey = (e: KeyboardEvent) => {66 if (e.key === "Escape") setOpen(false);67 };68 document.addEventListener("keydown", onKey);69 const prev = document.body.style.overflow;70 document.body.style.overflow = "hidden";71 return () => {72 document.removeEventListener("keydown", onKey);73 document.body.style.overflow = prev;74 };75 }, [open]);7677 return (78 <div className="lg:hidden">79 <div className="-mx-5 flex items-center gap-2 border-b border-border bg-bg-subtle/60 px-5 py-2.5 text-[13px] sm:-mx-8 sm:px-8">80 <Button variant="outline" size="sm" onClick={() => setOpen(true)} aria-expanded={open} aria-controls="docs-drawer" className="gap-1.5">81 <Menu className="size-4" aria-hidden />82 Menu83 </Button>84 <span className="flex min-w-0 items-center gap-1 text-fg-muted">85 <span className="truncate">{current?.section ?? "Docs"}</span>86 <ChevronRight className="size-3.5 shrink-0 text-fg-subtle" aria-hidden />87 <span className="truncate font-medium text-fg">{current?.title ?? "Documentation"}</span>88 </span>89 </div>9091 {open ? (92 <div className="fixed inset-0 z-50" role="dialog" aria-modal="true" aria-label="Documentation navigation" id="docs-drawer">93 <button type="button" className="absolute inset-0 bg-fg/30 backdrop-blur-[2px] animate-fade-in" aria-label="Close navigation" onClick={() => setOpen(false)} />94 <div className="absolute inset-y-0 left-0 flex w-[min(20rem,85vw)] flex-col border-r border-border bg-bg shadow-lg animate-fade-in">95 <div className="flex h-14 items-center justify-between border-b border-border px-4">96 <span className="text-[13.5px] font-semibold text-fg">Documentation</span>97 <Button variant="ghost" size="icon-sm" onClick={() => setOpen(false)} aria-label="Close navigation">98 <X />99 </Button>100 </div>101 <div className="flex-1 overflow-y-auto px-3 py-4 scrollbar-thin">102 <NavLinks onNavigate={() => setOpen(false)} />103 </div>104 </div>105 </div>106 ) : null}107 </div>108 );109}110