import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react"; import { CATEGORIES, PAGES, SHOP } from "../config/shop.ts"; import { checkoutUrl, count, load, server, subscribe, total } from "../lib/cart.ts"; import { useMediaQuery } from "../lib/hooks.ts"; import { collectionUrl, lineUrl, money } from "../lib/shop.ts"; import { get as getMode, server as modeServer, subscribe as onMode, toggle } from "../client/mode.ts"; import { Icon } from "./Icon.jsx"; const CART = "/cart/"; const HOVER = 180; const LEAVE = 260; const LIMIT = 12; /* SEARCH */ let index = null; async function hits() { if (index) return index; try { index = await (await fetch("/search.json")).json(); } catch { index = []; } return index; } function Finder({ links, on }) { const [text, setText] = useState(""); const [found, setFound] = useState([]); const box = useRef(null); useEffect(() => { if (on) box.current?.focus(); }, [on]); useEffect(() => { const want = text.trim().toLowerCase(); if (!want) return; let live = true; const words = want.split(/\s+/); void hits().then((all) => { if (!live) return; setFound(all.filter((hit) => words.every((word) => `${hit.name} ${hit.kind} ${hit.tags ?? ""}`.toLowerCase().includes(word))).slice(0, LIMIT)); }); return () => { live = false; }; }, [text]); const empty = !text.trim(); return ( ); } /* BAG */ function Purse({ items, on }) { const n = count(items); return ( ); } /* HEADER */ export function Header({ route, catalog = [], fly = false }) { const links = [{ slug: "all", name: "All", href: SHOP }, ...CATEGORIES.map(([slug, name]) => ({ slug, name, href: `${SHOP}${slug}/` })), { slug: "pages", name: "Pages", href: "/pages/" }]; const items = useSyncExternalStore(subscribe, load, server); const mode = useSyncExternalStore(onMode, getMode, modeServer); const wide = useMediaQuery("(min-width: 768px)"); const [open, setOpen] = useState(""); const [level, setLevel] = useState(1); const timer = useRef(0); const n = count(items); const show = useCallback((name, deep = 1) => { clearTimeout(timer.current); setOpen(name); setLevel(deep); }, []); const hide = useCallback(() => { clearTimeout(timer.current); setOpen(""); setLevel(1); }, []); const later = useCallback( (name, wait) => { clearTimeout(timer.current); timer.current = setTimeout(() => (name ? show(name) : hide()), wait); }, [hide, show], ); useEffect(() => () => clearTimeout(timer.current), []); useEffect(() => { document.documentElement.classList.toggle("held", Boolean(open) && !wide); }, [open, wide]); useEffect(() => hide(), [hide, route, wide]); useEffect(() => { if (!open) return; const key = (event) => { if (event.key === "Escape") hide(); }; const away = (event) => { if (event.target instanceof Element && !event.target.closest(".top")) hide(); }; document.addEventListener("keydown", key); document.addEventListener("click", away); return () => { document.removeEventListener("keydown", key); document.removeEventListener("click", away); }; }, [hide, open]); const sticky = open === "search" || open === "bag" || (open && !wide); const current = (href) => (route === href || (href !== SHOP && route.startsWith(href)) ? "page" : undefined); const rows = (slug) => slug === "all" ? CATEGORIES.map(([one, name]) => ({ title: name, href: `${SHOP}${one}/` })) : slug === "pages" ? PAGES.filter((one) => one.href !== CART).map((one) => ({ title: one.name, href: one.href })) : catalog.filter((row) => row.category === slug).map((row) => ({ title: row.title, href: collectionUrl(row.handle) })); return (
!sticky && later("", LEAVE)}>
!sticky && later("", LEAVE)}> TheBird
{ if (!wide) return; event.preventDefault(); if (open === "bag") hide(); else show("bag"); }} >
clearTimeout(timer.current)}>
{links.map((one) => ( ))}
); }