components/chips/chips.react.tsx
65 lines and 12 functions in DrDustinEdwards/capsomer, at commit d3966cc on 2026-10-24. This file on GitHub.
Functions
| Function | Lines | Complexity | Covered | CRAP | Mutation |
|---|---|---|---|---|---|
| FilterChips | 24 to 69 | 2 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| <anonymous:31> | 31 to 31 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| <anonymous:32> | 32 to 32 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| set | 33 to 33 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| <anonymous:33> | 33 to 33 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| <anonymous:33> | 33 to 33 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| toggle | 34 to 39 | 2 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| clear | 40 to 44 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| <anonymous:51> | 51 to 55 | 4 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| <anonymous:52> | 52 to 52 | 2 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| <anonymous:56> | 56 to 60 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
| <anonymous:57> | 57 to 57 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: mutation testing has not run on this repo yet |
Imports
It imports no other module of this repo. No module of this repo imports it.
Source
import { useId, useRef, type ReactNode } from "react";
export interface ChipOption<V extends string> {
value: V;
label: ReactNode;
disabled?: boolean;
}
export interface FilterChipsProps<V extends string> {
// The visible label of the group: "Status".
label: ReactNode;
// Up to six.
options: Array<ChipOption<V>>;
// Controlled: the pressed values, in any order.
value: V[];
// Called with the pressed values in the options' order. Keep them in the address
// (writeParam from "capsomer/behaviour/chips" makes ?status=blocked,running).
onChange: (value: V[]) => void;
// What the filter leaves, in words: "3 of 14 jobs". Announced politely as it changes.
count: ReactNode;
size?: "sm" | "default" | "lg";
}
export function FilterChips<V extends string>({ label, options, value, onChange, count, size = "default" }: FilterChipsProps<V>) {
const labelId = useId();
const first = useRef<HTMLButtonElement>(null);
if (options.length > 6) console.warn("FilterChips: chips hold at most six values; use a select or a combobox for more.");
const pressed = new Set(value);
// A pressed value that no option carries (from a shared address) still shows, pressed,
// after the options, so a filter is never active without a chip to say so and release it.
const known = new Set(options.map((o) => o.value));
const extra = value.filter((v) => !known.has(v));
const set = (next: Set<V>) => onChange([...options.map((o) => o.value), ...extra].filter((v) => next.has(v)));
const toggle = (v: V) => {
const next = new Set(pressed);
if (next.has(v)) next.delete(v);
else next.add(v);
set(next);
};
const clear = () => {
set(new Set());
// Clear hides itself; focus goes to the first chip rather than being lost.
first.current?.focus();
};
return (
<div className="cap-chips" role="group" aria-labelledby={labelId}>
<span className="cap-chips-label" id={labelId}>
{label}
</span>
{options.map((o, i) => (
<button key={o.value} ref={i === 0 ? first : undefined} type="button" className="cap-chip" data-size={size === "default" ? undefined : size} aria-pressed={pressed.has(o.value)} aria-disabled={o.disabled || undefined} data-value={o.value} onClick={() => (o.disabled ? undefined : toggle(o.value))}>
{o.label}
</button>
))}
{extra.map((v) => (
<button key={v} type="button" className="cap-chip" aria-pressed="true" data-value={v} onClick={() => toggle(v)}>
{v}
</button>
))}
<button type="button" className="cap-link-btn" data-cap-part="clear" hidden={pressed.size === 0} onClick={clear}>
Clear
</button>
<span className="cap-chips-count" role="status">
{count}
</span>
</div>
);
}