72 lines
2.3 KiB
TypeScript
72 lines
2.3 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { Tabs as TabsPrimitive } from "radix-ui";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
type LibraryTab = "recommended" | "library" | "collections";
|
|
|
|
interface LibraryHeaderProps {
|
|
libraryName: string;
|
|
/**
|
|
* When true, the header renders transparently to sit over a hero backdrop,
|
|
* and switches to a glass surface once the user scrolls past a threshold.
|
|
*/
|
|
overlay?: boolean;
|
|
availableTabs?: readonly LibraryTab[];
|
|
}
|
|
|
|
const DEFAULT_TABS: readonly LibraryTab[] = ["recommended", "library", "collections"];
|
|
|
|
const TAB_LABELS: Record<LibraryTab, string> = {
|
|
recommended: "Recommended",
|
|
library: "Library",
|
|
collections: "Collections",
|
|
};
|
|
|
|
/** Scroll distance (in px) at which an overlay header switches to glass. */
|
|
const GLASS_THRESHOLD_PX = 160;
|
|
|
|
export default function LibraryHeader({
|
|
libraryName,
|
|
overlay = false,
|
|
availableTabs = DEFAULT_TABS,
|
|
}: LibraryHeaderProps) {
|
|
const [pastThreshold, setPastThreshold] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (!overlay) return;
|
|
const update = () => {
|
|
setPastThreshold(window.scrollY > GLASS_THRESHOLD_PX);
|
|
};
|
|
update();
|
|
window.addEventListener("scroll", update, { passive: true });
|
|
return () => window.removeEventListener("scroll", update);
|
|
}, [overlay]);
|
|
|
|
const scrolled = overlay && pastThreshold;
|
|
|
|
return (
|
|
<header
|
|
className={cn("library-marquee-header", overlay && "is-overlay")}
|
|
data-scrolled={overlay ? (scrolled ? "true" : "false") : undefined}
|
|
>
|
|
{/* Eyebrow is hidden on mobile — the top nav already identifies the
|
|
current page, and at narrow widths it would either crowd out the
|
|
tab pills or truncate to "LIBRAR…". */}
|
|
<div className="hidden min-w-0 items-center gap-4 sm:flex">
|
|
<p className="hero-eyebrow truncate">
|
|
<span>Library</span>
|
|
<span className="hero-eyebrow-divider">/</span>
|
|
<span className="hero-eyebrow-strong">{libraryName}</span>
|
|
</p>
|
|
</div>
|
|
<TabsPrimitive.List className="marquee-tab-bar" aria-label="Library view">
|
|
{availableTabs.map((tab) => (
|
|
<TabsPrimitive.Trigger key={tab} value={tab} className="marquee-tab-trigger">
|
|
{TAB_LABELS[tab]}
|
|
</TabsPrimitive.Trigger>
|
|
))}
|
|
</TabsPrimitive.List>
|
|
</header>
|
|
);
|
|
}
|