import { Link, useLocation } from 'react-router'; import { ConfiguredIcon } from '@/components/ConfiguredIcon'; import { LanguageSwitcher } from '@/components/LanguageSwitcher'; import { SearchSlot } from '@/components/Search'; import { ThemeToggle } from '@/components/ThemeToggle'; import { TopNav } from '@/components/TopNav'; import { VersionSwitcher } from '@/components/VersionSwitcher'; import { isExternalHref } from '@/lib/paths'; import { docsHomeUrl, getScopeByPathname, hasRootStandalonePage } from '@/lib/site-config'; import type { NormalizedLink, SiteModel } from '@/lib/types'; /** * Header hrefs come from config, so they may point inside the site or off it. * In-app routes go through react-router; anything external (or explicitly * opened in a new tab) stays a plain anchor and triggers a document load. */ function isRoutedHref(href: string, target?: string): boolean { return href.startsWith('/') && !isExternalHref(href) && target !== '_blank'; } function NavbarLinkItem({ link, primary = false }: { link: NormalizedLink; primary?: boolean }) { const iconOnly = !link.label; const accessibleLabel = link.ariaLabel || link.icon || link.href; const className = primary ? `ml-1 inline-flex items-center justify-center rounded-full bg-primary text-sm font-semibold text-primary-foreground hover:opacity-90 ${iconOnly ? 'size-8' : 'gap-1.5 px-3.5 py-1.5'}` : `inline-flex items-center rounded-md text-sm font-medium text-foreground hover:bg-accent hover:text-foreground ${iconOnly ? 'size-8 justify-center' : 'gap-1.5 px-2.5 py-1.5'}`; const content = ( <> {link.label} > ); if (isRoutedHref(link.href, link.target)) { return ( {content} ); } return ( {content} ); } export function Header({ site }: { site: SiteModel }) { const { logo, navbar, name, appearance, labels } = site; const { pathname } = useLocation(); // The header renders the navigation of whichever scope owns the current page. const docs = getScopeByPathname(pathname).docs; // The brand links to the standalone home page when one owns "/". const brandHref = logo?.href || (hasRootStandalonePage ? '/' : docsHomeUrl); const hasBrand = !!name || !!logo?.light || !!logo?.dark; const brandClassName = 'inline-flex items-center gap-2 text-xl font-bold text-foreground tracking-[-0.03em]'; const brandContent = ( <> {/* With `invert`, one logo serves both modes: a CSS filter forces it to white in dark mode instead of swapping to a dark variant. */} {logo?.invert && logo.light ? ( ) : ( <> {logo?.light ? : null} {logo?.dark ? ( ) : null} > )} {name ? {name} : null} > ); return ( {hasBrand ? ( isRoutedHref(brandHref, logo?.target) ? ( {brandContent} ) : ( {brandContent} ) ) : null} {docs.showTabs ? : null} {/* The sidebar collapses into a sheet on small screens, so its search control moves up here until the column is visible. */} {navbar?.links.map(link => ( ))} {!appearance.strict && } {navbar?.primary ? : null} ); }