"use client"; import { DISCIPLINE_LABELS, RESEARCH_MODE_LABELS } from "@autoscholar/schemas"; import Link from "next/link"; import { useEffect, useRef, useState } from "react"; import { IconClose, IconFilter, IconSearch } from "./Icons"; type Q = Record; const FILTER_KEYS = ["discipline", "mode", "series", "status", "from", "to", "code", "data"] as const; function hrefWithout(q: Q, key: string) { const u = new URLSearchParams(); for (const [k, v] of Object.entries(q)) if (v && k !== key && k !== "page") u.set(k, v); const s = u.toString(); return s ? `/research?${s}` : "/research"; } function Fields({ q, series, idPrefix, }: { q: Q; series: Array<{ slug: string; name: string }>; idPrefix: string; }) { const id = (s: string) => `${idPrefix}-${s}`; return (
Availability
); } /** Active-filter chips (applied filters); each chip removes its own filter. */ export function ActiveFilters({ q, series }: { q: Q; series: Array<{ slug: string; name: string }> }) { const labels: Array<[string, string]> = []; if (q.discipline) labels.push(["discipline", (DISCIPLINE_LABELS as Record)[q.discipline] ?? q.discipline]); if (q.mode) labels.push(["mode", (RESEARCH_MODE_LABELS as Record)[q.mode] ?? q.mode]); if (q.series) labels.push(["series", series.find((s) => s.slug === q.series)?.name ?? q.series]); if (q.status) labels.push(["status", q.status]); if (q.from) labels.push(["from", `from ${q.from}`]); if (q.to) labels.push(["to", `to ${q.to}`]); if (q.code === "1") labels.push(["code", "has code"]); if (q.data === "1") labels.push(["data", "has data"]); if (labels.length === 0) return null; return (
    {labels.map(([k, l]) => (
  • {l}
  • ))}
  • Clear all
); } /** * LibraryFilters — search bar + sort on every width; facet sidebar on desktop, bottom-sheet on mobile with an * active-filter count. Plain GET form so it works without JavaScript. */ export function LibraryFilters({ q, series, total, }: { q: Q; series: Array<{ slug: string; name: string }>; total: number; }) { const [open, setOpen] = useState(false); const active = FILTER_KEYS.filter((k) => q[k]).length; const sheet = useRef(null); useEffect(() => { if (!open) return; const prev = document.body.style.overflow; document.body.style.overflow = "hidden"; sheet.current?.querySelector("select,input,button")?.focus(); const onKey = (e: KeyboardEvent) => e.key === "Escape" && setOpen(false); document.addEventListener("keydown", onKey); return () => { document.body.style.overflow = prev; document.removeEventListener("keydown", onKey); }; }, [open]); const hidden = (prefix: string) => ( <> {q.q && } {q.sort && } {prefix} ); return ( {/* search + sort row */}
{FILTER_KEYS.map((k) => q[k] && )}

{total === 0 ? "No results" : `${total} ${total === 1 ? "paper" : "papers"}`} {q.q ? ` for “${q.q}”` : ""}

{/* mobile bottom sheet */} {open && ( <>
{hidden("Filters")}
Reset
)}
); } /** Desktop facet sidebar (plain GET form). Rendered in the library's left column. */ export function FilterSidebar({ q, series }: { q: Q; series: Array<{ slug: string; name: string }> }) { return (
{q.q && } {q.sort && }

Refine

Reset
); }