// FacetGroups.jsx — §22.4a SLICE-3 faceted left-pane filters (§5.1). // // Renders one collapsible group per facet field returned by the API, each with // per-value result counts and multi-select checkboxes. A `tags`-type field gets // a "filter values…" search box so it stays usable at 30+ values. Selection // state + the malformed toggle are owned by the parent (Catalog), which // re-fetches server-side on change. import { useState } from 'react' // Human label for the built-in state facet; declared fields use their own name // (or the schema `label` when the API supplies it on `fields`). function groupLabel(field, fields) { if (field === 'state') return 'State' const def = fields?.[field] return def?.label || field.charAt(0).toUpperCase() + field.slice(1) } function FacetGroup({ field, fields, counts, selected, onToggle, isTags }) { const [open, setOpen] = useState(true) const [valueSearch, setValueSearch] = useState('') const entries = Object.entries(counts).sort((a, b) => b[1] - a[1]) const needle = valueSearch.trim().toLowerCase() const shown = isTags && needle ? entries.filter(([v]) => v.toLowerCase().includes(needle)) : entries return (
{open && (
{isTags && entries.length > 8 && ( setValueSearch(e.target.value)} /> )} {shown.length === 0 && (
No values.
)} {shown.map(([value, count]) => ( ))}
)}
) } export default function FacetGroups({ facets, fields, selections, onToggle, malformedOnly, onToggleMalformed, onClear, hasActiveFilters, }) { // The API returns facets in field order with `state` last; render in that // order. `selections` is { field: Set }. const fieldOrder = Object.keys(facets || {}) // A `tags`-type field renders the value-search box. const isTags = (field) => fields?.[field]?.type === 'tags' return (
{hasActiveFilters && ( )} {fieldOrder.map(field => ( ))}
) }