import type {
ComponentPropsWithRef,
ReactNode,
} from "react"
import type { BadgeShapeProps } from "./useBadgeShape.tsx"
import { useBadgeShape } from "./useBadgeShape.tsx"
export type BadgeProps = ComponentPropsWithRef<"span"> &
BadgeShapeProps & {
/**
* The exclusivity, and it is deliberately the *only* thing this
* arm says. Both props stay declared on `BadgeShapeProps` so
* `react-docgen` can see them and `storyControls.test.ts` can
* require a control for each; this arm's whole job is to refuse
* the case where both arrive with a value.
*/
} & ({ categorical?: never } | { intent?: never })
/**
* Four repos, and rip-deck declares the **identical** `TONE_CLASS`
* map twice — `VerdictBadge.tsx` and `TowerAlerts.tsx`, same five
* hardcoded hexes, no relationship to each other beyond
* copy-paste. mux-magic's `statusClassMap` is the same idea spelled
* a third way, keyed by a bare `string` so an unrecognised status
* renders with no colour at all and no error.
*
* `intent` replaces all of it. The **status** half of the problem —
* which state maps to which intent, and what it is called in
* English — lives in `statusIntent.ts` as exhaustive switches over
* `@charcuterie/logic`'s shared machines, so
* `{getAsyncLabel(status)}`
* is the whole of mux-magic's `StatusBadge`, and adding a state to
* the machine is a compile error rather than a blank pill.
*
* **No implicit role.** A badge is a word about something else; it
* is not a live region, and giving it `role="status"` would make
* every re-render announce itself. Where a status genuinely needs
* announcing that is `LiveStatusIndicator`'s job, and where a badge
* needs a name beyond its text, `aria-label` still works. The story
* asserts the text is queryable, which is what an agent actually
* matches on.
*
* **`intent` or `categorical`, never both.** The two families are
* both seven roles wide and both paint a pill, and they answer
* opposite questions: an intent is a *claim* the design system
* makes (`danger` says what happens if you press the thing), and a
* categorical index is a claim about nothing at all — it is a
* colour a user picked for a label. Which is why the exclusivity is
* in the type rather than in a precedence rule: a badge is one
* colour, and `` is a
* question with no answer.
*
* **It is capped at its container.** `shrink-0` and
* `whitespace-nowrap` alone let a long label paint straight through
* the neighbouring column — no clipping, no error, just a pill lying
* across the next cell. `max-inline-size: 100%` is what stops it, and
* `overflow` is what happens next.
*/
export const Badge = ({
appearance,
categorical,
children,
className,
icon,
intent,
overflow,
size,
title,
...spanProps
}: BadgeProps): ReactNode => {
const shape = useBadgeShape({
appearance,
categorical,
children,
className,
icon,
intent,
overflow,
size,
title,
})
return (
{shape.content}
)
}