/**
* Interaction & visual variant prop types.
* @see docs/PROPS-VOCABULARY.md#interaction-variants
*/
import type * as React from "react";
/**
* Button visual style.
*
* `bare` (gh#404) is the control WITHOUT geometry: a real button — hitbox, focus ring, keyboard
* semantics, `aria-label` — around content that already IS the shape (a `Badge`, a truncating
* title). Every other variant loads a height and an inline inset from its `size`, so wrapping a
* chip meant writing `className="h-auto p-0"`, an audit error with no legal replacement; `link`
* was not it either, since it still loads a size class and adds primary colour plus a hover
* underline. The 24×24 target WCAG 2.2 SC 2.5.8 requires is kept by a pseudo-element, so the box
* stays exactly as big as its content.
*/
export type ButtonVariantProp = "default" | "destructive" | "outline" | "dashed" | "secondary" | "ghost" | "link" | "bare";
/**
* Corner shape — maps to the radius tokens (default = control/component radius). Shared by Button
* + Badge.
*/
export type ShapeProp = "default" | "pill" | "sharp";
/**
* Avatar geometry — WHAT the mark represents, not just its corner radius, which is why it is a
* separate vocabulary from the control `ShapeProp` (`default | pill | sharp`): an entity mark is a
* ROUNDED rect, a value `ShapeProp` cannot express (its `sharp` = `--radius-sharp` = 0).
* - `circle` (default) — a person: the fully-round `--radius-pill` identity avatar.
* - `square` — an organization / service entity mark for an entity header: the compact rounded
* square on the brand surface (`--avatar-square-*` tokens).
*/
export type AvatarShapeProp = "circle" | "square";
/**
* Text size — steps of the type scale (NEVER an arbitrary px). `sm` = base.
*
* TEN steps, in TWO ramps that meet at `2xl`, exactly where Tailwind's own scale stops naming
* t-shirt sizes and starts counting (`xl` → `2xl` → `3xl`):
*
* - `2xs`…`2xl` — the UI ramp. `--font-size-base` × `--font-size-ratio`ⁿ (φ^¼), ≈11…22px. This is
* the dense enterprise scale and nothing about it moves.
* - `3xl`…`5xl` — the DISPLAY ramp, ≈28/42/54px, for a marketing hero / CTA headline. Its own
* base is `--font-size-display` and its own step is `--font-size-display-ratio` (1.28, bolder
* than the body's φ^¼ because marketing wants more contrast), so a service retunes the whole
* display ramp without touching a single admin screen.
*
* The four display tokens shipped at `foundation.css:513-521` with NOTHING public able to reach
* them, so every marketing page wrote its own `font-size` class instead — 58 bespoke classes
* across two showcases (gh#826). These three steps ARE that public surface. `--font-size-display`
* itself is deliberately NOT a step: it is the ramp's single re-tuning knob and `5xl` already
* resolves to it, so a `"display"` member would be a second name for the same value.
*
* SHARED WITH `Heading size`, on purpose. Radix Themes is the prior art — one numeric ladder that
* `Heading` and `Text` both read, so "make this figure as big as that headline" is one step name
* rather than a lookup between two ramps. (Material 3 and Polaris take the other fork, named ROLES
* on separate ramps; that fork is equally defensible but it would put a second size vocabulary
* beside this one, which `check:prop-vocabulary` exists to prevent.)
*/
export type TextSizeProp = "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "5xl";
/**
* Icon size — the NINE steps of the `--icon-size-*` scale (docs/TOKENS.md · gh#326), not the four
* of the control `SizeProp`.
*
* The icon axis already had a vocabulary before it had a name, and it is a FIXED LIST rather than
* a ratio: 14/16 = 0.875 but 20/16 = 1.25, because a 1px-stroke glyph cannot sit on half a pixel.
* Naming the whole list here — as `TextSizeProp` does for type — is what lets a consumer put a
* standalone glyph on the scale (``) instead of reaching for a
* `className="size-4"` the consumer rules forbid.
*/
export type IconSizeProp = "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl";
/** Text colour intent — maps to semantic foreground tokens (no raw palette). */
/**
* `inherit` is the one that is NOT a colour: it takes whatever the surface already set (gh#643).
*
* Every other member paints an ABSOLUTE token, which is right on a page surface and wrong the
* moment the text sits on a coloured one — a brand fill, a tinted calendar block, a status band.
* There was no way to say "use this surface's own foreground", so `Text` on `bg-primary` painted
* `--foreground` over `--primary` and measured 2.45:1. Found by check:frame-axe on this package's
* OWN colour-token page, of all places, and on two showcases besides.
*/
export type TextToneProp = "default" | "muted" | "primary" | "success" | "warning" | "destructive" | "info" | "inherit";
/** Font weight — the reference-design canon is THREE weights only: `regular` (400 body), `medium` (500
* heading/label), `bold` (700 emphasis). 600/`semibold` is forbidden. */
export type FontWeightProp = "regular" | "medium" | "semibold" | "bold";
/** Heading level — drives both the `--heading-h*` size token and the semantic `
` element. */
export type HeadingLevelProp = 1 | 2 | 3 | 4;
/** Inline text alignment (logical, RTL-safe). */
export type TextAlignProp = "start" | "center" | "end";
/**
* How a run of text treats the whitespace it was given.
*
* `normal` is CSS's own behaviour and the default: newlines and runs of spaces collapse. That is
* right for a label and wrong for text a PERSON typed into a textarea — a plain-text note, a
* pasted log, an issue description — where the line breaks and the indentation ARE content.
*
* `pre-wrap` keeps both and still wraps at the container's edge, so the block cannot force a
* horizontal scrollbar the way `pre` would. It is a closed union rather than a boolean because the
* remaining CSS keywords (`pre`, `pre-line`, `nowrap`) are values on the same axis, not more flags.
*/
export type TextWhitespaceProp = "normal" | "pre-wrap";
/**
* antd `Typography` `type` — its four-value emphasis axis (`BaseType`).
*
* This library already publishes the same axis as `TextToneProp`, which is WIDER (it also carries
* `default`, `primary` and `info`) and which every existing call site is written in. Both spellings
* are accepted on `Text` / `Title` / `Paragraph` / `Link`; **`tone` WINS when both are passed**,
* because `tone` is the vocabulary `check:prop-vocabulary` governs and the one the CSS keys on.
* The fold is `secondary → muted`, `danger → destructive`, `success` / `warning` unchanged.
*/
export type TypographyTypeProp = "secondary" | "success" | "warning" | "danger";
/**
* antd `Typography.Title` `level` — 1…5.
*
* `HeadingLevelProp` stops at 4 because `--heading-h4` is already 12.5px, BELOW the 14px body step.
* antd's fifth level is carried here rather than widened into `HeadingLevelProp`, so `Heading` —
* used across the package and at consumer call sites — keeps the four levels its tokens actually
* define while `Title` reaches antd's five. Level 5 reads `--heading-h5`, which is bound to the
* existing `--font-size-2xs` step (≈11.1px); it is not a new number.
*/
export type TitleLevelProp = 1 | 2 | 3 | 4 | 5;
/**
* antd `CopyConfig` — the copy affordance beside a run of text.
*
* `format: "text/html"` reaches the real `ClipboardItem` path. `tooltips` takes `false` to suppress
* the tooltip, one node for both states, or `[copy, copied]` for each.
*/
export type TypographyCopyConfigProp = {
/** Text to copy. A function may be async — it is awaited. Defaults to the rendered children. */
text?: string | (() => string | Promise);
/** Fired AFTER the write resolves. Never fired when the clipboard refuses — see the component. */
onCopy?: (event?: React.MouseEvent) => void;
/** Icon node, or `[copy, copied]` for the two states. */
icon?: React.ReactNode;
/** Tooltip text: `false` suppresses it, a node replaces both, `[copy, copied]` sets each. */
tooltips?: React.ReactNode;
/** `text/html` also writes an HTML flavour to the clipboard. Default `text/plain`. */
format?: "text/plain" | "text/html";
/** Tab order of the copy button. */
tabIndex?: number;
};
/** antd `EditConfig` — in-place editing of a run of text. */
export type TypographyEditConfigProp = {
/** The value to edit. Falls back to the children when they are a plain string. */
text?: string;
/** Controlled editing state. */
editing?: boolean;
/** Icon node for the edit trigger. */
icon?: React.ReactNode;
/** Tooltip on the edit trigger; `false` suppresses it. */
tooltip?: React.ReactNode;
/** Fired when editing starts. */
onStart?: () => void;
/** Fired with the TRIMMED value when editing is confirmed (Enter, or blur). */
onChange?: (value: string) => void;
/** Fired when editing is abandoned (Escape). */
onCancel?: () => void;
/** Fired after Enter confirms — NOT after a blur, matching antd. */
onEnd?: () => void;
/** Character ceiling on the editing textarea. */
maxLength?: number;
/** Auto-grow the textarea. `true`, or `{ minRows, maxRows }`. Default `true`. */
autoSize?: boolean | {
minRows?: number;
maxRows?: number;
};
/** What opens the editor: the icon, the text itself, or both. Default `["icon"]`. */
triggerType?: ("icon" | "text")[];
/** Node shown in the editor's corner; `null` removes it. */
enterIcon?: React.ReactNode;
/** Tab order of the edit button. */
tabIndex?: number;
};
/**
* antd `EllipsisConfig` — the truncation contract.
*
* `Text` already carries this library's own `truncate` (one line) and `clamp` (N lines). All three
* spellings are accepted and **`ellipsis` WINS** when they collide, because it is the only one that
* can carry an expand control, a suffix or a tooltip.
*/
export type TypographyEllipsisConfigProp = {
/** Lines kept before truncating. Default 1. */
rows?: number;
/** Show an expand control. `"collapsible"` also keeps a collapse control once expanded. */
expandable?: boolean | "collapsible";
/** Text pinned AFTER the ellipsis (a unit, a count). */
suffix?: string;
/** The expand/collapse label — a node, or a function of the current state. */
symbol?: React.ReactNode | ((expanded: boolean) => React.ReactNode);
/** Uncontrolled initial expanded state. */
defaultExpanded?: boolean;
/** Controlled expanded state. */
expanded?: boolean;
/** Fired when the expand/collapse control is used. */
onExpand?: (e: React.MouseEvent, info: {
expanded: boolean;
}) => void;
/** Fired when the measured overflow state flips. */
onEllipsis?: (ellipsis: boolean) => void;
/** Tooltip carrying the full text while it is truncated. `true` uses the children. */
tooltip?: React.ReactNode;
};
/**
* antd `ActionsConfig` — which side of the text the copy / edit / expand cluster sits on.
*
* The name is antd's. `PROP_ALIASES_FORBIDDEN` reserves the bare word `actions` for a ReactNode
* SLOT (`ActionsProp`, toolbars); this is not that — it is a placement config for controls the
* component renders itself, and antd's spelling wins per DESIGN-AUTHORITY's prop-surface rule.
* `start` / `end` are logical, so they mirror in RTL.
*/
export type TypographyActionsConfigProp = {
placement?: "start" | "end";
};
/** Badge visual style. */
export type BadgeVariantProp = "default" | "secondary" | "outline" | "dashed";
/**
* AppSettingPicker trigger presentation.
* - `labeled` (default) — the leading icon + the selected value inside a full-width control
* (settings forms, preference panels).
* - `icon` — a square, icon-only utility trigger (e.g. a topbar globe locale switcher). It
* STRUCTURALLY drops the value text and the picker's owned trigger width, keeping the localized
* `aria-label`, focus ring, keyboard behaviour and a `--control-height` tap target (which is
* ≥44px on coarse/touch pointers per Rule #24) — so consumers never hide internal nodes via CSS.
* - `bar` — the same structural drops as `icon`, re-shaped as a CELL OF THE BAR rather than a
* control dropped into it: it fills the bar's height and squares its corners, so the hover
* surface paints the whole strip. Reach for it in a `Topbar` slot or AppShell's own bar, where
* `icon` leaves a --control-height pill floating in a taller strip and reads as a different
* control family from the bar's own chrome (`TopbarItem`).
*/
export type AppSettingPickerAppearanceProp = "labeled" | "icon" | "bar" | "inline";
/**
* AppSettingToggle presentation. The toggle has no menu, so it has no `labeled`/`inline` form —
* the two members here are the two BOXES a one-tap cycler can take.
* - `bar` (default) — a CELL of the bar (`TopbarItem`): full bar height, the bar's own hover
* surface, square corners (`--topbar-item-radius`). This is the canonical placement, which is
* why it is the default: a toggle exists for a top bar.
* - `icon` — a square `--control-height` ghost button for everywhere that is NOT a bar (a
* settings row, a card header). In a taller bar this leaves a pill floating mid-strip, which is
* the defect `bar` exists to avoid.
*/
export type AppSettingToggleAppearanceProp = "bar" | "icon";
/** Button size preset. */
export type SizeProp = "xs" | "sm" | "md" | "lg";
/** Button size preset; icon-only sizes are a documented Button subset. */
export type ButtonSizeProp = SizeProp | "default" | "icon" | "icon-xs" | "icon-sm" | "icon-lg";
/**
* Validation state a data-entry control PAINTS — Ant Design's `status` axis, which
* docs/DESIGN-AUTHORITY.md names the taxonomy authority for this library.
*
* Two members, not antd's five. `error` and `warning` are the two antd paints from the prop alone;
* `success` and `validating` are only ever drawn together with antd's `hasFeedback` icon slot,
* which this library does not have (FormField owns the message and the icon), and `""` is antd's
* way of spelling "no status" in a required field — `undefined` already says that here.
*
* `error` and `aria-invalid` are the same state seen from two sides: a control given
* `status="error"` also reports `aria-invalid`, so the paint and the announcement can never drift.
* `warning` deliberately does NOT set `aria-invalid` — a warning is not a validity failure, and
* antd does not announce one either.
*/
export type ControlStatusProp = "error" | "warning";
/**
* How much chrome a data-entry control draws — Ant Design's `variant` axis.
* - `outlined` (default) — the historical field: boundary, surface and resting shadow.
* - `filled` — no boundary at rest, a tinted surface instead; the dense-form treatment.
* - `borderless` — neither boundary nor surface, for a field embedded in a box that already draws
* one (a composer inside a Card, an inline edit cell).
*
* antd's fourth member `underlined` is deliberately absent — see docs/DESIGN-AUTHORITY.md: a
* single bottom rule is a Material convention, and SmartHR (the Japanese-UI authority here) draws
* every form field as a full box.
*/
export type ControlVariantProp = "outlined" | "filled" | "borderless";
/** Form layout — label position relative to its control (Ant-style). */
export type FormLayoutProp = "vertical" | "horizontal" | "inline";
/** Descriptions layout — label over value (`vertical`) or beside it (`horizontal`); the
* `FormLayoutProp` subset that a metadata grid supports (no `inline`). */
export type DescriptionsLayoutProp = Extract;
/** Responsive breakpoint name (mobile-first); used by `collapseBelow` etc. */
export type BreakpointProp = "sm" | "md" | "lg" | "xl";
/** Dialog confirm button emphasis. */
export type ConfirmVariantProp = "default" | "destructive";
/** Semantic color/status intent. */
export type ToneProp = "default" | "success" | "warning" | "destructive" | "info" | "muted" | "neutral";
/**
* Alert STRUCTURAL axis (orthogonal to `tone`, which owns colour/semantics):
* `default` — the inline card (rounded, framed on all sides);
* `banner` — the full-bleed page/shell attention strip the `Banner` export renders
* (square corners, hairline block-end rule only, `--banner-*` token geometry);
* `callout` — the in-prose aside the `Callout` export renders (leading rail, `--callout-*`
* geometry) and, uniquely, the one value that is NOT a live region: it carries `role="note"`
* because it is part of the document being read, not an update to it (gh#765).
*/
export type AlertVariantProp = "default" | "banner" | "callout";
/**
* Callout kind — the five admonitions GitHub documents (`> [!NOTE]`, `[!TIP]`, `[!IMPORTANT]`,
* `[!WARNING]`, `[!CAUTION]`), which Obsidian's lower-case spelling maps onto one-for-one. It is a
* PRESET, not a second colour axis: each kind resolves to a `tone` and a leading glyph that
* `tone`/`icon` still override per instance.
*/
export type CalloutKindProp = "note" | "tip" | "important" | "warning" | "caution";
/** Sort direction for table columns. */
export type SortDirectionProp = "asc" | "desc";
/** Table column text alignment. */
export type ColumnAlignProp = "left" | "center" | "right";
/** Active sort state on DataTable. */
export type SortStateProp = {
key: string;
direction: SortDirectionProp;
};
/**
* Entrance-stagger ordinal for `Reveal` — an INDEX into the motion ladder, never a raw ms. `0` =
* enter immediately; `1..6` each add one `--reveal-stagger-step` of delay so a column of revealed
* rows cascades in.
*/
export type RevealDelayProp = 0 | 1 | 2 | 3 | 4 | 5 | 6;
/**
* What STARTS a `Reveal` — `mount` (default, the historical behaviour: the entrance plays as soon
* as the element renders) or `view` (the entrance waits until the element reaches the viewport).
* A trigger, never a second component: the animation, the tokens and the reduced-motion contract
* are identical either way.
*/
export type RevealTriggerProp = "mount" | "view";
/**
* How much of an element must be inside the viewport to count as in view — Motion's `useInView`
* `amount`, name and type unchanged. `"some"` (default) is any pixel, `"all"` is the whole box, a
* number is an explicit 0..1 ratio.
*/
export type InViewAmountProp = "some" | "all" | number;
/**
* Ambient-activity mark for `Activity` — the LOOP counterpart to `RevealDelayProp`'s one-shot
* ladder. `dots` (default) is the three-dot ellipsis convention (someone is typing); `pulse` a
* single breathing mark (live / recording); `bar` an indeterminate sweep (syncing).
*/
export type ActivityVariantProp = "dots" | "pulse" | "bar";
/**
* Whether an ambient indicator announces its label to assistive technology. Default `false` —
* DELIBERATELY.
*/
export type ActivityAnnounceProp = false | "polite";