buttonStyles/chipStyles glued the hover guard on at runtime (`${ACT}hover:text-fg`). Tailwind emits CSS
only for a class it finds whole in the source, so all 22 guarded hovers shipped with no rule: no text or
edge colour on hover for ghost/outline/danger buttons, no white layer on the player's control row, no
underline on a link button, no accent hover on token/suggest/ghost chips. The stylesheet had 0 rules
for `not-aria-disabled`.
- `@custom-variant act` (index.css): the same guard as the state layers; `:disabled` never matches an
<a>, so one variant serves buttons and links and the per-element variant maps go.
- ui/tailwindScan.contract.test.ts: every class the primitives can emit stands whole in their source.
- a lint rule refuses a variant right after an interpolation; the `enabled:hover:` rule now points at
`act:hover:`.
39 lines
1.9 KiB
TypeScript
39 lines
1.9 KiB
TypeScript
import clsx from "clsx";
|
|
|
|
// The chip family's geometry and paint, apart from the component (`Chip.tsx`) so the hidden measuring
|
|
// mirror of an overflowing chip lane can lay out exactly like the chips it measures.
|
|
|
|
export type ChipVariant = "filter" | "token" | "tag" | "suggest" | "ghost";
|
|
export type ChipSize =
|
|
| "md" // 28, text-sm — the applied-filter row above the feed
|
|
| "sm" // 24, text-xs — a filter row
|
|
| "xs"; // 24, text-2xs, tight — a tag in a table row
|
|
|
|
const PAINT: Record<ChipVariant, string> = {
|
|
// `.pill` owns the resting, hover AND pressed colours; a colour utility here would beat the pressed
|
|
// fill, so the open-popover state is the only one spelled out.
|
|
filter: "pill border shadow-xs aria-expanded:border-accent aria-expanded:text-accent",
|
|
token: "chip act:hover:border-accent act:hover:bg-accent/20 aria-expanded:border-accent",
|
|
// B3: a filled control's hover mixes its own text colour into the fill, never an opacity dim.
|
|
tag: "chip-solid font-medium act:hover:bg-[color-mix(in_srgb,var(--accent-fg)_14%,var(--accent))]",
|
|
suggest:
|
|
"border border-dashed border-accent/50 text-accent/90 act:hover:text-accent act:hover:border-accent",
|
|
ghost:
|
|
"border border-dashed border-border text-muted act:hover:text-accent act:hover:border-accent",
|
|
};
|
|
|
|
const SIZES: Record<ChipSize, string> = {
|
|
md: "h-7 px-2.5 text-sm gap-1",
|
|
sm: "h-6 px-2.5 text-xs gap-1.5",
|
|
xs: "h-6 px-1.5 text-2xs gap-1",
|
|
};
|
|
|
|
const BASE =
|
|
"inline-flex min-w-6 shrink-0 items-center justify-center rounded-full whitespace-nowrap transition touch-target disabled:opacity-50 aria-disabled:opacity-50 [&>svg]:shrink-0";
|
|
|
|
/** The chip's geometry + paint as a class string: for the hidden measuring mirror of an overflowing
|
|
* chip lane, which must lay out exactly like the chips it measures but must not be buttons. */
|
|
export function chipClass(variant: ChipVariant = "filter", size: ChipSize = "sm") {
|
|
return clsx(BASE, PAINT[variant], SIZES[size]);
|
|
}
|