import type { ComponentPropsWithRef, ReactNode } from "react"; import type { BadgeShapeProps } from "../Badge/useBadgeShape.tsx"; export type BadgeButtonProps = Omit, "disabled"> & BadgeShapeProps & { /** * Required, and that is the component's argument in one line: a * pill nobody can press is a `Badge`, and a pill that looks * pressable and is not is the defect this exists to stop. */ onClick: NonNullable["onClick"]>; /** * `isDisabled`, not `disabled` — the same prop `Button` keeps * instead of the native one, so a control's disabled state is * spelled one way across the library. */ isDisabled?: boolean; } & ({ categorical?: never; } | { intent?: never; }); /** * A badge you can press. * * A sibling component rather than a prop, exactly as `ButtonLink` is * to `Button`: the paint is shared (both call `useBadgeShape`, so * there is one class list and `BadgeButton.test.tsx` compares the * two elements' *computed* styles rather than trusting that), and * what differs is the element and everything the platform hangs off * it. * * **Why not `asChild`, and why not `Badge` growing an `onClick`.** * This library has no polymorphism pattern and has not needed one; * adding it here would mean every future component answering "can it * be something else?" instead of "what is it?". And an element type * that changes as a *side effect* of passing a handler is a footgun * with no error: forget the handler and a control silently ships as * a `` — not focusable, not in the tab order, invisible to * `getByRole("button")` and to every agent driving the app. * * **What the element buys, and none of it is paint.** Focus, the tab * order, Enter and Space, `:disabled`, form participation, and a * `button` role a screen reader announces as pressable. QueuePilot * had six pills doing all of this with a hand-rolled * `