spb/wp-spboucher-ai
Public
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