/** Foundation component prop types — @see docs/COMPONENTS.md#foundation */ import type * as React from "react"; import type { ActivityAnnounceProp, ActivityVariantProp, AsChildProp, ButtonSizeProp, ButtonVariantProp, ChildrenProp, ClassNameProp, DescriptionProp, DisabledProp, FontWeightProp, HeadingLevelProp, IconSizeProp, IdProp, InViewAmountProp, LabelProp, OnClickProp, OnOpenChangeProp, OpenProp, PendingProp, RevealDelayProp, RevealTriggerProp, ShapeProp, SizeProp, TextAlignProp, TextSizeProp, TextToneProp, TextWhitespaceProp, TextBreakProp, TypographyActionsConfigProp, TypographyCopyConfigProp, TypographyEditConfigProp, TypographyEllipsisConfigProp, TypographyTypeProp } from "../vocabulary/index.js"; /** * @see Text — typographic primitive; replaces hand-rolled ``. * * This IS antd `Typography.Text`: every prop antd declares on it is here, alongside the props * this library already shipped. Where the two name the same axis, BOTH spellings are accepted and * the winner is stated at the prop — `tone` over `type`, `ellipsis` over `truncate` / `clamp`. */ export type TextProp = Omit, "color"> & Omit & { /** Render element. Default `span`. */ as?: "span" | "p" | "div" | "a" | "label" | "strong" | "em" | "small" | "code" | "kbd" | "dt" | "dd" | "caption" | "abbr"; /** * Render the typography onto the child element instead of emitting one — for a router link * (`…`). The child owns the element and its * navigation; Text owns the type step, tone, weight and truncation. */ asChild?: AsChildProp; /** * This text IS a link: underline on hover and on keyboard focus, at the token underline offset, * and the focus mark every other interactive element draws. * * It is an AFFORDANCE, not a colour — `tone` still owns the colour and simply defaults to * `primary` here, so a destructive link (`link tone="destructive"`) reads destructive and still * underlines. Use this INSTEAD of `className="text-primary hover:underline"`, and instead of * `Button variant="link"` whenever the link sits in running content: `.ui-button` is a control * box (`white-space: nowrap`, `flex-shrink: 0`, a `--control-height` tier and inline padding), * so in a table cell it cannot wrap and cannot share the cell's line height. */ link?: boolean; /** Size from the type scale — never an arbitrary px. Default `sm` (base). */ size?: TextSizeProp; /** Semantic colour intent. Default `default` (foreground). */ tone?: TextToneProp; /** Weight (system 2-weight: 400/500). Default `regular`. */ weight?: FontWeightProp; align?: TextAlignProp; /** Single-line ellipsis. Mutually exclusive with `clamp` — when both are set, `clamp` wins. */ truncate?: boolean; /** * Multi-line clamp — max rendered lines (integer ≥ 1); overflow ends in an ellipsis. Token-owned * line-clamp styling (never write the `line-clamp-N` utility page-side). */ clamp?: number; /** * antd `ellipsis`. `true` is one line; the object form carries `suffix`, `symbol`, * `defaultExpanded` / `expanded`, `onEllipsis` and `tooltip`. * * antd omits `rows`, `expandable` and `onExpand` from `Typography.Text` — an inline run has no * second line to expand into — and that omission is ported; reach for `Paragraph` when you want * them. It OUTRANKS this library's own `truncate` and `clamp` when both are passed, because it * is the only spelling that can also carry a suffix or a tooltip. */ ellipsis?: boolean | Omit; /** * Whitespace handling. Default `normal` (CSS's own: newlines and space runs collapse). * * `pre-wrap` is for text a PERSON typed — a plain-text note, an issue description, a pasted log * — where the line breaks and the indentation are CONTENT, not formatting. It preserves both and * still wraps long lines at the container edge, and it breaks an over-long unbroken token (a URL, * an id) rather than letting it overflow. * * Precedence is explicit and resolved in the component, not by CSS ordering: `truncate` is a * single-line contract and WINS (dev builds warn, and `data-whitespace` is not emitted), while * `clamp` composes with it — a clamped pre-wrap block shows its first N real lines. */ whitespace?: TextWhitespaceProp; /** * Where an over-long unbroken token may break. Default `normal` (CSS's own: it stays whole). * * `anywhere` is for a MACHINE identifier — an email address, a coupon code, a login id, a * template key — in a narrow `DataTable` cell or `Descriptions` value: it may be split at any * character to fit, so the column shrinks to the viewport instead of scrolling the page. It * also releases a `nowrap` it inherited (a table cell's default) back to `normal` — newlines * still collapse, because an identifier's whitespace is not content. * * `phrase` is for Japanese PROSE in a narrow measure — a name, an organisation, a status in a * table cell: it breaks between phrases (`word-break: auto-phrase`) instead of mid-word, never * starts a line with a small kana or 「ー」 (`line-break: strict`), adds `text-wrap: pretty`, and * releases an inherited `nowrap` too. Each phrase stays whole, so a column is as wide as its * longest phrase. Browsers without `auto-phrase` keep their own CJK breaking. * * Independent of `whitespace`: `whitespace="pre-wrap" break="anywhere"` keeps typed line breaks * AND splits a long token. `truncate` WINS (one line has nowhere to break to), so the attribute * is not emitted under it; `clamp` composes. */ break?: TextBreakProp; /** Tabular figures for aligned numbers. */ tabular?: boolean; decoration?: "none" | "underline" | "line-through"; /** Inline code chip; use with as="code". */ chip?: boolean; /** Monospace family (codes, ids). */ mono?: boolean; htmlFor?: string; /** * Anchor attributes, for `as="a"` (and for the `` a router link supplies under `asChild`). * * Declared explicitly rather than by widening the base to `AnchorHTMLAttributes`, and for the * same reason `htmlFor` is declared explicitly for `as="label"`: the element union is the * contract, so each polymorphic branch names the attributes it actually accepts instead of every * span silently offering an `href` it will never render. */ href?: string; target?: React.HTMLAttributeAnchorTarget; rel?: string; download?: React.AnchorHTMLAttributes["download"]; }; /** @see Heading — h1..h4 sized from the `--heading-h*` tokens, or from `size` when one is given. */ export type HeadingProp = Omit, "color"> & { /** Heading level — sets size token AND the semantic element (override the element with `as`). */ level?: HeadingLevelProp; /** * Visual size, overriding the step `level` would have taken — the SAME ten-step ladder * `Text size` reads, so a headline and a stat figure beside it are one step name apart rather * than a lookup between two ramps. * * `level` still owns the document outline, and that separation is the point (gh#826). The * enterprise heading ramp tops out at `--heading-h1` ≈ 20px by deliberate 渋み restraint, so a * marketing hero had no way to be both a real `

` and 54px: the display tokens existed, * nothing public reached them, and every marketing page wrote its own class instead. Now it is * ``, and no admin screen's `

` moves. * * Omit it and `level` decides, exactly as before — this is an override, not a second default. */ size?: TextSizeProp; as?: "h1" | "h2" | "h3" | "h4" | "div"; tone?: TextToneProp; align?: TextAlignProp; truncate?: boolean; /** Weight (system canon: 400 · 500 · 700). Default `medium` — set `bold` for an emphasised title. */ weight?: FontWeightProp; }; /** * The antd `BlockProps` behaviour shared by `Text` / `Link`. * * The seven decoration flags each WRAP the content in a real element, in antd's own nesting order * (strong → u → del → code → mark → kbd → i), so the meaning reaches a screen reader instead of * only the pixels. That is why `strong` is not folded into `weight` and `code` is not folded into * `mono`: `weight`/`mono` change how the text is painted, these change what it IS. */ export type TypographyBlockProp = { /** Which side the copy / edit / expand cluster sits on. Default `{ placement: "end" }`. */ actions?: TypographyActionsConfigProp; /** * Emphasis, antd's spelling. This library's `tone` is the same axis and is WIDER, so **`tone` * wins** when both are passed. Folds `secondary → muted`, `danger → destructive`. */ type?: TypographyTypeProp; /** Renders as unavailable — the disabled foreground, `not-allowed`, and no text selection. */ disabled?: boolean; /** Copy affordance. `true` copies the rendered text; the object form is antd's `CopyConfig`. */ copyable?: boolean | TypographyCopyConfigProp; /** In-place editing. `true` is the default trigger set; the object form is antd's `EditConfig`. */ editable?: boolean | TypographyEditConfigProp; /** * Truncation. `true` is one line; the object form is antd's `EllipsisConfig`. * * `Text` and `Link` NARROW this (antd does too — an inline run has no second line to expand * into), which is why they intersect `Omit` and redeclare it. */ ellipsis?: boolean | TypographyEllipsisConfigProp; /** Wrap in ``. (`mono` only swaps the FAMILY; this changes the element and its meaning.) */ code?: boolean; /** Wrap in `` — highlighted. */ mark?: boolean; /** Wrap in `` — underlined. */ underline?: boolean; /** Wrap in `` — struck through. */ delete?: boolean; /** Wrap in `` — bold, and semantically strong. Composes with `weight`. */ strong?: boolean; /** Wrap in `` — a key or key combination. */ keyboard?: boolean; /** Wrap in `` — italic. */ italic?: boolean; /** antd's (private) `component` alias for `as`; `as` wins when both are passed. */ component?: string; }; /** * @see Link — antd `Typography.Link`. * * `Text link` is this library's own inline link affordance and is unchanged. `Link` is antd's * anchor-by-default flavour of it: it renders ``, defaults `tone` to `primary`, and adds * antd's `rel="noopener noreferrer"` guard whenever `target="_blank"` is set without an explicit * `rel`. antd restricts `ellipsis` to a boolean here, and that restriction is ported. */ export type LinkProp = Omit & { /** Rendered element. Default `a`. */ as?: "a" | "span"; /** Single-line truncation. antd allows only a boolean on `Link`. */ ellipsis?: boolean; }; /** @see Button */ export type ButtonProp = React.ButtonHTMLAttributes & { variant?: ButtonVariantProp; size?: ButtonSizeProp; /** Corner shape — `default` (control radius) · `pill` (fully rounded) · `sharp` (square). */ shape?: ShapeProp; fullWidth?: boolean; /** * Take the space the siblings leave, and let a long label ELLIPSE instead of widening the row — * the same axis `Flex` calls `fill`, for the same reason. * * Button ships `flex-shrink: 0`, right almost everywhere and wrong in a constrained bar: an * account menu holding an avatar plus a person's name keeps its full width while the cluster * clips it, so a keyboard user tabs to a control they cannot see (SC 2.4.7). Until this axis * existed the only move was `className="min-w-0 flex-1"`, which ui-audit blocks — and which this * package's own Topbar guidance recommended, so the docs prescribed the utility the audit forbids. * * Sets `flex: 1 1 auto` and `min-inline-size: 0`; pair it with a `` label. */ fill?: boolean; /** Allow a text button to grow vertically for multi-line labels. */ wrap?: boolean; /** Logical content alignment, especially for full-width collection actions. */ align?: TextAlignProp; asChild?: AsChildProp; onClick?: OnClickProp; disabled?: DisabledProp; /** * In-flight state — shows a leading spinner (replaces a leading icon if present), * sets `aria-busy` and blocks activation while keeping the label to avoid layout shift. */ loading?: PendingProp; /** Optional label to swap in while `loading` (pass the `t()`-translated string). */ loadingText?: string; /** * Optional numeric count rendered as a borderless counter pill after the label (filter tabs / * segmented toggles, e.g. "Chờ bay 18"). */ count?: number; /** * Cap for `count` (Ant Badge parity). When `count` exceeds it the pill shows `{overflowCount}+` * (e.g. */ overflowCount?: number; /** * Whether to render the pill when `count` is 0 (Ant Badge parity). Defaults to * `true` (a `0` pill shows); pass `false` to hide the pill at zero. */ showZero?: boolean; /** * Localized description of what the count MEANS, folded into the accessible name so the control * never announces as a bare digit run. The SAME prop and the same contract `Toggle` has carried * since gh#312 — without it a counted button measured `"Git3"` in the accessibility tree, not * "Git, 3 pages" (gh#734). * * The pill itself is `aria-hidden` and the spoken form is an `sr-only` sibling, so the name * reads `"