@antscorp/ui 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/atoms/AddButton.d.ts +2 -1
- package/dist/atoms/Chip.d.ts +4 -1
- package/dist/atoms/RemoveButton.d.ts +2 -1
- package/dist/atoms/TextAction.d.ts +2 -1
- package/dist/docs.json +1 -1
- package/dist/lib/forwardable.d.ts +8 -0
- package/dist/molecules/CheckGroup.d.ts +2 -1
- package/dist/molecules/CheckRow.d.ts +4 -1
- package/dist/molecules/CommitTextField.d.ts +2 -1
- package/dist/molecules/ConfirmDialog.d.ts +2 -1
- package/dist/molecules/DateTimeInput.d.ts +2 -1
- package/dist/molecules/DurationInput.d.ts +2 -1
- package/dist/molecules/PercentRow.d.ts +2 -1
- package/dist/molecules/RevealRadioGroup.d.ts +2 -1
- package/dist/molecules/RevealSwitch.d.ts +2 -1
- package/dist/molecules/Select.d.ts +2 -1
- package/dist/molecules/StatusButton.d.ts +2 -1
- package/dist/molecules/ToggleGrid.d.ts +2 -1
- package/dist/molecules/TokenTextField.d.ts +2 -1
- package/dist/organisms/TokenPickerDialog.d.ts +2 -1
- package/dist/organisms/TransferPicker.d.ts +2 -1
- package/dist/theme/AntsomiTheme.d.ts +3 -2
- package/dist/theme/AntsomiTheme.js +8 -7
- package/package.json +3 -3
- package/src/atoms/AddButton.tsx +2 -1
- package/src/atoms/Chip.tsx +4 -1
- package/src/atoms/RemoveButton.tsx +3 -1
- package/src/atoms/TextAction.tsx +3 -1
- package/src/lib/forwardable.ts +8 -0
- package/src/molecules/CheckGroup.tsx +3 -3
- package/src/molecules/CheckRow.tsx +4 -1
- package/src/molecules/CommitTextField.tsx +15 -12
- package/src/molecules/ConfirmDialog.tsx +5 -1
- package/src/molecules/DateTimeInput.tsx +3 -2
- package/src/molecules/DurationInput.tsx +4 -1
- package/src/molecules/PercentRow.tsx +3 -2
- package/src/molecules/RevealRadioGroup.tsx +3 -3
- package/src/molecules/RevealSwitch.tsx +3 -1
- package/src/molecules/Select.tsx +16 -13
- package/src/molecules/StatusButton.tsx +3 -1
- package/src/molecules/ToggleGrid.tsx +3 -3
- package/src/molecules/TokenTextField.tsx +3 -3
- package/src/organisms/TokenPickerDialog.tsx +2 -1
- package/src/organisms/TransferPicker.tsx +3 -3
- package/src/theme/AntsomiTheme.tsx +9 -3
package/dist/docs.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"schemaVersion":1,"package":"@antscorp/ui","version":"0.1.4","components":[{"name":"AccentCard","level":"molecules","category":"Layout","keywords":["card","header","rows","footer"],"import":"import { AccentCard } from \"@antscorp/ui\";","description":"Group related rows under a titled header, with an optional close action and footer.","usage":"Group related rows under a titled header, with an optional close action and footer. Choose stripe or banner to match the surrounding composition.","bestPractices":[{"guidance":true,"description":"Provide labels.close with onClose, and disable or make the supplied row controls read-only in the host."},{"guidance":false,"description":"Do not expect isDisabled to disable the card's children; it only blocks closing."}],"anatomy":[{"name":"Header","required":true,"description":"The title and optional named close button."},{"name":"Rows","required":true,"description":"Host content stacked below the header."},{"name":"Footer","required":false,"description":"Host content separated from the rows by a line."}],"accessibility":["The card is a group named by title; labels.close names its optional close button."],"props":[{"path":"title","type":"string","required":true,"description":"The text of the header bar; it also names the card."},{"path":"children","type":"ReactNode","required":true,"description":"The card's rows, such as `SideLabelRow`s; they bring their own padding."},{"path":"labels","type":"{ readonly close?: string | undefined; } | undefined","required":false,"description":"The texts the card shows; the host translates them."},{"path":"labels.close","type":"string | undefined","required":false,"description":"The name of the close button, such as \"Remove item\"; required with `onClose`, which shows no button without it."},{"path":"onClose","type":"(() => void) | undefined","required":false,"description":"Called by the close button at the end of the header; no button without it."},{"path":"footer","type":"ReactNode","required":false,"description":"Content under the rows, such as an action and its caption, set off by a line above it; none\nwithout it."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the close button and blocks it; the rows are the host's to disable."},{"path":"variant","type":"\"banner\" | \"stripe\" | undefined","required":false,"description":"`stripe` (default): a grey border with an accent stripe down the start edge and a light\nheader bar. `banner`: no border nor stripe but a soft shadow,\nunder a dark blue header bar with the title in white capitals and a white close cross."}],"examples":[{"name":"Default","description":"A card of two rows, with a close button in its header.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"WithoutClose","description":"No close button: none is offered, e.g. the only card of a list.","source":"export const WithoutClose: Story = {\n render: (args) => <AccentCard {...args} onClose={undefined} />,\n};","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"Interactive","description":"Closing takes the card away; a button brings it back.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(true, (isShown, set) =>\n isShown ? (\n <AccentCard\n {...args}\n onClose={() => {\n args.onClose?.();\n set(false);\n }}\n />\n ) : (\n <Button\n label=\"Show again\"\n size=\"sm\"\n onClick={() => {\n set(true);\n }}\n />\n ),\n ),\n};","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"WithFooter","description":"A footer under the rows: an action and its caption, set off by a line.","source":"export const WithFooter: Story = { args: { footer } };","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"ReadOnly","description":"Read-only: the host offers no close (no `onClose`) and no footer action; the rows stay, not dimmed.","source":"export const ReadOnly: Story = {\n render: (args) => <AccentCard {...args} onClose={undefined} />,\n};","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"ManyRows","description":"Many rows in one card.","source":"export const ManyRows: Story = {\n args: {\n children: Array.from({ length: 10 }, (_, index) => (\n <SideLabelRow key={index} label={`Row ${index + 1}`}>\n <Text type=\"body\">Value {index + 1}</Text>\n </SideLabelRow>\n )),\n },\n};","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"Disabled","description":"The close button blocked.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"LongTitle","description":"A long title.","source":"export const LongTitle: Story = {\n args: { title: \"A long title for a card that has a lot to say about what it holds\" },\n};","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"Empty","description":"No rows yet: the header alone.","source":"export const Empty: Story = { args: { children: null } };","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <AccentCard {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"Banner","description":"The banner look: a dark blue header with a white title in capitals and a white cross, a soft shadow.","source":"export const Banner: Story = { args: { variant: \"banner\", title: \"Included items\" } };","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"BannerWithoutClose","description":"The banner without its cross.","source":"export const BannerWithoutClose: Story = {\n args: { variant: \"banner\", title: \"Included items\" },\n render: (args) => <AccentCard {...args} onClose={undefined} />,\n};","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"BannerDisabled","description":"The banner with its cross blocked.","source":"export const BannerDisabled: Story = {\n args: { variant: \"banner\", title: \"Included items\", isDisabled: true },\n};","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"BannerNarrow","description":"The banner with a long title, in a drawer-width frame.","source":"export const BannerNarrow: Story = {\n args: { variant: \"banner\", title: \"A long title for a card that has a lot to say\" },\n render: (args) => (\n <Narrow>\n <AccentCard {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"With close\">\n <AccentCard {...args} />\n </Case>\n <Case caption=\"Without close\">\n <AccentCard {...args} onClose={undefined} />\n </Case>\n <Case caption=\"Disabled\">\n <AccentCard {...args} isDisabled />\n </Case>\n <Case caption=\"With a footer\">\n <AccentCard {...args} footer={footer} />\n </Case>\n <Case caption=\"Banner\">\n <AccentCard {...args} variant=\"banner\" />\n </Case>\n <Case caption=\"Banner, disabled\">\n <AccentCard {...args} variant=\"banner\" isDisabled />\n </Case>\n <Case caption=\"Narrow\">\n <Narrow>\n <AccentCard {...args} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/AccentCard.stories.tsx"}],"primaryExample":"Default"},{"name":"AddButton","level":"atoms","category":"Inputs","keywords":["add","button","list","plus"],"import":"import { AddButton } from \"@antscorp/ui\";","description":"Add a row or item with a labeled plus button.","usage":"Add a row or item with a labeled plus button. Use TextAction for an action that does not need the add glyph.","bestPractices":[{"guidance":true,"description":"Keep label specific about what will be added and let the host update the list in onClick."},{"guidance":false,"description":"Do not expect the button to add data itself; isReadOnly hides the entire editing affordance."}],"anatomy":[{"name":"Plus","required":true,"description":"The theme's ui:add glyph, sized with the button."},{"name":"Label","required":true,"description":"The translated action text, optionally shown in capitals."}],"accessibility":["The button is named by label; uppercase presentation preserves that name."],"props":[{"path":"label","type":"string","required":true,"description":"Accessible label for the button (required for accessibility).\nRendered as visible text by default. When `isIconOnly` is true,\nused as aria-label instead."},{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether the button is disabled."},{"path":"variant","type":"\"ghost\" | \"secondary\" | undefined","required":false,"description":"`ghost` by default; `secondary` for an add that needs more weight."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The button's height, `md` by default; its plus follows it."},{"path":"shape","type":"\"default\" | \"pill\" | undefined","required":false,"description":"`default` keeps the button's own corners; `pill` rounds them fully; the theme colours its outline."},{"path":"case","type":"\"sentence\" | \"upper\" | undefined","required":false,"description":"`sentence` (default) shows the label as given; `upper` in capitals, such as \"+ AND\"."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Read-only: an add is an editing affordance, so nothing is drawn."},{"path":"onClick","type":"() => void","required":true,"description":"Called when the button is pressed."}],"examples":[{"name":"Default","description":"The quiet button that adds a row.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/AddButton.stories.tsx"},{"name":"Secondary","description":"With more weight, for an add that stands alone.","source":"export const Secondary: Story = { args: { variant: \"secondary\" } };","file":"packages/ui/src/atoms/AddButton.stories.tsx"},{"name":"Pill","description":"A pill outlined in the accent colour, as an add under a card's rows.","source":"export const Pill: Story = { args: { variant: \"secondary\", shape: \"pill\", label: \"Refine\" } };","file":"packages/ui/src/atoms/AddButton.stories.tsx"},{"name":"UpperPill","description":"A pill whose label shows in capitals, such as \"+ AND\".","source":"export const UpperPill: Story = {\n args: { variant: \"secondary\", shape: \"pill\", size: \"sm\", case: \"upper\", label: \"And\" },\n};","file":"packages/ui/src/atoms/AddButton.stories.tsx"},{"name":"Outline","description":"The outline add: secondary, `md`, its plus at 18px.","source":"export const Outline: Story = { args: { variant: \"secondary\", size: \"md\" } };","file":"packages/ui/src/atoms/AddButton.stories.tsx"},{"name":"Interactive","description":"Each press adds a row to a count shown beside it.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(0, (rows, set) => (\n <>\n <AddButton\n {...args}\n label={`Add row (${rows} so far)`}\n onClick={() => {\n args.onClick();\n set(rows + 1);\n }}\n />\n </>\n )),\n};","file":"packages/ui/src/atoms/AddButton.stories.tsx"},{"name":"Disabled","description":"Not pressable, e.g. for a read-only list or when a limit is reached.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/atoms/AddButton.stories.tsx"},{"name":"Medium","description":"The `md` height, for a form whose other controls are `md`.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/atoms/AddButton.stories.tsx"},{"name":"ReadOnly","description":"Read-only: an add is an editing affordance, so nothing is drawn.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/atoms/AddButton.stories.tsx"},{"name":"LongLabel","description":"A long label wraps or truncates rather than breaking the layout.","source":"export const LongLabel: Story = {\n args: { label: \"Add another very long named row to the end of this particular list\" },\n};","file":"packages/ui/src/atoms/AddButton.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: { label: \"Add another very long named row to the end of this list\" },\n render: (args) => (\n <Narrow>\n <AddButton {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/AddButton.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <AddButton {...args} />\n </Case>\n <Case caption=\"Upper pill\">\n <AddButton {...args} variant=\"secondary\" shape=\"pill\" size=\"sm\" case=\"upper\" label=\"And\" />\n </Case>\n <Case caption=\"Secondary\">\n <AddButton {...args} variant=\"secondary\" />\n </Case>\n <Case caption=\"Pill\">\n <AddButton {...args} variant=\"secondary\" shape=\"pill\" />\n </Case>\n <Case caption=\"Pill, disabled\">\n <AddButton {...args} variant=\"secondary\" shape=\"pill\" isDisabled />\n </Case>\n <Case caption=\"Disabled\">\n <AddButton {...args} isDisabled />\n </Case>\n <Case caption=\"Medium\">\n <AddButton {...args} size=\"md\" />\n </Case>\n <Case caption=\"Outline (secondary, md)\">\n <AddButton {...args} variant=\"secondary\" size=\"md\" />\n </Case>\n <Case caption=\"Outline, disabled\">\n <AddButton {...args} variant=\"secondary\" size=\"md\" isDisabled />\n </Case>\n <Case caption=\"Read-only (nothing drawn)\">\n <AddButton {...args} isReadOnly />\n </Case>\n <Case caption=\"Narrow, long label\">\n <Narrow>\n <AddButton {...args} label=\"Add another very long named row to the end of this list\" />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/AddButton.stories.tsx"}],"primaryExample":"Default"},{"name":"AddGlyph","level":"atoms","category":"Data display","keywords":["plus","glyph","icon","add"],"import":"import { AddGlyph } from \"@antscorp/ui\";","description":"Draw the theme's decorative plus inside a custom add control.","usage":"Draw the theme's decorative plus inside a custom add control. Use AddButton when a complete labeled action is needed.","bestPractices":[{"guidance":true,"description":"Match size to the containing button and provide its action label on that button."},{"guidance":false,"description":"Do not use the glyph alone as an action or expect it to draw in a theme without ui:add."}],"accessibility":["The glyph is decorative and hidden from assistive technology; the containing button supplies the name."],"props":[{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The height of the button it sits in, `md` by default."}],"examples":[{"name":"Default","description":"The plus of an `sm` button, at 12px.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/AddGlyph.stories.tsx"},{"name":"Medium","description":"The plus of an `md` button, at 18px.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/atoms/AddGlyph.stories.tsx"},{"name":"InSmallButton","description":"In an `sm` secondary button, as a button draws it.","source":"export const InSmallButton: Story = {\n render: (args) => (\n <Button label=\"Add row\" variant=\"secondary\" size=\"sm\" icon={<AddGlyph {...args} />} />\n ),\n};","file":"packages/ui/src/atoms/AddGlyph.stories.tsx"},{"name":"InMediumButton","description":"In an `md` secondary button, the outline add.","source":"export const InMediumButton: Story = {\n args: { size: \"md\" },\n render: (args) => (\n <Button label=\"Add row\" variant=\"secondary\" size=\"md\" icon={<AddGlyph {...args} />} />\n ),\n};","file":"packages/ui/src/atoms/AddGlyph.stories.tsx"},{"name":"Disabled","description":"In a disabled button: it takes the button's dimmed colour.","source":"export const Disabled: Story = {\n args: { size: \"md\" },\n render: (args) => (\n <Button\n label=\"Add row\"\n variant=\"secondary\"\n size=\"md\"\n isDisabled\n icon={<AddGlyph {...args} />}\n />\n ),\n};","file":"packages/ui/src/atoms/AddGlyph.stories.tsx"},{"name":"InNarrowFrame","description":"In a long-labelled button in a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: { size: \"md\" },\n render: (args) => (\n <Narrow>\n <Button\n label=\"Add another very long named row to the end of this list\"\n variant=\"secondary\"\n size=\"md\"\n icon={<AddGlyph {...args} />}\n />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/AddGlyph.stories.tsx"},{"name":"AllStates","description":"Each size alone and in its button.","source":"export const AllStates: Story = {\n render: () => (\n <Gallery>\n <Case caption=\"sm\">\n <AddGlyph size=\"sm\" />\n </Case>\n <Case caption=\"md\">\n <AddGlyph size=\"md\" />\n </Case>\n <Case caption=\"In an sm button\">\n <Button label=\"Add row\" variant=\"secondary\" size=\"sm\" icon={<AddGlyph size=\"sm\" />} />\n </Case>\n <Case caption=\"In an md button\">\n <Button label=\"Add row\" variant=\"secondary\" size=\"md\" icon={<AddGlyph size=\"md\" />} />\n </Case>\n <Case caption=\"In a disabled md button\">\n <Button\n label=\"Add row\"\n variant=\"secondary\"\n size=\"md\"\n isDisabled\n icon={<AddGlyph size=\"md\" />}\n />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/AddGlyph.stories.tsx"}],"primaryExample":"Default"},{"name":"AntsomiTheme","level":"theme","category":"Theme","keywords":["theme","provider","tokens","icons","mode"],"import":"import { AntsomiTheme } from \"@antscorp/ui\";","description":"Apply the Antsomi base theme to a subtree and forward Astryx's light, dark or system mode.","usage":"Apply the Antsomi base theme to a subtree and forward Astryx's light, dark or system mode. Extend antsomiTheme with defineTheme and pass the result through theme for a scoped override.","bestPractices":[{"guidance":true,"description":"Import @antscorp/ui/theme.css separately and load Roboto in the host; nest providers when an override should affect only a subtree."},{"guidance":false,"description":"Do not expect the provider to load CSS or fonts, or replace theme tokens with hard-coded colors in consuming views."}],"props":[{"path":"mode","type":"ThemeMode","required":false,"description":"Color mode - 'system' follows OS preference"},{"path":"children","type":"React.ReactNode","required":true,"description":"Children to render"},{"path":"theme","type":"DefinedTheme","required":false,"description":"Optional override created with `defineTheme({ extends: antsomiTheme, … })`."}],"examples":[{"name":"Default","description":"The Antsomi base theme is selected without a theme prop.","source":"export const Default: Story = {};","file":"packages/ui/src/theme/AntsomiTheme.stories.tsx"},{"name":"Extended","description":"Extends the base theme, replacing only the accent token with a palette value.","source":"export const Extended: Story = { args: { theme: extendedTheme } };","file":"packages/ui/src/theme/AntsomiTheme.stories.tsx"},{"name":"Dark","description":"Forwards Astryx's dark mode to the subtree.","source":"export const Dark: Story = { args: { mode: \"dark\" } };","file":"packages/ui/src/theme/AntsomiTheme.stories.tsx"},{"name":"Nested","description":"A nested override affects only its own controls; outer controls retain the base theme.","source":"export const Nested = {\n render: (args) => (\n <AntsomiTheme {...args}>\n <VStack gap={3}>\n <Text>Base theme</Text>\n {args.children}\n <AntsomiTheme theme={extendedTheme} mode={args.mode ?? \"light\"}>\n <VStack gap={3}>\n <Text>Extended theme</Text>\n {args.children}\n </VStack>\n </AntsomiTheme>\n </VStack>\n </AntsomiTheme>\n ),\n} satisfies Story;","file":"packages/ui/src/theme/AntsomiTheme.stories.tsx"},{"name":"AllStates","description":"The base, extended, dark and nested theme states together.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <AntsomiTheme {...args} />\n </Case>\n <Case caption=\"Extended\">\n <AntsomiTheme {...args} theme={extendedTheme} />\n </Case>\n <Case caption=\"Dark\">\n <AntsomiTheme {...args} mode=\"dark\" />\n </Case>\n <Case caption=\"Nested\">{Nested.render(args)}</Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/theme/AntsomiTheme.stories.tsx"}],"primaryExample":"Default"},{"name":"CheckGroup","level":"molecules","category":"Selection","keywords":["checkboxes","multiple","options","group"],"import":"import { CheckGroup } from \"@antscorp/ui\";","description":"Choose several values from a short list of labeled checkboxes.","usage":"Choose several values from a short list of labeled checkboxes. Use ToggleGrid when a compact grid of toggle buttons fits the choices better.","bestPractices":[{"guidance":true,"description":"Keep value controlled and accept onChange values in options order; use vertical orientation when labels need more room."},{"guidance":false,"description":"Do not use checkboxes for mutually exclusive choices; use RevealRadioGroup."}],"anatomy":[{"name":"Group label","required":true,"description":"Names the set through FieldGroup."},{"name":"Checkboxes","required":true,"description":"One controlled checkbox per option, in a wrapping row or column."},{"name":"Error","required":false,"description":"A translated problem with the selection."}],"accessibility":["Supply label for the group and a clear label on every option."],"props":[{"path":"label","type":"string","required":true,"description":"Names the group; read out before each field in it."},{"path":"isDisabled","type":"boolean","required":false,"description":"Disables fields through the unframed fieldset; with `frame=\"card\"`, disable children yourself."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the group as a whole, already in the user's language."},{"path":"options","type":"readonly CheckGroupOption<string>[]","required":true,"description":"The checkboxes, in order."},{"path":"options[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What the picked list holds for this checkbox."},{"path":"options[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The checkbox's text."},{"path":"value","type":"readonly string[]","required":true,"description":"The picked values."},{"path":"onChange","type":"(value: string[]) => void","required":true,"description":"Called with the picked values, in the order of `options`."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The checkboxes' size, `md` by default (Astryx's default; the theme draws it at 18px)."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows the pick, not dimmed, and blocks changes."},{"path":"orientation","type":"\"horizontal\" | \"vertical\" | undefined","required":false,"description":"In a wrapping row (default) or a column."}],"examples":[{"name":"Default","description":"Some boxes checked, in a wrapping row.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"NoneSelected","description":"None checked.","source":"export const NoneSelected: Story = { args: { value: [] } };","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"AllSelected","description":"Every box checked.","source":"export const AllSelected: Story = { args: { value: weekdays.map((day) => day.value) } };","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"Interactive","description":"Checking boxes changes the pick, kept in the order of the options.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<string[]>([], (value, set) => (\n <CheckGroup\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"Vertical","description":"In a column.","source":"export const Vertical: Story = { args: { orientation: \"vertical\" } };","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"WithError","description":"With a problem with the pick.","source":"export const WithError: Story = { args: { value: [], error: \"Pick at least one day.\" } };","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"Disabled","description":"Dimmed and blocked.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"ReadOnly","description":"Shows the pick, not dimmed, and blocks changes.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"Small","description":"The compact `sm` size.","source":"export const Small: Story = { args: { size: \"sm\" } };","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"SingleOption","description":"A single option.","source":"export const SingleOption: Story = { args: { options: weekdays.slice(0, 1), value: [] } };","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"LongContent","description":"Many options with long labels.","source":"export const LongContent: Story = {\n args: {\n options: Array.from({ length: 12 }, (_, index) => ({\n value: String(index),\n label: `Option number ${index + 1} with a rather long name`,\n })),\n value: [\"0\", \"5\"],\n },\n};","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, where the row wraps.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <CheckGroup {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Some checked\">\n <CheckGroup {...args} />\n </Case>\n <Case caption=\"None checked, error\">\n <CheckGroup {...args} value={[]} error=\"Pick at least one day.\" />\n </Case>\n <Case caption=\"Vertical\">\n <CheckGroup {...args} orientation=\"vertical\" />\n </Case>\n <Case caption=\"Disabled\">\n <CheckGroup {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <CheckGroup {...args} isReadOnly />\n </Case>\n <Case caption=\"Small\">\n <CheckGroup {...args} size=\"sm\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"}],"primaryExample":"Default"},{"name":"CheckRow","level":"molecules","category":"Selection","keywords":["checkbox","row","pick","list"],"import":"import { CheckRow } from \"@antscorp/ui\";","description":"Show one checkbox choice with optional supporting text and end content.","usage":"Show one checkbox choice with optional supporting text and end content. Use RemovableRow for a value that is already picked and needs a removal action.","bestPractices":[{"guidance":true,"description":"Keep label meaningful because it names the checkbox, and reserve hoverContent for supplementary row actions."},{"guidance":false,"description":"Do not assume clicking the text toggles the checkbox; the checkbox itself is the control."}],"anatomy":[{"name":"Checkbox","required":true,"description":"The controlled boolean choice."},{"name":"Text","required":true,"description":"A one-line label and optional one-line description."},{"name":"End content","required":false,"description":"A value optionally replaced by hover actions."}],"accessibility":["label names the checkbox; provide names on interactive controls supplied through hoverContent."],"props":[{"path":"label","type":"string","required":true,"description":"Label text for the checkbox (always rendered for accessibility)."},{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether the checkbox is disabled."},{"path":"isReadOnly","type":"boolean","required":false,"default":"false","description":"Whether the checkbox is read-only.\nDisplays the current state at full opacity but prevents interaction.\nUnlike `isDisabled`, read-only checkboxes are not visually dimmed."},{"path":"description","type":"string | undefined","required":false,"description":"A second line under the text, such as an id under a name; one line too."},{"path":"value","type":"boolean","required":true,"description":"Whether the box is ticked."},{"path":"onChange","type":"(value: boolean) => void","required":true,"description":"Called with the box's next state."},{"path":"endContent","type":"ReactNode","required":false,"description":"Content at the end of the row, such as a count."},{"path":"hoverContent","type":"ReactNode","required":false,"description":"Content shown instead of `endContent` while the row is hovered or holds the focus, such as a\nlink to the item; the two share one place, so nothing moves. None without it."}],"examples":[{"name":"Default","description":"A box and its text.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/CheckRow.stories.tsx"},{"name":"Filled","description":"Ticked, with a second line and a count at the end.","source":"export const Filled: Story = { args: { value: true, description: \"ID 1001\", endContent: count } };","file":"packages/ui/src/molecules/CheckRow.stories.tsx"},{"name":"Interactive","description":"Ticking changes the box.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(false, (value, set) => (\n <CheckRow\n {...args}\n value={value}\n endContent={count}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/CheckRow.stories.tsx"},{"name":"HoverContent","description":"A count at rest, an action instead while the row is hovered or focused.","source":"export const HoverContent: Story = { args: { endContent: count, hoverContent: open } };","file":"packages/ui/src/molecules/CheckRow.stories.tsx"},{"name":"EndTip","description":"A count not ready yet, explained by a tip.","source":"export const EndTip: Story = {\n args: {\n endContent: (\n <>\n <Text color=\"secondary\">--</Text>\n <InfoTip label=\"Not counted yet.\" />\n </>\n ),\n },\n};","file":"packages/ui/src/molecules/CheckRow.stories.tsx"},{"name":"Disabled","description":"Blocked and dimmed, e.g. at the most that can be picked.","source":"export const Disabled: Story = { args: { isDisabled: true, endContent: count } };","file":"packages/ui/src/molecules/CheckRow.stories.tsx"},{"name":"ReadOnly","description":"Read-only: the state shows, not dimmed, and cannot change.","source":"export const ReadOnly: Story = { args: { isReadOnly: true, value: true, endContent: count } };","file":"packages/ui/src/molecules/CheckRow.stories.tsx"},{"name":"LongText","description":"A long text and second line, each cut to one line; hover for the whole text.","source":"export const LongText: Story = {\n args: { label: LONG, description: \"ID 1234567890-abcdefghij-klmnopqrst\", endContent: count },\n render: (args) => (\n <Narrow>\n <CheckRow {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/CheckRow.stories.tsx"},{"name":"Many","description":"Many rows one under the other, as a list of options.","source":"export const Many: Story = {\n render: (args) => (\n <VStack gap={1.5}>\n {Array.from({ length: 12 }, (_, index) => (\n <CheckRow\n key={index}\n {...args}\n label={`City ${index + 1}`}\n value={index % 3 === 0}\n endContent={<Text color=\"secondary\">{(index + 1) * 100}</Text>}\n hoverContent={open}\n />\n ))}\n </VStack>\n ),\n};","file":"packages/ui/src/molecules/CheckRow.stories.tsx"},{"name":"Loading","description":"Loading: `RowSkeletons variant=\"check\"` in the rows' place, as high as they are.","source":"export const Loading: Story = {\n render: () => <RowSkeletons variant=\"check\" labels={{ loading: \"Loading\" }} />,\n};","file":"packages/ui/src/molecules/CheckRow.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <CheckRow {...args} description=\"ID 1001\" endContent={count} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/CheckRow.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <CheckRow {...args} />\n </Case>\n <Case caption=\"Ticked, second line and count\">\n <CheckRow {...args} value description=\"ID 1001\" endContent={count} />\n </Case>\n <Case caption=\"Hover content (hover the row)\">\n <CheckRow {...args} endContent={count} hoverContent={open} />\n </Case>\n <Case caption=\"Disabled\">\n <CheckRow {...args} isDisabled endContent={count} />\n </Case>\n <Case caption=\"Read-only\">\n <CheckRow {...args} isReadOnly value endContent={count} />\n </Case>\n <Case caption=\"Long, narrow\">\n <Narrow>\n <CheckRow {...args} label={LONG} endContent={count} />\n </Narrow>\n </Case>\n <Case caption=\"Loading\">\n <RowSkeletons variant=\"check\" count={2} labels={{ loading: \"Loading\" }} />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/CheckRow.stories.tsx"}],"primaryExample":"Default"},{"name":"Chip","level":"atoms","category":"Data display","keywords":["pill","picked value","remove"],"import":"import { Chip } from \"@antscorp/ui\";","description":"Show one picked value in a compact pill.","usage":"Show one picked value in a compact pill. Supply onRemove and labels.remove together to offer removal; the host owns the picked values.","bestPractices":[{"guidance":true,"description":"Use a short, recognizable label and provide a translated remove label naming the value."},{"guidance":false,"description":"Do not use colour alone to explain why a value is invalid; provide surrounding validation text."}],"anatomy":[{"name":"Label","required":true,"description":"The picked value, truncated with a tooltip when needed."},{"name":"Remove button","required":false,"description":"Removes the value; hidden in read-only mode."}],"accessibility":["Name the remove action through labels.remove.","Hover removal also appears on keyboard focus; keep the action reachable with Tab."],"related":[{"name":"astryx/Badge","when":"Show an informational status or category rather than a removable picked value."}],"props":[{"path":"label","type":"string","required":true,"description":"The text label displayed in the token."},{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether the token is disabled."},{"path":"tone","type":"\"accent\" | \"danger\" | \"neutral\" | undefined","required":false,"description":"`neutral` (default) is the plain grey chip; `accent` the pale accent one; `danger` a value no longer valid."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The chip's height, `sm` or `md` (default), as the controls beside it."},{"path":"maxWidth","type":"number | undefined","required":false,"description":"The widest the chip grows, in pixels; 200 by default."},{"path":"labels","type":"{ readonly remove?: string | undefined; } | undefined","required":false,"description":"The texts the chip shows; the host translates them."},{"path":"labels.remove","type":"string | undefined","required":false,"description":"The name of the remove button, such as \"Remove Web\"; required with `onRemove`, which shows no button without it."},{"path":"onRemove","type":"(() => void) | undefined","required":false,"description":"Called by the remove button; no button without it."},{"path":"isRemoveOnHover","type":"boolean","required":false,"description":"Shows the remove button only while the chip is hovered or has the keyboard, as a small round\nbutton over its end; it stays in the tab order. Without it the button always shows after the text."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Read-only: no remove button."}],"examples":[{"name":"Default","description":"An accent chip whose remove button shows on hover or keyboard focus.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/Chip.stories.tsx"},{"name":"Neutral","description":"The plain grey chip, its remove button always shown.","source":"export const Neutral: Story = { args: { tone: \"neutral\", isRemoveOnHover: false } };","file":"packages/ui/src/atoms/Chip.stories.tsx"},{"name":"Danger","description":"A value no longer valid.","source":"export const Danger: Story = { args: { tone: \"danger\", label: \"Deleted source\" } };","file":"packages/ui/src/atoms/Chip.stories.tsx"},{"name":"WithoutRemove","description":"No remove button: none is offered.","source":"export const WithoutRemove: Story = { render: (args) => <Chip {...args} onRemove={undefined} /> };","file":"packages/ui/src/atoms/Chip.stories.tsx"},{"name":"Interactive","description":"Removing takes the chip away from a row of them.","source":"export const Interactive: Story = {\n render: (args) =>\n withState([\"Website\", \"Mobile app\", \"Kiosk\"], (items, set) => (\n <HStack gap={2} wrap=\"wrap\">\n {items.map((item) => (\n <Chip\n {...args}\n key={item}\n label={item}\n labels={{ remove: `Remove ${item}` }}\n onRemove={() => {\n args.onRemove?.();\n set(items.filter((other) => other !== item));\n }}\n />\n ))}\n </HStack>\n )),\n};","file":"packages/ui/src/atoms/Chip.stories.tsx"},{"name":"LongLabel","description":"A long label is cut short at 200px; the tooltip shows it whole.","source":"export const LongLabel: Story = { args: { label: LONG } };","file":"packages/ui/src/atoms/Chip.stories.tsx"},{"name":"NarrowMaxWidth","description":"A narrower limit.","source":"export const NarrowMaxWidth: Story = { args: { label: LONG, maxWidth: 120 } };","file":"packages/ui/src/atoms/Chip.stories.tsx"},{"name":"Disabled","description":"Dimmed, its remove button blocked.","source":"export const Disabled: Story = { args: { isDisabled: true, isRemoveOnHover: false } };","file":"packages/ui/src/atoms/Chip.stories.tsx"},{"name":"ReadOnly","description":"Read-only: the value without a remove button.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/atoms/Chip.stories.tsx"},{"name":"Medium","description":"The `md` height.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/atoms/Chip.stories.tsx"},{"name":"InNarrowFrame","description":"Many chips wrapping in a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <HStack gap={2} wrap=\"wrap\">\n {[\"Website\", \"Mobile app\", \"Kiosk\", LONG, \"Email\", \"Call centre\"].map((item) => (\n <Chip {...args} key={item} label={item} />\n ))}\n </HStack>\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/Chip.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Accent, remove on hover\">\n <Chip {...args} />\n </Case>\n <Case caption=\"Neutral, remove always\">\n <Chip {...args} tone=\"neutral\" isRemoveOnHover={false} />\n </Case>\n <Case caption=\"Danger\">\n <Chip {...args} tone=\"danger\" label=\"Deleted source\" />\n </Case>\n <Case caption=\"Long label\">\n <Chip {...args} label={LONG} />\n </Case>\n <Case caption=\"Disabled\">\n <Chip {...args} isDisabled isRemoveOnHover={false} />\n </Case>\n <Case caption=\"Read-only\">\n <Chip {...args} isReadOnly />\n </Case>\n <Case caption=\"Medium\">\n <Chip {...args} size=\"md\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/Chip.stories.tsx"}],"primaryExample":"Default"},{"name":"ChipListAddPopover","level":"organisms","category":"Selection","keywords":["chips","multiple","popover","apply"],"import":"import { ChipListAddPopover } from \"@antscorp/ui\";","description":"Pick several values as removable chips through a searchable checkbox popover.","usage":"Pick several values as removable chips through a searchable checkbox popover. Checkbox edits remain a draft until Apply; closing another way drops them.","bestPractices":[{"guidance":true,"description":"Use onRemove to confirm direct chip removal, and separately handle removals arriving in onChange from Apply."},{"guidance":false,"description":"Do not discard unknown stored values just because options has not loaded them; they remain visible as invalid values or loading placeholders."}],"anatomy":[{"name":"Picked values","required":false,"description":"Tokens or pill Chips when values are picked, with unknown values retained."},{"name":"Add button","required":false,"description":"Opens the checkbox popover unless read-only."},{"name":"Popover","required":false,"description":"Search, draft checkboxes and Apply."},{"name":"Error","required":false,"description":"A translated selection problem."}],"accessibility":["Supply translated search, add and apply labels plus labels.remove for each chip; labels.loading names loading placeholders."],"related":[{"name":"TransferPicker","when":"Keep available rows and picked values visible together with immediate changes."}],"props":[{"path":"label","type":"string","required":true,"description":"Names the list."},{"path":"options","type":"readonly ChipListOption<string>[]","required":true,"description":"What can be picked."},{"path":"options[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What the picked list holds for this option."},{"path":"options[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The chip's and the checkbox's text."},{"path":"options[].isInvalid","type":"boolean","required":false,"description":"Flagged as no longer valid: its chip shows in the error tone."},{"path":"value","type":"readonly string[]","required":true,"description":"The picked values, as chips. A value missing from `options` shows by itself, as invalid."},{"path":"onChange","type":"(value: string[]) => void","required":true,"description":"Called with the picked values: those of `options` in their order, then any unknown ones."},{"path":"onRemove","type":"((value: string) => void) | undefined","required":false,"description":"When given, a chip's remove button calls it with the value instead of `onChange`, so the host\ncan ask first. Removals made through the popover's Apply arrive in `onChange`; the host can\ncompare the next list with `value` there to confirm those too."},{"path":"labels","type":"{ readonly add: string; readonly search: string; readonly apply: string; readonly noMatch: string; readonly remove: (label: string) => string; readonly empty?: string | undefined; readonly loading?: string | undefined; }","required":true,"description":"The texts the view shows; the host translates them."},{"path":"labels.add","type":"string","required":false,"requiredWhenParent":true,"description":"The add button's text and the popover's name, such as \"Add source\"."},{"path":"labels.search","type":"string","required":false,"requiredWhenParent":true,"description":"The search box's name and placeholder."},{"path":"labels.apply","type":"string","required":false,"requiredWhenParent":true,"description":"The popover's apply button, such as \"Apply\"."},{"path":"labels.noMatch","type":"string","required":false,"requiredWhenParent":true,"description":"Shown in the popover when the search matches nothing."},{"path":"labels.remove","type":"(label: string) => string","required":false,"requiredWhenParent":true,"description":"The name of a chip's remove button, such as \"Remove Web\"."},{"path":"labels.empty","type":"string | undefined","required":false,"description":"Shown while nothing is picked."},{"path":"labels.loading","type":"string | undefined","required":false,"description":"The name of the loading status, such as \"Loading\"; used while `isLoading`."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The input's and buttons' size, `md` by default; the checkboxes are always `md`, as every checkbox."},{"path":"chipTone","type":"\"accent\" | \"neutral\" | undefined","required":false,"description":"Draws the picked values as pill `Chip`s of this tone (an invalid one in `danger`), spaced\nwider; without it they are small tokens."},{"path":"isRemoveOnHover","type":"boolean","required":false,"description":"With `chipTone`, a chip's remove button shows only while it is hovered or has the keyboard."},{"path":"addShape","type":"\"default\" | \"pill\" | undefined","required":false,"description":"The add button's shape: `default`, or `pill` for a fully rounded one."},{"path":"isOpen","type":"boolean","required":false,"description":"Whether the popover is open; give it with `onOpenChange` to control it."},{"path":"onOpenChange","type":"((isOpen: boolean) => void) | undefined","required":false,"description":"Called when the popover opens or closes (closing without Apply drops the ticks)."},{"path":"isLoading","type":"boolean","required":false,"description":"The options are loading: a picked value `options` already knows is its chip, every other one\na skeleton chip (none while nothing is picked), and the add button is blocked."},{"path":"loadingCount","type":"number | undefined","required":false,"description":"While `isLoading`, this many skeleton chips stand in for the whole list, picked or not: for a\nlist that loads anew, such as the sources of an event just picked. Without it, the chips follow\n`isLoading`'s rule."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the list; the add button and the remove buttons are blocked, and nothing is hidden."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Read-only: no add button and no remove buttons."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the pick, already in the user's language."}],"examples":[{"name":"Default","description":"Two chips and the add button.","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"Empty","description":"Nothing picked: the empty text.","source":"export const Empty: Story = { args: { value: [] } };","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"Interactive","description":"Picking in the popover and removing chips change the list.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<string[]>([\"website\"], (value, set) => (\n <ChipListAddPopover\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"PopoverOpen","description":"The popover open with a pending tick: Apply is enabled; closing it instead drops the tick.","source":"export const PopoverOpen: Story = {\n args: { isOpen: true, onOpenChange: fn<() => void>() },\n play: async () => {\n await userEvent.click(\n await within(document.body).findByRole(\"checkbox\", { name: \"Call center\" }),\n );\n },\n};","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"InvalidChip","description":"A chip for a value that is flagged invalid, or missing from the options, shows in red.","source":"export const InvalidChip: Story = {\n args: {\n options: options.map((option, index) => ({\n value: option.value,\n label: option.label,\n isInvalid: index === 1,\n })),\n value: [\"website\", \"mobile-app\", \"gone-id\"],\n },\n};","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"ConfirmedRemoval","description":"The host asks before a removal: `onRemove` takes it over from `onChange`.","source":"export const ConfirmedRemoval: Story = { args: { onRemove: fn<(value: string) => void>() } };","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"Loading","description":"The options are loading, the picked labels known (the last list): the chips stay, the add button is blocked.","source":"export const Loading: Story = { args: { isLoading: true } };","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"LoadingUnknownLabels","description":"The options are loading, the picked labels unknown: one skeleton chip per picked value.","source":"export const LoadingUnknownLabels: Story = { args: { isLoading: true, options: [] } };","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"LoadingList","description":"A list loading anew (the sources of an event just picked): five skeleton chips stand in for it.","source":"export const LoadingList: Story = { args: { isLoading: true, loadingCount: 5 } };","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"WithError","description":"A problem with the pick.","source":"export const WithError: Story = { args: { error: \"A source no longer exists.\" } };","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"Disabled","description":"Dimmed: the add and remove buttons are blocked, and nothing is hidden.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"ReadOnly","description":"Read-only: no add button, no remove buttons.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"Medium","description":"The `md` size.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"SingleChip","description":"A single chip.","source":"export const SingleChip: Story = { args: { value: [\"website\"] } };","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"ManyChips","description":"Thirty chips, which wrap.","source":"export const ManyChips: Story = {\n args: {\n options: Array.from({ length: 30 }, (_, index) => ({\n value: `source-${index}`,\n label: `Source number ${index + 1}`,\n })),\n value: Array.from({ length: 30 }, (_, index) => `source-${index}`),\n },\n};","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"AccentChips","description":"Pill chips in the accent tone, their remove buttons shown on hover or focus, and a pill add button.","source":"export const AccentChips: Story = {\n args: { chipTone: \"accent\", isRemoveOnHover: true, addShape: \"pill\" },\n};","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"AccentChipsLong","description":"Pill chips with a long label, cut short with a tooltip, and an invalid one.","source":"export const AccentChipsLong: Story = {\n args: {\n ...AccentChips.args,\n options: [\n ...options,\n { value: \"long\", label: \"A source with a very long name that does not fit the chip\" },\n ],\n value: [\"website\", \"long\", \"gone-id\"],\n },\n};","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"AccentChipsReadOnly","description":"Pill chips, read-only: no remove buttons and no add button.","source":"export const AccentChipsReadOnly: Story = { args: { ...AccentChips.args, isReadOnly: true } };","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, where the chips wrap.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <ChipListAddPopover {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Picked\">\n <ChipListAddPopover {...args} />\n </Case>\n <Case caption=\"Empty\">\n <ChipListAddPopover {...args} value={[]} />\n </Case>\n <Case caption=\"Invalid chip, error\">\n <ChipListAddPopover {...args} value={[\"website\", \"gone-id\"]} error=\"A source is gone.\" />\n </Case>\n <Case caption=\"Loading, labels known\">\n <ChipListAddPopover {...args} isLoading />\n </Case>\n <Case caption=\"Loading, labels unknown\">\n <ChipListAddPopover {...args} options={[]} isLoading />\n </Case>\n <Case caption=\"Loading, one known and one unknown\">\n <ChipListAddPopover {...args} value={[\"website\", \"gone-id\"]} isLoading />\n </Case>\n <Case caption=\"Loading anew, five skeleton chips\">\n <ChipListAddPopover {...args} isLoading loadingCount={5} />\n </Case>\n <Case caption=\"Loading, read-only, medium, labels unknown\">\n <ChipListAddPopover {...args} options={[]} isLoading isReadOnly size=\"md\" />\n </Case>\n <Case caption=\"Disabled\">\n <ChipListAddPopover {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <ChipListAddPopover {...args} isReadOnly />\n </Case>\n <Case caption=\"Medium\">\n <ChipListAddPopover {...args} size=\"md\" />\n </Case>\n <Case caption=\"Accent pill chips\">\n <ChipListAddPopover\n {...args}\n chipTone=\"accent\"\n isRemoveOnHover\n addShape=\"pill\"\n value={[\"website\", \"gone-id\"]}\n />\n </Case>\n <Case caption=\"Accent pill chips, disabled\">\n <ChipListAddPopover {...args} chipTone=\"accent\" addShape=\"pill\" isDisabled />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"}],"primaryExample":"Default"},{"name":"ChipSkeletons","level":"atoms","category":"Feedback","keywords":["loading","skeleton","chips","placeholder"],"import":"import { ChipSkeletons } from \"@antscorp/ui\";","description":"Reserve the shape of picked values while their labels load.","usage":"Reserve the shape of picked values while their labels load. Use RowSkeletons for a vertical options list.","bestPractices":[{"guidance":true,"description":"Match count, size and shape to the values being loaded, and supply label for the loading status."},{"guidance":false,"description":"Do not replace already known labels with skeletons unless the whole list is being reloaded."}],"anatomy":[{"name":"Chip bars","required":true,"description":"A row of rounded shimmer bars with varied widths."}],"accessibility":["The wrapper has role status and uses label as its accessible name."],"props":[{"path":"label","type":"string | undefined","required":false,"description":"The loading status's accessible name, e.g. \"Loading\"."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The chips' height step, `md` by default."},{"path":"shape","type":"\"chip\" | \"token\" | undefined","required":false,"description":"`chip` is as high as the controls (`Chip`); `token` is Astryx's Token, 8px shorter."},{"path":"count","type":"number | undefined","required":false,"description":"How many chips stand in, 3 by default; as many as the values that load, when known."}],"examples":[{"name":"Default","description":"As high as Astryx's Token chips.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/ChipSkeletons.stories.tsx"},{"name":"ChipShape","description":"As high as the controls, for `Chip`s.","source":"export const ChipShape: Story = { args: { shape: \"chip\" } };","file":"packages/ui/src/atoms/ChipSkeletons.stories.tsx"},{"name":"Medium","description":"The taller step.","source":"export const Medium: Story = { args: { size: \"md\", shape: \"chip\" } };","file":"packages/ui/src/atoms/ChipSkeletons.stories.tsx"},{"name":"One","description":"One chip per value that loads: here a single one.","source":"export const One: Story = { args: { count: 1 } };","file":"packages/ui/src/atoms/ChipSkeletons.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <ChipSkeletons {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/ChipSkeletons.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Token, sm\">\n <ChipSkeletons {...args} />\n </Case>\n <Case caption=\"Chip, sm\">\n <ChipSkeletons {...args} shape=\"chip\" />\n </Case>\n <Case caption=\"Chip, md\">\n <ChipSkeletons {...args} shape=\"chip\" size=\"md\" />\n </Case>\n <Case caption=\"One\">\n <ChipSkeletons {...args} count={1} />\n </Case>\n <Case caption=\"Narrow\">\n <Narrow>\n <ChipSkeletons {...args} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/ChipSkeletons.stories.tsx"}],"primaryExample":"Default"},{"name":"ChoiceDialog","level":"molecules","category":"Overlays","keywords":["dialog","choice","confirm","radio"],"import":"import { ChoiceDialog } from \"@antscorp/ui\";","description":"Ask for one choice before confirming a change, with optional details under the selected option.","usage":"Ask for one choice before confirming a change, with optional details under the selected option. Use ConfirmDialog when no choice is required.","bestPractices":[{"guidance":true,"description":"Keep value and option details controlled, and use isConfirmDisabled while the selected details are incomplete."},{"guidance":false,"description":"Do not apply the choice merely when onValueChange fires; onConfirm is the explicit confirmation action."}],"anatomy":[{"name":"Header","required":true,"description":"The question supplied by title."},{"name":"Description","required":true,"description":"Explains what the user is choosing."},{"name":"Choices","required":true,"description":"A radio list revealing the selected option's detail."},{"name":"Actions","required":true,"description":"Translated cancel and confirm buttons."}],"accessibility":["Provide descriptive title and option labels; closing the dialog, including Escape, calls onCancel."],"props":[{"path":"isOpen","type":"boolean","required":true,"description":"Open while a choice waits for an answer."},{"path":"title","type":"string","required":true,"description":"The question, e.g. \"How should these rows be saved?\"."},{"path":"description","type":"string","required":true,"description":"What the user is choosing before confirming."},{"path":"options","type":"readonly { readonly value: RadioListProps[\"value\"]; readonly label: string; readonly detail?: ReactNode; }[]","required":true,"description":"The choices, in order; only the selected one's detail is shown."},{"path":"options[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What `onValueChange` gets when this option is picked."},{"path":"options[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The option's text."},{"path":"options[].detail","type":"ReactNode","required":false,"description":"Extra content below this option while it is selected; may contain controls."},{"path":"value","type":"string","required":true,"description":"The selected option's value, controlled by the caller."},{"path":"onValueChange","type":"(value: string) => void","required":true,"description":"Called with the value of the option picked."},{"path":"labels","type":"{ readonly confirm: string; readonly cancel: string; }","required":true,"description":"The texts of the two buttons; the host translates them."},{"path":"labels.confirm","type":"string","required":false,"requiredWhenParent":true,"description":"The confirming button, e.g. \"Save\"."},{"path":"labels.cancel","type":"string","required":false,"requiredWhenParent":true,"description":"The button that drops the choice, e.g. \"Cancel\"."},{"path":"isConfirmDisabled","type":"boolean","required":false,"description":"Blocks confirmation while the selected option's details are incomplete."},{"path":"onConfirm","type":"() => void","required":true,"description":"Called when the user confirms the current choice."},{"path":"onCancel","type":"() => void","required":true,"description":"Called when the user cancels or closes the dialog, including Escape."}],"examples":[{"name":"Default","description":"Open with the first option chosen and no extra controls.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/ChoiceDialog.stories.tsx"},{"name":"Closed","description":"Closed: nothing shows.","source":"export const Closed: Story = { args: { isOpen: false } };","file":"packages/ui/src/molecules/ChoiceDialog.stories.tsx"},{"name":"WithDetail","description":"The second option chosen, with a usable text field directly below it.","source":"export const WithDetail: Story = { args: { value: \"copy\" } };","file":"packages/ui/src/molecules/ChoiceDialog.stories.tsx"},{"name":"Interactive","description":"Picking another option reveals its controls; the caller owns the selected value.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(args.value, (value, set) => (\n <ChoiceDialog\n {...args}\n value={value}\n onValueChange={(next) => {\n args.onValueChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/ChoiceDialog.stories.tsx"},{"name":"ConfirmDisabled","description":"Confirmation is blocked while an option's required details are incomplete.","source":"export const ConfirmDisabled: Story = {\n args: {\n value: \"copy\",\n isConfirmDisabled: true,\n options: [\n { value: \"replace\", label: \"Replace existing rows\" },\n {\n value: \"copy\",\n label: \"Save a separate copy\",\n detail: <TextInput label=\"Copy name\" value=\"\" onChange={fn<(value: string) => void>()} />,\n },\n ],\n },\n};","file":"packages/ui/src/molecules/ChoiceDialog.stories.tsx"},{"name":"LongContent","description":"Long question, description, option labels and buttons wrap without shortening the text.","source":"export const LongContent: Story = {\n args: {\n title: \"How should all of the selected rows be saved alongside the rows already in this table?\",\n description:\n \"Choose whether to replace every row already saved in this table or keep the existing rows and save all of the selected rows in a separate copy with a name of your choice.\",\n options: [\n { value: \"replace\", label: \"Replace all of the existing rows with the newly selected rows\" },\n {\n value: \"copy\",\n label: \"Keep all existing rows and save the selected rows together in a separate copy\",\n },\n ],\n labels: { confirm: \"Save all selected rows\", cancel: \"Keep everything as it is\" },\n },\n};","file":"packages/ui/src/molecules/ChoiceDialog.stories.tsx"},{"name":"ManyOptions","description":"Ten choices exercise the dialog's scrollable content.","source":"export const ManyOptions: Story = {\n args: {\n value: \"format-1\",\n options: Array.from({ length: 10 }, (_, index) => ({\n value: `format-${index + 1}`,\n label: `Saved format ${index + 1}`,\n })),\n },\n};","file":"packages/ui/src/molecules/ChoiceDialog.stories.tsx"},{"name":"Empty","description":"An empty collection; the caller blocks confirmation until choices become available.","source":"export const Empty: Story = { args: { options: [], isConfirmDisabled: true } };","file":"packages/ui/src/molecules/ChoiceDialog.stories.tsx"},{"name":"SingleOption","description":"Just one available option.","source":"export const SingleOption: Story = { args: { options: options.slice(0, 1) } };","file":"packages/ui/src/molecules/ChoiceDialog.stories.tsx"},{"name":"InNarrowFrame","description":"Long content inside a real 280px viewport, so the modal's viewport clamping is exercised.","source":"export const InNarrowFrame: Story = {\n render: (_args, { id }) => (\n // A raw iframe gives top-layer modal content its own viewport; a surrounding box cannot.\n <iframe\n title=\"Choice dialog at 280px\"\n width={280}\n height={600}\n src={`./iframe.html?id=${id.split(\"--\")[0]}--long-content&viewMode=story`}\n />\n ),\n};","file":"packages/ui/src/molecules/ChoiceDialog.stories.tsx"},{"name":"AllStates","description":"The key states, each in its own viewport so their modal layers do not overlap.","source":"export const AllStates: Story = {\n render: (_args, { id }) => (\n <Gallery>\n {[\n { caption: \"Default\", story: \"default\" },\n { caption: \"Second option with detail\", story: \"with-detail\" },\n { caption: \"Confirmation disabled\", story: \"confirm-disabled\" },\n { caption: \"Long content\", story: \"long-content\" },\n { caption: \"Many options\", story: \"many-options\" },\n ].map(({ caption, story }) => (\n <Case key={story} caption={caption}>\n {/* A raw iframe isolates each modal's top layer and focus from the other previews. */}\n <iframe\n title={caption}\n width=\"100%\"\n height={600}\n src={`./iframe.html?id=${id.split(\"--\")[0]}--${story}&viewMode=story`}\n />\n </Case>\n ))}\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/ChoiceDialog.stories.tsx"}],"primaryExample":"Default"},{"name":"ChoiceTiles","level":"molecules","category":"Selection","keywords":["tiles","choices","actions","cards"],"import":"import { ChoiceTiles } from \"@antscorp/ui\";","description":"Offer a few actions as cards with an icon, title and short description.","usage":"Offer a few actions as cards with an icon, title and short description. Use RevealRadioGroup when a persistent selected value is required.","bestPractices":[{"guidance":true,"description":"Make each title distinguish the action and use onSelect to show or perform what follows."},{"guidance":false,"description":"Do not use tiles as toggles; this view has no selected state."}],"anatomy":[{"name":"Icon circle","required":true,"description":"Host glyph displayed in an accent circle on each tile."},{"name":"Title","required":true,"description":"The bold action text and button name."},{"name":"Description","required":true,"description":"Supporting text limited to two lines."}],"accessibility":["label names the group; each tile is a button named by its title and supports click, Enter and Space."],"props":[{"path":"label","type":"string","required":true,"description":"The name of the group of tiles, for assistive technology; the host shows its own caption."},{"path":"items","type":"readonly ChoiceTile<string>[]","required":true,"description":"The tiles, in order; they wrap onto more rows when the width runs out."},{"path":"items[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What `onSelect` is called with when the tile is picked."},{"path":"items[].title","type":"string","required":false,"requiredWhenParent":true,"description":"The tile's bold title; it also names its button."},{"path":"items[].description","type":"string","required":false,"requiredWhenParent":true,"description":"A line under the title saying what the choice does."},{"path":"items[].icon","type":"ReactNode","required":false,"requiredWhenParent":true,"description":"The glyph in the tile's circle, drawn in the accent colour, such as an `Icon`."},{"path":"onSelect","type":"(value: string) => void","required":true,"description":"Called with a tile's value when it is picked, by a click, Enter or Space."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims every tile and blocks picking."}],"examples":[{"name":"Default","description":"Four tiles in a row, as a form offers its ways to go on.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/ChoiceTiles.stories.tsx"},{"name":"Empty","description":"No tile offered: nothing shows.","source":"export const Empty: Story = { args: { items: [] } };","file":"packages/ui/src/molecules/ChoiceTiles.stories.tsx"},{"name":"SingleTile","description":"A single tile, when only one way is offered.","source":"export const SingleTile: Story = { args: { items: TILES.slice(0, 1) } };","file":"packages/ui/src/molecules/ChoiceTiles.stories.tsx"},{"name":"Interactive","description":"Picking a tile shows what was picked, as a host shows the editor that follows.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<string | null>(null, (value, set) =>\n value === null ? (\n <ChoiceTiles\n {...args}\n onSelect={(next) => {\n args.onSelect(next);\n set(next);\n }}\n />\n ) : (\n <VStack gap={2}>\n <Text>Picked: {value}</Text>\n <TextAction\n label=\"Start over\"\n onClick={() => {\n set(null);\n }}\n />\n </VStack>\n ),\n ),\n};","file":"packages/ui/src/molecules/ChoiceTiles.stories.tsx"},{"name":"Disabled","description":"Every tile dimmed and blocked, as on a read-only or disabled form.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/ChoiceTiles.stories.tsx"},{"name":"LongContent","description":"Long titles and descriptions: the description stops at two lines.","source":"export const LongContent: Story = {\n args: {\n items: [\n {\n value: \"long\",\n title: \"A rather long title for a tile\",\n description:\n \"A description long enough to need more than the two lines a tile has room for, so it is cut\",\n icon: <Icon icon=\"info\" size=\"lg\" />,\n },\n ...TILES.slice(1),\n ],\n },\n};","file":"packages/ui/src/molecules/ChoiceTiles.stories.tsx"},{"name":"ManyTiles","description":"Many tiles wrap onto more rows.","source":"export const ManyTiles: Story = {\n args: {\n items: Array.from({ length: 10 }, (_, index) => ({\n value: `tile-${index + 1}`,\n title: `Tile ${index + 1}`,\n description: \"One more way\",\n icon: <Icon icon=\"check\" size=\"lg\" />,\n })),\n },\n};","file":"packages/ui/src/molecules/ChoiceTiles.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, the tiles wrap.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <ChoiceTiles {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/ChoiceTiles.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <ChoiceTiles {...args} />\n </Case>\n <Case caption=\"Single tile\">\n <ChoiceTiles {...args} items={TILES.slice(0, 1)} />\n </Case>\n <Case caption=\"Disabled\">\n <ChoiceTiles {...args} isDisabled />\n </Case>\n <Case caption=\"Narrow\">\n <Narrow>\n <ChoiceTiles {...args} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/ChoiceTiles.stories.tsx"}],"primaryExample":"Default"},{"name":"CommitTextField","level":"molecules","category":"Inputs","keywords":["text","draft","commit","blur"],"import":"import { CommitTextField } from \"@antscorp/ui\";","description":"Edit text locally and report a single change on blur or single-line Enter.","usage":"Edit text locally and report a single change on blur or single-line Enter. Both single-line and multiline fields forward controlXstyle and containerXstyle. Use TokenTextField when matched tokens need atomic pills.","bestPractices":[{"guidance":true,"description":"Return accepted text through value; use isDraftKeptOnCommit only when a refused draft should remain visible."},{"guidance":false,"description":"Do not expect Enter to commit a multiline field; it inserts a new line."}],"anatomy":[{"name":"Label","required":true,"description":"The field's accessible name, optionally visually hidden."},{"name":"Input","required":true,"description":"A single-line control or a multiline text area holding a draft."},{"name":"Help and error","required":false,"description":"Translated description and validation text."}],"accessibility":["Always supply label, including when isLabelHidden is true."],"related":[{"name":"TokenTextField","when":"Matched tokens need to appear as atomic pills."}],"props":[{"path":"containerXstyle","type":"StyleXStyles","required":false,"description":"StyleX styles for the field container (the Field box around the label,\ncontrol, and status). Has no effect where no Field box renders: inside\nInputGroup (style the InputGroup instead) and in a `horizontal-labels`\nFormLayout."},{"path":"controlXstyle","type":"StyleXStyles","required":false,"description":"StyleX styles for the visual control box: the element that paints the\nfield border and background and holds its icons and end controls. Never\nthe native input."},{"path":"label","type":"string","required":true,"description":"Label text for the input (always rendered for accessibility)."},{"path":"isLabelHidden","type":"boolean","required":false,"default":"false","description":"Whether to visually hide the label (still accessible to screen readers)."},{"path":"description","type":"string","required":false,"description":"Description text displayed between the label and input."},{"path":"isRequired","type":"boolean","required":false,"default":"false","description":"Whether the field is required. Mutually exclusive with isOptional."},{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether the input is disabled."},{"path":"isReadOnly","type":"boolean","required":false,"default":"false","description":"Whether the input is read-only.\nThe value is shown at full opacity and still submits with the form, but\ncannot be edited. Unlike `isDisabled`, a read-only input is not dimmed and\nstays in the tab order — use it for a value the user should see and send\nbut not change. `isDisabled` takes precedence when both are set."},{"path":"placeholder","type":"string","required":false,"description":"Placeholder text shown when the input is empty."},{"path":"width","type":"SizeValue","required":false,"description":"Width of the field. Numbers are treated as pixels, strings are used as-is\n(e.g. `'100%'`). Sizes the whole field (label, control, and status) so they\nstay aligned, unlike setting width via `controlXstyle`/`controlClassName`/`controlStyle`."},{"path":"value","type":"string","required":true,"description":"The committed text. A new value from outside (an agent, undo) replaces the draft."},{"path":"onCommit","type":"(value: string) => void","required":true,"description":"Called with the draft on blur or Enter, only when it differs from `value`."},{"path":"size","type":"\"md\" | \"sm\"","required":false,"description":"The input's height, `md` by default."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the value, already in the user's language."},{"path":"isMultiline","type":"boolean","required":false,"description":"A text area instead of a single line; Enter then adds a line."},{"path":"rows","type":"number","required":false,"description":"For `isMultiline`: how many rows the text area shows, `4` by default."},{"path":"isDraftKeptOnCommit","type":"boolean","required":false,"description":"`false` (default): after a commit the draft goes back to `value`, so a refused text does not\nstay in the field. `true` keeps the typed text until `value` changes from outside."},{"path":"maxLength","type":"number","required":false,"description":"Limits insertions, preserving existing text and allowing over-limit values to shrink."},{"path":"pattern","type":"() => RegExp","required":false,"description":"Fresh global pattern for whole tokens, counted as one character even in plain text."}],"examples":[{"name":"Default","description":"Type, then leave the field or press Enter to commit.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"StyledParts","description":"Multiline fields forward the same control and Field styles as single-line fields.","source":"export const StyledParts: Story = {\n args: {\n isMultiline: true,\n controlXstyle: partStyles.control,\n containerXstyle: partStyles.container,\n },\n};","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"EmptyWithPlaceholder","description":"Empty, with a placeholder and help under the label.","source":"export const EmptyWithPlaceholder: Story = {\n args: { value: \"\", placeholder: \"Untitled\", description: \"Shown in the list.\" },\n};","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"Interactive","description":"The committed value follows what was typed, as a host would store it.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(args.value, (value, set) => (\n <CommitTextField\n {...args}\n value={value}\n description={`Committed: \"${value}\"`}\n onCommit={(next) => {\n args.onCommit(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"WithError","description":"With a problem with the value.","source":"export const WithError: Story = { args: { error: \"A name is required.\" } };","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"Disabled","description":"Dimmed and blocked.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"ReadOnly","description":"Shows the text, not dimmed, and blocks typing.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"Medium","description":"The `md` height.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"Multiline","description":"A four-row text area; Enter adds a line.","source":"export const Multiline: Story = { args: { isMultiline: true, label: \"Notes\", value: \"\" } };","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"TallMultiline","description":"A text area of eight rows.","source":"export const TallMultiline: Story = {\n args: { isMultiline: true, rows: 8, label: \"Notes\", value: \"\" },\n};","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"Required","description":"Marked as required.","source":"export const Required: Story = { args: { isRequired: true } };","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"KeepsDraftAfterCommit","description":"The typed text stays in the field after a commit that the host refuses.","source":"export const KeepsDraftAfterCommit: Story = {\n args: { isDraftKeptOnCommit: true, description: \"The host ignores every commit.\" },\n};","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"LongValue","description":"A long value in a single line, which the input scrolls.","source":"export const LongValue: Story = {\n args: {\n value:\n \"A welcome series for everyone who signed up this month but has not yet placed a first order\",\n },\n};","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"MaxLength","description":"Insertions fit 10 characters; an existing longer value stays and can be shortened.","source":"export const MaxLength: Story = { args: { maxLength: 10 } };","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"LabelHidden","description":"The label kept for screen readers only.","source":"export const LabelHidden: Story = { args: { isLabelHidden: true } };","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: { value: \"A welcome series for everyone who signed up\", description: \"Shown in the list.\" },\n render: (args) => (\n <Narrow>\n <CommitTextField {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Filled\">\n <CommitTextField {...args} />\n </Case>\n <Case caption=\"Empty\">\n <CommitTextField {...args} value=\"\" placeholder=\"Untitled\" />\n </Case>\n <Case caption=\"Error\">\n <CommitTextField {...args} error=\"A name is required.\" />\n </Case>\n <Case caption=\"Disabled\">\n <CommitTextField {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <CommitTextField {...args} isReadOnly />\n </Case>\n <Case caption=\"Medium\">\n <CommitTextField {...args} size=\"md\" />\n </Case>\n <Case caption=\"Multiline\">\n <CommitTextField {...args} isMultiline value=\"Line one\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"}],"primaryExample":"Default"},{"name":"ConfirmDialog","level":"molecules","category":"Overlays","keywords":["confirmation","dialog","cancel","loss"],"import":"import { ConfirmDialog } from \"@antscorp/ui\";","description":"Ask before a change that loses work, optionally paired with useConfirmedChange.","usage":"Ask before a change that loses work, optionally paired with useConfirmedChange. Use ChoiceDialog when the user must choose an option before confirming.","bestPractices":[{"guidance":true,"description":"Explain the consequence in description and make labels.action name the actual change."},{"guidance":false,"description":"Do not perform the pending change until onConfirm; onCancel should discard the pending request."}],"anatomy":[{"name":"Header","required":true,"description":"The confirmation question supplied by title."},{"name":"Description","required":true,"description":"The consequence of the pending change."},{"name":"Actions","required":true,"description":"The cancel button and confirming action."}],"accessibility":["Supply a clear title and translated action and cancel labels; dialog behavior is provided by Astryx Dialog."],"props":[{"path":"isOpen","type":"boolean","required":true,"description":"Whether the dialog is open."},{"path":"title","type":"string","required":true,"description":"Dialog title. Linked to the dialog via `aria-labelledby`."},{"path":"description","type":"string","required":true,"description":"Consequence description. Linked to the dialog via `aria-describedby`."},{"path":"labels","type":"{ readonly action: string; readonly cancel: string; }","required":true,"description":"The texts of the two buttons; the host translates them."},{"path":"labels.action","type":"string","required":false,"requiredWhenParent":true,"description":"The confirming button, e.g. \"Remove\"."},{"path":"labels.cancel","type":"string","required":false,"requiredWhenParent":true,"description":"The button that drops the change, e.g. \"Cancel\"."},{"path":"onConfirm","type":"() => void","required":true,"description":"Called when the user confirms."},{"path":"onCancel","type":"() => void","required":true,"description":"Called when the user cancels or closes the dialog."}],"examples":[{"name":"Default","description":"The dialog while a change waits for an answer (pending).","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/ConfirmDialog.stories.tsx"},{"name":"Closed","description":"Closed: nothing shows.","source":"export const Closed: Story = { args: { isOpen: false } };","file":"packages/ui/src/molecules/ConfirmDialog.stories.tsx"},{"name":"WithUseConfirmedChange","description":"With `useConfirmedChange`: the button asks for a change, the dialog holds it until answered.","source":"export const WithUseConfirmedChange: Story = { render: () => <WithConfirmedChange /> };","file":"packages/ui/src/molecules/ConfirmDialog.stories.tsx"},{"name":"LongContent","description":"Long title and description, and long button labels.","source":"export const LongContent: Story = {\n args: {\n title: \"Remove every branch that still has steps in it, together with all of their steps?\",\n description:\n \"The branches, every step inside them and the settings of those steps go. Anything that points at them is left without a target. Undo brings them all back, in the order they were.\",\n labels: { action: \"Remove branches and steps\", cancel: \"Keep them for now\" },\n },\n};","file":"packages/ui/src/molecules/ConfirmDialog.stories.tsx"}],"primaryExample":"Default"},{"name":"DateTimeInput","level":"molecules","category":"Inputs","keywords":["date","time","partial","calendar"],"import":"import { DateTimeInput } from \"@antscorp/ui\";","description":"Edit a local date and time while either part is incomplete.","usage":"Edit a local date and time while either part is incomplete. Set hasTime=false for a date-only value; labels.timezone only displays a note. hourFormat defaults to 12h; choose 24h when needed. Localize calendar and clear text with the facade's InternationalizationProvider.","bestPractices":[{"guidance":true,"description":"Keep wire values as strings and use frame=none when the individual date and time labels should be visible."},{"guidance":false,"description":"Do not require the value to be complete on every onChange; empty and partial strings are supported."}],"anatomy":[{"name":"Date input","required":true,"description":"The date portion, optionally constrained by min."},{"name":"Time input","required":false,"description":"The time portion when hasTime is enabled."},{"name":"Note","required":false,"description":"labels.timezone shown beside the time."},{"name":"Help and error","required":false,"description":"Group guidance or date-field guidance with frame=none."}],"accessibility":["Supply label and labels.date and labels.time; the default bare group hides the individual visible labels while preserving their names.","Read-only mode shows the date and time strings in named read-only text fields with no clear or picker actions."],"props":[{"path":"isDisabled","type":"boolean","required":false,"description":"Disables fields through the unframed fieldset; with `frame=\"card\"`, disable children yourself."},{"path":"hourFormat","type":"TimeInputHourFormat","required":false,"default":"'12h'","description":"Hour format for display.\n- '12h': Display as 12-hour with AM/PM (e.g., \"2:30 PM\")\n- '24h': Display as 24-hour (e.g., \"14:30\")"},{"path":"label","type":"string","required":true,"description":"The group's label, e.g. \"Start date\"; it names the group and is not shown."},{"path":"labels","type":"{ readonly date: string; readonly time: string; readonly timezone?: string | undefined; }","required":true,"description":"The texts the inputs show; the host translates them."},{"path":"labels.date","type":"string","required":false,"requiredWhenParent":true,"description":"The date input's label: its accessible name in a group, shown with `frame=\"none\"`."},{"path":"labels.time","type":"string","required":false,"requiredWhenParent":true,"description":"The time input's label: its accessible name in a group, shown with `frame=\"none\"`."},{"path":"labels.timezone","type":"string | undefined","required":false,"description":"A time zone, or any note, shown beside the time."},{"path":"value","type":"string","required":true,"description":"The local date-time (`YYYY-MM-DD HH:mm:ss`); empty or partial while it is being filled in."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The inputs' size, `md` by default."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows readable date and time fields at full opacity, without clear or picker actions."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the date-time, already in the user's language."},{"path":"hasTime","type":"boolean","required":false,"description":"`false` picks a date only: the time input is left out and `onChange` gets `YYYY-MM-DD`, or empty."},{"path":"frame","type":"\"bare\" | \"none\"","required":false,"description":"`bare` (default) puts both inputs, with hidden labels, in an unframed group that `label` names\nfor assistive tech, for a host that shows the label itself. `none` draws no group: it shows\n`labels.date` and `labels.time` above the inputs and hangs `description` and `error` on the\ndate input."},{"path":"description","type":"string | undefined","required":false,"description":"Help above the inputs, or under the date input with `frame=\"none\"`."},{"path":"isRequired","type":"boolean","required":false,"description":"Marks the date as required."},{"path":"isTimeLockedWithoutDate","type":"boolean","required":false,"description":"The time input stays disabled until a date is picked."},{"path":"min","type":"string | undefined","required":false,"description":"The earliest valid calendar date (`YYYY-MM-DD`); an absent or invalid minimum leaves dates unconstrained."},{"path":"onChange","type":"(value: string) => void","required":true,"description":"Called with the new date-time; a part not picked yet is empty, a new date starts at 00:00:00."}],"examples":[{"name":"Default","description":"A date and a time with the time zone beside them.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"TwentyFourHour","description":"An explicit 24-hour clock; the default remains 12-hour.","source":"export const TwentyFourHour: Story = { args: { hourFormat: \"24h\", value: \"2026-10-13 13:30:00\" } };","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"Empty","description":"Nothing picked yet.","source":"export const Empty: Story = { args: { value: \"\" } };","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"DateOnly","description":"Only the date picked, so the time is empty.","source":"export const DateOnly: Story = { args: { value: \"2026-10-13\" } };","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"Interactive","description":"The value follows what is picked.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(\"\", (value, set) => (\n <DateTimeInput\n {...args}\n value={value}\n labels={{ ...args.labels, timezone: value === \"\" ? \"Nothing picked\" : value }}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"NoTimezone","description":"Without a time zone note.","source":"export const NoTimezone: Story = {\n args: { labels: { date: \"Start date, date\", time: \"Start date, time\" } },\n};","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"WithError","description":"With a problem with the date-time.","source":"export const WithError: Story = { args: { error: \"The start must be in the future.\" } };","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"Disabled","description":"Dimmed and blocked.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"ReadOnly","description":"Shows the date-time, not dimmed, and ignores changes.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"Medium","description":"The `md` size.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"DateOnlyPicker","description":"A date only: no time input.","source":"export const DateOnlyPicker: Story = { args: { hasTime: false, value: \"2026-10-13\" } };","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"Inline","description":"Labels shown above the inputs, with help and the problem on the date, and no group around.","source":"export const Inline: Story = {\n args: {\n frame: \"none\",\n labels: { date: \"Start date\", time: \"Start time\" },\n description: \"When sending begins.\",\n isRequired: true,\n },\n};","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"TimeLockedWithoutDate","description":"Inline, with the time locked until a date is picked.","source":"export const TimeLockedWithoutDate: Story = {\n args: { ...Inline.args, value: \"\", isTimeLockedWithoutDate: true },\n};","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"InlineDateOnly","description":"Inline and date only.","source":"export const InlineDateOnly: Story = { args: { ...Inline.args, hasTime: false } };","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"WithMin","description":"The calendar offers no date before the 10th, such as an end after its start.","source":"export const WithMin: Story = { args: { min: \"2026-10-10\" } };","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"WithDescription","description":"With help under the label.","source":"export const WithDescription: Story = { args: { description: \"When sending begins.\" } };","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"LongContent","description":"A long label and a long time zone note.","source":"export const LongContent: Story = {\n args: {\n label: \"The date and time on which the campaign should start sending\",\n labels: {\n date: \"Start date, date\",\n time: \"Start date, time\",\n timezone: \"(GMT-03:30) America/St_Johns, Newfoundland and Labrador\",\n },\n },\n};","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, where the pieces wrap.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <DateTimeInput {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Filled\">\n <DateTimeInput {...args} />\n </Case>\n <Case caption=\"Empty\">\n <DateTimeInput {...args} value=\"\" />\n </Case>\n <Case caption=\"Error\">\n <DateTimeInput {...args} error=\"The start must be in the future.\" />\n </Case>\n <Case caption=\"Inline\">\n <DateTimeInput\n {...args}\n frame=\"none\"\n labels={{ date: \"Start date\", time: \"Start time\" }}\n error=\"Too early\"\n />\n </Case>\n <Case caption=\"Date only\">\n <DateTimeInput {...args} hasTime={false} />\n </Case>\n <Case caption=\"Disabled\">\n <DateTimeInput {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <DateTimeInput {...args} isReadOnly />\n </Case>\n <Case caption=\"Medium\">\n <DateTimeInput {...args} size=\"md\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"}],"primaryExample":"Default"},{"name":"DurationInput","level":"molecules","category":"Inputs","keywords":["duration","days","hours","seconds"],"import":"import { DurationInput } from \"@antscorp/ui\";","description":"Edit a length of time as days, hours, minutes and seconds.","usage":"Edit a length of time as days, hours, minutes and seconds. Use DateTimeInput for a calendar date and time instead of an elapsed duration.","bestPractices":[{"guidance":true,"description":"Translate every unit label and use max to constrain parts; reaching max.days disables the remaining parts."},{"guidance":false,"description":"Do not treat this partial duration object as an instant or assume unset parts are explicitly stored as zero."}],"anatomy":[{"name":"Part inputs","required":true,"description":"Four number inputs, each followed by its translated unit."},{"name":"Error","required":false,"description":"A translated problem with the whole duration."}],"accessibility":["label names the unframed group; labels names each unit's input."],"props":[{"path":"label","type":"string","required":true,"description":"Names the group; read out before each field in it."},{"path":"isDisabled","type":"boolean","required":false,"description":"Disables fields through the unframed fieldset; with `frame=\"card\"`, disable children yourself."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the group as a whole, already in the user's language."},{"path":"labels","type":"Readonly<Record<\"days\" | \"hours\" | \"minutes\" | \"seconds\", string>>","required":true,"description":"The unit of each part, already in the user's language, e.g. \"day(s)\": shown after its input\nand naming it for assistive tech."},{"path":"max","type":"Readonly<Partial<Record<\"days\" | \"hours\" | \"minutes\" | \"seconds\", number>>> | undefined","required":false,"description":"The most each part may be. At `max.days` days the other parts are disabled, as a length of\nwhole days that may not grow."},{"path":"value","type":"Readonly<Partial<Record<\"days\" | \"hours\" | \"minutes\" | \"seconds\", number>>> | undefined","required":true,"description":"The duration; a part not set shows empty, and undefined shows every part empty."},{"path":"onChange","type":"(duration: DurationValue) => void","required":true,"description":"Called with the whole new duration on each change."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The inputs' size, `md` by default."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows the value, not dimmed, and blocks changes."}],"examples":[{"name":"Default","description":"A duration with some parts set and the others empty.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"Empty","description":"Nothing set yet, or a stored value that is not a duration.","source":"export const Empty: Story = { args: { value: undefined } };","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"AllParts","description":"Every part set.","source":"export const AllParts: Story = { args: { value: { days: 3, hours: 4, minutes: 5, seconds: 6 } } };","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"Interactive","description":"The duration follows what is typed.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<DurationValue>({ days: 1 }, (value, set) => (\n <DurationInput\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"WithError","description":"With a problem with the duration.","source":"export const WithError: Story = { args: { error: \"The wait must be longer than zero.\" } };","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"Disabled","description":"Read-only.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"ReadOnly","description":"Shows the value, not dimmed, and blocks changes.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"WithMax","description":"With limits, interactive: each part stops at its max (try 75 minutes), and 365 days disables the rest.","source":"export const WithMax: Story = { ...Interactive, args: { max: MAX } };","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"AtMax","description":"Days at their max: the other parts are disabled.","source":"export const AtMax: Story = { args: { max: MAX, value: { days: 365 } } };","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"Medium","description":"The `md` size, for a form whose other controls are `md`.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"LongContent","description":"Large numbers and long part labels.","source":"export const LongContent: Story = {\n args: {\n label: \"Wait before the next message is sent to the same person\",\n labels: {\n days: \"Whole days\",\n hours: \"Whole hours\",\n minutes: \"Whole minutes\",\n seconds: \"Whole seconds\",\n },\n value: { days: 36500, hours: 23, minutes: 59, seconds: 59 },\n },\n};","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, where the parts wrap.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <DurationInput {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Filled\">\n <DurationInput {...args} />\n </Case>\n <Case caption=\"Empty\">\n <DurationInput {...args} value={undefined} />\n </Case>\n <Case caption=\"Error\">\n <DurationInput {...args} error=\"The wait must be longer than zero.\" />\n </Case>\n <Case caption=\"Disabled\">\n <DurationInput {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <DurationInput {...args} isReadOnly />\n </Case>\n <Case caption=\"At max\">\n <DurationInput {...args} max={MAX} value={{ days: 365 }} />\n </Case>\n <Case caption=\"Medium\">\n <DurationInput {...args} size=\"md\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/DurationInput.stories.tsx"}],"primaryExample":"Default"},{"name":"FieldGroup","level":"molecules","category":"Layout","keywords":["fields","group","fieldset","card"],"import":"import { FieldGroup } from \"@antscorp/ui\";","description":"Name several related fields and show a problem with the group as a whole.","usage":"Name several related fields and show a problem with the group as a whole. Use FieldRow to place a separate caption beside the grouped controls.","bestPractices":[{"guidance":true,"description":"Supply label even without a frame, and disable child controls explicitly when using frame=card."},{"guidance":false,"description":"Do not expect isDisabled to disable the card variant; only the unframed fieldset disables its descendants."}],"anatomy":[{"name":"Group label","required":true,"description":"An accessible name, visible above content in the card variant."},{"name":"Fields","required":true,"description":"Host controls with the requested gap."},{"name":"Description and error","required":false,"description":"Help and a translated problem with the group."}],"accessibility":["The unframed variant is a named fieldset; the card variant uses role group."],"props":[{"path":"label","type":"string","required":true,"description":"Names the group; read out before each field in it."},{"path":"frame","type":"\"card\" | \"none\" | undefined","required":false,"description":"`none` (default) draws no frame, and the label only names the group for assistive tech. `card`\nis a transparent Astryx card (role `group`) with the label above its content; it is not a\nfieldset, so `isDisabled` has no effect."},{"path":"description","type":"string | undefined","required":false,"description":"Help under the label."},{"path":"isDisabled","type":"boolean","required":false,"description":"Disables fields through the unframed fieldset; with `frame=\"card\"`, disable children yourself."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the group as a whole, already in the user's language."},{"path":"gap","type":"2 | 4 | undefined","required":false,"description":"The space between the fields, in Astryx spacing steps: `2` (8px, default) or `4` (16px)."},{"path":"children","type":"ReactNode","required":true,"description":"The fields."}],"examples":[{"name":"Default","description":"Fields named by one label, which only assistive tech reads.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/FieldGroup.stories.tsx"},{"name":"WithDescription","description":"With help under the label.","source":"export const WithDescription: Story = { args: { description: \"How we reach this person.\" } };","file":"packages/ui/src/molecules/FieldGroup.stories.tsx"},{"name":"Interactive","description":"Typing in a field inside the group.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(\"\", (name, set) => (\n <FieldGroup {...args}>\n <TextInput label=\"Name\" value={name} onChange={set} />\n </FieldGroup>\n )),\n};","file":"packages/ui/src/molecules/FieldGroup.stories.tsx"},{"name":"WithError","description":"With a problem with the group as a whole.","source":"export const WithError: Story = { args: { error: \"Fill in at least one field.\" } };","file":"packages/ui/src/molecules/FieldGroup.stories.tsx"},{"name":"Disabled","description":"Every field in the group is disabled.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/FieldGroup.stories.tsx"},{"name":"LongContent","description":"A long label, description and error, and many fields.","source":"export const LongContent: Story = {\n args: {\n label: \"Contact details for the person who receives the weekly summary of every report\",\n description:\n \"How we reach this person, in order of preference, when the first way fails and again later.\",\n error: \"At least one of these fields must be filled in before the group can be saved.\",\n children: Array.from({ length: 10 }, (_, index) => (\n <TextInput key={index} label={`Field ${index + 1}`} value=\"\" onChange={fn<() => void>()} />\n )),\n },\n};","file":"packages/ui/src/molecules/FieldGroup.stories.tsx"},{"name":"SingleChild","description":"A group of one line.","source":"export const SingleChild: Story = { args: { children: <span>One line</span> } };","file":"packages/ui/src/molecules/FieldGroup.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: { description: \"How we reach this person.\", error: \"Fill in at least one field.\" },\n render: (args) => (\n <Narrow>\n <FieldGroup {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/FieldGroup.stories.tsx"},{"name":"Card","description":"A transparent card with the label above the fields, for a group nested in a form.","source":"export const Card: Story = {\n args: { frame: \"card\", description: \"How we reach this person.\", error: \"Fill in one field.\" },\n};","file":"packages/ui/src/molecules/FieldGroup.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <FieldGroup {...args} />\n </Case>\n <Case caption=\"Description\">\n <FieldGroup {...args} description=\"How we reach this person.\" />\n </Case>\n <Case caption=\"Error\">\n <FieldGroup {...args} error=\"Fill in at least one field.\" />\n </Case>\n <Case caption=\"Disabled\">\n <FieldGroup {...args} isDisabled />\n </Case>\n <Case caption=\"Card\">\n <FieldGroup {...args} frame=\"card\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/FieldGroup.stories.tsx"}],"primaryExample":"Default"},{"name":"FieldHeading","level":"atoms","category":"Data display","keywords":["heading","caption","field"],"import":"import { FieldHeading } from \"@antscorp/ui\";","description":"Show a separate supporting caption above controls whose own labels are hidden.","usage":"Show a separate supporting caption above controls whose own labels are hidden. Use FieldRow when the form needs a caption column beside its controls.","bestPractices":[{"guidance":true,"description":"Keep each control's own accessible label even when FieldHeading supplies the visible caption."},{"guidance":false,"description":"Do not treat this text as a label association or a semantic heading element."}],"props":[{"path":"label","type":"string","required":true,"description":"The heading's text, such as \"Start date\"."}],"examples":[{"name":"Default","description":"A field's heading on its own.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/FieldHeading.stories.tsx"},{"name":"AboveAControl","description":"Above a control whose own label is hidden, as a form's fields read.","source":"export const AboveAControl: Story = {\n render: (args) => (\n <VStack gap={1}>\n <FieldHeading {...args} />\n <Selector label={args.label} isLabelHidden options={[\"Once\", \"By day\"]} value=\"Once\" />\n </VStack>\n ),\n};","file":"packages/ui/src/atoms/FieldHeading.stories.tsx"},{"name":"LongHeading","description":"A long heading in a narrow frame wraps.","source":"export const LongHeading: Story = {\n args: { label: \"How many times should each item be included in this collection?\" },\n render: (args) => (\n <Narrow>\n <FieldHeading {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/FieldHeading.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <FieldHeading {...args} />\n </Case>\n <Case caption=\"Long\">\n <FieldHeading label=\"How many times should each item be included in this collection?\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/FieldHeading.stories.tsx"}],"primaryExample":"Default"},{"name":"FieldRow","level":"molecules","category":"Layout","keywords":["form","caption","columns","responsive"],"import":"import { FieldRow } from \"@antscorp/ui\";","description":"Arrange a form section as a caption column beside its controls, stacking when room is limited.","usage":"Arrange a form section as a caption column beside its controls, stacking when room is limited. Use SideLabelRow for padded rows inside a card.","bestPractices":[{"guidance":true,"description":"Keep caption widths consistent through the theme or labelWidth and use align=start for controls without a useful text baseline."},{"guidance":false,"description":"Do not rely on the visible caption to name individual controls; keep their own labels."}],"anatomy":[{"name":"Caption column","required":false,"description":"The label and optional description; its space remains when no label is supplied in horizontal layout."},{"name":"Control column","required":true,"description":"Host controls that move below the caption in a narrow layout."}],"props":[{"path":"label","type":"string | undefined","required":false,"description":"The row's caption, in a column at the start. Without it the column stays, empty, so the\ncontent lines up with the captioned rows around it; stacked,\nnothing is drawn above the content."},{"path":"description","type":"string | undefined","required":false,"description":"Help under the caption."},{"path":"labelWidth","type":"number | undefined","required":false,"description":"Overrides the theme's caption column width in pixels; unthemed rows default to 160px."},{"path":"orientation","type":"\"horizontal\" | \"vertical\" | undefined","required":false,"description":"`horizontal` (default) puts the caption beside the content, and stacks it above when there is no room; `vertical` always stacks."},{"path":"align","type":"\"baseline\" | \"start\" | undefined","required":false,"description":"`baseline` (default) sets the caption on the content's first line of text; `start` tops them\nboth, for content that opens with a control without text, such as a radio list, whose baseline\nis the control's bottom edge."},{"path":"children","type":"ReactNode","required":true,"description":"The row's controls; single-line controls use the theme's cap, with a 400px fallback."}],"examples":[{"name":"Default","description":"The caption at the start, the controls beside it.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/FieldRow.stories.tsx"},{"name":"WithDescription","description":"With help under the caption.","source":"export const WithDescription: Story = { args: { description: \"When it runs.\" } };","file":"packages/ui/src/molecules/FieldRow.stories.tsx"},{"name":"WideCaption","description":"A wider caption column.","source":"export const WideCaption: Story = { args: { labelWidth: 260 } };","file":"packages/ui/src/molecules/FieldRow.stories.tsx"},{"name":"Vertical","description":"Always stacked: the caption above the controls.","source":"export const Vertical: Story = { args: { orientation: \"vertical\" } };","file":"packages/ui/src/molecules/FieldRow.stories.tsx"},{"name":"AlignStart","description":"Content that opens with radios, topped with the caption: a radio has no text baseline.","source":"export const AlignStart: Story = { args: { align: \"start\", children: radios } };","file":"packages/ui/src/molecules/FieldRow.stories.tsx"},{"name":"NoCaption","description":"No caption: its column stays empty, so the controls line up with the captioned rows above.","source":"export const NoCaption: Story = { args: { label: undefined } };","file":"packages/ui/src/molecules/FieldRow.stories.tsx"},{"name":"LongContent","description":"A long caption and help, and several controls.","source":"export const LongContent: Story = {\n args: {\n label: \"A caption long enough to wrap over several lines of the column\",\n description: \"Help text that is also quite long and wraps in its own column too.\",\n children: (\n <>\n {field}\n {field}\n {field}\n </>\n ),\n },\n};","file":"packages/ui/src/molecules/FieldRow.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame there is no room beside the caption, so it moves above.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <FieldRow {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/FieldRow.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Beside\">\n <FieldRow {...args} />\n </Case>\n <Case caption=\"Radios, align start\">\n <FieldRow {...args} align=\"start\">\n {radios}\n </FieldRow>\n </Case>\n <Case caption=\"No caption\">\n <FieldRow {...args} label={undefined} />\n </Case>\n <Case caption=\"Vertical\">\n <FieldRow {...args} orientation=\"vertical\" />\n </Case>\n <Case caption=\"Narrow\">\n <Narrow>\n <FieldRow {...args} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/FieldRow.stories.tsx"}],"primaryExample":"Default"},{"name":"FieldSkeleton","level":"molecules","category":"Feedback","keywords":["field","loading","skeleton","placeholder"],"import":"import { FieldSkeleton } from \"@antscorp/ui\";","description":"Reserve a form field's shape while its data loads.","usage":"Reserve a form field's shape while its data loads. Use RowSkeletons for a list and ChipSkeletons for picked values.","bestPractices":[{"guidance":true,"description":"Show the real label when known and match isLabelHidden to the field that will replace the placeholder."},{"guidance":false,"description":"Do not add a separate status for every skeleton inside one named loading region; set hasStatus=false."}],"anatomy":[{"name":"Label","required":false,"description":"The real caption or a short shimmer bar unless isLabelHidden."},{"name":"Control placeholder","required":true,"description":"A shimmer as high as a control, respecting horizontal-labels layout."}],"accessibility":["The default wrapper is a status named by labels.loading; hasStatus=false hides the placeholder from assistive technology."],"props":[{"path":"label","type":"string | undefined","required":false,"description":"The field's real label, shown as a Field shows it; without it a short bar stands in for it."},{"path":"isLabelHidden","type":"boolean","required":false,"description":"Shows the control's shimmer alone, for a field whose caption is outside it (a `FieldRow`)."},{"path":"hasStatus","type":"boolean","required":false,"description":"Whether it is a status of its own (default); `false` draws no status and hides it from\nassistive tech, for a skeleton inside one status that names the whole load."},{"path":"labels","type":"{ readonly loading?: string | undefined; } | undefined","required":false,"description":"The texts the view shows; the host translates them."},{"path":"labels.loading","type":"string | undefined","required":false,"description":"The loading status's accessible name, e.g. \"Loading\"."}],"examples":[{"name":"Default","description":"No label known: a short bar stands in for it over the control.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/FieldSkeleton.stories.tsx"},{"name":"WithLabel","description":"The real label over the shimmering control.","source":"export const WithLabel: Story = { args: { label: \"Country\" } };","file":"packages/ui/src/molecules/FieldSkeleton.stories.tsx"},{"name":"LabelHidden","description":"The control's shimmer alone, for a caption outside it such as a `FieldRow`'s.","source":"export const LabelHidden: Story = { args: { isLabelHidden: true } };","file":"packages/ui/src/molecules/FieldSkeleton.stories.tsx"},{"name":"LongLabel","description":"A long label, as a Field wraps it.","source":"export const LongLabel: Story = {\n args: {\n label: \"The country the customer was living in when they first signed up for the newsletter\",\n },\n};","file":"packages/ui/src/molecules/FieldSkeleton.stories.tsx"},{"name":"HorizontalLabels","description":"Labels left of the controls, between real fields, as a settings form lays them out.","source":"export const HorizontalLabels: Story = {\n args: { label: \"Country\" },\n render: (args) => (\n <FormLayout direction=\"horizontal-labels\">\n <FieldSkeleton {...args} label=\"Name\" />\n <FieldSkeleton {...args} />\n <FieldSkeleton {...args} label={undefined} />\n </FormLayout>\n ),\n};","file":"packages/ui/src/molecules/FieldSkeleton.stories.tsx"},{"name":"InOneStatus","description":"Rows inside one status that names the load: each with `hasStatus={false}`, no status of its own\nand hidden from assistive tech.","source":"export const InOneStatus: Story = {\n render: (args) => (\n // A raw element: Astryx stacks take no role or name.\n <div role=\"status\" aria-label=\"Loading\">\n <FieldSkeleton {...args} hasStatus={false} />\n <FieldSkeleton {...args} hasStatus={false} />\n </div>\n ),\n};","file":"packages/ui/src/molecules/FieldSkeleton.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: LongLabel.args ?? {},\n render: (args) => (\n <Narrow>\n <FieldSkeleton {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/FieldSkeleton.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"No label\">\n <FieldSkeleton {...args} />\n </Case>\n <Case caption=\"Label\">\n <FieldSkeleton {...args} label=\"Country\" />\n </Case>\n <Case caption=\"Label hidden\">\n <FieldSkeleton {...args} isLabelHidden />\n </Case>\n <Case caption=\"Horizontal labels\">\n <FormLayout direction=\"horizontal-labels\">\n <FieldSkeleton {...args} label=\"Country\" />\n </FormLayout>\n </Case>\n <Case caption=\"No status of its own\">\n <FieldSkeleton {...args} hasStatus={false} />\n </Case>\n <Case caption=\"Narrow\">\n <Narrow>\n <FieldSkeleton {...args} label=\"Country\" />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/FieldSkeleton.stories.tsx"}],"primaryExample":"Default"},{"name":"FrequencyLimitRow","level":"molecules","category":"Inputs","keywords":["frequency","limit","count","window"],"import":"import { FrequencyLimitRow } from \"@antscorp/ui\";","description":"Edit a count per time window as one sentence.","usage":"Edit a count per time window as one sentence. The window identified by everyWindow also reveals a number of units.","bestPractices":[{"guidance":true,"description":"Keep value controlled and supply translated labels and connecting words; use paths when the inputs must be found by revealPath."},{"guidance":false,"description":"Do not retain every for unrelated windows; switching away from everyWindow drops that extra number."}],"anatomy":[{"name":"Count input","required":true,"description":"The number of occurrences followed by words.times."},{"name":"Window select","required":true,"description":"The offered counting window."},{"name":"Every input","required":false,"description":"An extra number and words.every for the designated window."},{"name":"Trailing content","required":false,"description":"Host content finishing the sentence."}],"accessibility":["labels.times, labels.window and labels.every supply the hidden accessible input labels."],"props":[{"path":"value","type":"FrequencyLimit<string>","required":true,"description":"The limit shown."},{"path":"value.times","type":"number","required":false,"requiredWhenParent":true,"description":"How many times."},{"path":"value.window","type":"string","required":false,"requiredWhenParent":true,"description":"The window the times count in."},{"path":"value.every","type":"number | undefined","required":false,"description":"For the `everyWindow` only: how many days, weeks… the window lasts."},{"path":"windows","type":"readonly FrequencyWindow<string>[]","required":true,"description":"The windows to pick from."},{"path":"windows[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What `window` holds."},{"path":"windows[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The window's text, e.g. \"per day\"."},{"path":"everyWindow","type":"string | undefined","required":false,"description":"The window that shows the extra \"every N\" number; none when absent."},{"path":"labels","type":"{ readonly times: string; readonly window: string; readonly every: string; }","required":true,"description":"The label of each input, already in the user's language; hidden, read out by assistive tech."},{"path":"labels.times","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"labels.window","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"labels.every","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"words","type":"{ readonly times: string; readonly every: string; }","required":true,"description":"The words after the times and after the every number, e.g. \"time(s) per\" and \"day(s)\", so the row reads as a sentence."},{"path":"words.times","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"words.every","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"children","type":"ReactNode","required":false,"description":"Shown at the end of the sentence, e.g. who the limit counts per."},{"path":"max","type":"{ readonly times?: number | undefined; readonly every?: number | undefined; } | undefined","required":false,"description":"The largest times and every number the inputs take; none when absent."},{"path":"max.times","type":"number | undefined","required":false,"description":""},{"path":"max.every","type":"number | undefined","required":false,"description":""},{"path":"onChange","type":"(value: FrequencyLimit<string>) => void","required":true,"description":"Called with the whole new limit on each change."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The inputs' size, `md` by default."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims every input and blocks them."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows the value, not dimmed, and blocks changes."},{"path":"paths","type":"{ readonly times: Path; readonly window: Path; readonly every: Path; } | undefined","required":false,"description":"Marks each input with its value's path (`PathMark`, relative to the enclosing mark), so it can\nbe revealed; none when absent."},{"path":"paths.times","type":"Path","required":false,"requiredWhenParent":true,"description":""},{"path":"paths.window","type":"Path","required":false,"requiredWhenParent":true,"description":""},{"path":"paths.every","type":"Path","required":false,"requiredWhenParent":true,"description":""}],"examples":[{"name":"Default","description":"A limit of three times today.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"EveryWindow","description":"The \"every\" window shows its extra number.","source":"export const EveryWindow: Story = { args: { value: { times: 1, window: \"every\", every: 7 } } };","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"Interactive","description":"Picking the \"every\" window adds its number; any other drops it.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<FrequencyLimit<Window>>({ times: 1, window: \"hour\" }, (value, set) => (\n <FrequencyLimitRow\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"Disabled","description":"Disabled: dimmed and blocked.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"ReadOnly","description":"Shows the value, not dimmed, and blocks changes.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"Medium","description":"The `md` size, for a form whose other controls are `md`.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"WithMax","description":"At most 1000 times in at most 999 days: the steppers stop there.","source":"export const WithMax: Story = {\n args: { value: { times: 1000, window: \"every\", every: 999 }, max: { times: 1000, every: 999 } },\n};","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"LongContent","description":"A large count and long words between the inputs.","source":"export const LongContent: Story = {\n args: {\n value: { times: 1000000, window: \"every\", every: 365 },\n words: { times: \"times in total, counted in\", every: \"days long, for each\" },\n },\n};","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"WithTrailing","description":"Something at the end of the sentence, e.g. who the limit counts per.","source":"export const WithTrailing: Story = { args: { children: \"per person\" } };","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"Marked","description":"Each input marked with its value's path, so a host can reveal it (`PathMark`); looks the same.","source":"export const Marked: Story = {\n args: {\n value: { times: 2, window: \"every\", every: 3 },\n paths: { times: [\"cap\", \"times\"], window: [\"cap\", \"per\"], every: [\"cap\", \"per\", \"days\"] },\n },\n};","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"NoEveryWindow","description":"Without an \"every\" window, so no window shows a second number.","source":"export const NoEveryWindow: Story = {\n args: { everyWindow: undefined, windows: windows.slice(0, 2) },\n};","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, where the inputs wrap.","source":"export const InNarrowFrame: Story = {\n args: { value: { times: 2, window: \"every\", every: 3 } },\n render: (args) => (\n <Narrow>\n <FrequencyLimitRow {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"AllStates","description":"Each window option, one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n {windows.map((window) => (\n <Case key={window.value} caption={window.label}>\n <FrequencyLimitRow\n {...args}\n value={{\n times: 2,\n window: window.value,\n ...(window.value === \"every\" ? { every: 3 } : {}),\n }}\n />\n </Case>\n ))}\n <Case caption=\"Disabled\">\n <FrequencyLimitRow {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <FrequencyLimitRow {...args} isReadOnly />\n </Case>\n <Case caption=\"Medium\">\n <FrequencyLimitRow {...args} size=\"md\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"}],"primaryExample":"Default"},{"name":"HourOfDayRow","level":"organisms","category":"Selection","keywords":["hours","day","schedule","drag"],"import":"import { HourOfDayRow } from \"@antscorp/ui\";","description":"Pick hours in one day by toggling cells or dragging a run.","usage":"Pick hours in one day by toggling cells or dragging a run. Use HourOfWeekGrid when selections vary by weekday.","bestPractices":[{"guidance":true,"description":"Pass hours from 0 to 23 and format names through labels.hour; store the sorted unique result from onChange."},{"guidance":false,"description":"Do not compress the cells to fit a narrow panel; the row keeps its cell size and scrolls horizontally."}],"anatomy":[{"name":"Hour cells","required":true,"description":"Twenty-four cells showing picked hours and drag preview."},{"name":"Hour labels","required":true,"description":"Every third hour named below the row."},{"name":"Error","required":false,"description":"A translated problem below the selection."}],"accessibility":["label names the row; arrow keys move between hours and Space or Enter toggles the focused hour."],"props":[{"path":"label","type":"string","required":true,"description":"Names the row."},{"path":"value","type":"readonly number[]","required":true,"description":"The picked hours, 0 to 23."},{"path":"onChange","type":"(value: number[]) => void","required":true,"description":"Called with the picked hours, sorted and unique."},{"path":"labels","type":"{ readonly hour: (hour: number) => string; }","required":true,"description":"The texts the row shows; the host translates and formats them."},{"path":"labels.hour","type":"(hour: number) => string","required":false,"requiredWhenParent":true,"description":"The text for an hour (0 to 23), such as \"3AM\"; every third one shows under the row."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the row and blocks it."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows the pick, not dimmed, and ignores changes."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the pick, already in the user's language."}],"examples":[{"name":"Default","description":"Every hour picked, the default of the form this came from.","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/HourOfDayRow.stories.tsx"},{"name":"Empty","description":"Nothing picked.","source":"export const Empty: Story = { args: { value: [] } };","file":"packages/ui/src/organisms/HourOfDayRow.stories.tsx"},{"name":"Interactive","description":"Drag over hours to pick or clear a run; press one to toggle it.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<readonly number[]>(workHours, (value, set) => (\n <HourOfDayRow\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/organisms/HourOfDayRow.stories.tsx"},{"name":"WithError","description":"Nothing picked, and the problem that says so.","source":"export const WithError: Story = { args: { value: [], error: \"Pick at least one hour\" } };","file":"packages/ui/src/organisms/HourOfDayRow.stories.tsx"},{"name":"Disabled","description":"Dimmed and blocked.","source":"export const Disabled: Story = { args: { value: workHours, isDisabled: true } };","file":"packages/ui/src/organisms/HourOfDayRow.stories.tsx"},{"name":"ReadOnly","description":"Read-only: shown as it is, not dimmed.","source":"export const ReadOnly: Story = { args: { value: workHours, isReadOnly: true } };","file":"packages/ui/src/organisms/HourOfDayRow.stories.tsx"},{"name":"SingleHour","description":"A single hour picked.","source":"export const SingleHour: Story = { args: { value: [12] } };","file":"packages/ui/src/organisms/HourOfDayRow.stories.tsx"},{"name":"InNarrowFrame","description":"Narrower than the row: it scrolls sideways.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <HourOfDayRow {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/organisms/HourOfDayRow.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Every hour\">\n <HourOfDayRow {...args} />\n </Case>\n <Case caption=\"Work hours\">\n <HourOfDayRow {...args} value={workHours} />\n </Case>\n <Case caption=\"Error\">\n <HourOfDayRow {...args} value={[]} error=\"Pick at least one hour\" />\n </Case>\n <Case caption=\"Disabled\">\n <HourOfDayRow {...args} value={workHours} isDisabled />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/HourOfDayRow.stories.tsx"}],"primaryExample":"Default"},{"name":"HourOfWeekGrid","level":"organisms","category":"Selection","keywords":["hours","week","schedule","grid"],"import":"import { HourOfWeekGrid } from \"@antscorp/ui\";","description":"Pick hours across seven days by dragging a rectangle, toggling a day, or using the everyday row.","usage":"Pick hours across seven days by dragging a rectangle, toggling a day, or using the everyday row. Use HourOfDayRow when one daily selection is sufficient.","bestPractices":[{"guidance":true,"description":"Supply seven day labels Monday first and store cells as day * 24 + hour, from 0 to 167."},{"guidance":false,"description":"Do not assume read-only matches disabled presentation; read-only hides the everyday editing row while disabled keeps it."}],"anatomy":[{"name":"Day rows","required":true,"description":"Seven rows of twenty-four hour cells, with day-name actions."},{"name":"Everyday row","required":false,"description":"Controls that toggle whole hours across all days, hidden when read-only."},{"name":"Hour axis","required":true,"description":"Formatted hour names along the grid."},{"name":"Error","required":false,"description":"A translated problem below the grid."}],"accessibility":["label names the grid even when visually hidden; cells expose selection, arrow keys move focus, and Space or Enter toggles a cell."],"props":[{"path":"label","type":"string","required":true,"description":"Names the grid."},{"path":"isLabelHidden","type":"boolean","required":false,"description":"Keeps the label for assistive tech only, when the host already says what the grid is."},{"path":"value","type":"readonly number[]","required":true,"description":"The picked hours: cells `day * 24 + hour`, Monday being day 0, so 0 to 167."},{"path":"onChange","type":"(value: number[]) => void","required":true,"description":"Called with the picked cells, sorted and unique."},{"path":"labels","type":"{ readonly days: readonly string[]; readonly hour: (hour: number) => string; readonly everyday: string; }","required":true,"description":"The texts the grid shows; the host translates and formats them."},{"path":"labels.days","type":"readonly string[]","required":false,"requiredWhenParent":true,"description":"The seven day names, Monday first."},{"path":"labels.hour","type":"(hour: number) => string","required":false,"requiredWhenParent":true,"description":"The text for an hour (0 to 23), such as \"3AM\"."},{"path":"labels.everyday","type":"string","required":false,"requiredWhenParent":true,"description":"The name of the row that picks a whole hour on every day, such as \"Everyday\"."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the grid and blocks it; the \"everyday\" row stays."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Read-only: no \"everyday\" row, and nothing changes."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the pick, already in the user's language."}],"examples":[{"name":"Default","description":"Every hour picked, the default of the form this came from.","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/HourOfWeekGrid.stories.tsx"},{"name":"Empty","description":"Nothing picked.","source":"export const Empty: Story = { args: { value: [] } };","file":"packages/ui/src/organisms/HourOfWeekGrid.stories.tsx"},{"name":"Interactive","description":"Drag over cells to pick or clear a rectangle; press a day or an \"everyday\" cell to toggle it.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<readonly number[]>(workHours, (value, set) => (\n <HourOfWeekGrid\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/organisms/HourOfWeekGrid.stories.tsx"},{"name":"Weekdays","description":"Monday to Friday, 9 to 6.","source":"export const Weekdays: Story = { args: { value: workHours } };","file":"packages/ui/src/organisms/HourOfWeekGrid.stories.tsx"},{"name":"LabelHidden","description":"No visible label, when the host already says what the grid is; it still names the grid.","source":"export const LabelHidden: Story = { args: { isLabelHidden: true } };","file":"packages/ui/src/organisms/HourOfWeekGrid.stories.tsx"},{"name":"WithError","description":"No hour picked, with the problem under the grid.","source":"export const WithError: Story = { args: { value: [], error: \"Pick at least one hour.\" } };","file":"packages/ui/src/organisms/HourOfWeekGrid.stories.tsx"},{"name":"ReadOnly","description":"Read-only: no \"everyday\" row, and nothing changes.","source":"export const ReadOnly: Story = { args: { value: workHours, isReadOnly: true } };","file":"packages/ui/src/organisms/HourOfWeekGrid.stories.tsx"},{"name":"Disabled","description":"Dimmed and blocked, with the \"everyday\" row still drawn.","source":"export const Disabled: Story = { args: { value: workHours, isDisabled: true } };","file":"packages/ui/src/organisms/HourOfWeekGrid.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, where the grid scrolls sideways and keeps its cell size.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <HourOfWeekGrid {...args} value={workHours} />\n </Narrow>\n ),\n};","file":"packages/ui/src/organisms/HourOfWeekGrid.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Everything\">\n <HourOfWeekGrid {...args} />\n </Case>\n <Case caption=\"Weekdays 9 to 6\">\n <HourOfWeekGrid {...args} value={workHours} />\n </Case>\n <Case caption=\"Empty, error\">\n <HourOfWeekGrid {...args} value={[]} error=\"Pick at least one hour.\" />\n </Case>\n <Case caption=\"Read-only\">\n <HourOfWeekGrid {...args} value={workHours} isReadOnly />\n </Case>\n <Case caption=\"Disabled\">\n <HourOfWeekGrid {...args} value={workHours} isDisabled />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/HourOfWeekGrid.stories.tsx"}],"primaryExample":"Default"},{"name":"HoverSwap","level":"atoms","category":"Layout","keywords":["hover","actions","focus","row"],"import":"import { HoverSwap } from \"@antscorp/ui\";","description":"Share one position between resting content and row actions shown on hover or focus.","usage":"Share one position between resting content and row actions shown on hover or focus. Use permanent end content when the action should always be visible.","bestPractices":[{"guidance":true,"description":"Put stylex.defaultMarker() on the ancestor row so hover and focus-within reach both layers."},{"guidance":false,"description":"Do not assume the transparent layer is removed from the tab order or the accessibility tree."}],"anatomy":[{"name":"Resting layer","required":false,"description":"children is visible at rest."},{"name":"Action layer","required":true,"description":"hoverContent occupies the same grid cell and appears on ancestor hover or focus."}],"accessibility":["Controls in the transparent action layer remain tabbable; focusing within the marked row reveals them."],"props":[{"path":"children","type":"ReactNode","required":false,"description":"What shows at rest, such as a count."},{"path":"hoverContent","type":"ReactNode","required":true,"description":"What shows instead while the row is hovered or holds the focus, such as its actions."}],"examples":[{"name":"Default","description":"A count at rest; hover the row, or tab into it, for its remove button.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/HoverSwap.stories.tsx"},{"name":"EmptyAtRest","description":"Nothing at rest: the actions appear on hover only.","source":"export const EmptyAtRest: Story = { args: { children: undefined } };","file":"packages/ui/src/atoms/HoverSwap.stories.tsx"},{"name":"LongContent","description":"A long value at rest: the place is as wide as the wider content, so nothing moves.","source":"export const LongContent: Story = {\n args: { children: <Text color=\"secondary\">12,345,678 members</Text> },\n};","file":"packages/ui/src/atoms/HoverSwap.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width row.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <Row>\n <Text>Hanoi</Text>\n <HoverSwap {...args} />\n </Row>\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/HoverSwap.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Count, remove on hover\">\n <Row>\n <Text>Hanoi</Text>\n <HoverSwap {...args} />\n </Row>\n </Case>\n <Case caption=\"Nothing at rest\">\n <Row>\n <Text>Hue</Text>\n <HoverSwap hoverContent={args.hoverContent} />\n </Row>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/HoverSwap.stories.tsx"}],"primaryExample":"Default"},{"name":"InfoTip","level":"atoms","category":"Feedback","keywords":["help","tooltip","warning","explanation"],"import":"import { InfoTip } from \"@antscorp/ui\";","description":"Put a short supplementary explanation beside a control or value.","usage":"Put a short supplementary explanation beside a control or value. Use visible error text for a problem the user must act on.","bestPractices":[{"guidance":true,"description":"Supply a translated label explaining the nearby value; choose error tone for a warning glyph."},{"guidance":false,"description":"Do not put essential instructions only in a tooltip."}],"anatomy":[{"name":"Glyph","required":true,"description":"An info icon, or a warning icon in error tone."},{"name":"Tooltip","required":false,"description":"The explanation supplied by label, shown on hover or keyboard focus."}],"accessibility":["Tab reaches the icon's wrapper, named by label, and focus opens the tooltip. Keep essential instructions visible outside it."],"props":[{"path":"label","type":"string","required":true,"description":"The explanation, shown in the tooltip and read as the icon's name; already translated."},{"path":"tone","type":"\"error\" | \"info\" | undefined","required":false,"description":"`error`: a ⚠ in the error tone, for a problem; `info` (default): an (i) in the accent."}],"examples":[{"name":"Default","description":"The (i) whose tooltip explains the control beside it; hover or focus it.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/InfoTip.stories.tsx"},{"name":"ShortText","description":"A short explanation.","source":"export const ShortText: Story = { args: { label: \"Optional.\" } };","file":"packages/ui/src/atoms/InfoTip.stories.tsx"},{"name":"LongText","description":"A long explanation, which wraps inside the tooltip.","source":"export const LongText: Story = {\n args: {\n label:\n \"Rows are applied from the top and the first one that matches wins, so put the most specific rules first and the catch-all last. Rows below a match are never reached.\",\n },\n};","file":"packages/ui/src/atoms/InfoTip.stories.tsx"},{"name":"ErrorTone","description":"A problem: a ⚠ in the error tone, the reason in the tooltip.","source":"export const ErrorTone: Story = { args: { label: \"This item is archived.\", tone: \"error\" } };","file":"packages/ui/src/atoms/InfoTip.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, next to a label.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n Assignment <InfoTip {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/InfoTip.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <InfoTip {...args} />\n </Case>\n <Case caption=\"Short\">\n <InfoTip label=\"Optional.\" />\n </Case>\n <Case caption=\"Error tone\">\n <InfoTip label=\"This item is archived.\" tone=\"error\" />\n </Case>\n <Case caption=\"Long\">\n <InfoTip label=\"Rows are applied from the top and the first one that matches wins, so put the most specific rules first.\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/InfoTip.stories.tsx"}],"primaryExample":"Default"},{"name":"InlineEditableText","level":"molecules","category":"Inputs","keywords":["inline","text","edit","draft"],"import":"import { InlineEditableText } from \"@antscorp/ui\";","description":"Edit a title or body text in place, committing on blur or Enter without a separate edit mode.","usage":"Edit a title or body text in place, committing on blur or Enter without a separate edit mode. Use CommitTextField for a conventional labeled field.","bestPractices":[{"guidance":true,"description":"Supply both labels.edit and icons.edit to offer a pencil that focuses the input."},{"guidance":false,"description":"Do not expect a field or pencil in read-only mode; the view renders plain text with the placeholder when empty."}],"anatomy":[{"name":"Text","required":true,"description":"A draft input, or plain text in read-only mode."},{"name":"Trailing content","required":false,"description":"The after slot between the text and pencil."},{"name":"Pencil","required":false,"description":"A named host-supplied edit glyph shown on hover."}],"accessibility":["label names the input; labels.edit names the optional pencil button."],"props":[{"path":"label","type":"string","required":true,"description":"The field's accessible name; its label is not shown."},{"path":"value","type":"string","required":true,"description":"The committed text."},{"path":"placeholder","type":"string","required":true,"description":"Shown while the text is empty."},{"path":"onCommit","type":"(value: string) => void","required":true,"description":"Called with the text on blur or Enter, when it changed."},{"path":"maxLength","type":"number | undefined","required":false,"description":"The most characters the text keeps."},{"path":"labels","type":"{ readonly edit?: string | undefined; } | undefined","required":false,"description":"The texts the view shows; the host translates them."},{"path":"labels.edit","type":"string | undefined","required":false,"description":"The pencil's name, such as \"Edit name\"; the pencil shows only with this and `icons.edit`."},{"path":"icons","type":"{ readonly edit?: ReactNode; } | undefined","required":false,"description":"The host's glyphs."},{"path":"icons.edit","type":"ReactNode","required":false,"description":"The pencil, shown on hover, that puts the cursor in the field."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The input's height, `md` by default."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the field and blocks typing."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Plain text, nothing to edit: no field and no pencil."},{"path":"variant","type":"\"body\" | \"title\" | undefined","required":false,"description":"A bold title, or small grey body text; `body` by default."},{"path":"width","type":"number | undefined","required":false,"description":"The field's width in pixels, 240 by default."},{"path":"xstyle","type":"StyleXStyles","required":false,"description":"Extra styles for the text, in the field and in the plain text alike, e.g. a colour or a maximum width."},{"path":"after","type":"ReactNode","required":false,"description":"Shown after the text and before the pencil, e.g. a second field; the pencil stays at the end of the row."}],"examples":[{"name":"Title","description":"A bold title that reads as text; hover for the underline and the pencil.","source":"export const Title: Story = {};","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"Body","description":"Small grey body text, e.g. a description.","source":"export const Body: Story = {\n args: { variant: \"body\", label: \"Description\", value: \"Sent weekly\" },\n};","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"Empty","description":"Empty: the placeholder shows.","source":"export const Empty: Story = { args: { value: \"\" } };","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"Interactive","description":"Typing and leaving the field saves the text.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(args.value, (value, set) => (\n <InlineEditableText\n {...args}\n value={value}\n onCommit={(next) => {\n args.onCommit(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"ReadOnly","description":"Plain text, nothing to edit.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"ReadOnlyEmpty","description":"Plain text, empty: the placeholder.","source":"export const ReadOnlyEmpty: Story = { args: { isReadOnly: true, value: \"\", variant: \"body\" } };","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"Disabled","description":"The field and the pencil dimmed and blocked.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"Medium","description":"The `md` height.","source":"export const Medium: Story = { args: { size: \"md\", variant: \"body\" } };","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"LongValue","description":"A long value, shown whole in a tooltip.","source":"export const LongValue: Story = {\n args: {\n value:\n \"A welcome series for everyone who signed up this month but has not yet placed a first order\",\n },\n};","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"MaxLength","description":"Text cut at 20 characters.","source":"export const MaxLength: Story = { args: { maxLength: 20 } };","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: { width: 200, value: \"A welcome series for everyone who signed up this month\" },\n render: (args) => (\n <Narrow>\n <InlineEditableText {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"WithTrailingField","description":"A second field after the first; the pencil stays at the end of the row.","source":"export const WithTrailingField: Story = {\n args: {\n after: <span>Sent weekly</span>,\n },\n};","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"Restyled","description":"The text restyled through `xstyle`, here narrower than its content.","source":"export const Restyled: Story = {\n args: { xstyle: narrow, isReadOnly: true, value: \"A title cut short by a maximum width\" },\n};","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Title\">\n <InlineEditableText {...args} />\n </Case>\n <Case caption=\"Body\">\n <InlineEditableText {...args} variant=\"body\" value=\"Sent weekly\" />\n </Case>\n <Case caption=\"Empty\">\n <InlineEditableText {...args} value=\"\" />\n </Case>\n <Case caption=\"Read-only title\">\n <InlineEditableText {...args} isReadOnly />\n </Case>\n <Case caption=\"Read-only body\">\n <InlineEditableText {...args} isReadOnly variant=\"body\" value=\"Sent weekly\" />\n </Case>\n <Case caption=\"Disabled\">\n <InlineEditableText {...args} isDisabled />\n </Case>\n <Case caption=\"Medium\">\n <InlineEditableText {...args} size=\"md\" variant=\"body\" />\n </Case>\n <Case caption=\"Trailing field\">\n <InlineEditableText {...args} after={<span>Sent weekly</span>} />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"}],"primaryExample":"Title"},{"name":"JointLabel","level":"atoms","category":"Data display","keywords":["joint","and","or","rows"],"import":"import { JointLabel } from \"@antscorp/ui\";","description":"Show a short connecting word between related rows.","usage":"Show a short connecting word between related rows. Use LabeledDivider when alternatives need a horizontal rule.","bestPractices":[{"guidance":true,"description":"Pass the translated word in normal case and use width to align neighboring joints."},{"guidance":false,"description":"Do not encode rule evaluation in this label; the host owns the meaning and data."}],"props":[{"path":"label","type":"string","required":true,"description":"The joint word between two rows, such as \"or\", \"and\" or \"if\"; shown in capitals unless `case` says otherwise."},{"path":"width","type":"number | undefined","required":false,"description":"A fixed width in pixels, so the words of a list line up. Its own width without it."},{"path":"case","type":"\"sentence\" | \"upper\" | undefined","required":false,"description":"`upper` (default) shows the word in small capitals, as a quiet joint; `sentence` shows it as\ngiven, in body text, as a row's label reads (such as \"Where\", then \"And\")."}],"examples":[{"name":"Default","description":"The word that joins two rows; it is shown in capitals whatever its case.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/JointLabel.stories.tsx"},{"name":"FixedWidth","description":"A fixed width, so the words of a list line up.","source":"export const FixedWidth: Story = { args: { label: \"and\", width: 40 } };","file":"packages/ui/src/atoms/JointLabel.stories.tsx"},{"name":"AlignedList","description":"Words of different lengths in one fixed width, as in a list of rows.","source":"export const AlignedList: Story = {\n render: () => (\n <VStack gap={1}>\n <JointLabel label=\"if\" width={40} />\n <JointLabel label=\"and\" width={40} />\n <JointLabel label=\"or\" width={40} />\n </VStack>\n ),\n};","file":"packages/ui/src/atoms/JointLabel.stories.tsx"},{"name":"SentenceCase","description":"Sentence case, in body text: the word reads as a row's label, as \"Where\" then \"And\".","source":"export const SentenceCase: Story = { args: { label: \"Where\", width: 90, case: \"sentence\" } };","file":"packages/ui/src/atoms/JointLabel.stories.tsx"},{"name":"SentenceList","description":"A list's labels in sentence case, in one fixed width.","source":"export const SentenceList: Story = {\n render: () => (\n <VStack gap={1}>\n <JointLabel label=\"Where\" width={90} case=\"sentence\" />\n <JointLabel label=\"And\" width={90} case=\"sentence\" />\n <JointLabel label=\"And\" width={90} case=\"sentence\" />\n </VStack>\n ),\n};","file":"packages/ui/src/atoms/JointLabel.stories.tsx"},{"name":"LongWord","description":"A long word in a narrow frame.","source":"export const LongWord: Story = {\n args: { label: \"otherwise\" },\n render: (args) => (\n <Narrow>\n <JointLabel {...args} width={40} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/JointLabel.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <JointLabel {...args} />\n </Case>\n <Case caption=\"Fixed width\">\n <JointLabel label=\"and\" width={40} />\n </Case>\n <Case caption=\"Sentence case\">\n <JointLabel label=\"Where\" width={90} case=\"sentence\" />\n </Case>\n <Case caption=\"Long word\">\n <JointLabel label=\"otherwise\" width={40} />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/JointLabel.stories.tsx"}],"primaryExample":"Default"},{"name":"LabeledDivider","level":"atoms","category":"Layout","keywords":["separator","divider","alternatives","rule"],"import":"import { LabeledDivider } from \"@antscorp/ui\";","description":"Separate two blocks with a dashed line and a short joining word.","usage":"Separate two blocks with a dashed line and a short joining word. Use JointLabel for a compact word without the rule.","bestPractices":[{"guidance":true,"description":"Keep label short and use case to control capitalization without changing the translated text."},{"guidance":false,"description":"Do not use the divider as an interactive control."}],"anatomy":[{"name":"Rules","required":true,"description":"Dashed lines extend on both sides of the word."},{"name":"Joint word","required":true,"description":"The centered label explains the relationship between blocks."}],"accessibility":["The wrapper is a horizontal separator named by label."],"props":[{"path":"label","type":"string","required":true,"description":"The short word on the rule, such as \"or\"; it also names the separator."},{"path":"case","type":"\"sentence\" | \"upper\" | undefined","required":false,"description":"`sentence` (default) shows the word as given; `upper` in capitals, such as \"OR\"."}],"examples":[{"name":"Default","description":"The dashed rule with its word, as between two alternatives.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/LabeledDivider.stories.tsx"},{"name":"BetweenBlocks","description":"Between two blocks of content, as it is used.","source":"export const BetweenBlocks: Story = {\n render: (args) => (\n <VStack>\n <Text type=\"body\">First alternative</Text>\n <LabeledDivider {...args} />\n <Text type=\"body\">Second alternative</Text>\n </VStack>\n ),\n};","file":"packages/ui/src/atoms/LabeledDivider.stories.tsx"},{"name":"Upper","description":"The word in capitals, given in sentence case: \"or\" becomes \"OR\".","source":"export const Upper: Story = { args: { label: \"or\", case: \"upper\" } };","file":"packages/ui/src/atoms/LabeledDivider.stories.tsx"},{"name":"LongLabel","description":"A longer word.","source":"export const LongLabel: Story = { args: { label: \"or any of the following instead\" } };","file":"packages/ui/src/atoms/LabeledDivider.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <LabeledDivider {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/LabeledDivider.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <LabeledDivider {...args} />\n </Case>\n <Case caption=\"Upper\">\n <LabeledDivider label=\"or\" case=\"upper\" />\n </Case>\n <Case caption=\"Long label\">\n <LabeledDivider label=\"or any of the following instead\" />\n </Case>\n <Case caption=\"Narrow\">\n <Narrow>\n <LabeledDivider {...args} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/LabeledDivider.stories.tsx"}],"primaryExample":"Default"},{"name":"LoadMoreRow","level":"molecules","category":"Feedback","keywords":["pagination","loading","retry","sentinel"],"import":"import { LoadMoreRow } from \"@antscorp/ui\";","description":"Finish a paged list with loading placeholders or a failed-page retry action.","usage":"Finish a paged list with loading placeholders or a failed-page retry action. Use LoadState for the initial load that replaces the whole content.","bestPractices":[{"guidance":true,"description":"Keep loaded rows above this view and attach sentinelRef to the host's load-more observer."},{"guidance":false,"description":"Do not pass error without onRetry and expect an error row; both are needed to show it."}],"anatomy":[{"name":"Loading rows","required":false,"description":"Two timed skeleton rows in the requested variant."},{"name":"Retry row","required":false,"description":"The translated error and retry button."}],"accessibility":["Supply a clear translated labels.retry for the retry button."],"props":[{"path":"isLoadingMore","type":"boolean","required":false,"description":"The next page is loading: two skeleton rows, kept at least 300 ms once shown (`useLoadingShown`)."},{"path":"variant","type":"\"check\" | \"framed\" | \"option\" | undefined","required":false,"description":"The shape of those skeleton rows, as `RowSkeletons`' `variant`: `option` (default) or the list's own rows, such as `check`."},{"path":"error","type":"string | undefined","required":false,"description":"Why the next page failed, already in the user's language; shown with a retry button."},{"path":"onRetry","type":"(() => void) | undefined","required":false,"description":"Called when the retry button is pressed; no error row without it."},{"path":"labels","type":"{ readonly retry: string; }","required":true,"description":"The texts the view shows; the host translates them."},{"path":"labels.retry","type":"string","required":false,"requiredWhenParent":true,"description":"The retry button's text, e.g. \"Retry\"."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The retry button's size, `md` by default."},{"path":"sentinelRef","type":"RefObject<HTMLSpanElement | null> | undefined","required":false,"description":"The sentinel a list's load-more observer watches (`useLoadMore`)."}],"examples":[{"name":"Idle","description":"Nothing loading and nothing failed: only the invisible sentinel.","source":"export const Idle: Story = {};","file":"packages/ui/src/molecules/LoadMoreRow.stories.tsx"},{"name":"LoadingMore","description":"The next page is loading.","source":"export const LoadingMore: Story = { args: { isLoadingMore: true } };","file":"packages/ui/src/molecules/LoadMoreRow.stories.tsx"},{"name":"LoadingMoreChecks","description":"The next page is loading, in a list of checkbox rows: the skeleton rows take `CheckRow`'s shape.","source":"export const LoadingMoreChecks: Story = { args: { isLoadingMore: true, variant: \"check\" } };","file":"packages/ui/src/molecules/LoadMoreRow.stories.tsx"},{"name":"LoadError","description":"The next page failed: its error and a retry button, the loaded items kept above.","source":"export const LoadError: Story = { args: { error: \"Could not load more.\" } };","file":"packages/ui/src/molecules/LoadMoreRow.stories.tsx"},{"name":"ErrorWithoutRetry","description":"An error without a way to retry shows nothing.","source":"export const ErrorWithoutRetry: Story = {\n render: (args) => <LoadMoreRow labels={args.labels} error=\"Could not load more.\" />,\n};","file":"packages/ui/src/molecules/LoadMoreRow.stories.tsx"},{"name":"Medium","description":"The `md` size.","source":"export const Medium: Story = { args: { error: \"Could not load more.\", size: \"md\" } };","file":"packages/ui/src/molecules/LoadMoreRow.stories.tsx"},{"name":"InNarrowFrame","description":"A long error in a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: { error: \"Could not load more segments: the server took too long to answer.\" },\n render: (args) => (\n <Narrow>\n <LoadMoreRow {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/LoadMoreRow.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Idle\">\n <LoadMoreRow {...args} />\n </Case>\n <Case caption=\"Loading more\">\n <LoadMoreRow {...args} isLoadingMore />\n </Case>\n <Case caption=\"Error\">\n <LoadMoreRow {...args} error=\"Could not load more.\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/LoadMoreRow.stories.tsx"}],"primaryExample":"Idle"},{"name":"LoadState","level":"molecules","category":"Feedback","keywords":["loading","error","retry","placeholder"],"import":"import { LoadState } from \"@antscorp/ui\";","description":"Switch between a content-shaped loading placeholder, a failure with retry, and loaded children.","usage":"Switch between a content-shaped loading placeholder, a failure with retry, and loaded children. Use LoadMoreRow for later pages so existing results can remain visible.","bestPractices":[{"guidance":true,"description":"Choose FieldSkeleton or RowSkeletons as placeholder and give that placeholder its loading status name."},{"guidance":false,"description":"Do not use a generic spinner in place of the content-shaped skeleton."}],"anatomy":[{"name":"Placeholder","required":false,"description":"Host-supplied skeleton content while loading."},{"name":"Error","required":false,"description":"A failure title, optional detail and retry button."},{"name":"Content","required":false,"description":"The children after loading succeeds."}],"accessibility":["The placeholder names its own loading status; labels.retry names the error action."],"props":[{"path":"isLoading","type":"boolean","required":true,"description":"Shows `placeholder` instead of the children; a slow load keeps it at least 300 ms (`useLoadingShown`)."},{"path":"placeholder","type":"ReactNode","required":true,"description":"Drawn while loading: a skeleton in the shape of what loads (`FieldSkeleton`, `RowSkeletons`),\nwhich names its own loading status."},{"path":"error","type":"{ readonly title: string; readonly detail?: string | undefined; } | undefined","required":false,"description":"The load's failure: shown with a retry button instead of the children."},{"path":"error.title","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"error.detail","type":"string | undefined","required":false,"description":""},{"path":"labels","type":"{ readonly retry: string; }","required":true,"description":"The texts the view shows; the host translates them."},{"path":"labels.retry","type":"string","required":false,"requiredWhenParent":true,"description":"The retry button's text, e.g. \"Retry\"."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The retry button's size, `md` by default."},{"path":"onRetry","type":"() => void","required":true,"description":"Called when the retry button is pressed."},{"path":"children","type":"ReactNode","required":true,"description":"What shows once loaded, when neither loading nor failed."}],"examples":[{"name":"Loaded","description":"Loaded: the children show.","source":"export const Loaded: Story = {};","file":"packages/ui/src/molecules/LoadState.stories.tsx"},{"name":"Loading","description":"Still loading: the placeholder, here rows of a list.","source":"export const Loading: Story = { args: { isLoading: true } };","file":"packages/ui/src/molecules/LoadState.stories.tsx"},{"name":"LoadingField","description":"Loading a field: the placeholder in a field's shape.","source":"export const LoadingField: Story = {\n args: {\n isLoading: true,\n placeholder: <FieldSkeleton label=\"Country\" labels={{ loading: \"Loading\" }} />,\n },\n};","file":"packages/ui/src/molecules/LoadState.stories.tsx"},{"name":"ErrorTitleOnly","description":"Failed, with a title only.","source":"export const ErrorTitleOnly: Story = { args: { error: { title: \"Could not load the list.\" } } };","file":"packages/ui/src/molecules/LoadState.stories.tsx"},{"name":"ErrorWithDetail","description":"Failed, with a reason.","source":"export const ErrorWithDetail: Story = {\n args: { error: { title: \"Could not load the list.\", detail: \"The server answered 503.\" } },\n};","file":"packages/ui/src/molecules/LoadState.stories.tsx"},{"name":"Medium","description":"The `md` size, for the error's retry button.","source":"export const Medium: Story = { args: { size: \"md\", error: { title: \"Could not load the list.\" } } };","file":"packages/ui/src/molecules/LoadState.stories.tsx"},{"name":"Interactive","description":"Retrying goes from the error through loading to the content.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<\"error\" | \"loading\" | \"loaded\">(\"error\", (state, set) => (\n <LoadState\n {...args}\n isLoading={state === \"loading\"}\n error={state === \"error\" ? { title: \"Could not load the list.\" } : undefined}\n onRetry={() => {\n args.onRetry();\n set(\"loading\");\n setTimeout(() => {\n set(\"loaded\");\n }, 1000);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/LoadState.stories.tsx"},{"name":"LongContent","description":"A very long title and reason.","source":"export const LongContent: Story = {\n args: {\n error: {\n title:\n \"Could not load the list of items for this account, which has more than ten thousand entries\",\n detail:\n \"The server answered with a gateway timeout after waiting for a minute and a half; try again, or narrow the list down first.\",\n },\n },\n};","file":"packages/ui/src/molecules/LoadState.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: LongContent.args ?? {},\n render: (args) => (\n <Narrow>\n <LoadState {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/LoadState.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Loaded\">\n <LoadState {...args} />\n </Case>\n <Case caption=\"Loading\">\n <LoadState {...args} isLoading />\n </Case>\n <Case caption=\"Error\">\n <LoadState\n {...args}\n error={{ title: \"Could not load.\", detail: \"The server answered 503.\" }}\n />\n </Case>\n <Case caption=\"Loading a field\">\n <LoadState\n {...args}\n isLoading\n placeholder={<FieldSkeleton label=\"Country\" labels={{ loading: \"Loading\" }} />}\n />\n </Case>\n <Case caption=\"Medium error\">\n <LoadState {...args} size=\"md\" error={{ title: \"Could not load.\" }} />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/LoadState.stories.tsx"}],"primaryExample":"Loaded"},{"name":"LogoImage","level":"atoms","category":"Data display","keywords":["logo","avatar","image","fallback"],"import":"import { LogoImage } from \"@antscorp/ui\";","description":"Show a decorative logo or avatar beside content that already names it.","usage":"Show a decorative logo or avatar beside content that already names it. A skeleton reserves its box until loading finishes, and fallback replaces a failed image.","bestPractices":[{"guidance":true,"description":"Supply a fallback appropriate to the surrounding control and match size to its reserved box."},{"guidance":false,"description":"Do not use this decorative image to carry information that is absent from adjacent text."}],"anatomy":[{"name":"Image","required":false,"description":"A lazy-loaded image in the requested square or round box, until a load failure replaces it."},{"name":"Loading placeholder","required":false,"description":"A matching skeleton while the image loads."},{"name":"Fallback","required":false,"description":"Host content shown instead of the image after a load failure."}],"accessibility":["The image has an empty alt attribute; provide the identifying text elsewhere."],"props":[{"path":"src","type":"string","required":true,"description":"The image's URL."},{"path":"size","type":"number","required":true,"description":"Its width and height, in pixels; the skeleton takes the same box."},{"path":"fallback","type":"ReactNode","required":true,"description":"Shown in place of the image when it fails to load, such as an icon."},{"path":"isRound","type":"boolean","required":false,"description":"A circle rather than a square: the image's corners and the skeleton are round."},{"path":"isDimmed","type":"boolean","required":false,"description":"Dimmed, as the control it sits in is disabled."}],"examples":[{"name":"Default","description":"A logo, loaded: a skeleton of its size stands in while it loads.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/LogoImage.stories.tsx"},{"name":"Small","description":"Small, as in a list row.","source":"export const Small: Story = { args: { size: 16, fallback: <Icon icon=\"info\" size=\"sm\" /> } };","file":"packages/ui/src/atoms/LogoImage.stories.tsx"},{"name":"Round","description":"Round, as in a circle: the image's corners and the skeleton are round.","source":"export const Round: Story = { args: { isRound: true } };","file":"packages/ui/src/atoms/LogoImage.stories.tsx"},{"name":"Dimmed","description":"Dimmed, as in a disabled control.","source":"export const Dimmed: Story = { args: { isDimmed: true } };","file":"packages/ui/src/atoms/LogoImage.stories.tsx"},{"name":"Broken","description":"The image fails to load: the fallback shows instead.","source":"export const Broken: Story = { args: { src: BROKEN } };","file":"packages/ui/src/atoms/LogoImage.stories.tsx"},{"name":"Many","description":"Many logos in a row, some broken.","source":"export const Many: Story = {\n render: (args) => (\n <HStack gap={2} wrap=\"wrap\">\n {Array.from({ length: 12 }, (_, index) => (\n <LogoImage key={index} {...args} src={index % 4 === 3 ? BROKEN : LOGO} />\n ))}\n </HStack>\n ),\n};","file":"packages/ui/src/atoms/LogoImage.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <LogoImage {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/LogoImage.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Loaded\">\n <LogoImage {...args} />\n </Case>\n <Case caption=\"Round\">\n <LogoImage {...args} isRound />\n </Case>\n <Case caption=\"Dimmed\">\n <LogoImage {...args} isDimmed />\n </Case>\n <Case caption=\"Broken\">\n <LogoImage {...args} src={BROKEN} />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/LogoImage.stories.tsx"}],"primaryExample":"Default"},{"name":"MetaTable","level":"molecules","category":"Data display","keywords":["table","metadata","names","counts"],"import":"import { MetaTable } from \"@antscorp/ui\";","description":"Display a read-only list of names with an optional short-value column.","usage":"Display a read-only list of names with an optional short-value column. Use TransferPicker when users need to edit the picked values.","bestPractices":[{"guidance":true,"description":"Provide labels.meta to show the meta column and labels.loading when rows are loading."},{"guidance":false,"description":"Do not expect a row with error to remain a link; its name becomes plain error-toned text with an explanation."}],"anatomy":[{"name":"Name column","required":true,"description":"A heading and names, optionally linked in a new tab."},{"name":"Meta column","required":false,"description":"Short values under labels.meta."},{"name":"Problem tip","required":false,"description":"A warning explanation for an invalid row."},{"name":"Loading cells","required":false,"description":"Shimmer bars in the real columns for loading rows."}],"accessibility":["The first loading row provides the one status named by labels.loading."],"props":[{"path":"label","type":"string","required":true,"description":"Heads the column of names, such as \"Cities\"."},{"path":"items","type":"readonly MetaTableItem[]","required":true,"description":"The rows, in their order."},{"path":"items[].id","type":"string","required":false,"requiredWhenParent":true,"description":"The row's stable key, such as the id of what it stands for."},{"path":"items[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The row's name."},{"path":"items[].href","type":"string | undefined","required":false,"description":"Where the name links to, opened in a new tab; plain text without it or with an `error`."},{"path":"items[].error","type":"string | undefined","required":false,"description":"A problem with what the row stands for, already translated: the name in the error tone and a ⚠ tip."},{"path":"items[].meta","type":"string | undefined","required":false,"description":"A short value at the end of the row, such as a count; `labels.meta` heads its column."},{"path":"items[].isLoading","type":"boolean","required":false,"description":"What the row stands for is loading: shimmer bars in its cells instead of its name and value,\nkept at least 300 ms once shown (`useLoadingShown`)."},{"path":"labels","type":"{ readonly meta?: string | undefined; readonly loading?: string | undefined; } | undefined","required":false,"description":"The texts the table shows; the host translates them."},{"path":"labels.meta","type":"string | undefined","required":false,"description":"Heads the column of the rows' `meta`, such as \"Members\"; no such column without it."},{"path":"labels.loading","type":"string | undefined","required":false,"description":"The loading status's accessible name while rows load, such as \"Loading\"."}],"examples":[{"name":"Default","description":"Names as links, their counts at the end, one with a problem.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/MetaTable.stories.tsx"},{"name":"Empty","description":"No rows: the headings alone.","source":"export const Empty: Story = { args: { items: [] } };","file":"packages/ui/src/molecules/MetaTable.stories.tsx"},{"name":"PlainNames","description":"No links: the names as plain text.","source":"export const PlainNames: Story = {\n args: { items: ITEMS.map(({ id, label, meta: value }) => ({ id, label, meta: value })) },\n};","file":"packages/ui/src/molecules/MetaTable.stories.tsx"},{"name":"WithError","description":"A problem: the name in the error tone, a ⚠ holding the reason.","source":"export const WithError: Story = { args: { items: [ITEMS[2] ?? { id: \"old\", label: \"Old city\" }] } };","file":"packages/ui/src/molecules/MetaTable.stories.tsx"},{"name":"NamesOnly","description":"No value column: the names alone.","source":"export const NamesOnly: Story = { args: { labels: undefined } };","file":"packages/ui/src/molecules/MetaTable.stories.tsx"},{"name":"LongText","description":"A long name cut to one line; hover it for the whole name.","source":"export const LongText: Story = { args: { items: [LONG, ...ITEMS] } };","file":"packages/ui/src/molecules/MetaTable.stories.tsx"},{"name":"ManyRows","description":"Many rows.","source":"export const ManyRows: Story = {\n args: {\n items: Array.from({ length: 15 }, (_, index) => ({\n id: String(index),\n label: `City ${index + 1}`,\n href: `https://example.com/${index + 1}`,\n meta: `${(index + 1) * 100}`,\n })),\n },\n};","file":"packages/ui/src/molecules/MetaTable.stories.tsx"},{"name":"LoadingRows","description":"Rows still loading: shimmer bars in their cells, in the real columns, under one status.","source":"export const LoadingRows: Story = {\n args: {\n items: [\n ...ITEMS.slice(0, 1),\n { id: \"far-1\", label: \"far-1\", isLoading: true },\n { id: \"far-2\", label: \"far-2\", isLoading: true },\n ],\n labels: { meta: \"Members\", loading: \"Loading\" },\n },\n};","file":"packages/ui/src/molecules/MetaTable.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: { items: [LONG, ...ITEMS] },\n render: (args) => (\n <Narrow>\n <MetaTable {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/MetaTable.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Links and counts\">\n <MetaTable {...args} />\n </Case>\n <Case caption=\"Names only\">\n <MetaTable {...args} labels={undefined} />\n </Case>\n <Case caption=\"Loading rows\">\n <MetaTable {...args} {...LoadingRows.args} />\n </Case>\n <Case caption=\"Empty\">\n <MetaTable {...args} items={[]} />\n </Case>\n <Case caption=\"Long, narrow\">\n <Narrow>\n <MetaTable {...args} items={[LONG]} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/MetaTable.stories.tsx"}],"primaryExample":"Default"},{"name":"PanelSearchInput","level":"atoms","category":"Inputs","keywords":["search","query","panel","clear"],"import":"import { PanelSearchInput } from \"@antscorp/ui\";","description":"Place a controlled search row at the top of a dropdown panel.","usage":"Place a controlled search row at the top of a dropdown panel. Use SearchableGroupedList when filtering and grouped results should be composed for you.","bestPractices":[{"guidance":true,"description":"Supply label and labels.clear, and update value on each onChange; add a Divider below when separating results."},{"guidance":false,"description":"Do not expect the input to filter data itself or provide disabled and read-only modes."}],"anatomy":[{"name":"Search glyph","required":true,"description":"A magnifier at the start or end of the row."},{"name":"Input","required":true,"description":"The controlled text query."},{"name":"Clear button","required":false,"description":"Clears a nonempty query and restores focus to the input."}],"accessibility":["The input is named by label and the clear button by labels.clear."],"props":[{"path":"label","type":"string","required":true,"description":"The search box's accessible name (it has no visible label), such as \"Search\"."},{"path":"placeholder","type":"string | undefined","required":false,"description":"Shown while the query is empty; `label` when absent."},{"path":"value","type":"string","required":true,"description":"The query."},{"path":"onChange","type":"(value: string) => void","required":true,"description":"Called with the next query on each keystroke, and with \"\" on clear."},{"path":"labels","type":"{ readonly clear: string; }","required":true,"description":"The texts the row shows; the host translates them."},{"path":"labels.clear","type":"string","required":false,"requiredWhenParent":true,"description":"The clear button's name, such as \"Clear search\"; it shows only an icon."},{"path":"iconPosition","type":"\"end\" | \"start\" | undefined","required":false,"description":"Where the magnifier sits: `start` (default) or `end`, after the clear button."},{"path":"ref","type":"Ref<HTMLInputElement> | undefined","required":false,"description":"The input element, to focus it (with `useFocusLater` in a pop-over, say)."}],"examples":[{"name":"Default","description":"Empty: the magnifier and the placeholder (the label without one).","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/PanelSearchInput.stories.tsx"},{"name":"Filled","description":"A query typed: the clear button shows.","source":"export const Filled: Story = { args: { value: \"price\" } };","file":"packages/ui/src/atoms/PanelSearchInput.stories.tsx"},{"name":"WithPlaceholder","description":"Its own placeholder.","source":"export const WithPlaceholder: Story = { args: { placeholder: \"Search options\" } };","file":"packages/ui/src/atoms/PanelSearchInput.stories.tsx"},{"name":"IconAtEnd","description":"The magnifier at the end, after the clear button.","source":"export const IconAtEnd: Story = { args: { iconPosition: \"end\", value: \"price\" } };","file":"packages/ui/src/atoms/PanelSearchInput.stories.tsx"},{"name":"Interactive","description":"Typing and clearing change the query.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(\"\", (value, set) => (\n <PanelSearchInput\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/atoms/PanelSearchInput.stories.tsx"},{"name":"LongQuery","description":"A long query scrolls inside the box.","source":"export const LongQuery: Story = {\n args: { value: \"the number of times the visitor opened the newsletter last quarter\" },\n};","file":"packages/ui/src/atoms/PanelSearchInput.stories.tsx"},{"name":"InNarrow","description":"In a 280px pop-over width.","source":"export const InNarrow: Story = {\n args: { value: \"sku\" },\n decorators: [\n (Story) => (\n <Narrow>\n <Story />\n </Narrow>\n ),\n ],\n};","file":"packages/ui/src/atoms/PanelSearchInput.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Empty\">\n <PanelSearchInput {...args} />\n </Case>\n <Case caption=\"Filled\">\n <PanelSearchInput {...args} value=\"price\" />\n </Case>\n <Case caption=\"Placeholder\">\n <PanelSearchInput {...args} placeholder=\"Search options\" />\n </Case>\n <Case caption=\"Magnifier at the end\">\n <PanelSearchInput {...args} iconPosition=\"end\" />\n </Case>\n <Case caption=\"Long query\">\n <PanelSearchInput {...args} value={LongQuery.args?.value ?? \"\"} />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/PanelSearchInput.stories.tsx"}],"primaryExample":"Default"},{"name":"PathMark","level":"atoms","category":"Layout","keywords":["path","reveal","focus","form"],"import":"import { PathMark } from \"@antscorp/ui\";","description":"Mark controls so revealPath can scroll to and focus the value associated with a data path.","usage":"Mark controls so revealPath can scroll to and focus the value associated with a data path. Nested marks interpret their paths relative to the enclosing mark.","bestPractices":[{"guidance":true,"description":"Wrap the controls of the value and use a relative path inside another PathMark."},{"guidance":false,"description":"Do not expect PathMark to render a visual frame or provide a control label."}],"accessibility":["Keep accessible names on the wrapped controls; PathMark only carries their path for revealPath."],"props":[{"path":"path","type":"Path","required":true,"description":"Where the marked controls' value is, relative to the nearest enclosing `PathMark` (absolute\nat the root), such as `[\"days\"]` inside a mark of `[\"settings\"]`."},{"path":"children","type":"ReactNode","required":true,"description":"The controls of that value."}],"examples":[{"name":"Default","description":"A marked input: nothing shows of the mark, which adds no box.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/PathMark.stories.tsx"},{"name":"Nested","description":"Nested: the inner mark's path is relative, its attribute `settings.name.first`.","source":"export const Nested: Story = {\n render: (args) => (\n <PathMark path={[\"settings\"]}>\n <PathMark path={[\"name\"]}>\n <PathMark {...args} path={[\"first\"]} />\n </PathMark>\n </PathMark>\n ),\n};","file":"packages/ui/src/atoms/PathMark.stories.tsx"},{"name":"Empty","description":"Marks nothing visible.","source":"export const Empty: Story = { args: { children: null } };","file":"packages/ui/src/atoms/PathMark.stories.tsx"},{"name":"Several","description":"A mark around several controls, laid out by the stack holding it as if it were not there.","source":"export const Several: Story = {\n render: (args) => (\n <VStack gap={2}>\n <PathMark {...args}>\n <TextInput label=\"First name\" value=\"\" onChange={() => {}} />\n <TextInput label=\"Last name\" value=\"\" onChange={() => {}} />\n </PathMark>\n </VStack>\n ),\n};","file":"packages/ui/src/atoms/PathMark.stories.tsx"},{"name":"Interactive","description":"Press the button: `revealPath` scrolls the marked input at the bottom into view and focuses it.","source":"export const Interactive: Story = {\n render: (args) => <Reveal path={args.path} />,\n};","file":"packages/ui/src/atoms/PathMark.stories.tsx"},{"name":"InNarrow","description":"In a drawer-width frame.","source":"export const InNarrow: Story = {\n render: (args) => (\n <Narrow>\n <PathMark {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/PathMark.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"One control\">\n <PathMark {...args} />\n </Case>\n <Case caption=\"Narrow\">\n <Narrow>\n <PathMark {...args} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/PathMark.stories.tsx"}],"primaryExample":"Default"},{"name":"PercentRow","level":"molecules","category":"Inputs","keywords":["percent","slider","share","commit"],"import":"import { PercentRow } from \"@antscorp/ui\";","description":"Edit a whole percentage through a number input and a bounded slider.","usage":"Edit a whole percentage through a number input and a bounded slider. Dragging previews locally and reports the change only when the interaction ends.","bestPractices":[{"guidance":true,"description":"Use onChange to store committed values and isRemoveDisabled when removal must also be blocked."},{"guidance":false,"description":"Do not assume isDisabled blocks removal; it locks the share while the optional remove action stays active."}],"anatomy":[{"name":"Caption","required":false,"description":"The FieldRow caption beside the controls."},{"name":"Percent input","required":true,"description":"The controlled whole percentage."},{"name":"Slider","required":true,"description":"A bounded percentage control with local drag preview."},{"name":"Remove button","required":false,"description":"A named removal action supplied through onRemove."}],"accessibility":["labels.share names the controls and labels.remove names the optional remove button."],"props":[{"path":"label","type":"string | undefined","required":false,"description":"The row's caption, in a column at the start. Without it the column stays, empty, so the\ncontent lines up with the captioned rows around it; stacked,\nnothing is drawn above the content."},{"path":"labelWidth","type":"number | undefined","required":false,"description":"Overrides the theme's caption column width in pixels; unthemed rows default to 160px."},{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether the input is disabled."},{"path":"isReadOnly","type":"boolean","required":false,"default":"false","description":"Whether the input is read-only.\nThe value is shown at full opacity and still submits with the form, but\ncannot be edited. Unlike `isDisabled`, a read-only input is not dimmed and\nstays in the tab order — use it for a value the user should see and send\nbut not change. Stepping is off in every form while read-only: arrow keys,\nthe wheel, and the number steppers. `isDisabled` takes precedence when both\nare set."},{"path":"disabledMessage","type":"string","required":false,"description":"Explains why the input is disabled. When set together with `isDisabled`,\nthe input shows a tooltip with this text on hover and keyboard focus, and\nstays focusable (via `aria-disabled`) so the reason is discoverable by\nkeyboard and assistive technology. The field cannot be edited (it becomes\nread-only) while disabled.\n\nUse this instead of wrapping a disabled input in `Tooltip` — disabled\ncontrols don't emit the pointer events an external tooltip needs."},{"path":"labels","type":"{ readonly share: string; readonly remove: string; }","required":true,"description":"The controls' accessible name and the optional remove button's name."},{"path":"labels.share","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"labels.remove","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"value","type":"number","required":true,"description":"The share, in whole percent."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The controls' size, `md` by default."},{"path":"isRemoveDisabled","type":"boolean","required":false,"description":"Disables only removal, `false` by default; a locked share may still be removed."},{"path":"onChange","type":"(value: number) => void","required":true,"description":"Called on input commit or when slider interaction ends, never during dragging."},{"path":"onRemove","type":"(() => void) | undefined","required":false,"description":"Shows a remove button when given."}],"examples":[{"name":"Default","description":"A filled percent input and slider beside the caption.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/PercentRow.stories.tsx"},{"name":"Interactive","description":"Input commits and completed slider interactions update the share.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(50, (value, set) => <PercentRow {...args} value={value} onChange={set} />),\n};","file":"packages/ui/src/molecules/PercentRow.stories.tsx"},{"name":"Disabled","description":"The share is locked and the caption dimmed; removal stays active.","source":"export const Disabled: Story = {\n args: {\n isDisabled: true,\n disabledMessage: \"This share is calculated.\",\n onRemove: fn<() => void>(),\n },\n};","file":"packages/ui/src/molecules/PercentRow.stories.tsx"},{"name":"RemovalDisabled","description":"A disabled share whose removal is also disabled.","source":"export const RemovalDisabled: Story = {\n args: { ...Disabled.args, isRemoveDisabled: true },\n};","file":"packages/ui/src/molecules/PercentRow.stories.tsx"},{"name":"ReadOnly","description":"Read-only keeps the value visible and hides removal.","source":"export const ReadOnly: Story = { args: { isReadOnly: true, onRemove: fn<() => void>() } };","file":"packages/ui/src/molecules/PercentRow.stories.tsx"},{"name":"Removable","description":"Removal is available when its callback is supplied.","source":"export const Removable: Story = { args: { onRemove: fn<() => void>() } };","file":"packages/ui/src/molecules/PercentRow.stories.tsx"},{"name":"Zero","description":"No share at the lower bound.","source":"export const Zero: Story = { args: { value: 0 } };","file":"packages/ui/src/molecules/PercentRow.stories.tsx"},{"name":"Hundred","description":"The whole share at the upper bound.","source":"export const Hundred: Story = { args: { value: 100 } };","file":"packages/ui/src/molecules/PercentRow.stories.tsx"},{"name":"LongContent","description":"A caption wraps within its column.","source":"export const LongContent: Story = {\n args: { label: \"A group with a very long caption spanning several lines\" },\n};","file":"packages/ui/src/molecules/PercentRow.stories.tsx"},{"name":"Small","description":"Compact controls follow the requested size.","source":"export const Small: Story = { args: { size: \"sm\" } };","file":"packages/ui/src/molecules/PercentRow.stories.tsx"},{"name":"InNarrowFrame","description":"The caption stacks and the slider shrinks in a narrow frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <PercentRow {...args} onRemove={fn<() => void>()} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/PercentRow.stories.tsx"},{"name":"AllStates","description":"Filled, disabled, removable, read-only and boundary states together.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Filled\">\n <PercentRow {...args} />\n </Case>\n <Case caption=\"Disabled\">\n <PercentRow {...args} {...Disabled.args} />\n </Case>\n <Case caption=\"Removal disabled\">\n <PercentRow {...args} {...RemovalDisabled.args} />\n </Case>\n <Case caption=\"Removable\">\n <PercentRow {...args} {...Removable.args} />\n </Case>\n <Case caption=\"Read-only\">\n <PercentRow {...args} {...ReadOnly.args} />\n </Case>\n <Case caption=\"Zero\">\n <PercentRow {...args} value={0} />\n </Case>\n <Case caption=\"Hundred\">\n <PercentRow {...args} value={100} />\n </Case>\n <Case caption=\"Long caption\">\n <PercentRow {...args} {...LongContent.args} />\n </Case>\n <Case caption=\"Narrow\">\n <Narrow>\n <PercentRow {...args} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/PercentRow.stories.tsx"}],"primaryExample":"Default"},{"name":"PhoneFrame","level":"molecules","category":"Layout","keywords":["phone","preview","device","content"],"import":"import { PhoneFrame } from \"@antscorp/ui\";","description":"Preview content inside a drawn phone with a status bar and titled header.","usage":"Preview content inside a drawn phone with a status bar and titled header. Use it for a visual preview rather than interactive device navigation.","bestPractices":[{"guidance":true,"description":"Give title a recognizable name and keep preview content readable within the scrolling body."},{"guidance":false,"description":"Do not expect the drawn back chevron or status-bar symbols to perform actions."}],"anatomy":[{"name":"Device frame","required":true,"description":"A rounded phone outline with a camera island."},{"name":"Status bar","required":true,"description":"Clock, signal and battery imagery."},{"name":"Header","required":true,"description":"A back chevron, avatar and title."},{"name":"Content","required":true,"description":"Host preview content that scrolls within the device."}],"accessibility":["The frame is a figure named by title."],"props":[{"path":"title","type":"string","required":true,"description":"Who the screen's conversation is with, beside the avatar; it also names the frame."},{"path":"avatarSrc","type":"string | undefined","required":false,"description":"The URL of the avatar's picture; without one the avatar shows the title's initials."},{"path":"labels","type":"{ readonly time?: string | undefined; } | undefined","required":false,"description":"Inner texts: `time`, the status bar's clock, `\"9:41\"` by default (Apple's pictured time)."},{"path":"labels.time","type":"string | undefined","required":false,"description":""},{"path":"children","type":"ReactNode","required":true,"description":"The screen's content, such as chat messages or an empty-state text."}],"examples":[{"name":"Default","description":"One message on the screen, under the sender's avatar and name.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/PhoneFrame.stories.tsx"},{"name":"NoAvatar","description":"No picture: the avatar shows the title's initials.","source":"export const NoAvatar: Story = { args: { avatarSrc: undefined } };","file":"packages/ui/src/molecules/PhoneFrame.stories.tsx"},{"name":"Empty","description":"Nothing to show yet: an empty-state text.","source":"export const Empty: Story = {\n args: { children: <Text type=\"supporting\">Nothing to preview yet</Text> },\n};","file":"packages/ui/src/molecules/PhoneFrame.stories.tsx"},{"name":"LongContent","description":"A long message that scrolls in the screen, and a long title cut to one line.","source":"export const LongContent: Story = {\n args: {\n title: \"A sender with a very long name that does not fit the bar\",\n children: message(\"A long message that goes on. \".repeat(40)),\n },\n};","file":"packages/ui/src/molecules/PhoneFrame.stories.tsx"},{"name":"ManyMessages","description":"Several messages one under the other.","source":"export const ManyMessages: Story = {\n args: {\n children: (\n <>\n {message(\"First part\")}\n {message(\"Second part\")}\n {message(\"Third part\")}\n </>\n ),\n },\n};","file":"packages/ui/src/molecules/PhoneFrame.stories.tsx"},{"name":"OtherTime","description":"Another clock in the status bar.","source":"export const OtherTime: Story = { args: { labels: { time: \"14:05\" } } };","file":"packages/ui/src/molecules/PhoneFrame.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame: the phone shrinks, keeping its proportions.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <PhoneFrame {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/PhoneFrame.stories.tsx"},{"name":"AllStates","description":"The key states side by side.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"A message\">\n <PhoneFrame {...args} />\n </Case>\n <Case caption=\"No avatar\">\n <PhoneFrame title={args.title}>{args.children}</PhoneFrame>\n </Case>\n <Case caption=\"Empty\">\n <PhoneFrame {...args}>\n <Text type=\"supporting\">Nothing to preview yet</Text>\n </PhoneFrame>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/PhoneFrame.stories.tsx"}],"primaryExample":"Default"},{"name":"ProblemsButton","level":"molecules","category":"Feedback","keywords":["errors","problems","popover","count"],"import":"import { ProblemsButton } from \"@antscorp/ui\";","description":"Summarize validation problems in a count button that opens a selectable list.","usage":"Summarize validation problems in a count button that opens a selectable list. The view renders nothing when items is empty.","bestPractices":[{"guidance":true,"description":"Translate labels.count with the problem count and use onPick's index to locate the corresponding problem."},{"guidance":false,"description":"Do not assume picking a problem fixes it; the host decides how to reveal or address it."}],"anatomy":[{"name":"Count button","required":true,"description":"An error glyph and the number of problems when there are problems."},{"name":"Popover","required":false,"description":"A titled, scrollable list built while open."},{"name":"Problem actions","required":false,"description":"A button per problem that reports its index and closes the popover."}],"accessibility":["labels.count names the count button; title labels the problem list."],"props":[{"path":"items","type":"readonly string[]","required":true,"description":"The problems, already translated; nothing shows without one."},{"path":"title","type":"string","required":true,"description":"The popover's heading, such as \"Errors\"."},{"path":"labels","type":"{ readonly count: (count: number) => string; }","required":true,"description":"The texts the view shows; the host translates them."},{"path":"labels.count","type":"(count: number) => string","required":false,"requiredWhenParent":true,"description":"The button's name for a number of problems, such as \"3 errors\"."},{"path":"onPick","type":"(index: number) => void","required":true,"description":"Called with the index of the problem pressed in the list, which then closes."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The button's height, `md` by default."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dimmed and not pressable."}],"examples":[{"name":"Default","description":"Three problems: the glyph and the count; press it for the list.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/ProblemsButton.stories.tsx"},{"name":"Empty","description":"No problem: nothing renders.","source":"export const Empty: Story = { args: { items: [] } };","file":"packages/ui/src/molecules/ProblemsButton.stories.tsx"},{"name":"Open","description":"The list open.","source":"export const Open: Story = { play: open };","file":"packages/ui/src/molecules/ProblemsButton.stories.tsx"},{"name":"Interactive","description":"Picking a problem removes it, as fixing it would; the button goes at none.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(ITEMS, (items, set) => (\n <ProblemsButton\n {...args}\n items={items}\n onPick={(index) => {\n args.onPick(index);\n set(items.filter((_item, at) => at !== index));\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/ProblemsButton.stories.tsx"},{"name":"Single","description":"One problem.","source":"export const Single: Story = { args: { items: ITEMS.slice(0, 1) } };","file":"packages/ui/src/molecules/ProblemsButton.stories.tsx"},{"name":"Many","description":"Fourteen problems: the open list scrolls.","source":"export const Many: Story = { args: { items: MANY }, play: open };","file":"packages/ui/src/molecules/ProblemsButton.stories.tsx"},{"name":"LongText","description":"Long problems wrap in the open list.","source":"export const LongText: Story = { args: { items: LONG }, play: open };","file":"packages/ui/src/molecules/ProblemsButton.stories.tsx"},{"name":"Disabled","description":"Not pressable.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/ProblemsButton.stories.tsx"},{"name":"Small","description":"The `sm` height.","source":"export const Small: Story = { args: { size: \"sm\" } };","file":"packages/ui/src/molecules/ProblemsButton.stories.tsx"},{"name":"InNarrow","description":"In a drawer-width frame, open.","source":"export const InNarrow: Story = {\n render: (args) => (\n <Narrow>\n <ProblemsButton {...args} />\n </Narrow>\n ),\n play: open,\n};","file":"packages/ui/src/molecules/ProblemsButton.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Three\">\n <ProblemsButton {...args} />\n </Case>\n <Case caption=\"One\">\n <ProblemsButton {...args} items={ITEMS.slice(0, 1)} />\n </Case>\n <Case caption=\"Many\">\n <ProblemsButton {...args} items={MANY} />\n </Case>\n <Case caption=\"Small\">\n <ProblemsButton {...args} size=\"sm\" />\n </Case>\n <Case caption=\"Disabled\">\n <ProblemsButton {...args} isDisabled />\n </Case>\n <Case caption=\"None (renders nothing)\">\n <ProblemsButton {...args} items={[]} />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/ProblemsButton.stories.tsx"}],"primaryExample":"Default"},{"name":"RemovableRow","level":"molecules","category":"Inputs","keywords":["picked","row","remove","framed"],"import":"import { RemovableRow } from \"@antscorp/ui\";","description":"Show a picked value with supporting text and a remove action.","usage":"Show a picked value with supporting text and a remove action. Use CheckRow when the row is an option to tick instead.","bestPractices":[{"guidance":true,"description":"Name the removal through labels.remove and use isFramed when picked values need bordered single-line rows."},{"guidance":false,"description":"Do not expect isReadOnly to hide host-supplied hover actions; the host must make those appropriate for viewing."}],"anatomy":[{"name":"Text","required":true,"description":"The value label with an optional description."},{"name":"Problem","required":false,"description":"Validation text below an unframed row or a warning tip in a framed row."},{"name":"End content","required":false,"description":"Host content optionally swapped for hover actions."},{"name":"Remove button","required":false,"description":"Removes the picked value unless read-only."}],"accessibility":["labels.remove names the remove button; name interactive host-supplied hover actions separately."],"props":[{"path":"label","type":"string","required":true,"description":"The row's text."},{"path":"description","type":"string | undefined","required":false,"description":"A second line under the text, such as an id under a name."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with what the row stands for, already translated: shown under it in the error tone;\nwith `isFramed`, the frame and the text turn to the error tone and a ⚠ beside the text holds\nit in a tooltip."},{"path":"endContent","type":"ReactNode","required":false,"description":"Content at the end of the row, before the remove button, such as a count."},{"path":"hoverContent","type":"ReactNode","required":false,"description":"Content shown instead of `endContent` while the row is hovered or holds the focus, such as a\nlink to the item; the remove button then shows with it, only then. None without it."},{"path":"isFramed","type":"boolean","required":false,"description":"A bordered box around the row: its text on one line, the whole text\nin a tooltip when cut. As high as `RowSkeletons variant=\"framed\"`."},{"path":"labels","type":"{ readonly remove: string; }","required":true,"description":"The texts the row shows; the host translates them."},{"path":"labels.remove","type":"string","required":false,"requiredWhenParent":true,"description":"The remove button's name, such as \"Remove Hanoi\"."},{"path":"onRemove","type":"() => void","required":true,"description":"Called by the remove button."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The remove button's size, `md` by default."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the remove button and blocks it; nothing is hidden."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Hides the remove button; the row is not dimmed."}],"examples":[{"name":"Default","description":"A text and its remove button.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"Filled","description":"A second line and a count at the end.","source":"export const Filled: Story = {\n args: { description: \"ID 1001\", endContent: <Text color=\"secondary\">1,250</Text> },\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"EndTip","description":"A count not ready yet, explained by a tip.","source":"export const EndTip: Story = {\n args: {\n endContent: (\n <>\n <Text color=\"secondary\">--</Text>\n <InfoTip label=\"Not counted yet; it will be ready in a few hours.\" />\n </>\n ),\n },\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"Interactive","description":"Removing takes the row away; a button brings it back.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(true, (isShown, set) =>\n isShown ? (\n <RemovableRow\n {...args}\n onRemove={() => {\n args.onRemove();\n set(false);\n }}\n />\n ) : (\n <Button\n label=\"Bring it back\"\n size=\"sm\"\n onClick={() => {\n set(true);\n }}\n />\n ),\n ),\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"WithError","description":"A problem with what the row stands for, in the error tone.","source":"export const WithError: Story = {\n args: { label: \"Old city\", error: \"This city no longer exists.\" },\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"Disabled","description":"The remove button dimmed and blocked.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"ReadOnly","description":"Read-only: no remove button, not dimmed.","source":"export const ReadOnly: Story = { args: { isReadOnly: true, description: \"ID 1001\" } };","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"Medium","description":"The `md` size.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"LongText","description":"A long text wraps beside its end content.","source":"export const LongText: Story = {\n args: {\n label: \"A city with a very long name that does not fit on one line of a narrow column\",\n description: \"ID 1234567890-abcdefghij-klmnopqrst\",\n endContent: <Text color=\"secondary\">12,345,678</Text>,\n },\n render: (args) => (\n <Narrow>\n <RemovableRow {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"Many","description":"Many rows one under the other, as a list of picks.","source":"export const Many: Story = {\n render: (args) => (\n <VStack gap={1.5}>\n {Array.from({ length: 10 }, (_, index) => (\n <RemovableRow\n key={index}\n {...args}\n label={`City ${index + 1}`}\n labels={{ remove: `Remove City ${index + 1}` }}\n />\n ))}\n </VStack>\n ),\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"Framed","description":"Framed, as a picked item: a bordered box, the text on one line.","source":"export const Framed: Story = {\n args: { isFramed: true, endContent: <Text color=\"secondary\">1,250</Text> },\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"FramedHoverContent","description":"Framed with hover content: the count at rest; the action and the remove button on hover or focus.","source":"export const FramedHoverContent: Story = {\n args: { isFramed: true, endContent: <Text color=\"secondary\">1,250</Text>, hoverContent: open },\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"FramedError","description":"Framed with a problem: a red frame, the text in the error tone and a ⚠ holding the reason.","source":"export const FramedError: Story = {\n args: { isFramed: true, label: \"Old city\", error: \"This city no longer exists.\" },\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"FramedDescription","description":"Framed with a second line.","source":"export const FramedDescription: Story = {\n args: { isFramed: true, description: \"ID 1001\" },\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"FramedReadOnly","description":"Framed and read-only: no remove button; the hover content still shows.","source":"export const FramedReadOnly: Story = {\n args: { isFramed: true, isReadOnly: true, hoverContent: open },\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"FramedLongText","description":"Framed, a long text cut to one line; hover for the whole text.","source":"export const FramedLongText: Story = {\n args: {\n isFramed: true,\n label: \"A city with a very long name that does not fit on one line of a narrow column\",\n endContent: <Text color=\"secondary\">12,345,678</Text>,\n },\n render: (args) => (\n <Narrow>\n <RemovableRow {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"FramedLoading","description":"Loading: `RowSkeletons variant=\"framed\"` in the rows' place, as high as they are.","source":"export const FramedLoading: Story = {\n render: () => <RowSkeletons variant=\"framed\" labels={{ loading: \"Loading\" }} />,\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <RemovableRow {...args} description=\"ID 1001\" />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <RemovableRow {...args} />\n </Case>\n <Case caption=\"Second line and count\">\n <RemovableRow\n {...args}\n description=\"ID 1001\"\n endContent={<Text color=\"secondary\">1,250</Text>}\n />\n </Case>\n <Case caption=\"Error\">\n <RemovableRow {...args} error=\"This city no longer exists.\" />\n </Case>\n <Case caption=\"Disabled\">\n <RemovableRow {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <RemovableRow {...args} isReadOnly />\n </Case>\n <Case caption=\"Framed, hover content (hover the row)\">\n <RemovableRow\n {...args}\n isFramed\n endContent={<Text color=\"secondary\">1,250</Text>}\n hoverContent={open}\n />\n </Case>\n <Case caption=\"Framed, error\">\n <RemovableRow {...args} isFramed error=\"This city no longer exists.\" />\n </Case>\n <Case caption=\"Framed, loading\">\n <RowSkeletons variant=\"framed\" count={2} labels={{ loading: \"Loading\" }} />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"}],"primaryExample":"Default"},{"name":"RemoveButton","level":"atoms","category":"Inputs","keywords":["remove","button","row","delete"],"import":"import { RemoveButton } from \"@antscorp/ui\";","description":"Offer an icon-only action to remove the surrounding row or item.","usage":"Offer an icon-only action to remove the surrounding row or item. Use TextAction for a removal that should have a visible text caption.","bestPractices":[{"guidance":true,"description":"Name what will be removed through label and perform the data change in onClick."},{"guidance":false,"description":"Do not use isDisabled for a read-only presentation when isReadOnly should hide the removal affordance."}],"accessibility":["label names the icon-only button; include the item or row being removed when needed to distinguish actions."],"props":[{"path":"label","type":"string","required":true,"description":"Accessible label for the button (required for accessibility).\nRendered as visible text by default. When `isIconOnly` is true,\nused as aria-label instead."},{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether the button is disabled."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The button's height, `md` by default."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Read-only: a remove is an editing affordance, so nothing is drawn."},{"path":"onClick","type":"() => void","required":true,"description":"Called when the button is pressed."}],"examples":[{"name":"Default","description":"The cross that removes the row it sits in.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/RemoveButton.stories.tsx"},{"name":"Interactive","description":"Pressing it removes the row beside it.","source":"export const Interactive: Story = {\n render: (args) =>\n withState([\"First\", \"Second\", \"Third\"], (rows, set) => (\n <>\n {rows.map((row) => (\n <div key={row}>\n {row}\n <RemoveButton\n {...args}\n label={`Remove ${row}`}\n onClick={() => {\n args.onClick();\n set(rows.filter((item) => item !== row));\n }}\n />\n </div>\n ))}\n </>\n )),\n};","file":"packages/ui/src/atoms/RemoveButton.stories.tsx"},{"name":"Disabled","description":"Not pressable, e.g. for a read-only list.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/atoms/RemoveButton.stories.tsx"},{"name":"Medium","description":"The `md` height, for a form whose other controls are `md`.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/atoms/RemoveButton.stories.tsx"},{"name":"ReadOnly","description":"Read-only: a remove is an editing affordance, so nothing is drawn.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/atoms/RemoveButton.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <RemoveButton {...args} />\n </Case>\n <Case caption=\"Disabled\">\n <RemoveButton {...args} isDisabled />\n </Case>\n <Case caption=\"Medium\">\n <RemoveButton {...args} size=\"md\" />\n </Case>\n <Case caption=\"Read-only (nothing drawn)\">\n <RemoveButton {...args} isReadOnly />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/RemoveButton.stories.tsx"}],"primaryExample":"Default"},{"name":"RepeatableList","level":"organisms","category":"Inputs","keywords":["list","repeatable","add","rows"],"import":"import { RepeatableList } from \"@antscorp/ui\";","description":"Compose an editable list whose items and removal controls are supplied by the host.","usage":"Compose an editable list whose items and removal controls are supplied by the host. Use RuleList when every item should have a RuleRow and first/rest joining words.","bestPractices":[{"guidance":true,"description":"Provide stable itemKey values and pass disabled or read-only state into the controls rendered by renderItem."},{"guidance":false,"description":"Do not hide the add button yourself at max; the list keeps it disabled with labels.maxReached when supplied."}],"anatomy":[{"name":"Items","required":false,"description":"Host-rendered rows when items exist, optionally sharing column headings and tracks."},{"name":"Separators","required":false,"description":"Joint words or labeled dividers between items."},{"name":"Add row","required":false,"description":"A named add button, optionally placed by renderAdd."},{"name":"Feedback","required":false,"description":"Empty, maximum and validation messages."}],"accessibility":["label names the list's group; supply labels.add for its add action and accessible names inside rendered items."],"props":[{"path":"label","type":"string","required":true,"description":"Names the list."},{"path":"items","type":"readonly unknown[]","required":true,"description":"The items, in order."},{"path":"itemKey","type":"(item: unknown, index: number) => string","required":true,"description":"A stable key for an item, so rows keep their state when others are added or removed."},{"path":"renderItem","type":"(item: unknown, index: number) => ReactNode","required":true,"description":"Draws an item; it brings its own remove control, e.g. through `RuleRow`."},{"path":"columns","type":"{ readonly labels: readonly string[]; readonly template: string; } | undefined","required":false,"description":"Column headings and their CSS tracks, shared with each `RuleRow` in the list; a last track for\nthe remove button is added, read-only rows included. No headings without it."},{"path":"columns.labels","type":"readonly string[]","required":false,"requiredWhenParent":true,"description":""},{"path":"columns.template","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"frame","type":"\"card\" | \"none\" | undefined","required":false,"description":"`none` (default) draws no frame, and `label` only names the list for assistive tech; `card` is a transparent card with the label above."},{"path":"description","type":"string | undefined","required":false,"description":"Help under the label."},{"path":"isErrorAboveAdd","type":"boolean","required":false,"description":"Puts `error` between the items and the add button instead of below the button."},{"path":"labels","type":"{ readonly joint?: string | undefined; readonly add?: string | undefined; readonly maxReached?: string | undefined; readonly empty?: string | undefined; } | undefined","required":false,"description":"The texts the list shows; the host translates them."},{"path":"labels.joint","type":"string | undefined","required":false,"description":"A word between two items, such as \"or\"; none without it. `separator` says how it shows."},{"path":"labels.add","type":"string | undefined","required":false,"description":"The add button's text, such as \"Add trigger\"; required with `onAdd`, which shows no button without it."},{"path":"labels.maxReached","type":"string | undefined","required":false,"description":"Shown above the add button once `max` items are there."},{"path":"labels.empty","type":"string | undefined","required":false,"description":"Shown while there are no items."},{"path":"addVariant","type":"\"ghost\" | \"secondary\" | undefined","required":false,"description":"The add button's weight: `ghost` by default; `secondary` for an add that needs more weight."},{"path":"addShape","type":"\"default\" | \"pill\" | undefined","required":false,"description":"The add button's shape: `default`, or `pill` for a fully rounded one (see `AddButton`)."},{"path":"renderAdd","type":"((button: ReactNode) => ReactNode) | undefined","required":false,"description":"Puts the add button in a row of the host's, e.g. after a joint word as the items have, at the list's full width as the items are; the button alone, at its own width, without it."},{"path":"separator","type":"\"divider\" | \"joint\" | undefined","required":false,"description":"How `labels.joint` shows between two items: `joint` (default) a small word, `divider` a dashed rule with the word on it."},{"path":"gap","type":"2 | 4 | undefined","required":false,"description":"The space between the rows, the add row too, in Astryx spacing steps: `2` (8px, default) or `4` (16px)."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The add button's height, `md` by default."},{"path":"onAdd","type":"(() => void) | undefined","required":false,"description":"Called when the add button is pressed. No add button without it."},{"path":"isAddDisabled","type":"boolean","required":false,"description":"Blocks the add button alone (e.g. until something the items need is chosen); the items stay as they are."},{"path":"max","type":"number | undefined","required":false,"description":"The most items the list holds; at that many the add button stays, but disabled."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the list as a whole, already in the user's language."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the list and its add button; the items' own controls are the host's to disable."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Hides the add button and the maximum note; the items' own remove buttons are the host's to hide."}],"examples":[{"name":"Default","description":"Items joined by \"or\", with an add button.","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"Columns","description":"Column headings and row controls share tracks, including the reserved remove button column.","source":"export const Columns: Story = {\n args: {\n labels: { add: \"Add item\" },\n columns: { labels: [\"Name\", \"Value\"], template: \"minmax(0, 1fr) minmax(0, 1fr)\" },\n renderItem: (item: string) => (\n <RuleRow labels={{ remove: `Remove ${item}` }} onRemove={fn<() => void>()}>\n <TextInput label=\"Name\" value={item} onChange={fn<() => void>()} />\n <TextInput label=\"Value\" value=\"Example\" onChange={fn<() => void>()} />\n </RuleRow>\n ),\n },\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"ReadOnlyColumns","description":"The same header tracks with a read-only row; removal space stays reserved.","source":"export const ReadOnlyColumns: Story = {\n args: {\n ...Columns.args,\n isReadOnly: true,\n renderItem: (item: string) => (\n <RuleRow labels={{ remove: `Remove ${item}` }} isReadOnly onRemove={fn<() => void>()}>\n <TextInput label=\"Name\" value={item} isReadOnly />\n <TextInput label=\"Value\" value=\"Example\" isReadOnly />\n </RuleRow>\n ),\n },\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"NarrowColumns","description":"Column headings and controls in a narrow host.","source":"export const NarrowColumns: Story = {\n render: (args) => (\n <Narrow>\n <RepeatableList {...args} {...Columns.args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"Empty","description":"No item yet: the empty text and the add button.","source":"export const Empty: Story = {\n args: { items: [], labels: { add: \"Add trigger\", empty: \"Nothing here yet.\" } },\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"Interactive","description":"Adding and removing items; the joint words follow.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<readonly string[]>([\"Item 1\", \"Item 2\"], (items, set) => (\n <RepeatableList\n {...args}\n items={items}\n renderItem={(item) =>\n row(item, () => {\n set(items.filter((other) => other !== item));\n })\n }\n labels={{ joint: \"or\", add: \"Add item\" }}\n onAdd={() => {\n set([...items, `Item ${items.length + 1}`]);\n }}\n />\n )),\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"WithError","description":"A problem with the list as a whole.","source":"export const WithError: Story = { args: { error: \"Add at least one trigger.\" } };","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"Disabled","description":"Dimmed: the group and the add button cannot be used, and nothing is hidden.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"ReadOnly","description":"Read-only: no add button, and the rows (here `RuleRow`) hide their remove buttons.","source":"export const ReadOnly: Story = {\n args: { isReadOnly: true, renderItem: (item: string) => row(item, fn<() => void>(), true) },\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"Medium","description":"The add button at the `md` height.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"SingleItem","description":"A single item, so no joint word.","source":"export const SingleItem: Story = { args: { items: names.slice(0, 1) } };","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"ManyItems","description":"Twelve items.","source":"export const ManyItems: Story = {\n args: { items: Array.from({ length: 12 }, (_, index) => `Trigger number ${index + 1}`) },\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"MaxReached","description":"At the maximum: the add button stays, disabled, under a note.","source":"export const MaxReached: Story = {\n args: {\n max: 3,\n labels: { joint: \"or\", add: \"Add trigger\", maxReached: \"You can add up to 3 triggers.\" },\n },\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"AddDisabled","description":"Only the add button is blocked; the items stay as they are.","source":"export const AddDisabled: Story = { args: { isAddDisabled: true } };","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"NoJoint","description":"Without a joint word.","source":"export const NoJoint: Story = { args: { labels: { add: \"Add trigger\" } } };","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, where rows wrap.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <RepeatableList {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"CardFrame","description":"A transparent card with help, and the problem above the add button.","source":"export const CardFrame: Story = {\n args: {\n frame: \"card\",\n labels: { add: \"Add trigger\" },\n description: \"Added one by one.\",\n error: \"Add at least one trigger.\",\n isErrorAboveAdd: true,\n },\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"SecondaryAdd","description":"The add button as a secondary button, for an add that needs more weight.","source":"export const SecondaryAdd: Story = {\n args: { addVariant: \"secondary\" },\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"OutlineAdd","description":"The outline add: secondary, `md`, its plus at the button's size.","source":"export const OutlineAdd: Story = {\n args: { addVariant: \"secondary\", size: \"md\" },\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"DividerSeparator","description":"Items as cards apart, a dashed rule with \"OR\" between them, and a pill add button.","source":"export const DividerSeparator: Story = {\n args: {\n separator: \"divider\",\n labels: { joint: \"OR\", add: \"OR\" },\n addVariant: \"secondary\",\n addShape: \"pill\",\n },\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"AddInRow","description":"The add button after a word of the host's, in a row as the items are.","source":"export const AddInRow: Story = {\n args: {\n renderAdd: (button: React.ReactNode) => (\n <RuleRow labels={{ remove: \"Unused\", joint: \"or\" }} isReadOnly onRemove={fn<() => void>()}>\n {button}\n </RuleRow>\n ),\n },\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args: RepeatableListProps<string>) => (\n <Gallery>\n <Case caption=\"Column headings\">\n <RepeatableList {...args} {...Columns.args} />\n </Case>\n <Case caption=\"Read-only columns\">\n <RepeatableList {...args} {...ReadOnlyColumns.args} />\n </Case>\n <Case caption=\"Filled\">\n <RepeatableList {...args} />\n </Case>\n <Case caption=\"Empty\">\n <RepeatableList\n {...args}\n items={[]}\n labels={{ add: \"Add trigger\", empty: \"Nothing here yet.\" }}\n />\n </Case>\n <Case caption=\"Error\">\n <RepeatableList {...args} error=\"Add at least one trigger.\" />\n </Case>\n <Case caption=\"Max reached\">\n <RepeatableList\n {...args}\n max={3}\n labels={{ joint: \"or\", add: \"Add trigger\", maxReached: \"You can add up to 3 triggers.\" }}\n />\n </Case>\n <Case caption=\"Disabled\">\n <RepeatableList {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <RepeatableList\n {...args}\n isReadOnly\n renderItem={(item: string) => row(item, fn<() => void>(), true)}\n />\n </Case>\n <Case caption=\"Medium\">\n <RepeatableList {...args} size=\"md\" />\n </Case>\n <Case caption=\"Card\">\n <RepeatableList {...args} frame=\"card\" description=\"Added one by one.\" />\n </Case>\n <Case caption=\"Secondary add\">\n <RepeatableList {...args} addVariant=\"secondary\" />\n </Case>\n <Case caption=\"Outline add\">\n <RepeatableList {...args} addVariant=\"secondary\" size=\"md\" />\n </Case>\n <Case caption=\"Divider, pill add\">\n <RepeatableList\n {...args}\n separator=\"divider\"\n labels={{ joint: \"OR\", add: \"OR\" }}\n addVariant=\"secondary\"\n addShape=\"pill\"\n />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"}],"primaryExample":"Default"},{"name":"ResizableDrawer","level":"organisms","category":"Overlays","keywords":["drawer","resize","panel","expand"],"import":"import { ResizableDrawer } from \"@antscorp/ui\";","description":"Place a resizable panel at the bottom of its parent, with a fixed header and scrolling body.","usage":"Place a resizable panel at the bottom of its parent, with a fixed header and scrolling body. Expansion covers that parent and Escape shrinks an expanded drawer.","bestPractices":[{"guidance":true,"description":"Keep isExpanded controlled through onExpandedChange and provide labels and glyphs for expand and shrink."},{"guidance":false,"description":"Do not expect the drawer to cover the browser viewport unless its parent is sized for that purpose."}],"anatomy":[{"name":"Resize bar","required":true,"description":"The handle for changing the local drawer height."},{"name":"Header","required":true,"description":"Host title, optional end content, expand and close buttons."},{"name":"Banners","required":false,"description":"Host messages above the body."},{"name":"Body","required":true,"description":"Scrolling host content."}],"accessibility":["label names the drawer; labels names the expand, shrink, close and optional resize controls; Escape shrinks when expanded."],"props":[{"path":"label","type":"string","required":true,"description":"Names the drawer for assistive technology, such as \"Settings of Wait\"."},{"path":"title","type":"ReactNode","required":true,"description":"The header's start: a title, or an editable name."},{"path":"headerEnd","type":"ReactNode","required":false,"description":"The header's end, before the expand and close buttons, such as a switch."},{"path":"banners","type":"ReactNode","required":false,"description":"Messages above the body, such as Astryx `Banner`s."},{"path":"children","type":"ReactNode","required":true,"description":"The body, which scrolls."},{"path":"isExpanded","type":"boolean","required":true,"description":"Covers the whole area it sits in (its parent), instead of a share of it."},{"path":"onExpandedChange","type":"(isExpanded: boolean) => void","required":true,"description":"Called with the new state: by the expand button, by Escape, and by starting a resize."},{"path":"onClose","type":"() => void","required":true,"description":"Called when the close button is pressed."},{"path":"icons","type":"{ readonly expand: ReactNode; readonly shrink: ReactNode; }","required":true,"description":"The host's glyphs."},{"path":"icons.expand","type":"ReactNode","required":false,"requiredWhenParent":true,"description":"The expand button's glyph, drawn when the drawer is not expanded."},{"path":"icons.shrink","type":"ReactNode","required":false,"requiredWhenParent":true,"description":"The expand button's glyph once the drawer is expanded."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The header buttons' size, `md` by default."},{"path":"labels","type":"{ readonly expand: string; readonly shrink: string; readonly close: string; readonly resize?: string | undefined; }","required":true,"description":"The texts of the buttons and the resize bar; the host translates them."},{"path":"labels.expand","type":"string","required":false,"requiredWhenParent":true,"description":"The expand button, such as \"Expand\"."},{"path":"labels.shrink","type":"string","required":false,"requiredWhenParent":true,"description":"The same button once expanded, such as \"Shrink\"."},{"path":"labels.close","type":"string","required":false,"requiredWhenParent":true,"description":"The close button, such as \"Close\"."},{"path":"labels.resize","type":"string | undefined","required":false,"description":"The resize bar; Astryx's own text when absent."},{"path":"autoSaveId","type":"string | undefined","required":false,"description":"Remembers the height under this key between visits. Not remembered without it."},{"path":"xstyle","type":"StyleXStyles","required":false,"description":"StyleX styles for the drawer's surface, such as a shadow; must come from `stylex.create`."},{"path":"defaultSize","type":"string | number | undefined","required":false,"description":"The starting height, a number of pixels or a share such as \"50%\" of the parent."}],"examples":[{"name":"Default","description":"Half of its parent; drag the bar on top to resize it.","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/ResizableDrawer.stories.tsx"},{"name":"Interactive","description":"Expanding and shrinking: the button, and Escape while expanded.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(false, (isExpanded, set) => (\n <ResizableDrawer\n {...args}\n isExpanded={isExpanded}\n onExpandedChange={(next) => {\n args.onExpandedChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/organisms/ResizableDrawer.stories.tsx"},{"name":"Expanded","description":"Covering its whole parent.","source":"export const Expanded: Story = { args: { isExpanded: true } };","file":"packages/ui/src/organisms/ResizableDrawer.stories.tsx"},{"name":"WithHeaderEnd","description":"Something at the header's end, such as a switch.","source":"export const WithHeaderEnd: Story = {\n args: { headerEnd: <Switch label=\"On\" value onChange={fn<() => void>()} /> },\n};","file":"packages/ui/src/organisms/ResizableDrawer.stories.tsx"},{"name":"WithBanners","description":"Banners above the body.","source":"export const WithBanners: Story = {\n args: {\n banners: [\n <Banner key=\"a\" status=\"warning\" title=\"Fill in the name.\" />,\n <Banner key=\"b\" status=\"warning\" title=\"Pick at least one hour.\" />,\n ],\n },\n};","file":"packages/ui/src/organisms/ResizableDrawer.stories.tsx"},{"name":"LongContent","description":"A body longer than the drawer scrolls.","source":"export const LongContent: Story = {\n args: {\n children: Array.from({ length: 30 }, (_, index) => (\n <Text key={index} as=\"p\">\n Paragraph {index + 1} of a long body.\n </Text>\n )),\n },\n};","file":"packages/ui/src/organisms/ResizableDrawer.stories.tsx"},{"name":"WithOwnStyle","description":"The host's own styles on the surface, here a stronger shadow.","source":"export const WithOwnStyle: Story = { args: { xstyle: styles.strong } };","file":"packages/ui/src/organisms/ResizableDrawer.stories.tsx"},{"name":"SmallStart","description":"A small starting height, in pixels.","source":"export const SmallStart: Story = { args: { defaultSize: 160 } };","file":"packages/ui/src/organisms/ResizableDrawer.stories.tsx"},{"name":"Medium","description":"The header buttons at the `md` height.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/organisms/ResizableDrawer.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <Tall>\n <ResizableDrawer {...args} />\n </Tall>\n </Case>\n <Case caption=\"Expanded, with a banner\">\n <Tall>\n <ResizableDrawer\n {...args}\n isExpanded\n banners={<Banner status=\"warning\" title=\"Fill in the name.\" />}\n />\n </Tall>\n </Case>\n <Case caption=\"Medium\">\n <Tall>\n <ResizableDrawer {...args} size=\"md\" />\n </Tall>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/ResizableDrawer.stories.tsx"}],"primaryExample":"Default"},{"name":"RevealRadioGroup","level":"molecules","category":"Selection","keywords":["radio","choice","reveal","details"],"import":"import { RevealRadioGroup } from \"@antscorp/ui\";","description":"Choose one option and reveal only its related content below the list or beneath that option.","usage":"Choose one option and reveal only its related content below the list or beneath that option. Use RevealSwitch for a single boolean setting.","bestPractices":[{"guidance":true,"description":"Keep value controlled and choose contentPlacement=option when each option's detail should follow its label."},{"guidance":false,"description":"Do not expect the group to make controls inside option content read-only or disabled; configure those controls in the host."}],"anatomy":[{"name":"Group label","required":true,"description":"The group's name, optionally visually hidden."},{"name":"Radio options","required":true,"description":"The mutually exclusive choices with optional explanatory tips."},{"name":"Revealed content","required":false,"description":"Only the chosen option's content."}],"accessibility":["Supply label even when isLabelHidden, and clear labels for every option."],"props":[{"path":"label","type":"string","required":true,"description":"Label text for the radio group (always rendered for accessibility)."},{"path":"isLabelHidden","type":"boolean","required":false,"default":"false","description":"Whether to visually hide the label (still accessible to screen readers)."},{"path":"orientation","type":"\"horizontal\" | \"vertical\"","required":false,"default":"\"vertical\"","description":"Layout direction of the radio items."},{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether all radio items are disabled."},{"path":"options","type":"readonly RevealRadioOption<string>[]","required":true,"description":"The choices, in order."},{"path":"options[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What `onChange` gets when this option is picked."},{"path":"options[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The option's text."},{"path":"options[].isDisabled","type":"boolean","required":false,"description":"Dims and blocks this option while keeping a stored choice visible."},{"path":"options[].tip","type":"string | undefined","required":false,"description":"An explanation shown in an (i) beside the label."},{"path":"options[].content","type":"ReactNode","required":false,"description":"What shows while this option is the chosen one: below the list, or under the option (`contentPlacement`)."},{"path":"value","type":"string","required":true,"description":"The chosen option's value."},{"path":"onChange","type":"(value: string) => void","required":true,"description":"Called with the value of the option picked."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The radios' size, `md` by default (Astryx's default; the theme draws it at 18px)."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows the value, not dimmed, and ignores changes."},{"path":"contentGap","type":"0 | 0.5 | 1 | 2 | undefined","required":false,"description":"The space between the list and the revealed content, `2` by default."},{"path":"contentPlacement","type":"\"list\" | \"option\" | undefined","required":false,"description":"`list` (default) shows the chosen option's content below the whole list; `option` shows it\nright under the chosen option, in line with its label, the options after it below the content."}],"examples":[{"name":"DisabledOption","description":"One option is disabled; a stored choice still reveals its content.","source":"export const DisabledOption: Story = {\n args: {\n options: [\n { value: \"unlimited\", label: \"Unlimited\" },\n { value: \"limited\", label: \"Limited\", isDisabled: true, content: times },\n ],\n },\n};","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"Default","description":"The chosen option reveals its content below the list.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"TightContent","description":"The content tucked close under the list.","source":"export const TightContent: Story = { args: { orientation: \"horizontal\", contentGap: 0.5 } };","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"UnderOption","description":"The chosen option's content under it, in line with its label, the next options below it.","source":"export const UnderOption: Story = {\n args: {\n contentPlacement: \"option\",\n options: [\n { value: \"unlimited\", label: \"Unlimited\" },\n { value: \"limited\", label: \"Limited\", content: times },\n { value: \"capped\", label: \"Capped per day\" },\n ],\n },\n};","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"NothingRevealed","description":"An option without content reveals nothing.","source":"export const NothingRevealed: Story = { args: { value: \"unlimited\" } };","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"Interactive","description":"Picking an option swaps the content.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<Limit>(\"unlimited\", (value, set) => (\n <RevealRadioGroup\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"HorizontalWithTips","description":"In a row, with an (i) explaining each option.","source":"export const HorizontalWithTips: Story = {\n args: {\n orientation: \"horizontal\",\n options: [\n { value: \"unlimited\", label: \"Unlimited\", tip: \"No cap on how often.\" },\n { value: \"limited\", label: \"Limited\", tip: \"At most N times.\", content: times },\n ],\n },\n};","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"LabelHidden","description":"The label kept for screen readers only.","source":"export const LabelHidden: Story = { args: { isLabelHidden: true } };","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"Disabled","description":"Read-only.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"ReadOnly","description":"Shows the value, not dimmed, and blocks changes.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"Small","description":"The compact `sm` size, for a dense form whose other controls are `sm`.","source":"export const Small: Story = { args: { size: \"sm\" } };","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"LongContent","description":"Long labels and tips.","source":"export const LongContent: Story = {\n args: {\n options: [\n {\n value: \"unlimited\",\n label: \"Unlimited: a person may enter every time they match, however often that is\",\n tip: \"There is no cap on how often the same person can enter.\",\n },\n {\n value: \"limited\",\n label: \"Limited: a person may enter only a set number of times in a period of time\",\n content: times,\n },\n ],\n },\n};","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: { orientation: \"horizontal\" },\n render: (args) => (\n <Narrow>\n <RevealRadioGroup {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Content revealed\">\n <RevealRadioGroup {...args} />\n </Case>\n <Case caption=\"Nothing revealed\">\n <RevealRadioGroup {...args} value=\"unlimited\" />\n </Case>\n <Case caption=\"Horizontal\">\n <RevealRadioGroup {...args} orientation=\"horizontal\" />\n </Case>\n <Case caption=\"Disabled\">\n <RevealRadioGroup {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <RevealRadioGroup {...args} isReadOnly />\n </Case>\n <Case caption=\"Small\">\n <RevealRadioGroup {...args} size=\"sm\" />\n </Case>\n <Case caption=\"Under the option\">\n <RevealRadioGroup {...args} contentPlacement=\"option\" />\n </Case>\n <Case caption=\"Under the option, small\">\n <RevealRadioGroup {...args} contentPlacement=\"option\" size=\"sm\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"}],"primaryExample":"DisabledOption"},{"name":"RevealSwitch","level":"molecules","category":"Selection","keywords":["switch","boolean","reveal","settings"],"import":"import { RevealSwitch } from \"@antscorp/ui\";","description":"Turn a setting on or off and reveal its controls only while it is on.","usage":"Turn a setting on or off and reveal its controls only while it is on. Use RevealRadioGroup when choosing among several alternatives.","bestPractices":[{"guidance":true,"description":"Keep value controlled and give the switch a label that explains what enabling it does."},{"guidance":false,"description":"Do not expect disabling or making the switch read-only to change the behavior of supplied children."}],"anatomy":[{"name":"Switch","required":true,"description":"The labeled boolean control."},{"name":"Content","required":false,"description":"Host controls shown below the switch while value is true."}],"accessibility":["label names the switch; the host supplies accessible labels for revealed controls."],"props":[{"path":"label","type":"string","required":true,"description":"Label text for the switch (always rendered for accessibility)."},{"path":"value","type":"boolean","required":true,"description":"Whether the switch is on or off."},{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether the switch is disabled."},{"path":"onChange","type":"(value: boolean) => void","required":true,"description":"Called with the new state."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The switch's size, `md` by default."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows the value, not dimmed, and ignores changes."},{"path":"children","type":"ReactNode","required":false,"description":"What shows below the switch while it is on."}],"examples":[{"name":"On","description":"On: the children show below the switch.","source":"export const On: Story = {};","file":"packages/ui/src/molecules/RevealSwitch.stories.tsx"},{"name":"Off","description":"Off: the children are hidden.","source":"export const Off: Story = { args: { value: false } };","file":"packages/ui/src/molecules/RevealSwitch.stories.tsx"},{"name":"Interactive","description":"Toggling shows and hides the children.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(false, (value, set) => (\n <RevealSwitch\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/RevealSwitch.stories.tsx"},{"name":"Disabled","description":"Read-only, on.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/RevealSwitch.stories.tsx"},{"name":"ReadOnly","description":"Shows the value, not dimmed, and blocks changes.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/molecules/RevealSwitch.stories.tsx"},{"name":"Medium","description":"The `md` size, for a form whose other controls are `md`.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/molecules/RevealSwitch.stories.tsx"},{"name":"NoChildren","description":"On with no children: only the switch.","source":"export const NoChildren: Story = { args: { children: undefined } };","file":"packages/ui/src/molecules/RevealSwitch.stories.tsx"},{"name":"LongContent","description":"A long label and a lot of revealed content.","source":"export const LongContent: Story = {\n args: {\n label: \"Only people who match every one of the conditions below, and no one else at all\",\n children: Array.from({ length: 10 }, (_, index) => (\n <TextInput key={index} label={`Filter ${index + 1}`} value=\"\" onChange={fn<() => void>()} />\n )),\n },\n};","file":"packages/ui/src/molecules/RevealSwitch.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: { label: \"Only people who match every one of the conditions below\" },\n render: (args) => (\n <Narrow>\n <RevealSwitch {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/RevealSwitch.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"On\">\n <RevealSwitch {...args} />\n </Case>\n <Case caption=\"Off\">\n <RevealSwitch {...args} value={false} />\n </Case>\n <Case caption=\"Disabled\">\n <RevealSwitch {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <RevealSwitch {...args} isReadOnly />\n </Case>\n <Case caption=\"Medium\">\n <RevealSwitch {...args} size=\"md\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/RevealSwitch.stories.tsx"}],"primaryExample":"On"},{"name":"RowSkeletons","level":"atoms","category":"Feedback","keywords":["loading","skeleton","rows","tiles"],"import":"import { RowSkeletons } from \"@antscorp/ui\";","description":"Stand in for options, checkbox rows or framed picks while data loads.","usage":"Stand in for options, checkbox rows or framed picks while data loads. Use ChipSkeletons for compact picked values and FieldSkeleton for a field.","bestPractices":[{"guidance":true,"description":"Match layout, variant, count and hasIcon to the content that will replace the placeholder."},{"guidance":false,"description":"Do not create multiple loading statuses inside one named load; use hasStatus=false for nested placeholders."}],"anatomy":[{"name":"Text bars","required":true,"description":"Varied shimmer widths stand in for row or tile labels."},{"name":"Leading shape","required":false,"description":"An icon placeholder or checkbox shape when requested."},{"name":"End bar","required":false,"description":"A short trailing value in check and framed variants."}],"accessibility":["By default the wrapper has role status, named through labels.loading; hasStatus=false hides it from assistive technology."],"props":[{"path":"count","type":"number | undefined","required":false,"description":"How many rows (or tiles) stand in for the list, 3 by default."},{"path":"hasIcon","type":"boolean","required":false,"description":"Each row starts with a square icon cell, as a list of items with icons; each tile has a\nround icon cell over its text."},{"path":"layout","type":"\"grid\" | \"list\" | undefined","required":false,"description":"`grid`: three tiles a row, as `SearchableGroupedList`'s tiles; `list` (default): one row each,\nas an option row."},{"path":"variant","type":"\"check\" | \"framed\" | \"option\" | undefined","required":false,"description":"The shape of each row of a list: `option` (default) as an option row; `check` as a\n`CheckRow` (a box, a name, a short value at the end); `framed` as a framed `RemovableRow` (a\nbordered box around a name and a short value). `check` and `framed` rows are exactly as high\nas the rows they stand for and sit 6px apart (spacing step 1.5), so a list laid out with that\ngap does not move when they are swapped."},{"path":"hasStatus","type":"boolean","required":false,"description":"Whether it is a status of its own (default); `false` draws no status and hides it from\nassistive tech, for rows inside or beside one status that names the whole load."},{"path":"labels","type":"{ readonly loading?: string | undefined; } | undefined","required":false,"description":"The texts the view shows; the host translates them."},{"path":"labels.loading","type":"string | undefined","required":false,"description":"The loading status's accessible name, e.g. \"Loading\"."}],"examples":[{"name":"Default","description":"Three text rows, as a list of options while it loads.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"WithIcon","description":"Each row with an icon cell first, as a list of items with icons.","source":"export const WithIcon: Story = { args: { hasIcon: true } };","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"SingleRow","description":"A single row, as the end of a list loading its next page.","source":"export const SingleRow: Story = { args: { count: 1 } };","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"ManyRows","description":"Many rows, the widths cycling.","source":"export const ManyRows: Story = { args: { count: 12 } };","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"CheckRows","description":"Rows shaped as `CheckRow`s: a box, a name bar and an end bar, as high as a real row.","source":"export const CheckRows: Story = { args: { variant: \"check\" } };","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"FramedRows","description":"Rows shaped as framed `RemovableRow`s: a box around a name bar and an end bar.","source":"export const FramedRows: Story = { args: { variant: \"framed\" } };","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"ManyCheckRows","description":"Many check rows, the widths cycling.","source":"export const ManyCheckRows: Story = { args: { variant: \"check\", count: 12 } };","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"FramedInNarrowFrame","description":"Framed rows in a drawer-width frame.","source":"export const FramedInNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <RowSkeletons {...args} variant=\"framed\" />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"Grid","description":"Three tiles a row, each an icon circle over a text bar, as a palette's tiles while they load.","source":"export const Grid: Story = { args: { layout: \"grid\", hasIcon: true, count: 6 } };","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"GridTextOnly","description":"Tiles without an icon, a text bar each.","source":"export const GridTextOnly: Story = { args: { layout: \"grid\" } };","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"ManyTiles","description":"Many tiles, the widths cycling.","source":"export const ManyTiles: Story = { args: { layout: \"grid\", hasIcon: true, count: 12 } };","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"GridInNarrowFrame","description":"Tiles in a drawer-width frame.","source":"export const GridInNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <RowSkeletons {...args} layout=\"grid\" hasIcon count={6} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"NoStatus","description":"Rows beside a status that names the load: no status of their own, hidden from assistive tech.","source":"export const NoStatus: Story = { args: { hasStatus: false, variant: \"framed\" } };","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <RowSkeletons {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Text rows\">\n <RowSkeletons {...args} />\n </Case>\n <Case caption=\"With icons\">\n <RowSkeletons {...args} hasIcon />\n </Case>\n <Case caption=\"Two rows\">\n <RowSkeletons {...args} count={2} />\n </Case>\n <Case caption=\"Narrow, with icons\">\n <Narrow>\n <RowSkeletons {...args} hasIcon count={5} />\n </Narrow>\n </Case>\n <Case caption=\"Check rows\">\n <RowSkeletons {...args} variant=\"check\" />\n </Case>\n <Case caption=\"Framed rows\">\n <RowSkeletons {...args} variant=\"framed\" />\n </Case>\n <Case caption=\"No status of its own\">\n <RowSkeletons {...args} variant=\"framed\" hasStatus={false} />\n </Case>\n <Case caption=\"Tiles\">\n <RowSkeletons {...args} layout=\"grid\" hasIcon count={6} />\n </Case>\n <Case caption=\"Tiles, text only\">\n <RowSkeletons {...args} layout=\"grid\" />\n </Case>\n <Case caption=\"Narrow, tiles\">\n <Narrow>\n <RowSkeletons {...args} layout=\"grid\" hasIcon count={9} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"}],"primaryExample":"Default"},{"name":"RuleGroups","level":"organisms","category":"Inputs","keywords":["rules","groups","conditions","and","or"],"import":"import { RuleGroups } from \"@antscorp/ui\";","description":"Edit alternative groups of rules, where all rules in one group must hold.","usage":"Edit alternative groups of rules, where all rules in one group must hold. Use RuleList for a single flat conjunction of rules.","bestPractices":[{"guidance":true,"description":"Keep groups controlled and configure controls returned by renderRule for the same disabled or read-only state as the groups."},{"guidance":false,"description":"Do not expect a disabled add at maxRules or maxGroups; this view hides adds at their limits and protects the final rule from removal."}],"anatomy":[{"name":"Rule cards","required":false,"description":"Titled accent cards containing host controls and optional close actions when groups contain rules."},{"name":"And joints","required":false,"description":"Words and add-rule buttons within each group."},{"name":"Or dividers","required":false,"description":"Dashed separators and the add-group action."},{"name":"Error","required":false,"description":"A translated problem with all groups."}],"accessibility":["label names the whole group and labels.remove names rule-card close actions; supplied controls keep their own labels."],"props":[{"path":"label","type":"string","required":true,"description":"Names the whole set of groups for assistive tech; it is not shown."},{"path":"groups","type":"readonly (readonly unknown[])[]","required":true,"description":"The groups, any of which may hold; each holds rules that all must hold."},{"path":"ruleKey","type":"((rule: unknown, at: RuleGroupsAt) => string) | undefined","required":false,"description":"A stable key for a rule; its position without it."},{"path":"renderRule","type":"(rule: unknown, at: RuleGroupsAt) => ReactNode","required":true,"description":"The body of a rule's card, such as a field, an operator and a value; the host supplies the\ncontrols, which sit on one wrapping row, top-aligned so a problem under one keeps the others\nlevel."},{"path":"labels","type":"{ readonly ruleTitle: string; readonly remove: string; readonly and: string; readonly or: string; }","required":true,"description":"The texts the groups show; the host translates them."},{"path":"labels.ruleTitle","type":"string","required":false,"requiredWhenParent":true,"description":"The title of every rule's card, such as \"Have attributes\"."},{"path":"labels.remove","type":"string","required":false,"requiredWhenParent":true,"description":"The name of a card's close button, such as \"Remove rule\"."},{"path":"labels.and","type":"string","required":false,"requiredWhenParent":true,"description":"The word between two rules of a group, and the text of the button that adds one, such as \"and\"."},{"path":"labels.or","type":"string","required":false,"requiredWhenParent":true,"description":"The word on the divider between two groups, and the text of the button that adds one, such as \"or\"."},{"path":"onAddRule","type":"(group: number) => void","required":true,"description":"Called with a group's index when its add button is pressed."},{"path":"onRemoveRule","type":"(at: RuleGroupsAt) => void","required":true,"description":"Called with a rule's place when its card's close button is pressed."},{"path":"onAddGroup","type":"() => void","required":true,"description":"Called when the button after the last group is pressed."},{"path":"maxGroups","type":"number | undefined","required":false,"description":"The most groups there may be; at that many the button that adds one is gone."},{"path":"maxRules","type":"number | undefined","required":false,"description":"The most rules a group may hold; at that many its add button is gone."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the groups as a whole, already in the user's language."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the groups and blocks the close and add buttons, hiding nothing; the rules' controls are the host's to disable."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Hides every close and add button; the rules' controls are the host's to make read-only."}],"examples":[{"name":"Default","description":"Two groups: two rules joined by \"And\", a dashed \"Or\", one rule; each group ends with its \"+ And\", the last with \"+ Or\".","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"},{"name":"OneRule","description":"One rule, the only one left: no close button on its card.","source":"export const OneRule: Story = { args: { groups: [[hanoi]] } };","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"},{"name":"SeveralRules","description":"Several rules in one group.","source":"export const SeveralRules: Story = { args: { groups: [[hanoi, pro, hanoi, pro]] } };","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"},{"name":"Empty","description":"No group at all, as an old document may hold: only \"+ Or\", to start one.","source":"export const Empty: Story = { args: { groups: [] } };","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"},{"name":"Interactive","description":"Adding and removing rules and groups; removing a group's last rule removes the group.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<readonly (readonly Rule[])[]>(several, (value, set) => (\n <RuleGroups\n {...args}\n groups={value}\n onAddRule={(group) => {\n set(\n value.map((rules, at) =>\n at === group ? [...rules, { field: \"city\", value: \"\" }] : rules,\n ),\n );\n }}\n onAddGroup={() => {\n set([...value, [{ field: \"city\", value: \"\" }]]);\n }}\n onRemoveRule={({ group, rule }) => {\n set(\n value\n .map((rules, at) =>\n at === group ? rules.filter((_r, index) => index !== rule) : rules,\n )\n .filter((rules) => rules.length > 0),\n );\n }}\n />\n )),\n};","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"},{"name":"AtLimits","description":"A group at its limit of 3 rules has no \"+ And\"; at 2 groups there is no \"+ Or\".","source":"export const AtLimits: Story = {\n args: { groups: [[hanoi, pro, hanoi], [pro]], maxRules: 3, maxGroups: 2 },\n};","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"},{"name":"WithError","description":"A problem with the whole, under the groups; a rule's own problem under its control.","source":"export const WithError: Story = {\n args: {\n groups: [[hanoi, { field: \"plan\", value: \"\" }]],\n error: \"Fill in every rule's value\",\n },\n};","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"},{"name":"Disabled","description":"Dimmed: the close and add buttons are blocked, and nothing is hidden.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"},{"name":"ReadOnly","description":"Read-only: no close nor add button.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"},{"name":"Long","description":"Long content: a long field name, and ten rules in a group.","source":"export const Long: Story = {\n args: {\n groups: [\n Array.from({ length: 10 }, (_, index) => ({ field: \"note\", value: `Value ${index + 1}` })),\n ],\n },\n};","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, where a card's controls wrap.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <RuleGroups {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Filled\">\n <RuleGroups {...args} />\n </Case>\n <Case caption=\"One rule\">\n <RuleGroups {...args} groups={[[hanoi]]} />\n </Case>\n <Case caption=\"Empty\">\n <RuleGroups {...args} groups={[]} />\n </Case>\n <Case caption=\"At limits\">\n <RuleGroups {...args} {...AtLimits.args} />\n </Case>\n <Case caption=\"Error\">\n <RuleGroups {...args} {...WithError.args} />\n </Case>\n <Case caption=\"Disabled\">\n <RuleGroups {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <RuleGroups {...args} isReadOnly />\n </Case>\n <Case caption=\"Narrow\">\n <Narrow>\n <RuleGroups {...args} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"}],"primaryExample":"Default"},{"name":"RuleList","level":"organisms","category":"Inputs","keywords":["rules","list","conditions","joint"],"import":"import { RuleList } from \"@antscorp/ui\";","description":"Edit rules that all must hold, with one joining word on the first row and another on later rows.","usage":"Edit rules that all must hold, with one joining word on the first row and another on later rows. Use RuleGroups when alternatives need separate groups of rule cards.","bestPractices":[{"guidance":true,"description":"Provide stable itemKey values and pass the list's size and lock state to controls returned by renderControls."},{"guidance":false,"description":"Do not expect isReadOnly to change host-rendered controls; it hides the list's add and remove actions."}],"anatomy":[{"name":"Rule rows","required":true,"description":"RuleRow controls with first/rest joint labels and removal actions."},{"name":"Add row","required":false,"description":"An optional add action, with a joint when hasAddJoint is enabled."},{"name":"Feedback","required":false,"description":"Empty, maximum and validation messages."}],"accessibility":["label names the list; labels.remove and labels.add name editing actions, and each supplied control needs its own label."],"props":[{"path":"label","type":"string","required":true,"description":"Names the list."},{"path":"items","type":"readonly unknown[]","required":true,"description":"The rules, in order."},{"path":"itemKey","type":"(item: unknown, index: number) => string","required":true,"description":"A stable key for a rule."},{"path":"renderControls","type":"(item: unknown, index: number) => ReactNode","required":true,"description":"The controls of a rule's row, such as a field, an operator and a value; the host supplies\nthem, each with the list's `size` so the row lines up."},{"path":"labels","type":"{ readonly remove: string; readonly jointFirst: string; readonly jointRest: string; readonly add?: string | undefined; readonly maxReached?: string | undefined; readonly empty?: string | undefined; }","required":true,"description":"The texts the list shows; the host translates them."},{"path":"labels.remove","type":"string","required":false,"requiredWhenParent":true,"description":"What a row's remove button does, such as \"Remove rule\"."},{"path":"labels.jointFirst","type":"string","required":false,"requiredWhenParent":true,"description":"The joint word on the first row, such as \"where\"."},{"path":"labels.jointRest","type":"string","required":false,"requiredWhenParent":true,"description":"The joint word on every later row, such as \"and\"."},{"path":"labels.add","type":"string | undefined","required":false,"description":"The add button's text; required with `onAdd`."},{"path":"labels.maxReached","type":"string | undefined","required":false,"description":"Shown above the add button once `max` rules are there."},{"path":"labels.empty","type":"string | undefined","required":false,"description":"Shown while there are no rules."},{"path":"jointWidth","type":"number | undefined","required":false,"description":"The joint words' width in pixels; the row's default (28) when absent."},{"path":"jointCase","type":"\"sentence\" | \"upper\" | undefined","required":false,"description":"The joint words' case: `upper` (default) small capitals, or `sentence`, as given in body text, as rows' labels read."},{"path":"hasAddJoint","type":"boolean","required":false,"description":"Puts the add button in a last row after its joint word (`jointRest`, or `jointFirst` while there are no rules), lined up with the rules."},{"path":"addShape","type":"\"default\" | \"pill\" | undefined","required":false,"description":"The add button's shape: `default`, or `pill` for a fully rounded one."},{"path":"onRemove","type":"(index: number) => void","required":true,"description":"Called with the index of the rule to remove."},{"path":"onAdd","type":"(() => void) | undefined","required":false,"description":"Called when the add button is pressed; leave it out when the host adds elsewhere, e.g. one button for several lists."},{"path":"addVariant","type":"\"ghost\" | \"secondary\" | undefined","required":false,"description":"The add button's weight, `ghost` by default."},{"path":"rowGap","type":"2 | 4 | undefined","required":false,"description":"The space between the rows, the add row too, in Astryx spacing steps: `2` (8px, default) or `4` (16px)."},{"path":"align","type":"\"center\" | \"start\" | undefined","required":false,"description":"How each row's controls sit on it, `center` by default; `start` keeps their input line level when one shows a problem under it (see `RuleRow`)."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The size of the remove and add buttons, `md` by default."},{"path":"max","type":"number | undefined","required":false,"description":"The most rules the list holds; at that many the add button stays, but disabled."},{"path":"isAddDisabled","type":"boolean","required":false,"description":"Blocks the add button alone (e.g. until something the rules need is chosen); the rows stay as they are."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the list as a whole, already in the user's language."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the list; the rows' remove buttons and the add button are blocked."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Hides the add button and every remove button."}],"examples":[{"name":"Default","description":"The first row starts with \"where\", later ones with \"and\".","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"Empty","description":"No rule yet.","source":"export const Empty: Story = {\n args: { items: [], labels: { ...labels, empty: \"No rule: everything matches.\" } },\n};","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"Interactive","description":"Adding and removing rules.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<readonly Rule[]>(rules.slice(0, 2), (list, set) => (\n <RuleList\n {...args}\n items={list}\n onRemove={(index) => {\n set(list.filter((_rule, at) => at !== index));\n }}\n onAdd={() => {\n set([...list, { id: String(Date.now()), field: \"city\", value: \"\" }]);\n }}\n />\n )),\n};","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"WithError","description":"A problem with the list as a whole.","source":"export const WithError: Story = { args: { error: \"Fill in every rule.\" } };","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"Disabled","description":"Dimmed: the rows' remove buttons and the add button are blocked, and nothing is hidden.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"ReadOnly","description":"Read-only: the add button and every remove button are hidden.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"Medium","description":"The remove and add buttons at the `md` height.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"WideJoints","description":"Wider joint words, so \"where\" fits.","source":"export const WideJoints: Story = { args: { jointWidth: 56 } };","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"SingleRule","description":"A single rule: only \"where\".","source":"export const SingleRule: Story = { args: { items: rules.slice(0, 1) } };","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"MaxReached","description":"Twenty rules, the limit of the form this came from; the add button is disabled.","source":"export const MaxReached: Story = {\n args: {\n items: Array.from({ length: 20 }, (_, index) => ({\n id: String(index),\n field: \"city\",\n value: `Value ${index + 1}`,\n })),\n max: 20,\n labels: { ...labels, maxReached: \"You can add up to 20 rules.\" },\n },\n};","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"AddDisabled","description":"Only the add button is blocked; the rows stay editable.","source":"export const AddDisabled: Story = { args: { isAddDisabled: true } };","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"WithoutAdd","description":"For a host that adds elsewhere, such as one button for several lists.","source":"export const WithoutAdd: Story = { render: (args) => <RuleList {...args} onAdd={undefined} /> };","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, where rows wrap.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <RuleList {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"SentenceJoints","description":"Joints in sentence case, as rows' labels, and the pill add button in a last row after \"And\".","source":"export const SentenceJoints: Story = {\n args: {\n labels: { ...labels, jointFirst: \"Where\", jointRest: \"And\" },\n jointCase: \"sentence\",\n jointWidth: 90,\n hasAddJoint: true,\n addVariant: \"secondary\",\n addShape: \"pill\",\n },\n};","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"SentenceJointsEmpty","description":"No rule yet: the add row starts with \"Where\".","source":"export const SentenceJointsEmpty: Story = {\n args: { ...SentenceJoints.args, items: [] },\n};","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"RowsApart","description":"Rows 16px apart, including the add row.","source":"export const RowsApart: Story = { args: { ...SentenceJoints.args, rowGap: 4 } };","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"RowProblems","description":"A row whose value shows a problem under it: with `align=\"start\"` its input line, joint word and remove button stay level with the field's.","source":"export const RowProblems: Story = {\n args: {\n align: \"start\",\n items: [\n rules[0] ?? { id: \"x\", field: \"city\", value: \"\" },\n { id: \"d\", field: \"plan\", value: \"\" },\n ],\n },\n};","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Filled\">\n <RuleList {...args} />\n </Case>\n <Case caption=\"Empty\">\n <RuleList\n {...args}\n items={[]}\n labels={{ ...labels, empty: \"No rule: everything matches.\" }}\n />\n </Case>\n <Case caption=\"Error\">\n <RuleList {...args} error=\"Fill in every rule.\" />\n </Case>\n <Case caption=\"Max reached\">\n <RuleList\n {...args}\n max={3}\n labels={{ ...labels, maxReached: \"You can add up to 3 rules.\" }}\n />\n </Case>\n <Case caption=\"Disabled\">\n <RuleList {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <RuleList {...args} isReadOnly />\n </Case>\n <Case caption=\"Medium\">\n <RuleList {...args} size=\"md\" />\n </Case>\n <Case caption=\"A row's problem, top-aligned\">\n <RuleList {...args} {...RowProblems.args} />\n </Case>\n <Case caption=\"Rows 16px apart\">\n <RuleList {...args} rowGap={4} />\n </Case>\n <Case caption=\"Sentence joints, add in a row\">\n <RuleList\n {...args}\n labels={{ ...labels, jointFirst: \"Where\", jointRest: \"And\" }}\n jointCase=\"sentence\"\n jointWidth={90}\n hasAddJoint\n addVariant=\"secondary\"\n addShape=\"pill\"\n />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/RuleList.stories.tsx"}],"primaryExample":"Default"},{"name":"RuleRow","level":"molecules","category":"Inputs","keywords":["rule","row","joint","remove"],"import":"import { RuleRow } from \"@antscorp/ui\";","description":"Arrange a joining word, host controls and a remove button on a wrapping row.","usage":"Arrange a joining word, host controls and a remove button on a wrapping row. Inside RepeatableList with columns, the row uses the shared grid tracks.","bestPractices":[{"guidance":true,"description":"Give host controls the row's size and use align=start when a control shows validation below its input."},{"guidance":false,"description":"Do not assume isDisabled or isReadOnly changes the supplied controls; those flags control the row's remove affordance."}],"anatomy":[{"name":"Joint word","required":false,"description":"The translated word joining this row to its predecessor."},{"name":"Controls","required":true,"description":"Host-supplied fields, operators and values."},{"name":"Remove button","required":false,"description":"A named action, with its grid track preserved in read-only column layouts."}],"accessibility":["labels.remove names the remove button; each supplied control needs its own label."],"props":[{"path":"labels","type":"{ readonly remove: string; readonly joint?: string | undefined; }","required":true,"description":"The texts the row shows; the host translates them."},{"path":"labels.remove","type":"string","required":false,"requiredWhenParent":true,"description":"What the remove button does, such as \"Remove rule\"; it shows only an icon."},{"path":"labels.joint","type":"string | undefined","required":false,"description":"The word that joins the row to the one above, such as \"if\" or \"and\"; no word without it."},{"path":"align","type":"\"center\" | \"end\" | \"start\" | undefined","required":false,"description":"How the controls sit on the row: centred; on the top edge, so controls showing a problem under\nthem keep their input line level with the others; or on the bottom edge when they have labels."},{"path":"jointWidth","type":"number | undefined","required":false,"description":"The joint word's width in pixels, so words like \"where\" fit; 28 by default."},{"path":"jointCase","type":"\"sentence\" | \"upper\" | undefined","required":false,"description":"The joint word's case: `upper` (default) small capitals, or `sentence`, as given in body text (see `JointLabel`)."},{"path":"children","type":"ReactNode","required":true,"description":"The row's controls, such as a field, an operator and a value; the host supplies them. Give\nevery control the row's `size` (`md` by default, the remove button's height) so the row lines up."},{"path":"onRemove","type":"() => void","required":true,"description":"Called when the remove button is pressed."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The remove button's size, `md` by default; give the controls the same size so the row lines up."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the remove button and blocks it; the controls are the host's to disable."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Hides the remove button."}],"examples":[{"name":"Default","description":"The first row of a group: it starts with \"if\".","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"And","description":"A later row, joined with \"and\".","source":"export const And: Story = { args: { labels: { joint: \"and\", remove: \"Remove rule\" } } };","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"WideJoint","description":"A wider joint word, for words such as \"where\" that do not fit the default width.","source":"export const WideJoint: Story = {\n args: { labels: { joint: \"where\", remove: \"Remove rule\" }, jointWidth: 56 },\n};","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"NoJoint","description":"Without a joint word.","source":"export const NoJoint: Story = { args: { labels: { remove: \"Remove rule\" } } };","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"Interactive","description":"Rows line up on their joint words; removing one drops it.","source":"export const Interactive: Story = {\n render: (args) =>\n withState([\"a\", \"b\", \"c\"], (rows, set) => (\n <VStack gap={2}>\n {rows.map((row, index) => (\n <RuleRow\n key={row}\n {...args}\n labels={{ joint: index === 0 ? \"if\" : \"and\", remove: `Remove ${row}` }}\n onRemove={() => {\n args.onRemove();\n set(rows.filter((item) => item !== row));\n }}\n />\n ))}\n </VStack>\n )),\n};","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"AlignedToEnd","description":"Controls with labels sit on the bottom edge.","source":"export const AlignedToEnd: Story = { args: { align: \"end\" } };","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"AlignedToStart","description":"One control shows a problem under it: on the top edge, every input line, the joint word and the remove button stay level.","source":"export const AlignedToStart: Story = { args: { align: \"start\", children: controlsWithError } };","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"Disabled","description":"The remove button dimmed and blocked.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"ReadOnly","description":"Read-only: the remove button is hidden.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"Medium","description":"The `md` remove button, beside `md` controls.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"LongContent","description":"Many controls and a long remove label, which wrap.","source":"export const LongContent: Story = {\n args: {\n labels: { joint: \"if\", remove: \"Remove this rule and every value it holds\" },\n children: Array.from({ length: 10 }, (_, index) => (\n <TextInput\n key={index}\n label={`Value ${index + 1}`}\n isLabelHidden\n size=\"sm\"\n value={`Value ${index + 1}`}\n onChange={fn<() => void>()}\n />\n )),\n },\n};","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, where the row wraps.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <RuleRow {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"First (if)\">\n <RuleRow {...args} />\n </Case>\n <Case caption=\"Later (and)\">\n <RuleRow {...args} labels={{ ...args.labels, joint: \"and\" }} />\n </Case>\n <Case caption=\"No joint\">\n <RuleRow {...args} labels={{ remove: args.labels.remove }} />\n </Case>\n <Case caption=\"Disabled\">\n <RuleRow {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <RuleRow {...args} isReadOnly />\n </Case>\n <Case caption=\"Medium\">\n <RuleRow {...args} size=\"md\" />\n </Case>\n <Case caption=\"Top-aligned, one control with a problem\">\n <RuleRow {...args} align=\"start\">\n {controlsWithError}\n </RuleRow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/RuleRow.stories.tsx"}],"primaryExample":"Default"},{"name":"SearchableGroupedList","level":"organisms","category":"Selection","keywords":["search","groups","list","tiles"],"import":"import { SearchableGroupedList } from \"@antscorp/ui\";","description":"Browse host-rendered items in titled groups, filtering locally as text is typed.","usage":"Browse host-rendered items in titled groups, filtering locally as text is typed. Use TreeSelect when the result must be a single leaf chosen from two-level groups.","bestPractices":[{"guidance":true,"description":"Provide stable group keys and globally unique item keys; use itemText for the text to match and placeholder for independently loading groups."},{"guidance":false,"description":"Do not expect isDisabled to block host-rendered item actions; it only affects the uncapped list's search control."}],"anatomy":[{"name":"Search","required":true,"description":"The local query input with clear action."},{"name":"Group headings","required":true,"description":"Titles, optionally toggling collapsed groups."},{"name":"Items","required":true,"description":"Host rows or three-column tiles, replaced by group placeholders when supplied."},{"name":"No-match message","required":false,"description":"Shown when there are no matching items."}],"accessibility":["Supply labels.search and labels.clear, and scroll.label when using a scrolling region; item renderers supply their own accessible controls."],"props":[{"path":"labels","type":"{ readonly search: string; readonly noMatch: string; readonly clear: string; }","required":true,"description":"The texts the list shows; the host translates them."},{"path":"labels.search","type":"string","required":false,"requiredWhenParent":true,"description":"The search box's accessible name, and its placeholder unless `placeholder` is given, such as \"Search\"."},{"path":"labels.noMatch","type":"string","required":false,"requiredWhenParent":true,"description":"Shown when the search (or the data) leaves no item."},{"path":"labels.clear","type":"string","required":false,"requiredWhenParent":true,"description":"The clear button's name, such as \"Clear search\"; it shows only an icon."},{"path":"placeholder","type":"string | undefined","required":false,"description":"The search box's placeholder; `labels.search` when absent."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The search box's size, `md` by default; the uncapped list's only."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the search box and blocks it, the uncapped list's only; the items are the host's to disable."},{"path":"groups","type":"readonly ItemGroup<unknown>[]","required":true,"description":"The groups, in order. A group with no item (or no match) and no placeholder is left out."},{"path":"groups[].key","type":"string","required":false,"requiredWhenParent":true,"description":"A stable key for the group."},{"path":"groups[].title","type":"string","required":false,"requiredWhenParent":true,"description":"The heading over the group's items."},{"path":"groups[].items","type":"readonly unknown[]","required":false,"requiredWhenParent":true,"description":"The group's items."},{"path":"groups[].placeholder","type":"ReactNode","required":false,"description":"Drawn instead of the group's items while they load (a skeleton, or the load's error); a group\nwith one is shown even with no items or no match."},{"path":"itemKey","type":"(item: unknown) => string","required":true,"description":"A stable key for an item, unique across groups."},{"path":"itemText","type":"(item: unknown) => string","required":true,"description":"The text the search matches on for an item."},{"path":"renderItem","type":"(item: unknown) => ReactNode","required":true,"description":"Draws an item; keep it memoized when there are hundreds of items."},{"path":"layout","type":"\"grid\" | \"list\" | undefined","required":false,"description":"`grid`: three tiles a row; `list` (default): one row each."},{"path":"hasAutoFocus","type":"boolean","required":false,"description":"Focuses the search box once shown, in a pop-over too, which shows a frame after mounting."},{"path":"isCollapsible","type":"boolean","required":false,"description":"Each group folds under its title, all open at first. While searching, every group with a match\nis open; clearing the search brings back the groups the user folded."},{"path":"scroll","type":"{ readonly maxHeight?: number | undefined; readonly label: string; } | undefined","required":false,"description":"Caps the height, the groups scrolling under the search box, in a pop-over say; with no\n`maxHeight` it fills its parent's height, in a side panel say. The list then pads its own edges,\nso the search row (its magnifier at the end) and the rule under it reach the\nedges of a flush (unpadded) surface, and so does the scrollbar."},{"path":"scroll.maxHeight","type":"number | undefined","required":false,"description":""},{"path":"scroll.label","type":"string","required":false,"requiredWhenParent":true,"description":""}],"examples":[{"name":"Default","description":"Groups of rows under their headings; type to filter.","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"Grid","description":"Three tiles a row.","source":"export const Grid: Story = { args: { layout: \"grid\" } };","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"Empty","description":"No group: the no-match text.","source":"export const Empty: Story = { args: { groups: [] } };","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"EmptyGroup","description":"A group without items is left out.","source":"export const EmptyGroup: Story = {\n args: { groups: [...groups.slice(0, 1), { key: \"x\", title: \"Hidden\", items: [] }] },\n};","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"SingleItem","description":"A single item.","source":"export const SingleItem: Story = {\n args: { groups: [{ key: \"a\", title: \"Entry points\", items: [\"Schedule\"] }] },\n};","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"ManyItems","description":"Forty-eight items in four groups, as tiles.","source":"export const ManyItems: Story = { args: { groups: many, layout: \"grid\" } };","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"AutoFocus","description":"Focuses the search box when shown.","source":"export const AutoFocus: Story = { args: { hasAutoFocus: true } };","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <SearchableGroupedList {...args} layout=\"grid\" />\n </Narrow>\n ),\n};","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"Collapsible","description":"Each group folds under its title; a search opens every group with a match.","source":"export const Collapsible: Story = { args: { isCollapsible: true } };","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"Capped","description":"Capped at 200px: the groups scroll under the search box, as in a pop-over.","source":"export const Capped: Story = {\n args: { groups: many, isCollapsible: true, scroll: { maxHeight: 200, label: \"Items\" } },\n};","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"Loading","description":"One group still loading: skeleton rows in its place, the other groups usable.","source":"export const Loading: Story = { args: { groups: loading } };","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"LoadingGrid","description":"One group still loading, as tiles: skeleton tiles in its place.","source":"export const LoadingGrid: Story = { args: { groups: loadingGrid, layout: \"grid\" } };","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"WithPlaceholder","description":"A placeholder that differs from the search box's name.","source":"export const WithPlaceholder: Story = { args: { placeholder: \"Search fruit and veg\" } };","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"Medium","description":"The search box at the `md` height.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"Disabled","description":"The search box dimmed and blocked.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Rows\">\n <SearchableGroupedList {...args} />\n </Case>\n <Case caption=\"Tiles\">\n <SearchableGroupedList {...args} layout=\"grid\" />\n </Case>\n <Case caption=\"Collapsible, capped\">\n <SearchableGroupedList\n {...args}\n isCollapsible\n scroll={{ maxHeight: 200, label: \"Items\" }}\n />\n </Case>\n <Case caption=\"Empty\">\n <SearchableGroupedList {...args} groups={[]} />\n </Case>\n <Case caption=\"Loading\">\n <SearchableGroupedList {...args} groups={loading} />\n </Case>\n <Case caption=\"Loading tiles\">\n <SearchableGroupedList {...args} groups={loadingGrid} layout=\"grid\" />\n </Case>\n <Case caption=\"Medium\">\n <SearchableGroupedList {...args} size=\"md\" />\n </Case>\n <Case caption=\"Disabled\">\n <SearchableGroupedList {...args} isDisabled />\n </Case>\n <Case caption=\"Placeholder\">\n <SearchableGroupedList {...args} placeholder=\"Search fruit and veg\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"}],"primaryExample":"Default"},{"name":"Select","level":"molecules","category":"Selection","keywords":["choice","options","search"],"import":"import { Select } from \"@antscorp/ui\";","description":"Choose one value from options, optionally grouped into sections.","usage":"Choose one value from options, optionally grouped into sections. Keep value controlled through onChange. Offer onClear for an empty selection; style the control and Field via controlXstyle and containerXstyle. Localize upstream strings with the facade's InternationalizationProvider.","bestPractices":[{"guidance":true,"description":"Use hasSearch for long lists and keep the known label available through labels.value while options load."},{"guidance":false,"description":"Do not supply labels.warning alongside error expecting both to appear; warning takes precedence over the validation error."},{"guidance":false,"description":"Do not disable a field merely to display its value; use isReadOnly when editing is unavailable."}],"anatomy":[{"name":"Field label","required":true,"description":"Names the choice even when visually hidden."},{"name":"Trigger","required":true,"description":"Shows the selected value or placeholder and opens the list."},{"name":"Options","required":false,"description":"The choices, sections and optional search field, while the list is open and editable."}],"accessibility":["Always provide label, including when isLabelHidden is true.","Supply translated description and error text so the choice and its validation are clear."],"related":[{"name":"TreeSelect","when":"Choose one leaf from a two-level tree."},{"name":"ChipListAddPopover","when":"Choose multiple values in a compact popover with an Apply draft."},{"name":"TransferPicker","when":"Choose multiple values with available and picked rows visible together."}],"props":[{"path":"containerXstyle","type":"StyleXStyles","required":false,"description":"StyleX styles for the field container (the Field box around the label,\ncontrol, and status). Has no effect where no Field box renders: inside\nInputGroup (style the InputGroup instead) and in a `horizontal-labels`\nFormLayout."},{"path":"controlXstyle","type":"StyleXStyles","required":false,"description":"StyleX styles for the visual control box: the element that paints the\nfield border and background and holds its icons and end controls. Never\nthe native input."},{"path":"label","type":"string","required":true,"description":"Label text for the selector (always rendered for accessibility)."},{"path":"isLabelHidden","type":"boolean","required":false,"default":"false","description":"Whether to visually hide the label (still accessible to screen readers)."},{"path":"description","type":"string","required":false,"description":"Description text displayed between the label and selector."},{"path":"isRequired","type":"boolean","required":false,"default":"false","description":"Whether the field is required. Mutually exclusive with isOptional."},{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether the selector is disabled."},{"path":"isReadOnly","type":"boolean","required":false,"default":"false","description":"Whether the selector is read-only.\nThe selected value stays visible, focusable, and included in form\nsubmission, and retains its combobox identity with `aria-readonly`. The\nselection surface and editing affordances are removed.\nUnlike `isDisabled`, a read-only selector is not dimmed and keeps its\nvalue in form submission. `isDisabled` takes precedence when both are set."},{"path":"placeholder","type":"string","required":false,"default":"'Select...'","description":"Placeholder text when no value is selected."},{"path":"width","type":"SizeValue","required":false,"description":"Width of the field. Numbers are treated as pixels, strings are used as-is\n(e.g. `'100%'`). Sizes the whole field (label, control, and status) so they\nstay aligned, unlike setting width via `controlXstyle`/`controlClassName`/`controlStyle`."},{"path":"hasSearch","type":"boolean","required":false,"default":"false","description":"Whether to show a search input for filtering options."},{"path":"options","type":"readonly SelectorOptionType[]","required":true,"description":"The list, in order. Item types: string | SelectorDivider | SelectorOptionData | SelectorSection."},{"path":"options[].type","type":"\"divider\" | \"section\"","required":false,"description":"Required in SelectorDivider. Required in SelectorSection."},{"path":"options[].value","type":"string","required":false,"description":"Required in SelectorOptionData."},{"path":"options[].label","type":"string | undefined","required":false,"description":"In SelectorOptionData."},{"path":"options[].description","type":"ReactNode","required":false,"description":"In SelectorOptionData."},{"path":"options[].disabled","type":"boolean","required":false,"description":"In SelectorOptionData."},{"path":"options[].icon","type":"IconType | ReactNode","required":false,"description":"In SelectorOptionData."},{"path":"options[].action","type":"ReactNode","required":false,"description":"In SelectorOptionData. The option's secondary control — an Edit button beside a saved label —\nas one node the caller renders and names; the host only places it. In\n`MultiSelector`, any option carrying one turns the popup into a grid whose\nrows pair the option with its action, reachable by pointer, touch, the\ninline-end arrow, and a screen reader (`spec:AST-058`). `Selector` does\nnot render it yet and warns in development when an option carries one.\nAbsent and `null` mean the same: none."},{"path":"options[].title","type":"string | undefined","required":false,"description":"In SelectorSection."},{"path":"options[].options","type":"SelectorOptionData[]","required":false,"description":"Required in SelectorSection."},{"path":"options[].options[].value","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"options[].options[].label","type":"string","required":false,"description":""},{"path":"options[].options[].description","type":"ReactNode","required":false,"description":""},{"path":"options[].options[].disabled","type":"boolean","required":false,"description":""},{"path":"options[].options[].icon","type":"IconType | ReactNode","required":false,"description":""},{"path":"options[].options[].action","type":"ReactNode","required":false,"description":"The option's secondary control — an Edit button beside a saved label —\nas one node the caller renders and names; the host only places it. In\n`MultiSelector`, any option carrying one turns the popup into a grid whose\nrows pair the option with its action, reachable by pointer, touch, the\ninline-end arrow, and a screen reader (`spec:AST-058`). `Selector` does\nnot render it yet and warns in development when an option carries one.\nAbsent and `null` mean the same: none."},{"path":"value","type":"string | null | undefined","required":false,"description":"The picked option's value; `null` or none shows the placeholder."},{"path":"onChange","type":"(value: string) => void","required":true,"description":"Called with the value of the option picked."},{"path":"onClear","type":"(() => void) | undefined","required":false,"description":"Called by the clear button, which shows only when this is given and an option is picked."},{"path":"variant","type":"\"ghost\" | \"input\" | undefined","required":false,"description":"The trigger's look: an underlined field (`input`, the default) or a bare text button (`ghost`)."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The trigger's size, md by default; the kit narrows the upstream sizes to sm and md."},{"path":"isLoading","type":"boolean","required":false,"description":"The options are loading: a spinner in place of the chevron and the trigger busy from 300 ms\non, for at least 300 ms (`useLoadingShown`), and the list does not open meanwhile. The picked\nlabel shows when `options` (the last list) or `labels.value` know it; otherwise the trigger is\nblank, with no placeholder."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the pick, already in the user's language, shown under the trigger."},{"path":"labels","type":"{ readonly search?: string | undefined; readonly noMatch?: string | undefined; readonly warning?: string | undefined; readonly value?: string | undefined; } | undefined","required":false,"description":"Host texts; missing strings use the facade's InternationalizationProvider locale (English by default)."},{"path":"labels.search","type":"string | undefined","required":false,"description":"The search box's placeholder, such as \"Search\"."},{"path":"labels.noMatch","type":"string | undefined","required":false,"description":"Shown in the list when the search leaves no option."},{"path":"labels.warning","type":"string | undefined","required":false,"description":"A caution about the pick, shown as an icon in the trigger with this text in its tooltip."},{"path":"labels.value","type":"string | undefined","required":false,"description":"The trigger's text while `isLoading` when `value` is not among `options`, such as a stored label."}],"examples":[{"name":"StyledParts","description":"The visual control and surrounding Field have independent StyleX parts.","source":"export const StyledParts: Story = {\n args: { controlXstyle: partStyles.control, containerXstyle: partStyles.container },\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"Default","description":"A short value; open it: the pop-up is as wide as the field.","source":"export const Default: Story = {\n render: () => (\n <Narrow>\n {withState(\"customer\", (value, set) => (\n <Select label=\"Data access\" isLabelHidden options={OPTIONS} value={value} onChange={set} />\n ))}\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"LongLabel","description":"A long option picked, in a narrow field: it ends in an ellipsis; hover the open option for the whole label.","source":"export const LongLabel: Story = {\n render: (args) => (\n <Narrow>\n <Select {...args} value=\"long\" />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"SectionsAndSearch","description":"Sections and many options, with a search box.","source":"export const SectionsAndSearch: Story = {\n render: () => (\n <Narrow>\n {withState(\"customer\", (value, set) => (\n <Select\n label=\"Data access\"\n isLabelHidden\n options={SECTIONS}\n hasSearch\n labels={{ search: \"Search\", noMatch: \"No views\" }}\n value={value}\n onChange={set}\n />\n ))}\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"Empty","description":"Nothing picked yet: the placeholder.","source":"export const Empty: Story = {\n render: (args) => (\n <Narrow>\n <Select {...args} placeholder=\"Pick a view\" />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"Error","description":"An error under the field.","source":"export const Error: Story = {\n render: (args) => (\n <Narrow>\n <Select {...args} isLabelHidden={false} error=\"Pick a view\" />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"Disabled","description":"Disabled: it does not open.","source":"export const Disabled: Story = {\n render: (args) => (\n <Narrow>\n <Select {...args} value=\"customer\" isDisabled />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"Clearable","description":"A pick that can be cleared: the clear button shows while an option is picked.","source":"export const Clearable: Story = {\n render: () => (\n <Narrow>\n {withState<string | null>(\"customer\", (value, set) => (\n <Select\n label=\"Data access\"\n isLabelHidden\n options={OPTIONS}\n value={value}\n onChange={set}\n onClear={() => {\n set(null);\n }}\n />\n ))}\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"Warning","description":"A caution about the pick: an icon in the trigger, its text in a tooltip.","source":"export const Warning: Story = {\n render: (args) => (\n <Narrow>\n <Select\n {...args}\n value=\"view-1\"\n labels={{ warning: \"You no longer have access to this view\" }}\n />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"Loading","description":"The options loading, with the picked label known (from the last list or `labels.value`): the\nlabel stays, a spinner stands where the chevron was, and the list does not open.","source":"export const Loading: Story = {\n render: (args) => (\n <Narrow>\n <Select {...args} options={[]} value=\"customer\" isLoading labels={{ value: \"Customer\" }} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"LoadingUnknownLabel","description":"The options loading with the picked label unknown: a blank trigger and the spinner.","source":"export const LoadingUnknownLabel: Story = {\n render: (args) => (\n <Narrow>\n <Select {...args} options={[]} value=\"customer\" placeholder=\"Pick a view\" isLoading />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"Ghost","description":"The bare text-button look, for a choice inside a sentence.","source":"export const Ghost: Story = {\n render: (args) => <Select {...args} value=\"customer\" variant=\"ghost\" size=\"sm\" />,\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Short value\">\n <Narrow>\n <Select {...args} value=\"customer\" />\n </Narrow>\n </Case>\n <Case caption=\"Long value, cut\">\n <Narrow>\n <Select {...args} value=\"long\" />\n </Narrow>\n </Case>\n <Case caption=\"Placeholder\">\n <Narrow>\n <Select {...args} placeholder=\"Pick a view\" />\n </Narrow>\n </Case>\n <Case caption=\"Error\">\n <Narrow>\n <Select {...args} value=\"customer\" isLabelHidden={false} error=\"Pick a view\" />\n </Narrow>\n </Case>\n <Case caption=\"Disabled\">\n <Narrow>\n <Select {...args} value=\"customer\" isDisabled />\n </Narrow>\n </Case>\n <Case caption=\"Loading, label known\">\n <Narrow>\n <Select {...args} value=\"customer\" isLoading />\n </Narrow>\n </Case>\n <Case caption=\"Loading, label unknown\">\n <Narrow>\n <Select {...args} options={[]} value=\"customer\" isLoading />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/Select.stories.tsx"}],"primaryExample":"Default"},{"name":"SideLabelRow","level":"molecules","category":"Layout","keywords":["card","row","caption","responsive"],"import":"import { SideLabelRow } from \"@antscorp/ui\";","description":"Place a narrow caption beside controls inside a card, with padding for the card's rows.","usage":"Place a narrow caption beside controls inside a card, with padding for the card's rows. Use FieldRow for a form section with theme-driven caption and control widths.","bestPractices":[{"guidance":true,"description":"Keep labelWidth consistent across the card and let controls move below the caption when space runs out."},{"guidance":false,"description":"Do not treat the visual caption as an accessible label association for every supplied control."}],"anatomy":[{"name":"Caption","required":true,"description":"The narrow label column, wrapping long text."},{"name":"Controls","required":true,"description":"Host content beside the caption or beneath it in a narrow layout."}],"props":[{"path":"label","type":"string","required":true,"description":"The row's label, in the narrow column at its start."},{"path":"labelWidth","type":"number | undefined","required":false,"description":"The label column's width in pixels, 90 by default; a longer label wraps in it."},{"path":"children","type":"ReactNode","required":true,"description":"The row's controls."}],"examples":[{"name":"Default","description":"A label beside one control, centred on it.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/SideLabelRow.stories.tsx"},{"name":"Rows","description":"Several rows one under the other: the labels line up in their column.","source":"export const Rows: Story = {\n render: () => (\n <VStack>\n <SideLabelRow label=\"Pick one\">{control}</SideLabelRow>\n <SideLabelRow label=\"Where\">{control}</SideLabelRow>\n <SideLabelRow label=\"And\">{control}</SideLabelRow>\n </VStack>\n ),\n};","file":"packages/ui/src/molecules/SideLabelRow.stories.tsx"},{"name":"Interactive","description":"A control the user types in.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(\"\", (value, set) => (\n <SideLabelRow {...args}>\n <TextInput label=\"Name\" isLabelHidden size=\"md\" value={value} onChange={set} />\n </SideLabelRow>\n )),\n};","file":"packages/ui/src/molecules/SideLabelRow.stories.tsx"},{"name":"TallContent","description":"Content taller than one line: the label stays on the first.","source":"export const TallContent: Story = {\n args: {\n children: (\n <>\n {control}\n {control}\n </>\n ),\n },\n};","file":"packages/ui/src/molecules/SideLabelRow.stories.tsx"},{"name":"LongLabel","description":"A label longer than its column wraps in it.","source":"export const LongLabel: Story = { args: { label: \"A label much longer than its column\" } };","file":"packages/ui/src/molecules/SideLabelRow.stories.tsx"},{"name":"WideLabel","description":"A wider label column.","source":"export const WideLabel: Story = { args: { label: \"Pick one option\", labelWidth: 140 } };","file":"packages/ui/src/molecules/SideLabelRow.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame the controls move under the label.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <SideLabelRow {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/SideLabelRow.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <SideLabelRow {...args} />\n </Case>\n <Case caption=\"Long label\">\n <SideLabelRow {...args} label=\"A label much longer than its column\" />\n </Case>\n <Case caption=\"Narrow\">\n <Narrow>\n <SideLabelRow {...args} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/SideLabelRow.stories.tsx"}],"primaryExample":"Default"},{"name":"SplitButton","level":"molecules","category":"Inputs","keywords":["button","menu","actions","split"],"import":"import { SplitButton } from \"@antscorp/ui\";","description":"Pair a primary action with a menu of related alternatives.","usage":"Pair a primary action with a menu of related alternatives. With no items, only the main button is shown.","bestPractices":[{"guidance":true,"description":"Use isMainDisabled to block only the primary action, and labels.menu to name the menu arrow."},{"guidance":false,"description":"Do not assume isLoading disables the alternatives menu; it affects the main action while the menu stays usable."}],"anatomy":[{"name":"Main button","required":true,"description":"The default action, optionally busy or disabled."},{"name":"Menu button","required":false,"description":"A named arrow or host glyph opening the alternatives."},{"name":"Menu","required":false,"description":"Related actions, each with its own optional disabled state."}],"accessibility":["label names the main action and labels.menu names the icon-only menu button."],"props":[{"path":"label","type":"string","required":true,"description":"The main button's text."},{"path":"onClick","type":"() => void","required":true,"description":"Called when the main button is pressed."},{"path":"labels","type":"{ readonly menu: string; }","required":true,"description":"The texts the view shows; the host translates them."},{"path":"labels.menu","type":"string","required":false,"requiredWhenParent":true,"description":"The arrow's accessible name, e.g. \"More options\"; it shows only an icon."},{"path":"items","type":"readonly SplitButtonItem[]","required":true,"description":"The menu's entries; without any, only the main button shows."},{"path":"items[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The entry's text."},{"path":"items[].onSelect","type":"() => void","required":false,"requiredWhenParent":true,"description":"Called when the entry is chosen."},{"path":"items[].isDisabled","type":"boolean","required":false,"description":"Not choosable."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The buttons' height, `md` by default."},{"path":"isDisabled","type":"boolean","required":false,"description":"Neither the button nor the menu can be used."},{"path":"isMainDisabled","type":"boolean","required":false,"description":"Disables only the main button; the menu stays usable."},{"path":"isLoading","type":"boolean","required":false,"description":"The main button shows a spinner and is announced as busy; the menu is not affected."},{"path":"isRound","type":"boolean","required":false,"description":"Rounds its two ends into a pill; the inner corners stay square."},{"path":"variant","type":"\"primary\" | \"secondary\" | undefined","required":false,"description":"The button's weight, `primary` by default."},{"path":"icons","type":"{ readonly menu?: ReactNode; } | undefined","required":false,"description":"The glyphs of inner parts: `menu`, the menu button's, a down chevron by default."},{"path":"icons.menu","type":"ReactNode","required":false,"description":""},{"path":"maxWidth","type":"number | undefined","required":false,"description":"The widest it gets, in pixels: a longer label is cut short with an ellipsis and shows whole\nin a tooltip. Its content's width when absent."}],"examples":[{"name":"Default","description":"The main button with its menu arrow; open the menu to pick an entry.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"Small","description":"The `sm` height, for a dense toolbar.","source":"export const Small: Story = { args: { size: \"sm\" } };","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"Secondary","description":"A quieter weight.","source":"export const Secondary: Story = { args: { variant: \"secondary\" } };","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"NoItems","description":"Without entries only the main button shows.","source":"export const NoItems: Story = { args: { items: [] } };","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"MenuWithDisabledItems","description":"An entry that cannot be chosen.","source":"export const MenuWithDisabledItems: Story = {\n args: {\n items: [\n { label: \"Save as a copy\", onSelect: fn<() => void>() },\n { label: \"Save as a template\", onSelect: fn<() => void>(), isDisabled: true },\n ],\n },\n};","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"Disabled","description":"Not usable, e.g. while there is nothing to save.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"LongContent","description":"Many entries with long labels.","source":"export const LongContent: Story = {\n args: {\n label: \"Save all of the changes\",\n items: Array.from({ length: 12 }, (_, index) => ({\n label: `Save the changes under the name number ${index + 1} in a new place`,\n onSelect: fn<() => void>(),\n })),\n },\n};","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"SingleItem","description":"A single entry.","source":"export const SingleItem: Story = {\n args: { items: [{ label: \"Save as a copy\", onSelect: fn<() => void>() }] },\n};","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <SplitButton {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"Loading","description":"The main button busy with a spinner; the menu stays usable.","source":"export const Loading: Story = { args: { isLoading: true } };","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"MainDisabled","description":"Only the main button is disabled, e.g. nothing to save, while the menu stays usable.","source":"export const MainDisabled: Story = { args: { isMainDisabled: true } };","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"MenuIcon","description":"Another glyph on the menu button, e.g. \"more\" for a menu of actions on an item.","source":"export const MenuIcon: Story = {\n args: { variant: \"secondary\", icons: { menu: <Icon icon=\"moreHorizontal\" size=\"sm\" /> } },\n};","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"Round","description":"A pill with both ends rounded.","source":"export const Round: Story = { args: { isRound: true, variant: \"secondary\" } };","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <SplitButton {...args} />\n </Case>\n <Case caption=\"Secondary\">\n <SplitButton {...args} variant=\"secondary\" />\n </Case>\n <Case caption=\"No items\">\n <SplitButton {...args} items={[]} />\n </Case>\n <Case caption=\"Disabled\">\n <SplitButton {...args} isDisabled />\n </Case>\n <Case caption=\"Small\">\n <SplitButton {...args} size=\"sm\" />\n </Case>\n <Case caption=\"Round\">\n <SplitButton {...args} isRound />\n </Case>\n <Case caption=\"Loading\">\n <SplitButton {...args} isLoading />\n </Case>\n <Case caption=\"Main disabled\">\n <SplitButton {...args} isMainDisabled />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"MaxWidth","description":"Capped at 160px: the long label is cut short and shows whole in a tooltip on hover.","source":"export const MaxWidth: Story = {\n args: { label: \"Variant 3 with a name far too long to fit\", maxWidth: 160 },\n};","file":"packages/ui/src/molecules/SplitButton.stories.tsx"}],"primaryExample":"Default"},{"name":"StatusButton","level":"molecules","category":"Feedback","keywords":["status","dot","button","connection"],"import":"import { StatusButton } from \"@antscorp/ui\";","description":"Combine an action with a small dot showing a related state.","usage":"Combine an action with a small dot showing a related state. Use a plain button when no state needs to be communicated.","bestPractices":[{"guidance":true,"description":"Translate labels.status to explain the dot independently of its tone."},{"guidance":false,"description":"Do not rely on the dot's color alone to convey the state."}],"anatomy":[{"name":"Button text","required":true,"description":"The action supplied by label."},{"name":"Status dot","required":true,"description":"A named dot in the supplied status tone."},{"name":"Icon","required":false,"description":"Host-supplied leading content."}],"accessibility":["labels.status names the status dot and label names the action."],"props":[{"path":"label","type":"string","required":true,"description":"Accessible label for the button (required for accessibility).\nRendered as visible text by default. When `isIconOnly` is true,\nused as aria-label instead."},{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether the button is disabled."},{"path":"icon","type":"ReactNode","required":false,"description":"Icon element rendered before the label text."},{"path":"status","type":"keyof StatusDotVariantMap","required":true,"description":"The dot's tone: neutral, accent, success, warning or error."},{"path":"labels","type":"{ readonly status: string; }","required":true,"description":"The texts the view shows; the host translates them."},{"path":"labels.status","type":"string","required":false,"requiredWhenParent":true,"description":"What the dot means, read out for it, e.g. \"Connected\"."},{"path":"onClick","type":"() => void","required":true,"description":"Called when the button is pressed."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The button's height, `md` by default."}],"examples":[{"name":"Default","description":"Connected: a green dot.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/StatusButton.stories.tsx"},{"name":"Neutral","description":"Neutral: off.","source":"export const Neutral: Story = { args: { status: \"neutral\", labels: { status: \"Off\" } } };","file":"packages/ui/src/molecules/StatusButton.stories.tsx"},{"name":"Warning","description":"Warning: connecting.","source":"export const Warning: Story = { args: { status: \"warning\", labels: { status: \"Connecting\" } } };","file":"packages/ui/src/molecules/StatusButton.stories.tsx"},{"name":"Failed","description":"Error: failed.","source":"export const Failed: Story = { args: { status: \"error\", labels: { status: \"Failed\" } } };","file":"packages/ui/src/molecules/StatusButton.stories.tsx"},{"name":"Interactive","description":"Each press moves the dot to the next tone.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(0, (index, set) => (\n <StatusButton\n {...args}\n status={tones[index % tones.length] ?? \"neutral\"}\n labels={{ status: tones[index % tones.length] ?? \"neutral\" }}\n onClick={() => {\n args.onClick();\n set(index + 1);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/StatusButton.stories.tsx"},{"name":"Disabled","description":"Not pressable.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/StatusButton.stories.tsx"},{"name":"Medium","description":"The `md` height.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/molecules/StatusButton.stories.tsx"},{"name":"LongLabel","description":"A long label.","source":"export const LongLabel: Story = {\n args: { label: \"Connect to the assistant that runs on this computer\" },\n};","file":"packages/ui/src/molecules/StatusButton.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: { label: \"Connect to the assistant that runs on this computer\" },\n render: (args) => (\n <Narrow>\n <StatusButton {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/StatusButton.stories.tsx"},{"name":"AllStates","description":"Each status tone one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n {tones.map((tone) => (\n <Case key={tone} caption={tone}>\n <StatusButton {...args} status={tone} labels={{ status: tone }} />\n </Case>\n ))}\n <Case caption=\"Disabled\">\n <StatusButton {...args} isDisabled />\n </Case>\n <Case caption=\"Medium\">\n <StatusButton {...args} size=\"md\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/StatusButton.stories.tsx"}],"primaryExample":"Default"},{"name":"TagSelect","level":"molecules","category":"Selection","keywords":["tag","color","choice","popover"],"import":"import { TagSelect } from \"@antscorp/ui\";","description":"Choose one value through a small colored tag and a popover of tags.","usage":"Choose one value through a small colored tag and a popover of tags. Use Select for a conventional field with a larger options list.","bestPractices":[{"guidance":true,"description":"Keep option labels concise and provide label for the tag button and title for its popover."},{"guidance":false,"description":"Do not use the colored tag alone to explain the selected value; its text must identify it."}],"anatomy":[{"name":"Picked tag","required":true,"description":"The selected label, or the raw unknown value in blue."},{"name":"Popover heading","required":false,"description":"The supplied title while the tag list is open."},{"name":"Tag choices","required":false,"description":"Wrapping tag buttons that choose a value and close the popover."}],"accessibility":["label names the tag button; read-only mode shows the tag without a button."],"props":[{"path":"label","type":"string","required":true,"description":"Names the tag button for assistive tech, such as \"Kind of the value\"; not shown."},{"path":"title","type":"string","required":true,"description":"The heading of the popover over the tags, such as \"Kind\"."},{"path":"options","type":"readonly TagSelectOption<string>[]","required":true,"description":"The tags to pick from, in order."},{"path":"options[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What `onChange` gets when the tag is picked."},{"path":"options[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The tag's text; cut short with an ellipsis past 70px, and shown whole in a tooltip."},{"path":"options[].tone","type":"TagTone","required":false,"requiredWhenParent":true,"description":"The tag's fill."},{"path":"value","type":"string","required":true,"description":"The picked tag's value; one no option has shows as its own text, in the blue tone."},{"path":"onChange","type":"(value: string) => void","required":true,"description":"Called with a tag's value when it is picked in the popover; the popover then closes."},{"path":"isDisabled","type":"boolean","required":false,"description":"Blocks the tag; the control holding it dims it (a `TreeSelect` frame), so it is dimmed once."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows the picked tag, not dimmed; it does not open the popover."}],"examples":[{"name":"Default","description":"The first tag picked; pressing it opens the tags, and a press on one picks it.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/TagSelect.stories.tsx"},{"name":"Open","description":"The popover open, the picked tag pressed.","source":"export const Open: Story = {\n play: async ({ canvasElement }) => {\n await userEvent.click(\n within(canvasElement).getByRole(\"button\", { name: \"Format of the value\" }),\n );\n },\n};","file":"packages/ui/src/molecules/TagSelect.stories.tsx"},{"name":"Tones","description":"Each tone: cyan, blue and green.","source":"export const Tones: Story = {\n render: (args) => (\n <Gallery>\n {OPTIONS.map((option) => (\n <Case key={option.value} caption={`${option.tone}: ${option.label}`}>\n <TagSelect {...args} value={option.value} />\n </Case>\n ))}\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/TagSelect.stories.tsx"},{"name":"LongLabel","description":"A tag too long for 70px: cut short, whole in a tooltip, here and in the popover.","source":"export const LongLabel: Story = { args: { options: LONG, value: \"very-long\" } };","file":"packages/ui/src/molecules/TagSelect.stories.tsx"},{"name":"SingleOption","description":"A single tag to pick.","source":"export const SingleOption: Story = { args: { options: OPTIONS.slice(0, 1) } };","file":"packages/ui/src/molecules/TagSelect.stories.tsx"},{"name":"UnknownValue","description":"A value no option has: its own text, in the blue tone.","source":"export const UnknownValue: Story = { args: { value: \"other\" } };","file":"packages/ui/src/molecules/TagSelect.stories.tsx"},{"name":"InTreeSelect","description":"As a `TreeSelect`'s `startAddon`: one underlined control, the tag, a line, the select.","source":"export const InTreeSelect: Story = { render: (args) => framed(args) };","file":"packages/ui/src/molecules/TagSelect.stories.tsx"},{"name":"InTreeSelectStates","description":"As a `TreeSelect`'s `startAddon`, each state: the underline red on error, dimmed, read-only.","source":"export const InTreeSelectStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Error\">{framed(args, { error: \"Field is required\" })}</Case>\n <Case caption=\"Disabled\">{framed(args, { isDisabled: true })}</Case>\n <Case caption=\"Read-only\">{framed(args, { isReadOnly: true })}</Case>\n <Case caption=\"Narrow\">\n <Narrow>{framed(args, { width: \"100%\" })}</Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/TagSelect.stories.tsx"},{"name":"Disabled","description":"Blocked; on its own not dimmed, as the control holding it dims it.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/TagSelect.stories.tsx"},{"name":"ReadOnly","description":"The tag, not dimmed; it does not open.","source":"export const ReadOnly: Story = { args: { isReadOnly: true, value: \"long\" } };","file":"packages/ui/src/molecules/TagSelect.stories.tsx"},{"name":"InNarrow","description":"In a drawer-width frame.","source":"export const InNarrow: Story = {\n render: (args) => (\n <Narrow>\n <TagSelect {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/TagSelect.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Picked\">\n <TagSelect {...args} />\n </Case>\n <Case caption=\"Long label\">\n <TagSelect {...args} options={LONG} value=\"very-long\" />\n </Case>\n <Case caption=\"Unknown value\">\n <TagSelect {...args} value=\"other\" />\n </Case>\n <Case caption=\"Disabled\">\n <TagSelect {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <TagSelect {...args} isReadOnly value=\"long\" />\n </Case>\n <Case caption=\"In a tree select\">{framed(args)}</Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/TagSelect.stories.tsx"}],"primaryExample":"Default"},{"name":"TextAction","level":"atoms","category":"Inputs","keywords":["action","text","button","chevron"],"import":"import { TextAction } from \"@antscorp/ui\";","description":"Offer a quiet action styled as bold accent text, optionally with an icon or menu chevron.","usage":"Offer a quiet action styled as bold accent text, optionally with an icon or menu chevron. Use AddButton for an add action with the kit's plus glyph.","bestPractices":[{"guidance":true,"description":"Pass label in normal case and let case control its appearance; omit onClick when a Popover wrapper owns activation."},{"guidance":false,"description":"Do not treat this action as navigation; it renders a button without an href."}],"anatomy":[{"name":"Label","required":true,"description":"The translated action text."},{"name":"Leading icon","required":false,"description":"Host-supplied content before the label."},{"name":"Chevron","required":false,"description":"An indicator for an action that opens a menu."}],"accessibility":["The text supplies the button's accessible name."],"props":[{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether the link is disabled.\nA disabled link renders as a plain anchor without an href (and without\ntarget/rel/onClick), so it cannot navigate or run onClick. It keeps a tab\nstop and is announced as a disabled link."},{"path":"label","type":"string","required":true,"description":"What it does, such as \"Remove all\"; shown in capitals by default, so pass it in normal case."},{"path":"icon","type":"ReactNode","required":false,"description":"A glyph before the text, in the text's colour, such as an Astryx `Icon` of a bin for a removal."},{"path":"case","type":"\"sentence\" | \"upper\" | undefined","required":false,"description":"`upper` (default) shows the label in capitals, such as \"REMOVE ALL\"; `sentence` as given."},{"path":"onClick","type":"(() => void) | undefined","required":false,"description":"Called when it is pressed. Leave it out when a wrapper handles the presses, such as an Astryx\n`Popover` it is the trigger of (the popover finds the button and listens to it)."},{"path":"hasChevron","type":"boolean","required":false,"description":"A down chevron after the text, for an action that opens a menu."}],"examples":[{"name":"Default","description":"Bold accent capitals with no frame; the label is passed in normal case.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/TextAction.stories.tsx"},{"name":"WithChevron","description":"A chevron after the text, for an action that opens a menu.","source":"export const WithChevron: Story = { args: { label: \"Create new\", hasChevron: true } };","file":"packages/ui/src/atoms/TextAction.stories.tsx"},{"name":"WithIcon","description":"A glyph before the text and the label as given (\"Remove\" with a glyph).","source":"export const WithIcon: Story = {\n args: { label: \"Remove\", case: \"sentence\", icon: <Icon icon=\"close\" /> },\n};","file":"packages/ui/src/atoms/TextAction.stories.tsx"},{"name":"Disabled","description":"Dimmed and not pressable, e.g. nothing to remove.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/atoms/TextAction.stories.tsx"},{"name":"Interactive","description":"Pressing it counts.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(0, (count, set) => (\n <VStack gap={1}>\n <TextAction\n {...args}\n onClick={() => {\n args.onClick?.();\n set(count + 1);\n }}\n />\n <Text type=\"supporting\">Pressed {count} times</Text>\n </VStack>\n )),\n};","file":"packages/ui/src/atoms/TextAction.stories.tsx"},{"name":"LongText","description":"A long label stays on one line.","source":"export const LongText: Story = {\n args: { label: \"Create a new item from what you picked\", hasChevron: true },\n};","file":"packages/ui/src/atoms/TextAction.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <TextAction {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/TextAction.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <TextAction {...args} />\n </Case>\n <Case caption=\"With chevron\">\n <TextAction {...args} label=\"Create new\" hasChevron />\n </Case>\n <Case caption=\"With icon, sentence case\">\n <TextAction {...args} label=\"Remove\" case=\"sentence\" icon={<Icon icon=\"close\" />} />\n </Case>\n <Case caption=\"With icon, disabled\">\n <TextAction\n {...args}\n label=\"Remove\"\n case=\"sentence\"\n icon={<Icon icon=\"close\" />}\n isDisabled\n />\n </Case>\n <Case caption=\"Disabled\">\n <TextAction {...args} isDisabled />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/TextAction.stories.tsx"}],"primaryExample":"Default"},{"name":"ToggleGrid","level":"molecules","category":"Selection","keywords":["toggle","grid","multiple","days"],"import":"import { ToggleGrid } from \"@antscorp/ui\";","description":"Choose several compact values in a grid of toggle buttons, such as numbered days or hours.","usage":"Choose several compact values in a grid of toggle buttons, such as numbered days or hours. Use CheckGroup for longer checkbox labels.","bestPractices":[{"guidance":true,"description":"Keep value controlled and accept changes in options order; use span for an option that should fill more tracks."},{"guidance":false,"description":"Do not use the grid for a single exclusive choice or assume toggling preserves click order."}],"anatomy":[{"name":"Group label","required":true,"description":"Names the set through FieldGroup."},{"name":"Toggle buttons","required":true,"description":"One pressed or unpressed button per option, with optional column spans."},{"name":"Error","required":false,"description":"A translated selection problem."}],"accessibility":["label names the group; option labels name the toggle buttons and their pressed states show the pick."],"props":[{"path":"label","type":"string","required":true,"description":"Names the group; read out before each field in it."},{"path":"isDisabled","type":"boolean","required":false,"description":"Disables fields through the unframed fieldset; with `frame=\"card\"`, disable children yourself."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the group as a whole, already in the user's language."},{"path":"options","type":"readonly ToggleGridOption<string>[]","required":true,"description":"The buttons, in order."},{"path":"options[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What the picked list holds for this button."},{"path":"options[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The button's text."},{"path":"options[].span","type":"number | undefined","required":false,"description":"How many columns the button spans; one by default, e.g. to let a last option fill a row."},{"path":"columns","type":"number | undefined","required":false,"description":"How many 40px columns the grid has; one column of Astryx's default without it."},{"path":"value","type":"readonly string[]","required":true,"description":"The picked values."},{"path":"onChange","type":"(value: string[]) => void","required":true,"description":"Called with the picked values, in the order of `options`."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The buttons' size, `md` by default."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows the value, not dimmed, and ignores changes."}],"examples":[{"name":"Default","description":"Some buttons on, with \"End of month\" spanning the last row.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"},{"name":"NoneSelected","description":"None picked.","source":"export const NoneSelected: Story = { args: { value: [] } };","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"},{"name":"AllSelected","description":"Every button on.","source":"export const AllSelected: Story = { args: { value: days.map((day) => day.value) } };","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"},{"name":"Interactive","description":"Clicking toggles buttons; the pick stays in the order of the options.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<string[]>([], (value, set) => (\n <ToggleGrid\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"},{"name":"WithError","description":"With a problem with the pick.","source":"export const WithError: Story = { args: { value: [], error: \"Pick at least one day.\" } };","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"},{"name":"Disabled","description":"Read-only.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"},{"name":"ReadOnly","description":"Shows the value, not dimmed, and blocks changes.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"},{"name":"Medium","description":"The `md` size, for a form whose other controls are `md`.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"},{"name":"SingleOption","description":"Just a few options (a single option, in four columns).","source":"export const SingleOption: Story = { args: { options: days.slice(0, 1), columns: 4, value: [] } };","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"},{"name":"LongContent","description":"A long group label and long option text.","source":"export const LongContent: Story = {\n args: {\n label: \"The days of the month on which the message should go out to everyone\",\n options: [\n { value: \"a\", label: \"First working day\", span: 3 },\n { value: \"b\", label: \"Last working day\", span: 3 },\n ],\n columns: 6,\n value: [\"a\"],\n },\n};","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, where seven 40px columns just fit.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <ToggleGrid {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Some selected\">\n <ToggleGrid {...args} />\n </Case>\n <Case caption=\"None selected, error\">\n <ToggleGrid {...args} value={[]} error=\"Pick at least one day.\" />\n </Case>\n <Case caption=\"Disabled\">\n <ToggleGrid {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <ToggleGrid {...args} isReadOnly />\n </Case>\n <Case caption=\"Medium\">\n <ToggleGrid {...args} size=\"md\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"}],"primaryExample":"Default"},{"name":"TokenPickerDialog","level":"organisms","category":"Overlays","keywords":["token","picker","definition","dialog"],"import":"import { TokenPickerDialog } from \"@antscorp/ui\";","description":"Compose a token definition from a group, a leaf, a display name and fallback text.","usage":"Compose a token definition from a group, a leaf, a display name and fallback text. Use TokenTextField to edit the resulting raw token string inline.","bestPractices":[{"guidance":true,"description":"Keep the definition controlled and reset its draft and items when onGroupChange selects a different group."},{"guidance":false,"description":"Do not expect confirmation while loading, without a picked item or with a blank display name; read-only mode hides confirmation."}],"anatomy":[{"name":"Dialog heading","required":true,"description":"The supplied title."},{"name":"Group and item selectors","required":true,"description":"A group choice and a searchable flat or two-level leaf choice, with a loading skeleton when needed."},{"name":"Fallback text","required":true,"description":"The controlled defaultValue field."},{"name":"Display name","required":true,"description":"A required name, defaulted from the picked leaf until customized."},{"name":"Actions","required":true,"description":"Cancel and, when editable, Insert or Save according to mode."}],"accessibility":["Provide the translated group, item, displayName and defaultValue field labels; the dialog uses Astryx Dialog behavior."],"props":[{"path":"isOpen","type":"boolean","required":true,"description":"Whether the dialog is open."},{"path":"onClose","type":"() => void","required":true,"description":"Closes or cancels the dialog."},{"path":"title","type":"string","required":true,"description":"Dialog heading."},{"path":"groups","type":"readonly { readonly value: string; readonly label: string; }[]","required":true,"description":"Available groups."},{"path":"groups[].value","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"groups[].label","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"group","type":"string | null","required":true,"description":"Selected group key."},{"path":"onGroupChange","type":"(value: string) => void","required":true,"description":"Changes the group; the host supplies its items and resets its draft."},{"path":"items","type":"readonly TreeItem[]","required":true,"description":"Flat leaves or two-level groups of leaves."},{"path":"items[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What is picked with this leaf; unique across the tree."},{"path":"items[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The row's text."},{"path":"items[].isDisabled","type":"boolean","required":false,"description":"Shown dimmed; cannot be picked, nor opened when a group."},{"path":"items[].children","type":"readonly TreeItem[] | undefined","required":false,"description":"Makes the item a group, even when empty: it opens and closes and is never picked. Two levels\nonly: the leaves' own `children` are not shown."},{"path":"itemKey","type":"(item: TreeItem) => string","required":true,"description":"Extracts a leaf's stable key, matching its value."},{"path":"isLoading","type":"boolean","required":false,"description":"Items are loading; a timed skeleton replaces the item control."},{"path":"value","type":"TokenPickerValue","required":true,"description":"The controlled definition draft."},{"path":"value.item","type":"string | null","required":false,"requiredWhenParent":true,"description":"Picked leaf key, or null before a pick."},{"path":"value.displayName","type":"string","required":false,"requiredWhenParent":true,"description":"The editable visible name."},{"path":"value.defaultValue","type":"string","required":false,"requiredWhenParent":true,"description":"Fallback text supplied to the host."},{"path":"onChange","type":"(value: TokenPickerValue) => void","required":true,"description":"Changes any part of the definition draft."},{"path":"onConfirm","type":"() => void","required":true,"description":"Confirms a complete definition."},{"path":"mode","type":"\"add\" | \"edit\"","required":true,"description":"Add uses Insert; edit uses Save and the supplied prefilled draft."},{"path":"size","type":"\"md\" | \"sm\"","required":false,"description":"Size of the dialog's controls."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims and blocks all editing controls."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows the draft without editing or confirmation affordances."},{"path":"labels","type":"{ readonly search: string; readonly noMatch: string; readonly clear: string; readonly value?: string | undefined; readonly loadMoreError?: string | undefined; readonly retry?: string | undefined; readonly loading?: string | undefined; } & { readonly group: string; readonly item: string; readonly displayName: string; readonly defaultValue: string; readonly placeholder: string; readonly insert: string; readonly save: string; readonly cancel: string; }","required":true,"description":"Translated field, search and action labels."}],"examples":[{"name":"Default","description":"Groups and an empty definition draft.","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/TokenPickerDialog.stories.tsx"},{"name":"Interactive","description":"Controlled picks, display name and fallback text.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(args.value, (value, set) => (\n <TokenPickerDialog {...args} value={value} onChange={set} />\n )),\n};","file":"packages/ui/src/organisms/TokenPickerDialog.stories.tsx"},{"name":"Search","description":"Search a two-level item tree.","source":"export const Search: Story = {};","file":"packages/ui/src/organisms/TokenPickerDialog.stories.tsx"},{"name":"Empty","description":"No items available.","source":"export const Empty: Story = { args: { items: [] } };","file":"packages/ui/src/organisms/TokenPickerDialog.stories.tsx"},{"name":"Loading","description":"Skeleton while the items load.","source":"export const Loading: Story = { args: { isLoading: true } };","file":"packages/ui/src/organisms/TokenPickerDialog.stories.tsx"},{"name":"EditMode","description":"Edit keeps the supplied name and default value.","source":"export const EditMode: Story = {\n args: { mode: \"edit\", value: { item: \"name\", displayName: \"Recipient\", defaultValue: \"friend\" } },\n};","file":"packages/ui/src/organisms/TokenPickerDialog.stories.tsx"},{"name":"Disabled","description":"All editing controls are disabled.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/organisms/TokenPickerDialog.stories.tsx"},{"name":"ReadOnly","description":"Read-only draft without Save.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/organisms/TokenPickerDialog.stories.tsx"},{"name":"LongContent","description":"Long labels in the group and item controls.","source":"export const LongContent: Story = {\n args: {\n items: [\n {\n value: \"long\",\n label: \"A very long item name that should fit the control and remain readable\",\n },\n ],\n },\n};","file":"packages/ui/src/organisms/TokenPickerDialog.stories.tsx"},{"name":"InNarrowFrame","description":"A real narrow viewport exercises the modal's responsive columns.","source":"export const InNarrowFrame: Story = {\n render: (_args, { id }) => (\n // A raw iframe gives top-layer modal content its own viewport.\n <iframe\n title=\"Token picker at 280px\"\n width={280}\n height={600}\n src={`./iframe.html?id=${id.split(\"--\")[0]}--edit-mode&viewMode=story`}\n />\n ),\n};","file":"packages/ui/src/organisms/TokenPickerDialog.stories.tsx"},{"name":"AllStates","description":"Principal states, each with its own viewport and focus.","source":"export const AllStates: Story = {\n render: (_args, { id }) => (\n <Gallery>\n {[\"default\", \"empty\", \"loading\", \"edit-mode\", \"disabled\", \"read-only\", \"long-content\"].map(\n (story) => (\n <Case key={story} caption={story}>\n {/* A raw iframe isolates each modal's top layer from the other previews. */}\n <iframe\n title={story}\n width=\"100%\"\n height={600}\n src={`./iframe.html?id=${id.split(\"--\")[0]}--${story}&viewMode=story`}\n />\n </Case>\n ),\n )}\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/TokenPickerDialog.stories.tsx"}],"primaryExample":"Default"},{"name":"TokenTextField","level":"molecules","category":"Inputs","keywords":["text","tokens","editor","draft"],"import":"import { TokenTextField } from \"@antscorp/ui\";","description":"Edit a controlled string containing atomic token pills.","usage":"Edit a controlled string containing atomic token pills. pattern returns a fresh global regular expression matching whole tokens and describe supplies their appearance. Local edits commit on blur or single-line Enter; an outside value replaces the draft.","bestPractices":[{"guidance":true,"description":"Keep raw tokens in the string and use onDraftChange only for draft feedback such as a live length counter."},{"guidance":false,"description":"Do not return a pattern that matches empty strings or treat the visible token label as the stored value."}],"anatomy":[{"name":"Field label","required":true,"description":"Names the editable string."},{"name":"Editor","required":true,"description":"Text and atomic tokens; loads separately with a skeleton."},{"name":"Add button","required":false,"description":"Opens the host's picker through onAddClick."}],"accessibility":["Provide label and translated labels.add, labels.remove and labels.loading.","Use isReadOnly to hide editing affordances while keeping the value readable."],"related":[{"name":"CommitTextField","when":"Edit ordinary text without atomic token pills."}],"props":[{"path":"label","type":"string","required":true,"description":"Label text for the field (always rendered for accessibility)."},{"path":"isLabelHidden","type":"boolean","required":false,"default":"false","description":"Whether to visually hide the label and description (still accessible to screen readers)."},{"path":"description","type":"string","required":false,"description":"Description text displayed between the label and input.\nHidden when isLabelHidden is true."},{"path":"isRequired","type":"boolean","required":false,"default":"false","description":"Whether the field is required. Mutually exclusive with isOptional."},{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether the associated input is disabled."},{"path":"value","type":"string","required":true,"description":"The committed string; outside changes replace the local draft."},{"path":"onChange","type":"(value: string) => void","required":true,"description":"Commits the local draft on blur, or Enter in a single line."},{"path":"onDraftChange","type":"(value: string) => void","required":false,"description":"Observes local edits without committing them, e.g. for a live character counter."},{"path":"pattern","type":"() => RegExp","required":true,"description":"Returns a fresh global pattern matching whole tokens, without empty matches."},{"path":"describe","type":"(raw: string) => TokenLook","required":true,"description":"Describes a matched string without changing it."},{"path":"isMultiline","type":"boolean","required":false,"description":"Multiple lines; Enter inserts a line instead of committing."},{"path":"rows","type":"number","required":false,"description":"Minimum visible rows for multiple lines, one by default: the field grows as lines are added."},{"path":"maxRows","type":"number","required":false,"description":"Most visible rows for multiple lines before the text scrolls, ten by default."},{"path":"maxLength","type":"number","required":false,"description":"Fits insertions to this length (one per token); existing over-limit text may shrink."},{"path":"placeholder","type":"string","required":false,"description":"Text shown while empty."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Blocks edits without dimming and hides editing affordances."},{"path":"error","type":"string","required":false,"description":"A translated validation problem."},{"path":"size","type":"\"md\" | \"sm\"","required":false,"description":"Control height, medium by default."},{"path":"onTokenClick","type":"(raw: string, replace: (text: string) => void) => void","required":false,"description":"Opens a picker; its callback replaces this token even after focus moves."},{"path":"onAddClick","type":"(insert: (text: string) => void) => void","required":false,"description":"Opens a picker; its callback inserts at the retained selection."},{"path":"onCopy","type":"(text: string) => Record<string, string>","required":false,"description":"Clipboard representations of the selected raw text; plain text by default."},{"path":"onPaste","type":"(data: DataTransfer, insert: (text: string) => void) => boolean","required":false,"description":"Returns true when the host handled this paste through insert."},{"path":"labels","type":"{ readonly add: string; readonly remove: (label: string) => string; readonly loading: string; }","required":true,"description":"Names of the field's editing and loading affordances."},{"path":"labels.add","type":"string","required":false,"requiredWhenParent":true,"description":"Add button's accessible name."},{"path":"labels.remove","type":"(label: string) => string","required":false,"requiredWhenParent":true,"description":"Remove button's accessible name, derived from the token label."},{"path":"labels.loading","type":"string","required":false,"requiredWhenParent":true,"description":"Loading status's accessible name."},{"path":"icons","type":"{ readonly add: IconProps[\"icon\"]; }","required":true,"description":"The host's add glyph."},{"path":"icons.add","type":"`${string}:${string}` | IconName | IconType","required":false,"requiredWhenParent":true,"description":""}],"examples":[{"name":"Default","description":"Filled with a token; typing commits on blur or Enter.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"Empty","description":"Empty field with a hint.","source":"export const Empty: Story = { args: { value: \"\", placeholder: \"Enter text\" } };","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"ManyTokens","description":"Many atomic pills on multiple lines.","source":"export const ManyTokens: Story = {\n args: {\n value: Array.from({ length: 12 }, (_, i) => `#{item ${i}}`).join(\" \"),\n isMultiline: true,\n },\n};","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"MaxRows","description":"Many lines: the field grows to `maxRows` (three here), then scrolls with the thin hover bar.","source":"export const MaxRows: Story = {\n args: {\n value: Array.from({ length: 8 }, (_, i) => `Line ${i + 1} #{item ${i}}`).join(\"\\n\"),\n isMultiline: true,\n rows: 2,\n maxRows: 3,\n },\n};","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"LongText","description":"Long ordinary text and a long token label.","source":"export const LongText: Story = {\n args: { value: `${\"Some long text \".repeat(20)}#{A very long label that must be truncated}` },\n};","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"UnknownToken","description":"Unknown token with an error outline.","source":"export const UnknownToken: Story = {\n args: { describe: (raw) => ({ label: raw, tone: \"red\", state: \"unknown\" }) },\n};","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"ReadOnlyToken","description":"An opaque token cannot be opened or removed with its button.","source":"export const ReadOnlyToken: Story = {\n args: { describe: (raw) => ({ label: raw, tone: \"pink\", state: \"readOnly\" }) },\n};","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"Disabled","description":"Disabled editor and add button.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"ReadOnly","description":"Read-only value without editing affordances.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"SingleLine","description":"A single line strips pasted line breaks.","source":"export const SingleLine: Story = {};","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"MaxLengthReached","description":"A token counts as one: this field has reached its limit.","source":"export const MaxLengthReached: Story = { args: { value: \"1234#{name}\", maxLength: 5 } };","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"Error","description":"A translated validation message.","source":"export const Error: Story = { args: { error: \"Check this text.\" } };","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"Required","description":"Required state is reflected by the field indicator and the editor's accessible attributes.","source":"export const Required: Story = { args: { isRequired: true } };","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"Narrow","description":"Drawer-width field.","source":"export const Narrow: Story = {\n render: (args) => (\n <NarrowFrame>\n <TokenTextField {...args} />\n </NarrowFrame>\n ),\n};","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"Interactive","description":"Controlled text and a picker that inserts at the retained caret.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(args.value, (value, setValue) =>\n withState<{ readonly insert: ((text: string) => void) | null }>(\n { insert: null },\n ({ insert }, setPicker) =>\n withState<TokenPickerValue>(\n { item: null, displayName: \"\", defaultValue: \"\" },\n (draft, setDraft) => (\n <>\n <TokenTextField\n {...args}\n value={value}\n onChange={setValue}\n onAddClick={(next) => {\n setPicker({ insert: next });\n }}\n />\n <TokenPickerDialog\n title=\"Pick a token\"\n isOpen={insert !== null}\n onClose={() => {\n setPicker({ insert: null });\n }}\n groups={[{ value: \"profile\", label: \"Profile\" }]}\n group=\"profile\"\n onGroupChange={fn<(value: string) => void>()}\n items={[{ value: \"name\", label: \"Name\" }]}\n itemKey={(item) => item.value}\n value={draft}\n onChange={setDraft}\n mode=\"add\"\n labels={{\n group: \"Group\",\n item: \"Item\",\n displayName: \"Display name\",\n defaultValue: \"Default value\",\n placeholder: \"Select an item\",\n search: \"Search\",\n clear: \"Clear\",\n noMatch: \"No items\",\n insert: \"Insert\",\n save: \"Save\",\n cancel: \"Cancel\",\n }}\n onConfirm={() => {\n insert?.(`#{${draft.displayName}}`);\n setPicker({ insert: null });\n }}\n />\n </>\n ),\n ),\n ),\n ),\n};","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"AllStates","description":"Main states together.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Empty\">\n <TokenTextField {...args} value=\"\" placeholder=\"Enter text\" />\n </Case>\n <Case caption=\"Filled\">\n <TokenTextField {...args} />\n </Case>\n <Case caption=\"Many tokens\">\n <TokenTextField\n {...args}\n isMultiline\n value={Array.from({ length: 12 }, (_, i) => `#{item ${i}}`).join(\" \")}\n />\n </Case>\n <Case caption=\"Long text\">\n <TokenTextField {...args} value={\"Some long text \".repeat(20)} />\n </Case>\n <Case caption=\"Unknown\">\n <TokenTextField\n {...args}\n describe={(raw) => ({ label: raw, tone: \"red\", state: \"unknown\" })}\n />\n </Case>\n <Case caption=\"Read-only token\">\n <TokenTextField\n {...args}\n describe={(raw) => ({ label: raw, tone: \"pink\", state: \"readOnly\" })}\n />\n </Case>\n <Case caption=\"Disabled\">\n <TokenTextField {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <TokenTextField {...args} isReadOnly />\n </Case>\n <Case caption=\"Length limit\">\n <TokenTextField {...args} value=\"1234#{name}\" maxLength={5} />\n </Case>\n <Case caption=\"Error\">\n <TokenTextField {...args} error=\"Check this text.\" />\n </Case>\n <Case caption=\"Narrow\">\n <NarrowFrame>\n <TokenTextField {...args} />\n </NarrowFrame>\n </Case>\n {tones.map((tone) => (\n <Case key={tone} caption={tone}>\n <TokenTextField {...args} describe={(raw) => ({ ...args.describe(raw), tone })} />\n </Case>\n ))}\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"}],"primaryExample":"Default"},{"name":"TransferLayout","level":"molecules","category":"Layout","keywords":["transfer","columns","scroll","slots"],"import":"import { TransferLayout } from \"@antscorp/ui\";","description":"Lay out available and picked content in two independently scrolling columns with fixed headers.","usage":"Lay out available and picked content in two independently scrolling columns with fixed headers. Use TransferPicker when the kit should also manage selection controls and search.","bestPractices":[{"guidance":true,"description":"Name both columns through labels and use startRef when the host observes paging in the start viewport."},{"guidance":false,"description":"Do not expect layout slots to implement filtering, picking or removal; those behaviors belong to the host."}],"anatomy":[{"name":"Start column","required":true,"description":"The start viewport with optional header, fixed title and footer."},{"name":"End column","required":true,"description":"The end viewport with an optional fixed title and header."},{"name":"Header strip","required":false,"description":"Host controls above the viewports; endHeader is omitted with isEndFullHeight."}],"accessibility":["labels.start and labels.end name the two content columns."],"props":[{"path":"start","type":"ReactNode","required":true,"description":"The start column's content, such as the rows to pick from; it scrolls."},{"path":"end","type":"ReactNode","required":true,"description":"The end column's content, such as the picked rows; it scrolls."},{"path":"startHeader","type":"ReactNode","required":false,"description":"The header strip over the start column, such as a search box; it does not scroll."},{"path":"startTitle","type":"ReactNode","required":false,"description":"The heading of the start column's list, such as its name and count; it stays while the list scrolls."},{"path":"endTitle","type":"ReactNode","required":false,"description":"The heading of the end column's list; it stays while the list scrolls."},{"path":"endHeader","type":"ReactNode","required":false,"description":"The header strip over the end column, such as a \"Remove all\"; ignored with `isEndFullHeight`."},{"path":"startFooter","type":"ReactNode","required":false,"description":"Content under the start column only, such as an action and its caption; none without it."},{"path":"isEndFullHeight","type":"boolean","required":false,"description":"The end column runs the frame's full height, over the header strip too (no `endHeader`)."},{"path":"height","type":"number | undefined","required":false,"description":"The columns' height in pixels, under the header strip; 300 by default."},{"path":"startRef","type":"RefObject<HTMLDivElement | null> | undefined","required":false,"description":"The start column's scrolling viewport, to watch its end (`useLoadMore`'s `rootRef`) or\nscroll it back to the top."},{"path":"labels","type":"{ readonly start: string; readonly end: string; }","required":true,"description":"The texts the layout shows; the host translates them."},{"path":"labels.start","type":"string","required":false,"requiredWhenParent":true,"description":"The start column's accessible name, such as \"Cities\"."},{"path":"labels.end","type":"string","required":false,"requiredWhenParent":true,"description":"The end column's accessible name, such as \"Picked cities\"."}],"examples":[{"name":"Default","description":"Options at the start, picks at the end, a header strip over each.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"},{"name":"Empty","description":"Nothing in either column: the frame keeps its height.","source":"export const Empty: Story = { args: { start: null, end: null } };","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"},{"name":"Interactive","description":"Ticking an option adds it to the end column; removing takes it away.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<readonly string[]>([], (picked, set) => (\n <TransferLayout\n {...args}\n start={\n <VStack gap={1.5}>\n {CITIES.map((city) => (\n <CheckRow\n key={city}\n label={city}\n value={picked.includes(city)}\n onChange={(isPicked) => {\n set(isPicked ? [...picked, city] : picked.filter((c) => c !== city));\n }}\n />\n ))}\n </VStack>\n }\n end={\n <VStack gap={1.5}>\n {picked.map((city) => (\n <RemovableRow\n key={city}\n label={city}\n isFramed\n labels={{ remove: `Remove ${city}` }}\n onRemove={() => {\n set(picked.filter((c) => c !== city));\n }}\n />\n ))}\n </VStack>\n }\n />\n )),\n};","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"},{"name":"ManyRows","description":"Many rows: each column scrolls on its own under its fixed header.","source":"export const ManyRows: Story = {\n args: { start: options([], 30), end: pickedRows(CITIES.slice(0, 20)) },\n};","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"},{"name":"WithTitles","description":"Titles over the lists: they stay put while the rows scroll under them.","source":"export const WithTitles: Story = {\n args: {\n startTitle: <Text weight=\"bold\">Cities (30)</Text>,\n endTitle: <Text weight=\"bold\">Picked cities (20)</Text>,\n start: options([], 30),\n end: pickedRows(CITIES.slice(0, 20)),\n },\n};","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"},{"name":"WithFooter","description":"A footer under the start column, with a line at its end.","source":"export const WithFooter: Story = { args: { startFooter: footer } };","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"},{"name":"EndFullHeight","description":"The end column spans the header strip, as when the end needs no header.","source":"export const EndFullHeight: Story = {\n args: { isEndFullHeight: true, end: pickedRows(CITIES.slice(0, 20)) },\n};","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"},{"name":"Loading","description":"Loading: skeletons in the rows' places, the headers real.","source":"export const Loading: Story = {\n args: {\n start: <RowSkeletons variant=\"check\" count={6} labels={{ loading: \"Loading\" }} />,\n end: <RowSkeletons variant=\"framed\" count={2} labels={{ loading: \"Loading\" }} />,\n },\n};","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"},{"name":"Short","description":"A lower frame.","source":"export const Short: Story = { args: { height: 160 } };","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame: the columns share the width evenly.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <TransferLayout {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"},{"name":"InBannerCard","description":"Inside a banner card with a shared header.","source":"export const InBannerCard: Story = {\n render: (args) => (\n <AccentCard title=\"Included items\" variant=\"banner\">\n <TransferLayout {...args} startFooter={footer} />\n </AccentCard>\n ),\n};","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <TransferLayout {...args} />\n </Case>\n <Case caption=\"With footer\">\n <TransferLayout {...args} startFooter={footer} />\n </Case>\n <Case caption=\"End spans header\">\n <TransferLayout {...args} isEndFullHeight />\n </Case>\n <Case caption=\"Empty\">\n <TransferLayout {...args} start={null} end={null} height={120} />\n </Case>\n <Case caption=\"In a banner card\">\n <AccentCard title=\"Included items\" variant=\"banner\">\n <TransferLayout {...args} startFooter={footer} />\n </AccentCard>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"}],"primaryExample":"Default"},{"name":"TransferPicker","level":"organisms","category":"Selection","keywords":["transfer","multiple","picked","paging"],"import":"import { TransferPicker } from \"@antscorp/ui\";","description":"Pick several options in two columns, keeping available rows and removable picks visible together.","usage":"Pick several options in two columns, keeping available rows and removable picks visible together. Use ChipListAddPopover when a compact chip list with an Apply draft is more appropriate.","bestPractices":[{"guidance":true,"description":"Supply labels.loadMoreError, labels.retry and onRetryLoadMore together for paging recovery. The error pauses automatic loading; clear it and set isLoadingMore while retrying, then update options and hasMore after success."},{"guidance":false,"description":"Do not treat select-all as only adding visible rows: when picks already exist, it clears the entire selection, including unknown picks."},{"guidance":true,"description":"Supply pickedOptions or pickedLabels for values absent from the loaded options and use onSearchChange when the host performs search."},{"guidance":false,"description":"Do not replace the current pick with only the visible options after search or paging; unknown picks are intentionally retained."}],"anatomy":[{"name":"Available column","required":true,"description":"Search, a title and checkbox rows, with optional details and select-all."},{"name":"Picked column","required":true,"description":"Framed picked rows with optional remove-all and row actions."},{"name":"Loading and paging rows","required":false,"description":"Skeletons or a failed-page retry row that preserve loaded content."},{"name":"Host slots","required":false,"description":"Toolbar, end header and footer content."},{"name":"Error","required":false,"description":"A translated selection problem below the picker."}],"accessibility":["Supply search, clearSearch, picked and remove labels; disabled and read-only states block picking while search remains usable."],"related":[{"name":"ChipListAddPopover","when":"Use compact removable chips and apply checkbox changes as one draft."}],"props":[{"path":"onLoadMore","type":"(() => void) | undefined","required":false,"description":"Called to load the next page; nothing loads without it."},{"path":"hasMore","type":"boolean","required":false,"description":"More pages are left to load."},{"path":"isLoadingMore","type":"boolean","required":false,"description":"A page is loading now, so none is asked for."},{"path":"label","type":"string","required":true,"description":"Names the list of options (the start column)."},{"path":"options","type":"readonly TransferPickerOption<string>[]","required":true,"description":"The options to pick from."},{"path":"options[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What the picked list holds for this option."},{"path":"options[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The option's text."},{"path":"options[].description","type":"string | undefined","required":false,"description":"A second line under the text, such as an id under a name."},{"path":"options[].meta","type":"string | undefined","required":false,"description":"A short value in a column at the end of the row, such as a count; `labels.meta` heads it."},{"path":"options[].metaInfo","type":"string | undefined","required":false,"description":"An explanation of `meta` in an (i) tip beside it, such as why a count is missing."},{"path":"options[].error","type":"string | undefined","required":false,"description":"A problem with the option once picked: its picked row shows it in the error tone."},{"path":"value","type":"readonly string[]","required":true,"description":"The picked values. A value missing from `options` stays, shown by its `pickedLabels` entry, or\nby the value itself without one (a skeleton row instead while `isPickedLoading`)."},{"path":"pickedLabels","type":"Readonly<Partial<Record<string, string>>> | undefined","required":false,"description":"Labels of picked values that `options` may lack, such as those not on a loaded page."},{"path":"pickedOptions","type":"readonly TransferPickerOption<string>[] | undefined","required":false,"description":"What the picked rows show of picked values, before `options` and `pickedLabels`: the details\n(description, meta, error) of picks the options lack, such as those no longer offered."},{"path":"pickedOptions[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What the picked list holds for this option."},{"path":"pickedOptions[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The option's text."},{"path":"pickedOptions[].description","type":"string | undefined","required":false,"description":"A second line under the text, such as an id under a name."},{"path":"pickedOptions[].meta","type":"string | undefined","required":false,"description":"A short value in a column at the end of the row, such as a count; `labels.meta` heads it."},{"path":"pickedOptions[].metaInfo","type":"string | undefined","required":false,"description":"An explanation of `meta` in an (i) tip beside it, such as why a count is missing."},{"path":"pickedOptions[].error","type":"string | undefined","required":false,"description":"A problem with the option once picked: its picked row shows it in the error tone."},{"path":"total","type":"number | undefined","required":false,"description":"How many options there are in all, shown after the list's name, such as \"Cities (120)\"."},{"path":"max","type":"number | undefined","required":false,"description":"The most values that can be picked: at that many, the unpicked boxes are blocked."},{"path":"startToolbar","type":"ReactNode","required":false,"description":"Controls in the start header, above the search, such as a filter of where the options come from."},{"path":"endHeader","type":"ReactNode","required":false,"description":"Controls in the end header, before \"Remove all\", such as how the picks combine."},{"path":"startFooter","type":"ReactNode","required":false,"description":"Content under the start column only, such as an action and its caption; none without it."},{"path":"isEndFullHeight","type":"boolean","required":false,"description":"The end column runs the frame's full height, over the header strip: no end header, its\n`endHeader` and \"Remove all\" at the top of the column instead, beside its title."},{"path":"height","type":"number | undefined","required":false,"description":"The columns' height in pixels, under the header strip; 300 by default."},{"path":"isLoading","type":"boolean","required":false,"description":"The first options are loading: skeleton rows stand where they will be, while the header, the\nsearch and the list's heading stay."},{"path":"isPickedLoading","type":"boolean","required":false,"description":"The picked values are being looked up: those not named yet (in `options`, `pickedOptions` or\n`pickedLabels`) show as skeleton rows in their place, not by the bare value."},{"path":"renderRowActions","type":"((value: string) => ReactNode) | undefined","required":false,"description":"Actions of a value, such as a link to it: shown at the end of its option row and its picked row\nwhile the row is hovered or focused, in place of its meta (with the remove button on a picked\nrow, which then shows only then). None without it."},{"path":"onSearchChange","type":"((search: string) => void) | undefined","required":false,"description":"Called with the search text as it is typed. When given, the search is the host's (a server\nsearch, say): `options` are shown as given, not filtered."},{"path":"onRetryLoadMore","type":"(() => void) | undefined","required":false,"description":"Called by the retry button of `labels.loadMoreError`, to load the failed page again."},{"path":"onChange","type":"(value: string[]) => void","required":true,"description":"Called with the picked values: those of `value` in their order, a newly picked one appended, an unpicked one removed; a value missing from `options` is kept."},{"path":"labels","type":"{ readonly search: string; readonly clearSearch: string; readonly picked: string; readonly noMatch: string; readonly remove: (label: string) => string; readonly loading?: string | undefined; readonly loadMoreError?: string | undefined; readonly retry?: string | undefined; readonly meta?: string | undefined; readonly selectAll?: string | undefined; readonly removeAll?: string | undefined; }","required":true,"description":"The texts the picker shows; the host translates them."},{"path":"labels.search","type":"string","required":false,"requiredWhenParent":true,"description":"The search box's name and placeholder."},{"path":"labels.clearSearch","type":"string","required":false,"requiredWhenParent":true,"description":"The search box's clear button's name, such as \"Clear search\"."},{"path":"labels.picked","type":"string","required":false,"requiredWhenParent":true,"description":"The title of the picked column, such as \"Selected (3)\"."},{"path":"labels.noMatch","type":"string","required":false,"requiredWhenParent":true,"description":"Shown in the options column when the search matches nothing."},{"path":"labels.remove","type":"(label: string) => string","required":false,"requiredWhenParent":true,"description":"The name of an option's remove button, such as \"Remove Hanoi\"."},{"path":"labels.loading","type":"string | undefined","required":false,"description":"The accessible name of the skeleton rows of `isLoading` and `isPickedLoading`, such as \"Loading\"."},{"path":"labels.loadMoreError","type":"string | undefined","required":false,"description":"Why the last `onLoadMore` failed: a row at the end of the list with a retry button\n(`retry`, `onRetryLoadMore`); the loaded options stay, and nothing more loads until it is\nretried."},{"path":"labels.retry","type":"string | undefined","required":false,"description":"The retry button of `loadMoreError`, such as \"Retry\"; the error row needs it."},{"path":"labels.meta","type":"string | undefined","required":false,"description":"Heads the column of the options' `meta`, such as \"Members\"; no heading without it."},{"path":"labels.selectAll","type":"string | undefined","required":false,"description":"The name of a box before the list's name that ticks the shown options (up to `max`) while\nnothing is picked, and clears every pick otherwise; no box without it."},{"path":"labels.removeAll","type":"string | undefined","required":false,"description":"A text action in the end header that clears every pick, such as \"Remove all\"; none without it."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The remove and retry buttons' size, `md` by default; the checkboxes are always `md`, as every checkbox."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the picker; the boxes, the remove buttons and \"Remove all\" are blocked, and nothing is hidden. The search stays usable."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows the pick, not dimmed, and hides the remove buttons and \"Remove all\"; the boxes are blocked."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the pick, already in the user's language, under the picker."}],"examples":[{"name":"Default","description":"Options on the left, one picked on the right.","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"Empty","description":"Nothing picked: the picked column shows only its title.","source":"export const Empty: Story = { args: { value: [] } };","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"Interactive","description":"Searching, ticking and removing change the pick; the picked ones stay in the order they were picked.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<string[]>([], (value, set) => (\n <TransferPicker\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"WithError","description":"A problem with the pick.","source":"export const WithError: Story = { args: { value: [], error: \"Pick at least one city.\" } };","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"Disabled","description":"Dimmed: the boxes and remove buttons are blocked, and nothing is hidden.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"ReadOnly","description":"Read-only: the remove buttons are hidden and the boxes blocked.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"Medium","description":"The `md` size: the remove buttons are larger.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"LongNames","description":"Long names: one line each, cut with an ellipsis, the whole name in a tooltip.","source":"export const LongNames: Story = {\n args: { options: long, value: [\"long-1\", \"long-2\"], labels: fullLabels },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"Loading","description":"The first page is loading: skeleton rows stand where the options go; the header, search and heading stay.","source":"export const Loading: Story = {\n args: { options: [], value: [], isLoading: true, total: 0, labels: fullLabels },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"PickedLoading","description":"The picks are being looked up: those not named yet are skeleton rows in their place, under one status.","source":"export const PickedLoading: Story = {\n args: { value: [\"hue\", \"far-1\", \"far-2\"], isPickedLoading: true, labels: fullLabels },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"EndFullHeight","description":"The end column runs the full height, its \"Remove all\" atop it, beside the title; a filter sits above the search.","source":"export const EndFullHeight: Story = {\n args: { isEndFullHeight: true, labels: fullLabels, startToolbar: sourceFilter },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"WithFooter","description":"A footer under the options column only, such as an action to create one.","source":"export const WithFooter: Story = {\n args: { labels: fullLabels, startFooter: footer },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"WithRowActions","description":"Hovering or focusing a row swaps its count for its actions; a picked row shows its remove button then too.","source":"export const WithRowActions: Story = {\n args: { options: detailed, labels: fullLabels, renderRowActions: open },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"Short","description":"A shorter frame: the columns are 200px high.","source":"export const Short: Story = { args: { options: many.slice(0, 20), height: 200 } };","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"SingleOption","description":"A single option.","source":"export const SingleOption: Story = { args: { options: cities.slice(0, 1), value: [] } };","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"ThousandOptions","description":"A thousand options: the search stays quick.","source":"export const ThousandOptions: Story = {\n args: { options: many, value: [\"option-3\", \"option-500\", \"option-999\"] },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"UnknownPicked","description":"A picked value that is not among the options shows by itself.","source":"export const UnknownPicked: Story = { args: { value: [\"hue\", \"gone-id\"] } };","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"PagedAndSearched","description":"Options paged and searched by the host: scrolling near the end loads the next 20, searching starts over; the picked option is not on a loaded page and shows by its `pickedLabels` entry.","source":"export const PagedAndSearched: Story = { render: (args) => <Paged {...args} /> };","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"LoadingMore","description":"The next page is loading: skeleton rows end the list.","source":"export const LoadingMore: Story = {\n args: {\n options: many.slice(0, 20),\n hasMore: true,\n isLoadingMore: true,\n onLoadMore: fn<() => void>(),\n },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"LoadMoreError","description":"A later page failed: the loaded options stay, ended by its error and a Retry button; nothing more loads until it is retried.","source":"export const LoadMoreError: Story = {\n args: {\n options: many.slice(0, 20),\n hasMore: true,\n onLoadMore: fn<() => void>(),\n onRetryLoadMore: fn<() => void>(),\n labels: { ...labels, retry: \"Retry\", loadMoreError: \"Could not load more options.\" },\n },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"Retrying","description":"Retrying a failed page: Retry turns the error row into the loading row.","source":"export const Retrying: Story = {\n args: { options: many.slice(0, 20), hasMore: true, labels: { ...labels, retry: \"Retry\" } },\n render: (args) =>\n withState(false, (isRetrying, set) => (\n <TransferPicker\n {...args}\n isLoadingMore={isRetrying}\n labels={{\n ...args.labels,\n ...(!isRetrying && { loadMoreError: \"Could not load more options.\" }),\n }}\n onRetryLoadMore={() => {\n set(true);\n }}\n />\n )),\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"NoMore","description":"Every page is loaded: scrolling loads nothing more.","source":"export const NoMore: Story = {\n args: { options: many.slice(0, 20), hasMore: false, onLoadMore: fn<() => void>() },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"HostSearchNoMatch","description":"The host searches: the options are shown as given, here none matching.","source":"export const HostSearchNoMatch: Story = {\n args: { options: [], onSearchChange: fn<(search: string) => void>() },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"PickedNotLoaded","description":"A picked value the loaded options lack, named by `pickedLabels`.","source":"export const PickedNotLoaded: Story = {\n args: { value: [\"hue\", \"option-999\"], pickedLabels: { \"option-999\": \"Option number 1000\" } },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"WithDetails","description":"Each option with a second line and a count, the count headed, the total after the list's name; one count is missing, explained by its tip.","source":"export const WithDetails: Story = {\n args: { options: detailed, total: 120, value: [\"hue\", \"da-nang\"], labels: fullLabels },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"AtMax","description":"At the cap of two: the unpicked boxes are blocked until a pick is removed.","source":"export const AtMax: Story = { args: { max: 2, value: [\"hue\", \"hanoi\"] } };","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"PickedWithError","description":"A pick no longer offered shows its problem in the error tone, by its `pickedOptions` entry.","source":"export const PickedWithError: Story = {\n args: { value: [\"hue\", \"gone-id\"], pickedOptions: gone, labels: fullLabels },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"WithHostControls","description":"The host's controls above the search and in the end header, before \"Remove all\"; select all ticks the shown options while nothing is picked and clears every pick otherwise. Everything is live.","source":"export const WithHostControls: Story = {\n args: { options: detailed, total: 6, labels: fullLabels },\n render: (args) =>\n withState<string[]>([\"hue\", \"gone-id\"], (value, set) => (\n <TransferPicker\n {...args}\n startToolbar={sourceFilter}\n endHeader={combine}\n startFooter={footer}\n renderRowActions={open}\n pickedOptions={gone}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"ReadOnlyWithDetails","description":"Every opt-in part read-only: the picks and the host's header stay, not dimmed; Remove all and the remove buttons are hidden.","source":"export const ReadOnlyWithDetails: Story = {\n args: {\n options: detailed,\n total: 6,\n value: [\"hue\", \"gone-id\"],\n pickedOptions: gone,\n labels: fullLabels,\n isReadOnly: true,\n endHeader: combine,\n },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <TransferPicker {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"AllStates","description":"The key states one under the other, each as its story above.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n {(\n [\n [\"Some picked\", Default],\n [\"Nothing picked, error\", WithError],\n [\"Disabled\", Disabled],\n [\"Read-only, with details and the host's header\", ReadOnlyWithDetails],\n [\"Medium\", Medium],\n [\"Long names\", LongNames],\n [\"Loading the first page\", Loading],\n [\"Picks being looked up\", PickedLoading],\n [\"End column of the full height, a filter\", EndFullHeight],\n [\"A footer\", WithFooter],\n [\"Row actions\", WithRowActions],\n [\"Loading more\", LoadingMore],\n [\"A later page failed\", LoadMoreError],\n [\"Details, total, select all\", WithDetails],\n [\"A pick with a problem\", PickedWithError],\n [\"At the cap\", AtMax],\n [\"Host search, no match\", HostSearchNoMatch],\n [\"A pick not loaded\", PickedNotLoaded],\n ] satisfies [string, Story][]\n ).map(([caption, story]) => (\n <Case key={caption} caption={caption}>\n <TransferPicker {...args} {...story.args} />\n </Case>\n ))}\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"}],"primaryExample":"Default"},{"name":"TreeOptions","level":"molecules","category":"Selection","keywords":["tree","options","leaves","groups"],"import":"import { TreeOptions } from \"@antscorp/ui\";","description":"Pick one leaf from a flat or two-level tree while groups only expand and collapse.","usage":"Pick one leaf from a flat or two-level tree while groups only expand and collapse. Use TreeSelect to put this tree behind a searchable field trigger.","bestPractices":[{"guidance":true,"description":"Give every item a unique value and use isExpandedByDefault at mount when all groups should start open."},{"guidance":false,"description":"Do not supply deeper nesting and expect it to render; only the top level and its children are shown."}],"anatomy":[{"name":"Rows","required":true,"description":"Labels with a leaf selection mark or a group's trailing chevron."},{"name":"Child rows","required":false,"description":"Leaves indented beneath an expanded group."}],"accessibility":["The named tree exposes levels, expansion, selection and disabled states; each parent owns its child group.","Up/Down and Home/End rove enabled rows; Right opens or enters, Left collapses or returns to the parent, and Enter/Space acts. Tab follows focus; typing finds a label."],"props":[{"path":"label","type":"string","required":true,"description":"Names the tree for assistive tech."},{"path":"items","type":"readonly TreeItem[]","required":true,"description":"The rows, in order: leaves and groups of leaves."},{"path":"items[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What is picked with this leaf; unique across the tree."},{"path":"items[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The row's text."},{"path":"items[].isDisabled","type":"boolean","required":false,"description":"Shown dimmed; cannot be picked, nor opened when a group."},{"path":"items[].children","type":"readonly TreeItem[] | undefined","required":false,"description":"Makes the item a group, even when empty: it opens and closes and is never picked. Two levels\nonly: the leaves' own `children` are not shown."},{"path":"value","type":"string | null","required":true,"description":"The picked leaf's value, shown selected; `null` when none."},{"path":"onChange","type":"(value: string) => void","required":true,"description":"Called with a leaf's value when it is clicked, or pressed with Enter or Space."},{"path":"isExpandedByDefault","type":"boolean","required":false,"description":"Every group starts open; without it only the one holding `value`. Read when mounted: remount\n(a new `key`) to apply a change."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The rows' size, `md` by default, as the Selector's option rows."}],"examples":[{"name":"Default","description":"Leaves and closed groups, each group's chevron at the end of its row.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/TreeOptions.stories.tsx"},{"name":"Empty","description":"No rows.","source":"export const Empty: Story = { args: { items: [] } };","file":"packages/ui/src/molecules/TreeOptions.stories.tsx"},{"name":"Expanded","description":"Every group open (`isExpandedByDefault`); leaves sit one step in.","source":"export const Expanded: Story = { args: { isExpandedByDefault: true } };","file":"packages/ui/src/molecules/TreeOptions.stories.tsx"},{"name":"ChosenChild","description":"A group's leaf picked: shown selected, its group open.","source":"export const ChosenChild: Story = { args: { value: \"order.total\" } };","file":"packages/ui/src/molecules/TreeOptions.stories.tsx"},{"name":"Interactive","description":"Clicking a group opens or closes it; clicking a leaf picks it.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<string | null>(null, (value, set) => (\n <TreeOptions\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/TreeOptions.stories.tsx"},{"name":"DisabledItems","description":"Disabled rows, at the top and in a group, dimmed.","source":"export const DisabledItems: Story = { args: { isExpandedByDefault: true } };","file":"packages/ui/src/molecules/TreeOptions.stories.tsx"},{"name":"LongLabels","description":"Long labels truncate on one line.","source":"export const LongLabels: Story = {\n args: {\n isExpandedByDefault: true,\n items: [\n {\n value: \"a\",\n label: \"Products viewed during the last session on the mobile application\",\n children: [{ value: \"a.1\", label: \"Name of the product category looked at the longest\" }],\n },\n ],\n },\n decorators: [\n (Story) => (\n <Narrow>\n <Story />\n </Narrow>\n ),\n ],\n};","file":"packages/ui/src/molecules/TreeOptions.stories.tsx"},{"name":"Many","description":"Many rows.","source":"export const Many: Story = { args: { items: many, isExpandedByDefault: true } };","file":"packages/ui/src/molecules/TreeOptions.stories.tsx"},{"name":"Medium","description":"The `md` rows.","source":"export const Medium: Story = { args: { size: \"md\", isExpandedByDefault: true } };","file":"packages/ui/src/molecules/TreeOptions.stories.tsx"},{"name":"InNarrow","description":"In a 280px drawer.","source":"export const InNarrow: Story = {\n args: { value: \"cart.sku\" },\n decorators: [\n (Story) => (\n <Narrow>\n <Story />\n </Narrow>\n ),\n ],\n};","file":"packages/ui/src/molecules/TreeOptions.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Closed groups\">\n <TreeOptions {...args} />\n </Case>\n <Case caption=\"Expanded\">\n <TreeOptions {...args} isExpandedByDefault />\n </Case>\n <Case caption=\"Child picked\">\n <TreeOptions {...args} value=\"order.total\" />\n </Case>\n <Case caption=\"Medium\">\n <TreeOptions {...args} size=\"md\" isExpandedByDefault />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/TreeOptions.stories.tsx"}],"primaryExample":"Default"},{"name":"TreeSearchPanel","level":"organisms","category":"Selection","keywords":["tree","search","panel","paging"],"import":"import { TreeSearchPanel } from \"@antscorp/ui\";","description":"Compose the search and results panel used inside TreeSelect.","usage":"Compose the search and results panel used inside TreeSelect. Use TreeSelect for a complete trigger and popover around this panel.","bestPractices":[{"guidance":true,"description":"Keep search value and selectedValue controlled, and use isSearchedByHost when items are already filtered remotely."},{"guidance":false,"description":"Do not expect the panel to close a surrounding popover after onPick; its host owns that behavior."}],"anatomy":[{"name":"Search input","required":true,"description":"A controlled query that focuses a frame after mounting."},{"name":"Results","required":true,"description":"TreeOptions, loading rows or a no-match message."},{"name":"Paging row","required":false,"description":"A load-more sentinel, loading placeholders or a retry action."}],"accessibility":["label names the tree; labels names search and clear. TreeOptions owns child groups and handles arrows, Home/End and Enter/Space."],"props":[{"path":"label","type":"string","required":true,"description":"Names the list for assistive tech."},{"path":"items","type":"readonly TreeItem[]","required":true,"description":"The rows, in order: leaves and groups of leaves."},{"path":"items[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What is picked with this leaf; unique across the tree."},{"path":"items[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The row's text."},{"path":"items[].isDisabled","type":"boolean","required":false,"description":"Shown dimmed; cannot be picked, nor opened when a group."},{"path":"items[].children","type":"readonly TreeItem[] | undefined","required":false,"description":"Makes the item a group, even when empty: it opens and closes and is never picked. Two levels\nonly: the leaves' own `children` are not shown."},{"path":"selectedValue","type":"string | null","required":true,"description":"The picked leaf's value, marked in the list; `null` when none is."},{"path":"labels","type":"{ readonly search: string; readonly noMatch: string; readonly clear: string; readonly value?: string | undefined; readonly loadMoreError?: string | undefined; readonly retry?: string | undefined; readonly loading?: string | undefined; }","required":true,"description":"The texts the panel shows, as `TreeSelect`'s; the host translates them."},{"path":"labels.search","type":"string","required":false,"requiredWhenParent":true,"description":"The search box's name and placeholder, such as \"Search\"."},{"path":"labels.noMatch","type":"string","required":false,"requiredWhenParent":true,"description":"Shown in the popover when the search (or the data) leaves no row."},{"path":"labels.clear","type":"string","required":false,"requiredWhenParent":true,"description":"The search box's clear button, such as \"Clear search\"; it shows only an icon."},{"path":"labels.value","type":"string | undefined","required":false,"description":"The trigger's text when `value` is not among `items`: a pick not on a loaded page, a stored label."},{"path":"labels.loadMoreError","type":"string | undefined","required":false,"description":"Why the last `onLoadMore` failed: a row at the end of the list with a retry button\n(`retry`, `onRetryLoadMore`); the loaded items stay, and nothing more loads until it is\nretried."},{"path":"labels.retry","type":"string | undefined","required":false,"description":"The retry button of `loadMoreError`, such as \"Retry\"; the error row needs it."},{"path":"labels.loading","type":"string | undefined","required":false,"description":"The name of the loading status, such as \"Loading\"; used while `isLoading`."},{"path":"size","type":"\"md\" | \"sm\"","required":true,"description":"The rows' size."},{"path":"isExpandedByDefault","type":"boolean","required":true,"description":"Every group opens; without it only the one holding the value, a lone group, or all while searching."},{"path":"onPick","type":"(value: string) => void","required":true,"description":"Called with a leaf's value when it is picked."},{"path":"value","type":"string","required":true,"description":"The search box's text, the panel's controlled value."},{"path":"onChange","type":"(value: string) => void","required":true,"description":"Called with the search text as it is typed."},{"path":"isSearchedByHost","type":"boolean","required":true,"description":"The host searches: `items` are shown as given, not matched against `value`."},{"path":"more","type":"Pick<TreeSelectProps, \"hasMore\" | \"isLoading\" | \"isLoadingMore\" | \"onLoadMore\" | \"onRetryLoadMore\">","required":true,"description":"Whether and how the list loads more, as `TreeSelect`'s props of the same names; `isLoading`\nshows skeleton rows instead of `labels.noMatch` while the list is empty."}],"examples":[{"name":"Default","description":"The search box over the tree, the group holding the value open.","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"},{"name":"Searched","description":"Typed in: only matching rows, every group open.","source":"export const Searched: Story = { args: { value: \"tot\" } };","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"},{"name":"NoMatch","description":"A search that matches nothing.","source":"export const NoMatch: Story = { args: { value: \"zzz\" } };","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"},{"name":"Empty","description":"No rows at all.","source":"export const Empty: Story = { args: { items: [] } };","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"},{"name":"ExpandedByDefault","description":"Every group open.","source":"export const ExpandedByDefault: Story = {\n args: { isExpandedByDefault: true, selectedValue: null },\n};","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"},{"name":"Loading","description":"Loading the first rows: skeleton rows.","source":"export const Loading: Story = { args: { items: [], more: { isLoading: true } } };","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"},{"name":"LoadingMore","description":"More rows loading at the end of the list.","source":"export const LoadingMore: Story = {\n args: { items: MANY, selectedValue: null, more: { hasMore: true, isLoadingMore: true } },\n};","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"},{"name":"LoadMoreError","description":"The next page failed: an error row with Retry.","source":"export const LoadMoreError: Story = {\n args: {\n items: MANY,\n selectedValue: null,\n labels: { ...meta.args.labels, loadMoreError: \"Could not load more\" },\n more: { hasMore: true, onRetryLoadMore: fn<() => void>() },\n },\n};","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"},{"name":"SearchedByHost","description":"The host searches: rows shown as given.","source":"export const SearchedByHost: Story = { args: { value: \"x\", isSearchedByHost: true } };","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"},{"name":"LongLabel","description":"A long label.","source":"export const LongLabel: Story = { args: { items: LONG, selectedValue: \"long\" } };","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"},{"name":"InNarrow","description":"In a drawer-width frame.","source":"export const InNarrow: Story = {\n render: (args) => (\n <Narrow>\n <TreeSearchPanel {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <TreeSearchPanel {...args} />\n </Case>\n <Case caption=\"No match\">\n <TreeSearchPanel {...args} value=\"zzz\" />\n </Case>\n <Case caption=\"Loading\">\n <TreeSearchPanel {...args} items={[]} more={{ isLoading: true }} />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"}],"primaryExample":"Default"},{"name":"TreeSelect","level":"organisms","category":"Selection","keywords":["tree","select","search","leaf"],"import":"import { TreeSelect } from \"@antscorp/ui\";","description":"Choose a single leaf through a searchable two-level tree in a popover.","usage":"Choose a single leaf through a searchable two-level tree in a popover. Use Select for a conventional flat or sectioned options list.","bestPractices":[{"guidance":true,"description":"Supply labels.search, labels.noMatch and labels.clear for the panel. For paging recovery, supply labels.loadMoreError, labels.retry and onRetryLoadMore together; clear the error and set isLoadingMore while retrying, then update items and hasMore after success."},{"guidance":true,"description":"Keep item values unique and supply labels.value for a selected leaf that is absent from the loaded items."},{"guidance":false,"description":"Do not expect group rows to become selected values or nesting deeper than two levels to render."}],"anatomy":[{"name":"Trigger","required":true,"description":"The selected label or placeholder, with loading or read-only presentation."},{"name":"Start addon","required":false,"description":"A separate host control sharing the frame, outside the trigger button."},{"name":"Panel","required":false,"description":"Search, a tree, and optional paging or retry rows while the popover is open."},{"name":"Error","required":false,"description":"A translated problem below the trigger."}],"accessibility":["Supply label even when hidden. Up/Down and Home/End rove enabled rows; Right opens or enters a group, Left collapses or returns, and Enter/Space acts. Parents own child groups; Escape closes."],"related":[{"name":"Select","when":"Choose one value from flat options or sections without a tree."}],"props":[{"path":"onLoadMore","type":"(() => void) | undefined","required":false,"description":"Called to load the next page; nothing loads without it."},{"path":"hasMore","type":"boolean","required":false,"description":"More pages are left to load."},{"path":"isLoadingMore","type":"boolean","required":false,"description":"A page is loading now, so none is asked for."},{"path":"label","type":"string","required":true,"description":"Names the control for assistive tech and the popover; shown only when `isLabelHidden` is false."},{"path":"isLabelHidden","type":"boolean","required":false,"description":"Hides the label, as in a rule row; `true` by default. `false` shows it above the trigger, as a form field's."},{"path":"items","type":"readonly TreeItem[]","required":true,"description":"The rows, in order: leaves and groups of leaves."},{"path":"items[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What is picked with this leaf; unique across the tree."},{"path":"items[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The row's text."},{"path":"items[].isDisabled","type":"boolean","required":false,"description":"Shown dimmed; cannot be picked, nor opened when a group."},{"path":"items[].children","type":"readonly TreeItem[] | undefined","required":false,"description":"Makes the item a group, even when empty: it opens and closes and is never picked. Two levels\nonly: the leaves' own `children` are not shown."},{"path":"value","type":"string | null","required":true,"description":"The picked leaf's value; `null`, or a value no leaf has, shows the placeholder (or\n`labels.value`)."},{"path":"onSearchChange","type":"((search: string) => void) | undefined","required":false,"description":"Called with the search text as it is typed, and with \"\" when the popover opens again. When\ngiven, the search is the host's (a server search, say): `items` are shown as given."},{"path":"onRetryLoadMore","type":"(() => void) | undefined","required":false,"description":"Called by the retry button of `labels.loadMoreError`, to load the failed page again."},{"path":"onChange","type":"(value: string) => void","required":true,"description":"Called with a leaf's value when it is picked; the popover then closes."},{"path":"placeholder","type":"string | undefined","required":false,"description":"Shown in the trigger while nothing is picked."},{"path":"labels","type":"{ readonly search: string; readonly noMatch: string; readonly clear: string; readonly value?: string | undefined; readonly loadMoreError?: string | undefined; readonly retry?: string | undefined; readonly loading?: string | undefined; }","required":true,"description":"The texts the view shows; the host translates them."},{"path":"labels.search","type":"string","required":false,"requiredWhenParent":true,"description":"The search box's name and placeholder, such as \"Search\"."},{"path":"labels.noMatch","type":"string","required":false,"requiredWhenParent":true,"description":"Shown in the popover when the search (or the data) leaves no row."},{"path":"labels.clear","type":"string","required":false,"requiredWhenParent":true,"description":"The search box's clear button, such as \"Clear search\"; it shows only an icon."},{"path":"labels.value","type":"string | undefined","required":false,"description":"The trigger's text when `value` is not among `items`: a pick not on a loaded page, a stored label."},{"path":"labels.loadMoreError","type":"string | undefined","required":false,"description":"Why the last `onLoadMore` failed: a row at the end of the list with a retry button\n(`retry`, `onRetryLoadMore`); the loaded items stay, and nothing more loads until it is\nretried."},{"path":"labels.retry","type":"string | undefined","required":false,"description":"The retry button of `loadMoreError`, such as \"Retry\"; the error row needs it."},{"path":"labels.loading","type":"string | undefined","required":false,"description":"The name of the loading status, such as \"Loading\"; used while `isLoading`."},{"path":"isExpandedByDefault","type":"boolean","required":false,"description":"Every group opens with the popover; without it only the one holding the value, or a lone group."},{"path":"isLoading","type":"boolean","required":false,"description":"The items are loading: the trigger keeps the label `items` or `labels.value` know (else blank)\nand does not open, busy with a spinner in place of the chevron from 300 ms on, for at least\n300 ms (`useLoadingShown`); an open popover (a host's search) shows skeleton rows instead\nof `noMatch`."},{"path":"width","type":"string | number | undefined","required":false,"description":"The trigger's width, in pixels or any CSS length such as `\"100%\"`; as wide as its content without it."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The trigger's size, `md` by default (the theme's 30px control height)."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the trigger and blocks it."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows the picked label, not dimmed, without the chevron; the popover does not open."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the pick, already in the user's language, shown under the trigger."},{"path":"startAddon","type":"ReactNode","required":false,"description":"A control of its own at the start of the trigger's frame, such as a `TagSelect`, set off from\nthe trigger by a short vertical line; the frame's underline, hover and status take in both.\nNot inside the trigger button, so it takes its own presses. None without it."}],"examples":[{"name":"Default","description":"Leaves and closed groups; nothing picked, so the placeholder shows.","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"Empty","description":"No items: the popover says there is nothing to pick.","source":"export const Empty: Story = {\n args: { items: [] },\n play: ({ canvasElement }) => open(canvasElement),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"Flat","description":"Leaves only, one picked.","source":"export const Flat: Story = { args: { items: flat, value: \"email\" } };","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"Interactive","description":"Picking a leaf changes the value and closes the popover; a group only opens and closes.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<string | null>(null, (value, set) => (\n <TreeSelect\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"GroupsClosed","description":"Open with the groups closed: none holds the value and there is more than one.","source":"export const GroupsClosed: Story = { play: ({ canvasElement }) => open(canvasElement) };","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"OpenByDefault","description":"Open with every group expanded (`isExpandedByDefault`).","source":"export const OpenByDefault: Story = {\n args: { isExpandedByDefault: true },\n play: ({ canvasElement }) => open(canvasElement),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"ChosenChild","description":"A group's child picked: the trigger shows the child, and its group opens with the popover.","source":"export const ChosenChild: Story = {\n args: { value: \"order.total\" },\n play: ({ canvasElement }) => open(canvasElement),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"DisabledItems","description":"Disabled rows, at the top and in a group, show dimmed and cannot be picked.","source":"export const DisabledItems: Story = {\n args: { items: withDisabled, isExpandedByDefault: true },\n play: ({ canvasElement }) => open(canvasElement),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"SearchGroupLabel","description":"A search matching a group's label: the group stays, open, with only the children that match (none).","source":"export const SearchGroupLabel: Story = {\n play: ({ canvasElement }) => open(canvasElement, \"cart\"),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"SearchChildLabel","description":"A search matching a child's label: its group stays, open, with only that child.","source":"export const SearchChildLabel: Story = {\n play: ({ canvasElement }) => open(canvasElement, \"sku\"),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"SearchNoMatch","description":"A search matching nothing: the no-match text.","source":"export const SearchNoMatch: Story = {\n play: ({ canvasElement }) => open(canvasElement, \"zzz\"),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"LongLabels","description":"Long labels: the trigger truncates the picked one.","source":"export const LongLabels: Story = {\n args: { items: long, value: \"b.1\", isExpandedByDefault: true },\n play: ({ canvasElement }) => open(canvasElement),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"Many","description":"Many rows: the tree scrolls under the search box, at most 200px high.","source":"export const Many: Story = {\n args: { items: many, isExpandedByDefault: true },\n play: ({ canvasElement }) => open(canvasElement),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"Loading","description":"The items still loading, the pick's label known: it stays, a spinner where the chevron is; it\ndoes not open. With the label unknown the trigger is blank (see `AllStates`).","source":"export const Loading: Story = { args: { items: grouped, value: \"order.total\", isLoading: true } };","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"PagedAndSearched","description":"Rows paged and searched by the host: scrolling near the end loads 20 more, a search starts over, and the trigger names a pick not on a loaded page (`labels.value`).","source":"export const PagedAndSearched: Story = {\n render: (args) => <Paged {...args} />,\n play: ({ canvasElement }) => open(canvasElement),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"LoadingMore","description":"The next page is loading: skeleton rows end the list.","source":"export const LoadingMore: Story = {\n args: {\n items: pages.slice(0, 20),\n hasMore: true,\n isLoadingMore: true,\n onLoadMore: fn<() => void>(),\n },\n play: ({ canvasElement }) => open(canvasElement),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"LoadMoreError","description":"A later page failed: the loaded rows stay, ended by its error and a Retry button; nothing more loads until it is retried.","source":"export const LoadMoreError: Story = {\n args: {\n ...FAILED,\n labels: { ...FAILED.labels, loadMoreError: \"Could not load more rows.\" },\n onRetryLoadMore: fn<() => void>(),\n },\n play: ({ canvasElement }) => open(canvasElement),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"Retrying","description":"Retrying a failed page: Retry turns the error row into the loading row.","source":"export const Retrying: Story = {\n args: FAILED,\n render: (args) =>\n withState(false, (isRetrying, set) => (\n <TreeSelect\n {...args}\n isLoadingMore={isRetrying}\n labels={isRetrying ? args.labels : { ...args.labels, loadMoreError: \"Failed.\" }}\n onRetryLoadMore={() => {\n set(true);\n }}\n />\n )),\n play: ({ canvasElement }) => open(canvasElement),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"NoMore","description":"Every page is loaded: scrolling loads nothing more.","source":"export const NoMore: Story = {\n args: { items: pages.slice(0, 20), hasMore: false, onLoadMore: fn<() => void>() },\n play: ({ canvasElement }) => open(canvasElement),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"HostSearchNoMatch","description":"The host searches: the rows are shown as given, here none matching the search.","source":"export const HostSearchNoMatch: Story = {\n args: { items: [], onSearchChange: fn<(search: string) => void>() },\n play: ({ canvasElement }) => open(canvasElement, \"zzz\"),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"PickedNotLoaded","description":"The picked value is not among the items: the trigger shows `labels.value`.","source":"export const PickedNotLoaded: Story = {\n args: {\n items: pages.slice(0, 20),\n value: \"row-150\",\n labels: { ...LABELS, value: \"Row 150\" },\n },\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"WithLabel","description":"The label shown above the trigger, as a form field's (`isLabelHidden: false`).","source":"export const WithLabel: Story = { args: { isLabelHidden: false } };","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"WithError","description":"A problem with the pick, under the trigger.","source":"export const WithError: Story = { args: { error: \"Field is required\" } };","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"Disabled","description":"Disabled: dimmed and blocked.","source":"export const Disabled: Story = { args: { value: \"title\", isDisabled: true } };","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"ReadOnly","description":"Read-only: the value, not dimmed, without the chevron; it does not open.","source":"export const ReadOnly: Story = { args: { value: \"cart.price\", isReadOnly: true } };","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"InNarrow","description":"In a 280px drawer, full width.","source":"export const InNarrow: Story = {\n args: { width: \"100%\", value: \"cart.quantity\" },\n render: (args) => (\n <Narrow>\n <TreeSelect {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Placeholder\">\n <TreeSelect {...args} />\n </Case>\n <Case caption=\"Leaf picked\">\n <TreeSelect {...args} items={flat} value=\"email\" />\n </Case>\n <Case caption=\"Child picked\">\n <TreeSelect {...args} value=\"order.total\" />\n </Case>\n <Case caption=\"Long label\">\n <TreeSelect {...args} items={long} value=\"b.1\" />\n </Case>\n <Case caption=\"Loading: label known, then unknown\">\n <TreeSelect {...args} value=\"order.total\" isLoading />\n <TreeSelect {...args} items={[]} value=\"order.total\" isLoading />\n </Case>\n <Case caption=\"Error\">\n <TreeSelect {...args} error=\"Field is required\" />\n </Case>\n <Case caption=\"Pick not on a loaded page\">\n <TreeSelect\n {...args}\n items={pages.slice(0, 20)}\n value=\"row-150\"\n labels={{ ...args.labels, value: \"Row 150\" }}\n />\n </Case>\n <Case caption=\"Disabled\">\n <TreeSelect {...args} value=\"title\" isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <TreeSelect {...args} value=\"cart.price\" isReadOnly />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"}],"primaryExample":"Default"},{"name":"TruncatedOption","level":"atoms","category":"Selection","keywords":["option","ellipsis","select","label"],"import":"import { TruncatedOption } from \"@antscorp/ui\";","description":"Render a Selector option with a one-line label and a tooltip when it is cut.","usage":"Render a Selector option with a one-line label and a tooltip when it is cut. Use TruncatedText for plain text outside an option.","bestPractices":[{"guidance":true,"description":"Provide a recognizable option label; an absent label falls back to option.value."},{"guidance":false,"description":"Do not expect this presentation view to own selection or open a list."}],"anatomy":[{"name":"Label","required":true,"description":"The option label, or its value, rendered through TruncatedText."},{"name":"Icon","required":false,"description":"The option's supplied icon."},{"name":"Description","required":false,"description":"Supporting option text rendered by SelectorOption."}],"props":[{"path":"option","type":"SelectorOptionData","required":true,"description":"The Selector option to show."},{"path":"option.value","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"option.label","type":"string","required":false,"description":""},{"path":"option.description","type":"ReactNode","required":false,"description":""},{"path":"option.disabled","type":"boolean","required":false,"description":""},{"path":"option.icon","type":"IconType | ReactNode","required":false,"description":""},{"path":"option.action","type":"ReactNode","required":false,"description":"The option's secondary control — an Edit button beside a saved label —\nas one node the caller renders and names; the host only places it. In\n`MultiSelector`, any option carrying one turns the popup into a grid whose\nrows pair the option with its action, reachable by pointer, touch, the\ninline-end arrow, and a screen reader (`spec:AST-058`). `Selector` does\nnot render it yet and warns in development when an option carries one.\nAbsent and `null` mean the same: none."}],"examples":[{"name":"Default","description":"A short label that fits: no ellipsis, no tooltip.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/TruncatedOption.stories.tsx"},{"name":"LongLabel","description":"A long label cut with an ellipsis; hover it for the whole label.","source":"export const LongLabel: Story = {\n args: { option: { value: \"a\", label: LONG } },\n render: (args) => (\n <Narrow>\n <TruncatedOption {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/TruncatedOption.stories.tsx"},{"name":"ValueOnly","description":"No label: the option's value stands for it.","source":"export const ValueOnly: Story = { args: { option: { value: \"view-42\" } } };","file":"packages/ui/src/atoms/TruncatedOption.stories.tsx"},{"name":"WithDescription","description":"With a description under the label.","source":"export const WithDescription: Story = {\n args: { option: { value: \"a\", label: LONG, description: \"Shared by Nhi\" } },\n render: (args) => (\n <Narrow>\n <TruncatedOption {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/TruncatedOption.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: () => (\n <Gallery>\n <Case caption=\"Fits\">\n <TruncatedOption option={{ value: \"a\", label: \"Customer\" }} />\n </Case>\n <Case caption=\"Cut, narrow\">\n <Narrow>\n <TruncatedOption option={{ value: \"a\", label: LONG }} />\n </Narrow>\n </Case>\n <Case caption=\"Value only\">\n <TruncatedOption option={{ value: \"view-42\" }} />\n </Case>\n <Case caption=\"With a description\">\n <Narrow>\n <TruncatedOption option={{ value: \"a\", label: LONG, description: \"Shared by Nhi\" }} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/TruncatedOption.stories.tsx"}],"primaryExample":"Default"},{"name":"TruncatedText","level":"atoms","category":"Data display","keywords":["text","ellipsis","tooltip","overflow"],"import":"import { TruncatedText } from \"@antscorp/ui\";","description":"Keep a value on one line within the space its parent supplies.","usage":"Keep a value on one line within the space its parent supplies. Astryx Text shows the whole label in a tooltip when the text is cut.","bestPractices":[{"guidance":true,"description":"Give the parent a constrained width so truncation has a useful boundary."},{"guidance":false,"description":"Do not use error tone as the only explanation of a problem; provide the reason nearby."}],"props":[{"path":"label","type":"string","required":true,"description":"The text; shown whole in a tooltip when it does not fit."},{"path":"tone","type":"\"default\" | \"error\" | undefined","required":false,"description":"`error` draws it in the error tone, for what has a problem; `default` by default."}],"examples":[{"name":"Default","description":"A short text that fits: no ellipsis, no tooltip.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/TruncatedText.stories.tsx"},{"name":"LongText","description":"A long text cut with an ellipsis; hover it for the whole text.","source":"export const LongText: Story = {\n args: { label: LONG },\n render: (args) => (\n <Narrow>\n <TruncatedText {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/TruncatedText.stories.tsx"},{"name":"ErrorTone","description":"The error tone, for what has a problem.","source":"export const ErrorTone: Story = { args: { label: \"Old city\", tone: \"error\" } };","file":"packages/ui/src/atoms/TruncatedText.stories.tsx"},{"name":"Empty","description":"No text: nothing to show.","source":"export const Empty: Story = { args: { label: \"\" } };","file":"packages/ui/src/atoms/TruncatedText.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Fits\">\n <TruncatedText {...args} />\n </Case>\n <Case caption=\"Cut, narrow\">\n <Narrow>\n <TruncatedText label={LONG} />\n </Narrow>\n </Case>\n <Case caption=\"Error tone\">\n <TruncatedText label=\"Old city\" tone=\"error\" />\n </Case>\n <Case caption=\"Error tone, cut\">\n <Narrow>\n <TruncatedText label={LONG} tone=\"error\" />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/TruncatedText.stories.tsx"}],"primaryExample":"Default"}]}
|
|
1
|
+
{"schemaVersion":1,"package":"@antscorp/ui","version":"0.1.5","components":[{"name":"AccentCard","level":"molecules","category":"Layout","keywords":["card","header","rows","footer"],"import":"import { AccentCard } from \"@antscorp/ui\";","description":"Group related rows under a titled header, with an optional close action and footer.","usage":"Group related rows under a titled header, with an optional close action and footer. Choose stripe or banner to match the surrounding composition.","bestPractices":[{"guidance":true,"description":"Provide labels.close with onClose, and disable or make the supplied row controls read-only in the host."},{"guidance":false,"description":"Do not expect isDisabled to disable the card's children; it only blocks closing."}],"anatomy":[{"name":"Header","required":true,"description":"The title and optional named close button."},{"name":"Rows","required":true,"description":"Host content stacked below the header."},{"name":"Footer","required":false,"description":"Host content separated from the rows by a line."}],"accessibility":["The card is a group named by title; labels.close names its optional close button."],"props":[{"path":"title","type":"string","required":true,"description":"The text of the header bar; it also names the card."},{"path":"children","type":"ReactNode","required":true,"description":"The card's rows, such as `SideLabelRow`s; they bring their own padding."},{"path":"labels","type":"{ readonly close?: string | undefined; } | undefined","required":false,"description":"The texts the card shows; the host translates them."},{"path":"labels.close","type":"string | undefined","required":false,"description":"The name of the close button, such as \"Remove item\"; required with `onClose`, which shows no button without it."},{"path":"onClose","type":"(() => void) | undefined","required":false,"description":"Called by the close button at the end of the header; no button without it."},{"path":"footer","type":"ReactNode","required":false,"description":"Content under the rows, such as an action and its caption, set off by a line above it; none\nwithout it."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the close button and blocks it; the rows are the host's to disable."},{"path":"variant","type":"\"banner\" | \"stripe\" | undefined","required":false,"description":"`stripe` (default): a grey border with an accent stripe down the start edge and a light\nheader bar. `banner`: no border nor stripe but a soft shadow,\nunder a dark blue header bar with the title in white capitals and a white close cross."}],"examples":[{"name":"Default","description":"A card of two rows, with a close button in its header.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"WithoutClose","description":"No close button: none is offered, e.g. the only card of a list.","source":"export const WithoutClose: Story = {\n render: (args) => <AccentCard {...args} onClose={undefined} />,\n};","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"Interactive","description":"Closing takes the card away; a button brings it back.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(true, (isShown, set) =>\n isShown ? (\n <AccentCard\n {...args}\n onClose={() => {\n args.onClose?.();\n set(false);\n }}\n />\n ) : (\n <Button\n label=\"Show again\"\n size=\"sm\"\n onClick={() => {\n set(true);\n }}\n />\n ),\n ),\n};","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"WithFooter","description":"A footer under the rows: an action and its caption, set off by a line.","source":"export const WithFooter: Story = { args: { footer } };","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"ReadOnly","description":"Read-only: the host offers no close (no `onClose`) and no footer action; the rows stay, not dimmed.","source":"export const ReadOnly: Story = {\n render: (args) => <AccentCard {...args} onClose={undefined} />,\n};","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"ManyRows","description":"Many rows in one card.","source":"export const ManyRows: Story = {\n args: {\n children: Array.from({ length: 10 }, (_, index) => (\n <SideLabelRow key={index} label={`Row ${index + 1}`}>\n <Text type=\"body\">Value {index + 1}</Text>\n </SideLabelRow>\n )),\n },\n};","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"Disabled","description":"The close button blocked.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"LongTitle","description":"A long title.","source":"export const LongTitle: Story = {\n args: { title: \"A long title for a card that has a lot to say about what it holds\" },\n};","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"Empty","description":"No rows yet: the header alone.","source":"export const Empty: Story = { args: { children: null } };","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <AccentCard {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"Banner","description":"The banner look: a dark blue header with a white title in capitals and a white cross, a soft shadow.","source":"export const Banner: Story = { args: { variant: \"banner\", title: \"Included items\" } };","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"BannerWithoutClose","description":"The banner without its cross.","source":"export const BannerWithoutClose: Story = {\n args: { variant: \"banner\", title: \"Included items\" },\n render: (args) => <AccentCard {...args} onClose={undefined} />,\n};","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"BannerDisabled","description":"The banner with its cross blocked.","source":"export const BannerDisabled: Story = {\n args: { variant: \"banner\", title: \"Included items\", isDisabled: true },\n};","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"BannerNarrow","description":"The banner with a long title, in a drawer-width frame.","source":"export const BannerNarrow: Story = {\n args: { variant: \"banner\", title: \"A long title for a card that has a lot to say\" },\n render: (args) => (\n <Narrow>\n <AccentCard {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/AccentCard.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"With close\">\n <AccentCard {...args} />\n </Case>\n <Case caption=\"Without close\">\n <AccentCard {...args} onClose={undefined} />\n </Case>\n <Case caption=\"Disabled\">\n <AccentCard {...args} isDisabled />\n </Case>\n <Case caption=\"With a footer\">\n <AccentCard {...args} footer={footer} />\n </Case>\n <Case caption=\"Banner\">\n <AccentCard {...args} variant=\"banner\" />\n </Case>\n <Case caption=\"Banner, disabled\">\n <AccentCard {...args} variant=\"banner\" isDisabled />\n </Case>\n <Case caption=\"Narrow\">\n <Narrow>\n <AccentCard {...args} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/AccentCard.stories.tsx"}],"primaryExample":"Default"},{"name":"AddButton","level":"atoms","category":"Inputs","keywords":["add","button","list","plus"],"import":"import { AddButton } from \"@antscorp/ui\";","description":"Add a row or item with a labeled plus button.","usage":"Add a row or item with a labeled plus button. Use TextAction for an action that does not need the add glyph.","bestPractices":[{"guidance":true,"description":"Keep label specific about what will be added and let the host update the list in onClick."},{"guidance":false,"description":"Do not expect the button to add data itself; isReadOnly hides the entire editing affordance."}],"anatomy":[{"name":"Plus","required":true,"description":"The theme's ui:add glyph, sized with the button."},{"name":"Label","required":true,"description":"The translated action text, optionally shown in capitals."}],"accessibility":["The button is named by label; uppercase presentation preserves that name."],"props":[{"path":"label","type":"string","required":true,"description":"Accessible label for the button (required for accessibility).\nRendered as visible text by default. When `isIconOnly` is true,\nused as aria-label instead."},{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether the button is disabled."},{"path":"variant","type":"\"ghost\" | \"secondary\" | undefined","required":false,"description":"`ghost` by default; `secondary` for an add that needs more weight."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The button's height, `md` by default; its plus follows it."},{"path":"shape","type":"\"default\" | \"pill\" | undefined","required":false,"description":"`default` keeps the button's own corners; `pill` rounds them fully; the theme colours its outline."},{"path":"case","type":"\"sentence\" | \"upper\" | undefined","required":false,"description":"`sentence` (default) shows the label as given; `upper` in capitals, such as \"+ AND\"."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Read-only: an add is an editing affordance, so nothing is drawn."},{"path":"onClick","type":"() => void","required":true,"description":"Called when the button is pressed."}],"examples":[{"name":"Default","description":"The quiet button that adds a row.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/AddButton.stories.tsx"},{"name":"Secondary","description":"With more weight, for an add that stands alone.","source":"export const Secondary: Story = { args: { variant: \"secondary\" } };","file":"packages/ui/src/atoms/AddButton.stories.tsx"},{"name":"Pill","description":"A pill outlined in the accent colour, as an add under a card's rows.","source":"export const Pill: Story = { args: { variant: \"secondary\", shape: \"pill\", label: \"Refine\" } };","file":"packages/ui/src/atoms/AddButton.stories.tsx"},{"name":"UpperPill","description":"A pill whose label shows in capitals, such as \"+ AND\".","source":"export const UpperPill: Story = {\n args: { variant: \"secondary\", shape: \"pill\", size: \"sm\", case: \"upper\", label: \"And\" },\n};","file":"packages/ui/src/atoms/AddButton.stories.tsx"},{"name":"Outline","description":"The outline add: secondary, `md`, its plus at 18px.","source":"export const Outline: Story = { args: { variant: \"secondary\", size: \"md\" } };","file":"packages/ui/src/atoms/AddButton.stories.tsx"},{"name":"Interactive","description":"Each press adds a row to a count shown beside it.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(0, (rows, set) => (\n <>\n <AddButton\n {...args}\n label={`Add row (${rows} so far)`}\n onClick={() => {\n args.onClick();\n set(rows + 1);\n }}\n />\n </>\n )),\n};","file":"packages/ui/src/atoms/AddButton.stories.tsx"},{"name":"Disabled","description":"Not pressable, e.g. for a read-only list or when a limit is reached.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/atoms/AddButton.stories.tsx"},{"name":"Medium","description":"The `md` height, for a form whose other controls are `md`.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/atoms/AddButton.stories.tsx"},{"name":"ReadOnly","description":"Read-only: an add is an editing affordance, so nothing is drawn.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/atoms/AddButton.stories.tsx"},{"name":"LongLabel","description":"A long label wraps or truncates rather than breaking the layout.","source":"export const LongLabel: Story = {\n args: { label: \"Add another very long named row to the end of this particular list\" },\n};","file":"packages/ui/src/atoms/AddButton.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: { label: \"Add another very long named row to the end of this list\" },\n render: (args) => (\n <Narrow>\n <AddButton {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/AddButton.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <AddButton {...args} />\n </Case>\n <Case caption=\"Upper pill\">\n <AddButton {...args} variant=\"secondary\" shape=\"pill\" size=\"sm\" case=\"upper\" label=\"And\" />\n </Case>\n <Case caption=\"Secondary\">\n <AddButton {...args} variant=\"secondary\" />\n </Case>\n <Case caption=\"Pill\">\n <AddButton {...args} variant=\"secondary\" shape=\"pill\" />\n </Case>\n <Case caption=\"Pill, disabled\">\n <AddButton {...args} variant=\"secondary\" shape=\"pill\" isDisabled />\n </Case>\n <Case caption=\"Disabled\">\n <AddButton {...args} isDisabled />\n </Case>\n <Case caption=\"Medium\">\n <AddButton {...args} size=\"md\" />\n </Case>\n <Case caption=\"Outline (secondary, md)\">\n <AddButton {...args} variant=\"secondary\" size=\"md\" />\n </Case>\n <Case caption=\"Outline, disabled\">\n <AddButton {...args} variant=\"secondary\" size=\"md\" isDisabled />\n </Case>\n <Case caption=\"Read-only (nothing drawn)\">\n <AddButton {...args} isReadOnly />\n </Case>\n <Case caption=\"Narrow, long label\">\n <Narrow>\n <AddButton {...args} label=\"Add another very long named row to the end of this list\" />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/AddButton.stories.tsx"}],"primaryExample":"Default"},{"name":"AddGlyph","level":"atoms","category":"Data display","keywords":["plus","glyph","icon","add"],"import":"import { AddGlyph } from \"@antscorp/ui\";","description":"Draw the theme's decorative plus inside a custom add control.","usage":"Draw the theme's decorative plus inside a custom add control. Use AddButton when a complete labeled action is needed.","bestPractices":[{"guidance":true,"description":"Match size to the containing button and provide its action label on that button."},{"guidance":false,"description":"Do not use the glyph alone as an action or expect it to draw in a theme without ui:add."}],"accessibility":["The glyph is decorative and hidden from assistive technology; the containing button supplies the name."],"props":[{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The height of the button it sits in, `md` by default."}],"examples":[{"name":"Default","description":"The plus of an `sm` button, at 12px.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/AddGlyph.stories.tsx"},{"name":"Medium","description":"The plus of an `md` button, at 18px.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/atoms/AddGlyph.stories.tsx"},{"name":"InSmallButton","description":"In an `sm` secondary button, as a button draws it.","source":"export const InSmallButton: Story = {\n render: (args) => (\n <Button label=\"Add row\" variant=\"secondary\" size=\"sm\" icon={<AddGlyph {...args} />} />\n ),\n};","file":"packages/ui/src/atoms/AddGlyph.stories.tsx"},{"name":"InMediumButton","description":"In an `md` secondary button, the outline add.","source":"export const InMediumButton: Story = {\n args: { size: \"md\" },\n render: (args) => (\n <Button label=\"Add row\" variant=\"secondary\" size=\"md\" icon={<AddGlyph {...args} />} />\n ),\n};","file":"packages/ui/src/atoms/AddGlyph.stories.tsx"},{"name":"Disabled","description":"In a disabled button: it takes the button's dimmed colour.","source":"export const Disabled: Story = {\n args: { size: \"md\" },\n render: (args) => (\n <Button\n label=\"Add row\"\n variant=\"secondary\"\n size=\"md\"\n isDisabled\n icon={<AddGlyph {...args} />}\n />\n ),\n};","file":"packages/ui/src/atoms/AddGlyph.stories.tsx"},{"name":"InNarrowFrame","description":"In a long-labelled button in a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: { size: \"md\" },\n render: (args) => (\n <Narrow>\n <Button\n label=\"Add another very long named row to the end of this list\"\n variant=\"secondary\"\n size=\"md\"\n icon={<AddGlyph {...args} />}\n />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/AddGlyph.stories.tsx"},{"name":"AllStates","description":"Each size alone and in its button.","source":"export const AllStates: Story = {\n render: () => (\n <Gallery>\n <Case caption=\"sm\">\n <AddGlyph size=\"sm\" />\n </Case>\n <Case caption=\"md\">\n <AddGlyph size=\"md\" />\n </Case>\n <Case caption=\"In an sm button\">\n <Button label=\"Add row\" variant=\"secondary\" size=\"sm\" icon={<AddGlyph size=\"sm\" />} />\n </Case>\n <Case caption=\"In an md button\">\n <Button label=\"Add row\" variant=\"secondary\" size=\"md\" icon={<AddGlyph size=\"md\" />} />\n </Case>\n <Case caption=\"In a disabled md button\">\n <Button\n label=\"Add row\"\n variant=\"secondary\"\n size=\"md\"\n isDisabled\n icon={<AddGlyph size=\"md\" />}\n />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/AddGlyph.stories.tsx"}],"primaryExample":"Default"},{"name":"AntsomiTheme","level":"theme","category":"Theme","keywords":["theme","provider","tokens","icons","mode"],"import":"import { AntsomiTheme } from \"@antscorp/ui\";","description":"Apply the Antsomi base theme to a subtree and forward Astryx's light, dark or system mode.","usage":"Apply the Antsomi base theme to a subtree and forward Astryx's light, dark or system mode. Extend antsomiTheme with defineTheme and pass the result through theme for a scoped override.","bestPractices":[{"guidance":true,"description":"Import @antscorp/ui/theme.css separately and load Roboto in the host; nest providers when an override should affect only a subtree."},{"guidance":false,"description":"Do not expect the provider to load CSS or fonts, or replace theme tokens with hard-coded colors in consuming views."}],"props":[{"path":"mode","type":"ThemeMode | undefined","required":false,"description":"Color mode - 'system' follows OS preference"},{"path":"children","type":"React.ReactNode","required":true,"description":"Children to render"},{"path":"theme","type":"DefinedTheme","required":false,"description":"Optional override created with `defineTheme({ extends: antsomiTheme, … })`."}],"examples":[{"name":"Default","description":"The Antsomi base theme is selected without a theme prop.","source":"export const Default: Story = {};","file":"packages/ui/src/theme/AntsomiTheme.stories.tsx"},{"name":"Extended","description":"Extends the base theme, replacing only the accent token with a palette value.","source":"export const Extended: Story = { args: { theme: extendedTheme } };","file":"packages/ui/src/theme/AntsomiTheme.stories.tsx"},{"name":"Dark","description":"Forwards Astryx's dark mode to the subtree.","source":"export const Dark: Story = { args: { mode: \"dark\" } };","file":"packages/ui/src/theme/AntsomiTheme.stories.tsx"},{"name":"Nested","description":"A nested override affects only its own controls; outer controls retain the base theme.","source":"export const Nested = {\n render: (args) => (\n <AntsomiTheme {...args}>\n <VStack gap={3}>\n <Text>Base theme</Text>\n {args.children}\n <AntsomiTheme theme={extendedTheme} mode={args.mode ?? \"light\"}>\n <VStack gap={3}>\n <Text>Extended theme</Text>\n {args.children}\n </VStack>\n </AntsomiTheme>\n </VStack>\n </AntsomiTheme>\n ),\n} satisfies Story;","file":"packages/ui/src/theme/AntsomiTheme.stories.tsx"},{"name":"AllStates","description":"The base, extended, dark and nested theme states together.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <AntsomiTheme {...args} />\n </Case>\n <Case caption=\"Extended\">\n <AntsomiTheme {...args} theme={extendedTheme} />\n </Case>\n <Case caption=\"Dark\">\n <AntsomiTheme {...args} mode=\"dark\" />\n </Case>\n <Case caption=\"Nested\">{Nested.render(args)}</Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/theme/AntsomiTheme.stories.tsx"}],"primaryExample":"Default"},{"name":"CheckGroup","level":"molecules","category":"Selection","keywords":["checkboxes","multiple","options","group"],"import":"import { CheckGroup } from \"@antscorp/ui\";","description":"Choose several values from a short list of labeled checkboxes.","usage":"Choose several values from a short list of labeled checkboxes. Use ToggleGrid when a compact grid of toggle buttons fits the choices better.","bestPractices":[{"guidance":true,"description":"Keep value controlled and accept onChange values in options order; use vertical orientation when labels need more room."},{"guidance":false,"description":"Do not use checkboxes for mutually exclusive choices; use RevealRadioGroup."}],"anatomy":[{"name":"Group label","required":true,"description":"Names the set through FieldGroup."},{"name":"Checkboxes","required":true,"description":"One controlled checkbox per option, in a wrapping row or column."},{"name":"Error","required":false,"description":"A translated problem with the selection."}],"accessibility":["Supply label for the group and a clear label on every option."],"props":[{"path":"label","type":"string","required":true,"description":"Names the group; read out before each field in it."},{"path":"isDisabled","type":"boolean","required":false,"description":"Disables fields through the unframed fieldset; with `frame=\"card\"`, disable children yourself."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the group as a whole, already in the user's language."},{"path":"options","type":"readonly CheckGroupOption<string>[]","required":true,"description":"The checkboxes, in order."},{"path":"options[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What the picked list holds for this checkbox."},{"path":"options[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The checkbox's text."},{"path":"value","type":"readonly string[]","required":true,"description":"The picked values."},{"path":"onChange","type":"(value: string[]) => void","required":true,"description":"Called with the picked values, in the order of `options`."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The checkboxes' size, `md` by default (Astryx's default; the theme draws it at 18px)."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows the pick, not dimmed, and blocks changes."},{"path":"orientation","type":"\"horizontal\" | \"vertical\" | undefined","required":false,"description":"In a wrapping row (default) or a column."}],"examples":[{"name":"Default","description":"Some boxes checked, in a wrapping row.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"NoneSelected","description":"None checked.","source":"export const NoneSelected: Story = { args: { value: [] } };","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"AllSelected","description":"Every box checked.","source":"export const AllSelected: Story = { args: { value: weekdays.map((day) => day.value) } };","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"Interactive","description":"Checking boxes changes the pick, kept in the order of the options.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<string[]>([], (value, set) => (\n <CheckGroup\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"Vertical","description":"In a column.","source":"export const Vertical: Story = { args: { orientation: \"vertical\" } };","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"WithError","description":"With a problem with the pick.","source":"export const WithError: Story = { args: { value: [], error: \"Pick at least one day.\" } };","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"Disabled","description":"Dimmed and blocked.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"ReadOnly","description":"Shows the pick, not dimmed, and blocks changes.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"Small","description":"The compact `sm` size.","source":"export const Small: Story = { args: { size: \"sm\" } };","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"SingleOption","description":"A single option.","source":"export const SingleOption: Story = { args: { options: weekdays.slice(0, 1), value: [] } };","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"LongContent","description":"Many options with long labels.","source":"export const LongContent: Story = {\n args: {\n options: Array.from({ length: 12 }, (_, index) => ({\n value: String(index),\n label: `Option number ${index + 1} with a rather long name`,\n })),\n value: [\"0\", \"5\"],\n },\n};","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, where the row wraps.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <CheckGroup {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Some checked\">\n <CheckGroup {...args} />\n </Case>\n <Case caption=\"None checked, error\">\n <CheckGroup {...args} value={[]} error=\"Pick at least one day.\" />\n </Case>\n <Case caption=\"Vertical\">\n <CheckGroup {...args} orientation=\"vertical\" />\n </Case>\n <Case caption=\"Disabled\">\n <CheckGroup {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <CheckGroup {...args} isReadOnly />\n </Case>\n <Case caption=\"Small\">\n <CheckGroup {...args} size=\"sm\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/CheckGroup.stories.tsx"}],"primaryExample":"Default"},{"name":"CheckRow","level":"molecules","category":"Selection","keywords":["checkbox","row","pick","list"],"import":"import { CheckRow } from \"@antscorp/ui\";","description":"Show one checkbox choice with optional supporting text and end content.","usage":"Show one checkbox choice with optional supporting text and end content. Use RemovableRow for a value that is already picked and needs a removal action.","bestPractices":[{"guidance":true,"description":"Keep label meaningful because it names the checkbox, and reserve hoverContent for supplementary row actions."},{"guidance":false,"description":"Do not assume clicking the text toggles the checkbox; the checkbox itself is the control."}],"anatomy":[{"name":"Checkbox","required":true,"description":"The controlled boolean choice."},{"name":"Text","required":true,"description":"A one-line label and optional one-line description."},{"name":"End content","required":false,"description":"A value optionally replaced by hover actions."}],"accessibility":["label names the checkbox; provide names on interactive controls supplied through hoverContent."],"props":[{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether the checkbox is disabled."},{"path":"isReadOnly","type":"boolean","required":false,"default":"false","description":"Whether the checkbox is read-only.\nDisplays the current state at full opacity but prevents interaction.\nUnlike `isDisabled`, read-only checkboxes are not visually dimmed."},{"path":"label","type":"string","required":true,"description":"The row's text and the checkbox's name; one line, the whole text in a tooltip when cut."},{"path":"description","type":"string | undefined","required":false,"description":"A second line under the text, such as an id under a name; one line too."},{"path":"value","type":"boolean","required":true,"description":"Whether the box is ticked."},{"path":"onChange","type":"(value: boolean) => void","required":true,"description":"Called with the box's next state."},{"path":"endContent","type":"ReactNode","required":false,"description":"Content at the end of the row, such as a count."},{"path":"hoverContent","type":"ReactNode","required":false,"description":"Content shown instead of `endContent` while the row is hovered or holds the focus, such as a\nlink to the item; the two share one place, so nothing moves. None without it."}],"examples":[{"name":"Default","description":"A box and its text.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/CheckRow.stories.tsx"},{"name":"Filled","description":"Ticked, with a second line and a count at the end.","source":"export const Filled: Story = { args: { value: true, description: \"ID 1001\", endContent: count } };","file":"packages/ui/src/molecules/CheckRow.stories.tsx"},{"name":"Interactive","description":"Ticking changes the box.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(false, (value, set) => (\n <CheckRow\n {...args}\n value={value}\n endContent={count}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/CheckRow.stories.tsx"},{"name":"HoverContent","description":"A count at rest, an action instead while the row is hovered or focused.","source":"export const HoverContent: Story = { args: { endContent: count, hoverContent: open } };","file":"packages/ui/src/molecules/CheckRow.stories.tsx"},{"name":"EndTip","description":"A count not ready yet, explained by a tip.","source":"export const EndTip: Story = {\n args: {\n endContent: (\n <>\n <Text color=\"secondary\">--</Text>\n <InfoTip label=\"Not counted yet.\" />\n </>\n ),\n },\n};","file":"packages/ui/src/molecules/CheckRow.stories.tsx"},{"name":"Disabled","description":"Blocked and dimmed, e.g. at the most that can be picked.","source":"export const Disabled: Story = { args: { isDisabled: true, endContent: count } };","file":"packages/ui/src/molecules/CheckRow.stories.tsx"},{"name":"ReadOnly","description":"Read-only: the state shows, not dimmed, and cannot change.","source":"export const ReadOnly: Story = { args: { isReadOnly: true, value: true, endContent: count } };","file":"packages/ui/src/molecules/CheckRow.stories.tsx"},{"name":"LongText","description":"A long text and second line, each cut to one line; hover for the whole text.","source":"export const LongText: Story = {\n args: { label: LONG, description: \"ID 1234567890-abcdefghij-klmnopqrst\", endContent: count },\n render: (args) => (\n <Narrow>\n <CheckRow {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/CheckRow.stories.tsx"},{"name":"Many","description":"Many rows one under the other, as a list of options.","source":"export const Many: Story = {\n render: (args) => (\n <VStack gap={1.5}>\n {Array.from({ length: 12 }, (_, index) => (\n <CheckRow\n key={index}\n {...args}\n label={`City ${index + 1}`}\n value={index % 3 === 0}\n endContent={<Text color=\"secondary\">{(index + 1) * 100}</Text>}\n hoverContent={open}\n />\n ))}\n </VStack>\n ),\n};","file":"packages/ui/src/molecules/CheckRow.stories.tsx"},{"name":"Loading","description":"Loading: `RowSkeletons variant=\"check\"` in the rows' place, as high as they are.","source":"export const Loading: Story = {\n render: () => <RowSkeletons variant=\"check\" labels={{ loading: \"Loading\" }} />,\n};","file":"packages/ui/src/molecules/CheckRow.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <CheckRow {...args} description=\"ID 1001\" endContent={count} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/CheckRow.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <CheckRow {...args} />\n </Case>\n <Case caption=\"Ticked, second line and count\">\n <CheckRow {...args} value description=\"ID 1001\" endContent={count} />\n </Case>\n <Case caption=\"Hover content (hover the row)\">\n <CheckRow {...args} endContent={count} hoverContent={open} />\n </Case>\n <Case caption=\"Disabled\">\n <CheckRow {...args} isDisabled endContent={count} />\n </Case>\n <Case caption=\"Read-only\">\n <CheckRow {...args} isReadOnly value endContent={count} />\n </Case>\n <Case caption=\"Long, narrow\">\n <Narrow>\n <CheckRow {...args} label={LONG} endContent={count} />\n </Narrow>\n </Case>\n <Case caption=\"Loading\">\n <RowSkeletons variant=\"check\" count={2} labels={{ loading: \"Loading\" }} />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/CheckRow.stories.tsx"}],"primaryExample":"Default"},{"name":"Chip","level":"atoms","category":"Data display","keywords":["pill","picked value","remove"],"import":"import { Chip } from \"@antscorp/ui\";","description":"Show one picked value in a compact pill.","usage":"Show one picked value in a compact pill. Supply onRemove and labels.remove together to offer removal; the host owns the picked values.","bestPractices":[{"guidance":true,"description":"Use a short, recognizable label and provide a translated remove label naming the value."},{"guidance":false,"description":"Do not use colour alone to explain why a value is invalid; provide surrounding validation text."}],"anatomy":[{"name":"Label","required":true,"description":"The picked value, truncated with a tooltip when needed."},{"name":"Remove button","required":false,"description":"Removes the value; hidden in read-only mode."}],"accessibility":["Name the remove action through labels.remove.","Hover removal also appears on keyboard focus; keep the action reachable with Tab."],"related":[{"name":"astryx/Badge","when":"Show an informational status or category rather than a removable picked value."}],"props":[{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether the token is disabled."},{"path":"label","type":"string","required":true,"description":"The chip's text; cut short with an ellipsis past `maxWidth`, and shown whole in a tooltip."},{"path":"tone","type":"\"accent\" | \"danger\" | \"neutral\" | undefined","required":false,"description":"`neutral` (default) is the plain grey chip; `accent` the pale accent one; `danger` a value no longer valid."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The chip's height, `sm` or `md` (default), as the controls beside it."},{"path":"maxWidth","type":"number | undefined","required":false,"description":"The widest the chip grows, in pixels; 200 by default."},{"path":"labels","type":"{ readonly remove?: string | undefined; } | undefined","required":false,"description":"The texts the chip shows; the host translates them."},{"path":"labels.remove","type":"string | undefined","required":false,"description":"The name of the remove button, such as \"Remove Web\"; required with `onRemove`, which shows no button without it."},{"path":"onRemove","type":"(() => void) | undefined","required":false,"description":"Called by the remove button; no button without it."},{"path":"isRemoveOnHover","type":"boolean","required":false,"description":"Shows the remove button only while the chip is hovered or has the keyboard, as a small round\nbutton over its end; it stays in the tab order. Without it the button always shows after the text."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Read-only: no remove button."}],"examples":[{"name":"Default","description":"An accent chip whose remove button shows on hover or keyboard focus.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/Chip.stories.tsx"},{"name":"Neutral","description":"The plain grey chip, its remove button always shown.","source":"export const Neutral: Story = { args: { tone: \"neutral\", isRemoveOnHover: false } };","file":"packages/ui/src/atoms/Chip.stories.tsx"},{"name":"Danger","description":"A value no longer valid.","source":"export const Danger: Story = { args: { tone: \"danger\", label: \"Deleted source\" } };","file":"packages/ui/src/atoms/Chip.stories.tsx"},{"name":"WithoutRemove","description":"No remove button: none is offered.","source":"export const WithoutRemove: Story = { render: (args) => <Chip {...args} onRemove={undefined} /> };","file":"packages/ui/src/atoms/Chip.stories.tsx"},{"name":"Interactive","description":"Removing takes the chip away from a row of them.","source":"export const Interactive: Story = {\n render: (args) =>\n withState([\"Website\", \"Mobile app\", \"Kiosk\"], (items, set) => (\n <HStack gap={2} wrap=\"wrap\">\n {items.map((item) => (\n <Chip\n {...args}\n key={item}\n label={item}\n labels={{ remove: `Remove ${item}` }}\n onRemove={() => {\n args.onRemove?.();\n set(items.filter((other) => other !== item));\n }}\n />\n ))}\n </HStack>\n )),\n};","file":"packages/ui/src/atoms/Chip.stories.tsx"},{"name":"LongLabel","description":"A long label is cut short at 200px; the tooltip shows it whole.","source":"export const LongLabel: Story = { args: { label: LONG } };","file":"packages/ui/src/atoms/Chip.stories.tsx"},{"name":"NarrowMaxWidth","description":"A narrower limit.","source":"export const NarrowMaxWidth: Story = { args: { label: LONG, maxWidth: 120 } };","file":"packages/ui/src/atoms/Chip.stories.tsx"},{"name":"Disabled","description":"Dimmed, its remove button blocked.","source":"export const Disabled: Story = { args: { isDisabled: true, isRemoveOnHover: false } };","file":"packages/ui/src/atoms/Chip.stories.tsx"},{"name":"ReadOnly","description":"Read-only: the value without a remove button.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/atoms/Chip.stories.tsx"},{"name":"Medium","description":"The `md` height.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/atoms/Chip.stories.tsx"},{"name":"InNarrowFrame","description":"Many chips wrapping in a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <HStack gap={2} wrap=\"wrap\">\n {[\"Website\", \"Mobile app\", \"Kiosk\", LONG, \"Email\", \"Call centre\"].map((item) => (\n <Chip {...args} key={item} label={item} />\n ))}\n </HStack>\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/Chip.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Accent, remove on hover\">\n <Chip {...args} />\n </Case>\n <Case caption=\"Neutral, remove always\">\n <Chip {...args} tone=\"neutral\" isRemoveOnHover={false} />\n </Case>\n <Case caption=\"Danger\">\n <Chip {...args} tone=\"danger\" label=\"Deleted source\" />\n </Case>\n <Case caption=\"Long label\">\n <Chip {...args} label={LONG} />\n </Case>\n <Case caption=\"Disabled\">\n <Chip {...args} isDisabled isRemoveOnHover={false} />\n </Case>\n <Case caption=\"Read-only\">\n <Chip {...args} isReadOnly />\n </Case>\n <Case caption=\"Medium\">\n <Chip {...args} size=\"md\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/Chip.stories.tsx"}],"primaryExample":"Default"},{"name":"ChipListAddPopover","level":"organisms","category":"Selection","keywords":["chips","multiple","popover","apply"],"import":"import { ChipListAddPopover } from \"@antscorp/ui\";","description":"Pick several values as removable chips through a searchable checkbox popover.","usage":"Pick several values as removable chips through a searchable checkbox popover. Checkbox edits remain a draft until Apply; closing another way drops them.","bestPractices":[{"guidance":true,"description":"Use onRemove to confirm direct chip removal, and separately handle removals arriving in onChange from Apply."},{"guidance":false,"description":"Do not discard unknown stored values just because options has not loaded them; they remain visible as invalid values or loading placeholders."}],"anatomy":[{"name":"Picked values","required":false,"description":"Tokens or pill Chips when values are picked, with unknown values retained."},{"name":"Add button","required":false,"description":"Opens the checkbox popover unless read-only."},{"name":"Popover","required":false,"description":"Search, draft checkboxes and Apply."},{"name":"Error","required":false,"description":"A translated selection problem."}],"accessibility":["Supply translated search, add and apply labels plus labels.remove for each chip; labels.loading names loading placeholders."],"related":[{"name":"TransferPicker","when":"Keep available rows and picked values visible together with immediate changes."}],"props":[{"path":"label","type":"string","required":true,"description":"Names the list."},{"path":"options","type":"readonly ChipListOption<string>[]","required":true,"description":"What can be picked."},{"path":"options[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What the picked list holds for this option."},{"path":"options[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The chip's and the checkbox's text."},{"path":"options[].isInvalid","type":"boolean","required":false,"description":"Flagged as no longer valid: its chip shows in the error tone."},{"path":"value","type":"readonly string[]","required":true,"description":"The picked values, as chips. A value missing from `options` shows by itself, as invalid."},{"path":"onChange","type":"(value: string[]) => void","required":true,"description":"Called with the picked values: those of `options` in their order, then any unknown ones."},{"path":"onRemove","type":"((value: string) => void) | undefined","required":false,"description":"When given, a chip's remove button calls it with the value instead of `onChange`, so the host\ncan ask first. Removals made through the popover's Apply arrive in `onChange`; the host can\ncompare the next list with `value` there to confirm those too."},{"path":"labels","type":"{ readonly add: string; readonly search: string; readonly apply: string; readonly noMatch: string; readonly remove: (label: string) => string; readonly empty?: string | undefined; readonly loading?: string | undefined; }","required":true,"description":"The texts the view shows; the host translates them."},{"path":"labels.add","type":"string","required":false,"requiredWhenParent":true,"description":"The add button's text and the popover's name, such as \"Add source\"."},{"path":"labels.search","type":"string","required":false,"requiredWhenParent":true,"description":"The search box's name and placeholder."},{"path":"labels.apply","type":"string","required":false,"requiredWhenParent":true,"description":"The popover's apply button, such as \"Apply\"."},{"path":"labels.noMatch","type":"string","required":false,"requiredWhenParent":true,"description":"Shown in the popover when the search matches nothing."},{"path":"labels.remove","type":"(label: string) => string","required":false,"requiredWhenParent":true,"description":"The name of a chip's remove button, such as \"Remove Web\"."},{"path":"labels.empty","type":"string | undefined","required":false,"description":"Shown while nothing is picked."},{"path":"labels.loading","type":"string | undefined","required":false,"description":"The name of the loading status, such as \"Loading\"; used while `isLoading`."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The input's and buttons' size, `md` by default; the checkboxes are always `md`, as every checkbox."},{"path":"chipTone","type":"\"accent\" | \"neutral\" | undefined","required":false,"description":"Draws the picked values as pill `Chip`s of this tone (an invalid one in `danger`), spaced\nwider; without it they are small tokens."},{"path":"isRemoveOnHover","type":"boolean","required":false,"description":"With `chipTone`, a chip's remove button shows only while it is hovered or has the keyboard."},{"path":"addShape","type":"\"default\" | \"pill\" | undefined","required":false,"description":"The add button's shape: `default`, or `pill` for a fully rounded one."},{"path":"isOpen","type":"boolean","required":false,"description":"Whether the popover is open; give it with `onOpenChange` to control it."},{"path":"onOpenChange","type":"((isOpen: boolean) => void) | undefined","required":false,"description":"Called when the popover opens or closes (closing without Apply drops the ticks)."},{"path":"isLoading","type":"boolean","required":false,"description":"The options are loading: a picked value `options` already knows is its chip, every other one\na skeleton chip (none while nothing is picked), and the add button is blocked."},{"path":"loadingCount","type":"number | undefined","required":false,"description":"While `isLoading`, this many skeleton chips stand in for the whole list, picked or not: for a\nlist that loads anew, such as the sources of an event just picked. Without it, the chips follow\n`isLoading`'s rule."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the list; the add button and the remove buttons are blocked, and nothing is hidden."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Read-only: no add button and no remove buttons."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the pick, already in the user's language."}],"examples":[{"name":"Default","description":"Two chips and the add button.","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"Empty","description":"Nothing picked: the empty text.","source":"export const Empty: Story = { args: { value: [] } };","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"Interactive","description":"Picking in the popover and removing chips change the list.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<string[]>([\"website\"], (value, set) => (\n <ChipListAddPopover\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"PopoverOpen","description":"The popover open with a pending tick: Apply is enabled; closing it instead drops the tick.","source":"export const PopoverOpen: Story = {\n args: { isOpen: true, onOpenChange: fn<() => void>() },\n play: async () => {\n await userEvent.click(\n await within(document.body).findByRole(\"checkbox\", { name: \"Call center\" }),\n );\n },\n};","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"InvalidChip","description":"A chip for a value that is flagged invalid, or missing from the options, shows in red.","source":"export const InvalidChip: Story = {\n args: {\n options: options.map((option, index) => ({\n value: option.value,\n label: option.label,\n isInvalid: index === 1,\n })),\n value: [\"website\", \"mobile-app\", \"gone-id\"],\n },\n};","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"ConfirmedRemoval","description":"The host asks before a removal: `onRemove` takes it over from `onChange`.","source":"export const ConfirmedRemoval: Story = { args: { onRemove: fn<(value: string) => void>() } };","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"Loading","description":"The options are loading, the picked labels known (the last list): the chips stay, the add button is blocked.","source":"export const Loading: Story = { args: { isLoading: true } };","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"LoadingUnknownLabels","description":"The options are loading, the picked labels unknown: one skeleton chip per picked value.","source":"export const LoadingUnknownLabels: Story = { args: { isLoading: true, options: [] } };","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"LoadingList","description":"A list loading anew (the sources of an event just picked): five skeleton chips stand in for it.","source":"export const LoadingList: Story = { args: { isLoading: true, loadingCount: 5 } };","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"WithError","description":"A problem with the pick.","source":"export const WithError: Story = { args: { error: \"A source no longer exists.\" } };","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"Disabled","description":"Dimmed: the add and remove buttons are blocked, and nothing is hidden.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"ReadOnly","description":"Read-only: no add button, no remove buttons.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"Medium","description":"The `md` size.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"SingleChip","description":"A single chip.","source":"export const SingleChip: Story = { args: { value: [\"website\"] } };","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"ManyChips","description":"Thirty chips, which wrap.","source":"export const ManyChips: Story = {\n args: {\n options: Array.from({ length: 30 }, (_, index) => ({\n value: `source-${index}`,\n label: `Source number ${index + 1}`,\n })),\n value: Array.from({ length: 30 }, (_, index) => `source-${index}`),\n },\n};","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"AccentChips","description":"Pill chips in the accent tone, their remove buttons shown on hover or focus, and a pill add button.","source":"export const AccentChips: Story = {\n args: { chipTone: \"accent\", isRemoveOnHover: true, addShape: \"pill\" },\n};","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"AccentChipsLong","description":"Pill chips with a long label, cut short with a tooltip, and an invalid one.","source":"export const AccentChipsLong: Story = {\n args: {\n ...AccentChips.args,\n options: [\n ...options,\n { value: \"long\", label: \"A source with a very long name that does not fit the chip\" },\n ],\n value: [\"website\", \"long\", \"gone-id\"],\n },\n};","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"AccentChipsReadOnly","description":"Pill chips, read-only: no remove buttons and no add button.","source":"export const AccentChipsReadOnly: Story = { args: { ...AccentChips.args, isReadOnly: true } };","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, where the chips wrap.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <ChipListAddPopover {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Picked\">\n <ChipListAddPopover {...args} />\n </Case>\n <Case caption=\"Empty\">\n <ChipListAddPopover {...args} value={[]} />\n </Case>\n <Case caption=\"Invalid chip, error\">\n <ChipListAddPopover {...args} value={[\"website\", \"gone-id\"]} error=\"A source is gone.\" />\n </Case>\n <Case caption=\"Loading, labels known\">\n <ChipListAddPopover {...args} isLoading />\n </Case>\n <Case caption=\"Loading, labels unknown\">\n <ChipListAddPopover {...args} options={[]} isLoading />\n </Case>\n <Case caption=\"Loading, one known and one unknown\">\n <ChipListAddPopover {...args} value={[\"website\", \"gone-id\"]} isLoading />\n </Case>\n <Case caption=\"Loading anew, five skeleton chips\">\n <ChipListAddPopover {...args} isLoading loadingCount={5} />\n </Case>\n <Case caption=\"Loading, read-only, medium, labels unknown\">\n <ChipListAddPopover {...args} options={[]} isLoading isReadOnly size=\"md\" />\n </Case>\n <Case caption=\"Disabled\">\n <ChipListAddPopover {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <ChipListAddPopover {...args} isReadOnly />\n </Case>\n <Case caption=\"Medium\">\n <ChipListAddPopover {...args} size=\"md\" />\n </Case>\n <Case caption=\"Accent pill chips\">\n <ChipListAddPopover\n {...args}\n chipTone=\"accent\"\n isRemoveOnHover\n addShape=\"pill\"\n value={[\"website\", \"gone-id\"]}\n />\n </Case>\n <Case caption=\"Accent pill chips, disabled\">\n <ChipListAddPopover {...args} chipTone=\"accent\" addShape=\"pill\" isDisabled />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/ChipListAddPopover.stories.tsx"}],"primaryExample":"Default"},{"name":"ChipSkeletons","level":"atoms","category":"Feedback","keywords":["loading","skeleton","chips","placeholder"],"import":"import { ChipSkeletons } from \"@antscorp/ui\";","description":"Reserve the shape of picked values while their labels load.","usage":"Reserve the shape of picked values while their labels load. Use RowSkeletons for a vertical options list.","bestPractices":[{"guidance":true,"description":"Match count, size and shape to the values being loaded, and supply label for the loading status."},{"guidance":false,"description":"Do not replace already known labels with skeletons unless the whole list is being reloaded."}],"anatomy":[{"name":"Chip bars","required":true,"description":"A row of rounded shimmer bars with varied widths."}],"accessibility":["The wrapper has role status and uses label as its accessible name."],"props":[{"path":"label","type":"string | undefined","required":false,"description":"The loading status's accessible name, e.g. \"Loading\"."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The chips' height step, `md` by default."},{"path":"shape","type":"\"chip\" | \"token\" | undefined","required":false,"description":"`chip` is as high as the controls (`Chip`); `token` is Astryx's Token, 8px shorter."},{"path":"count","type":"number | undefined","required":false,"description":"How many chips stand in, 3 by default; as many as the values that load, when known."}],"examples":[{"name":"Default","description":"As high as Astryx's Token chips.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/ChipSkeletons.stories.tsx"},{"name":"ChipShape","description":"As high as the controls, for `Chip`s.","source":"export const ChipShape: Story = { args: { shape: \"chip\" } };","file":"packages/ui/src/atoms/ChipSkeletons.stories.tsx"},{"name":"Medium","description":"The taller step.","source":"export const Medium: Story = { args: { size: \"md\", shape: \"chip\" } };","file":"packages/ui/src/atoms/ChipSkeletons.stories.tsx"},{"name":"One","description":"One chip per value that loads: here a single one.","source":"export const One: Story = { args: { count: 1 } };","file":"packages/ui/src/atoms/ChipSkeletons.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <ChipSkeletons {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/ChipSkeletons.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Token, sm\">\n <ChipSkeletons {...args} />\n </Case>\n <Case caption=\"Chip, sm\">\n <ChipSkeletons {...args} shape=\"chip\" />\n </Case>\n <Case caption=\"Chip, md\">\n <ChipSkeletons {...args} shape=\"chip\" size=\"md\" />\n </Case>\n <Case caption=\"One\">\n <ChipSkeletons {...args} count={1} />\n </Case>\n <Case caption=\"Narrow\">\n <Narrow>\n <ChipSkeletons {...args} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/ChipSkeletons.stories.tsx"}],"primaryExample":"Default"},{"name":"ChoiceDialog","level":"molecules","category":"Overlays","keywords":["dialog","choice","confirm","radio"],"import":"import { ChoiceDialog } from \"@antscorp/ui\";","description":"Ask for one choice before confirming a change, with optional details under the selected option.","usage":"Ask for one choice before confirming a change, with optional details under the selected option. Use ConfirmDialog when no choice is required.","bestPractices":[{"guidance":true,"description":"Keep value and option details controlled, and use isConfirmDisabled while the selected details are incomplete."},{"guidance":false,"description":"Do not apply the choice merely when onValueChange fires; onConfirm is the explicit confirmation action."}],"anatomy":[{"name":"Header","required":true,"description":"The question supplied by title."},{"name":"Description","required":true,"description":"Explains what the user is choosing."},{"name":"Choices","required":true,"description":"A radio list revealing the selected option's detail."},{"name":"Actions","required":true,"description":"Translated cancel and confirm buttons."}],"accessibility":["Provide descriptive title and option labels; closing the dialog, including Escape, calls onCancel."],"props":[{"path":"isOpen","type":"boolean","required":true,"description":"Open while a choice waits for an answer."},{"path":"title","type":"string","required":true,"description":"The question, e.g. \"How should these rows be saved?\"."},{"path":"description","type":"string","required":true,"description":"What the user is choosing before confirming."},{"path":"options","type":"readonly { readonly value: RadioListProps[\"value\"]; readonly label: string; readonly detail?: ReactNode; }[]","required":true,"description":"The choices, in order; only the selected one's detail is shown."},{"path":"options[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What `onValueChange` gets when this option is picked."},{"path":"options[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The option's text."},{"path":"options[].detail","type":"ReactNode","required":false,"description":"Extra content below this option while it is selected; may contain controls."},{"path":"value","type":"string","required":true,"description":"The selected option's value, controlled by the caller."},{"path":"onValueChange","type":"(value: string) => void","required":true,"description":"Called with the value of the option picked."},{"path":"labels","type":"{ readonly confirm: string; readonly cancel: string; }","required":true,"description":"The texts of the two buttons; the host translates them."},{"path":"labels.confirm","type":"string","required":false,"requiredWhenParent":true,"description":"The confirming button, e.g. \"Save\"."},{"path":"labels.cancel","type":"string","required":false,"requiredWhenParent":true,"description":"The button that drops the choice, e.g. \"Cancel\"."},{"path":"isConfirmDisabled","type":"boolean","required":false,"description":"Blocks confirmation while the selected option's details are incomplete."},{"path":"onConfirm","type":"() => void","required":true,"description":"Called when the user confirms the current choice."},{"path":"onCancel","type":"() => void","required":true,"description":"Called when the user cancels or closes the dialog, including Escape."}],"examples":[{"name":"Default","description":"Open with the first option chosen and no extra controls.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/ChoiceDialog.stories.tsx"},{"name":"Closed","description":"Closed: nothing shows.","source":"export const Closed: Story = { args: { isOpen: false } };","file":"packages/ui/src/molecules/ChoiceDialog.stories.tsx"},{"name":"WithDetail","description":"The second option chosen, with a usable text field directly below it.","source":"export const WithDetail: Story = { args: { value: \"copy\" } };","file":"packages/ui/src/molecules/ChoiceDialog.stories.tsx"},{"name":"Interactive","description":"Picking another option reveals its controls; the caller owns the selected value.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(args.value, (value, set) => (\n <ChoiceDialog\n {...args}\n value={value}\n onValueChange={(next) => {\n args.onValueChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/ChoiceDialog.stories.tsx"},{"name":"ConfirmDisabled","description":"Confirmation is blocked while an option's required details are incomplete.","source":"export const ConfirmDisabled: Story = {\n args: {\n value: \"copy\",\n isConfirmDisabled: true,\n options: [\n { value: \"replace\", label: \"Replace existing rows\" },\n {\n value: \"copy\",\n label: \"Save a separate copy\",\n detail: <TextInput label=\"Copy name\" value=\"\" onChange={fn<(value: string) => void>()} />,\n },\n ],\n },\n};","file":"packages/ui/src/molecules/ChoiceDialog.stories.tsx"},{"name":"LongContent","description":"Long question, description, option labels and buttons wrap without shortening the text.","source":"export const LongContent: Story = {\n args: {\n title: \"How should all of the selected rows be saved alongside the rows already in this table?\",\n description:\n \"Choose whether to replace every row already saved in this table or keep the existing rows and save all of the selected rows in a separate copy with a name of your choice.\",\n options: [\n { value: \"replace\", label: \"Replace all of the existing rows with the newly selected rows\" },\n {\n value: \"copy\",\n label: \"Keep all existing rows and save the selected rows together in a separate copy\",\n },\n ],\n labels: { confirm: \"Save all selected rows\", cancel: \"Keep everything as it is\" },\n },\n};","file":"packages/ui/src/molecules/ChoiceDialog.stories.tsx"},{"name":"ManyOptions","description":"Ten choices exercise the dialog's scrollable content.","source":"export const ManyOptions: Story = {\n args: {\n value: \"format-1\",\n options: Array.from({ length: 10 }, (_, index) => ({\n value: `format-${index + 1}`,\n label: `Saved format ${index + 1}`,\n })),\n },\n};","file":"packages/ui/src/molecules/ChoiceDialog.stories.tsx"},{"name":"Empty","description":"An empty collection; the caller blocks confirmation until choices become available.","source":"export const Empty: Story = { args: { options: [], isConfirmDisabled: true } };","file":"packages/ui/src/molecules/ChoiceDialog.stories.tsx"},{"name":"SingleOption","description":"Just one available option.","source":"export const SingleOption: Story = { args: { options: options.slice(0, 1) } };","file":"packages/ui/src/molecules/ChoiceDialog.stories.tsx"},{"name":"InNarrowFrame","description":"Long content inside a real 280px viewport, so the modal's viewport clamping is exercised.","source":"export const InNarrowFrame: Story = {\n render: (_args, { id }) => (\n // A raw iframe gives top-layer modal content its own viewport; a surrounding box cannot.\n <iframe\n title=\"Choice dialog at 280px\"\n width={280}\n height={600}\n src={`./iframe.html?id=${id.split(\"--\")[0]}--long-content&viewMode=story`}\n />\n ),\n};","file":"packages/ui/src/molecules/ChoiceDialog.stories.tsx"},{"name":"AllStates","description":"The key states, each in its own viewport so their modal layers do not overlap.","source":"export const AllStates: Story = {\n render: (_args, { id }) => (\n <Gallery>\n {[\n { caption: \"Default\", story: \"default\" },\n { caption: \"Second option with detail\", story: \"with-detail\" },\n { caption: \"Confirmation disabled\", story: \"confirm-disabled\" },\n { caption: \"Long content\", story: \"long-content\" },\n { caption: \"Many options\", story: \"many-options\" },\n ].map(({ caption, story }) => (\n <Case key={story} caption={caption}>\n {/* A raw iframe isolates each modal's top layer and focus from the other previews. */}\n <iframe\n title={caption}\n width=\"100%\"\n height={600}\n src={`./iframe.html?id=${id.split(\"--\")[0]}--${story}&viewMode=story`}\n />\n </Case>\n ))}\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/ChoiceDialog.stories.tsx"}],"primaryExample":"Default"},{"name":"ChoiceTiles","level":"molecules","category":"Selection","keywords":["tiles","choices","actions","cards"],"import":"import { ChoiceTiles } from \"@antscorp/ui\";","description":"Offer a few actions as cards with an icon, title and short description.","usage":"Offer a few actions as cards with an icon, title and short description. Use RevealRadioGroup when a persistent selected value is required.","bestPractices":[{"guidance":true,"description":"Make each title distinguish the action and use onSelect to show or perform what follows."},{"guidance":false,"description":"Do not use tiles as toggles; this view has no selected state."}],"anatomy":[{"name":"Icon circle","required":true,"description":"Host glyph displayed in an accent circle on each tile."},{"name":"Title","required":true,"description":"The bold action text and button name."},{"name":"Description","required":true,"description":"Supporting text limited to two lines."}],"accessibility":["label names the group; each tile is a button named by its title and supports click, Enter and Space."],"props":[{"path":"label","type":"string","required":true,"description":"The name of the group of tiles, for assistive technology; the host shows its own caption."},{"path":"items","type":"readonly ChoiceTile<string>[]","required":true,"description":"The tiles, in order; they wrap onto more rows when the width runs out."},{"path":"items[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What `onSelect` is called with when the tile is picked."},{"path":"items[].title","type":"string","required":false,"requiredWhenParent":true,"description":"The tile's bold title; it also names its button."},{"path":"items[].description","type":"string","required":false,"requiredWhenParent":true,"description":"A line under the title saying what the choice does."},{"path":"items[].icon","type":"ReactNode","required":false,"requiredWhenParent":true,"description":"The glyph in the tile's circle, drawn in the accent colour, such as an `Icon`."},{"path":"onSelect","type":"(value: string) => void","required":true,"description":"Called with a tile's value when it is picked, by a click, Enter or Space."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims every tile and blocks picking."}],"examples":[{"name":"Default","description":"Four tiles in a row, as a form offers its ways to go on.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/ChoiceTiles.stories.tsx"},{"name":"Empty","description":"No tile offered: nothing shows.","source":"export const Empty: Story = { args: { items: [] } };","file":"packages/ui/src/molecules/ChoiceTiles.stories.tsx"},{"name":"SingleTile","description":"A single tile, when only one way is offered.","source":"export const SingleTile: Story = { args: { items: TILES.slice(0, 1) } };","file":"packages/ui/src/molecules/ChoiceTiles.stories.tsx"},{"name":"Interactive","description":"Picking a tile shows what was picked, as a host shows the editor that follows.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<string | null>(null, (value, set) =>\n value === null ? (\n <ChoiceTiles\n {...args}\n onSelect={(next) => {\n args.onSelect(next);\n set(next);\n }}\n />\n ) : (\n <VStack gap={2}>\n <Text>Picked: {value}</Text>\n <TextAction\n label=\"Start over\"\n onClick={() => {\n set(null);\n }}\n />\n </VStack>\n ),\n ),\n};","file":"packages/ui/src/molecules/ChoiceTiles.stories.tsx"},{"name":"Disabled","description":"Every tile dimmed and blocked, as on a read-only or disabled form.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/ChoiceTiles.stories.tsx"},{"name":"LongContent","description":"Long titles and descriptions: the description stops at two lines.","source":"export const LongContent: Story = {\n args: {\n items: [\n {\n value: \"long\",\n title: \"A rather long title for a tile\",\n description:\n \"A description long enough to need more than the two lines a tile has room for, so it is cut\",\n icon: <Icon icon=\"info\" size=\"lg\" />,\n },\n ...TILES.slice(1),\n ],\n },\n};","file":"packages/ui/src/molecules/ChoiceTiles.stories.tsx"},{"name":"ManyTiles","description":"Many tiles wrap onto more rows.","source":"export const ManyTiles: Story = {\n args: {\n items: Array.from({ length: 10 }, (_, index) => ({\n value: `tile-${index + 1}`,\n title: `Tile ${index + 1}`,\n description: \"One more way\",\n icon: <Icon icon=\"check\" size=\"lg\" />,\n })),\n },\n};","file":"packages/ui/src/molecules/ChoiceTiles.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, the tiles wrap.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <ChoiceTiles {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/ChoiceTiles.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <ChoiceTiles {...args} />\n </Case>\n <Case caption=\"Single tile\">\n <ChoiceTiles {...args} items={TILES.slice(0, 1)} />\n </Case>\n <Case caption=\"Disabled\">\n <ChoiceTiles {...args} isDisabled />\n </Case>\n <Case caption=\"Narrow\">\n <Narrow>\n <ChoiceTiles {...args} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/ChoiceTiles.stories.tsx"}],"primaryExample":"Default"},{"name":"CommitTextField","level":"molecules","category":"Inputs","keywords":["text","draft","commit","blur"],"import":"import { CommitTextField } from \"@antscorp/ui\";","description":"Edit text locally and report a single change on blur or single-line Enter.","usage":"Edit text locally and report a single change on blur or single-line Enter. Both single-line and multiline fields forward controlXstyle and containerXstyle. Use TokenTextField when matched tokens need atomic pills.","bestPractices":[{"guidance":true,"description":"Return accepted text through value; use isDraftKeptOnCommit only when a refused draft should remain visible."},{"guidance":false,"description":"Do not expect Enter to commit a multiline field; it inserts a new line."}],"anatomy":[{"name":"Label","required":true,"description":"The field's accessible name, optionally visually hidden."},{"name":"Input","required":true,"description":"A single-line control or a multiline text area holding a draft."},{"name":"Help and error","required":false,"description":"Translated description and validation text."}],"accessibility":["Always supply label, including when isLabelHidden is true."],"related":[{"name":"TokenTextField","when":"Matched tokens need to appear as atomic pills."}],"props":[{"path":"containerXstyle","type":"StyleXStyles","required":false,"description":"StyleX styles for the field container (the Field box around the label,\ncontrol, and status). Has no effect where no Field box renders: inside\nInputGroup (style the InputGroup instead) and in a `horizontal-labels`\nFormLayout."},{"path":"controlXstyle","type":"StyleXStyles","required":false,"description":"StyleX styles for the visual control box: the element that paints the\nfield border and background and holds its icons and end controls. Never\nthe native input."},{"path":"label","type":"string","required":true,"description":"Label text for the input (always rendered for accessibility)."},{"path":"isLabelHidden","type":"boolean","required":false,"default":"false","description":"Whether to visually hide the label (still accessible to screen readers)."},{"path":"description","type":"string | undefined","required":false,"description":"Description text displayed between the label and input."},{"path":"isRequired","type":"boolean","required":false,"default":"false","description":"Whether the field is required. Mutually exclusive with isOptional."},{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether the input is disabled."},{"path":"isReadOnly","type":"boolean","required":false,"default":"false","description":"Whether the input is read-only.\nThe value is shown at full opacity and still submits with the form, but\ncannot be edited. Unlike `isDisabled`, a read-only input is not dimmed and\nstays in the tab order — use it for a value the user should see and send\nbut not change. `isDisabled` takes precedence when both are set."},{"path":"placeholder","type":"string | undefined","required":false,"description":"Placeholder text shown when the input is empty."},{"path":"width","type":"SizeValue | undefined","required":false,"description":"Width of the field. Numbers are treated as pixels, strings are used as-is\n(e.g. `'100%'`). Sizes the whole field (label, control, and status) so they\nstay aligned, unlike setting width via `controlXstyle`/`controlClassName`/`controlStyle`."},{"path":"value","type":"string","required":true,"description":"The committed text. A new value from outside (an agent, undo) replaces the draft."},{"path":"onCommit","type":"(value: string) => void","required":true,"description":"Called with the draft on blur or Enter, only when it differs from `value`."},{"path":"size","type":"\"md\" | \"sm\"","required":false,"description":"The input's height, `md` by default."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the value, already in the user's language."},{"path":"isMultiline","type":"boolean","required":false,"description":"A text area instead of a single line; Enter then adds a line."},{"path":"rows","type":"number","required":false,"description":"For `isMultiline`: how many rows the text area shows, `4` by default."},{"path":"isDraftKeptOnCommit","type":"boolean","required":false,"description":"`false` (default): after a commit the draft goes back to `value`, so a refused text does not\nstay in the field. `true` keeps the typed text until `value` changes from outside."},{"path":"maxLength","type":"number","required":false,"description":"Limits insertions, preserving existing text and allowing over-limit values to shrink."},{"path":"pattern","type":"() => RegExp","required":false,"description":"Fresh global pattern for whole tokens, counted as one character even in plain text."}],"examples":[{"name":"Default","description":"Type, then leave the field or press Enter to commit.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"StyledParts","description":"Multiline fields forward the same control and Field styles as single-line fields.","source":"export const StyledParts: Story = {\n args: {\n isMultiline: true,\n controlXstyle: partStyles.control,\n containerXstyle: partStyles.container,\n },\n};","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"EmptyWithPlaceholder","description":"Empty, with a placeholder and help under the label.","source":"export const EmptyWithPlaceholder: Story = {\n args: { value: \"\", placeholder: \"Untitled\", description: \"Shown in the list.\" },\n};","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"Interactive","description":"The committed value follows what was typed, as a host would store it.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(args.value, (value, set) => (\n <CommitTextField\n {...args}\n value={value}\n description={`Committed: \"${value}\"`}\n onCommit={(next) => {\n args.onCommit(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"WithError","description":"With a problem with the value.","source":"export const WithError: Story = { args: { error: \"A name is required.\" } };","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"Disabled","description":"Dimmed and blocked.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"ReadOnly","description":"Shows the text, not dimmed, and blocks typing.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"Medium","description":"The `md` height.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"Multiline","description":"A four-row text area; Enter adds a line.","source":"export const Multiline: Story = { args: { isMultiline: true, label: \"Notes\", value: \"\" } };","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"TallMultiline","description":"A text area of eight rows.","source":"export const TallMultiline: Story = {\n args: { isMultiline: true, rows: 8, label: \"Notes\", value: \"\" },\n};","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"Required","description":"Marked as required.","source":"export const Required: Story = { args: { isRequired: true } };","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"KeepsDraftAfterCommit","description":"The typed text stays in the field after a commit that the host refuses.","source":"export const KeepsDraftAfterCommit: Story = {\n args: { isDraftKeptOnCommit: true, description: \"The host ignores every commit.\" },\n};","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"LongValue","description":"A long value in a single line, which the input scrolls.","source":"export const LongValue: Story = {\n args: {\n value:\n \"A welcome series for everyone who signed up this month but has not yet placed a first order\",\n },\n};","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"MaxLength","description":"Insertions fit 10 characters; an existing longer value stays and can be shortened.","source":"export const MaxLength: Story = { args: { maxLength: 10 } };","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"LabelHidden","description":"The label kept for screen readers only.","source":"export const LabelHidden: Story = { args: { isLabelHidden: true } };","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: { value: \"A welcome series for everyone who signed up\", description: \"Shown in the list.\" },\n render: (args) => (\n <Narrow>\n <CommitTextField {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Filled\">\n <CommitTextField {...args} />\n </Case>\n <Case caption=\"Empty\">\n <CommitTextField {...args} value=\"\" placeholder=\"Untitled\" />\n </Case>\n <Case caption=\"Error\">\n <CommitTextField {...args} error=\"A name is required.\" />\n </Case>\n <Case caption=\"Disabled\">\n <CommitTextField {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <CommitTextField {...args} isReadOnly />\n </Case>\n <Case caption=\"Medium\">\n <CommitTextField {...args} size=\"md\" />\n </Case>\n <Case caption=\"Multiline\">\n <CommitTextField {...args} isMultiline value=\"Line one\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/CommitTextField.stories.tsx"}],"primaryExample":"Default"},{"name":"ConfirmDialog","level":"molecules","category":"Overlays","keywords":["confirmation","dialog","cancel","loss"],"import":"import { ConfirmDialog } from \"@antscorp/ui\";","description":"Ask before a change that loses work, optionally paired with useConfirmedChange.","usage":"Ask before a change that loses work, optionally paired with useConfirmedChange. Use ChoiceDialog when the user must choose an option before confirming.","bestPractices":[{"guidance":true,"description":"Explain the consequence in description and make labels.action name the actual change."},{"guidance":false,"description":"Do not perform the pending change until onConfirm; onCancel should discard the pending request."}],"anatomy":[{"name":"Header","required":true,"description":"The confirmation question supplied by title."},{"name":"Description","required":true,"description":"The consequence of the pending change."},{"name":"Actions","required":true,"description":"The cancel button and confirming action."}],"accessibility":["Supply a clear title and translated action and cancel labels; dialog behavior is provided by Astryx Dialog."],"props":[{"path":"isOpen","type":"boolean","required":true,"description":"Whether the dialog is open."},{"path":"title","type":"string","required":true,"description":"Dialog title. Linked to the dialog via `aria-labelledby`."},{"path":"description","type":"string","required":true,"description":"Consequence description. Linked to the dialog via `aria-describedby`."},{"path":"labels","type":"{ readonly action: string; readonly cancel: string; }","required":true,"description":"The texts of the two buttons; the host translates them."},{"path":"labels.action","type":"string","required":false,"requiredWhenParent":true,"description":"The confirming button, e.g. \"Remove\"."},{"path":"labels.cancel","type":"string","required":false,"requiredWhenParent":true,"description":"The button that drops the change, e.g. \"Cancel\"."},{"path":"onConfirm","type":"() => void","required":true,"description":"Called when the user confirms."},{"path":"onCancel","type":"() => void","required":true,"description":"Called when the user cancels or closes the dialog."}],"examples":[{"name":"Default","description":"The dialog while a change waits for an answer (pending).","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/ConfirmDialog.stories.tsx"},{"name":"Closed","description":"Closed: nothing shows.","source":"export const Closed: Story = { args: { isOpen: false } };","file":"packages/ui/src/molecules/ConfirmDialog.stories.tsx"},{"name":"WithUseConfirmedChange","description":"With `useConfirmedChange`: the button asks for a change, the dialog holds it until answered.","source":"export const WithUseConfirmedChange: Story = { render: () => <WithConfirmedChange /> };","file":"packages/ui/src/molecules/ConfirmDialog.stories.tsx"},{"name":"LongContent","description":"Long title and description, and long button labels.","source":"export const LongContent: Story = {\n args: {\n title: \"Remove every branch that still has steps in it, together with all of their steps?\",\n description:\n \"The branches, every step inside them and the settings of those steps go. Anything that points at them is left without a target. Undo brings them all back, in the order they were.\",\n labels: { action: \"Remove branches and steps\", cancel: \"Keep them for now\" },\n },\n};","file":"packages/ui/src/molecules/ConfirmDialog.stories.tsx"}],"primaryExample":"Default"},{"name":"DateTimeInput","level":"molecules","category":"Inputs","keywords":["date","time","partial","calendar"],"import":"import { DateTimeInput } from \"@antscorp/ui\";","description":"Edit a local date and time while either part is incomplete.","usage":"Edit a local date and time while either part is incomplete. Set hasTime=false for a date-only value; labels.timezone only displays a note. hourFormat defaults to 12h; choose 24h when needed. Localize calendar and clear text with the facade's InternationalizationProvider.","bestPractices":[{"guidance":true,"description":"Keep wire values as strings and use frame=none when the individual date and time labels should be visible."},{"guidance":false,"description":"Do not require the value to be complete on every onChange; empty and partial strings are supported."}],"anatomy":[{"name":"Date input","required":true,"description":"The date portion, optionally constrained by min."},{"name":"Time input","required":false,"description":"The time portion when hasTime is enabled."},{"name":"Note","required":false,"description":"labels.timezone shown beside the time."},{"name":"Help and error","required":false,"description":"Group guidance or date-field guidance with frame=none."}],"accessibility":["Supply label and labels.date and labels.time; the default bare group hides the individual visible labels while preserving their names.","Read-only mode shows the date and time strings in named read-only text fields with no clear or picker actions."],"props":[{"path":"isDisabled","type":"boolean","required":false,"description":"Disables fields through the unframed fieldset; with `frame=\"card\"`, disable children yourself."},{"path":"hourFormat","type":"TimeInputHourFormat | undefined","required":false,"default":"'12h'","description":"Hour format for display.\n- '12h': Display as 12-hour with AM/PM (e.g., \"2:30 PM\")\n- '24h': Display as 24-hour (e.g., \"14:30\")"},{"path":"label","type":"string","required":true,"description":"The group's label, e.g. \"Start date\"; it names the group and is not shown."},{"path":"labels","type":"{ readonly date: string; readonly time: string; readonly timezone?: string | undefined; }","required":true,"description":"The texts the inputs show; the host translates them."},{"path":"labels.date","type":"string","required":false,"requiredWhenParent":true,"description":"The date input's label: its accessible name in a group, shown with `frame=\"none\"`."},{"path":"labels.time","type":"string","required":false,"requiredWhenParent":true,"description":"The time input's label: its accessible name in a group, shown with `frame=\"none\"`."},{"path":"labels.timezone","type":"string | undefined","required":false,"description":"A time zone, or any note, shown beside the time."},{"path":"value","type":"string","required":true,"description":"The local date-time (`YYYY-MM-DD HH:mm:ss`); empty or partial while it is being filled in."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The inputs' size, `md` by default."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows readable date and time fields at full opacity, without clear or picker actions."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the date-time, already in the user's language."},{"path":"hasTime","type":"boolean","required":false,"description":"`false` picks a date only: the time input is left out and `onChange` gets `YYYY-MM-DD`, or empty."},{"path":"frame","type":"\"bare\" | \"none\"","required":false,"description":"`bare` (default) puts both inputs, with hidden labels, in an unframed group that `label` names\nfor assistive tech, for a host that shows the label itself. `none` draws no group: it shows\n`labels.date` and `labels.time` above the inputs and hangs `description` and `error` on the\ndate input."},{"path":"description","type":"string | undefined","required":false,"description":"Help above the inputs, or under the date input with `frame=\"none\"`."},{"path":"isRequired","type":"boolean","required":false,"description":"Marks the date as required."},{"path":"isTimeLockedWithoutDate","type":"boolean","required":false,"description":"The time input stays disabled until a date is picked."},{"path":"min","type":"string | undefined","required":false,"description":"The earliest valid calendar date (`YYYY-MM-DD`); an absent or invalid minimum leaves dates unconstrained."},{"path":"onChange","type":"(value: string) => void","required":true,"description":"Called with the new date-time; a part not picked yet is empty, a new date starts at 00:00:00."}],"examples":[{"name":"Default","description":"A date and a time with the time zone beside them.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"TwentyFourHour","description":"An explicit 24-hour clock; the default remains 12-hour.","source":"export const TwentyFourHour: Story = { args: { hourFormat: \"24h\", value: \"2026-10-13 13:30:00\" } };","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"Empty","description":"Nothing picked yet.","source":"export const Empty: Story = { args: { value: \"\" } };","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"DateOnly","description":"Only the date picked, so the time is empty.","source":"export const DateOnly: Story = { args: { value: \"2026-10-13\" } };","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"Interactive","description":"The value follows what is picked.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(\"\", (value, set) => (\n <DateTimeInput\n {...args}\n value={value}\n labels={{ ...args.labels, timezone: value === \"\" ? \"Nothing picked\" : value }}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"NoTimezone","description":"Without a time zone note.","source":"export const NoTimezone: Story = {\n args: { labels: { date: \"Start date, date\", time: \"Start date, time\" } },\n};","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"WithError","description":"With a problem with the date-time.","source":"export const WithError: Story = { args: { error: \"The start must be in the future.\" } };","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"Disabled","description":"Dimmed and blocked.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"ReadOnly","description":"Shows the date-time, not dimmed, and ignores changes.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"Medium","description":"The `md` size.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"DateOnlyPicker","description":"A date only: no time input.","source":"export const DateOnlyPicker: Story = { args: { hasTime: false, value: \"2026-10-13\" } };","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"Inline","description":"Labels shown above the inputs, with help and the problem on the date, and no group around.","source":"export const Inline: Story = {\n args: {\n frame: \"none\",\n labels: { date: \"Start date\", time: \"Start time\" },\n description: \"When sending begins.\",\n isRequired: true,\n },\n};","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"TimeLockedWithoutDate","description":"Inline, with the time locked until a date is picked.","source":"export const TimeLockedWithoutDate: Story = {\n args: { ...Inline.args, value: \"\", isTimeLockedWithoutDate: true },\n};","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"InlineDateOnly","description":"Inline and date only.","source":"export const InlineDateOnly: Story = { args: { ...Inline.args, hasTime: false } };","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"WithMin","description":"The calendar offers no date before the 10th, such as an end after its start.","source":"export const WithMin: Story = { args: { min: \"2026-10-10\" } };","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"WithDescription","description":"With help under the label.","source":"export const WithDescription: Story = { args: { description: \"When sending begins.\" } };","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"LongContent","description":"A long label and a long time zone note.","source":"export const LongContent: Story = {\n args: {\n label: \"The date and time on which the campaign should start sending\",\n labels: {\n date: \"Start date, date\",\n time: \"Start date, time\",\n timezone: \"(GMT-03:30) America/St_Johns, Newfoundland and Labrador\",\n },\n },\n};","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, where the pieces wrap.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <DateTimeInput {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Filled\">\n <DateTimeInput {...args} />\n </Case>\n <Case caption=\"Empty\">\n <DateTimeInput {...args} value=\"\" />\n </Case>\n <Case caption=\"Error\">\n <DateTimeInput {...args} error=\"The start must be in the future.\" />\n </Case>\n <Case caption=\"Inline\">\n <DateTimeInput\n {...args}\n frame=\"none\"\n labels={{ date: \"Start date\", time: \"Start time\" }}\n error=\"Too early\"\n />\n </Case>\n <Case caption=\"Date only\">\n <DateTimeInput {...args} hasTime={false} />\n </Case>\n <Case caption=\"Disabled\">\n <DateTimeInput {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <DateTimeInput {...args} isReadOnly />\n </Case>\n <Case caption=\"Medium\">\n <DateTimeInput {...args} size=\"md\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/DateTimeInput.stories.tsx"}],"primaryExample":"Default"},{"name":"DurationInput","level":"molecules","category":"Inputs","keywords":["duration","days","hours","seconds"],"import":"import { DurationInput } from \"@antscorp/ui\";","description":"Edit a length of time as days, hours, minutes and seconds.","usage":"Edit a length of time as days, hours, minutes and seconds. Use DateTimeInput for a calendar date and time instead of an elapsed duration.","bestPractices":[{"guidance":true,"description":"Translate every unit label and use max to constrain parts; reaching max.days disables the remaining parts."},{"guidance":false,"description":"Do not treat this partial duration object as an instant or assume unset parts are explicitly stored as zero."}],"anatomy":[{"name":"Part inputs","required":true,"description":"Four number inputs, each followed by its translated unit."},{"name":"Error","required":false,"description":"A translated problem with the whole duration."}],"accessibility":["label names the unframed group; labels names each unit's input."],"props":[{"path":"label","type":"string","required":true,"description":"Names the group; read out before each field in it."},{"path":"isDisabled","type":"boolean","required":false,"description":"Disables fields through the unframed fieldset; with `frame=\"card\"`, disable children yourself."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the group as a whole, already in the user's language."},{"path":"labels","type":"Readonly<Record<\"days\" | \"hours\" | \"minutes\" | \"seconds\", string>>","required":true,"description":"The unit of each part, already in the user's language, e.g. \"day(s)\": shown after its input\nand naming it for assistive tech."},{"path":"max","type":"Readonly<Partial<Record<\"days\" | \"hours\" | \"minutes\" | \"seconds\", number>>> | undefined","required":false,"description":"The most each part may be. At `max.days` days the other parts are disabled, as a length of\nwhole days that may not grow."},{"path":"value","type":"Readonly<Partial<Record<\"days\" | \"hours\" | \"minutes\" | \"seconds\", number>>> | undefined","required":true,"description":"The duration; a part not set shows empty, and undefined shows every part empty."},{"path":"onChange","type":"(duration: DurationValue) => void","required":true,"description":"Called with the whole new duration on each change."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The inputs' size, `md` by default."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows the value, not dimmed, and blocks changes."}],"examples":[{"name":"Default","description":"A duration with some parts set and the others empty.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"Empty","description":"Nothing set yet, or a stored value that is not a duration.","source":"export const Empty: Story = { args: { value: undefined } };","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"AllParts","description":"Every part set.","source":"export const AllParts: Story = { args: { value: { days: 3, hours: 4, minutes: 5, seconds: 6 } } };","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"Interactive","description":"The duration follows what is typed.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<DurationValue>({ days: 1 }, (value, set) => (\n <DurationInput\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"WithError","description":"With a problem with the duration.","source":"export const WithError: Story = { args: { error: \"The wait must be longer than zero.\" } };","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"Disabled","description":"Read-only.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"ReadOnly","description":"Shows the value, not dimmed, and blocks changes.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"WithMax","description":"With limits, interactive: each part stops at its max (try 75 minutes), and 365 days disables the rest.","source":"export const WithMax: Story = { ...Interactive, args: { max: MAX } };","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"AtMax","description":"Days at their max: the other parts are disabled.","source":"export const AtMax: Story = { args: { max: MAX, value: { days: 365 } } };","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"Medium","description":"The `md` size, for a form whose other controls are `md`.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"LongContent","description":"Large numbers and long part labels.","source":"export const LongContent: Story = {\n args: {\n label: \"Wait before the next message is sent to the same person\",\n labels: {\n days: \"Whole days\",\n hours: \"Whole hours\",\n minutes: \"Whole minutes\",\n seconds: \"Whole seconds\",\n },\n value: { days: 36500, hours: 23, minutes: 59, seconds: 59 },\n },\n};","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, where the parts wrap.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <DurationInput {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/DurationInput.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Filled\">\n <DurationInput {...args} />\n </Case>\n <Case caption=\"Empty\">\n <DurationInput {...args} value={undefined} />\n </Case>\n <Case caption=\"Error\">\n <DurationInput {...args} error=\"The wait must be longer than zero.\" />\n </Case>\n <Case caption=\"Disabled\">\n <DurationInput {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <DurationInput {...args} isReadOnly />\n </Case>\n <Case caption=\"At max\">\n <DurationInput {...args} max={MAX} value={{ days: 365 }} />\n </Case>\n <Case caption=\"Medium\">\n <DurationInput {...args} size=\"md\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/DurationInput.stories.tsx"}],"primaryExample":"Default"},{"name":"FieldGroup","level":"molecules","category":"Layout","keywords":["fields","group","fieldset","card"],"import":"import { FieldGroup } from \"@antscorp/ui\";","description":"Name several related fields and show a problem with the group as a whole.","usage":"Name several related fields and show a problem with the group as a whole. Use FieldRow to place a separate caption beside the grouped controls.","bestPractices":[{"guidance":true,"description":"Supply label even without a frame, and disable child controls explicitly when using frame=card."},{"guidance":false,"description":"Do not expect isDisabled to disable the card variant; only the unframed fieldset disables its descendants."}],"anatomy":[{"name":"Group label","required":true,"description":"An accessible name, visible above content in the card variant."},{"name":"Fields","required":true,"description":"Host controls with the requested gap."},{"name":"Description and error","required":false,"description":"Help and a translated problem with the group."}],"accessibility":["The unframed variant is a named fieldset; the card variant uses role group."],"props":[{"path":"label","type":"string","required":true,"description":"Names the group; read out before each field in it."},{"path":"frame","type":"\"card\" | \"none\" | undefined","required":false,"description":"`none` (default) draws no frame, and the label only names the group for assistive tech. `card`\nis a transparent Astryx card (role `group`) with the label above its content; it is not a\nfieldset, so `isDisabled` has no effect."},{"path":"description","type":"string | undefined","required":false,"description":"Help under the label."},{"path":"isDisabled","type":"boolean","required":false,"description":"Disables fields through the unframed fieldset; with `frame=\"card\"`, disable children yourself."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the group as a whole, already in the user's language."},{"path":"gap","type":"2 | 4 | undefined","required":false,"description":"The space between the fields, in Astryx spacing steps: `2` (8px, default) or `4` (16px)."},{"path":"children","type":"ReactNode","required":true,"description":"The fields."}],"examples":[{"name":"Default","description":"Fields named by one label, which only assistive tech reads.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/FieldGroup.stories.tsx"},{"name":"WithDescription","description":"With help under the label.","source":"export const WithDescription: Story = { args: { description: \"How we reach this person.\" } };","file":"packages/ui/src/molecules/FieldGroup.stories.tsx"},{"name":"Interactive","description":"Typing in a field inside the group.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(\"\", (name, set) => (\n <FieldGroup {...args}>\n <TextInput label=\"Name\" value={name} onChange={set} />\n </FieldGroup>\n )),\n};","file":"packages/ui/src/molecules/FieldGroup.stories.tsx"},{"name":"WithError","description":"With a problem with the group as a whole.","source":"export const WithError: Story = { args: { error: \"Fill in at least one field.\" } };","file":"packages/ui/src/molecules/FieldGroup.stories.tsx"},{"name":"Disabled","description":"Every field in the group is disabled.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/FieldGroup.stories.tsx"},{"name":"LongContent","description":"A long label, description and error, and many fields.","source":"export const LongContent: Story = {\n args: {\n label: \"Contact details for the person who receives the weekly summary of every report\",\n description:\n \"How we reach this person, in order of preference, when the first way fails and again later.\",\n error: \"At least one of these fields must be filled in before the group can be saved.\",\n children: Array.from({ length: 10 }, (_, index) => (\n <TextInput key={index} label={`Field ${index + 1}`} value=\"\" onChange={fn<() => void>()} />\n )),\n },\n};","file":"packages/ui/src/molecules/FieldGroup.stories.tsx"},{"name":"SingleChild","description":"A group of one line.","source":"export const SingleChild: Story = { args: { children: <span>One line</span> } };","file":"packages/ui/src/molecules/FieldGroup.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: { description: \"How we reach this person.\", error: \"Fill in at least one field.\" },\n render: (args) => (\n <Narrow>\n <FieldGroup {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/FieldGroup.stories.tsx"},{"name":"Card","description":"A transparent card with the label above the fields, for a group nested in a form.","source":"export const Card: Story = {\n args: { frame: \"card\", description: \"How we reach this person.\", error: \"Fill in one field.\" },\n};","file":"packages/ui/src/molecules/FieldGroup.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <FieldGroup {...args} />\n </Case>\n <Case caption=\"Description\">\n <FieldGroup {...args} description=\"How we reach this person.\" />\n </Case>\n <Case caption=\"Error\">\n <FieldGroup {...args} error=\"Fill in at least one field.\" />\n </Case>\n <Case caption=\"Disabled\">\n <FieldGroup {...args} isDisabled />\n </Case>\n <Case caption=\"Card\">\n <FieldGroup {...args} frame=\"card\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/FieldGroup.stories.tsx"}],"primaryExample":"Default"},{"name":"FieldHeading","level":"atoms","category":"Data display","keywords":["heading","caption","field"],"import":"import { FieldHeading } from \"@antscorp/ui\";","description":"Show a separate supporting caption above controls whose own labels are hidden.","usage":"Show a separate supporting caption above controls whose own labels are hidden. Use FieldRow when the form needs a caption column beside its controls.","bestPractices":[{"guidance":true,"description":"Keep each control's own accessible label even when FieldHeading supplies the visible caption."},{"guidance":false,"description":"Do not treat this text as a label association or a semantic heading element."}],"props":[{"path":"label","type":"string","required":true,"description":"The heading's text, such as \"Start date\"."}],"examples":[{"name":"Default","description":"A field's heading on its own.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/FieldHeading.stories.tsx"},{"name":"AboveAControl","description":"Above a control whose own label is hidden, as a form's fields read.","source":"export const AboveAControl: Story = {\n render: (args) => (\n <VStack gap={1}>\n <FieldHeading {...args} />\n <Selector label={args.label} isLabelHidden options={[\"Once\", \"By day\"]} value=\"Once\" />\n </VStack>\n ),\n};","file":"packages/ui/src/atoms/FieldHeading.stories.tsx"},{"name":"LongHeading","description":"A long heading in a narrow frame wraps.","source":"export const LongHeading: Story = {\n args: { label: \"How many times should each item be included in this collection?\" },\n render: (args) => (\n <Narrow>\n <FieldHeading {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/FieldHeading.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <FieldHeading {...args} />\n </Case>\n <Case caption=\"Long\">\n <FieldHeading label=\"How many times should each item be included in this collection?\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/FieldHeading.stories.tsx"}],"primaryExample":"Default"},{"name":"FieldRow","level":"molecules","category":"Layout","keywords":["form","caption","columns","responsive"],"import":"import { FieldRow } from \"@antscorp/ui\";","description":"Arrange a form section as a caption column beside its controls, stacking when room is limited.","usage":"Arrange a form section as a caption column beside its controls, stacking when room is limited. Use SideLabelRow for padded rows inside a card.","bestPractices":[{"guidance":true,"description":"Keep caption widths consistent through the theme or labelWidth and use align=start for controls without a useful text baseline."},{"guidance":false,"description":"Do not rely on the visible caption to name individual controls; keep their own labels."}],"anatomy":[{"name":"Caption column","required":false,"description":"The label and optional description; its space remains when no label is supplied in horizontal layout."},{"name":"Control column","required":true,"description":"Host controls that move below the caption in a narrow layout."}],"props":[{"path":"label","type":"string | undefined","required":false,"description":"The row's caption, in a column at the start. Without it the column stays, empty, so the\ncontent lines up with the captioned rows around it; stacked,\nnothing is drawn above the content."},{"path":"description","type":"string | undefined","required":false,"description":"Help under the caption."},{"path":"labelWidth","type":"number | undefined","required":false,"description":"Overrides the theme's caption column width in pixels; unthemed rows default to 160px."},{"path":"orientation","type":"\"horizontal\" | \"vertical\" | undefined","required":false,"description":"`horizontal` (default) puts the caption beside the content, and stacks it above when there is no room; `vertical` always stacks."},{"path":"align","type":"\"baseline\" | \"start\" | undefined","required":false,"description":"`baseline` (default) sets the caption on the content's first line of text; `start` tops them\nboth, for content that opens with a control without text, such as a radio list, whose baseline\nis the control's bottom edge."},{"path":"children","type":"ReactNode","required":true,"description":"The row's controls; single-line controls use the theme's cap, with a 400px fallback."}],"examples":[{"name":"Default","description":"The caption at the start, the controls beside it.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/FieldRow.stories.tsx"},{"name":"WithDescription","description":"With help under the caption.","source":"export const WithDescription: Story = { args: { description: \"When it runs.\" } };","file":"packages/ui/src/molecules/FieldRow.stories.tsx"},{"name":"WideCaption","description":"A wider caption column.","source":"export const WideCaption: Story = { args: { labelWidth: 260 } };","file":"packages/ui/src/molecules/FieldRow.stories.tsx"},{"name":"Vertical","description":"Always stacked: the caption above the controls.","source":"export const Vertical: Story = { args: { orientation: \"vertical\" } };","file":"packages/ui/src/molecules/FieldRow.stories.tsx"},{"name":"AlignStart","description":"Content that opens with radios, topped with the caption: a radio has no text baseline.","source":"export const AlignStart: Story = { args: { align: \"start\", children: radios } };","file":"packages/ui/src/molecules/FieldRow.stories.tsx"},{"name":"NoCaption","description":"No caption: its column stays empty, so the controls line up with the captioned rows above.","source":"export const NoCaption: Story = { args: { label: undefined } };","file":"packages/ui/src/molecules/FieldRow.stories.tsx"},{"name":"LongContent","description":"A long caption and help, and several controls.","source":"export const LongContent: Story = {\n args: {\n label: \"A caption long enough to wrap over several lines of the column\",\n description: \"Help text that is also quite long and wraps in its own column too.\",\n children: (\n <>\n {field}\n {field}\n {field}\n </>\n ),\n },\n};","file":"packages/ui/src/molecules/FieldRow.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame there is no room beside the caption, so it moves above.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <FieldRow {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/FieldRow.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Beside\">\n <FieldRow {...args} />\n </Case>\n <Case caption=\"Radios, align start\">\n <FieldRow {...args} align=\"start\">\n {radios}\n </FieldRow>\n </Case>\n <Case caption=\"No caption\">\n <FieldRow {...args} label={undefined} />\n </Case>\n <Case caption=\"Vertical\">\n <FieldRow {...args} orientation=\"vertical\" />\n </Case>\n <Case caption=\"Narrow\">\n <Narrow>\n <FieldRow {...args} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/FieldRow.stories.tsx"}],"primaryExample":"Default"},{"name":"FieldSkeleton","level":"molecules","category":"Feedback","keywords":["field","loading","skeleton","placeholder"],"import":"import { FieldSkeleton } from \"@antscorp/ui\";","description":"Reserve a form field's shape while its data loads.","usage":"Reserve a form field's shape while its data loads. Use RowSkeletons for a list and ChipSkeletons for picked values.","bestPractices":[{"guidance":true,"description":"Show the real label when known and match isLabelHidden to the field that will replace the placeholder."},{"guidance":false,"description":"Do not add a separate status for every skeleton inside one named loading region; set hasStatus=false."}],"anatomy":[{"name":"Label","required":false,"description":"The real caption or a short shimmer bar unless isLabelHidden."},{"name":"Control placeholder","required":true,"description":"A shimmer as high as a control, respecting horizontal-labels layout."}],"accessibility":["The default wrapper is a status named by labels.loading; hasStatus=false hides the placeholder from assistive technology."],"props":[{"path":"label","type":"string | undefined","required":false,"description":"The field's real label, shown as a Field shows it; without it a short bar stands in for it."},{"path":"isLabelHidden","type":"boolean","required":false,"description":"Shows the control's shimmer alone, for a field whose caption is outside it (a `FieldRow`)."},{"path":"hasStatus","type":"boolean","required":false,"description":"Whether it is a status of its own (default); `false` draws no status and hides it from\nassistive tech, for a skeleton inside one status that names the whole load."},{"path":"labels","type":"{ readonly loading?: string | undefined; } | undefined","required":false,"description":"The texts the view shows; the host translates them."},{"path":"labels.loading","type":"string | undefined","required":false,"description":"The loading status's accessible name, e.g. \"Loading\"."}],"examples":[{"name":"Default","description":"No label known: a short bar stands in for it over the control.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/FieldSkeleton.stories.tsx"},{"name":"WithLabel","description":"The real label over the shimmering control.","source":"export const WithLabel: Story = { args: { label: \"Country\" } };","file":"packages/ui/src/molecules/FieldSkeleton.stories.tsx"},{"name":"LabelHidden","description":"The control's shimmer alone, for a caption outside it such as a `FieldRow`'s.","source":"export const LabelHidden: Story = { args: { isLabelHidden: true } };","file":"packages/ui/src/molecules/FieldSkeleton.stories.tsx"},{"name":"LongLabel","description":"A long label, as a Field wraps it.","source":"export const LongLabel: Story = {\n args: {\n label: \"The country the customer was living in when they first signed up for the newsletter\",\n },\n};","file":"packages/ui/src/molecules/FieldSkeleton.stories.tsx"},{"name":"HorizontalLabels","description":"Labels left of the controls, between real fields, as a settings form lays them out.","source":"export const HorizontalLabels: Story = {\n args: { label: \"Country\" },\n render: (args) => (\n <FormLayout direction=\"horizontal-labels\">\n <FieldSkeleton {...args} label=\"Name\" />\n <FieldSkeleton {...args} />\n <FieldSkeleton {...args} label={undefined} />\n </FormLayout>\n ),\n};","file":"packages/ui/src/molecules/FieldSkeleton.stories.tsx"},{"name":"InOneStatus","description":"Rows inside one status that names the load: each with `hasStatus={false}`, no status of its own\nand hidden from assistive tech.","source":"export const InOneStatus: Story = {\n render: (args) => (\n // A raw element: Astryx stacks take no role or name.\n <div role=\"status\" aria-label=\"Loading\">\n <FieldSkeleton {...args} hasStatus={false} />\n <FieldSkeleton {...args} hasStatus={false} />\n </div>\n ),\n};","file":"packages/ui/src/molecules/FieldSkeleton.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: LongLabel.args ?? {},\n render: (args) => (\n <Narrow>\n <FieldSkeleton {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/FieldSkeleton.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"No label\">\n <FieldSkeleton {...args} />\n </Case>\n <Case caption=\"Label\">\n <FieldSkeleton {...args} label=\"Country\" />\n </Case>\n <Case caption=\"Label hidden\">\n <FieldSkeleton {...args} isLabelHidden />\n </Case>\n <Case caption=\"Horizontal labels\">\n <FormLayout direction=\"horizontal-labels\">\n <FieldSkeleton {...args} label=\"Country\" />\n </FormLayout>\n </Case>\n <Case caption=\"No status of its own\">\n <FieldSkeleton {...args} hasStatus={false} />\n </Case>\n <Case caption=\"Narrow\">\n <Narrow>\n <FieldSkeleton {...args} label=\"Country\" />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/FieldSkeleton.stories.tsx"}],"primaryExample":"Default"},{"name":"FrequencyLimitRow","level":"molecules","category":"Inputs","keywords":["frequency","limit","count","window"],"import":"import { FrequencyLimitRow } from \"@antscorp/ui\";","description":"Edit a count per time window as one sentence.","usage":"Edit a count per time window as one sentence. The window identified by everyWindow also reveals a number of units.","bestPractices":[{"guidance":true,"description":"Keep value controlled and supply translated labels and connecting words; use paths when the inputs must be found by revealPath."},{"guidance":false,"description":"Do not retain every for unrelated windows; switching away from everyWindow drops that extra number."}],"anatomy":[{"name":"Count input","required":true,"description":"The number of occurrences followed by words.times."},{"name":"Window select","required":true,"description":"The offered counting window."},{"name":"Every input","required":false,"description":"An extra number and words.every for the designated window."},{"name":"Trailing content","required":false,"description":"Host content finishing the sentence."}],"accessibility":["labels.times, labels.window and labels.every supply the hidden accessible input labels."],"props":[{"path":"value","type":"FrequencyLimit<string>","required":true,"description":"The limit shown."},{"path":"value.times","type":"number","required":false,"requiredWhenParent":true,"description":"How many times."},{"path":"value.window","type":"string","required":false,"requiredWhenParent":true,"description":"The window the times count in."},{"path":"value.every","type":"number | undefined","required":false,"description":"For the `everyWindow` only: how many days, weeks… the window lasts."},{"path":"windows","type":"readonly FrequencyWindow<string>[]","required":true,"description":"The windows to pick from."},{"path":"windows[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What `window` holds."},{"path":"windows[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The window's text, e.g. \"per day\"."},{"path":"everyWindow","type":"string | undefined","required":false,"description":"The window that shows the extra \"every N\" number; none when absent."},{"path":"labels","type":"{ readonly times: string; readonly window: string; readonly every: string; }","required":true,"description":"The label of each input, already in the user's language; hidden, read out by assistive tech."},{"path":"labels.times","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"labels.window","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"labels.every","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"words","type":"{ readonly times: string; readonly every: string; }","required":true,"description":"The words after the times and after the every number, e.g. \"time(s) per\" and \"day(s)\", so the row reads as a sentence."},{"path":"words.times","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"words.every","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"children","type":"ReactNode","required":false,"description":"Shown at the end of the sentence, e.g. who the limit counts per."},{"path":"max","type":"{ readonly times?: number | undefined; readonly every?: number | undefined; } | undefined","required":false,"description":"The largest times and every number the inputs take; none when absent."},{"path":"max.times","type":"number | undefined","required":false,"description":""},{"path":"max.every","type":"number | undefined","required":false,"description":""},{"path":"onChange","type":"(value: FrequencyLimit<string>) => void","required":true,"description":"Called with the whole new limit on each change."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The inputs' size, `md` by default."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims every input and blocks them."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows the value, not dimmed, and blocks changes."},{"path":"paths","type":"{ readonly times: Path; readonly window: Path; readonly every: Path; } | undefined","required":false,"description":"Marks each input with its value's path (`PathMark`, relative to the enclosing mark), so it can\nbe revealed; none when absent."},{"path":"paths.times","type":"Path","required":false,"requiredWhenParent":true,"description":""},{"path":"paths.window","type":"Path","required":false,"requiredWhenParent":true,"description":""},{"path":"paths.every","type":"Path","required":false,"requiredWhenParent":true,"description":""}],"examples":[{"name":"Default","description":"A limit of three times today.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"EveryWindow","description":"The \"every\" window shows its extra number.","source":"export const EveryWindow: Story = { args: { value: { times: 1, window: \"every\", every: 7 } } };","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"Interactive","description":"Picking the \"every\" window adds its number; any other drops it.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<FrequencyLimit<Window>>({ times: 1, window: \"hour\" }, (value, set) => (\n <FrequencyLimitRow\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"Disabled","description":"Disabled: dimmed and blocked.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"ReadOnly","description":"Shows the value, not dimmed, and blocks changes.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"Medium","description":"The `md` size, for a form whose other controls are `md`.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"WithMax","description":"At most 1000 times in at most 999 days: the steppers stop there.","source":"export const WithMax: Story = {\n args: { value: { times: 1000, window: \"every\", every: 999 }, max: { times: 1000, every: 999 } },\n};","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"LongContent","description":"A large count and long words between the inputs.","source":"export const LongContent: Story = {\n args: {\n value: { times: 1000000, window: \"every\", every: 365 },\n words: { times: \"times in total, counted in\", every: \"days long, for each\" },\n },\n};","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"WithTrailing","description":"Something at the end of the sentence, e.g. who the limit counts per.","source":"export const WithTrailing: Story = { args: { children: \"per person\" } };","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"Marked","description":"Each input marked with its value's path, so a host can reveal it (`PathMark`); looks the same.","source":"export const Marked: Story = {\n args: {\n value: { times: 2, window: \"every\", every: 3 },\n paths: { times: [\"cap\", \"times\"], window: [\"cap\", \"per\"], every: [\"cap\", \"per\", \"days\"] },\n },\n};","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"NoEveryWindow","description":"Without an \"every\" window, so no window shows a second number.","source":"export const NoEveryWindow: Story = {\n args: { everyWindow: undefined, windows: windows.slice(0, 2) },\n};","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, where the inputs wrap.","source":"export const InNarrowFrame: Story = {\n args: { value: { times: 2, window: \"every\", every: 3 } },\n render: (args) => (\n <Narrow>\n <FrequencyLimitRow {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"},{"name":"AllStates","description":"Each window option, one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n {windows.map((window) => (\n <Case key={window.value} caption={window.label}>\n <FrequencyLimitRow\n {...args}\n value={{\n times: 2,\n window: window.value,\n ...(window.value === \"every\" ? { every: 3 } : {}),\n }}\n />\n </Case>\n ))}\n <Case caption=\"Disabled\">\n <FrequencyLimitRow {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <FrequencyLimitRow {...args} isReadOnly />\n </Case>\n <Case caption=\"Medium\">\n <FrequencyLimitRow {...args} size=\"md\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/FrequencyLimitRow.stories.tsx"}],"primaryExample":"Default"},{"name":"HourOfDayRow","level":"organisms","category":"Selection","keywords":["hours","day","schedule","drag"],"import":"import { HourOfDayRow } from \"@antscorp/ui\";","description":"Pick hours in one day by toggling cells or dragging a run.","usage":"Pick hours in one day by toggling cells or dragging a run. Use HourOfWeekGrid when selections vary by weekday.","bestPractices":[{"guidance":true,"description":"Pass hours from 0 to 23 and format names through labels.hour; store the sorted unique result from onChange."},{"guidance":false,"description":"Do not compress the cells to fit a narrow panel; the row keeps its cell size and scrolls horizontally."}],"anatomy":[{"name":"Hour cells","required":true,"description":"Twenty-four cells showing picked hours and drag preview."},{"name":"Hour labels","required":true,"description":"Every third hour named below the row."},{"name":"Error","required":false,"description":"A translated problem below the selection."}],"accessibility":["label names the row; arrow keys move between hours and Space or Enter toggles the focused hour."],"props":[{"path":"label","type":"string","required":true,"description":"Names the row."},{"path":"value","type":"readonly number[]","required":true,"description":"The picked hours, 0 to 23."},{"path":"onChange","type":"(value: number[]) => void","required":true,"description":"Called with the picked hours, sorted and unique."},{"path":"labels","type":"{ readonly hour: (hour: number) => string; }","required":true,"description":"The texts the row shows; the host translates and formats them."},{"path":"labels.hour","type":"(hour: number) => string","required":false,"requiredWhenParent":true,"description":"The text for an hour (0 to 23), such as \"3AM\"; every third one shows under the row."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the row and blocks it."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows the pick, not dimmed, and ignores changes."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the pick, already in the user's language."}],"examples":[{"name":"Default","description":"Every hour picked, the default of the form this came from.","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/HourOfDayRow.stories.tsx"},{"name":"Empty","description":"Nothing picked.","source":"export const Empty: Story = { args: { value: [] } };","file":"packages/ui/src/organisms/HourOfDayRow.stories.tsx"},{"name":"Interactive","description":"Drag over hours to pick or clear a run; press one to toggle it.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<readonly number[]>(workHours, (value, set) => (\n <HourOfDayRow\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/organisms/HourOfDayRow.stories.tsx"},{"name":"WithError","description":"Nothing picked, and the problem that says so.","source":"export const WithError: Story = { args: { value: [], error: \"Pick at least one hour\" } };","file":"packages/ui/src/organisms/HourOfDayRow.stories.tsx"},{"name":"Disabled","description":"Dimmed and blocked.","source":"export const Disabled: Story = { args: { value: workHours, isDisabled: true } };","file":"packages/ui/src/organisms/HourOfDayRow.stories.tsx"},{"name":"ReadOnly","description":"Read-only: shown as it is, not dimmed.","source":"export const ReadOnly: Story = { args: { value: workHours, isReadOnly: true } };","file":"packages/ui/src/organisms/HourOfDayRow.stories.tsx"},{"name":"SingleHour","description":"A single hour picked.","source":"export const SingleHour: Story = { args: { value: [12] } };","file":"packages/ui/src/organisms/HourOfDayRow.stories.tsx"},{"name":"InNarrowFrame","description":"Narrower than the row: it scrolls sideways.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <HourOfDayRow {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/organisms/HourOfDayRow.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Every hour\">\n <HourOfDayRow {...args} />\n </Case>\n <Case caption=\"Work hours\">\n <HourOfDayRow {...args} value={workHours} />\n </Case>\n <Case caption=\"Error\">\n <HourOfDayRow {...args} value={[]} error=\"Pick at least one hour\" />\n </Case>\n <Case caption=\"Disabled\">\n <HourOfDayRow {...args} value={workHours} isDisabled />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/HourOfDayRow.stories.tsx"}],"primaryExample":"Default"},{"name":"HourOfWeekGrid","level":"organisms","category":"Selection","keywords":["hours","week","schedule","grid"],"import":"import { HourOfWeekGrid } from \"@antscorp/ui\";","description":"Pick hours across seven days by dragging a rectangle, toggling a day, or using the everyday row.","usage":"Pick hours across seven days by dragging a rectangle, toggling a day, or using the everyday row. Use HourOfDayRow when one daily selection is sufficient.","bestPractices":[{"guidance":true,"description":"Supply seven day labels Monday first and store cells as day * 24 + hour, from 0 to 167."},{"guidance":false,"description":"Do not assume read-only matches disabled presentation; read-only hides the everyday editing row while disabled keeps it."}],"anatomy":[{"name":"Day rows","required":true,"description":"Seven rows of twenty-four hour cells, with day-name actions."},{"name":"Everyday row","required":false,"description":"Controls that toggle whole hours across all days, hidden when read-only."},{"name":"Hour axis","required":true,"description":"Formatted hour names along the grid."},{"name":"Error","required":false,"description":"A translated problem below the grid."}],"accessibility":["label names the grid even when visually hidden; cells expose selection, arrow keys move focus, and Space or Enter toggles a cell."],"props":[{"path":"label","type":"string","required":true,"description":"Names the grid."},{"path":"isLabelHidden","type":"boolean","required":false,"description":"Keeps the label for assistive tech only, when the host already says what the grid is."},{"path":"value","type":"readonly number[]","required":true,"description":"The picked hours: cells `day * 24 + hour`, Monday being day 0, so 0 to 167."},{"path":"onChange","type":"(value: number[]) => void","required":true,"description":"Called with the picked cells, sorted and unique."},{"path":"labels","type":"{ readonly days: readonly string[]; readonly hour: (hour: number) => string; readonly everyday: string; }","required":true,"description":"The texts the grid shows; the host translates and formats them."},{"path":"labels.days","type":"readonly string[]","required":false,"requiredWhenParent":true,"description":"The seven day names, Monday first."},{"path":"labels.hour","type":"(hour: number) => string","required":false,"requiredWhenParent":true,"description":"The text for an hour (0 to 23), such as \"3AM\"."},{"path":"labels.everyday","type":"string","required":false,"requiredWhenParent":true,"description":"The name of the row that picks a whole hour on every day, such as \"Everyday\"."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the grid and blocks it; the \"everyday\" row stays."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Read-only: no \"everyday\" row, and nothing changes."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the pick, already in the user's language."}],"examples":[{"name":"Default","description":"Every hour picked, the default of the form this came from.","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/HourOfWeekGrid.stories.tsx"},{"name":"Empty","description":"Nothing picked.","source":"export const Empty: Story = { args: { value: [] } };","file":"packages/ui/src/organisms/HourOfWeekGrid.stories.tsx"},{"name":"Interactive","description":"Drag over cells to pick or clear a rectangle; press a day or an \"everyday\" cell to toggle it.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<readonly number[]>(workHours, (value, set) => (\n <HourOfWeekGrid\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/organisms/HourOfWeekGrid.stories.tsx"},{"name":"Weekdays","description":"Monday to Friday, 9 to 6.","source":"export const Weekdays: Story = { args: { value: workHours } };","file":"packages/ui/src/organisms/HourOfWeekGrid.stories.tsx"},{"name":"LabelHidden","description":"No visible label, when the host already says what the grid is; it still names the grid.","source":"export const LabelHidden: Story = { args: { isLabelHidden: true } };","file":"packages/ui/src/organisms/HourOfWeekGrid.stories.tsx"},{"name":"WithError","description":"No hour picked, with the problem under the grid.","source":"export const WithError: Story = { args: { value: [], error: \"Pick at least one hour.\" } };","file":"packages/ui/src/organisms/HourOfWeekGrid.stories.tsx"},{"name":"ReadOnly","description":"Read-only: no \"everyday\" row, and nothing changes.","source":"export const ReadOnly: Story = { args: { value: workHours, isReadOnly: true } };","file":"packages/ui/src/organisms/HourOfWeekGrid.stories.tsx"},{"name":"Disabled","description":"Dimmed and blocked, with the \"everyday\" row still drawn.","source":"export const Disabled: Story = { args: { value: workHours, isDisabled: true } };","file":"packages/ui/src/organisms/HourOfWeekGrid.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, where the grid scrolls sideways and keeps its cell size.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <HourOfWeekGrid {...args} value={workHours} />\n </Narrow>\n ),\n};","file":"packages/ui/src/organisms/HourOfWeekGrid.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Everything\">\n <HourOfWeekGrid {...args} />\n </Case>\n <Case caption=\"Weekdays 9 to 6\">\n <HourOfWeekGrid {...args} value={workHours} />\n </Case>\n <Case caption=\"Empty, error\">\n <HourOfWeekGrid {...args} value={[]} error=\"Pick at least one hour.\" />\n </Case>\n <Case caption=\"Read-only\">\n <HourOfWeekGrid {...args} value={workHours} isReadOnly />\n </Case>\n <Case caption=\"Disabled\">\n <HourOfWeekGrid {...args} value={workHours} isDisabled />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/HourOfWeekGrid.stories.tsx"}],"primaryExample":"Default"},{"name":"HoverSwap","level":"atoms","category":"Layout","keywords":["hover","actions","focus","row"],"import":"import { HoverSwap } from \"@antscorp/ui\";","description":"Share one position between resting content and row actions shown on hover or focus.","usage":"Share one position between resting content and row actions shown on hover or focus. Use permanent end content when the action should always be visible.","bestPractices":[{"guidance":true,"description":"Put stylex.defaultMarker() on the ancestor row so hover and focus-within reach both layers."},{"guidance":false,"description":"Do not assume the transparent layer is removed from the tab order or the accessibility tree."}],"anatomy":[{"name":"Resting layer","required":false,"description":"children is visible at rest."},{"name":"Action layer","required":true,"description":"hoverContent occupies the same grid cell and appears on ancestor hover or focus."}],"accessibility":["Controls in the transparent action layer remain tabbable; focusing within the marked row reveals them."],"props":[{"path":"children","type":"ReactNode","required":false,"description":"What shows at rest, such as a count."},{"path":"hoverContent","type":"ReactNode","required":true,"description":"What shows instead while the row is hovered or holds the focus, such as its actions."}],"examples":[{"name":"Default","description":"A count at rest; hover the row, or tab into it, for its remove button.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/HoverSwap.stories.tsx"},{"name":"EmptyAtRest","description":"Nothing at rest: the actions appear on hover only.","source":"export const EmptyAtRest: Story = { args: { children: undefined } };","file":"packages/ui/src/atoms/HoverSwap.stories.tsx"},{"name":"LongContent","description":"A long value at rest: the place is as wide as the wider content, so nothing moves.","source":"export const LongContent: Story = {\n args: { children: <Text color=\"secondary\">12,345,678 members</Text> },\n};","file":"packages/ui/src/atoms/HoverSwap.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width row.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <Row>\n <Text>Hanoi</Text>\n <HoverSwap {...args} />\n </Row>\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/HoverSwap.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Count, remove on hover\">\n <Row>\n <Text>Hanoi</Text>\n <HoverSwap {...args} />\n </Row>\n </Case>\n <Case caption=\"Nothing at rest\">\n <Row>\n <Text>Hue</Text>\n <HoverSwap hoverContent={args.hoverContent} />\n </Row>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/HoverSwap.stories.tsx"}],"primaryExample":"Default"},{"name":"InfoTip","level":"atoms","category":"Feedback","keywords":["help","tooltip","warning","explanation"],"import":"import { InfoTip } from \"@antscorp/ui\";","description":"Put a short supplementary explanation beside a control or value.","usage":"Put a short supplementary explanation beside a control or value. Use visible error text for a problem the user must act on.","bestPractices":[{"guidance":true,"description":"Supply a translated label explaining the nearby value; choose error tone for a warning glyph."},{"guidance":false,"description":"Do not put essential instructions only in a tooltip."}],"anatomy":[{"name":"Glyph","required":true,"description":"An info icon, or a warning icon in error tone."},{"name":"Tooltip","required":false,"description":"The explanation supplied by label, shown on hover or keyboard focus."}],"accessibility":["Tab reaches the icon's wrapper, named by label, and focus opens the tooltip. Keep essential instructions visible outside it."],"props":[{"path":"label","type":"string","required":true,"description":"The explanation, shown in the tooltip and read as the icon's name; already translated."},{"path":"tone","type":"\"error\" | \"info\" | undefined","required":false,"description":"`error`: a ⚠ in the error tone, for a problem; `info` (default): an (i) in the accent."}],"examples":[{"name":"Default","description":"The (i) whose tooltip explains the control beside it; hover or focus it.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/InfoTip.stories.tsx"},{"name":"ShortText","description":"A short explanation.","source":"export const ShortText: Story = { args: { label: \"Optional.\" } };","file":"packages/ui/src/atoms/InfoTip.stories.tsx"},{"name":"LongText","description":"A long explanation, which wraps inside the tooltip.","source":"export const LongText: Story = {\n args: {\n label:\n \"Rows are applied from the top and the first one that matches wins, so put the most specific rules first and the catch-all last. Rows below a match are never reached.\",\n },\n};","file":"packages/ui/src/atoms/InfoTip.stories.tsx"},{"name":"ErrorTone","description":"A problem: a ⚠ in the error tone, the reason in the tooltip.","source":"export const ErrorTone: Story = { args: { label: \"This item is archived.\", tone: \"error\" } };","file":"packages/ui/src/atoms/InfoTip.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, next to a label.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n Assignment <InfoTip {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/InfoTip.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <InfoTip {...args} />\n </Case>\n <Case caption=\"Short\">\n <InfoTip label=\"Optional.\" />\n </Case>\n <Case caption=\"Error tone\">\n <InfoTip label=\"This item is archived.\" tone=\"error\" />\n </Case>\n <Case caption=\"Long\">\n <InfoTip label=\"Rows are applied from the top and the first one that matches wins, so put the most specific rules first.\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/InfoTip.stories.tsx"}],"primaryExample":"Default"},{"name":"InlineEditableText","level":"molecules","category":"Inputs","keywords":["inline","text","edit","draft"],"import":"import { InlineEditableText } from \"@antscorp/ui\";","description":"Edit a title or body text in place, committing on blur or Enter without a separate edit mode.","usage":"Edit a title or body text in place, committing on blur or Enter without a separate edit mode. Use CommitTextField for a conventional labeled field.","bestPractices":[{"guidance":true,"description":"Supply both labels.edit and icons.edit to offer a pencil that focuses the input."},{"guidance":false,"description":"Do not expect a field or pencil in read-only mode; the view renders plain text with the placeholder when empty."}],"anatomy":[{"name":"Text","required":true,"description":"A draft input, or plain text in read-only mode."},{"name":"Trailing content","required":false,"description":"The after slot between the text and pencil."},{"name":"Pencil","required":false,"description":"A named host-supplied edit glyph shown on hover."}],"accessibility":["label names the input; labels.edit names the optional pencil button."],"props":[{"path":"label","type":"string","required":true,"description":"The field's accessible name; its label is not shown."},{"path":"value","type":"string","required":true,"description":"The committed text."},{"path":"placeholder","type":"string","required":true,"description":"Shown while the text is empty."},{"path":"onCommit","type":"(value: string) => void","required":true,"description":"Called with the text on blur or Enter, when it changed."},{"path":"maxLength","type":"number | undefined","required":false,"description":"The most characters the text keeps."},{"path":"labels","type":"{ readonly edit?: string | undefined; } | undefined","required":false,"description":"The texts the view shows; the host translates them."},{"path":"labels.edit","type":"string | undefined","required":false,"description":"The pencil's name, such as \"Edit name\"; the pencil shows only with this and `icons.edit`."},{"path":"icons","type":"{ readonly edit?: ReactNode; } | undefined","required":false,"description":"The host's glyphs."},{"path":"icons.edit","type":"ReactNode","required":false,"description":"The pencil, shown on hover, that puts the cursor in the field."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The input's height, `md` by default."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the field and blocks typing."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Plain text, nothing to edit: no field and no pencil."},{"path":"variant","type":"\"body\" | \"title\" | undefined","required":false,"description":"A bold title, or small grey body text; `body` by default."},{"path":"width","type":"number | undefined","required":false,"description":"The field's width in pixels, 240 by default."},{"path":"xstyle","type":"StyleXStyles","required":false,"description":"Extra styles for the text, in the field and in the plain text alike, e.g. a colour or a maximum width."},{"path":"after","type":"ReactNode","required":false,"description":"Shown after the text and before the pencil, e.g. a second field; the pencil stays at the end of the row."}],"examples":[{"name":"Title","description":"A bold title that reads as text; hover for the underline and the pencil.","source":"export const Title: Story = {};","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"Body","description":"Small grey body text, e.g. a description.","source":"export const Body: Story = {\n args: { variant: \"body\", label: \"Description\", value: \"Sent weekly\" },\n};","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"Empty","description":"Empty: the placeholder shows.","source":"export const Empty: Story = { args: { value: \"\" } };","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"Interactive","description":"Typing and leaving the field saves the text.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(args.value, (value, set) => (\n <InlineEditableText\n {...args}\n value={value}\n onCommit={(next) => {\n args.onCommit(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"ReadOnly","description":"Plain text, nothing to edit.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"ReadOnlyEmpty","description":"Plain text, empty: the placeholder.","source":"export const ReadOnlyEmpty: Story = { args: { isReadOnly: true, value: \"\", variant: \"body\" } };","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"Disabled","description":"The field and the pencil dimmed and blocked.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"Medium","description":"The `md` height.","source":"export const Medium: Story = { args: { size: \"md\", variant: \"body\" } };","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"LongValue","description":"A long value, shown whole in a tooltip.","source":"export const LongValue: Story = {\n args: {\n value:\n \"A welcome series for everyone who signed up this month but has not yet placed a first order\",\n },\n};","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"MaxLength","description":"Text cut at 20 characters.","source":"export const MaxLength: Story = { args: { maxLength: 20 } };","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: { width: 200, value: \"A welcome series for everyone who signed up this month\" },\n render: (args) => (\n <Narrow>\n <InlineEditableText {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"WithTrailingField","description":"A second field after the first; the pencil stays at the end of the row.","source":"export const WithTrailingField: Story = {\n args: {\n after: <span>Sent weekly</span>,\n },\n};","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"Restyled","description":"The text restyled through `xstyle`, here narrower than its content.","source":"export const Restyled: Story = {\n args: { xstyle: narrow, isReadOnly: true, value: \"A title cut short by a maximum width\" },\n};","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Title\">\n <InlineEditableText {...args} />\n </Case>\n <Case caption=\"Body\">\n <InlineEditableText {...args} variant=\"body\" value=\"Sent weekly\" />\n </Case>\n <Case caption=\"Empty\">\n <InlineEditableText {...args} value=\"\" />\n </Case>\n <Case caption=\"Read-only title\">\n <InlineEditableText {...args} isReadOnly />\n </Case>\n <Case caption=\"Read-only body\">\n <InlineEditableText {...args} isReadOnly variant=\"body\" value=\"Sent weekly\" />\n </Case>\n <Case caption=\"Disabled\">\n <InlineEditableText {...args} isDisabled />\n </Case>\n <Case caption=\"Medium\">\n <InlineEditableText {...args} size=\"md\" variant=\"body\" />\n </Case>\n <Case caption=\"Trailing field\">\n <InlineEditableText {...args} after={<span>Sent weekly</span>} />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/InlineEditableText.stories.tsx"}],"primaryExample":"Title"},{"name":"JointLabel","level":"atoms","category":"Data display","keywords":["joint","and","or","rows"],"import":"import { JointLabel } from \"@antscorp/ui\";","description":"Show a short connecting word between related rows.","usage":"Show a short connecting word between related rows. Use LabeledDivider when alternatives need a horizontal rule.","bestPractices":[{"guidance":true,"description":"Pass the translated word in normal case and use width to align neighboring joints."},{"guidance":false,"description":"Do not encode rule evaluation in this label; the host owns the meaning and data."}],"props":[{"path":"label","type":"string","required":true,"description":"The joint word between two rows, such as \"or\", \"and\" or \"if\"; shown in capitals unless `case` says otherwise."},{"path":"width","type":"number | undefined","required":false,"description":"A fixed width in pixels, so the words of a list line up. Its own width without it."},{"path":"case","type":"\"sentence\" | \"upper\" | undefined","required":false,"description":"`upper` (default) shows the word in small capitals, as a quiet joint; `sentence` shows it as\ngiven, in body text, as a row's label reads (such as \"Where\", then \"And\")."}],"examples":[{"name":"Default","description":"The word that joins two rows; it is shown in capitals whatever its case.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/JointLabel.stories.tsx"},{"name":"FixedWidth","description":"A fixed width, so the words of a list line up.","source":"export const FixedWidth: Story = { args: { label: \"and\", width: 40 } };","file":"packages/ui/src/atoms/JointLabel.stories.tsx"},{"name":"AlignedList","description":"Words of different lengths in one fixed width, as in a list of rows.","source":"export const AlignedList: Story = {\n render: () => (\n <VStack gap={1}>\n <JointLabel label=\"if\" width={40} />\n <JointLabel label=\"and\" width={40} />\n <JointLabel label=\"or\" width={40} />\n </VStack>\n ),\n};","file":"packages/ui/src/atoms/JointLabel.stories.tsx"},{"name":"SentenceCase","description":"Sentence case, in body text: the word reads as a row's label, as \"Where\" then \"And\".","source":"export const SentenceCase: Story = { args: { label: \"Where\", width: 90, case: \"sentence\" } };","file":"packages/ui/src/atoms/JointLabel.stories.tsx"},{"name":"SentenceList","description":"A list's labels in sentence case, in one fixed width.","source":"export const SentenceList: Story = {\n render: () => (\n <VStack gap={1}>\n <JointLabel label=\"Where\" width={90} case=\"sentence\" />\n <JointLabel label=\"And\" width={90} case=\"sentence\" />\n <JointLabel label=\"And\" width={90} case=\"sentence\" />\n </VStack>\n ),\n};","file":"packages/ui/src/atoms/JointLabel.stories.tsx"},{"name":"LongWord","description":"A long word in a narrow frame.","source":"export const LongWord: Story = {\n args: { label: \"otherwise\" },\n render: (args) => (\n <Narrow>\n <JointLabel {...args} width={40} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/JointLabel.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <JointLabel {...args} />\n </Case>\n <Case caption=\"Fixed width\">\n <JointLabel label=\"and\" width={40} />\n </Case>\n <Case caption=\"Sentence case\">\n <JointLabel label=\"Where\" width={90} case=\"sentence\" />\n </Case>\n <Case caption=\"Long word\">\n <JointLabel label=\"otherwise\" width={40} />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/JointLabel.stories.tsx"}],"primaryExample":"Default"},{"name":"LabeledDivider","level":"atoms","category":"Layout","keywords":["separator","divider","alternatives","rule"],"import":"import { LabeledDivider } from \"@antscorp/ui\";","description":"Separate two blocks with a dashed line and a short joining word.","usage":"Separate two blocks with a dashed line and a short joining word. Use JointLabel for a compact word without the rule.","bestPractices":[{"guidance":true,"description":"Keep label short and use case to control capitalization without changing the translated text."},{"guidance":false,"description":"Do not use the divider as an interactive control."}],"anatomy":[{"name":"Rules","required":true,"description":"Dashed lines extend on both sides of the word."},{"name":"Joint word","required":true,"description":"The centered label explains the relationship between blocks."}],"accessibility":["The wrapper is a horizontal separator named by label."],"props":[{"path":"label","type":"string","required":true,"description":"The short word on the rule, such as \"or\"; it also names the separator."},{"path":"case","type":"\"sentence\" | \"upper\" | undefined","required":false,"description":"`sentence` (default) shows the word as given; `upper` in capitals, such as \"OR\"."}],"examples":[{"name":"Default","description":"The dashed rule with its word, as between two alternatives.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/LabeledDivider.stories.tsx"},{"name":"BetweenBlocks","description":"Between two blocks of content, as it is used.","source":"export const BetweenBlocks: Story = {\n render: (args) => (\n <VStack>\n <Text type=\"body\">First alternative</Text>\n <LabeledDivider {...args} />\n <Text type=\"body\">Second alternative</Text>\n </VStack>\n ),\n};","file":"packages/ui/src/atoms/LabeledDivider.stories.tsx"},{"name":"Upper","description":"The word in capitals, given in sentence case: \"or\" becomes \"OR\".","source":"export const Upper: Story = { args: { label: \"or\", case: \"upper\" } };","file":"packages/ui/src/atoms/LabeledDivider.stories.tsx"},{"name":"LongLabel","description":"A longer word.","source":"export const LongLabel: Story = { args: { label: \"or any of the following instead\" } };","file":"packages/ui/src/atoms/LabeledDivider.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <LabeledDivider {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/LabeledDivider.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <LabeledDivider {...args} />\n </Case>\n <Case caption=\"Upper\">\n <LabeledDivider label=\"or\" case=\"upper\" />\n </Case>\n <Case caption=\"Long label\">\n <LabeledDivider label=\"or any of the following instead\" />\n </Case>\n <Case caption=\"Narrow\">\n <Narrow>\n <LabeledDivider {...args} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/LabeledDivider.stories.tsx"}],"primaryExample":"Default"},{"name":"LoadMoreRow","level":"molecules","category":"Feedback","keywords":["pagination","loading","retry","sentinel"],"import":"import { LoadMoreRow } from \"@antscorp/ui\";","description":"Finish a paged list with loading placeholders or a failed-page retry action.","usage":"Finish a paged list with loading placeholders or a failed-page retry action. Use LoadState for the initial load that replaces the whole content.","bestPractices":[{"guidance":true,"description":"Keep loaded rows above this view and attach sentinelRef to the host's load-more observer."},{"guidance":false,"description":"Do not pass error without onRetry and expect an error row; both are needed to show it."}],"anatomy":[{"name":"Loading rows","required":false,"description":"Two timed skeleton rows in the requested variant."},{"name":"Retry row","required":false,"description":"The translated error and retry button."}],"accessibility":["Supply a clear translated labels.retry for the retry button."],"props":[{"path":"isLoadingMore","type":"boolean","required":false,"description":"The next page is loading: two skeleton rows, kept at least 300 ms once shown (`useLoadingShown`)."},{"path":"variant","type":"\"check\" | \"framed\" | \"option\" | undefined","required":false,"description":"The shape of those skeleton rows, as `RowSkeletons`' `variant`: `option` (default) or the list's own rows, such as `check`."},{"path":"error","type":"string | undefined","required":false,"description":"Why the next page failed, already in the user's language; shown with a retry button."},{"path":"onRetry","type":"(() => void) | undefined","required":false,"description":"Called when the retry button is pressed; no error row without it."},{"path":"labels","type":"{ readonly retry: string; }","required":true,"description":"The texts the view shows; the host translates them."},{"path":"labels.retry","type":"string","required":false,"requiredWhenParent":true,"description":"The retry button's text, e.g. \"Retry\"."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The retry button's size, `md` by default."},{"path":"sentinelRef","type":"RefObject<HTMLSpanElement | null> | undefined","required":false,"description":"The sentinel a list's load-more observer watches (`useLoadMore`)."}],"examples":[{"name":"Idle","description":"Nothing loading and nothing failed: only the invisible sentinel.","source":"export const Idle: Story = {};","file":"packages/ui/src/molecules/LoadMoreRow.stories.tsx"},{"name":"LoadingMore","description":"The next page is loading.","source":"export const LoadingMore: Story = { args: { isLoadingMore: true } };","file":"packages/ui/src/molecules/LoadMoreRow.stories.tsx"},{"name":"LoadingMoreChecks","description":"The next page is loading, in a list of checkbox rows: the skeleton rows take `CheckRow`'s shape.","source":"export const LoadingMoreChecks: Story = { args: { isLoadingMore: true, variant: \"check\" } };","file":"packages/ui/src/molecules/LoadMoreRow.stories.tsx"},{"name":"LoadError","description":"The next page failed: its error and a retry button, the loaded items kept above.","source":"export const LoadError: Story = { args: { error: \"Could not load more.\" } };","file":"packages/ui/src/molecules/LoadMoreRow.stories.tsx"},{"name":"ErrorWithoutRetry","description":"An error without a way to retry shows nothing.","source":"export const ErrorWithoutRetry: Story = {\n render: (args) => <LoadMoreRow labels={args.labels} error=\"Could not load more.\" />,\n};","file":"packages/ui/src/molecules/LoadMoreRow.stories.tsx"},{"name":"Medium","description":"The `md` size.","source":"export const Medium: Story = { args: { error: \"Could not load more.\", size: \"md\" } };","file":"packages/ui/src/molecules/LoadMoreRow.stories.tsx"},{"name":"InNarrowFrame","description":"A long error in a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: { error: \"Could not load more segments: the server took too long to answer.\" },\n render: (args) => (\n <Narrow>\n <LoadMoreRow {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/LoadMoreRow.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Idle\">\n <LoadMoreRow {...args} />\n </Case>\n <Case caption=\"Loading more\">\n <LoadMoreRow {...args} isLoadingMore />\n </Case>\n <Case caption=\"Error\">\n <LoadMoreRow {...args} error=\"Could not load more.\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/LoadMoreRow.stories.tsx"}],"primaryExample":"Idle"},{"name":"LoadState","level":"molecules","category":"Feedback","keywords":["loading","error","retry","placeholder"],"import":"import { LoadState } from \"@antscorp/ui\";","description":"Switch between a content-shaped loading placeholder, a failure with retry, and loaded children.","usage":"Switch between a content-shaped loading placeholder, a failure with retry, and loaded children. Use LoadMoreRow for later pages so existing results can remain visible.","bestPractices":[{"guidance":true,"description":"Choose FieldSkeleton or RowSkeletons as placeholder and give that placeholder its loading status name."},{"guidance":false,"description":"Do not use a generic spinner in place of the content-shaped skeleton."}],"anatomy":[{"name":"Placeholder","required":false,"description":"Host-supplied skeleton content while loading."},{"name":"Error","required":false,"description":"A failure title, optional detail and retry button."},{"name":"Content","required":false,"description":"The children after loading succeeds."}],"accessibility":["The placeholder names its own loading status; labels.retry names the error action."],"props":[{"path":"isLoading","type":"boolean","required":true,"description":"Shows `placeholder` instead of the children; a slow load keeps it at least 300 ms (`useLoadingShown`)."},{"path":"placeholder","type":"ReactNode","required":true,"description":"Drawn while loading: a skeleton in the shape of what loads (`FieldSkeleton`, `RowSkeletons`),\nwhich names its own loading status."},{"path":"error","type":"{ readonly title: string; readonly detail?: string | undefined; } | undefined","required":false,"description":"The load's failure: shown with a retry button instead of the children."},{"path":"error.title","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"error.detail","type":"string | undefined","required":false,"description":""},{"path":"labels","type":"{ readonly retry: string; }","required":true,"description":"The texts the view shows; the host translates them."},{"path":"labels.retry","type":"string","required":false,"requiredWhenParent":true,"description":"The retry button's text, e.g. \"Retry\"."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The retry button's size, `md` by default."},{"path":"onRetry","type":"() => void","required":true,"description":"Called when the retry button is pressed."},{"path":"children","type":"ReactNode","required":true,"description":"What shows once loaded, when neither loading nor failed."}],"examples":[{"name":"Loaded","description":"Loaded: the children show.","source":"export const Loaded: Story = {};","file":"packages/ui/src/molecules/LoadState.stories.tsx"},{"name":"Loading","description":"Still loading: the placeholder, here rows of a list.","source":"export const Loading: Story = { args: { isLoading: true } };","file":"packages/ui/src/molecules/LoadState.stories.tsx"},{"name":"LoadingField","description":"Loading a field: the placeholder in a field's shape.","source":"export const LoadingField: Story = {\n args: {\n isLoading: true,\n placeholder: <FieldSkeleton label=\"Country\" labels={{ loading: \"Loading\" }} />,\n },\n};","file":"packages/ui/src/molecules/LoadState.stories.tsx"},{"name":"ErrorTitleOnly","description":"Failed, with a title only.","source":"export const ErrorTitleOnly: Story = { args: { error: { title: \"Could not load the list.\" } } };","file":"packages/ui/src/molecules/LoadState.stories.tsx"},{"name":"ErrorWithDetail","description":"Failed, with a reason.","source":"export const ErrorWithDetail: Story = {\n args: { error: { title: \"Could not load the list.\", detail: \"The server answered 503.\" } },\n};","file":"packages/ui/src/molecules/LoadState.stories.tsx"},{"name":"Medium","description":"The `md` size, for the error's retry button.","source":"export const Medium: Story = { args: { size: \"md\", error: { title: \"Could not load the list.\" } } };","file":"packages/ui/src/molecules/LoadState.stories.tsx"},{"name":"Interactive","description":"Retrying goes from the error through loading to the content.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<\"error\" | \"loading\" | \"loaded\">(\"error\", (state, set) => (\n <LoadState\n {...args}\n isLoading={state === \"loading\"}\n error={state === \"error\" ? { title: \"Could not load the list.\" } : undefined}\n onRetry={() => {\n args.onRetry();\n set(\"loading\");\n setTimeout(() => {\n set(\"loaded\");\n }, 1000);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/LoadState.stories.tsx"},{"name":"LongContent","description":"A very long title and reason.","source":"export const LongContent: Story = {\n args: {\n error: {\n title:\n \"Could not load the list of items for this account, which has more than ten thousand entries\",\n detail:\n \"The server answered with a gateway timeout after waiting for a minute and a half; try again, or narrow the list down first.\",\n },\n },\n};","file":"packages/ui/src/molecules/LoadState.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: LongContent.args ?? {},\n render: (args) => (\n <Narrow>\n <LoadState {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/LoadState.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Loaded\">\n <LoadState {...args} />\n </Case>\n <Case caption=\"Loading\">\n <LoadState {...args} isLoading />\n </Case>\n <Case caption=\"Error\">\n <LoadState\n {...args}\n error={{ title: \"Could not load.\", detail: \"The server answered 503.\" }}\n />\n </Case>\n <Case caption=\"Loading a field\">\n <LoadState\n {...args}\n isLoading\n placeholder={<FieldSkeleton label=\"Country\" labels={{ loading: \"Loading\" }} />}\n />\n </Case>\n <Case caption=\"Medium error\">\n <LoadState {...args} size=\"md\" error={{ title: \"Could not load.\" }} />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/LoadState.stories.tsx"}],"primaryExample":"Loaded"},{"name":"LogoImage","level":"atoms","category":"Data display","keywords":["logo","avatar","image","fallback"],"import":"import { LogoImage } from \"@antscorp/ui\";","description":"Show a decorative logo or avatar beside content that already names it.","usage":"Show a decorative logo or avatar beside content that already names it. A skeleton reserves its box until loading finishes, and fallback replaces a failed image.","bestPractices":[{"guidance":true,"description":"Supply a fallback appropriate to the surrounding control and match size to its reserved box."},{"guidance":false,"description":"Do not use this decorative image to carry information that is absent from adjacent text."}],"anatomy":[{"name":"Image","required":false,"description":"A lazy-loaded image in the requested square or round box, until a load failure replaces it."},{"name":"Loading placeholder","required":false,"description":"A matching skeleton while the image loads."},{"name":"Fallback","required":false,"description":"Host content shown instead of the image after a load failure."}],"accessibility":["The image has an empty alt attribute; provide the identifying text elsewhere."],"props":[{"path":"src","type":"string","required":true,"description":"The image's URL."},{"path":"size","type":"number","required":true,"description":"Its width and height, in pixels; the skeleton takes the same box."},{"path":"fallback","type":"ReactNode","required":true,"description":"Shown in place of the image when it fails to load, such as an icon."},{"path":"isRound","type":"boolean","required":false,"description":"A circle rather than a square: the image's corners and the skeleton are round."},{"path":"isDimmed","type":"boolean","required":false,"description":"Dimmed, as the control it sits in is disabled."}],"examples":[{"name":"Default","description":"A logo, loaded: a skeleton of its size stands in while it loads.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/LogoImage.stories.tsx"},{"name":"Small","description":"Small, as in a list row.","source":"export const Small: Story = { args: { size: 16, fallback: <Icon icon=\"info\" size=\"sm\" /> } };","file":"packages/ui/src/atoms/LogoImage.stories.tsx"},{"name":"Round","description":"Round, as in a circle: the image's corners and the skeleton are round.","source":"export const Round: Story = { args: { isRound: true } };","file":"packages/ui/src/atoms/LogoImage.stories.tsx"},{"name":"Dimmed","description":"Dimmed, as in a disabled control.","source":"export const Dimmed: Story = { args: { isDimmed: true } };","file":"packages/ui/src/atoms/LogoImage.stories.tsx"},{"name":"Broken","description":"The image fails to load: the fallback shows instead.","source":"export const Broken: Story = { args: { src: BROKEN } };","file":"packages/ui/src/atoms/LogoImage.stories.tsx"},{"name":"Many","description":"Many logos in a row, some broken.","source":"export const Many: Story = {\n render: (args) => (\n <HStack gap={2} wrap=\"wrap\">\n {Array.from({ length: 12 }, (_, index) => (\n <LogoImage key={index} {...args} src={index % 4 === 3 ? BROKEN : LOGO} />\n ))}\n </HStack>\n ),\n};","file":"packages/ui/src/atoms/LogoImage.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <LogoImage {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/LogoImage.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Loaded\">\n <LogoImage {...args} />\n </Case>\n <Case caption=\"Round\">\n <LogoImage {...args} isRound />\n </Case>\n <Case caption=\"Dimmed\">\n <LogoImage {...args} isDimmed />\n </Case>\n <Case caption=\"Broken\">\n <LogoImage {...args} src={BROKEN} />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/LogoImage.stories.tsx"}],"primaryExample":"Default"},{"name":"MetaTable","level":"molecules","category":"Data display","keywords":["table","metadata","names","counts"],"import":"import { MetaTable } from \"@antscorp/ui\";","description":"Display a read-only list of names with an optional short-value column.","usage":"Display a read-only list of names with an optional short-value column. Use TransferPicker when users need to edit the picked values.","bestPractices":[{"guidance":true,"description":"Provide labels.meta to show the meta column and labels.loading when rows are loading."},{"guidance":false,"description":"Do not expect a row with error to remain a link; its name becomes plain error-toned text with an explanation."}],"anatomy":[{"name":"Name column","required":true,"description":"A heading and names, optionally linked in a new tab."},{"name":"Meta column","required":false,"description":"Short values under labels.meta."},{"name":"Problem tip","required":false,"description":"A warning explanation for an invalid row."},{"name":"Loading cells","required":false,"description":"Shimmer bars in the real columns for loading rows."}],"accessibility":["The first loading row provides the one status named by labels.loading."],"props":[{"path":"label","type":"string","required":true,"description":"Heads the column of names, such as \"Cities\"."},{"path":"items","type":"readonly MetaTableItem[]","required":true,"description":"The rows, in their order."},{"path":"items[].id","type":"string","required":false,"requiredWhenParent":true,"description":"The row's stable key, such as the id of what it stands for."},{"path":"items[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The row's name."},{"path":"items[].href","type":"string | undefined","required":false,"description":"Where the name links to, opened in a new tab; plain text without it or with an `error`."},{"path":"items[].error","type":"string | undefined","required":false,"description":"A problem with what the row stands for, already translated: the name in the error tone and a ⚠ tip."},{"path":"items[].meta","type":"string | undefined","required":false,"description":"A short value at the end of the row, such as a count; `labels.meta` heads its column."},{"path":"items[].isLoading","type":"boolean","required":false,"description":"What the row stands for is loading: shimmer bars in its cells instead of its name and value,\nkept at least 300 ms once shown (`useLoadingShown`)."},{"path":"labels","type":"{ readonly meta?: string | undefined; readonly loading?: string | undefined; } | undefined","required":false,"description":"The texts the table shows; the host translates them."},{"path":"labels.meta","type":"string | undefined","required":false,"description":"Heads the column of the rows' `meta`, such as \"Members\"; no such column without it."},{"path":"labels.loading","type":"string | undefined","required":false,"description":"The loading status's accessible name while rows load, such as \"Loading\"."}],"examples":[{"name":"Default","description":"Names as links, their counts at the end, one with a problem.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/MetaTable.stories.tsx"},{"name":"Empty","description":"No rows: the headings alone.","source":"export const Empty: Story = { args: { items: [] } };","file":"packages/ui/src/molecules/MetaTable.stories.tsx"},{"name":"PlainNames","description":"No links: the names as plain text.","source":"export const PlainNames: Story = {\n args: { items: ITEMS.map(({ id, label, meta: value }) => ({ id, label, meta: value })) },\n};","file":"packages/ui/src/molecules/MetaTable.stories.tsx"},{"name":"WithError","description":"A problem: the name in the error tone, a ⚠ holding the reason.","source":"export const WithError: Story = { args: { items: [ITEMS[2] ?? { id: \"old\", label: \"Old city\" }] } };","file":"packages/ui/src/molecules/MetaTable.stories.tsx"},{"name":"NamesOnly","description":"No value column: the names alone.","source":"export const NamesOnly: Story = { args: { labels: undefined } };","file":"packages/ui/src/molecules/MetaTable.stories.tsx"},{"name":"LongText","description":"A long name cut to one line; hover it for the whole name.","source":"export const LongText: Story = { args: { items: [LONG, ...ITEMS] } };","file":"packages/ui/src/molecules/MetaTable.stories.tsx"},{"name":"ManyRows","description":"Many rows.","source":"export const ManyRows: Story = {\n args: {\n items: Array.from({ length: 15 }, (_, index) => ({\n id: String(index),\n label: `City ${index + 1}`,\n href: `https://example.com/${index + 1}`,\n meta: `${(index + 1) * 100}`,\n })),\n },\n};","file":"packages/ui/src/molecules/MetaTable.stories.tsx"},{"name":"LoadingRows","description":"Rows still loading: shimmer bars in their cells, in the real columns, under one status.","source":"export const LoadingRows: Story = {\n args: {\n items: [\n ...ITEMS.slice(0, 1),\n { id: \"far-1\", label: \"far-1\", isLoading: true },\n { id: \"far-2\", label: \"far-2\", isLoading: true },\n ],\n labels: { meta: \"Members\", loading: \"Loading\" },\n },\n};","file":"packages/ui/src/molecules/MetaTable.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: { items: [LONG, ...ITEMS] },\n render: (args) => (\n <Narrow>\n <MetaTable {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/MetaTable.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Links and counts\">\n <MetaTable {...args} />\n </Case>\n <Case caption=\"Names only\">\n <MetaTable {...args} labels={undefined} />\n </Case>\n <Case caption=\"Loading rows\">\n <MetaTable {...args} {...LoadingRows.args} />\n </Case>\n <Case caption=\"Empty\">\n <MetaTable {...args} items={[]} />\n </Case>\n <Case caption=\"Long, narrow\">\n <Narrow>\n <MetaTable {...args} items={[LONG]} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/MetaTable.stories.tsx"}],"primaryExample":"Default"},{"name":"PanelSearchInput","level":"atoms","category":"Inputs","keywords":["search","query","panel","clear"],"import":"import { PanelSearchInput } from \"@antscorp/ui\";","description":"Place a controlled search row at the top of a dropdown panel.","usage":"Place a controlled search row at the top of a dropdown panel. Use SearchableGroupedList when filtering and grouped results should be composed for you.","bestPractices":[{"guidance":true,"description":"Supply label and labels.clear, and update value on each onChange; add a Divider below when separating results."},{"guidance":false,"description":"Do not expect the input to filter data itself or provide disabled and read-only modes."}],"anatomy":[{"name":"Search glyph","required":true,"description":"A magnifier at the start or end of the row."},{"name":"Input","required":true,"description":"The controlled text query."},{"name":"Clear button","required":false,"description":"Clears a nonempty query and restores focus to the input."}],"accessibility":["The input is named by label and the clear button by labels.clear."],"props":[{"path":"label","type":"string","required":true,"description":"The search box's accessible name (it has no visible label), such as \"Search\"."},{"path":"placeholder","type":"string | undefined","required":false,"description":"Shown while the query is empty; `label` when absent."},{"path":"value","type":"string","required":true,"description":"The query."},{"path":"onChange","type":"(value: string) => void","required":true,"description":"Called with the next query on each keystroke, and with \"\" on clear."},{"path":"labels","type":"{ readonly clear: string; }","required":true,"description":"The texts the row shows; the host translates them."},{"path":"labels.clear","type":"string","required":false,"requiredWhenParent":true,"description":"The clear button's name, such as \"Clear search\"; it shows only an icon."},{"path":"iconPosition","type":"\"end\" | \"start\" | undefined","required":false,"description":"Where the magnifier sits: `start` (default) or `end`, after the clear button."},{"path":"ref","type":"Ref<HTMLInputElement> | undefined","required":false,"description":"The input element, to focus it (with `useFocusLater` in a pop-over, say)."}],"examples":[{"name":"Default","description":"Empty: the magnifier and the placeholder (the label without one).","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/PanelSearchInput.stories.tsx"},{"name":"Filled","description":"A query typed: the clear button shows.","source":"export const Filled: Story = { args: { value: \"price\" } };","file":"packages/ui/src/atoms/PanelSearchInput.stories.tsx"},{"name":"WithPlaceholder","description":"Its own placeholder.","source":"export const WithPlaceholder: Story = { args: { placeholder: \"Search options\" } };","file":"packages/ui/src/atoms/PanelSearchInput.stories.tsx"},{"name":"IconAtEnd","description":"The magnifier at the end, after the clear button.","source":"export const IconAtEnd: Story = { args: { iconPosition: \"end\", value: \"price\" } };","file":"packages/ui/src/atoms/PanelSearchInput.stories.tsx"},{"name":"Interactive","description":"Typing and clearing change the query.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(\"\", (value, set) => (\n <PanelSearchInput\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/atoms/PanelSearchInput.stories.tsx"},{"name":"LongQuery","description":"A long query scrolls inside the box.","source":"export const LongQuery: Story = {\n args: { value: \"the number of times the visitor opened the newsletter last quarter\" },\n};","file":"packages/ui/src/atoms/PanelSearchInput.stories.tsx"},{"name":"InNarrow","description":"In a 280px pop-over width.","source":"export const InNarrow: Story = {\n args: { value: \"sku\" },\n decorators: [\n (Story) => (\n <Narrow>\n <Story />\n </Narrow>\n ),\n ],\n};","file":"packages/ui/src/atoms/PanelSearchInput.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Empty\">\n <PanelSearchInput {...args} />\n </Case>\n <Case caption=\"Filled\">\n <PanelSearchInput {...args} value=\"price\" />\n </Case>\n <Case caption=\"Placeholder\">\n <PanelSearchInput {...args} placeholder=\"Search options\" />\n </Case>\n <Case caption=\"Magnifier at the end\">\n <PanelSearchInput {...args} iconPosition=\"end\" />\n </Case>\n <Case caption=\"Long query\">\n <PanelSearchInput {...args} value={LongQuery.args?.value ?? \"\"} />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/PanelSearchInput.stories.tsx"}],"primaryExample":"Default"},{"name":"PathMark","level":"atoms","category":"Layout","keywords":["path","reveal","focus","form"],"import":"import { PathMark } from \"@antscorp/ui\";","description":"Mark controls so revealPath can scroll to and focus the value associated with a data path.","usage":"Mark controls so revealPath can scroll to and focus the value associated with a data path. Nested marks interpret their paths relative to the enclosing mark.","bestPractices":[{"guidance":true,"description":"Wrap the controls of the value and use a relative path inside another PathMark."},{"guidance":false,"description":"Do not expect PathMark to render a visual frame or provide a control label."}],"accessibility":["Keep accessible names on the wrapped controls; PathMark only carries their path for revealPath."],"props":[{"path":"path","type":"Path","required":true,"description":"Where the marked controls' value is, relative to the nearest enclosing `PathMark` (absolute\nat the root), such as `[\"days\"]` inside a mark of `[\"settings\"]`."},{"path":"children","type":"ReactNode","required":true,"description":"The controls of that value."}],"examples":[{"name":"Default","description":"A marked input: nothing shows of the mark, which adds no box.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/PathMark.stories.tsx"},{"name":"Nested","description":"Nested: the inner mark's path is relative, its attribute `settings.name.first`.","source":"export const Nested: Story = {\n render: (args) => (\n <PathMark path={[\"settings\"]}>\n <PathMark path={[\"name\"]}>\n <PathMark {...args} path={[\"first\"]} />\n </PathMark>\n </PathMark>\n ),\n};","file":"packages/ui/src/atoms/PathMark.stories.tsx"},{"name":"Empty","description":"Marks nothing visible.","source":"export const Empty: Story = { args: { children: null } };","file":"packages/ui/src/atoms/PathMark.stories.tsx"},{"name":"Several","description":"A mark around several controls, laid out by the stack holding it as if it were not there.","source":"export const Several: Story = {\n render: (args) => (\n <VStack gap={2}>\n <PathMark {...args}>\n <TextInput label=\"First name\" value=\"\" onChange={() => {}} />\n <TextInput label=\"Last name\" value=\"\" onChange={() => {}} />\n </PathMark>\n </VStack>\n ),\n};","file":"packages/ui/src/atoms/PathMark.stories.tsx"},{"name":"Interactive","description":"Press the button: `revealPath` scrolls the marked input at the bottom into view and focuses it.","source":"export const Interactive: Story = {\n render: (args) => <Reveal path={args.path} />,\n};","file":"packages/ui/src/atoms/PathMark.stories.tsx"},{"name":"InNarrow","description":"In a drawer-width frame.","source":"export const InNarrow: Story = {\n render: (args) => (\n <Narrow>\n <PathMark {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/PathMark.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"One control\">\n <PathMark {...args} />\n </Case>\n <Case caption=\"Narrow\">\n <Narrow>\n <PathMark {...args} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/PathMark.stories.tsx"}],"primaryExample":"Default"},{"name":"PercentRow","level":"molecules","category":"Inputs","keywords":["percent","slider","share","commit"],"import":"import { PercentRow } from \"@antscorp/ui\";","description":"Edit a whole percentage through a number input and a bounded slider.","usage":"Edit a whole percentage through a number input and a bounded slider. Dragging previews locally and reports the change only when the interaction ends.","bestPractices":[{"guidance":true,"description":"Use onChange to store committed values and isRemoveDisabled when removal must also be blocked."},{"guidance":false,"description":"Do not assume isDisabled blocks removal; it locks the share while the optional remove action stays active."}],"anatomy":[{"name":"Caption","required":false,"description":"The FieldRow caption beside the controls."},{"name":"Percent input","required":true,"description":"The controlled whole percentage."},{"name":"Slider","required":true,"description":"A bounded percentage control with local drag preview."},{"name":"Remove button","required":false,"description":"A named removal action supplied through onRemove."}],"accessibility":["labels.share names the controls and labels.remove names the optional remove button."],"props":[{"path":"label","type":"string | undefined","required":false,"description":"The row's caption, in a column at the start. Without it the column stays, empty, so the\ncontent lines up with the captioned rows around it; stacked,\nnothing is drawn above the content."},{"path":"labelWidth","type":"number | undefined","required":false,"description":"Overrides the theme's caption column width in pixels; unthemed rows default to 160px."},{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether the input is disabled."},{"path":"isReadOnly","type":"boolean","required":false,"default":"false","description":"Whether the input is read-only.\nThe value is shown at full opacity and still submits with the form, but\ncannot be edited. Unlike `isDisabled`, a read-only input is not dimmed and\nstays in the tab order — use it for a value the user should see and send\nbut not change. Stepping is off in every form while read-only: arrow keys,\nthe wheel, and the number steppers. `isDisabled` takes precedence when both\nare set."},{"path":"disabledMessage","type":"string | undefined","required":false,"description":"Explains why the input is disabled. When set together with `isDisabled`,\nthe input shows a tooltip with this text on hover and keyboard focus, and\nstays focusable (via `aria-disabled`) so the reason is discoverable by\nkeyboard and assistive technology. The field cannot be edited (it becomes\nread-only) while disabled.\n\nUse this instead of wrapping a disabled input in `Tooltip` — disabled\ncontrols don't emit the pointer events an external tooltip needs."},{"path":"labels","type":"{ readonly share: string; readonly remove: string; }","required":true,"description":"The controls' accessible name and the optional remove button's name."},{"path":"labels.share","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"labels.remove","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"value","type":"number","required":true,"description":"The share, in whole percent."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The controls' size, `md` by default."},{"path":"isRemoveDisabled","type":"boolean","required":false,"description":"Disables only removal, `false` by default; a locked share may still be removed."},{"path":"onChange","type":"(value: number) => void","required":true,"description":"Called on input commit or when slider interaction ends, never during dragging."},{"path":"onRemove","type":"(() => void) | undefined","required":false,"description":"Shows a remove button when given."}],"examples":[{"name":"Default","description":"A filled percent input and slider beside the caption.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/PercentRow.stories.tsx"},{"name":"Interactive","description":"Input commits and completed slider interactions update the share.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(50, (value, set) => <PercentRow {...args} value={value} onChange={set} />),\n};","file":"packages/ui/src/molecules/PercentRow.stories.tsx"},{"name":"Disabled","description":"The share is locked and the caption dimmed; removal stays active.","source":"export const Disabled: Story = {\n args: {\n isDisabled: true,\n disabledMessage: \"This share is calculated.\",\n onRemove: fn<() => void>(),\n },\n};","file":"packages/ui/src/molecules/PercentRow.stories.tsx"},{"name":"RemovalDisabled","description":"A disabled share whose removal is also disabled.","source":"export const RemovalDisabled: Story = {\n args: { ...Disabled.args, isRemoveDisabled: true },\n};","file":"packages/ui/src/molecules/PercentRow.stories.tsx"},{"name":"ReadOnly","description":"Read-only keeps the value visible and hides removal.","source":"export const ReadOnly: Story = { args: { isReadOnly: true, onRemove: fn<() => void>() } };","file":"packages/ui/src/molecules/PercentRow.stories.tsx"},{"name":"Removable","description":"Removal is available when its callback is supplied.","source":"export const Removable: Story = { args: { onRemove: fn<() => void>() } };","file":"packages/ui/src/molecules/PercentRow.stories.tsx"},{"name":"Zero","description":"No share at the lower bound.","source":"export const Zero: Story = { args: { value: 0 } };","file":"packages/ui/src/molecules/PercentRow.stories.tsx"},{"name":"Hundred","description":"The whole share at the upper bound.","source":"export const Hundred: Story = { args: { value: 100 } };","file":"packages/ui/src/molecules/PercentRow.stories.tsx"},{"name":"LongContent","description":"A caption wraps within its column.","source":"export const LongContent: Story = {\n args: { label: \"A group with a very long caption spanning several lines\" },\n};","file":"packages/ui/src/molecules/PercentRow.stories.tsx"},{"name":"Small","description":"Compact controls follow the requested size.","source":"export const Small: Story = { args: { size: \"sm\" } };","file":"packages/ui/src/molecules/PercentRow.stories.tsx"},{"name":"InNarrowFrame","description":"The caption stacks and the slider shrinks in a narrow frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <PercentRow {...args} onRemove={fn<() => void>()} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/PercentRow.stories.tsx"},{"name":"AllStates","description":"Filled, disabled, removable, read-only and boundary states together.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Filled\">\n <PercentRow {...args} />\n </Case>\n <Case caption=\"Disabled\">\n <PercentRow {...args} {...Disabled.args} />\n </Case>\n <Case caption=\"Removal disabled\">\n <PercentRow {...args} {...RemovalDisabled.args} />\n </Case>\n <Case caption=\"Removable\">\n <PercentRow {...args} {...Removable.args} />\n </Case>\n <Case caption=\"Read-only\">\n <PercentRow {...args} {...ReadOnly.args} />\n </Case>\n <Case caption=\"Zero\">\n <PercentRow {...args} value={0} />\n </Case>\n <Case caption=\"Hundred\">\n <PercentRow {...args} value={100} />\n </Case>\n <Case caption=\"Long caption\">\n <PercentRow {...args} {...LongContent.args} />\n </Case>\n <Case caption=\"Narrow\">\n <Narrow>\n <PercentRow {...args} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/PercentRow.stories.tsx"}],"primaryExample":"Default"},{"name":"PhoneFrame","level":"molecules","category":"Layout","keywords":["phone","preview","device","content"],"import":"import { PhoneFrame } from \"@antscorp/ui\";","description":"Preview content inside a drawn phone with a status bar and titled header.","usage":"Preview content inside a drawn phone with a status bar and titled header. Use it for a visual preview rather than interactive device navigation.","bestPractices":[{"guidance":true,"description":"Give title a recognizable name and keep preview content readable within the scrolling body."},{"guidance":false,"description":"Do not expect the drawn back chevron or status-bar symbols to perform actions."}],"anatomy":[{"name":"Device frame","required":true,"description":"A rounded phone outline with a camera island."},{"name":"Status bar","required":true,"description":"Clock, signal and battery imagery."},{"name":"Header","required":true,"description":"A back chevron, avatar and title."},{"name":"Content","required":true,"description":"Host preview content that scrolls within the device."}],"accessibility":["The frame is a figure named by title."],"props":[{"path":"title","type":"string","required":true,"description":"Who the screen's conversation is with, beside the avatar; it also names the frame."},{"path":"avatarSrc","type":"string | undefined","required":false,"description":"The URL of the avatar's picture; without one the avatar shows the title's initials."},{"path":"labels","type":"{ readonly time?: string | undefined; } | undefined","required":false,"description":"Inner texts: `time`, the status bar's clock, `\"9:41\"` by default (Apple's pictured time)."},{"path":"labels.time","type":"string | undefined","required":false,"description":""},{"path":"children","type":"ReactNode","required":true,"description":"The screen's content, such as chat messages or an empty-state text."}],"examples":[{"name":"Default","description":"One message on the screen, under the sender's avatar and name.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/PhoneFrame.stories.tsx"},{"name":"NoAvatar","description":"No picture: the avatar shows the title's initials.","source":"export const NoAvatar: Story = { args: { avatarSrc: undefined } };","file":"packages/ui/src/molecules/PhoneFrame.stories.tsx"},{"name":"Empty","description":"Nothing to show yet: an empty-state text.","source":"export const Empty: Story = {\n args: { children: <Text type=\"supporting\">Nothing to preview yet</Text> },\n};","file":"packages/ui/src/molecules/PhoneFrame.stories.tsx"},{"name":"LongContent","description":"A long message that scrolls in the screen, and a long title cut to one line.","source":"export const LongContent: Story = {\n args: {\n title: \"A sender with a very long name that does not fit the bar\",\n children: message(\"A long message that goes on. \".repeat(40)),\n },\n};","file":"packages/ui/src/molecules/PhoneFrame.stories.tsx"},{"name":"ManyMessages","description":"Several messages one under the other.","source":"export const ManyMessages: Story = {\n args: {\n children: (\n <>\n {message(\"First part\")}\n {message(\"Second part\")}\n {message(\"Third part\")}\n </>\n ),\n },\n};","file":"packages/ui/src/molecules/PhoneFrame.stories.tsx"},{"name":"OtherTime","description":"Another clock in the status bar.","source":"export const OtherTime: Story = { args: { labels: { time: \"14:05\" } } };","file":"packages/ui/src/molecules/PhoneFrame.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame: the phone shrinks, keeping its proportions.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <PhoneFrame {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/PhoneFrame.stories.tsx"},{"name":"AllStates","description":"The key states side by side.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"A message\">\n <PhoneFrame {...args} />\n </Case>\n <Case caption=\"No avatar\">\n <PhoneFrame title={args.title}>{args.children}</PhoneFrame>\n </Case>\n <Case caption=\"Empty\">\n <PhoneFrame {...args}>\n <Text type=\"supporting\">Nothing to preview yet</Text>\n </PhoneFrame>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/PhoneFrame.stories.tsx"}],"primaryExample":"Default"},{"name":"ProblemsButton","level":"molecules","category":"Feedback","keywords":["errors","problems","popover","count"],"import":"import { ProblemsButton } from \"@antscorp/ui\";","description":"Summarize validation problems in a count button that opens a selectable list.","usage":"Summarize validation problems in a count button that opens a selectable list. The view renders nothing when items is empty.","bestPractices":[{"guidance":true,"description":"Translate labels.count with the problem count and use onPick's index to locate the corresponding problem."},{"guidance":false,"description":"Do not assume picking a problem fixes it; the host decides how to reveal or address it."}],"anatomy":[{"name":"Count button","required":true,"description":"An error glyph and the number of problems when there are problems."},{"name":"Popover","required":false,"description":"A titled, scrollable list built while open."},{"name":"Problem actions","required":false,"description":"A button per problem that reports its index and closes the popover."}],"accessibility":["labels.count names the count button; title labels the problem list."],"props":[{"path":"items","type":"readonly string[]","required":true,"description":"The problems, already translated; nothing shows without one."},{"path":"title","type":"string","required":true,"description":"The popover's heading, such as \"Errors\"."},{"path":"labels","type":"{ readonly count: (count: number) => string; }","required":true,"description":"The texts the view shows; the host translates them."},{"path":"labels.count","type":"(count: number) => string","required":false,"requiredWhenParent":true,"description":"The button's name for a number of problems, such as \"3 errors\"."},{"path":"onPick","type":"(index: number) => void","required":true,"description":"Called with the index of the problem pressed in the list, which then closes."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The button's height, `md` by default."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dimmed and not pressable."}],"examples":[{"name":"Default","description":"Three problems: the glyph and the count; press it for the list.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/ProblemsButton.stories.tsx"},{"name":"Empty","description":"No problem: nothing renders.","source":"export const Empty: Story = { args: { items: [] } };","file":"packages/ui/src/molecules/ProblemsButton.stories.tsx"},{"name":"Open","description":"The list open.","source":"export const Open: Story = { play: open };","file":"packages/ui/src/molecules/ProblemsButton.stories.tsx"},{"name":"Interactive","description":"Picking a problem removes it, as fixing it would; the button goes at none.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(ITEMS, (items, set) => (\n <ProblemsButton\n {...args}\n items={items}\n onPick={(index) => {\n args.onPick(index);\n set(items.filter((_item, at) => at !== index));\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/ProblemsButton.stories.tsx"},{"name":"Single","description":"One problem.","source":"export const Single: Story = { args: { items: ITEMS.slice(0, 1) } };","file":"packages/ui/src/molecules/ProblemsButton.stories.tsx"},{"name":"Many","description":"Fourteen problems: the open list scrolls.","source":"export const Many: Story = { args: { items: MANY }, play: open };","file":"packages/ui/src/molecules/ProblemsButton.stories.tsx"},{"name":"LongText","description":"Long problems wrap in the open list.","source":"export const LongText: Story = { args: { items: LONG }, play: open };","file":"packages/ui/src/molecules/ProblemsButton.stories.tsx"},{"name":"Disabled","description":"Not pressable.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/ProblemsButton.stories.tsx"},{"name":"Small","description":"The `sm` height.","source":"export const Small: Story = { args: { size: \"sm\" } };","file":"packages/ui/src/molecules/ProblemsButton.stories.tsx"},{"name":"InNarrow","description":"In a drawer-width frame, open.","source":"export const InNarrow: Story = {\n render: (args) => (\n <Narrow>\n <ProblemsButton {...args} />\n </Narrow>\n ),\n play: open,\n};","file":"packages/ui/src/molecules/ProblemsButton.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Three\">\n <ProblemsButton {...args} />\n </Case>\n <Case caption=\"One\">\n <ProblemsButton {...args} items={ITEMS.slice(0, 1)} />\n </Case>\n <Case caption=\"Many\">\n <ProblemsButton {...args} items={MANY} />\n </Case>\n <Case caption=\"Small\">\n <ProblemsButton {...args} size=\"sm\" />\n </Case>\n <Case caption=\"Disabled\">\n <ProblemsButton {...args} isDisabled />\n </Case>\n <Case caption=\"None (renders nothing)\">\n <ProblemsButton {...args} items={[]} />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/ProblemsButton.stories.tsx"}],"primaryExample":"Default"},{"name":"RemovableRow","level":"molecules","category":"Inputs","keywords":["picked","row","remove","framed"],"import":"import { RemovableRow } from \"@antscorp/ui\";","description":"Show a picked value with supporting text and a remove action.","usage":"Show a picked value with supporting text and a remove action. Use CheckRow when the row is an option to tick instead.","bestPractices":[{"guidance":true,"description":"Name the removal through labels.remove and use isFramed when picked values need bordered single-line rows."},{"guidance":false,"description":"Do not expect isReadOnly to hide host-supplied hover actions; the host must make those appropriate for viewing."}],"anatomy":[{"name":"Text","required":true,"description":"The value label with an optional description."},{"name":"Problem","required":false,"description":"Validation text below an unframed row or a warning tip in a framed row."},{"name":"End content","required":false,"description":"Host content optionally swapped for hover actions."},{"name":"Remove button","required":false,"description":"Removes the picked value unless read-only."}],"accessibility":["labels.remove names the remove button; name interactive host-supplied hover actions separately."],"props":[{"path":"label","type":"string","required":true,"description":"The row's text."},{"path":"description","type":"string | undefined","required":false,"description":"A second line under the text, such as an id under a name."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with what the row stands for, already translated: shown under it in the error tone;\nwith `isFramed`, the frame and the text turn to the error tone and a ⚠ beside the text holds\nit in a tooltip."},{"path":"endContent","type":"ReactNode","required":false,"description":"Content at the end of the row, before the remove button, such as a count."},{"path":"hoverContent","type":"ReactNode","required":false,"description":"Content shown instead of `endContent` while the row is hovered or holds the focus, such as a\nlink to the item; the remove button then shows with it, only then. None without it."},{"path":"isFramed","type":"boolean","required":false,"description":"A bordered box around the row: its text on one line, the whole text\nin a tooltip when cut. As high as `RowSkeletons variant=\"framed\"`."},{"path":"labels","type":"{ readonly remove: string; }","required":true,"description":"The texts the row shows; the host translates them."},{"path":"labels.remove","type":"string","required":false,"requiredWhenParent":true,"description":"The remove button's name, such as \"Remove Hanoi\"."},{"path":"onRemove","type":"() => void","required":true,"description":"Called by the remove button."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The remove button's size, `md` by default."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the remove button and blocks it; nothing is hidden."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Hides the remove button; the row is not dimmed."}],"examples":[{"name":"Default","description":"A text and its remove button.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"Filled","description":"A second line and a count at the end.","source":"export const Filled: Story = {\n args: { description: \"ID 1001\", endContent: <Text color=\"secondary\">1,250</Text> },\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"EndTip","description":"A count not ready yet, explained by a tip.","source":"export const EndTip: Story = {\n args: {\n endContent: (\n <>\n <Text color=\"secondary\">--</Text>\n <InfoTip label=\"Not counted yet; it will be ready in a few hours.\" />\n </>\n ),\n },\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"Interactive","description":"Removing takes the row away; a button brings it back.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(true, (isShown, set) =>\n isShown ? (\n <RemovableRow\n {...args}\n onRemove={() => {\n args.onRemove();\n set(false);\n }}\n />\n ) : (\n <Button\n label=\"Bring it back\"\n size=\"sm\"\n onClick={() => {\n set(true);\n }}\n />\n ),\n ),\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"WithError","description":"A problem with what the row stands for, in the error tone.","source":"export const WithError: Story = {\n args: { label: \"Old city\", error: \"This city no longer exists.\" },\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"Disabled","description":"The remove button dimmed and blocked.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"ReadOnly","description":"Read-only: no remove button, not dimmed.","source":"export const ReadOnly: Story = { args: { isReadOnly: true, description: \"ID 1001\" } };","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"Medium","description":"The `md` size.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"LongText","description":"A long text wraps beside its end content.","source":"export const LongText: Story = {\n args: {\n label: \"A city with a very long name that does not fit on one line of a narrow column\",\n description: \"ID 1234567890-abcdefghij-klmnopqrst\",\n endContent: <Text color=\"secondary\">12,345,678</Text>,\n },\n render: (args) => (\n <Narrow>\n <RemovableRow {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"Many","description":"Many rows one under the other, as a list of picks.","source":"export const Many: Story = {\n render: (args) => (\n <VStack gap={1.5}>\n {Array.from({ length: 10 }, (_, index) => (\n <RemovableRow\n key={index}\n {...args}\n label={`City ${index + 1}`}\n labels={{ remove: `Remove City ${index + 1}` }}\n />\n ))}\n </VStack>\n ),\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"Framed","description":"Framed, as a picked item: a bordered box, the text on one line.","source":"export const Framed: Story = {\n args: { isFramed: true, endContent: <Text color=\"secondary\">1,250</Text> },\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"FramedHoverContent","description":"Framed with hover content: the count at rest; the action and the remove button on hover or focus.","source":"export const FramedHoverContent: Story = {\n args: { isFramed: true, endContent: <Text color=\"secondary\">1,250</Text>, hoverContent: open },\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"FramedError","description":"Framed with a problem: a red frame, the text in the error tone and a ⚠ holding the reason.","source":"export const FramedError: Story = {\n args: { isFramed: true, label: \"Old city\", error: \"This city no longer exists.\" },\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"FramedDescription","description":"Framed with a second line.","source":"export const FramedDescription: Story = {\n args: { isFramed: true, description: \"ID 1001\" },\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"FramedReadOnly","description":"Framed and read-only: no remove button; the hover content still shows.","source":"export const FramedReadOnly: Story = {\n args: { isFramed: true, isReadOnly: true, hoverContent: open },\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"FramedLongText","description":"Framed, a long text cut to one line; hover for the whole text.","source":"export const FramedLongText: Story = {\n args: {\n isFramed: true,\n label: \"A city with a very long name that does not fit on one line of a narrow column\",\n endContent: <Text color=\"secondary\">12,345,678</Text>,\n },\n render: (args) => (\n <Narrow>\n <RemovableRow {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"FramedLoading","description":"Loading: `RowSkeletons variant=\"framed\"` in the rows' place, as high as they are.","source":"export const FramedLoading: Story = {\n render: () => <RowSkeletons variant=\"framed\" labels={{ loading: \"Loading\" }} />,\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <RemovableRow {...args} description=\"ID 1001\" />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <RemovableRow {...args} />\n </Case>\n <Case caption=\"Second line and count\">\n <RemovableRow\n {...args}\n description=\"ID 1001\"\n endContent={<Text color=\"secondary\">1,250</Text>}\n />\n </Case>\n <Case caption=\"Error\">\n <RemovableRow {...args} error=\"This city no longer exists.\" />\n </Case>\n <Case caption=\"Disabled\">\n <RemovableRow {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <RemovableRow {...args} isReadOnly />\n </Case>\n <Case caption=\"Framed, hover content (hover the row)\">\n <RemovableRow\n {...args}\n isFramed\n endContent={<Text color=\"secondary\">1,250</Text>}\n hoverContent={open}\n />\n </Case>\n <Case caption=\"Framed, error\">\n <RemovableRow {...args} isFramed error=\"This city no longer exists.\" />\n </Case>\n <Case caption=\"Framed, loading\">\n <RowSkeletons variant=\"framed\" count={2} labels={{ loading: \"Loading\" }} />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/RemovableRow.stories.tsx"}],"primaryExample":"Default"},{"name":"RemoveButton","level":"atoms","category":"Inputs","keywords":["remove","button","row","delete"],"import":"import { RemoveButton } from \"@antscorp/ui\";","description":"Offer an icon-only action to remove the surrounding row or item.","usage":"Offer an icon-only action to remove the surrounding row or item. Use TextAction for a removal that should have a visible text caption.","bestPractices":[{"guidance":true,"description":"Name what will be removed through label and perform the data change in onClick."},{"guidance":false,"description":"Do not use isDisabled for a read-only presentation when isReadOnly should hide the removal affordance."}],"accessibility":["label names the icon-only button; include the item or row being removed when needed to distinguish actions."],"props":[{"path":"label","type":"string","required":true,"description":"Accessible label for the button (required for accessibility).\nRendered as visible text by default. When `isIconOnly` is true,\nused as aria-label instead."},{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether the button is disabled."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The button's height, `md` by default."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Read-only: a remove is an editing affordance, so nothing is drawn."},{"path":"onClick","type":"() => void","required":true,"description":"Called when the button is pressed."}],"examples":[{"name":"Default","description":"The cross that removes the row it sits in.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/RemoveButton.stories.tsx"},{"name":"Interactive","description":"Pressing it removes the row beside it.","source":"export const Interactive: Story = {\n render: (args) =>\n withState([\"First\", \"Second\", \"Third\"], (rows, set) => (\n <>\n {rows.map((row) => (\n <div key={row}>\n {row}\n <RemoveButton\n {...args}\n label={`Remove ${row}`}\n onClick={() => {\n args.onClick();\n set(rows.filter((item) => item !== row));\n }}\n />\n </div>\n ))}\n </>\n )),\n};","file":"packages/ui/src/atoms/RemoveButton.stories.tsx"},{"name":"Disabled","description":"Not pressable, e.g. for a read-only list.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/atoms/RemoveButton.stories.tsx"},{"name":"Medium","description":"The `md` height, for a form whose other controls are `md`.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/atoms/RemoveButton.stories.tsx"},{"name":"ReadOnly","description":"Read-only: a remove is an editing affordance, so nothing is drawn.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/atoms/RemoveButton.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <RemoveButton {...args} />\n </Case>\n <Case caption=\"Disabled\">\n <RemoveButton {...args} isDisabled />\n </Case>\n <Case caption=\"Medium\">\n <RemoveButton {...args} size=\"md\" />\n </Case>\n <Case caption=\"Read-only (nothing drawn)\">\n <RemoveButton {...args} isReadOnly />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/RemoveButton.stories.tsx"}],"primaryExample":"Default"},{"name":"RepeatableList","level":"organisms","category":"Inputs","keywords":["list","repeatable","add","rows"],"import":"import { RepeatableList } from \"@antscorp/ui\";","description":"Compose an editable list whose items and removal controls are supplied by the host.","usage":"Compose an editable list whose items and removal controls are supplied by the host. Use RuleList when every item should have a RuleRow and first/rest joining words.","bestPractices":[{"guidance":true,"description":"Provide stable itemKey values and pass disabled or read-only state into the controls rendered by renderItem."},{"guidance":false,"description":"Do not hide the add button yourself at max; the list keeps it disabled with labels.maxReached when supplied."}],"anatomy":[{"name":"Items","required":false,"description":"Host-rendered rows when items exist, optionally sharing column headings and tracks."},{"name":"Separators","required":false,"description":"Joint words or labeled dividers between items."},{"name":"Add row","required":false,"description":"A named add button, optionally placed by renderAdd."},{"name":"Feedback","required":false,"description":"Empty, maximum and validation messages."}],"accessibility":["label names the list's group; supply labels.add for its add action and accessible names inside rendered items."],"props":[{"path":"label","type":"string","required":true,"description":"Names the list."},{"path":"items","type":"readonly unknown[]","required":true,"description":"The items, in order."},{"path":"itemKey","type":"(item: unknown, index: number) => string","required":true,"description":"A stable key for an item, so rows keep their state when others are added or removed."},{"path":"renderItem","type":"(item: unknown, index: number) => ReactNode","required":true,"description":"Draws an item; it brings its own remove control, e.g. through `RuleRow`."},{"path":"columns","type":"{ readonly labels: readonly string[]; readonly template: string; } | undefined","required":false,"description":"Column headings and their CSS tracks, shared with each `RuleRow` in the list; a last track for\nthe remove button is added, read-only rows included. No headings without it."},{"path":"columns.labels","type":"readonly string[]","required":false,"requiredWhenParent":true,"description":""},{"path":"columns.template","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"frame","type":"\"card\" | \"none\" | undefined","required":false,"description":"`none` (default) draws no frame, and `label` only names the list for assistive tech; `card` is a transparent card with the label above."},{"path":"description","type":"string | undefined","required":false,"description":"Help under the label."},{"path":"isErrorAboveAdd","type":"boolean","required":false,"description":"Puts `error` between the items and the add button instead of below the button."},{"path":"labels","type":"{ readonly joint?: string | undefined; readonly add?: string | undefined; readonly maxReached?: string | undefined; readonly empty?: string | undefined; } | undefined","required":false,"description":"The texts the list shows; the host translates them."},{"path":"labels.joint","type":"string | undefined","required":false,"description":"A word between two items, such as \"or\"; none without it. `separator` says how it shows."},{"path":"labels.add","type":"string | undefined","required":false,"description":"The add button's text, such as \"Add trigger\"; required with `onAdd`, which shows no button without it."},{"path":"labels.maxReached","type":"string | undefined","required":false,"description":"Shown above the add button once `max` items are there."},{"path":"labels.empty","type":"string | undefined","required":false,"description":"Shown while there are no items."},{"path":"addVariant","type":"\"ghost\" | \"secondary\" | undefined","required":false,"description":"The add button's weight: `ghost` by default; `secondary` for an add that needs more weight."},{"path":"addShape","type":"\"default\" | \"pill\" | undefined","required":false,"description":"The add button's shape: `default`, or `pill` for a fully rounded one (see `AddButton`)."},{"path":"renderAdd","type":"((button: ReactNode) => ReactNode) | undefined","required":false,"description":"Puts the add button in a row of the host's, e.g. after a joint word as the items have, at the list's full width as the items are; the button alone, at its own width, without it."},{"path":"separator","type":"\"divider\" | \"joint\" | undefined","required":false,"description":"How `labels.joint` shows between two items: `joint` (default) a small word, `divider` a dashed rule with the word on it."},{"path":"gap","type":"2 | 4 | undefined","required":false,"description":"The space between the rows, the add row too, in Astryx spacing steps: `2` (8px, default) or `4` (16px)."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The add button's height, `md` by default."},{"path":"onAdd","type":"(() => void) | undefined","required":false,"description":"Called when the add button is pressed. No add button without it."},{"path":"isAddDisabled","type":"boolean","required":false,"description":"Blocks the add button alone (e.g. until something the items need is chosen); the items stay as they are."},{"path":"max","type":"number | undefined","required":false,"description":"The most items the list holds; at that many the add button stays, but disabled."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the list as a whole, already in the user's language."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the list and its add button; the items' own controls are the host's to disable."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Hides the add button and the maximum note; the items' own remove buttons are the host's to hide."}],"examples":[{"name":"Default","description":"Items joined by \"or\", with an add button.","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"Columns","description":"Column headings and row controls share tracks, including the reserved remove button column.","source":"export const Columns: Story = {\n args: {\n labels: { add: \"Add item\" },\n columns: { labels: [\"Name\", \"Value\"], template: \"minmax(0, 1fr) minmax(0, 1fr)\" },\n renderItem: (item: string) => (\n <RuleRow labels={{ remove: `Remove ${item}` }} onRemove={fn<() => void>()}>\n <TextInput label=\"Name\" value={item} onChange={fn<() => void>()} />\n <TextInput label=\"Value\" value=\"Example\" onChange={fn<() => void>()} />\n </RuleRow>\n ),\n },\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"ReadOnlyColumns","description":"The same header tracks with a read-only row; removal space stays reserved.","source":"export const ReadOnlyColumns: Story = {\n args: {\n ...Columns.args,\n isReadOnly: true,\n renderItem: (item: string) => (\n <RuleRow labels={{ remove: `Remove ${item}` }} isReadOnly onRemove={fn<() => void>()}>\n <TextInput label=\"Name\" value={item} isReadOnly />\n <TextInput label=\"Value\" value=\"Example\" isReadOnly />\n </RuleRow>\n ),\n },\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"NarrowColumns","description":"Column headings and controls in a narrow host.","source":"export const NarrowColumns: Story = {\n render: (args) => (\n <Narrow>\n <RepeatableList {...args} {...Columns.args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"Empty","description":"No item yet: the empty text and the add button.","source":"export const Empty: Story = {\n args: { items: [], labels: { add: \"Add trigger\", empty: \"Nothing here yet.\" } },\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"Interactive","description":"Adding and removing items; the joint words follow.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<readonly string[]>([\"Item 1\", \"Item 2\"], (items, set) => (\n <RepeatableList\n {...args}\n items={items}\n renderItem={(item) =>\n row(item, () => {\n set(items.filter((other) => other !== item));\n })\n }\n labels={{ joint: \"or\", add: \"Add item\" }}\n onAdd={() => {\n set([...items, `Item ${items.length + 1}`]);\n }}\n />\n )),\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"WithError","description":"A problem with the list as a whole.","source":"export const WithError: Story = { args: { error: \"Add at least one trigger.\" } };","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"Disabled","description":"Dimmed: the group and the add button cannot be used, and nothing is hidden.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"ReadOnly","description":"Read-only: no add button, and the rows (here `RuleRow`) hide their remove buttons.","source":"export const ReadOnly: Story = {\n args: { isReadOnly: true, renderItem: (item: string) => row(item, fn<() => void>(), true) },\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"Medium","description":"The add button at the `md` height.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"SingleItem","description":"A single item, so no joint word.","source":"export const SingleItem: Story = { args: { items: names.slice(0, 1) } };","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"ManyItems","description":"Twelve items.","source":"export const ManyItems: Story = {\n args: { items: Array.from({ length: 12 }, (_, index) => `Trigger number ${index + 1}`) },\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"MaxReached","description":"At the maximum: the add button stays, disabled, under a note.","source":"export const MaxReached: Story = {\n args: {\n max: 3,\n labels: { joint: \"or\", add: \"Add trigger\", maxReached: \"You can add up to 3 triggers.\" },\n },\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"AddDisabled","description":"Only the add button is blocked; the items stay as they are.","source":"export const AddDisabled: Story = { args: { isAddDisabled: true } };","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"NoJoint","description":"Without a joint word.","source":"export const NoJoint: Story = { args: { labels: { add: \"Add trigger\" } } };","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, where rows wrap.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <RepeatableList {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"CardFrame","description":"A transparent card with help, and the problem above the add button.","source":"export const CardFrame: Story = {\n args: {\n frame: \"card\",\n labels: { add: \"Add trigger\" },\n description: \"Added one by one.\",\n error: \"Add at least one trigger.\",\n isErrorAboveAdd: true,\n },\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"SecondaryAdd","description":"The add button as a secondary button, for an add that needs more weight.","source":"export const SecondaryAdd: Story = {\n args: { addVariant: \"secondary\" },\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"OutlineAdd","description":"The outline add: secondary, `md`, its plus at the button's size.","source":"export const OutlineAdd: Story = {\n args: { addVariant: \"secondary\", size: \"md\" },\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"DividerSeparator","description":"Items as cards apart, a dashed rule with \"OR\" between them, and a pill add button.","source":"export const DividerSeparator: Story = {\n args: {\n separator: \"divider\",\n labels: { joint: \"OR\", add: \"OR\" },\n addVariant: \"secondary\",\n addShape: \"pill\",\n },\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"AddInRow","description":"The add button after a word of the host's, in a row as the items are.","source":"export const AddInRow: Story = {\n args: {\n renderAdd: (button: React.ReactNode) => (\n <RuleRow labels={{ remove: \"Unused\", joint: \"or\" }} isReadOnly onRemove={fn<() => void>()}>\n {button}\n </RuleRow>\n ),\n },\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args: RepeatableListProps<string>) => (\n <Gallery>\n <Case caption=\"Column headings\">\n <RepeatableList {...args} {...Columns.args} />\n </Case>\n <Case caption=\"Read-only columns\">\n <RepeatableList {...args} {...ReadOnlyColumns.args} />\n </Case>\n <Case caption=\"Filled\">\n <RepeatableList {...args} />\n </Case>\n <Case caption=\"Empty\">\n <RepeatableList\n {...args}\n items={[]}\n labels={{ add: \"Add trigger\", empty: \"Nothing here yet.\" }}\n />\n </Case>\n <Case caption=\"Error\">\n <RepeatableList {...args} error=\"Add at least one trigger.\" />\n </Case>\n <Case caption=\"Max reached\">\n <RepeatableList\n {...args}\n max={3}\n labels={{ joint: \"or\", add: \"Add trigger\", maxReached: \"You can add up to 3 triggers.\" }}\n />\n </Case>\n <Case caption=\"Disabled\">\n <RepeatableList {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <RepeatableList\n {...args}\n isReadOnly\n renderItem={(item: string) => row(item, fn<() => void>(), true)}\n />\n </Case>\n <Case caption=\"Medium\">\n <RepeatableList {...args} size=\"md\" />\n </Case>\n <Case caption=\"Card\">\n <RepeatableList {...args} frame=\"card\" description=\"Added one by one.\" />\n </Case>\n <Case caption=\"Secondary add\">\n <RepeatableList {...args} addVariant=\"secondary\" />\n </Case>\n <Case caption=\"Outline add\">\n <RepeatableList {...args} addVariant=\"secondary\" size=\"md\" />\n </Case>\n <Case caption=\"Divider, pill add\">\n <RepeatableList\n {...args}\n separator=\"divider\"\n labels={{ joint: \"OR\", add: \"OR\" }}\n addVariant=\"secondary\"\n addShape=\"pill\"\n />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/RepeatableList.stories.tsx"}],"primaryExample":"Default"},{"name":"ResizableDrawer","level":"organisms","category":"Overlays","keywords":["drawer","resize","panel","expand"],"import":"import { ResizableDrawer } from \"@antscorp/ui\";","description":"Place a resizable panel at the bottom of its parent, with a fixed header and scrolling body.","usage":"Place a resizable panel at the bottom of its parent, with a fixed header and scrolling body. Expansion covers that parent and Escape shrinks an expanded drawer.","bestPractices":[{"guidance":true,"description":"Keep isExpanded controlled through onExpandedChange and provide labels and glyphs for expand and shrink."},{"guidance":false,"description":"Do not expect the drawer to cover the browser viewport unless its parent is sized for that purpose."}],"anatomy":[{"name":"Resize bar","required":true,"description":"The handle for changing the local drawer height."},{"name":"Header","required":true,"description":"Host title, optional end content, expand and close buttons."},{"name":"Banners","required":false,"description":"Host messages above the body."},{"name":"Body","required":true,"description":"Scrolling host content."}],"accessibility":["label names the drawer; labels names the expand, shrink, close and optional resize controls; Escape shrinks when expanded."],"props":[{"path":"label","type":"string","required":true,"description":"Names the drawer for assistive technology, such as \"Settings of Wait\"."},{"path":"title","type":"ReactNode","required":true,"description":"The header's start: a title, or an editable name."},{"path":"headerEnd","type":"ReactNode","required":false,"description":"The header's end, before the expand and close buttons, such as a switch."},{"path":"banners","type":"ReactNode","required":false,"description":"Messages above the body, such as Astryx `Banner`s."},{"path":"children","type":"ReactNode","required":true,"description":"The body, which scrolls."},{"path":"isExpanded","type":"boolean","required":true,"description":"Covers the whole area it sits in (its parent), instead of a share of it."},{"path":"onExpandedChange","type":"(isExpanded: boolean) => void","required":true,"description":"Called with the new state: by the expand button, by Escape, and by starting a resize."},{"path":"onClose","type":"() => void","required":true,"description":"Called when the close button is pressed."},{"path":"icons","type":"{ readonly expand: ReactNode; readonly shrink: ReactNode; }","required":true,"description":"The host's glyphs."},{"path":"icons.expand","type":"ReactNode","required":false,"requiredWhenParent":true,"description":"The expand button's glyph, drawn when the drawer is not expanded."},{"path":"icons.shrink","type":"ReactNode","required":false,"requiredWhenParent":true,"description":"The expand button's glyph once the drawer is expanded."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The header buttons' size, `md` by default."},{"path":"labels","type":"{ readonly expand: string; readonly shrink: string; readonly close: string; readonly resize?: string | undefined; }","required":true,"description":"The texts of the buttons and the resize bar; the host translates them."},{"path":"labels.expand","type":"string","required":false,"requiredWhenParent":true,"description":"The expand button, such as \"Expand\"."},{"path":"labels.shrink","type":"string","required":false,"requiredWhenParent":true,"description":"The same button once expanded, such as \"Shrink\"."},{"path":"labels.close","type":"string","required":false,"requiredWhenParent":true,"description":"The close button, such as \"Close\"."},{"path":"labels.resize","type":"string | undefined","required":false,"description":"The resize bar; Astryx's own text when absent."},{"path":"autoSaveId","type":"string | undefined","required":false,"description":"Remembers the height under this key between visits. Not remembered without it."},{"path":"xstyle","type":"StyleXStyles","required":false,"description":"StyleX styles for the drawer's surface, such as a shadow; must come from `stylex.create`."},{"path":"defaultSize","type":"string | number | undefined","required":false,"description":"The starting height, a number of pixels or a share such as \"50%\" of the parent."}],"examples":[{"name":"Default","description":"Half of its parent; drag the bar on top to resize it.","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/ResizableDrawer.stories.tsx"},{"name":"Interactive","description":"Expanding and shrinking: the button, and Escape while expanded.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(false, (isExpanded, set) => (\n <ResizableDrawer\n {...args}\n isExpanded={isExpanded}\n onExpandedChange={(next) => {\n args.onExpandedChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/organisms/ResizableDrawer.stories.tsx"},{"name":"Expanded","description":"Covering its whole parent.","source":"export const Expanded: Story = { args: { isExpanded: true } };","file":"packages/ui/src/organisms/ResizableDrawer.stories.tsx"},{"name":"WithHeaderEnd","description":"Something at the header's end, such as a switch.","source":"export const WithHeaderEnd: Story = {\n args: { headerEnd: <Switch label=\"On\" value onChange={fn<() => void>()} /> },\n};","file":"packages/ui/src/organisms/ResizableDrawer.stories.tsx"},{"name":"WithBanners","description":"Banners above the body.","source":"export const WithBanners: Story = {\n args: {\n banners: [\n <Banner key=\"a\" status=\"warning\" title=\"Fill in the name.\" />,\n <Banner key=\"b\" status=\"warning\" title=\"Pick at least one hour.\" />,\n ],\n },\n};","file":"packages/ui/src/organisms/ResizableDrawer.stories.tsx"},{"name":"LongContent","description":"A body longer than the drawer scrolls.","source":"export const LongContent: Story = {\n args: {\n children: Array.from({ length: 30 }, (_, index) => (\n <Text key={index} as=\"p\">\n Paragraph {index + 1} of a long body.\n </Text>\n )),\n },\n};","file":"packages/ui/src/organisms/ResizableDrawer.stories.tsx"},{"name":"WithOwnStyle","description":"The host's own styles on the surface, here a stronger shadow.","source":"export const WithOwnStyle: Story = { args: { xstyle: styles.strong } };","file":"packages/ui/src/organisms/ResizableDrawer.stories.tsx"},{"name":"SmallStart","description":"A small starting height, in pixels.","source":"export const SmallStart: Story = { args: { defaultSize: 160 } };","file":"packages/ui/src/organisms/ResizableDrawer.stories.tsx"},{"name":"Medium","description":"The header buttons at the `md` height.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/organisms/ResizableDrawer.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <Tall>\n <ResizableDrawer {...args} />\n </Tall>\n </Case>\n <Case caption=\"Expanded, with a banner\">\n <Tall>\n <ResizableDrawer\n {...args}\n isExpanded\n banners={<Banner status=\"warning\" title=\"Fill in the name.\" />}\n />\n </Tall>\n </Case>\n <Case caption=\"Medium\">\n <Tall>\n <ResizableDrawer {...args} size=\"md\" />\n </Tall>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/ResizableDrawer.stories.tsx"}],"primaryExample":"Default"},{"name":"RevealRadioGroup","level":"molecules","category":"Selection","keywords":["radio","choice","reveal","details"],"import":"import { RevealRadioGroup } from \"@antscorp/ui\";","description":"Choose one option and reveal only its related content below the list or beneath that option.","usage":"Choose one option and reveal only its related content below the list or beneath that option. Use RevealSwitch for a single boolean setting.","bestPractices":[{"guidance":true,"description":"Keep value controlled and choose contentPlacement=option when each option's detail should follow its label."},{"guidance":false,"description":"Do not expect the group to make controls inside option content read-only or disabled; configure those controls in the host."}],"anatomy":[{"name":"Group label","required":true,"description":"The group's name, optionally visually hidden."},{"name":"Radio options","required":true,"description":"The mutually exclusive choices with optional explanatory tips."},{"name":"Revealed content","required":false,"description":"Only the chosen option's content."}],"accessibility":["Supply label even when isLabelHidden, and clear labels for every option."],"props":[{"path":"label","type":"string","required":true,"description":"Label text for the radio group (always rendered for accessibility)."},{"path":"isLabelHidden","type":"boolean","required":false,"default":"false","description":"Whether to visually hide the label (still accessible to screen readers)."},{"path":"orientation","type":"\"horizontal\" | \"vertical\" | undefined","required":false,"default":"\"vertical\"","description":"Layout direction of the radio items."},{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether all radio items are disabled."},{"path":"options","type":"readonly RevealRadioOption<string>[]","required":true,"description":"The choices, in order."},{"path":"options[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What `onChange` gets when this option is picked."},{"path":"options[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The option's text."},{"path":"options[].isDisabled","type":"boolean","required":false,"description":"Dims and blocks this option while keeping a stored choice visible."},{"path":"options[].tip","type":"string | undefined","required":false,"description":"An explanation shown in an (i) beside the label."},{"path":"options[].content","type":"ReactNode","required":false,"description":"What shows while this option is the chosen one: below the list, or under the option (`contentPlacement`)."},{"path":"value","type":"string","required":true,"description":"The chosen option's value."},{"path":"onChange","type":"(value: string) => void","required":true,"description":"Called with the value of the option picked."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The radios' size, `md` by default (Astryx's default; the theme draws it at 18px)."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows the value, not dimmed, and ignores changes."},{"path":"contentGap","type":"0 | 0.5 | 1 | 2 | undefined","required":false,"description":"The space between the list and the revealed content, `2` by default."},{"path":"contentPlacement","type":"\"list\" | \"option\" | undefined","required":false,"description":"`list` (default) shows the chosen option's content below the whole list; `option` shows it\nright under the chosen option, in line with its label, the options after it below the content."}],"examples":[{"name":"DisabledOption","description":"One option is disabled; a stored choice still reveals its content.","source":"export const DisabledOption: Story = {\n args: {\n options: [\n { value: \"unlimited\", label: \"Unlimited\" },\n { value: \"limited\", label: \"Limited\", isDisabled: true, content: times },\n ],\n },\n};","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"Default","description":"The chosen option reveals its content below the list.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"TightContent","description":"The content tucked close under the list.","source":"export const TightContent: Story = { args: { orientation: \"horizontal\", contentGap: 0.5 } };","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"UnderOption","description":"The chosen option's content under it, in line with its label, the next options below it.","source":"export const UnderOption: Story = {\n args: {\n contentPlacement: \"option\",\n options: [\n { value: \"unlimited\", label: \"Unlimited\" },\n { value: \"limited\", label: \"Limited\", content: times },\n { value: \"capped\", label: \"Capped per day\" },\n ],\n },\n};","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"NothingRevealed","description":"An option without content reveals nothing.","source":"export const NothingRevealed: Story = { args: { value: \"unlimited\" } };","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"Interactive","description":"Picking an option swaps the content.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<Limit>(\"unlimited\", (value, set) => (\n <RevealRadioGroup\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"HorizontalWithTips","description":"In a row, with an (i) explaining each option.","source":"export const HorizontalWithTips: Story = {\n args: {\n orientation: \"horizontal\",\n options: [\n { value: \"unlimited\", label: \"Unlimited\", tip: \"No cap on how often.\" },\n { value: \"limited\", label: \"Limited\", tip: \"At most N times.\", content: times },\n ],\n },\n};","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"LabelHidden","description":"The label kept for screen readers only.","source":"export const LabelHidden: Story = { args: { isLabelHidden: true } };","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"Disabled","description":"Read-only.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"ReadOnly","description":"Shows the value, not dimmed, and blocks changes.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"Small","description":"The compact `sm` size, for a dense form whose other controls are `sm`.","source":"export const Small: Story = { args: { size: \"sm\" } };","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"LongContent","description":"Long labels and tips.","source":"export const LongContent: Story = {\n args: {\n options: [\n {\n value: \"unlimited\",\n label: \"Unlimited: a person may enter every time they match, however often that is\",\n tip: \"There is no cap on how often the same person can enter.\",\n },\n {\n value: \"limited\",\n label: \"Limited: a person may enter only a set number of times in a period of time\",\n content: times,\n },\n ],\n },\n};","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: { orientation: \"horizontal\" },\n render: (args) => (\n <Narrow>\n <RevealRadioGroup {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Content revealed\">\n <RevealRadioGroup {...args} />\n </Case>\n <Case caption=\"Nothing revealed\">\n <RevealRadioGroup {...args} value=\"unlimited\" />\n </Case>\n <Case caption=\"Horizontal\">\n <RevealRadioGroup {...args} orientation=\"horizontal\" />\n </Case>\n <Case caption=\"Disabled\">\n <RevealRadioGroup {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <RevealRadioGroup {...args} isReadOnly />\n </Case>\n <Case caption=\"Small\">\n <RevealRadioGroup {...args} size=\"sm\" />\n </Case>\n <Case caption=\"Under the option\">\n <RevealRadioGroup {...args} contentPlacement=\"option\" />\n </Case>\n <Case caption=\"Under the option, small\">\n <RevealRadioGroup {...args} contentPlacement=\"option\" size=\"sm\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/RevealRadioGroup.stories.tsx"}],"primaryExample":"DisabledOption"},{"name":"RevealSwitch","level":"molecules","category":"Selection","keywords":["switch","boolean","reveal","settings"],"import":"import { RevealSwitch } from \"@antscorp/ui\";","description":"Turn a setting on or off and reveal its controls only while it is on.","usage":"Turn a setting on or off and reveal its controls only while it is on. Use RevealRadioGroup when choosing among several alternatives.","bestPractices":[{"guidance":true,"description":"Keep value controlled and give the switch a label that explains what enabling it does."},{"guidance":false,"description":"Do not expect disabling or making the switch read-only to change the behavior of supplied children."}],"anatomy":[{"name":"Switch","required":true,"description":"The labeled boolean control."},{"name":"Content","required":false,"description":"Host controls shown below the switch while value is true."}],"accessibility":["label names the switch; the host supplies accessible labels for revealed controls."],"props":[{"path":"label","type":"string","required":true,"description":"Label text for the switch (always rendered for accessibility)."},{"path":"value","type":"boolean","required":true,"description":"Whether the switch is on or off."},{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether the switch is disabled."},{"path":"onChange","type":"(value: boolean) => void","required":true,"description":"Called with the new state."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The switch's size, `md` by default."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows the value, not dimmed, and ignores changes."},{"path":"children","type":"ReactNode","required":false,"description":"What shows below the switch while it is on."}],"examples":[{"name":"On","description":"On: the children show below the switch.","source":"export const On: Story = {};","file":"packages/ui/src/molecules/RevealSwitch.stories.tsx"},{"name":"Off","description":"Off: the children are hidden.","source":"export const Off: Story = { args: { value: false } };","file":"packages/ui/src/molecules/RevealSwitch.stories.tsx"},{"name":"Interactive","description":"Toggling shows and hides the children.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(false, (value, set) => (\n <RevealSwitch\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/RevealSwitch.stories.tsx"},{"name":"Disabled","description":"Read-only, on.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/RevealSwitch.stories.tsx"},{"name":"ReadOnly","description":"Shows the value, not dimmed, and blocks changes.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/molecules/RevealSwitch.stories.tsx"},{"name":"Medium","description":"The `md` size, for a form whose other controls are `md`.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/molecules/RevealSwitch.stories.tsx"},{"name":"NoChildren","description":"On with no children: only the switch.","source":"export const NoChildren: Story = { args: { children: undefined } };","file":"packages/ui/src/molecules/RevealSwitch.stories.tsx"},{"name":"LongContent","description":"A long label and a lot of revealed content.","source":"export const LongContent: Story = {\n args: {\n label: \"Only people who match every one of the conditions below, and no one else at all\",\n children: Array.from({ length: 10 }, (_, index) => (\n <TextInput key={index} label={`Filter ${index + 1}`} value=\"\" onChange={fn<() => void>()} />\n )),\n },\n};","file":"packages/ui/src/molecules/RevealSwitch.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: { label: \"Only people who match every one of the conditions below\" },\n render: (args) => (\n <Narrow>\n <RevealSwitch {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/RevealSwitch.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"On\">\n <RevealSwitch {...args} />\n </Case>\n <Case caption=\"Off\">\n <RevealSwitch {...args} value={false} />\n </Case>\n <Case caption=\"Disabled\">\n <RevealSwitch {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <RevealSwitch {...args} isReadOnly />\n </Case>\n <Case caption=\"Medium\">\n <RevealSwitch {...args} size=\"md\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/RevealSwitch.stories.tsx"}],"primaryExample":"On"},{"name":"RowSkeletons","level":"atoms","category":"Feedback","keywords":["loading","skeleton","rows","tiles"],"import":"import { RowSkeletons } from \"@antscorp/ui\";","description":"Stand in for options, checkbox rows or framed picks while data loads.","usage":"Stand in for options, checkbox rows or framed picks while data loads. Use ChipSkeletons for compact picked values and FieldSkeleton for a field.","bestPractices":[{"guidance":true,"description":"Match layout, variant, count and hasIcon to the content that will replace the placeholder."},{"guidance":false,"description":"Do not create multiple loading statuses inside one named load; use hasStatus=false for nested placeholders."}],"anatomy":[{"name":"Text bars","required":true,"description":"Varied shimmer widths stand in for row or tile labels."},{"name":"Leading shape","required":false,"description":"An icon placeholder or checkbox shape when requested."},{"name":"End bar","required":false,"description":"A short trailing value in check and framed variants."}],"accessibility":["By default the wrapper has role status, named through labels.loading; hasStatus=false hides it from assistive technology."],"props":[{"path":"count","type":"number | undefined","required":false,"description":"How many rows (or tiles) stand in for the list, 3 by default."},{"path":"hasIcon","type":"boolean","required":false,"description":"Each row starts with a square icon cell, as a list of items with icons; each tile has a\nround icon cell over its text."},{"path":"layout","type":"\"grid\" | \"list\" | undefined","required":false,"description":"`grid`: three tiles a row, as `SearchableGroupedList`'s tiles; `list` (default): one row each,\nas an option row."},{"path":"variant","type":"\"check\" | \"framed\" | \"option\" | undefined","required":false,"description":"The shape of each row of a list: `option` (default) as an option row; `check` as a\n`CheckRow` (a box, a name, a short value at the end); `framed` as a framed `RemovableRow` (a\nbordered box around a name and a short value). `check` and `framed` rows are exactly as high\nas the rows they stand for and sit 6px apart (spacing step 1.5), so a list laid out with that\ngap does not move when they are swapped."},{"path":"hasStatus","type":"boolean","required":false,"description":"Whether it is a status of its own (default); `false` draws no status and hides it from\nassistive tech, for rows inside or beside one status that names the whole load."},{"path":"labels","type":"{ readonly loading?: string | undefined; } | undefined","required":false,"description":"The texts the view shows; the host translates them."},{"path":"labels.loading","type":"string | undefined","required":false,"description":"The loading status's accessible name, e.g. \"Loading\"."}],"examples":[{"name":"Default","description":"Three text rows, as a list of options while it loads.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"WithIcon","description":"Each row with an icon cell first, as a list of items with icons.","source":"export const WithIcon: Story = { args: { hasIcon: true } };","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"SingleRow","description":"A single row, as the end of a list loading its next page.","source":"export const SingleRow: Story = { args: { count: 1 } };","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"ManyRows","description":"Many rows, the widths cycling.","source":"export const ManyRows: Story = { args: { count: 12 } };","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"CheckRows","description":"Rows shaped as `CheckRow`s: a box, a name bar and an end bar, as high as a real row.","source":"export const CheckRows: Story = { args: { variant: \"check\" } };","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"FramedRows","description":"Rows shaped as framed `RemovableRow`s: a box around a name bar and an end bar.","source":"export const FramedRows: Story = { args: { variant: \"framed\" } };","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"ManyCheckRows","description":"Many check rows, the widths cycling.","source":"export const ManyCheckRows: Story = { args: { variant: \"check\", count: 12 } };","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"FramedInNarrowFrame","description":"Framed rows in a drawer-width frame.","source":"export const FramedInNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <RowSkeletons {...args} variant=\"framed\" />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"Grid","description":"Three tiles a row, each an icon circle over a text bar, as a palette's tiles while they load.","source":"export const Grid: Story = { args: { layout: \"grid\", hasIcon: true, count: 6 } };","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"GridTextOnly","description":"Tiles without an icon, a text bar each.","source":"export const GridTextOnly: Story = { args: { layout: \"grid\" } };","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"ManyTiles","description":"Many tiles, the widths cycling.","source":"export const ManyTiles: Story = { args: { layout: \"grid\", hasIcon: true, count: 12 } };","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"GridInNarrowFrame","description":"Tiles in a drawer-width frame.","source":"export const GridInNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <RowSkeletons {...args} layout=\"grid\" hasIcon count={6} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"NoStatus","description":"Rows beside a status that names the load: no status of their own, hidden from assistive tech.","source":"export const NoStatus: Story = { args: { hasStatus: false, variant: \"framed\" } };","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <RowSkeletons {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Text rows\">\n <RowSkeletons {...args} />\n </Case>\n <Case caption=\"With icons\">\n <RowSkeletons {...args} hasIcon />\n </Case>\n <Case caption=\"Two rows\">\n <RowSkeletons {...args} count={2} />\n </Case>\n <Case caption=\"Narrow, with icons\">\n <Narrow>\n <RowSkeletons {...args} hasIcon count={5} />\n </Narrow>\n </Case>\n <Case caption=\"Check rows\">\n <RowSkeletons {...args} variant=\"check\" />\n </Case>\n <Case caption=\"Framed rows\">\n <RowSkeletons {...args} variant=\"framed\" />\n </Case>\n <Case caption=\"No status of its own\">\n <RowSkeletons {...args} variant=\"framed\" hasStatus={false} />\n </Case>\n <Case caption=\"Tiles\">\n <RowSkeletons {...args} layout=\"grid\" hasIcon count={6} />\n </Case>\n <Case caption=\"Tiles, text only\">\n <RowSkeletons {...args} layout=\"grid\" />\n </Case>\n <Case caption=\"Narrow, tiles\">\n <Narrow>\n <RowSkeletons {...args} layout=\"grid\" hasIcon count={9} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/RowSkeletons.stories.tsx"}],"primaryExample":"Default"},{"name":"RuleGroups","level":"organisms","category":"Inputs","keywords":["rules","groups","conditions","and","or"],"import":"import { RuleGroups } from \"@antscorp/ui\";","description":"Edit alternative groups of rules, where all rules in one group must hold.","usage":"Edit alternative groups of rules, where all rules in one group must hold. Use RuleList for a single flat conjunction of rules.","bestPractices":[{"guidance":true,"description":"Keep groups controlled and configure controls returned by renderRule for the same disabled or read-only state as the groups."},{"guidance":false,"description":"Do not expect a disabled add at maxRules or maxGroups; this view hides adds at their limits and protects the final rule from removal."}],"anatomy":[{"name":"Rule cards","required":false,"description":"Titled accent cards containing host controls and optional close actions when groups contain rules."},{"name":"And joints","required":false,"description":"Words and add-rule buttons within each group."},{"name":"Or dividers","required":false,"description":"Dashed separators and the add-group action."},{"name":"Error","required":false,"description":"A translated problem with all groups."}],"accessibility":["label names the whole group and labels.remove names rule-card close actions; supplied controls keep their own labels."],"props":[{"path":"label","type":"string","required":true,"description":"Names the whole set of groups for assistive tech; it is not shown."},{"path":"groups","type":"readonly (readonly unknown[])[]","required":true,"description":"The groups, any of which may hold; each holds rules that all must hold."},{"path":"ruleKey","type":"((rule: unknown, at: RuleGroupsAt) => string) | undefined","required":false,"description":"A stable key for a rule; its position without it."},{"path":"renderRule","type":"(rule: unknown, at: RuleGroupsAt) => ReactNode","required":true,"description":"The body of a rule's card, such as a field, an operator and a value; the host supplies the\ncontrols, which sit on one wrapping row, top-aligned so a problem under one keeps the others\nlevel."},{"path":"labels","type":"{ readonly ruleTitle: string; readonly remove: string; readonly and: string; readonly or: string; }","required":true,"description":"The texts the groups show; the host translates them."},{"path":"labels.ruleTitle","type":"string","required":false,"requiredWhenParent":true,"description":"The title of every rule's card, such as \"Have attributes\"."},{"path":"labels.remove","type":"string","required":false,"requiredWhenParent":true,"description":"The name of a card's close button, such as \"Remove rule\"."},{"path":"labels.and","type":"string","required":false,"requiredWhenParent":true,"description":"The word between two rules of a group, and the text of the button that adds one, such as \"and\"."},{"path":"labels.or","type":"string","required":false,"requiredWhenParent":true,"description":"The word on the divider between two groups, and the text of the button that adds one, such as \"or\"."},{"path":"onAddRule","type":"(group: number) => void","required":true,"description":"Called with a group's index when its add button is pressed."},{"path":"onRemoveRule","type":"(at: RuleGroupsAt) => void","required":true,"description":"Called with a rule's place when its card's close button is pressed."},{"path":"onAddGroup","type":"() => void","required":true,"description":"Called when the button after the last group is pressed."},{"path":"maxGroups","type":"number | undefined","required":false,"description":"The most groups there may be; at that many the button that adds one is gone."},{"path":"maxRules","type":"number | undefined","required":false,"description":"The most rules a group may hold; at that many its add button is gone."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the groups as a whole, already in the user's language."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the groups and blocks the close and add buttons, hiding nothing; the rules' controls are the host's to disable."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Hides every close and add button; the rules' controls are the host's to make read-only."}],"examples":[{"name":"Default","description":"Two groups: two rules joined by \"And\", a dashed \"Or\", one rule; each group ends with its \"+ And\", the last with \"+ Or\".","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"},{"name":"OneRule","description":"One rule, the only one left: no close button on its card.","source":"export const OneRule: Story = { args: { groups: [[hanoi]] } };","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"},{"name":"SeveralRules","description":"Several rules in one group.","source":"export const SeveralRules: Story = { args: { groups: [[hanoi, pro, hanoi, pro]] } };","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"},{"name":"Empty","description":"No group at all, as an old document may hold: only \"+ Or\", to start one.","source":"export const Empty: Story = { args: { groups: [] } };","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"},{"name":"Interactive","description":"Adding and removing rules and groups; removing a group's last rule removes the group.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<readonly (readonly Rule[])[]>(several, (value, set) => (\n <RuleGroups\n {...args}\n groups={value}\n onAddRule={(group) => {\n set(\n value.map((rules, at) =>\n at === group ? [...rules, { field: \"city\", value: \"\" }] : rules,\n ),\n );\n }}\n onAddGroup={() => {\n set([...value, [{ field: \"city\", value: \"\" }]]);\n }}\n onRemoveRule={({ group, rule }) => {\n set(\n value\n .map((rules, at) =>\n at === group ? rules.filter((_r, index) => index !== rule) : rules,\n )\n .filter((rules) => rules.length > 0),\n );\n }}\n />\n )),\n};","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"},{"name":"AtLimits","description":"A group at its limit of 3 rules has no \"+ And\"; at 2 groups there is no \"+ Or\".","source":"export const AtLimits: Story = {\n args: { groups: [[hanoi, pro, hanoi], [pro]], maxRules: 3, maxGroups: 2 },\n};","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"},{"name":"WithError","description":"A problem with the whole, under the groups; a rule's own problem under its control.","source":"export const WithError: Story = {\n args: {\n groups: [[hanoi, { field: \"plan\", value: \"\" }]],\n error: \"Fill in every rule's value\",\n },\n};","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"},{"name":"Disabled","description":"Dimmed: the close and add buttons are blocked, and nothing is hidden.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"},{"name":"ReadOnly","description":"Read-only: no close nor add button.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"},{"name":"Long","description":"Long content: a long field name, and ten rules in a group.","source":"export const Long: Story = {\n args: {\n groups: [\n Array.from({ length: 10 }, (_, index) => ({ field: \"note\", value: `Value ${index + 1}` })),\n ],\n },\n};","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, where a card's controls wrap.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <RuleGroups {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Filled\">\n <RuleGroups {...args} />\n </Case>\n <Case caption=\"One rule\">\n <RuleGroups {...args} groups={[[hanoi]]} />\n </Case>\n <Case caption=\"Empty\">\n <RuleGroups {...args} groups={[]} />\n </Case>\n <Case caption=\"At limits\">\n <RuleGroups {...args} {...AtLimits.args} />\n </Case>\n <Case caption=\"Error\">\n <RuleGroups {...args} {...WithError.args} />\n </Case>\n <Case caption=\"Disabled\">\n <RuleGroups {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <RuleGroups {...args} isReadOnly />\n </Case>\n <Case caption=\"Narrow\">\n <Narrow>\n <RuleGroups {...args} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/RuleGroups.stories.tsx"}],"primaryExample":"Default"},{"name":"RuleList","level":"organisms","category":"Inputs","keywords":["rules","list","conditions","joint"],"import":"import { RuleList } from \"@antscorp/ui\";","description":"Edit rules that all must hold, with one joining word on the first row and another on later rows.","usage":"Edit rules that all must hold, with one joining word on the first row and another on later rows. Use RuleGroups when alternatives need separate groups of rule cards.","bestPractices":[{"guidance":true,"description":"Provide stable itemKey values and pass the list's size and lock state to controls returned by renderControls."},{"guidance":false,"description":"Do not expect isReadOnly to change host-rendered controls; it hides the list's add and remove actions."}],"anatomy":[{"name":"Rule rows","required":true,"description":"RuleRow controls with first/rest joint labels and removal actions."},{"name":"Add row","required":false,"description":"An optional add action, with a joint when hasAddJoint is enabled."},{"name":"Feedback","required":false,"description":"Empty, maximum and validation messages."}],"accessibility":["label names the list; labels.remove and labels.add name editing actions, and each supplied control needs its own label."],"props":[{"path":"label","type":"string","required":true,"description":"Names the list."},{"path":"items","type":"readonly unknown[]","required":true,"description":"The rules, in order."},{"path":"itemKey","type":"(item: unknown, index: number) => string","required":true,"description":"A stable key for a rule."},{"path":"renderControls","type":"(item: unknown, index: number) => ReactNode","required":true,"description":"The controls of a rule's row, such as a field, an operator and a value; the host supplies\nthem, each with the list's `size` so the row lines up."},{"path":"labels","type":"{ readonly remove: string; readonly jointFirst: string; readonly jointRest: string; readonly add?: string | undefined; readonly maxReached?: string | undefined; readonly empty?: string | undefined; }","required":true,"description":"The texts the list shows; the host translates them."},{"path":"labels.remove","type":"string","required":false,"requiredWhenParent":true,"description":"What a row's remove button does, such as \"Remove rule\"."},{"path":"labels.jointFirst","type":"string","required":false,"requiredWhenParent":true,"description":"The joint word on the first row, such as \"where\"."},{"path":"labels.jointRest","type":"string","required":false,"requiredWhenParent":true,"description":"The joint word on every later row, such as \"and\"."},{"path":"labels.add","type":"string | undefined","required":false,"description":"The add button's text; required with `onAdd`."},{"path":"labels.maxReached","type":"string | undefined","required":false,"description":"Shown above the add button once `max` rules are there."},{"path":"labels.empty","type":"string | undefined","required":false,"description":"Shown while there are no rules."},{"path":"jointWidth","type":"number | undefined","required":false,"description":"The joint words' width in pixels; the row's default (28) when absent."},{"path":"jointCase","type":"\"sentence\" | \"upper\" | undefined","required":false,"description":"The joint words' case: `upper` (default) small capitals, or `sentence`, as given in body text, as rows' labels read."},{"path":"hasAddJoint","type":"boolean","required":false,"description":"Puts the add button in a last row after its joint word (`jointRest`, or `jointFirst` while there are no rules), lined up with the rules."},{"path":"addShape","type":"\"default\" | \"pill\" | undefined","required":false,"description":"The add button's shape: `default`, or `pill` for a fully rounded one."},{"path":"onRemove","type":"(index: number) => void","required":true,"description":"Called with the index of the rule to remove."},{"path":"onAdd","type":"(() => void) | undefined","required":false,"description":"Called when the add button is pressed; leave it out when the host adds elsewhere, e.g. one button for several lists."},{"path":"addVariant","type":"\"ghost\" | \"secondary\" | undefined","required":false,"description":"The add button's weight, `ghost` by default."},{"path":"rowGap","type":"2 | 4 | undefined","required":false,"description":"The space between the rows, the add row too, in Astryx spacing steps: `2` (8px, default) or `4` (16px)."},{"path":"align","type":"\"center\" | \"start\" | undefined","required":false,"description":"How each row's controls sit on it, `center` by default; `start` keeps their input line level when one shows a problem under it (see `RuleRow`)."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The size of the remove and add buttons, `md` by default."},{"path":"max","type":"number | undefined","required":false,"description":"The most rules the list holds; at that many the add button stays, but disabled."},{"path":"isAddDisabled","type":"boolean","required":false,"description":"Blocks the add button alone (e.g. until something the rules need is chosen); the rows stay as they are."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the list as a whole, already in the user's language."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the list; the rows' remove buttons and the add button are blocked."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Hides the add button and every remove button."}],"examples":[{"name":"Default","description":"The first row starts with \"where\", later ones with \"and\".","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"Empty","description":"No rule yet.","source":"export const Empty: Story = {\n args: { items: [], labels: { ...labels, empty: \"No rule: everything matches.\" } },\n};","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"Interactive","description":"Adding and removing rules.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<readonly Rule[]>(rules.slice(0, 2), (list, set) => (\n <RuleList\n {...args}\n items={list}\n onRemove={(index) => {\n set(list.filter((_rule, at) => at !== index));\n }}\n onAdd={() => {\n set([...list, { id: String(Date.now()), field: \"city\", value: \"\" }]);\n }}\n />\n )),\n};","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"WithError","description":"A problem with the list as a whole.","source":"export const WithError: Story = { args: { error: \"Fill in every rule.\" } };","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"Disabled","description":"Dimmed: the rows' remove buttons and the add button are blocked, and nothing is hidden.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"ReadOnly","description":"Read-only: the add button and every remove button are hidden.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"Medium","description":"The remove and add buttons at the `md` height.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"WideJoints","description":"Wider joint words, so \"where\" fits.","source":"export const WideJoints: Story = { args: { jointWidth: 56 } };","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"SingleRule","description":"A single rule: only \"where\".","source":"export const SingleRule: Story = { args: { items: rules.slice(0, 1) } };","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"MaxReached","description":"Twenty rules, the limit of the form this came from; the add button is disabled.","source":"export const MaxReached: Story = {\n args: {\n items: Array.from({ length: 20 }, (_, index) => ({\n id: String(index),\n field: \"city\",\n value: `Value ${index + 1}`,\n })),\n max: 20,\n labels: { ...labels, maxReached: \"You can add up to 20 rules.\" },\n },\n};","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"AddDisabled","description":"Only the add button is blocked; the rows stay editable.","source":"export const AddDisabled: Story = { args: { isAddDisabled: true } };","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"WithoutAdd","description":"For a host that adds elsewhere, such as one button for several lists.","source":"export const WithoutAdd: Story = { render: (args) => <RuleList {...args} onAdd={undefined} /> };","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, where rows wrap.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <RuleList {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"SentenceJoints","description":"Joints in sentence case, as rows' labels, and the pill add button in a last row after \"And\".","source":"export const SentenceJoints: Story = {\n args: {\n labels: { ...labels, jointFirst: \"Where\", jointRest: \"And\" },\n jointCase: \"sentence\",\n jointWidth: 90,\n hasAddJoint: true,\n addVariant: \"secondary\",\n addShape: \"pill\",\n },\n};","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"SentenceJointsEmpty","description":"No rule yet: the add row starts with \"Where\".","source":"export const SentenceJointsEmpty: Story = {\n args: { ...SentenceJoints.args, items: [] },\n};","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"RowsApart","description":"Rows 16px apart, including the add row.","source":"export const RowsApart: Story = { args: { ...SentenceJoints.args, rowGap: 4 } };","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"RowProblems","description":"A row whose value shows a problem under it: with `align=\"start\"` its input line, joint word and remove button stay level with the field's.","source":"export const RowProblems: Story = {\n args: {\n align: \"start\",\n items: [\n rules[0] ?? { id: \"x\", field: \"city\", value: \"\" },\n { id: \"d\", field: \"plan\", value: \"\" },\n ],\n },\n};","file":"packages/ui/src/organisms/RuleList.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Filled\">\n <RuleList {...args} />\n </Case>\n <Case caption=\"Empty\">\n <RuleList\n {...args}\n items={[]}\n labels={{ ...labels, empty: \"No rule: everything matches.\" }}\n />\n </Case>\n <Case caption=\"Error\">\n <RuleList {...args} error=\"Fill in every rule.\" />\n </Case>\n <Case caption=\"Max reached\">\n <RuleList\n {...args}\n max={3}\n labels={{ ...labels, maxReached: \"You can add up to 3 rules.\" }}\n />\n </Case>\n <Case caption=\"Disabled\">\n <RuleList {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <RuleList {...args} isReadOnly />\n </Case>\n <Case caption=\"Medium\">\n <RuleList {...args} size=\"md\" />\n </Case>\n <Case caption=\"A row's problem, top-aligned\">\n <RuleList {...args} {...RowProblems.args} />\n </Case>\n <Case caption=\"Rows 16px apart\">\n <RuleList {...args} rowGap={4} />\n </Case>\n <Case caption=\"Sentence joints, add in a row\">\n <RuleList\n {...args}\n labels={{ ...labels, jointFirst: \"Where\", jointRest: \"And\" }}\n jointCase=\"sentence\"\n jointWidth={90}\n hasAddJoint\n addVariant=\"secondary\"\n addShape=\"pill\"\n />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/RuleList.stories.tsx"}],"primaryExample":"Default"},{"name":"RuleRow","level":"molecules","category":"Inputs","keywords":["rule","row","joint","remove"],"import":"import { RuleRow } from \"@antscorp/ui\";","description":"Arrange a joining word, host controls and a remove button on a wrapping row.","usage":"Arrange a joining word, host controls and a remove button on a wrapping row. Inside RepeatableList with columns, the row uses the shared grid tracks.","bestPractices":[{"guidance":true,"description":"Give host controls the row's size and use align=start when a control shows validation below its input."},{"guidance":false,"description":"Do not assume isDisabled or isReadOnly changes the supplied controls; those flags control the row's remove affordance."}],"anatomy":[{"name":"Joint word","required":false,"description":"The translated word joining this row to its predecessor."},{"name":"Controls","required":true,"description":"Host-supplied fields, operators and values."},{"name":"Remove button","required":false,"description":"A named action, with its grid track preserved in read-only column layouts."}],"accessibility":["labels.remove names the remove button; each supplied control needs its own label."],"props":[{"path":"labels","type":"{ readonly remove: string; readonly joint?: string | undefined; }","required":true,"description":"The texts the row shows; the host translates them."},{"path":"labels.remove","type":"string","required":false,"requiredWhenParent":true,"description":"What the remove button does, such as \"Remove rule\"; it shows only an icon."},{"path":"labels.joint","type":"string | undefined","required":false,"description":"The word that joins the row to the one above, such as \"if\" or \"and\"; no word without it."},{"path":"align","type":"\"center\" | \"end\" | \"start\" | undefined","required":false,"description":"How the controls sit on the row: centred; on the top edge, so controls showing a problem under\nthem keep their input line level with the others; or on the bottom edge when they have labels."},{"path":"jointWidth","type":"number | undefined","required":false,"description":"The joint word's width in pixels, so words like \"where\" fit; 28 by default."},{"path":"jointCase","type":"\"sentence\" | \"upper\" | undefined","required":false,"description":"The joint word's case: `upper` (default) small capitals, or `sentence`, as given in body text (see `JointLabel`)."},{"path":"children","type":"ReactNode","required":true,"description":"The row's controls, such as a field, an operator and a value; the host supplies them. Give\nevery control the row's `size` (`md` by default, the remove button's height) so the row lines up."},{"path":"onRemove","type":"() => void","required":true,"description":"Called when the remove button is pressed."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The remove button's size, `md` by default; give the controls the same size so the row lines up."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the remove button and blocks it; the controls are the host's to disable."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Hides the remove button."}],"examples":[{"name":"Default","description":"The first row of a group: it starts with \"if\".","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"And","description":"A later row, joined with \"and\".","source":"export const And: Story = { args: { labels: { joint: \"and\", remove: \"Remove rule\" } } };","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"WideJoint","description":"A wider joint word, for words such as \"where\" that do not fit the default width.","source":"export const WideJoint: Story = {\n args: { labels: { joint: \"where\", remove: \"Remove rule\" }, jointWidth: 56 },\n};","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"NoJoint","description":"Without a joint word.","source":"export const NoJoint: Story = { args: { labels: { remove: \"Remove rule\" } } };","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"Interactive","description":"Rows line up on their joint words; removing one drops it.","source":"export const Interactive: Story = {\n render: (args) =>\n withState([\"a\", \"b\", \"c\"], (rows, set) => (\n <VStack gap={2}>\n {rows.map((row, index) => (\n <RuleRow\n key={row}\n {...args}\n labels={{ joint: index === 0 ? \"if\" : \"and\", remove: `Remove ${row}` }}\n onRemove={() => {\n args.onRemove();\n set(rows.filter((item) => item !== row));\n }}\n />\n ))}\n </VStack>\n )),\n};","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"AlignedToEnd","description":"Controls with labels sit on the bottom edge.","source":"export const AlignedToEnd: Story = { args: { align: \"end\" } };","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"AlignedToStart","description":"One control shows a problem under it: on the top edge, every input line, the joint word and the remove button stay level.","source":"export const AlignedToStart: Story = { args: { align: \"start\", children: controlsWithError } };","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"Disabled","description":"The remove button dimmed and blocked.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"ReadOnly","description":"Read-only: the remove button is hidden.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"Medium","description":"The `md` remove button, beside `md` controls.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"LongContent","description":"Many controls and a long remove label, which wrap.","source":"export const LongContent: Story = {\n args: {\n labels: { joint: \"if\", remove: \"Remove this rule and every value it holds\" },\n children: Array.from({ length: 10 }, (_, index) => (\n <TextInput\n key={index}\n label={`Value ${index + 1}`}\n isLabelHidden\n size=\"sm\"\n value={`Value ${index + 1}`}\n onChange={fn<() => void>()}\n />\n )),\n },\n};","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, where the row wraps.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <RuleRow {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/RuleRow.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"First (if)\">\n <RuleRow {...args} />\n </Case>\n <Case caption=\"Later (and)\">\n <RuleRow {...args} labels={{ ...args.labels, joint: \"and\" }} />\n </Case>\n <Case caption=\"No joint\">\n <RuleRow {...args} labels={{ remove: args.labels.remove }} />\n </Case>\n <Case caption=\"Disabled\">\n <RuleRow {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <RuleRow {...args} isReadOnly />\n </Case>\n <Case caption=\"Medium\">\n <RuleRow {...args} size=\"md\" />\n </Case>\n <Case caption=\"Top-aligned, one control with a problem\">\n <RuleRow {...args} align=\"start\">\n {controlsWithError}\n </RuleRow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/RuleRow.stories.tsx"}],"primaryExample":"Default"},{"name":"SearchableGroupedList","level":"organisms","category":"Selection","keywords":["search","groups","list","tiles"],"import":"import { SearchableGroupedList } from \"@antscorp/ui\";","description":"Browse host-rendered items in titled groups, filtering locally as text is typed.","usage":"Browse host-rendered items in titled groups, filtering locally as text is typed. Use TreeSelect when the result must be a single leaf chosen from two-level groups.","bestPractices":[{"guidance":true,"description":"Provide stable group keys and globally unique item keys; use itemText for the text to match and placeholder for independently loading groups."},{"guidance":false,"description":"Do not expect isDisabled to block host-rendered item actions; it only affects the uncapped list's search control."}],"anatomy":[{"name":"Search","required":true,"description":"The local query input with clear action."},{"name":"Group headings","required":true,"description":"Titles, optionally toggling collapsed groups."},{"name":"Items","required":true,"description":"Host rows or three-column tiles, replaced by group placeholders when supplied."},{"name":"No-match message","required":false,"description":"Shown when there are no matching items."}],"accessibility":["Supply labels.search and labels.clear, and scroll.label when using a scrolling region; item renderers supply their own accessible controls."],"props":[{"path":"labels","type":"{ readonly search: string; readonly noMatch: string; readonly clear: string; }","required":true,"description":"The texts the list shows; the host translates them."},{"path":"labels.search","type":"string","required":false,"requiredWhenParent":true,"description":"The search box's accessible name, and its placeholder unless `placeholder` is given, such as \"Search\"."},{"path":"labels.noMatch","type":"string","required":false,"requiredWhenParent":true,"description":"Shown when the search (or the data) leaves no item."},{"path":"labels.clear","type":"string","required":false,"requiredWhenParent":true,"description":"The clear button's name, such as \"Clear search\"; it shows only an icon."},{"path":"placeholder","type":"string | undefined","required":false,"description":"The search box's placeholder; `labels.search` when absent."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The search box's size, `md` by default; the uncapped list's only."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the search box and blocks it, the uncapped list's only; the items are the host's to disable."},{"path":"groups","type":"readonly ItemGroup<unknown>[]","required":true,"description":"The groups, in order. A group with no item (or no match) and no placeholder is left out."},{"path":"groups[].key","type":"string","required":false,"requiredWhenParent":true,"description":"A stable key for the group."},{"path":"groups[].title","type":"string","required":false,"requiredWhenParent":true,"description":"The heading over the group's items."},{"path":"groups[].items","type":"readonly unknown[]","required":false,"requiredWhenParent":true,"description":"The group's items."},{"path":"groups[].placeholder","type":"ReactNode","required":false,"description":"Drawn instead of the group's items while they load (a skeleton, or the load's error); a group\nwith one is shown even with no items or no match."},{"path":"itemKey","type":"(item: unknown) => string","required":true,"description":"A stable key for an item, unique across groups."},{"path":"itemText","type":"(item: unknown) => string","required":true,"description":"The text the search matches on for an item."},{"path":"renderItem","type":"(item: unknown) => ReactNode","required":true,"description":"Draws an item; keep it memoized when there are hundreds of items."},{"path":"layout","type":"\"grid\" | \"list\" | undefined","required":false,"description":"`grid`: three tiles a row; `list` (default): one row each."},{"path":"hasAutoFocus","type":"boolean","required":false,"description":"Focuses the search box once shown, in a pop-over too, which shows a frame after mounting."},{"path":"isCollapsible","type":"boolean","required":false,"description":"Each group folds under its title, all open at first. While searching, every group with a match\nis open; clearing the search brings back the groups the user folded."},{"path":"scroll","type":"{ readonly maxHeight?: number | undefined; readonly label: string; } | undefined","required":false,"description":"Caps the height, the groups scrolling under the search box, in a pop-over say; with no\n`maxHeight` it fills its parent's height, in a side panel say. The list then pads its own edges,\nso the search row (its magnifier at the end) and the rule under it reach the\nedges of a flush (unpadded) surface, and so does the scrollbar."},{"path":"scroll.maxHeight","type":"number | undefined","required":false,"description":""},{"path":"scroll.label","type":"string","required":false,"requiredWhenParent":true,"description":""}],"examples":[{"name":"Default","description":"Groups of rows under their headings; type to filter.","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"Grid","description":"Three tiles a row.","source":"export const Grid: Story = { args: { layout: \"grid\" } };","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"Empty","description":"No group: the no-match text.","source":"export const Empty: Story = { args: { groups: [] } };","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"EmptyGroup","description":"A group without items is left out.","source":"export const EmptyGroup: Story = {\n args: { groups: [...groups.slice(0, 1), { key: \"x\", title: \"Hidden\", items: [] }] },\n};","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"SingleItem","description":"A single item.","source":"export const SingleItem: Story = {\n args: { groups: [{ key: \"a\", title: \"Entry points\", items: [\"Schedule\"] }] },\n};","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"ManyItems","description":"Forty-eight items in four groups, as tiles.","source":"export const ManyItems: Story = { args: { groups: many, layout: \"grid\" } };","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"AutoFocus","description":"Focuses the search box when shown.","source":"export const AutoFocus: Story = { args: { hasAutoFocus: true } };","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <SearchableGroupedList {...args} layout=\"grid\" />\n </Narrow>\n ),\n};","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"Collapsible","description":"Each group folds under its title; a search opens every group with a match.","source":"export const Collapsible: Story = { args: { isCollapsible: true } };","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"Capped","description":"Capped at 200px: the groups scroll under the search box, as in a pop-over.","source":"export const Capped: Story = {\n args: { groups: many, isCollapsible: true, scroll: { maxHeight: 200, label: \"Items\" } },\n};","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"Loading","description":"One group still loading: skeleton rows in its place, the other groups usable.","source":"export const Loading: Story = { args: { groups: loading } };","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"LoadingGrid","description":"One group still loading, as tiles: skeleton tiles in its place.","source":"export const LoadingGrid: Story = { args: { groups: loadingGrid, layout: \"grid\" } };","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"WithPlaceholder","description":"A placeholder that differs from the search box's name.","source":"export const WithPlaceholder: Story = { args: { placeholder: \"Search fruit and veg\" } };","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"Medium","description":"The search box at the `md` height.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"Disabled","description":"The search box dimmed and blocked.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Rows\">\n <SearchableGroupedList {...args} />\n </Case>\n <Case caption=\"Tiles\">\n <SearchableGroupedList {...args} layout=\"grid\" />\n </Case>\n <Case caption=\"Collapsible, capped\">\n <SearchableGroupedList\n {...args}\n isCollapsible\n scroll={{ maxHeight: 200, label: \"Items\" }}\n />\n </Case>\n <Case caption=\"Empty\">\n <SearchableGroupedList {...args} groups={[]} />\n </Case>\n <Case caption=\"Loading\">\n <SearchableGroupedList {...args} groups={loading} />\n </Case>\n <Case caption=\"Loading tiles\">\n <SearchableGroupedList {...args} groups={loadingGrid} layout=\"grid\" />\n </Case>\n <Case caption=\"Medium\">\n <SearchableGroupedList {...args} size=\"md\" />\n </Case>\n <Case caption=\"Disabled\">\n <SearchableGroupedList {...args} isDisabled />\n </Case>\n <Case caption=\"Placeholder\">\n <SearchableGroupedList {...args} placeholder=\"Search fruit and veg\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/SearchableGroupedList.stories.tsx"}],"primaryExample":"Default"},{"name":"Select","level":"molecules","category":"Selection","keywords":["choice","options","search"],"import":"import { Select } from \"@antscorp/ui\";","description":"Choose one value from options, optionally grouped into sections.","usage":"Choose one value from options, optionally grouped into sections. Keep value controlled through onChange. Offer onClear for an empty selection; style the control and Field via controlXstyle and containerXstyle. Localize upstream strings with the facade's InternationalizationProvider.","bestPractices":[{"guidance":true,"description":"Use hasSearch for long lists and keep the known label available through labels.value while options load."},{"guidance":false,"description":"Do not supply labels.warning alongside error expecting both to appear; warning takes precedence over the validation error."},{"guidance":false,"description":"Do not disable a field merely to display its value; use isReadOnly when editing is unavailable."}],"anatomy":[{"name":"Field label","required":true,"description":"Names the choice even when visually hidden."},{"name":"Trigger","required":true,"description":"Shows the selected value or placeholder and opens the list."},{"name":"Options","required":false,"description":"The choices, sections and optional search field, while the list is open and editable."}],"accessibility":["Always provide label, including when isLabelHidden is true.","Supply translated description and error text so the choice and its validation are clear."],"related":[{"name":"TreeSelect","when":"Choose one leaf from a two-level tree."},{"name":"ChipListAddPopover","when":"Choose multiple values in a compact popover with an Apply draft."},{"name":"TransferPicker","when":"Choose multiple values with available and picked rows visible together."}],"props":[{"path":"containerXstyle","type":"StyleXStyles","required":false,"description":"StyleX styles for the field container (the Field box around the label,\ncontrol, and status). Has no effect where no Field box renders: inside\nInputGroup (style the InputGroup instead) and in a `horizontal-labels`\nFormLayout."},{"path":"controlXstyle","type":"StyleXStyles","required":false,"description":"StyleX styles for the visual control box: the element that paints the\nfield border and background and holds its icons and end controls. Never\nthe native input."},{"path":"label","type":"string","required":true,"description":"Label text for the selector (always rendered for accessibility)."},{"path":"isLabelHidden","type":"boolean","required":false,"default":"false","description":"Whether to visually hide the label (still accessible to screen readers)."},{"path":"description","type":"string | undefined","required":false,"description":"Description text displayed between the label and selector."},{"path":"isRequired","type":"boolean","required":false,"default":"false","description":"Whether the field is required. Mutually exclusive with isOptional."},{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether the selector is disabled."},{"path":"isReadOnly","type":"boolean","required":false,"default":"false","description":"Whether the selector is read-only.\nThe selected value stays visible, focusable, and included in form\nsubmission, and retains its combobox identity with `aria-readonly`. The\nselection surface and editing affordances are removed.\nUnlike `isDisabled`, a read-only selector is not dimmed and keeps its\nvalue in form submission. `isDisabled` takes precedence when both are set."},{"path":"placeholder","type":"string | undefined","required":false,"default":"'Select...'","description":"Placeholder text when no value is selected."},{"path":"width","type":"SizeValue | undefined","required":false,"description":"Width of the field. Numbers are treated as pixels, strings are used as-is\n(e.g. `'100%'`). Sizes the whole field (label, control, and status) so they\nstay aligned, unlike setting width via `controlXstyle`/`controlClassName`/`controlStyle`."},{"path":"hasSearch","type":"boolean","required":false,"default":"false","description":"Whether to show a search input for filtering options."},{"path":"options","type":"readonly SelectorOptionType[]","required":true,"description":"The list, in order. Item types: string | SelectorDivider | SelectorOptionData | SelectorSection."},{"path":"options[].type","type":"\"divider\" | \"section\"","required":false,"description":"Required in SelectorDivider. Required in SelectorSection."},{"path":"options[].value","type":"string","required":false,"description":"Required in SelectorOptionData."},{"path":"options[].label","type":"string | undefined","required":false,"description":"In SelectorOptionData."},{"path":"options[].description","type":"ReactNode","required":false,"description":"In SelectorOptionData."},{"path":"options[].disabled","type":"boolean","required":false,"description":"In SelectorOptionData."},{"path":"options[].icon","type":"IconType | ReactNode","required":false,"description":"In SelectorOptionData."},{"path":"options[].action","type":"ReactNode","required":false,"description":"In SelectorOptionData. The option's secondary control — an Edit button beside a saved label —\nas one node the caller renders and names; the host only places it. In\n`MultiSelector`, any option carrying one turns the popup into a grid whose\nrows pair the option with its action, reachable by pointer, touch, the\ninline-end arrow, and a screen reader (`spec:AST-058`). `Selector` does\nnot render it yet and warns in development when an option carries one.\nAbsent and `null` mean the same: none."},{"path":"options[].title","type":"string | undefined","required":false,"description":"In SelectorSection."},{"path":"options[].options","type":"SelectorOptionData[]","required":false,"description":"Required in SelectorSection."},{"path":"options[].options[].value","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"options[].options[].label","type":"string","required":false,"description":""},{"path":"options[].options[].description","type":"ReactNode","required":false,"description":""},{"path":"options[].options[].disabled","type":"boolean","required":false,"description":""},{"path":"options[].options[].icon","type":"IconType | ReactNode","required":false,"description":""},{"path":"options[].options[].action","type":"ReactNode","required":false,"description":"The option's secondary control — an Edit button beside a saved label —\nas one node the caller renders and names; the host only places it. In\n`MultiSelector`, any option carrying one turns the popup into a grid whose\nrows pair the option with its action, reachable by pointer, touch, the\ninline-end arrow, and a screen reader (`spec:AST-058`). `Selector` does\nnot render it yet and warns in development when an option carries one.\nAbsent and `null` mean the same: none."},{"path":"value","type":"string | null | undefined","required":false,"description":"The picked option's value; `null` or none shows the placeholder."},{"path":"onChange","type":"(value: string) => void","required":true,"description":"Called with the value of the option picked."},{"path":"onClear","type":"(() => void) | undefined","required":false,"description":"Called by the clear button, which shows only when this is given and an option is picked."},{"path":"variant","type":"\"ghost\" | \"input\" | undefined","required":false,"description":"The trigger's look: an underlined field (`input`, the default) or a bare text button (`ghost`)."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The trigger's size, md by default; the kit narrows the upstream sizes to sm and md."},{"path":"isLoading","type":"boolean","required":false,"description":"The options are loading: a spinner in place of the chevron and the trigger busy from 300 ms\non, for at least 300 ms (`useLoadingShown`), and the list does not open meanwhile. The picked\nlabel shows when `options` (the last list) or `labels.value` know it; otherwise the trigger is\nblank, with no placeholder."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the pick, already in the user's language, shown under the trigger."},{"path":"labels","type":"{ readonly search?: string | undefined; readonly noMatch?: string | undefined; readonly warning?: string | undefined; readonly value?: string | undefined; } | undefined","required":false,"description":"Host texts; missing strings use the facade's InternationalizationProvider locale (English by default)."},{"path":"labels.search","type":"string | undefined","required":false,"description":"The search box's placeholder, such as \"Search\"."},{"path":"labels.noMatch","type":"string | undefined","required":false,"description":"Shown in the list when the search leaves no option."},{"path":"labels.warning","type":"string | undefined","required":false,"description":"A caution about the pick, shown as an icon in the trigger with this text in its tooltip."},{"path":"labels.value","type":"string | undefined","required":false,"description":"The trigger's text while `isLoading` when `value` is not among `options`, such as a stored label."}],"examples":[{"name":"StyledParts","description":"The visual control and surrounding Field have independent StyleX parts.","source":"export const StyledParts: Story = {\n args: { controlXstyle: partStyles.control, containerXstyle: partStyles.container },\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"Default","description":"A short value; open it: the pop-up is as wide as the field.","source":"export const Default: Story = {\n render: () => (\n <Narrow>\n {withState(\"customer\", (value, set) => (\n <Select label=\"Data access\" isLabelHidden options={OPTIONS} value={value} onChange={set} />\n ))}\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"LongLabel","description":"A long option picked, in a narrow field: it ends in an ellipsis; hover the open option for the whole label.","source":"export const LongLabel: Story = {\n render: (args) => (\n <Narrow>\n <Select {...args} value=\"long\" />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"SectionsAndSearch","description":"Sections and many options, with a search box.","source":"export const SectionsAndSearch: Story = {\n render: () => (\n <Narrow>\n {withState(\"customer\", (value, set) => (\n <Select\n label=\"Data access\"\n isLabelHidden\n options={SECTIONS}\n hasSearch\n labels={{ search: \"Search\", noMatch: \"No views\" }}\n value={value}\n onChange={set}\n />\n ))}\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"Empty","description":"Nothing picked yet: the placeholder.","source":"export const Empty: Story = {\n render: (args) => (\n <Narrow>\n <Select {...args} placeholder=\"Pick a view\" />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"Error","description":"An error under the field.","source":"export const Error: Story = {\n render: (args) => (\n <Narrow>\n <Select {...args} isLabelHidden={false} error=\"Pick a view\" />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"Disabled","description":"Disabled: it does not open.","source":"export const Disabled: Story = {\n render: (args) => (\n <Narrow>\n <Select {...args} value=\"customer\" isDisabled />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"Clearable","description":"A pick that can be cleared: the clear button shows while an option is picked.","source":"export const Clearable: Story = {\n render: () => (\n <Narrow>\n {withState<string | null>(\"customer\", (value, set) => (\n <Select\n label=\"Data access\"\n isLabelHidden\n options={OPTIONS}\n value={value}\n onChange={set}\n onClear={() => {\n set(null);\n }}\n />\n ))}\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"Warning","description":"A caution about the pick: an icon in the trigger, its text in a tooltip.","source":"export const Warning: Story = {\n render: (args) => (\n <Narrow>\n <Select\n {...args}\n value=\"view-1\"\n labels={{ warning: \"You no longer have access to this view\" }}\n />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"Loading","description":"The options loading, with the picked label known (from the last list or `labels.value`): the\nlabel stays, a spinner stands where the chevron was, and the list does not open.","source":"export const Loading: Story = {\n render: (args) => (\n <Narrow>\n <Select {...args} options={[]} value=\"customer\" isLoading labels={{ value: \"Customer\" }} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"LoadingUnknownLabel","description":"The options loading with the picked label unknown: a blank trigger and the spinner.","source":"export const LoadingUnknownLabel: Story = {\n render: (args) => (\n <Narrow>\n <Select {...args} options={[]} value=\"customer\" placeholder=\"Pick a view\" isLoading />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"Ghost","description":"The bare text-button look, for a choice inside a sentence.","source":"export const Ghost: Story = {\n render: (args) => <Select {...args} value=\"customer\" variant=\"ghost\" size=\"sm\" />,\n};","file":"packages/ui/src/molecules/Select.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Short value\">\n <Narrow>\n <Select {...args} value=\"customer\" />\n </Narrow>\n </Case>\n <Case caption=\"Long value, cut\">\n <Narrow>\n <Select {...args} value=\"long\" />\n </Narrow>\n </Case>\n <Case caption=\"Placeholder\">\n <Narrow>\n <Select {...args} placeholder=\"Pick a view\" />\n </Narrow>\n </Case>\n <Case caption=\"Error\">\n <Narrow>\n <Select {...args} value=\"customer\" isLabelHidden={false} error=\"Pick a view\" />\n </Narrow>\n </Case>\n <Case caption=\"Disabled\">\n <Narrow>\n <Select {...args} value=\"customer\" isDisabled />\n </Narrow>\n </Case>\n <Case caption=\"Loading, label known\">\n <Narrow>\n <Select {...args} value=\"customer\" isLoading />\n </Narrow>\n </Case>\n <Case caption=\"Loading, label unknown\">\n <Narrow>\n <Select {...args} options={[]} value=\"customer\" isLoading />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/Select.stories.tsx"}],"primaryExample":"Default"},{"name":"SideLabelRow","level":"molecules","category":"Layout","keywords":["card","row","caption","responsive"],"import":"import { SideLabelRow } from \"@antscorp/ui\";","description":"Place a narrow caption beside controls inside a card, with padding for the card's rows.","usage":"Place a narrow caption beside controls inside a card, with padding for the card's rows. Use FieldRow for a form section with theme-driven caption and control widths.","bestPractices":[{"guidance":true,"description":"Keep labelWidth consistent across the card and let controls move below the caption when space runs out."},{"guidance":false,"description":"Do not treat the visual caption as an accessible label association for every supplied control."}],"anatomy":[{"name":"Caption","required":true,"description":"The narrow label column, wrapping long text."},{"name":"Controls","required":true,"description":"Host content beside the caption or beneath it in a narrow layout."}],"props":[{"path":"label","type":"string","required":true,"description":"The row's label, in the narrow column at its start."},{"path":"labelWidth","type":"number | undefined","required":false,"description":"The label column's width in pixels, 90 by default; a longer label wraps in it."},{"path":"children","type":"ReactNode","required":true,"description":"The row's controls."}],"examples":[{"name":"Default","description":"A label beside one control, centred on it.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/SideLabelRow.stories.tsx"},{"name":"Rows","description":"Several rows one under the other: the labels line up in their column.","source":"export const Rows: Story = {\n render: () => (\n <VStack>\n <SideLabelRow label=\"Pick one\">{control}</SideLabelRow>\n <SideLabelRow label=\"Where\">{control}</SideLabelRow>\n <SideLabelRow label=\"And\">{control}</SideLabelRow>\n </VStack>\n ),\n};","file":"packages/ui/src/molecules/SideLabelRow.stories.tsx"},{"name":"Interactive","description":"A control the user types in.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(\"\", (value, set) => (\n <SideLabelRow {...args}>\n <TextInput label=\"Name\" isLabelHidden size=\"md\" value={value} onChange={set} />\n </SideLabelRow>\n )),\n};","file":"packages/ui/src/molecules/SideLabelRow.stories.tsx"},{"name":"TallContent","description":"Content taller than one line: the label stays on the first.","source":"export const TallContent: Story = {\n args: {\n children: (\n <>\n {control}\n {control}\n </>\n ),\n },\n};","file":"packages/ui/src/molecules/SideLabelRow.stories.tsx"},{"name":"LongLabel","description":"A label longer than its column wraps in it.","source":"export const LongLabel: Story = { args: { label: \"A label much longer than its column\" } };","file":"packages/ui/src/molecules/SideLabelRow.stories.tsx"},{"name":"WideLabel","description":"A wider label column.","source":"export const WideLabel: Story = { args: { label: \"Pick one option\", labelWidth: 140 } };","file":"packages/ui/src/molecules/SideLabelRow.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame the controls move under the label.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <SideLabelRow {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/SideLabelRow.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <SideLabelRow {...args} />\n </Case>\n <Case caption=\"Long label\">\n <SideLabelRow {...args} label=\"A label much longer than its column\" />\n </Case>\n <Case caption=\"Narrow\">\n <Narrow>\n <SideLabelRow {...args} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/SideLabelRow.stories.tsx"}],"primaryExample":"Default"},{"name":"SplitButton","level":"molecules","category":"Inputs","keywords":["button","menu","actions","split"],"import":"import { SplitButton } from \"@antscorp/ui\";","description":"Pair a primary action with a menu of related alternatives.","usage":"Pair a primary action with a menu of related alternatives. With no items, only the main button is shown.","bestPractices":[{"guidance":true,"description":"Use isMainDisabled to block only the primary action, and labels.menu to name the menu arrow."},{"guidance":false,"description":"Do not assume isLoading disables the alternatives menu; it affects the main action while the menu stays usable."}],"anatomy":[{"name":"Main button","required":true,"description":"The default action, optionally busy or disabled."},{"name":"Menu button","required":false,"description":"A named arrow or host glyph opening the alternatives."},{"name":"Menu","required":false,"description":"Related actions, each with its own optional disabled state."}],"accessibility":["label names the main action and labels.menu names the icon-only menu button."],"props":[{"path":"label","type":"string","required":true,"description":"The main button's text."},{"path":"onClick","type":"() => void","required":true,"description":"Called when the main button is pressed."},{"path":"labels","type":"{ readonly menu: string; }","required":true,"description":"The texts the view shows; the host translates them."},{"path":"labels.menu","type":"string","required":false,"requiredWhenParent":true,"description":"The arrow's accessible name, e.g. \"More options\"; it shows only an icon."},{"path":"items","type":"readonly SplitButtonItem[]","required":true,"description":"The menu's entries; without any, only the main button shows."},{"path":"items[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The entry's text."},{"path":"items[].onSelect","type":"() => void","required":false,"requiredWhenParent":true,"description":"Called when the entry is chosen."},{"path":"items[].isDisabled","type":"boolean","required":false,"description":"Not choosable."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The buttons' height, `md` by default."},{"path":"isDisabled","type":"boolean","required":false,"description":"Neither the button nor the menu can be used."},{"path":"isMainDisabled","type":"boolean","required":false,"description":"Disables only the main button; the menu stays usable."},{"path":"isLoading","type":"boolean","required":false,"description":"The main button shows a spinner and is announced as busy; the menu is not affected."},{"path":"isRound","type":"boolean","required":false,"description":"Rounds its two ends into a pill; the inner corners stay square."},{"path":"variant","type":"\"primary\" | \"secondary\" | undefined","required":false,"description":"The button's weight, `primary` by default."},{"path":"icons","type":"{ readonly menu?: ReactNode; } | undefined","required":false,"description":"The glyphs of inner parts: `menu`, the menu button's, a down chevron by default."},{"path":"icons.menu","type":"ReactNode","required":false,"description":""},{"path":"maxWidth","type":"number | undefined","required":false,"description":"The widest it gets, in pixels: a longer label is cut short with an ellipsis and shows whole\nin a tooltip. Its content's width when absent."}],"examples":[{"name":"Default","description":"The main button with its menu arrow; open the menu to pick an entry.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"Small","description":"The `sm` height, for a dense toolbar.","source":"export const Small: Story = { args: { size: \"sm\" } };","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"Secondary","description":"A quieter weight.","source":"export const Secondary: Story = { args: { variant: \"secondary\" } };","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"NoItems","description":"Without entries only the main button shows.","source":"export const NoItems: Story = { args: { items: [] } };","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"MenuWithDisabledItems","description":"An entry that cannot be chosen.","source":"export const MenuWithDisabledItems: Story = {\n args: {\n items: [\n { label: \"Save as a copy\", onSelect: fn<() => void>() },\n { label: \"Save as a template\", onSelect: fn<() => void>(), isDisabled: true },\n ],\n },\n};","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"Disabled","description":"Not usable, e.g. while there is nothing to save.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"LongContent","description":"Many entries with long labels.","source":"export const LongContent: Story = {\n args: {\n label: \"Save all of the changes\",\n items: Array.from({ length: 12 }, (_, index) => ({\n label: `Save the changes under the name number ${index + 1} in a new place`,\n onSelect: fn<() => void>(),\n })),\n },\n};","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"SingleItem","description":"A single entry.","source":"export const SingleItem: Story = {\n args: { items: [{ label: \"Save as a copy\", onSelect: fn<() => void>() }] },\n};","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <SplitButton {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"Loading","description":"The main button busy with a spinner; the menu stays usable.","source":"export const Loading: Story = { args: { isLoading: true } };","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"MainDisabled","description":"Only the main button is disabled, e.g. nothing to save, while the menu stays usable.","source":"export const MainDisabled: Story = { args: { isMainDisabled: true } };","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"MenuIcon","description":"Another glyph on the menu button, e.g. \"more\" for a menu of actions on an item.","source":"export const MenuIcon: Story = {\n args: { variant: \"secondary\", icons: { menu: <Icon icon=\"moreHorizontal\" size=\"sm\" /> } },\n};","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"Round","description":"A pill with both ends rounded.","source":"export const Round: Story = { args: { isRound: true, variant: \"secondary\" } };","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <SplitButton {...args} />\n </Case>\n <Case caption=\"Secondary\">\n <SplitButton {...args} variant=\"secondary\" />\n </Case>\n <Case caption=\"No items\">\n <SplitButton {...args} items={[]} />\n </Case>\n <Case caption=\"Disabled\">\n <SplitButton {...args} isDisabled />\n </Case>\n <Case caption=\"Small\">\n <SplitButton {...args} size=\"sm\" />\n </Case>\n <Case caption=\"Round\">\n <SplitButton {...args} isRound />\n </Case>\n <Case caption=\"Loading\">\n <SplitButton {...args} isLoading />\n </Case>\n <Case caption=\"Main disabled\">\n <SplitButton {...args} isMainDisabled />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/SplitButton.stories.tsx"},{"name":"MaxWidth","description":"Capped at 160px: the long label is cut short and shows whole in a tooltip on hover.","source":"export const MaxWidth: Story = {\n args: { label: \"Variant 3 with a name far too long to fit\", maxWidth: 160 },\n};","file":"packages/ui/src/molecules/SplitButton.stories.tsx"}],"primaryExample":"Default"},{"name":"StatusButton","level":"molecules","category":"Feedback","keywords":["status","dot","button","connection"],"import":"import { StatusButton } from \"@antscorp/ui\";","description":"Combine an action with a small dot showing a related state.","usage":"Combine an action with a small dot showing a related state. Use a plain button when no state needs to be communicated.","bestPractices":[{"guidance":true,"description":"Translate labels.status to explain the dot independently of its tone."},{"guidance":false,"description":"Do not rely on the dot's color alone to convey the state."}],"anatomy":[{"name":"Button text","required":true,"description":"The action supplied by label."},{"name":"Status dot","required":true,"description":"A named dot in the supplied status tone."},{"name":"Icon","required":false,"description":"Host-supplied leading content."}],"accessibility":["labels.status names the status dot and label names the action."],"props":[{"path":"label","type":"string","required":true,"description":"Accessible label for the button (required for accessibility).\nRendered as visible text by default. When `isIconOnly` is true,\nused as aria-label instead."},{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether the button is disabled."},{"path":"icon","type":"ReactNode","required":false,"description":"Icon element rendered before the label text."},{"path":"status","type":"keyof StatusDotVariantMap","required":true,"description":"The dot's tone: neutral, accent, success, warning or error."},{"path":"labels","type":"{ readonly status: string; }","required":true,"description":"The texts the view shows; the host translates them."},{"path":"labels.status","type":"string","required":false,"requiredWhenParent":true,"description":"What the dot means, read out for it, e.g. \"Connected\"."},{"path":"onClick","type":"() => void","required":true,"description":"Called when the button is pressed."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The button's height, `md` by default."}],"examples":[{"name":"Default","description":"Connected: a green dot.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/StatusButton.stories.tsx"},{"name":"Neutral","description":"Neutral: off.","source":"export const Neutral: Story = { args: { status: \"neutral\", labels: { status: \"Off\" } } };","file":"packages/ui/src/molecules/StatusButton.stories.tsx"},{"name":"Warning","description":"Warning: connecting.","source":"export const Warning: Story = { args: { status: \"warning\", labels: { status: \"Connecting\" } } };","file":"packages/ui/src/molecules/StatusButton.stories.tsx"},{"name":"Failed","description":"Error: failed.","source":"export const Failed: Story = { args: { status: \"error\", labels: { status: \"Failed\" } } };","file":"packages/ui/src/molecules/StatusButton.stories.tsx"},{"name":"Interactive","description":"Each press moves the dot to the next tone.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(0, (index, set) => (\n <StatusButton\n {...args}\n status={tones[index % tones.length] ?? \"neutral\"}\n labels={{ status: tones[index % tones.length] ?? \"neutral\" }}\n onClick={() => {\n args.onClick();\n set(index + 1);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/StatusButton.stories.tsx"},{"name":"Disabled","description":"Not pressable.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/StatusButton.stories.tsx"},{"name":"Medium","description":"The `md` height.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/molecules/StatusButton.stories.tsx"},{"name":"LongLabel","description":"A long label.","source":"export const LongLabel: Story = {\n args: { label: \"Connect to the assistant that runs on this computer\" },\n};","file":"packages/ui/src/molecules/StatusButton.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n args: { label: \"Connect to the assistant that runs on this computer\" },\n render: (args) => (\n <Narrow>\n <StatusButton {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/StatusButton.stories.tsx"},{"name":"AllStates","description":"Each status tone one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n {tones.map((tone) => (\n <Case key={tone} caption={tone}>\n <StatusButton {...args} status={tone} labels={{ status: tone }} />\n </Case>\n ))}\n <Case caption=\"Disabled\">\n <StatusButton {...args} isDisabled />\n </Case>\n <Case caption=\"Medium\">\n <StatusButton {...args} size=\"md\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/StatusButton.stories.tsx"}],"primaryExample":"Default"},{"name":"TagSelect","level":"molecules","category":"Selection","keywords":["tag","color","choice","popover"],"import":"import { TagSelect } from \"@antscorp/ui\";","description":"Choose one value through a small colored tag and a popover of tags.","usage":"Choose one value through a small colored tag and a popover of tags. Use Select for a conventional field with a larger options list.","bestPractices":[{"guidance":true,"description":"Keep option labels concise and provide label for the tag button and title for its popover."},{"guidance":false,"description":"Do not use the colored tag alone to explain the selected value; its text must identify it."}],"anatomy":[{"name":"Picked tag","required":true,"description":"The selected label, or the raw unknown value in blue."},{"name":"Popover heading","required":false,"description":"The supplied title while the tag list is open."},{"name":"Tag choices","required":false,"description":"Wrapping tag buttons that choose a value and close the popover."}],"accessibility":["label names the tag button; read-only mode shows the tag without a button."],"props":[{"path":"label","type":"string","required":true,"description":"Names the tag button for assistive tech, such as \"Kind of the value\"; not shown."},{"path":"title","type":"string","required":true,"description":"The heading of the popover over the tags, such as \"Kind\"."},{"path":"options","type":"readonly TagSelectOption<string>[]","required":true,"description":"The tags to pick from, in order."},{"path":"options[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What `onChange` gets when the tag is picked."},{"path":"options[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The tag's text; cut short with an ellipsis past 70px, and shown whole in a tooltip."},{"path":"options[].tone","type":"TagTone","required":false,"requiredWhenParent":true,"description":"The tag's fill."},{"path":"value","type":"string","required":true,"description":"The picked tag's value; one no option has shows as its own text, in the blue tone."},{"path":"onChange","type":"(value: string) => void","required":true,"description":"Called with a tag's value when it is picked in the popover; the popover then closes."},{"path":"isDisabled","type":"boolean","required":false,"description":"Blocks the tag; the control holding it dims it (a `TreeSelect` frame), so it is dimmed once."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows the picked tag, not dimmed; it does not open the popover."}],"examples":[{"name":"Default","description":"The first tag picked; pressing it opens the tags, and a press on one picks it.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/TagSelect.stories.tsx"},{"name":"Open","description":"The popover open, the picked tag pressed.","source":"export const Open: Story = {\n play: async ({ canvasElement }) => {\n await userEvent.click(\n within(canvasElement).getByRole(\"button\", { name: \"Format of the value\" }),\n );\n },\n};","file":"packages/ui/src/molecules/TagSelect.stories.tsx"},{"name":"Tones","description":"Each tone: cyan, blue and green.","source":"export const Tones: Story = {\n render: (args) => (\n <Gallery>\n {OPTIONS.map((option) => (\n <Case key={option.value} caption={`${option.tone}: ${option.label}`}>\n <TagSelect {...args} value={option.value} />\n </Case>\n ))}\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/TagSelect.stories.tsx"},{"name":"LongLabel","description":"A tag too long for 70px: cut short, whole in a tooltip, here and in the popover.","source":"export const LongLabel: Story = { args: { options: LONG, value: \"very-long\" } };","file":"packages/ui/src/molecules/TagSelect.stories.tsx"},{"name":"SingleOption","description":"A single tag to pick.","source":"export const SingleOption: Story = { args: { options: OPTIONS.slice(0, 1) } };","file":"packages/ui/src/molecules/TagSelect.stories.tsx"},{"name":"UnknownValue","description":"A value no option has: its own text, in the blue tone.","source":"export const UnknownValue: Story = { args: { value: \"other\" } };","file":"packages/ui/src/molecules/TagSelect.stories.tsx"},{"name":"InTreeSelect","description":"As a `TreeSelect`'s `startAddon`: one underlined control, the tag, a line, the select.","source":"export const InTreeSelect: Story = { render: (args) => framed(args) };","file":"packages/ui/src/molecules/TagSelect.stories.tsx"},{"name":"InTreeSelectStates","description":"As a `TreeSelect`'s `startAddon`, each state: the underline red on error, dimmed, read-only.","source":"export const InTreeSelectStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Error\">{framed(args, { error: \"Field is required\" })}</Case>\n <Case caption=\"Disabled\">{framed(args, { isDisabled: true })}</Case>\n <Case caption=\"Read-only\">{framed(args, { isReadOnly: true })}</Case>\n <Case caption=\"Narrow\">\n <Narrow>{framed(args, { width: \"100%\" })}</Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/TagSelect.stories.tsx"},{"name":"Disabled","description":"Blocked; on its own not dimmed, as the control holding it dims it.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/TagSelect.stories.tsx"},{"name":"ReadOnly","description":"The tag, not dimmed; it does not open.","source":"export const ReadOnly: Story = { args: { isReadOnly: true, value: \"long\" } };","file":"packages/ui/src/molecules/TagSelect.stories.tsx"},{"name":"InNarrow","description":"In a drawer-width frame.","source":"export const InNarrow: Story = {\n render: (args) => (\n <Narrow>\n <TagSelect {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/TagSelect.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Picked\">\n <TagSelect {...args} />\n </Case>\n <Case caption=\"Long label\">\n <TagSelect {...args} options={LONG} value=\"very-long\" />\n </Case>\n <Case caption=\"Unknown value\">\n <TagSelect {...args} value=\"other\" />\n </Case>\n <Case caption=\"Disabled\">\n <TagSelect {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <TagSelect {...args} isReadOnly value=\"long\" />\n </Case>\n <Case caption=\"In a tree select\">{framed(args)}</Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/TagSelect.stories.tsx"}],"primaryExample":"Default"},{"name":"TextAction","level":"atoms","category":"Inputs","keywords":["action","text","button","chevron"],"import":"import { TextAction } from \"@antscorp/ui\";","description":"Offer a quiet action styled as bold accent text, optionally with an icon or menu chevron.","usage":"Offer a quiet action styled as bold accent text, optionally with an icon or menu chevron. Use AddButton for an add action with the kit's plus glyph.","bestPractices":[{"guidance":true,"description":"Pass label in normal case and let case control its appearance; omit onClick when a Popover wrapper owns activation."},{"guidance":false,"description":"Do not treat this action as navigation; it renders a button without an href."}],"anatomy":[{"name":"Label","required":true,"description":"The translated action text."},{"name":"Leading icon","required":false,"description":"Host-supplied content before the label."},{"name":"Chevron","required":false,"description":"An indicator for an action that opens a menu."}],"accessibility":["The text supplies the button's accessible name."],"props":[{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether the link is disabled.\nA disabled link renders as a plain anchor without an href (and without\ntarget/rel/onClick), so it cannot navigate or run onClick. It keeps a tab\nstop and is announced as a disabled link."},{"path":"label","type":"string","required":true,"description":"What it does, such as \"Remove all\"; shown in capitals by default, so pass it in normal case."},{"path":"icon","type":"ReactNode","required":false,"description":"A glyph before the text, in the text's colour, such as an Astryx `Icon` of a bin for a removal."},{"path":"case","type":"\"sentence\" | \"upper\" | undefined","required":false,"description":"`upper` (default) shows the label in capitals, such as \"REMOVE ALL\"; `sentence` as given."},{"path":"onClick","type":"(() => void) | undefined","required":false,"description":"Called when it is pressed. Leave it out when a wrapper handles the presses, such as an Astryx\n`Popover` it is the trigger of (the popover finds the button and listens to it)."},{"path":"hasChevron","type":"boolean","required":false,"description":"A down chevron after the text, for an action that opens a menu."}],"examples":[{"name":"Default","description":"Bold accent capitals with no frame; the label is passed in normal case.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/TextAction.stories.tsx"},{"name":"WithChevron","description":"A chevron after the text, for an action that opens a menu.","source":"export const WithChevron: Story = { args: { label: \"Create new\", hasChevron: true } };","file":"packages/ui/src/atoms/TextAction.stories.tsx"},{"name":"WithIcon","description":"A glyph before the text and the label as given (\"Remove\" with a glyph).","source":"export const WithIcon: Story = {\n args: { label: \"Remove\", case: \"sentence\", icon: <Icon icon=\"close\" /> },\n};","file":"packages/ui/src/atoms/TextAction.stories.tsx"},{"name":"Disabled","description":"Dimmed and not pressable, e.g. nothing to remove.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/atoms/TextAction.stories.tsx"},{"name":"Interactive","description":"Pressing it counts.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(0, (count, set) => (\n <VStack gap={1}>\n <TextAction\n {...args}\n onClick={() => {\n args.onClick?.();\n set(count + 1);\n }}\n />\n <Text type=\"supporting\">Pressed {count} times</Text>\n </VStack>\n )),\n};","file":"packages/ui/src/atoms/TextAction.stories.tsx"},{"name":"LongText","description":"A long label stays on one line.","source":"export const LongText: Story = {\n args: { label: \"Create a new item from what you picked\", hasChevron: true },\n};","file":"packages/ui/src/atoms/TextAction.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <TextAction {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/TextAction.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <TextAction {...args} />\n </Case>\n <Case caption=\"With chevron\">\n <TextAction {...args} label=\"Create new\" hasChevron />\n </Case>\n <Case caption=\"With icon, sentence case\">\n <TextAction {...args} label=\"Remove\" case=\"sentence\" icon={<Icon icon=\"close\" />} />\n </Case>\n <Case caption=\"With icon, disabled\">\n <TextAction\n {...args}\n label=\"Remove\"\n case=\"sentence\"\n icon={<Icon icon=\"close\" />}\n isDisabled\n />\n </Case>\n <Case caption=\"Disabled\">\n <TextAction {...args} isDisabled />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/TextAction.stories.tsx"}],"primaryExample":"Default"},{"name":"ToggleGrid","level":"molecules","category":"Selection","keywords":["toggle","grid","multiple","days"],"import":"import { ToggleGrid } from \"@antscorp/ui\";","description":"Choose several compact values in a grid of toggle buttons, such as numbered days or hours.","usage":"Choose several compact values in a grid of toggle buttons, such as numbered days or hours. Use CheckGroup for longer checkbox labels.","bestPractices":[{"guidance":true,"description":"Keep value controlled and accept changes in options order; use span for an option that should fill more tracks."},{"guidance":false,"description":"Do not use the grid for a single exclusive choice or assume toggling preserves click order."}],"anatomy":[{"name":"Group label","required":true,"description":"Names the set through FieldGroup."},{"name":"Toggle buttons","required":true,"description":"One pressed or unpressed button per option, with optional column spans."},{"name":"Error","required":false,"description":"A translated selection problem."}],"accessibility":["label names the group; option labels name the toggle buttons and their pressed states show the pick."],"props":[{"path":"label","type":"string","required":true,"description":"Names the group; read out before each field in it."},{"path":"isDisabled","type":"boolean","required":false,"description":"Disables fields through the unframed fieldset; with `frame=\"card\"`, disable children yourself."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the group as a whole, already in the user's language."},{"path":"options","type":"readonly ToggleGridOption<string>[]","required":true,"description":"The buttons, in order."},{"path":"options[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What the picked list holds for this button."},{"path":"options[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The button's text."},{"path":"options[].span","type":"number | undefined","required":false,"description":"How many columns the button spans; one by default, e.g. to let a last option fill a row."},{"path":"columns","type":"number | undefined","required":false,"description":"How many 40px columns the grid has; one column of Astryx's default without it."},{"path":"value","type":"readonly string[]","required":true,"description":"The picked values."},{"path":"onChange","type":"(value: string[]) => void","required":true,"description":"Called with the picked values, in the order of `options`."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The buttons' size, `md` by default."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows the value, not dimmed, and ignores changes."}],"examples":[{"name":"Default","description":"Some buttons on, with \"End of month\" spanning the last row.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"},{"name":"NoneSelected","description":"None picked.","source":"export const NoneSelected: Story = { args: { value: [] } };","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"},{"name":"AllSelected","description":"Every button on.","source":"export const AllSelected: Story = { args: { value: days.map((day) => day.value) } };","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"},{"name":"Interactive","description":"Clicking toggles buttons; the pick stays in the order of the options.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<string[]>([], (value, set) => (\n <ToggleGrid\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"},{"name":"WithError","description":"With a problem with the pick.","source":"export const WithError: Story = { args: { value: [], error: \"Pick at least one day.\" } };","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"},{"name":"Disabled","description":"Read-only.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"},{"name":"ReadOnly","description":"Shows the value, not dimmed, and blocks changes.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"},{"name":"Medium","description":"The `md` size, for a form whose other controls are `md`.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"},{"name":"SingleOption","description":"Just a few options (a single option, in four columns).","source":"export const SingleOption: Story = { args: { options: days.slice(0, 1), columns: 4, value: [] } };","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"},{"name":"LongContent","description":"A long group label and long option text.","source":"export const LongContent: Story = {\n args: {\n label: \"The days of the month on which the message should go out to everyone\",\n options: [\n { value: \"a\", label: \"First working day\", span: 3 },\n { value: \"b\", label: \"Last working day\", span: 3 },\n ],\n columns: 6,\n value: [\"a\"],\n },\n};","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame, where seven 40px columns just fit.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <ToggleGrid {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Some selected\">\n <ToggleGrid {...args} />\n </Case>\n <Case caption=\"None selected, error\">\n <ToggleGrid {...args} value={[]} error=\"Pick at least one day.\" />\n </Case>\n <Case caption=\"Disabled\">\n <ToggleGrid {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <ToggleGrid {...args} isReadOnly />\n </Case>\n <Case caption=\"Medium\">\n <ToggleGrid {...args} size=\"md\" />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/ToggleGrid.stories.tsx"}],"primaryExample":"Default"},{"name":"TokenPickerDialog","level":"organisms","category":"Overlays","keywords":["token","picker","definition","dialog"],"import":"import { TokenPickerDialog } from \"@antscorp/ui\";","description":"Compose a token definition from a group, a leaf, a display name and fallback text.","usage":"Compose a token definition from a group, a leaf, a display name and fallback text. Use TokenTextField to edit the resulting raw token string inline.","bestPractices":[{"guidance":true,"description":"Keep the definition controlled and reset its draft and items when onGroupChange selects a different group."},{"guidance":false,"description":"Do not expect confirmation while loading, without a picked item or with a blank display name; read-only mode hides confirmation."}],"anatomy":[{"name":"Dialog heading","required":true,"description":"The supplied title."},{"name":"Group and item selectors","required":true,"description":"A group choice and a searchable flat or two-level leaf choice, with a loading skeleton when needed."},{"name":"Fallback text","required":true,"description":"The controlled defaultValue field."},{"name":"Display name","required":true,"description":"A required name, defaulted from the picked leaf until customized."},{"name":"Actions","required":true,"description":"Cancel and, when editable, Insert or Save according to mode."}],"accessibility":["Provide the translated group, item, displayName and defaultValue field labels; the dialog uses Astryx Dialog behavior."],"props":[{"path":"isOpen","type":"boolean","required":true,"description":"Whether the dialog is open."},{"path":"onClose","type":"() => void","required":true,"description":"Closes or cancels the dialog."},{"path":"title","type":"string","required":true,"description":"Dialog heading."},{"path":"groups","type":"readonly { readonly value: string; readonly label: string; }[]","required":true,"description":"Available groups."},{"path":"groups[].value","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"groups[].label","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"group","type":"string | null","required":true,"description":"Selected group key."},{"path":"onGroupChange","type":"(value: string) => void","required":true,"description":"Changes the group; the host supplies its items and resets its draft."},{"path":"items","type":"readonly TreeItem[]","required":true,"description":"Flat leaves or two-level groups of leaves."},{"path":"items[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What is picked with this leaf; unique across the tree."},{"path":"items[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The row's text."},{"path":"items[].isDisabled","type":"boolean","required":false,"description":"Shown dimmed; cannot be picked, nor opened when a group."},{"path":"items[].children","type":"readonly TreeItem[] | undefined","required":false,"description":"Makes the item a group, even when empty: it opens and closes and is never picked. Two levels\nonly: the leaves' own `children` are not shown."},{"path":"itemKey","type":"(item: TreeItem) => string","required":true,"description":"Extracts a leaf's stable key, matching its value."},{"path":"isLoading","type":"boolean","required":false,"description":"Items are loading; a timed skeleton replaces the item control."},{"path":"value","type":"TokenPickerValue","required":true,"description":"The controlled definition draft."},{"path":"value.item","type":"string | null","required":false,"requiredWhenParent":true,"description":"Picked leaf key, or null before a pick."},{"path":"value.displayName","type":"string","required":false,"requiredWhenParent":true,"description":"The editable visible name."},{"path":"value.defaultValue","type":"string","required":false,"requiredWhenParent":true,"description":"Fallback text supplied to the host."},{"path":"onChange","type":"(value: TokenPickerValue) => void","required":true,"description":"Changes any part of the definition draft."},{"path":"onConfirm","type":"() => void","required":true,"description":"Confirms a complete definition."},{"path":"mode","type":"\"add\" | \"edit\"","required":true,"description":"Add uses Insert; edit uses Save and the supplied prefilled draft."},{"path":"size","type":"\"md\" | \"sm\"","required":false,"description":"Size of the dialog's controls."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims and blocks all editing controls."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows the draft without editing or confirmation affordances."},{"path":"labels","type":"{ readonly search: string; readonly noMatch: string; readonly clear: string; readonly value?: string | undefined; readonly loadMoreError?: string | undefined; readonly retry?: string | undefined; readonly loading?: string | undefined; } & { readonly group: string; readonly item: string; readonly displayName: string; readonly defaultValue: string; readonly placeholder: string; readonly insert: string; readonly save: string; readonly cancel: string; }","required":true,"description":"Translated field, search and action labels."}],"examples":[{"name":"Default","description":"Groups and an empty definition draft.","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/TokenPickerDialog.stories.tsx"},{"name":"Interactive","description":"Controlled picks, display name and fallback text.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(args.value, (value, set) => (\n <TokenPickerDialog {...args} value={value} onChange={set} />\n )),\n};","file":"packages/ui/src/organisms/TokenPickerDialog.stories.tsx"},{"name":"Search","description":"Search a two-level item tree.","source":"export const Search: Story = {};","file":"packages/ui/src/organisms/TokenPickerDialog.stories.tsx"},{"name":"Empty","description":"No items available.","source":"export const Empty: Story = { args: { items: [] } };","file":"packages/ui/src/organisms/TokenPickerDialog.stories.tsx"},{"name":"Loading","description":"Skeleton while the items load.","source":"export const Loading: Story = { args: { isLoading: true } };","file":"packages/ui/src/organisms/TokenPickerDialog.stories.tsx"},{"name":"EditMode","description":"Edit keeps the supplied name and default value.","source":"export const EditMode: Story = {\n args: { mode: \"edit\", value: { item: \"name\", displayName: \"Recipient\", defaultValue: \"friend\" } },\n};","file":"packages/ui/src/organisms/TokenPickerDialog.stories.tsx"},{"name":"Disabled","description":"All editing controls are disabled.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/organisms/TokenPickerDialog.stories.tsx"},{"name":"ReadOnly","description":"Read-only draft without Save.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/organisms/TokenPickerDialog.stories.tsx"},{"name":"LongContent","description":"Long labels in the group and item controls.","source":"export const LongContent: Story = {\n args: {\n items: [\n {\n value: \"long\",\n label: \"A very long item name that should fit the control and remain readable\",\n },\n ],\n },\n};","file":"packages/ui/src/organisms/TokenPickerDialog.stories.tsx"},{"name":"InNarrowFrame","description":"A real narrow viewport exercises the modal's responsive columns.","source":"export const InNarrowFrame: Story = {\n render: (_args, { id }) => (\n // A raw iframe gives top-layer modal content its own viewport.\n <iframe\n title=\"Token picker at 280px\"\n width={280}\n height={600}\n src={`./iframe.html?id=${id.split(\"--\")[0]}--edit-mode&viewMode=story`}\n />\n ),\n};","file":"packages/ui/src/organisms/TokenPickerDialog.stories.tsx"},{"name":"AllStates","description":"Principal states, each with its own viewport and focus.","source":"export const AllStates: Story = {\n render: (_args, { id }) => (\n <Gallery>\n {[\"default\", \"empty\", \"loading\", \"edit-mode\", \"disabled\", \"read-only\", \"long-content\"].map(\n (story) => (\n <Case key={story} caption={story}>\n {/* A raw iframe isolates each modal's top layer from the other previews. */}\n <iframe\n title={story}\n width=\"100%\"\n height={600}\n src={`./iframe.html?id=${id.split(\"--\")[0]}--${story}&viewMode=story`}\n />\n </Case>\n ),\n )}\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/TokenPickerDialog.stories.tsx"}],"primaryExample":"Default"},{"name":"TokenTextField","level":"molecules","category":"Inputs","keywords":["text","tokens","editor","draft"],"import":"import { TokenTextField } from \"@antscorp/ui\";","description":"Edit a controlled string containing atomic token pills.","usage":"Edit a controlled string containing atomic token pills. pattern returns a fresh global regular expression matching whole tokens and describe supplies their appearance. Local edits commit on blur or single-line Enter; an outside value replaces the draft.","bestPractices":[{"guidance":true,"description":"Keep raw tokens in the string and use onDraftChange only for draft feedback such as a live length counter."},{"guidance":false,"description":"Do not return a pattern that matches empty strings or treat the visible token label as the stored value."}],"anatomy":[{"name":"Field label","required":true,"description":"Names the editable string."},{"name":"Editor","required":true,"description":"Text and atomic tokens; loads separately with a skeleton."},{"name":"Add button","required":false,"description":"Opens the host's picker through onAddClick."}],"accessibility":["Provide label and translated labels.add, labels.remove and labels.loading.","Use isReadOnly to hide editing affordances while keeping the value readable."],"related":[{"name":"CommitTextField","when":"Edit ordinary text without atomic token pills."}],"props":[{"path":"label","type":"string","required":true,"description":"Label text for the field (always rendered for accessibility)."},{"path":"isLabelHidden","type":"boolean","required":false,"default":"false","description":"Whether to visually hide the label and description (still accessible to screen readers)."},{"path":"description","type":"string | undefined","required":false,"description":"Description text displayed between the label and input.\nHidden when isLabelHidden is true."},{"path":"isRequired","type":"boolean","required":false,"default":"false","description":"Whether the field is required. Mutually exclusive with isOptional."},{"path":"isDisabled","type":"boolean","required":false,"default":"false","description":"Whether the associated input is disabled."},{"path":"value","type":"string","required":true,"description":"The committed string; outside changes replace the local draft."},{"path":"onChange","type":"(value: string) => void","required":true,"description":"Commits the local draft on blur, or Enter in a single line."},{"path":"onDraftChange","type":"(value: string) => void","required":false,"description":"Observes local edits without committing them, e.g. for a live character counter."},{"path":"pattern","type":"() => RegExp","required":true,"description":"Returns a fresh global pattern matching whole tokens, without empty matches."},{"path":"describe","type":"(raw: string) => TokenLook","required":true,"description":"Describes a matched string without changing it."},{"path":"isMultiline","type":"boolean","required":false,"description":"Multiple lines; Enter inserts a line instead of committing."},{"path":"rows","type":"number","required":false,"description":"Minimum visible rows for multiple lines, one by default: the field grows as lines are added."},{"path":"maxRows","type":"number","required":false,"description":"Most visible rows for multiple lines before the text scrolls, ten by default."},{"path":"maxLength","type":"number","required":false,"description":"Fits insertions to this length (one per token); existing over-limit text may shrink."},{"path":"placeholder","type":"string","required":false,"description":"Text shown while empty."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Blocks edits without dimming and hides editing affordances."},{"path":"error","type":"string","required":false,"description":"A translated validation problem."},{"path":"size","type":"\"md\" | \"sm\"","required":false,"description":"Control height, medium by default."},{"path":"onTokenClick","type":"(raw: string, replace: (text: string) => void) => void","required":false,"description":"Opens a picker; its callback replaces this token even after focus moves."},{"path":"onAddClick","type":"(insert: (text: string) => void) => void","required":false,"description":"Opens a picker; its callback inserts at the retained selection."},{"path":"onCopy","type":"(text: string) => Record<string, string>","required":false,"description":"Clipboard representations of the selected raw text; plain text by default."},{"path":"onPaste","type":"(data: DataTransfer, insert: (text: string) => void) => boolean","required":false,"description":"Returns true when the host handled this paste through insert."},{"path":"labels","type":"{ readonly add: string; readonly remove: (label: string) => string; readonly loading: string; }","required":true,"description":"Names of the field's editing and loading affordances."},{"path":"labels.add","type":"string","required":false,"requiredWhenParent":true,"description":"Add button's accessible name."},{"path":"labels.remove","type":"(label: string) => string","required":false,"requiredWhenParent":true,"description":"Remove button's accessible name, derived from the token label."},{"path":"labels.loading","type":"string","required":false,"requiredWhenParent":true,"description":"Loading status's accessible name."},{"path":"icons","type":"{ readonly add: IconProps[\"icon\"]; }","required":true,"description":"The host's add glyph."},{"path":"icons.add","type":"`${string}:${string}` | IconName | IconType","required":false,"requiredWhenParent":true,"description":""}],"examples":[{"name":"Default","description":"Filled with a token; typing commits on blur or Enter.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"Empty","description":"Empty field with a hint.","source":"export const Empty: Story = { args: { value: \"\", placeholder: \"Enter text\" } };","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"ManyTokens","description":"Many atomic pills on multiple lines.","source":"export const ManyTokens: Story = {\n args: {\n value: Array.from({ length: 12 }, (_, i) => `#{item ${i}}`).join(\" \"),\n isMultiline: true,\n },\n};","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"MaxRows","description":"Many lines: the field grows to `maxRows` (three here), then scrolls with the thin hover bar.","source":"export const MaxRows: Story = {\n args: {\n value: Array.from({ length: 8 }, (_, i) => `Line ${i + 1} #{item ${i}}`).join(\"\\n\"),\n isMultiline: true,\n rows: 2,\n maxRows: 3,\n },\n};","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"LongText","description":"Long ordinary text and a long token label.","source":"export const LongText: Story = {\n args: { value: `${\"Some long text \".repeat(20)}#{A very long label that must be truncated}` },\n};","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"UnknownToken","description":"Unknown token with an error outline.","source":"export const UnknownToken: Story = {\n args: { describe: (raw) => ({ label: raw, tone: \"red\", state: \"unknown\" }) },\n};","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"ReadOnlyToken","description":"An opaque token cannot be opened or removed with its button.","source":"export const ReadOnlyToken: Story = {\n args: { describe: (raw) => ({ label: raw, tone: \"pink\", state: \"readOnly\" }) },\n};","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"Disabled","description":"Disabled editor and add button.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"ReadOnly","description":"Read-only value without editing affordances.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"SingleLine","description":"A single line strips pasted line breaks.","source":"export const SingleLine: Story = {};","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"MaxLengthReached","description":"A token counts as one: this field has reached its limit.","source":"export const MaxLengthReached: Story = { args: { value: \"1234#{name}\", maxLength: 5 } };","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"Error","description":"A translated validation message.","source":"export const Error: Story = { args: { error: \"Check this text.\" } };","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"Required","description":"Required state is reflected by the field indicator and the editor's accessible attributes.","source":"export const Required: Story = { args: { isRequired: true } };","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"Narrow","description":"Drawer-width field.","source":"export const Narrow: Story = {\n render: (args) => (\n <NarrowFrame>\n <TokenTextField {...args} />\n </NarrowFrame>\n ),\n};","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"Interactive","description":"Controlled text and a picker that inserts at the retained caret.","source":"export const Interactive: Story = {\n render: (args) =>\n withState(args.value, (value, setValue) =>\n withState<{ readonly insert: ((text: string) => void) | null }>(\n { insert: null },\n ({ insert }, setPicker) =>\n withState<TokenPickerValue>(\n { item: null, displayName: \"\", defaultValue: \"\" },\n (draft, setDraft) => (\n <>\n <TokenTextField\n {...args}\n value={value}\n onChange={setValue}\n onAddClick={(next) => {\n setPicker({ insert: next });\n }}\n />\n <TokenPickerDialog\n title=\"Pick a token\"\n isOpen={insert !== null}\n onClose={() => {\n setPicker({ insert: null });\n }}\n groups={[{ value: \"profile\", label: \"Profile\" }]}\n group=\"profile\"\n onGroupChange={fn<(value: string) => void>()}\n items={[{ value: \"name\", label: \"Name\" }]}\n itemKey={(item) => item.value}\n value={draft}\n onChange={setDraft}\n mode=\"add\"\n labels={{\n group: \"Group\",\n item: \"Item\",\n displayName: \"Display name\",\n defaultValue: \"Default value\",\n placeholder: \"Select an item\",\n search: \"Search\",\n clear: \"Clear\",\n noMatch: \"No items\",\n insert: \"Insert\",\n save: \"Save\",\n cancel: \"Cancel\",\n }}\n onConfirm={() => {\n insert?.(`#{${draft.displayName}}`);\n setPicker({ insert: null });\n }}\n />\n </>\n ),\n ),\n ),\n ),\n};","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"},{"name":"AllStates","description":"Main states together.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Empty\">\n <TokenTextField {...args} value=\"\" placeholder=\"Enter text\" />\n </Case>\n <Case caption=\"Filled\">\n <TokenTextField {...args} />\n </Case>\n <Case caption=\"Many tokens\">\n <TokenTextField\n {...args}\n isMultiline\n value={Array.from({ length: 12 }, (_, i) => `#{item ${i}}`).join(\" \")}\n />\n </Case>\n <Case caption=\"Long text\">\n <TokenTextField {...args} value={\"Some long text \".repeat(20)} />\n </Case>\n <Case caption=\"Unknown\">\n <TokenTextField\n {...args}\n describe={(raw) => ({ label: raw, tone: \"red\", state: \"unknown\" })}\n />\n </Case>\n <Case caption=\"Read-only token\">\n <TokenTextField\n {...args}\n describe={(raw) => ({ label: raw, tone: \"pink\", state: \"readOnly\" })}\n />\n </Case>\n <Case caption=\"Disabled\">\n <TokenTextField {...args} isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <TokenTextField {...args} isReadOnly />\n </Case>\n <Case caption=\"Length limit\">\n <TokenTextField {...args} value=\"1234#{name}\" maxLength={5} />\n </Case>\n <Case caption=\"Error\">\n <TokenTextField {...args} error=\"Check this text.\" />\n </Case>\n <Case caption=\"Narrow\">\n <NarrowFrame>\n <TokenTextField {...args} />\n </NarrowFrame>\n </Case>\n {tones.map((tone) => (\n <Case key={tone} caption={tone}>\n <TokenTextField {...args} describe={(raw) => ({ ...args.describe(raw), tone })} />\n </Case>\n ))}\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/TokenTextField.stories.tsx"}],"primaryExample":"Default"},{"name":"TransferLayout","level":"molecules","category":"Layout","keywords":["transfer","columns","scroll","slots"],"import":"import { TransferLayout } from \"@antscorp/ui\";","description":"Lay out available and picked content in two independently scrolling columns with fixed headers.","usage":"Lay out available and picked content in two independently scrolling columns with fixed headers. Use TransferPicker when the kit should also manage selection controls and search.","bestPractices":[{"guidance":true,"description":"Name both columns through labels and use startRef when the host observes paging in the start viewport."},{"guidance":false,"description":"Do not expect layout slots to implement filtering, picking or removal; those behaviors belong to the host."}],"anatomy":[{"name":"Start column","required":true,"description":"The start viewport with optional header, fixed title and footer."},{"name":"End column","required":true,"description":"The end viewport with an optional fixed title and header."},{"name":"Header strip","required":false,"description":"Host controls above the viewports; endHeader is omitted with isEndFullHeight."}],"accessibility":["labels.start and labels.end name the two content columns."],"props":[{"path":"start","type":"ReactNode","required":true,"description":"The start column's content, such as the rows to pick from; it scrolls."},{"path":"end","type":"ReactNode","required":true,"description":"The end column's content, such as the picked rows; it scrolls."},{"path":"startHeader","type":"ReactNode","required":false,"description":"The header strip over the start column, such as a search box; it does not scroll."},{"path":"startTitle","type":"ReactNode","required":false,"description":"The heading of the start column's list, such as its name and count; it stays while the list scrolls."},{"path":"endTitle","type":"ReactNode","required":false,"description":"The heading of the end column's list; it stays while the list scrolls."},{"path":"endHeader","type":"ReactNode","required":false,"description":"The header strip over the end column, such as a \"Remove all\"; ignored with `isEndFullHeight`."},{"path":"startFooter","type":"ReactNode","required":false,"description":"Content under the start column only, such as an action and its caption; none without it."},{"path":"isEndFullHeight","type":"boolean","required":false,"description":"The end column runs the frame's full height, over the header strip too (no `endHeader`)."},{"path":"height","type":"number | undefined","required":false,"description":"The columns' height in pixels, under the header strip; 300 by default."},{"path":"startRef","type":"RefObject<HTMLDivElement | null> | undefined","required":false,"description":"The start column's scrolling viewport, to watch its end (`useLoadMore`'s `rootRef`) or\nscroll it back to the top."},{"path":"labels","type":"{ readonly start: string; readonly end: string; }","required":true,"description":"The texts the layout shows; the host translates them."},{"path":"labels.start","type":"string","required":false,"requiredWhenParent":true,"description":"The start column's accessible name, such as \"Cities\"."},{"path":"labels.end","type":"string","required":false,"requiredWhenParent":true,"description":"The end column's accessible name, such as \"Picked cities\"."}],"examples":[{"name":"Default","description":"Options at the start, picks at the end, a header strip over each.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"},{"name":"Empty","description":"Nothing in either column: the frame keeps its height.","source":"export const Empty: Story = { args: { start: null, end: null } };","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"},{"name":"Interactive","description":"Ticking an option adds it to the end column; removing takes it away.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<readonly string[]>([], (picked, set) => (\n <TransferLayout\n {...args}\n start={\n <VStack gap={1.5}>\n {CITIES.map((city) => (\n <CheckRow\n key={city}\n label={city}\n value={picked.includes(city)}\n onChange={(isPicked) => {\n set(isPicked ? [...picked, city] : picked.filter((c) => c !== city));\n }}\n />\n ))}\n </VStack>\n }\n end={\n <VStack gap={1.5}>\n {picked.map((city) => (\n <RemovableRow\n key={city}\n label={city}\n isFramed\n labels={{ remove: `Remove ${city}` }}\n onRemove={() => {\n set(picked.filter((c) => c !== city));\n }}\n />\n ))}\n </VStack>\n }\n />\n )),\n};","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"},{"name":"ManyRows","description":"Many rows: each column scrolls on its own under its fixed header.","source":"export const ManyRows: Story = {\n args: { start: options([], 30), end: pickedRows(CITIES.slice(0, 20)) },\n};","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"},{"name":"WithTitles","description":"Titles over the lists: they stay put while the rows scroll under them.","source":"export const WithTitles: Story = {\n args: {\n startTitle: <Text weight=\"bold\">Cities (30)</Text>,\n endTitle: <Text weight=\"bold\">Picked cities (20)</Text>,\n start: options([], 30),\n end: pickedRows(CITIES.slice(0, 20)),\n },\n};","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"},{"name":"WithFooter","description":"A footer under the start column, with a line at its end.","source":"export const WithFooter: Story = { args: { startFooter: footer } };","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"},{"name":"EndFullHeight","description":"The end column spans the header strip, as when the end needs no header.","source":"export const EndFullHeight: Story = {\n args: { isEndFullHeight: true, end: pickedRows(CITIES.slice(0, 20)) },\n};","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"},{"name":"Loading","description":"Loading: skeletons in the rows' places, the headers real.","source":"export const Loading: Story = {\n args: {\n start: <RowSkeletons variant=\"check\" count={6} labels={{ loading: \"Loading\" }} />,\n end: <RowSkeletons variant=\"framed\" count={2} labels={{ loading: \"Loading\" }} />,\n },\n};","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"},{"name":"Short","description":"A lower frame.","source":"export const Short: Story = { args: { height: 160 } };","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame: the columns share the width evenly.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <TransferLayout {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"},{"name":"InBannerCard","description":"Inside a banner card with a shared header.","source":"export const InBannerCard: Story = {\n render: (args) => (\n <AccentCard title=\"Included items\" variant=\"banner\">\n <TransferLayout {...args} startFooter={footer} />\n </AccentCard>\n ),\n};","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <TransferLayout {...args} />\n </Case>\n <Case caption=\"With footer\">\n <TransferLayout {...args} startFooter={footer} />\n </Case>\n <Case caption=\"End spans header\">\n <TransferLayout {...args} isEndFullHeight />\n </Case>\n <Case caption=\"Empty\">\n <TransferLayout {...args} start={null} end={null} height={120} />\n </Case>\n <Case caption=\"In a banner card\">\n <AccentCard title=\"Included items\" variant=\"banner\">\n <TransferLayout {...args} startFooter={footer} />\n </AccentCard>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/TransferLayout.stories.tsx"}],"primaryExample":"Default"},{"name":"TransferPicker","level":"organisms","category":"Selection","keywords":["transfer","multiple","picked","paging"],"import":"import { TransferPicker } from \"@antscorp/ui\";","description":"Pick several options in two columns, keeping available rows and removable picks visible together.","usage":"Pick several options in two columns, keeping available rows and removable picks visible together. Use ChipListAddPopover when a compact chip list with an Apply draft is more appropriate.","bestPractices":[{"guidance":true,"description":"Supply labels.loadMoreError, labels.retry and onRetryLoadMore together for paging recovery. The error pauses automatic loading; clear it and set isLoadingMore while retrying, then update options and hasMore after success."},{"guidance":false,"description":"Do not treat select-all as only adding visible rows: when picks already exist, it clears the entire selection, including unknown picks."},{"guidance":true,"description":"Supply pickedOptions or pickedLabels for values absent from the loaded options and use onSearchChange when the host performs search."},{"guidance":false,"description":"Do not replace the current pick with only the visible options after search or paging; unknown picks are intentionally retained."}],"anatomy":[{"name":"Available column","required":true,"description":"Search, a title and checkbox rows, with optional details and select-all."},{"name":"Picked column","required":true,"description":"Framed picked rows with optional remove-all and row actions."},{"name":"Loading and paging rows","required":false,"description":"Skeletons or a failed-page retry row that preserve loaded content."},{"name":"Host slots","required":false,"description":"Toolbar, end header and footer content."},{"name":"Error","required":false,"description":"A translated selection problem below the picker."}],"accessibility":["Supply search, clearSearch, picked and remove labels; disabled and read-only states block picking while search remains usable."],"related":[{"name":"ChipListAddPopover","when":"Use compact removable chips and apply checkbox changes as one draft."}],"props":[{"path":"onLoadMore","type":"(() => void) | undefined","required":false,"description":"Called to load the next page; nothing loads without it."},{"path":"hasMore","type":"boolean","required":false,"description":"More pages are left to load."},{"path":"isLoadingMore","type":"boolean","required":false,"description":"A page is loading now, so none is asked for."},{"path":"label","type":"string","required":true,"description":"Names the list of options (the start column)."},{"path":"options","type":"readonly TransferPickerOption<string>[]","required":true,"description":"The options to pick from."},{"path":"options[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What the picked list holds for this option."},{"path":"options[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The option's text."},{"path":"options[].description","type":"string | undefined","required":false,"description":"A second line under the text, such as an id under a name."},{"path":"options[].meta","type":"string | undefined","required":false,"description":"A short value in a column at the end of the row, such as a count; `labels.meta` heads it."},{"path":"options[].metaInfo","type":"string | undefined","required":false,"description":"An explanation of `meta` in an (i) tip beside it, such as why a count is missing."},{"path":"options[].error","type":"string | undefined","required":false,"description":"A problem with the option once picked: its picked row shows it in the error tone."},{"path":"value","type":"readonly string[]","required":true,"description":"The picked values. A value missing from `options` stays, shown by its `pickedLabels` entry, or\nby the value itself without one (a skeleton row instead while `isPickedLoading`)."},{"path":"pickedLabels","type":"Readonly<Partial<Record<string, string>>> | undefined","required":false,"description":"Labels of picked values that `options` may lack, such as those not on a loaded page."},{"path":"pickedOptions","type":"readonly TransferPickerOption<string>[] | undefined","required":false,"description":"What the picked rows show of picked values, before `options` and `pickedLabels`: the details\n(description, meta, error) of picks the options lack, such as those no longer offered."},{"path":"pickedOptions[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What the picked list holds for this option."},{"path":"pickedOptions[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The option's text."},{"path":"pickedOptions[].description","type":"string | undefined","required":false,"description":"A second line under the text, such as an id under a name."},{"path":"pickedOptions[].meta","type":"string | undefined","required":false,"description":"A short value in a column at the end of the row, such as a count; `labels.meta` heads it."},{"path":"pickedOptions[].metaInfo","type":"string | undefined","required":false,"description":"An explanation of `meta` in an (i) tip beside it, such as why a count is missing."},{"path":"pickedOptions[].error","type":"string | undefined","required":false,"description":"A problem with the option once picked: its picked row shows it in the error tone."},{"path":"total","type":"number | undefined","required":false,"description":"How many options there are in all, shown after the list's name, such as \"Cities (120)\"."},{"path":"max","type":"number | undefined","required":false,"description":"The most values that can be picked: at that many, the unpicked boxes are blocked."},{"path":"startToolbar","type":"ReactNode","required":false,"description":"Controls in the start header, above the search, such as a filter of where the options come from."},{"path":"endHeader","type":"ReactNode","required":false,"description":"Controls in the end header, before \"Remove all\", such as how the picks combine."},{"path":"startFooter","type":"ReactNode","required":false,"description":"Content under the start column only, such as an action and its caption; none without it."},{"path":"isEndFullHeight","type":"boolean","required":false,"description":"The end column runs the frame's full height, over the header strip: no end header, its\n`endHeader` and \"Remove all\" at the top of the column instead, beside its title."},{"path":"height","type":"number | undefined","required":false,"description":"The columns' height in pixels, under the header strip; 300 by default."},{"path":"isLoading","type":"boolean","required":false,"description":"The first options are loading: skeleton rows stand where they will be, while the header, the\nsearch and the list's heading stay."},{"path":"isPickedLoading","type":"boolean","required":false,"description":"The picked values are being looked up: those not named yet (in `options`, `pickedOptions` or\n`pickedLabels`) show as skeleton rows in their place, not by the bare value."},{"path":"renderRowActions","type":"((value: string) => ReactNode) | undefined","required":false,"description":"Actions of a value, such as a link to it: shown at the end of its option row and its picked row\nwhile the row is hovered or focused, in place of its meta (with the remove button on a picked\nrow, which then shows only then). None without it."},{"path":"onSearchChange","type":"((search: string) => void) | undefined","required":false,"description":"Called with the search text as it is typed. When given, the search is the host's (a server\nsearch, say): `options` are shown as given, not filtered."},{"path":"onRetryLoadMore","type":"(() => void) | undefined","required":false,"description":"Called by the retry button of `labels.loadMoreError`, to load the failed page again."},{"path":"onChange","type":"(value: string[]) => void","required":true,"description":"Called with the picked values: those of `value` in their order, a newly picked one appended, an unpicked one removed; a value missing from `options` is kept."},{"path":"labels","type":"{ readonly search: string; readonly clearSearch: string; readonly picked: string; readonly noMatch: string; readonly remove: (label: string) => string; readonly loading?: string | undefined; readonly loadMoreError?: string | undefined; readonly retry?: string | undefined; readonly meta?: string | undefined; readonly selectAll?: string | undefined; readonly removeAll?: string | undefined; }","required":true,"description":"The texts the picker shows; the host translates them."},{"path":"labels.search","type":"string","required":false,"requiredWhenParent":true,"description":"The search box's name and placeholder."},{"path":"labels.clearSearch","type":"string","required":false,"requiredWhenParent":true,"description":"The search box's clear button's name, such as \"Clear search\"."},{"path":"labels.picked","type":"string","required":false,"requiredWhenParent":true,"description":"The title of the picked column, such as \"Selected (3)\"."},{"path":"labels.noMatch","type":"string","required":false,"requiredWhenParent":true,"description":"Shown in the options column when the search matches nothing."},{"path":"labels.remove","type":"(label: string) => string","required":false,"requiredWhenParent":true,"description":"The name of an option's remove button, such as \"Remove Hanoi\"."},{"path":"labels.loading","type":"string | undefined","required":false,"description":"The accessible name of the skeleton rows of `isLoading` and `isPickedLoading`, such as \"Loading\"."},{"path":"labels.loadMoreError","type":"string | undefined","required":false,"description":"Why the last `onLoadMore` failed: a row at the end of the list with a retry button\n(`retry`, `onRetryLoadMore`); the loaded options stay, and nothing more loads until it is\nretried."},{"path":"labels.retry","type":"string | undefined","required":false,"description":"The retry button of `loadMoreError`, such as \"Retry\"; the error row needs it."},{"path":"labels.meta","type":"string | undefined","required":false,"description":"Heads the column of the options' `meta`, such as \"Members\"; no heading without it."},{"path":"labels.selectAll","type":"string | undefined","required":false,"description":"The name of a box before the list's name that ticks the shown options (up to `max`) while\nnothing is picked, and clears every pick otherwise; no box without it."},{"path":"labels.removeAll","type":"string | undefined","required":false,"description":"A text action in the end header that clears every pick, such as \"Remove all\"; none without it."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The remove and retry buttons' size, `md` by default; the checkboxes are always `md`, as every checkbox."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the picker; the boxes, the remove buttons and \"Remove all\" are blocked, and nothing is hidden. The search stays usable."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows the pick, not dimmed, and hides the remove buttons and \"Remove all\"; the boxes are blocked."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the pick, already in the user's language, under the picker."}],"examples":[{"name":"Default","description":"Options on the left, one picked on the right.","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"Empty","description":"Nothing picked: the picked column shows only its title.","source":"export const Empty: Story = { args: { value: [] } };","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"Interactive","description":"Searching, ticking and removing change the pick; the picked ones stay in the order they were picked.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<string[]>([], (value, set) => (\n <TransferPicker\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"WithError","description":"A problem with the pick.","source":"export const WithError: Story = { args: { value: [], error: \"Pick at least one city.\" } };","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"Disabled","description":"Dimmed: the boxes and remove buttons are blocked, and nothing is hidden.","source":"export const Disabled: Story = { args: { isDisabled: true } };","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"ReadOnly","description":"Read-only: the remove buttons are hidden and the boxes blocked.","source":"export const ReadOnly: Story = { args: { isReadOnly: true } };","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"Medium","description":"The `md` size: the remove buttons are larger.","source":"export const Medium: Story = { args: { size: \"md\" } };","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"LongNames","description":"Long names: one line each, cut with an ellipsis, the whole name in a tooltip.","source":"export const LongNames: Story = {\n args: { options: long, value: [\"long-1\", \"long-2\"], labels: fullLabels },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"Loading","description":"The first page is loading: skeleton rows stand where the options go; the header, search and heading stay.","source":"export const Loading: Story = {\n args: { options: [], value: [], isLoading: true, total: 0, labels: fullLabels },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"PickedLoading","description":"The picks are being looked up: those not named yet are skeleton rows in their place, under one status.","source":"export const PickedLoading: Story = {\n args: { value: [\"hue\", \"far-1\", \"far-2\"], isPickedLoading: true, labels: fullLabels },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"EndFullHeight","description":"The end column runs the full height, its \"Remove all\" atop it, beside the title; a filter sits above the search.","source":"export const EndFullHeight: Story = {\n args: { isEndFullHeight: true, labels: fullLabels, startToolbar: sourceFilter },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"WithFooter","description":"A footer under the options column only, such as an action to create one.","source":"export const WithFooter: Story = {\n args: { labels: fullLabels, startFooter: footer },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"WithRowActions","description":"Hovering or focusing a row swaps its count for its actions; a picked row shows its remove button then too.","source":"export const WithRowActions: Story = {\n args: { options: detailed, labels: fullLabels, renderRowActions: open },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"Short","description":"A shorter frame: the columns are 200px high.","source":"export const Short: Story = { args: { options: many.slice(0, 20), height: 200 } };","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"SingleOption","description":"A single option.","source":"export const SingleOption: Story = { args: { options: cities.slice(0, 1), value: [] } };","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"ThousandOptions","description":"A thousand options: the search stays quick.","source":"export const ThousandOptions: Story = {\n args: { options: many, value: [\"option-3\", \"option-500\", \"option-999\"] },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"UnknownPicked","description":"A picked value that is not among the options shows by itself.","source":"export const UnknownPicked: Story = { args: { value: [\"hue\", \"gone-id\"] } };","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"PagedAndSearched","description":"Options paged and searched by the host: scrolling near the end loads the next 20, searching starts over; the picked option is not on a loaded page and shows by its `pickedLabels` entry.","source":"export const PagedAndSearched: Story = { render: (args) => <Paged {...args} /> };","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"LoadingMore","description":"The next page is loading: skeleton rows end the list.","source":"export const LoadingMore: Story = {\n args: {\n options: many.slice(0, 20),\n hasMore: true,\n isLoadingMore: true,\n onLoadMore: fn<() => void>(),\n },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"LoadMoreError","description":"A later page failed: the loaded options stay, ended by its error and a Retry button; nothing more loads until it is retried.","source":"export const LoadMoreError: Story = {\n args: {\n options: many.slice(0, 20),\n hasMore: true,\n onLoadMore: fn<() => void>(),\n onRetryLoadMore: fn<() => void>(),\n labels: { ...labels, retry: \"Retry\", loadMoreError: \"Could not load more options.\" },\n },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"Retrying","description":"Retrying a failed page: Retry turns the error row into the loading row.","source":"export const Retrying: Story = {\n args: { options: many.slice(0, 20), hasMore: true, labels: { ...labels, retry: \"Retry\" } },\n render: (args) =>\n withState(false, (isRetrying, set) => (\n <TransferPicker\n {...args}\n isLoadingMore={isRetrying}\n labels={{\n ...args.labels,\n ...(!isRetrying && { loadMoreError: \"Could not load more options.\" }),\n }}\n onRetryLoadMore={() => {\n set(true);\n }}\n />\n )),\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"NoMore","description":"Every page is loaded: scrolling loads nothing more.","source":"export const NoMore: Story = {\n args: { options: many.slice(0, 20), hasMore: false, onLoadMore: fn<() => void>() },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"HostSearchNoMatch","description":"The host searches: the options are shown as given, here none matching.","source":"export const HostSearchNoMatch: Story = {\n args: { options: [], onSearchChange: fn<(search: string) => void>() },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"PickedNotLoaded","description":"A picked value the loaded options lack, named by `pickedLabels`.","source":"export const PickedNotLoaded: Story = {\n args: { value: [\"hue\", \"option-999\"], pickedLabels: { \"option-999\": \"Option number 1000\" } },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"WithDetails","description":"Each option with a second line and a count, the count headed, the total after the list's name; one count is missing, explained by its tip.","source":"export const WithDetails: Story = {\n args: { options: detailed, total: 120, value: [\"hue\", \"da-nang\"], labels: fullLabels },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"AtMax","description":"At the cap of two: the unpicked boxes are blocked until a pick is removed.","source":"export const AtMax: Story = { args: { max: 2, value: [\"hue\", \"hanoi\"] } };","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"PickedWithError","description":"A pick no longer offered shows its problem in the error tone, by its `pickedOptions` entry.","source":"export const PickedWithError: Story = {\n args: { value: [\"hue\", \"gone-id\"], pickedOptions: gone, labels: fullLabels },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"WithHostControls","description":"The host's controls above the search and in the end header, before \"Remove all\"; select all ticks the shown options while nothing is picked and clears every pick otherwise. Everything is live.","source":"export const WithHostControls: Story = {\n args: { options: detailed, total: 6, labels: fullLabels },\n render: (args) =>\n withState<string[]>([\"hue\", \"gone-id\"], (value, set) => (\n <TransferPicker\n {...args}\n startToolbar={sourceFilter}\n endHeader={combine}\n startFooter={footer}\n renderRowActions={open}\n pickedOptions={gone}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"ReadOnlyWithDetails","description":"Every opt-in part read-only: the picks and the host's header stay, not dimmed; Remove all and the remove buttons are hidden.","source":"export const ReadOnlyWithDetails: Story = {\n args: {\n options: detailed,\n total: 6,\n value: [\"hue\", \"gone-id\"],\n pickedOptions: gone,\n labels: fullLabels,\n isReadOnly: true,\n endHeader: combine,\n },\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"InNarrowFrame","description":"In a drawer-width frame.","source":"export const InNarrowFrame: Story = {\n render: (args) => (\n <Narrow>\n <TransferPicker {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"},{"name":"AllStates","description":"The key states one under the other, each as its story above.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n {(\n [\n [\"Some picked\", Default],\n [\"Nothing picked, error\", WithError],\n [\"Disabled\", Disabled],\n [\"Read-only, with details and the host's header\", ReadOnlyWithDetails],\n [\"Medium\", Medium],\n [\"Long names\", LongNames],\n [\"Loading the first page\", Loading],\n [\"Picks being looked up\", PickedLoading],\n [\"End column of the full height, a filter\", EndFullHeight],\n [\"A footer\", WithFooter],\n [\"Row actions\", WithRowActions],\n [\"Loading more\", LoadingMore],\n [\"A later page failed\", LoadMoreError],\n [\"Details, total, select all\", WithDetails],\n [\"A pick with a problem\", PickedWithError],\n [\"At the cap\", AtMax],\n [\"Host search, no match\", HostSearchNoMatch],\n [\"A pick not loaded\", PickedNotLoaded],\n ] satisfies [string, Story][]\n ).map(([caption, story]) => (\n <Case key={caption} caption={caption}>\n <TransferPicker {...args} {...story.args} />\n </Case>\n ))}\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/TransferPicker.stories.tsx"}],"primaryExample":"Default"},{"name":"TreeOptions","level":"molecules","category":"Selection","keywords":["tree","options","leaves","groups"],"import":"import { TreeOptions } from \"@antscorp/ui\";","description":"Pick one leaf from a flat or two-level tree while groups only expand and collapse.","usage":"Pick one leaf from a flat or two-level tree while groups only expand and collapse. Use TreeSelect to put this tree behind a searchable field trigger.","bestPractices":[{"guidance":true,"description":"Give every item a unique value and use isExpandedByDefault at mount when all groups should start open."},{"guidance":false,"description":"Do not supply deeper nesting and expect it to render; only the top level and its children are shown."}],"anatomy":[{"name":"Rows","required":true,"description":"Labels with a leaf selection mark or a group's trailing chevron."},{"name":"Child rows","required":false,"description":"Leaves indented beneath an expanded group."}],"accessibility":["The named tree exposes levels, expansion, selection and disabled states; each parent owns its child group.","Up/Down and Home/End rove enabled rows; Right opens or enters, Left collapses or returns to the parent, and Enter/Space acts. Tab follows focus; typing finds a label."],"props":[{"path":"label","type":"string","required":true,"description":"Names the tree for assistive tech."},{"path":"items","type":"readonly TreeItem[]","required":true,"description":"The rows, in order: leaves and groups of leaves."},{"path":"items[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What is picked with this leaf; unique across the tree."},{"path":"items[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The row's text."},{"path":"items[].isDisabled","type":"boolean","required":false,"description":"Shown dimmed; cannot be picked, nor opened when a group."},{"path":"items[].children","type":"readonly TreeItem[] | undefined","required":false,"description":"Makes the item a group, even when empty: it opens and closes and is never picked. Two levels\nonly: the leaves' own `children` are not shown."},{"path":"value","type":"string | null","required":true,"description":"The picked leaf's value, shown selected; `null` when none."},{"path":"onChange","type":"(value: string) => void","required":true,"description":"Called with a leaf's value when it is clicked, or pressed with Enter or Space."},{"path":"isExpandedByDefault","type":"boolean","required":false,"description":"Every group starts open; without it only the one holding `value`. Read when mounted: remount\n(a new `key`) to apply a change."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The rows' size, `md` by default, as the Selector's option rows."}],"examples":[{"name":"Default","description":"Leaves and closed groups, each group's chevron at the end of its row.","source":"export const Default: Story = {};","file":"packages/ui/src/molecules/TreeOptions.stories.tsx"},{"name":"Empty","description":"No rows.","source":"export const Empty: Story = { args: { items: [] } };","file":"packages/ui/src/molecules/TreeOptions.stories.tsx"},{"name":"Expanded","description":"Every group open (`isExpandedByDefault`); leaves sit one step in.","source":"export const Expanded: Story = { args: { isExpandedByDefault: true } };","file":"packages/ui/src/molecules/TreeOptions.stories.tsx"},{"name":"ChosenChild","description":"A group's leaf picked: shown selected, its group open.","source":"export const ChosenChild: Story = { args: { value: \"order.total\" } };","file":"packages/ui/src/molecules/TreeOptions.stories.tsx"},{"name":"Interactive","description":"Clicking a group opens or closes it; clicking a leaf picks it.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<string | null>(null, (value, set) => (\n <TreeOptions\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/molecules/TreeOptions.stories.tsx"},{"name":"DisabledItems","description":"Disabled rows, at the top and in a group, dimmed.","source":"export const DisabledItems: Story = { args: { isExpandedByDefault: true } };","file":"packages/ui/src/molecules/TreeOptions.stories.tsx"},{"name":"LongLabels","description":"Long labels truncate on one line.","source":"export const LongLabels: Story = {\n args: {\n isExpandedByDefault: true,\n items: [\n {\n value: \"a\",\n label: \"Products viewed during the last session on the mobile application\",\n children: [{ value: \"a.1\", label: \"Name of the product category looked at the longest\" }],\n },\n ],\n },\n decorators: [\n (Story) => (\n <Narrow>\n <Story />\n </Narrow>\n ),\n ],\n};","file":"packages/ui/src/molecules/TreeOptions.stories.tsx"},{"name":"Many","description":"Many rows.","source":"export const Many: Story = { args: { items: many, isExpandedByDefault: true } };","file":"packages/ui/src/molecules/TreeOptions.stories.tsx"},{"name":"Medium","description":"The `md` rows.","source":"export const Medium: Story = { args: { size: \"md\", isExpandedByDefault: true } };","file":"packages/ui/src/molecules/TreeOptions.stories.tsx"},{"name":"InNarrow","description":"In a 280px drawer.","source":"export const InNarrow: Story = {\n args: { value: \"cart.sku\" },\n decorators: [\n (Story) => (\n <Narrow>\n <Story />\n </Narrow>\n ),\n ],\n};","file":"packages/ui/src/molecules/TreeOptions.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Closed groups\">\n <TreeOptions {...args} />\n </Case>\n <Case caption=\"Expanded\">\n <TreeOptions {...args} isExpandedByDefault />\n </Case>\n <Case caption=\"Child picked\">\n <TreeOptions {...args} value=\"order.total\" />\n </Case>\n <Case caption=\"Medium\">\n <TreeOptions {...args} size=\"md\" isExpandedByDefault />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/molecules/TreeOptions.stories.tsx"}],"primaryExample":"Default"},{"name":"TreeSearchPanel","level":"organisms","category":"Selection","keywords":["tree","search","panel","paging"],"import":"import { TreeSearchPanel } from \"@antscorp/ui\";","description":"Compose the search and results panel used inside TreeSelect.","usage":"Compose the search and results panel used inside TreeSelect. Use TreeSelect for a complete trigger and popover around this panel.","bestPractices":[{"guidance":true,"description":"Keep search value and selectedValue controlled, and use isSearchedByHost when items are already filtered remotely."},{"guidance":false,"description":"Do not expect the panel to close a surrounding popover after onPick; its host owns that behavior."}],"anatomy":[{"name":"Search input","required":true,"description":"A controlled query that focuses a frame after mounting."},{"name":"Results","required":true,"description":"TreeOptions, loading rows or a no-match message."},{"name":"Paging row","required":false,"description":"A load-more sentinel, loading placeholders or a retry action."}],"accessibility":["label names the tree; labels names search and clear. TreeOptions owns child groups and handles arrows, Home/End and Enter/Space."],"props":[{"path":"label","type":"string","required":true,"description":"Names the list for assistive tech."},{"path":"items","type":"readonly TreeItem[]","required":true,"description":"The rows, in order: leaves and groups of leaves."},{"path":"items[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What is picked with this leaf; unique across the tree."},{"path":"items[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The row's text."},{"path":"items[].isDisabled","type":"boolean","required":false,"description":"Shown dimmed; cannot be picked, nor opened when a group."},{"path":"items[].children","type":"readonly TreeItem[] | undefined","required":false,"description":"Makes the item a group, even when empty: it opens and closes and is never picked. Two levels\nonly: the leaves' own `children` are not shown."},{"path":"selectedValue","type":"string | null","required":true,"description":"The picked leaf's value, marked in the list; `null` when none is."},{"path":"labels","type":"{ readonly search: string; readonly noMatch: string; readonly clear: string; readonly value?: string | undefined; readonly loadMoreError?: string | undefined; readonly retry?: string | undefined; readonly loading?: string | undefined; }","required":true,"description":"The texts the panel shows, as `TreeSelect`'s; the host translates them."},{"path":"labels.search","type":"string","required":false,"requiredWhenParent":true,"description":"The search box's name and placeholder, such as \"Search\"."},{"path":"labels.noMatch","type":"string","required":false,"requiredWhenParent":true,"description":"Shown in the popover when the search (or the data) leaves no row."},{"path":"labels.clear","type":"string","required":false,"requiredWhenParent":true,"description":"The search box's clear button, such as \"Clear search\"; it shows only an icon."},{"path":"labels.value","type":"string | undefined","required":false,"description":"The trigger's text when `value` is not among `items`: a pick not on a loaded page, a stored label."},{"path":"labels.loadMoreError","type":"string | undefined","required":false,"description":"Why the last `onLoadMore` failed: a row at the end of the list with a retry button\n(`retry`, `onRetryLoadMore`); the loaded items stay, and nothing more loads until it is\nretried."},{"path":"labels.retry","type":"string | undefined","required":false,"description":"The retry button of `loadMoreError`, such as \"Retry\"; the error row needs it."},{"path":"labels.loading","type":"string | undefined","required":false,"description":"The name of the loading status, such as \"Loading\"; used while `isLoading`."},{"path":"size","type":"\"md\" | \"sm\"","required":true,"description":"The rows' size."},{"path":"isExpandedByDefault","type":"boolean","required":true,"description":"Every group opens; without it only the one holding the value, a lone group, or all while searching."},{"path":"onPick","type":"(value: string) => void","required":true,"description":"Called with a leaf's value when it is picked."},{"path":"value","type":"string","required":true,"description":"The search box's text, the panel's controlled value."},{"path":"onChange","type":"(value: string) => void","required":true,"description":"Called with the search text as it is typed."},{"path":"isSearchedByHost","type":"boolean","required":true,"description":"The host searches: `items` are shown as given, not matched against `value`."},{"path":"more","type":"Pick<TreeSelectProps, \"hasMore\" | \"isLoading\" | \"isLoadingMore\" | \"onLoadMore\" | \"onRetryLoadMore\">","required":true,"description":"Whether and how the list loads more, as `TreeSelect`'s props of the same names; `isLoading`\nshows skeleton rows instead of `labels.noMatch` while the list is empty."}],"examples":[{"name":"Default","description":"The search box over the tree, the group holding the value open.","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"},{"name":"Searched","description":"Typed in: only matching rows, every group open.","source":"export const Searched: Story = { args: { value: \"tot\" } };","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"},{"name":"NoMatch","description":"A search that matches nothing.","source":"export const NoMatch: Story = { args: { value: \"zzz\" } };","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"},{"name":"Empty","description":"No rows at all.","source":"export const Empty: Story = { args: { items: [] } };","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"},{"name":"ExpandedByDefault","description":"Every group open.","source":"export const ExpandedByDefault: Story = {\n args: { isExpandedByDefault: true, selectedValue: null },\n};","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"},{"name":"Loading","description":"Loading the first rows: skeleton rows.","source":"export const Loading: Story = { args: { items: [], more: { isLoading: true } } };","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"},{"name":"LoadingMore","description":"More rows loading at the end of the list.","source":"export const LoadingMore: Story = {\n args: { items: MANY, selectedValue: null, more: { hasMore: true, isLoadingMore: true } },\n};","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"},{"name":"LoadMoreError","description":"The next page failed: an error row with Retry.","source":"export const LoadMoreError: Story = {\n args: {\n items: MANY,\n selectedValue: null,\n labels: { ...meta.args.labels, loadMoreError: \"Could not load more\" },\n more: { hasMore: true, onRetryLoadMore: fn<() => void>() },\n },\n};","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"},{"name":"SearchedByHost","description":"The host searches: rows shown as given.","source":"export const SearchedByHost: Story = { args: { value: \"x\", isSearchedByHost: true } };","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"},{"name":"LongLabel","description":"A long label.","source":"export const LongLabel: Story = { args: { items: LONG, selectedValue: \"long\" } };","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"},{"name":"InNarrow","description":"In a drawer-width frame.","source":"export const InNarrow: Story = {\n render: (args) => (\n <Narrow>\n <TreeSearchPanel {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Default\">\n <TreeSearchPanel {...args} />\n </Case>\n <Case caption=\"No match\">\n <TreeSearchPanel {...args} value=\"zzz\" />\n </Case>\n <Case caption=\"Loading\">\n <TreeSearchPanel {...args} items={[]} more={{ isLoading: true }} />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/TreeSearchPanel.stories.tsx"}],"primaryExample":"Default"},{"name":"TreeSelect","level":"organisms","category":"Selection","keywords":["tree","select","search","leaf"],"import":"import { TreeSelect } from \"@antscorp/ui\";","description":"Choose a single leaf through a searchable two-level tree in a popover.","usage":"Choose a single leaf through a searchable two-level tree in a popover. Use Select for a conventional flat or sectioned options list.","bestPractices":[{"guidance":true,"description":"Supply labels.search, labels.noMatch and labels.clear for the panel. For paging recovery, supply labels.loadMoreError, labels.retry and onRetryLoadMore together; clear the error and set isLoadingMore while retrying, then update items and hasMore after success."},{"guidance":true,"description":"Keep item values unique and supply labels.value for a selected leaf that is absent from the loaded items."},{"guidance":false,"description":"Do not expect group rows to become selected values or nesting deeper than two levels to render."}],"anatomy":[{"name":"Trigger","required":true,"description":"The selected label or placeholder, with loading or read-only presentation."},{"name":"Start addon","required":false,"description":"A separate host control sharing the frame, outside the trigger button."},{"name":"Panel","required":false,"description":"Search, a tree, and optional paging or retry rows while the popover is open."},{"name":"Error","required":false,"description":"A translated problem below the trigger."}],"accessibility":["Supply label even when hidden. Up/Down and Home/End rove enabled rows; Right opens or enters a group, Left collapses or returns, and Enter/Space acts. Parents own child groups; Escape closes."],"related":[{"name":"Select","when":"Choose one value from flat options or sections without a tree."}],"props":[{"path":"onLoadMore","type":"(() => void) | undefined","required":false,"description":"Called to load the next page; nothing loads without it."},{"path":"hasMore","type":"boolean","required":false,"description":"More pages are left to load."},{"path":"isLoadingMore","type":"boolean","required":false,"description":"A page is loading now, so none is asked for."},{"path":"label","type":"string","required":true,"description":"Names the control for assistive tech and the popover; shown only when `isLabelHidden` is false."},{"path":"isLabelHidden","type":"boolean","required":false,"description":"Hides the label, as in a rule row; `true` by default. `false` shows it above the trigger, as a form field's."},{"path":"items","type":"readonly TreeItem[]","required":true,"description":"The rows, in order: leaves and groups of leaves."},{"path":"items[].value","type":"string","required":false,"requiredWhenParent":true,"description":"What is picked with this leaf; unique across the tree."},{"path":"items[].label","type":"string","required":false,"requiredWhenParent":true,"description":"The row's text."},{"path":"items[].isDisabled","type":"boolean","required":false,"description":"Shown dimmed; cannot be picked, nor opened when a group."},{"path":"items[].children","type":"readonly TreeItem[] | undefined","required":false,"description":"Makes the item a group, even when empty: it opens and closes and is never picked. Two levels\nonly: the leaves' own `children` are not shown."},{"path":"value","type":"string | null","required":true,"description":"The picked leaf's value; `null`, or a value no leaf has, shows the placeholder (or\n`labels.value`)."},{"path":"onSearchChange","type":"((search: string) => void) | undefined","required":false,"description":"Called with the search text as it is typed, and with \"\" when the popover opens again. When\ngiven, the search is the host's (a server search, say): `items` are shown as given."},{"path":"onRetryLoadMore","type":"(() => void) | undefined","required":false,"description":"Called by the retry button of `labels.loadMoreError`, to load the failed page again."},{"path":"onChange","type":"(value: string) => void","required":true,"description":"Called with a leaf's value when it is picked; the popover then closes."},{"path":"placeholder","type":"string | undefined","required":false,"description":"Shown in the trigger while nothing is picked."},{"path":"labels","type":"{ readonly search: string; readonly noMatch: string; readonly clear: string; readonly value?: string | undefined; readonly loadMoreError?: string | undefined; readonly retry?: string | undefined; readonly loading?: string | undefined; }","required":true,"description":"The texts the view shows; the host translates them."},{"path":"labels.search","type":"string","required":false,"requiredWhenParent":true,"description":"The search box's name and placeholder, such as \"Search\"."},{"path":"labels.noMatch","type":"string","required":false,"requiredWhenParent":true,"description":"Shown in the popover when the search (or the data) leaves no row."},{"path":"labels.clear","type":"string","required":false,"requiredWhenParent":true,"description":"The search box's clear button, such as \"Clear search\"; it shows only an icon."},{"path":"labels.value","type":"string | undefined","required":false,"description":"The trigger's text when `value` is not among `items`: a pick not on a loaded page, a stored label."},{"path":"labels.loadMoreError","type":"string | undefined","required":false,"description":"Why the last `onLoadMore` failed: a row at the end of the list with a retry button\n(`retry`, `onRetryLoadMore`); the loaded items stay, and nothing more loads until it is\nretried."},{"path":"labels.retry","type":"string | undefined","required":false,"description":"The retry button of `loadMoreError`, such as \"Retry\"; the error row needs it."},{"path":"labels.loading","type":"string | undefined","required":false,"description":"The name of the loading status, such as \"Loading\"; used while `isLoading`."},{"path":"isExpandedByDefault","type":"boolean","required":false,"description":"Every group opens with the popover; without it only the one holding the value, or a lone group."},{"path":"isLoading","type":"boolean","required":false,"description":"The items are loading: the trigger keeps the label `items` or `labels.value` know (else blank)\nand does not open, busy with a spinner in place of the chevron from 300 ms on, for at least\n300 ms (`useLoadingShown`); an open popover (a host's search) shows skeleton rows instead\nof `noMatch`."},{"path":"width","type":"string | number | undefined","required":false,"description":"The trigger's width, in pixels or any CSS length such as `\"100%\"`; as wide as its content without it."},{"path":"size","type":"\"md\" | \"sm\" | undefined","required":false,"description":"The trigger's size, `md` by default (the theme's 30px control height)."},{"path":"isDisabled","type":"boolean","required":false,"description":"Dims the trigger and blocks it."},{"path":"isReadOnly","type":"boolean","required":false,"description":"Shows the picked label, not dimmed, without the chevron; the popover does not open."},{"path":"error","type":"string | undefined","required":false,"description":"A problem with the pick, already in the user's language, shown under the trigger."},{"path":"startAddon","type":"ReactNode","required":false,"description":"A control of its own at the start of the trigger's frame, such as a `TagSelect`, set off from\nthe trigger by a short vertical line; the frame's underline, hover and status take in both.\nNot inside the trigger button, so it takes its own presses. None without it."}],"examples":[{"name":"Default","description":"Leaves and closed groups; nothing picked, so the placeholder shows.","source":"export const Default: Story = {};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"Empty","description":"No items: the popover says there is nothing to pick.","source":"export const Empty: Story = {\n args: { items: [] },\n play: ({ canvasElement }) => open(canvasElement),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"Flat","description":"Leaves only, one picked.","source":"export const Flat: Story = { args: { items: flat, value: \"email\" } };","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"Interactive","description":"Picking a leaf changes the value and closes the popover; a group only opens and closes.","source":"export const Interactive: Story = {\n render: (args) =>\n withState<string | null>(null, (value, set) => (\n <TreeSelect\n {...args}\n value={value}\n onChange={(next) => {\n args.onChange(next);\n set(next);\n }}\n />\n )),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"GroupsClosed","description":"Open with the groups closed: none holds the value and there is more than one.","source":"export const GroupsClosed: Story = { play: ({ canvasElement }) => open(canvasElement) };","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"OpenByDefault","description":"Open with every group expanded (`isExpandedByDefault`).","source":"export const OpenByDefault: Story = {\n args: { isExpandedByDefault: true },\n play: ({ canvasElement }) => open(canvasElement),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"ChosenChild","description":"A group's child picked: the trigger shows the child, and its group opens with the popover.","source":"export const ChosenChild: Story = {\n args: { value: \"order.total\" },\n play: ({ canvasElement }) => open(canvasElement),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"DisabledItems","description":"Disabled rows, at the top and in a group, show dimmed and cannot be picked.","source":"export const DisabledItems: Story = {\n args: { items: withDisabled, isExpandedByDefault: true },\n play: ({ canvasElement }) => open(canvasElement),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"SearchGroupLabel","description":"A search matching a group's label: the group stays, open, with only the children that match (none).","source":"export const SearchGroupLabel: Story = {\n play: ({ canvasElement }) => open(canvasElement, \"cart\"),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"SearchChildLabel","description":"A search matching a child's label: its group stays, open, with only that child.","source":"export const SearchChildLabel: Story = {\n play: ({ canvasElement }) => open(canvasElement, \"sku\"),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"SearchNoMatch","description":"A search matching nothing: the no-match text.","source":"export const SearchNoMatch: Story = {\n play: ({ canvasElement }) => open(canvasElement, \"zzz\"),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"LongLabels","description":"Long labels: the trigger truncates the picked one.","source":"export const LongLabels: Story = {\n args: { items: long, value: \"b.1\", isExpandedByDefault: true },\n play: ({ canvasElement }) => open(canvasElement),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"Many","description":"Many rows: the tree scrolls under the search box, at most 200px high.","source":"export const Many: Story = {\n args: { items: many, isExpandedByDefault: true },\n play: ({ canvasElement }) => open(canvasElement),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"Loading","description":"The items still loading, the pick's label known: it stays, a spinner where the chevron is; it\ndoes not open. With the label unknown the trigger is blank (see `AllStates`).","source":"export const Loading: Story = { args: { items: grouped, value: \"order.total\", isLoading: true } };","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"PagedAndSearched","description":"Rows paged and searched by the host: scrolling near the end loads 20 more, a search starts over, and the trigger names a pick not on a loaded page (`labels.value`).","source":"export const PagedAndSearched: Story = {\n render: (args) => <Paged {...args} />,\n play: ({ canvasElement }) => open(canvasElement),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"LoadingMore","description":"The next page is loading: skeleton rows end the list.","source":"export const LoadingMore: Story = {\n args: {\n items: pages.slice(0, 20),\n hasMore: true,\n isLoadingMore: true,\n onLoadMore: fn<() => void>(),\n },\n play: ({ canvasElement }) => open(canvasElement),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"LoadMoreError","description":"A later page failed: the loaded rows stay, ended by its error and a Retry button; nothing more loads until it is retried.","source":"export const LoadMoreError: Story = {\n args: {\n ...FAILED,\n labels: { ...FAILED.labels, loadMoreError: \"Could not load more rows.\" },\n onRetryLoadMore: fn<() => void>(),\n },\n play: ({ canvasElement }) => open(canvasElement),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"Retrying","description":"Retrying a failed page: Retry turns the error row into the loading row.","source":"export const Retrying: Story = {\n args: FAILED,\n render: (args) =>\n withState(false, (isRetrying, set) => (\n <TreeSelect\n {...args}\n isLoadingMore={isRetrying}\n labels={isRetrying ? args.labels : { ...args.labels, loadMoreError: \"Failed.\" }}\n onRetryLoadMore={() => {\n set(true);\n }}\n />\n )),\n play: ({ canvasElement }) => open(canvasElement),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"NoMore","description":"Every page is loaded: scrolling loads nothing more.","source":"export const NoMore: Story = {\n args: { items: pages.slice(0, 20), hasMore: false, onLoadMore: fn<() => void>() },\n play: ({ canvasElement }) => open(canvasElement),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"HostSearchNoMatch","description":"The host searches: the rows are shown as given, here none matching the search.","source":"export const HostSearchNoMatch: Story = {\n args: { items: [], onSearchChange: fn<(search: string) => void>() },\n play: ({ canvasElement }) => open(canvasElement, \"zzz\"),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"PickedNotLoaded","description":"The picked value is not among the items: the trigger shows `labels.value`.","source":"export const PickedNotLoaded: Story = {\n args: {\n items: pages.slice(0, 20),\n value: \"row-150\",\n labels: { ...LABELS, value: \"Row 150\" },\n },\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"WithLabel","description":"The label shown above the trigger, as a form field's (`isLabelHidden: false`).","source":"export const WithLabel: Story = { args: { isLabelHidden: false } };","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"WithError","description":"A problem with the pick, under the trigger.","source":"export const WithError: Story = { args: { error: \"Field is required\" } };","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"Disabled","description":"Disabled: dimmed and blocked.","source":"export const Disabled: Story = { args: { value: \"title\", isDisabled: true } };","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"ReadOnly","description":"Read-only: the value, not dimmed, without the chevron; it does not open.","source":"export const ReadOnly: Story = { args: { value: \"cart.price\", isReadOnly: true } };","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"InNarrow","description":"In a 280px drawer, full width.","source":"export const InNarrow: Story = {\n args: { width: \"100%\", value: \"cart.quantity\" },\n render: (args) => (\n <Narrow>\n <TreeSelect {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Placeholder\">\n <TreeSelect {...args} />\n </Case>\n <Case caption=\"Leaf picked\">\n <TreeSelect {...args} items={flat} value=\"email\" />\n </Case>\n <Case caption=\"Child picked\">\n <TreeSelect {...args} value=\"order.total\" />\n </Case>\n <Case caption=\"Long label\">\n <TreeSelect {...args} items={long} value=\"b.1\" />\n </Case>\n <Case caption=\"Loading: label known, then unknown\">\n <TreeSelect {...args} value=\"order.total\" isLoading />\n <TreeSelect {...args} items={[]} value=\"order.total\" isLoading />\n </Case>\n <Case caption=\"Error\">\n <TreeSelect {...args} error=\"Field is required\" />\n </Case>\n <Case caption=\"Pick not on a loaded page\">\n <TreeSelect\n {...args}\n items={pages.slice(0, 20)}\n value=\"row-150\"\n labels={{ ...args.labels, value: \"Row 150\" }}\n />\n </Case>\n <Case caption=\"Disabled\">\n <TreeSelect {...args} value=\"title\" isDisabled />\n </Case>\n <Case caption=\"Read-only\">\n <TreeSelect {...args} value=\"cart.price\" isReadOnly />\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/organisms/TreeSelect.stories.tsx"}],"primaryExample":"Default"},{"name":"TruncatedOption","level":"atoms","category":"Selection","keywords":["option","ellipsis","select","label"],"import":"import { TruncatedOption } from \"@antscorp/ui\";","description":"Render a Selector option with a one-line label and a tooltip when it is cut.","usage":"Render a Selector option with a one-line label and a tooltip when it is cut. Use TruncatedText for plain text outside an option.","bestPractices":[{"guidance":true,"description":"Provide a recognizable option label; an absent label falls back to option.value."},{"guidance":false,"description":"Do not expect this presentation view to own selection or open a list."}],"anatomy":[{"name":"Label","required":true,"description":"The option label, or its value, rendered through TruncatedText."},{"name":"Icon","required":false,"description":"The option's supplied icon."},{"name":"Description","required":false,"description":"Supporting option text rendered by SelectorOption."}],"props":[{"path":"option","type":"SelectorOptionData","required":true,"description":"The Selector option to show."},{"path":"option.value","type":"string","required":false,"requiredWhenParent":true,"description":""},{"path":"option.label","type":"string","required":false,"description":""},{"path":"option.description","type":"ReactNode","required":false,"description":""},{"path":"option.disabled","type":"boolean","required":false,"description":""},{"path":"option.icon","type":"IconType | ReactNode","required":false,"description":""},{"path":"option.action","type":"ReactNode","required":false,"description":"The option's secondary control — an Edit button beside a saved label —\nas one node the caller renders and names; the host only places it. In\n`MultiSelector`, any option carrying one turns the popup into a grid whose\nrows pair the option with its action, reachable by pointer, touch, the\ninline-end arrow, and a screen reader (`spec:AST-058`). `Selector` does\nnot render it yet and warns in development when an option carries one.\nAbsent and `null` mean the same: none."}],"examples":[{"name":"Default","description":"A short label that fits: no ellipsis, no tooltip.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/TruncatedOption.stories.tsx"},{"name":"LongLabel","description":"A long label cut with an ellipsis; hover it for the whole label.","source":"export const LongLabel: Story = {\n args: { option: { value: \"a\", label: LONG } },\n render: (args) => (\n <Narrow>\n <TruncatedOption {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/TruncatedOption.stories.tsx"},{"name":"ValueOnly","description":"No label: the option's value stands for it.","source":"export const ValueOnly: Story = { args: { option: { value: \"view-42\" } } };","file":"packages/ui/src/atoms/TruncatedOption.stories.tsx"},{"name":"WithDescription","description":"With a description under the label.","source":"export const WithDescription: Story = {\n args: { option: { value: \"a\", label: LONG, description: \"Shared by Nhi\" } },\n render: (args) => (\n <Narrow>\n <TruncatedOption {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/TruncatedOption.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: () => (\n <Gallery>\n <Case caption=\"Fits\">\n <TruncatedOption option={{ value: \"a\", label: \"Customer\" }} />\n </Case>\n <Case caption=\"Cut, narrow\">\n <Narrow>\n <TruncatedOption option={{ value: \"a\", label: LONG }} />\n </Narrow>\n </Case>\n <Case caption=\"Value only\">\n <TruncatedOption option={{ value: \"view-42\" }} />\n </Case>\n <Case caption=\"With a description\">\n <Narrow>\n <TruncatedOption option={{ value: \"a\", label: LONG, description: \"Shared by Nhi\" }} />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/TruncatedOption.stories.tsx"}],"primaryExample":"Default"},{"name":"TruncatedText","level":"atoms","category":"Data display","keywords":["text","ellipsis","tooltip","overflow"],"import":"import { TruncatedText } from \"@antscorp/ui\";","description":"Keep a value on one line within the space its parent supplies.","usage":"Keep a value on one line within the space its parent supplies. Astryx Text shows the whole label in a tooltip when the text is cut.","bestPractices":[{"guidance":true,"description":"Give the parent a constrained width so truncation has a useful boundary."},{"guidance":false,"description":"Do not use error tone as the only explanation of a problem; provide the reason nearby."}],"props":[{"path":"label","type":"string","required":true,"description":"The text; shown whole in a tooltip when it does not fit."},{"path":"tone","type":"\"default\" | \"error\" | undefined","required":false,"description":"`error` draws it in the error tone, for what has a problem; `default` by default."}],"examples":[{"name":"Default","description":"A short text that fits: no ellipsis, no tooltip.","source":"export const Default: Story = {};","file":"packages/ui/src/atoms/TruncatedText.stories.tsx"},{"name":"LongText","description":"A long text cut with an ellipsis; hover it for the whole text.","source":"export const LongText: Story = {\n args: { label: LONG },\n render: (args) => (\n <Narrow>\n <TruncatedText {...args} />\n </Narrow>\n ),\n};","file":"packages/ui/src/atoms/TruncatedText.stories.tsx"},{"name":"ErrorTone","description":"The error tone, for what has a problem.","source":"export const ErrorTone: Story = { args: { label: \"Old city\", tone: \"error\" } };","file":"packages/ui/src/atoms/TruncatedText.stories.tsx"},{"name":"Empty","description":"No text: nothing to show.","source":"export const Empty: Story = { args: { label: \"\" } };","file":"packages/ui/src/atoms/TruncatedText.stories.tsx"},{"name":"AllStates","description":"The key states one under the other.","source":"export const AllStates: Story = {\n render: (args) => (\n <Gallery>\n <Case caption=\"Fits\">\n <TruncatedText {...args} />\n </Case>\n <Case caption=\"Cut, narrow\">\n <Narrow>\n <TruncatedText label={LONG} />\n </Narrow>\n </Case>\n <Case caption=\"Error tone\">\n <TruncatedText label=\"Old city\" tone=\"error\" />\n </Case>\n <Case caption=\"Error tone, cut\">\n <Narrow>\n <TruncatedText label={LONG} tone=\"error\" />\n </Narrow>\n </Case>\n </Gallery>\n ),\n};","file":"packages/ui/src/atoms/TruncatedText.stories.tsx"}],"primaryExample":"Default"}]}
|