From daccb2d2231a9b8742dbc9bf05c4d19f4e8e09e3 Mon Sep 17 00:00:00 2001 From: Tyler Date: Fri, 19 Jun 2026 19:36:41 -0600 Subject: [PATCH] feat(frontend): add FilterChips primitive --- src/components/ui/filter-chips.tsx | 84 ++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/components/ui/filter-chips.tsx diff --git a/src/components/ui/filter-chips.tsx b/src/components/ui/filter-chips.tsx new file mode 100644 index 0000000..a8f4f49 --- /dev/null +++ b/src/components/ui/filter-chips.tsx @@ -0,0 +1,84 @@ +import { X } from "lucide-react"; +import { cn } from "@/lib/utils"; + +export type FilterChipOption = { value: string; label: string }; + +type FilterChipsProps = { + /** Available chip options. Order is preserved. */ + options: FilterChipOption[]; + /** Currently selected value, or null for "all". */ + value: string | null; + /** Receives the new value, or null when "Clear" is pressed. */ + onChange: (value: string | null) => void; + /** Optional eyebrow label rendered before the chips. */ + eyebrow?: string; + className?: string; +}; + +/** + * Single-select filter chips. + * Voice: hairline-bordered pills; the active one fills with the electric + * blue accent at low opacity and lifts a hairline brighter than its + * peers. A small "Clear" pill appears once a value is set. + */ +export function FilterChips({ + options, + value, + onChange, + eyebrow, + className, +}: FilterChipsProps) { + return ( +
+ {eyebrow ? ( + + {eyebrow} + + ) : null} + {options.map((opt) => { + const isActive = opt.value === value; + return ( + + ); + })} + {value !== null ? ( + + ) : null} +
+ ); +}