All files / src/components/admin FacetRow.tsx

100% Statements 4/4
100% Branches 6/6
100% Functions 3/3
100% Lines 4/4

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88                                                                                                160x           630x 630x         3x                                                      
import { cn } from "../../lib/utils";
 
/**
 * A row of single-select facets — `.facet` from `.context/mockups/_base.css`
 * §Controls, in Tailwind.
 *
 * WHAT IT REPLACES
 * The admin library pages filtered by category with a row of filled
 * `rounded-full` chips. A full-radius filled chip is a pill, and the Job Book
 * has no pills: the printed vocabulary is a 2px-radius `.label` in caps on a
 * transparent ground, tinting on hover, and marking the selected one with a
 * 2px inset underline in page ink. Index red is NOT used here — it marks the
 * current item as a 3px inset bar and nothing else, and a filter is not the
 * current item.
 *
 * `aria-pressed` carries the selection, which is what the mockup's
 * `.facet[aria-pressed="true"]` selector keys off and what a screen reader
 * needs to hear. A filled ground alone would have said it only in colour.
 *
 * The group is a real `<fieldset>` with an `sr-only` `<legend>` rather than a
 * `div[role="group"]`: same semantics, the element that means it.
 *
 * Single-select and exclusive by design — `FacetedFilter` in `components/ui`
 * is the multi-select dropdown and a different control.
 */
export interface FacetOption {
	value: string;
	label: string;
	/** Right-aligned tabular count, when there is something to count. */
	count?: number;
}
 
export interface FacetRowProps {
	/** The group's accessible name, e.g. "Filter by category". */
	label: string;
	options: readonly FacetOption[];
	value: string;
	onChange: (value: string) => void;
	className?: string;
}
 
export function FacetRow({
	label,
	options,
	value,
	onChange,
	className,
}: FacetRowProps) {
	return (
		<fieldset
			className={cn("flex flex-wrap items-center gap-1", className)}
		>
			<legend className="sr-only">{label}</legend>
			{options.map((option) => {
				const isActive = option.value === value;
				return (
					<button
						key={option.value}
						type="button"
						aria-pressed={isActive}
						onClick={() => onChange(option.value)}
						className={cn(
							"ctl-32 inline-flex min-h-8 items-center gap-1.5 rounded-sm px-2.5",
							"text-xs font-semibold uppercase tracking-wider",
							"transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-foreground-default",
							isActive
								? "bg-foreground-default/7 text-foreground-default shadow-[inset_0_-2px_0_var(--foreground-default)]"
								: "text-foreground-muted hover:bg-foreground-default/7 hover:text-foreground-default",
						)}
					>
						{option.label}
						{option.count !== undefined && (
							<span
								className={cn(
									"tabular-nums",
									isActive ? "text-foreground-default" : "text-foreground-subtle",
								)}
							>
								{option.count.toLocaleString("en-IN")}
							</span>
						)}
					</button>
				);
			})}
		</fieldset>
	);
}