import { forwardRef } from "react"; import type { ButtonHTMLAttributes, ReactNode } from "react"; import clsx from "clsx"; import { X } from "lucide-react"; import { FocusSafeButton } from "./FocusSafeButton"; import { chipClass, type ChipSize, type ChipVariant } from "./chipStyles"; // The chip family (docs/DESIGN.md "One primitive per button family"): a small rounded-full token that // is a filter, an applied narrowing, a tag, or a suggestion. It was hand-painted in five files with // five geometries; this is the one. `FilterChipGroup` renders a single-select row of these. // // filter — a selectable option (a year, a tag filter, a content type). Toggles with `pressed`: the // `.pill` recipe in index.css paints the solid accent when on. Also the "+N" overflow and // "More…" triggers, which light up while their popover is open (aria-expanded). // token — an applied narrowing, tinted accent (the `.chip` recipe): it exists because it is on. // tag — a user's tag, the solid accent (`.chip-solid`). Dense: it sits in table rows. // suggest — dashed accent: a suggestion not yet taken. // ghost — dashed neutral: the "add a tag" trigger, quiet in every table row until reached for. // // Nothing goes under 24px (B1, WCAG 2.5.8) and a coarse pointer grows every chip to 44 (A3). type ChipProps = ButtonHTMLAttributes & { variant?: ChipVariant; size?: ChipSize; /** A filter chip that toggles: aria-pressed, painted solid when on. */ pressed?: boolean; /** Leading glyph (a lucide element). */ icon?: ReactNode; /** The whole chip removes what it stands for: a trailing × and the removal as its name. */ removeLabel?: string; }; /** A chip that is itself the control (a toggle, a removal, an accept, a "+N" trigger). */ export const Chip = forwardRef(function Chip( { variant = "filter", size = "sm", pressed, icon, removeLabel, type, className, children, ...rest }, ref, ) { return ( {icon} {children} {removeLabel && ); }); /** A chip whose label is not the control but which carries its own trailing × (a captured filter in * a saved view, a suggestion with a dismiss). Two sibling controls at most, never a button in a * button: `onClick` makes the body one (an accept), `onDismiss` the ×. */ export function DismissibleChip({ variant = "token", size = "sm", icon, children, onClick, bodyLabel, bodyTitle, onDismiss, dismissLabel, className, }: { variant?: Exclude; size?: ChipSize; icon?: ReactNode; children: ReactNode; onClick?: () => void; bodyLabel?: string; bodyTitle?: string; onDismiss: () => void; dismissLabel: string; className?: string; }) { const body = ( <> {icon} {children} ); return ( {onClick ? ( {body} ) : ( {body} )} ); }