feat(frontend): add FilterChips primitive

This commit is contained in:
Tyler
2026-06-19 19:36:41 -06:00
parent 511230ec04
commit daccb2d223
+84
View File
@@ -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 (
<div
className={cn(
"flex flex-wrap items-center gap-1.5",
className,
)}
role="radiogroup"
aria-label="Filter"
>
{eyebrow ? (
<span className="mr-1 text-[10.5px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
{eyebrow}
</span>
) : null}
{options.map((opt) => {
const isActive = opt.value === value;
return (
<button
key={opt.value}
type="button"
role="radio"
aria-checked={isActive}
onClick={() => onChange(isActive ? null : opt.value)}
className={cn(
"h-7 rounded-full px-3 text-xs font-medium transition-colors",
"ring-1 ring-inset focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
isActive
? "bg-accent/15 text-accent ring-accent/40 hover:bg-accent/20"
: "bg-transparent text-muted-foreground ring-border/60 hover:text-foreground hover:ring-border",
)}
>
{opt.label}
</button>
);
})}
{value !== null ? (
<button
type="button"
onClick={() => onChange(null)}
aria-label="Clear filter"
className={cn(
"h-7 rounded-full px-2.5 text-xs font-medium transition-colors",
"inline-flex items-center gap-1",
"text-muted-foreground hover:text-foreground",
"ring-1 ring-inset ring-border/40 hover:ring-border/60",
)}
>
<X className="h-3 w-3" strokeWidth={1.75} aria-hidden />
Clear
</button>
) : null}
</div>
);
}