SPB Git
3.3 KB · 44 lines tsx
1"use client";2import Link from "next/link";3import { usePathname } from "next/navigation";4import { useState } from "react";5import { Menu, X, Search } from "lucide-react";6import { href, t, type Locale } from "@/lib/i18n";7import { ThemeToggle } from "./theme-toggle";89export function SiteHeader({ locale }: { locale: Locale }) {10  const d = t(locale); const path = usePathname(); const [open, setOpen] = useState(false);11  const canonical = path.replace(/^\/fr(?=\/|$)/, "") || "/";12  const items = [["/papers", d.nav.papers], ["/topics", d.nav.topics], ["/methods", d.nav.methods], ["/datasets", d.nav.datasets], ["/authors", d.nav.authors], ["/research-map", d.nav.map], ["/timeline", d.nav.timeline], ["/about", d.nav.about]] as const;13  const other: Locale = locale === "en" ? "fr" : "en";14  const active = (p: string) => canonical === p || (p !== "/" && canonical.startsWith(p + "/"));15  return (16    <header className="sticky top-0 z-40 border-b border-line bg-bg/90 backdrop-blur supports-[backdrop-filter]:bg-bg/75">17      <div className="container flex items-center gap-2 sm:gap-3 h-16">18        <Link href={href(locale, "/")} className="flex items-baseline gap-2 no-underline min-w-0">19          <span className="serif text-[1.05rem] sm:text-xl text-fg whitespace-nowrap truncate">{d.site.title}</span>20          <span className="hidden lg:inline text-xs uppercase tracking-[.14em] text-fg-3">{d.site.affiliation}</span>21        </Link>22        <nav aria-label="Primary" className="hidden md:flex items-center gap-1 ml-auto">23          {items.map(([p, label]) => <Link key={p} href={href(locale, p)} aria-current={active(p) ? "page" : undefined} className={`px-2.5 py-1.5 rounded-md text-sm no-underline hover:bg-bg-3 ${active(p) ? "text-navy font-medium" : "text-fg-2"}`}>{label}</Link>)}24        </nav>25        <div className="flex items-center gap-1.5 ml-auto md:ml-2">26          <Link href={href(locale, "/search")} aria-label={d.nav.search} className="btn btn-sm !px-2"><Search size={16} aria-hidden /></Link>27          <Link href={href(other, canonical)} hrefLang={other} lang={other} aria-label={d.nav.language} className="btn btn-sm font-mono uppercase">{other}</Link>28          <ThemeToggle label={d.nav.lightDark} />29          <button type="button" className="btn btn-sm !px-2 md:hidden" aria-expanded={open} aria-controls="mobile-nav" aria-label={open ? d.nav.close : d.nav.menu} onClick={() => setOpen((o) => !o)}>{open ? <X size={16} aria-hidden /> : <Menu size={16} aria-hidden />}</button>30        </div>31      </div>32      {open && (33        <nav id="mobile-nav" aria-label="Mobile" className="md:hidden border-t border-line bg-bg">34          <div className="container py-2 grid grid-cols-2 gap-1">35            {items.map(([p, label]) => <Link key={p} href={href(locale, p)} onClick={() => setOpen(false)} className={`px-3 py-2 rounded-md text-sm no-underline hover:bg-bg-3 ${active(p) ? "text-navy font-medium" : "text-fg-2"}`}>{label}</Link>)}36            <Link href={href(locale, "/citation")} onClick={() => setOpen(false)} className="px-3 py-2 rounded-md text-sm no-underline text-fg-2 hover:bg-bg-3">{d.nav.citation}</Link>37            <Link href="/api/docs" onClick={() => setOpen(false)} className="px-3 py-2 rounded-md text-sm no-underline text-fg-2 hover:bg-bg-3">{d.nav.api}</Link>38          </div>39        </nav>40      )}41    </header>42  );43}44
Files · main
Menu

Theme