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 | 81x 324x 324x 324x 6x | import { cn } from "../../lib/utils";
import { PROJECT_STATUS_FACETS, type ProjectStatus } from "./project-status";
/**
* The projects list's status facets: `ALL 12 LIVE 8 DRAFT 3 ARCHIVED 1`.
*
* A facet is a `.label` CODE WITH A COUNT, not a pill and not a dropdown —
* `.context/mockups/projects-list.html`, `.facet` in `_base.css`. The pressed
* facet is marked by ink and ground, never by index red: selection is state,
* and index red is never used for state.
*
* WHY NOT `FacetedFilter`
* That primitive is a multi-select inside a popover with checkboxes and a
* `bg-primary-500` count pill. The mockup prints the four codes flat on the
* toolbar so the whole shape of the portfolio is readable without a click, and
* makes them single-select — which is what made archived reachable at all:
* the old default selection was `{draft, published}`, so an archived project
* had no page where it appeared and its unarchive action was unreachable.
*/
export interface ProjectFacetsProps {
/** `""` is the All facet. */
value: "" | ProjectStatus;
onChange: (value: "" | ProjectStatus) => void;
/** How many projects each facet matches. `all` is the unfiltered total. */
counts: {
all: number;
draft: number;
published: number;
archived: number;
};
className?: string;
}
export function ProjectFacets({
value,
onChange,
counts,
className,
}: ProjectFacetsProps) {
return (
// `fieldset` rather than `div role="group"`: native grouping semantics,
// and Biome's useSemanticElements rule rightly rejects the ARIA stand-in
// when an element exists. `min-w-0` defeats fieldset's min-content
// sizing quirk inside a flex row; the border/padding/margin resets undo
// the UA stylesheet.
<fieldset
className={cn(
"m-0 flex min-w-0 flex-wrap items-center gap-0.5 border-0 p-0",
className,
)}
>
<legend className="sr-only">Filter by status</legend>
{PROJECT_STATUS_FACETS.map((facet) => {
const count = facet.value === "" ? counts.all : counts[facet.value];
const isActive = facet.value === value;
return (
<button
key={facet.value || "all"}
type="button"
aria-pressed={isActive}
onClick={() => onChange(facet.value)}
className={cn(
// A 32px dense control: the toolbar tier.
"inline-flex 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-2 focus-visible:ring-primary-500",
isActive
? "bg-background-muted text-foreground-default"
: "text-foreground-muted hover:bg-foreground-default/7 hover:text-foreground-default",
)}
>
{facet.label}
<span
className={cn(
"tabular-nums",
isActive ? "text-foreground-default" : "text-foreground-subtle",
)}
>
{count}
</span>
</button>
);
})}
</fieldset>
);
}
|