"use client"; import { useReducedMotion } from "../motion/Preferences.tsx"; import { useEffect, useState, type ReactNode } from "react"; import { AnimatePresence, motion, useScroll, useSpring, useTransform, } from "motion/react"; import { Dialog } from "./Dialog.tsx"; export type NavLink = { href: string; label: string }; export function Nav({ brand, links, currentPath, trackChapters = true, className = "", }: { brand: ReactNode; links: NavLink[]; currentPath?: string; trackChapters?: boolean; className?: string; }) { const reduce = useReducedMotion(); const path = currentPath ?? (typeof window !== "undefined" ? window.location.pathname : ""); const { scrollYProgress } = useScroll(); const scaleX = useSpring(scrollYProgress, { stiffness: 140, damping: 28, mass: 0.4, }); const progress = reduce ? scrollYProgress : scaleX; const nodeLeft = useTransform( progress, (v) => `calc(${(v * 100).toFixed(3)}% - 3px)`, ); const [chapter, setChapter] = useState(""); const [open, setOpen] = useState(false); useEffect(() => { if (!trackChapters) return; const sections = Array.from(document.querySelectorAll("[data-chapter]")); if (sections.length === 0) return; const io = new IntersectionObserver( (entries) => { for (const e of entries) { if (e.isIntersecting) setChapter(e.target.getAttribute("data-chapter") ?? ""); } }, { rootMargin: "-40% 0px -55% 0px" }, ); sections.forEach((s) => io.observe(s)); return () => io.disconnect(); }, [trackChapters]); return ( <> {brand} {chapter && ( {chapter} )} {links.map((l) => { const active = path === l.href; return ( {l.label} ); })} setOpen(true)} aria-label="Open menu" aria-expanded={open} className="mizu-nav-burger" type="button" style={{ background: "transparent", border: "none", fontFamily: "var(--mizu-font-mono)", fontSize: 11, letterSpacing: "0.22em", textTransform: "uppercase", color: "var(--mizu-muted)", cursor: "pointer", }} > Menu {brand} setOpen(false)} style={{ background: "transparent", border: "none", fontFamily: "var(--mizu-font-mono)", fontSize: 11, letterSpacing: "0.22em", textTransform: "uppercase", color: "var(--mizu-muted)", cursor: "pointer", }} > Close {links.map((l, i) => ( setOpen(false)} style={{ display: "flex", alignItems: "baseline", gap: 20, borderBottom: "1px solid var(--mizu-line)", padding: "24px 0", textDecoration: "none", color: "var(--mizu-paper)", fontFamily: "var(--mizu-font-display)", }} className="mizu-nav-overlay-link" > 0{i + 1} {l.label} ))} Mizu — Interface Archive > ); }
Mizu — Interface Archive