The second half of the button sweep: the remaining hand-rolled buttons in
feature components, the design lab and the components/ui composites now
stand on Button, IconButton, Pressable, Chip or DragHandle.
- Chip + DismissibleChip (filter / token / tag / suggest / ghost, 24px
floor, 44 on touch) replace five chip geometries; FilterChipGroup
renders Chips; overflow lanes measure a mirror painted by chipClass.
- The Plex control row is IconButton's player rung (36, 20px glyph,
round, overlay; 44 on touch); the YT modal bar is on the sm rung with
pressed cycle toggles and names that survive a folded label.
- New variants: IconButton ghost-accent, danger-soft; Button warn on a
new state-warn utility. The .btn-{tone} recipes and btnCls are gone.
- Fixed on the way: membership rows (collection editor, Plex playlist
add) announce checked/mixed; a one-letter tag chip was 21px wide;
descriptionLinks moved out of lib/ (it renders components).
- local/button-primitive: a raw <button>/<FocusSafeButton> outside the
control-primitive files is a lint error (contract-tested, seen red).
38 lines
2.0 KiB
TypeScript
38 lines
2.0 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 ACT = "enabled:not-aria-disabled:";
|
|
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]);
|
|
}
|