"use client"; import { useId, useRef, useState, type MouseEvent, type ReactNode, type RefObject } from "react"; import { AnimatePresence, motion, useMotionValueEvent, useReducedMotion, useScroll, } from "motion/react"; export type GlassNavItem = { label: string; href: string }; type GlassNavbarProps = { /** Logo or site name on the left. */ brand: ReactNode; items: GlassNavItem[]; /** Button on the right, e.g. a sign-up link. */ action?: ReactNode; /** href of the active link at start. Defaults to the first item. */ active?: string; /** Called on click. Call e.preventDefault() to stop the browser following the link. */ onSelect?: (item: GlassNavItem, e: MouseEvent) => void; /** Pixels scrolled before the glass turns solid. */ threshold?: number; /** Scrolling box to watch. Leave out to watch the page. */ container?: RefObject; /** Colour of the active underline. */ accent?: string; /** Name read out by screen readers. */ label?: string; className?: string; }; /* GlassNavbar: a frosted top bar that firms up once the page scrolls. Fits its box: links on wide, a menu button on narrow. */ export function GlassNavbar({ brand, items, action, active, onSelect, threshold = 24, container, accent = "#a78bfa", label = "Main", className = "text-sm text-white", }: GlassNavbarProps) { const reduce = useReducedMotion(); const uid = useId(); const toggle = useRef(null); const [scrolled, setScrolled] = useState(false); const [open, setOpen] = useState(false); const [current, setCurrent] = useState(active ?? items[0]?.href); const { scrollY } = useScroll({ container }); useMotionValueEvent(scrollY, "change", (y) => setScrolled(y > threshold)); const pick = (item: GlassNavItem, e: MouseEvent) => { setCurrent(item.href); setOpen(false); onSelect?.(item, e); }; const ease = reduce ? { duration: 0 } : { duration: 0.3, ease: "easeOut" as const }; return (
{ if (e.key === "Escape" && open) { setOpen(false); toggle.current?.focus(); } }} >
{brand}
{action &&
{action}
}
{open && ( {action &&
{action}
}
)}
); }