feat: add auth
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* SiteHeader — the single global navigation header.
|
||||
*
|
||||
* Sits at the top of every page (mounted in app/layout.tsx) so the page-specific
|
||||
* PageHeader can stay focused on titles and breadcrumbs.
|
||||
*
|
||||
* Layout:
|
||||
*
|
||||
* ┌─────────────────────────────────────────────────────────────┐
|
||||
* │ PRABHUPĀDA Books · Letters · Lectures · Glossary ⌕ ⚙ ◐ │
|
||||
* │ SAYS │
|
||||
* └─────────────────────────────────────────────────────────────┘
|
||||
*
|
||||
* - Serif uppercase wordmark on the left (links home)
|
||||
* - Section nav inline on md+; collapsed into the mobile drawer below
|
||||
* - Right cluster: HeaderSearchButton, ReadingSettings, UserMenu
|
||||
* - Sticky with a subtle border + backdrop blur when scrolled
|
||||
*
|
||||
* The HeaderSearchButton's expand-into-full-width behavior is preserved by
|
||||
* giving it the same conditional flex treatment that PageHeader used to do —
|
||||
* when expanded on mobile the wordmark + nav collapse out of the way.
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { HeaderSearchButton } from "@/components/header-search";
|
||||
import { ReadingSettings } from "@/components/reading-settings";
|
||||
import { UserMenu } from "@/components/auth/user-menu";
|
||||
import { MobileNavToggle } from "@/components/site-mobile-nav";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const PRIMARY_NAV = [
|
||||
{ href: "/books", label: "Books" },
|
||||
{ href: "/letters", label: "Letters" },
|
||||
{ href: "/transcriptions", label: "Lectures" },
|
||||
{ href: "/essays", label: "Essays" },
|
||||
{ href: "/glossary", label: "Glossary" },
|
||||
] as const;
|
||||
|
||||
export function SiteHeader() {
|
||||
const [searchExpanded, setSearchExpanded] = useState(false);
|
||||
const pathname = usePathname();
|
||||
|
||||
function isActive(href: string): boolean {
|
||||
if (href === "/") return pathname === "/";
|
||||
return pathname.startsWith(href);
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-30 border-b border-border bg-secondary text-secondary-foreground">
|
||||
<div className="max-w-5xl mx-auto px-3 sm:px-6 h-14 md:h-16 flex items-center gap-2 md:gap-6">
|
||||
{/* Mobile hamburger — visible below md, hidden when search is expanded */}
|
||||
<div className={cn("md:hidden", searchExpanded && "hidden")}>
|
||||
<MobileNavToggle items={PRIMARY_NAV} />
|
||||
</div>
|
||||
|
||||
{/* Brand wordmark — two-line stacked "Prabhupāda / SAYS". Smaller
|
||||
type sizes on mobile so the row fits next to the hamburger and
|
||||
action icons on narrow phones (≤375px). */}
|
||||
<Link
|
||||
href="/"
|
||||
aria-label="Prabhupāda Says — home"
|
||||
className={cn(
|
||||
"shrink-0 leading-none text-foreground transition-opacity hover:opacity-80",
|
||||
"font-[family-name:var(--font-title)]",
|
||||
searchExpanded && "max-md:hidden",
|
||||
)}
|
||||
>
|
||||
<span className="block text-[0.78rem] md:text-lg font-semibold uppercase tracking-[0.08em] md:tracking-[0.12em]">
|
||||
Prabhupāda
|
||||
</span>
|
||||
<span className="block text-[0.55rem] md:text-[0.78rem] font-medium uppercase text-muted-foreground tracking-[0.18em] md:tracking-[0.22em] mt-0.5">
|
||||
Says
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
{/* Primary nav — desktop only; mobile uses the drawer.
|
||||
Stays visible even when search is expanded — on desktop the search
|
||||
input has a fixed max-width and fits alongside the nav, so there's
|
||||
no need to remove items (which would cause layout shift). */}
|
||||
<nav
|
||||
className="hidden md:flex items-center gap-5 flex-1 ml-2"
|
||||
aria-label="Primary"
|
||||
>
|
||||
{PRIMARY_NAV.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={cn(
|
||||
"text-sm transition-colors relative",
|
||||
isActive(item.href)
|
||||
? "text-foreground after:absolute after:left-0 after:right-0 after:-bottom-1 after:h-px after:bg-primary"
|
||||
: "text-muted-foreground hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Spacer pushes the action cluster to the right on mobile */}
|
||||
<div
|
||||
className={cn("flex-1 md:hidden", searchExpanded && "hidden")}
|
||||
aria-hidden
|
||||
/>
|
||||
|
||||
{/* Action cluster — search, reading-settings, user menu.
|
||||
On mobile when search is expanded, the cluster fills the row so
|
||||
the input can grow. On desktop it always stays shrink-0 — the
|
||||
expanded search input has its own fixed max-width and doesn't
|
||||
need to steal space from the nav. */}
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center gap-1 shrink-0",
|
||||
searchExpanded && "max-md:flex-1 max-md:min-w-0 max-md:shrink",
|
||||
)}
|
||||
>
|
||||
<HeaderSearchButton
|
||||
expanded={searchExpanded}
|
||||
onExpandedChange={setSearchExpanded}
|
||||
/>
|
||||
<div className={cn(searchExpanded && "max-md:hidden")}>
|
||||
<ReadingSettings />
|
||||
</div>
|
||||
<div className={cn(searchExpanded && "max-md:hidden")}>
|
||||
<UserMenu />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user