feat: add user centric features

This commit is contained in:
gosangam
2026-05-30 23:44:08 +05:30
parent 7bd2952caa
commit dcdd2383c6
63 changed files with 16783 additions and 1735 deletions
+19 -107
View File
@@ -1,133 +1,45 @@
"use client";
/**
* SiteHeader — the single global navigation header.
* SiteHeader — minimal sticky transparent header with the brand wordmark
* centred. Navigation lives in SiteSidebar (desktop) and SiteBottomNav
* (mobile), so the header's only job here is to anchor the brand.
*
* Sits at the top of every page (mounted in app/layout.tsx) so the page-specific
* PageHeader can stay focused on titles and breadcrumbs.
* Mobile also gets a Reading-settings affordance pinned to the right —
* the sidebar rail is hidden on mobile, so this is the only easy entry
* point to the theme + palette + font controls.
*
* Layout:
* Layout (mobile):
*
* ┌─────────────────────────────────────────────────────────────┐
* │ PRABHUPĀDA Books · Letters · Lectures · Glossary ⌕ ⚙ ◐ │
* │ SAYS │
* └─────────────────────────────────────────────────────────────┘
* ┌─────────────────────────────────────────────────────┐
* │ Prabhupāda ⚙ │
* │ 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.
* On md+ the gear hides and the sidebar carries it instead.
*/
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). */}
<header className="sticky top-0 z-30 bg-background/60 backdrop-blur-md">
<div className="relative h-12 sm:h-14 flex items-center justify-center px-4">
<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",
)}
className="leading-none text-foreground font-[family-name:var(--font-title)] transition-opacity hover:opacity-80"
>
<span className="block text-[0.78rem] md:text-lg font-semibold uppercase tracking-[0.08em] md:tracking-[0.12em]">
<span className="block text-center text-[0.8rem] sm:text-base font-semibold uppercase 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">
<span className="block text-center text-[0.55rem] sm:text-[0.7rem] font-medium uppercase text-muted-foreground 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 className="md:hidden absolute right-2 top-1/2 -translate-y-1/2">
<ReadingSettings />
</div>
</div>
</header>