@agentero/design-system 0.31.4 → 0.31.6

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.
@@ -248,19 +248,19 @@
248
248
  {
249
249
  "id": "components-accordion--default",
250
250
  "name": "Default",
251
- "snippet": "const Default = () => <Accordion.Root type=\"single\" collapsible enclosed={false} disabled={false}>\n <FaqItems />\n</Accordion.Root>;",
251
+ "snippet": "const Default = () => <Accordion.Root type=\"single\" collapsible enclosed={false} disabled={false}>\n {FAQS.map(({ value, question, answer }) => (\n <Accordion.Item key={value} value={value}>\n <Accordion.Trigger>{question}</Accordion.Trigger>\n <Accordion.Content>{answer}</Accordion.Content>\n </Accordion.Item>\n ))}\n</Accordion.Root>;",
252
252
  "description": "Exclusive accordion: one item open at a time, `collapsible` lets it close."
253
253
  },
254
254
  {
255
255
  "id": "components-accordion--multiple",
256
256
  "name": "Multiple",
257
- "snippet": "const Multiple = () => (\n <Accordion.Root type=\"multiple\" defaultValue={['covered', 'payment']}>\n <FaqItems />\n </Accordion.Root>\n);",
257
+ "snippet": "const Multiple = () => (\n <Accordion.Root type=\"multiple\" defaultValue={['covered', 'payment']}>\n {FAQS.map(({ value, question, answer }) => (\n <Accordion.Item key={value} value={value}>\n <Accordion.Trigger>{question}</Accordion.Trigger>\n <Accordion.Content>{answer}</Accordion.Content>\n </Accordion.Item>\n ))}\n </Accordion.Root>\n);",
258
258
  "description": "`type=\"multiple\"` lets several sections stay open at once."
259
259
  },
260
260
  {
261
261
  "id": "components-accordion--enclosed",
262
262
  "name": "Enclosed",
263
- "snippet": "const Enclosed = () => (\n <div className=\"flex flex-col gap-8\">\n <Accordion.Root type=\"multiple\" defaultValue={['covered']}>\n <FaqItems />\n </Accordion.Root>\n <Accordion.Root type=\"multiple\" enclosed defaultValue={['claim']}>\n <FaqItems />\n </Accordion.Root>\n </div>\n);",
263
+ "snippet": "const Enclosed = () => (\n <div className=\"flex flex-col gap-8\">\n <Accordion.Root type=\"multiple\" defaultValue={['covered']}>\n {FAQS.map(({ value, question, answer }) => (\n <Accordion.Item key={value} value={value}>\n <Accordion.Trigger>{question}</Accordion.Trigger>\n <Accordion.Content>{answer}</Accordion.Content>\n </Accordion.Item>\n ))}\n </Accordion.Root>\n <Accordion.Root type=\"multiple\" enclosed defaultValue={['claim']}>\n {FAQS.map(({ value, question, answer }) => (\n <Accordion.Item key={value} value={value}>\n <Accordion.Trigger>{question}</Accordion.Trigger>\n <Accordion.Content>{answer}</Accordion.Content>\n </Accordion.Item>\n ))}\n </Accordion.Root>\n </div>\n);",
264
264
  "description": "`enclosed` wraps the group in a bordered card (default borderless on top)."
265
265
  },
266
266
  {
@@ -278,13 +278,13 @@
278
278
  {
279
279
  "id": "components-accordion--expand-collapse",
280
280
  "name": "Expand Collapse",
281
- "snippet": "const ExpandCollapse = () => <Accordion.Root type=\"single\" collapsible>\n <FaqItems />\n</Accordion.Root>;",
281
+ "snippet": "const ExpandCollapse = () => <Accordion.Root type=\"single\" collapsible>\n {FAQS.map(({ value, question, answer }) => (\n <Accordion.Item key={value} value={value}>\n <Accordion.Trigger>{question}</Accordion.Trigger>\n <Accordion.Content>{answer}</Accordion.Content>\n </Accordion.Item>\n ))}\n</Accordion.Root>;",
282
282
  "description": "`collapsible` lets clicking the open item close it (reach an all-closed state)."
283
283
  },
284
284
  {
285
285
  "id": "components-accordion--non-collapsible",
286
286
  "name": "Non Collapsible",
287
- "snippet": "const NonCollapsible = () => <Accordion.Root type=\"single\" collapsible={false} defaultValue=\"covered\">\n <FaqItems />\n</Accordion.Root>;",
287
+ "snippet": "const NonCollapsible = () => <Accordion.Root type=\"single\" collapsible={false} defaultValue=\"covered\">\n {FAQS.map(({ value, question, answer }) => (\n <Accordion.Item key={value} value={value}>\n <Accordion.Trigger>{question}</Accordion.Trigger>\n <Accordion.Content>{answer}</Accordion.Content>\n </Accordion.Item>\n ))}\n</Accordion.Root>;",
288
288
  "description": "Without `collapsible`, clicking the already-open item keeps it open."
289
289
  }
290
290
  ],
@@ -2001,13 +2001,13 @@
2001
2001
  "```tsx\nimport { Combobox } from '@agentero/design-system/combobox';\n\n<Combobox.Root items={states}>\n <Combobox.Input placeholder=\"Search states\" />\n <Combobox.Content>\n <Combobox.Empty>No matches found</Combobox.Empty>\n <Combobox.List>\n {(state: string) => <Combobox.Item key={state} value={state}>{state}</Combobox.Item>}\n </Combobox.List>\n </Combobox.Content>\n</Combobox.Root>\n```"
2002
2002
  ]
2003
2003
  },
2004
- "description": "Combobox is a text input that filters a list and writes the chosen option back\ninto itself. Focusing the input or typing opens the list; picking a row fills the\ninput and closes it. Built on Base UI, whose Combobox primitive owns the\nfiltering, the keyboard navigation and the `role=\"combobox\"` wiring.",
2004
+ "description": "Combobox is a text input that filters a list and writes the chosen option back\ninto itself. Focusing the input or typing opens the list; picking a row fills the\ninput and closes it. With `multiple` the rows become checkbox-like toggles and the\nlist stays open between picks. Built on Base UI, whose Combobox primitive owns\nthe filtering, the keyboard navigation and the `role=\"combobox\"` wiring.",
2005
2005
  "summary": "Root provider for an input-triggered filterable list",
2006
2006
  "reactComponentMeta": {
2007
2007
  "displayName": "Combobox.Root",
2008
2008
  "exportName": "Combobox",
2009
2009
  "filePath": "/home/runner/work/design-system/design-system/src/combobox/index.ts",
2010
- "description": "Root of a combobox: a text input that filters a list of options and writes the\nchosen one back into itself. With `Input` as the only trigger, the list opens on\nfocus or on the first keystroke.\n\nPass the options as `items`. For a list that comes from the server, set\n`filter={null}`, hand the already-filtered rows to `filteredItems`, and request\nthem from `onInputValueChange`; `Status` then announces the wait.",
2010
+ "description": "Root of a combobox: a text input that filters a list of options and writes the\nchosen one back into itself. With `Input` as the only trigger, the list opens on\nfocus or on the first keystroke.\n\nPass the options as `items`. For a list that comes from the server, set\n`filter={null}`, hand the already-filtered rows to `filteredItems`, and request\nthem from `onInputValueChange`; `Status` then announces the wait.\n\nWith `multiple`, the value is an array and picking a row toggles it. The list\nstays open and keeps the typed query between picks, so several rows can be\nticked from one search; closing the list (Escape, a click outside) still resets\nthe query. Each row then shows whether it is selected — see `Item`. Nothing is\nwritten back into the input: render the selection next to the field.",
2011
2011
  "jsDocTags": {
2012
2012
  "summary": [
2013
2013
  "Root provider for an input-triggered filterable list"
@@ -2785,28 +2785,34 @@
2785
2785
  "snippet": "const NoMatches = () => (\n <Combobox.Root items={STATES}>\n <Combobox.Input placeholder=\"Search states\" aria-label=\"Search states\" />\n <Combobox.Content>\n <Combobox.Empty>No matches found</Combobox.Empty>\n <Combobox.List>\n {(state: string) => (\n <Combobox.Item key={state} value={state}>\n {state}\n </Combobox.Item>\n )}\n </Combobox.List>\n </Combobox.Content>\n </Combobox.Root>\n);",
2786
2786
  "description": "`Empty` stays mounted so screen readers announce the change; it renders its\nchildren only while nothing matches. It needs `items` on `Root` to know that."
2787
2787
  },
2788
+ {
2789
+ "id": "components-combobox--multiple-selection",
2790
+ "name": "Multiple Selection",
2791
+ "snippet": "const MultipleSelection = () => {\n const [selected, setSelected] = useState<string[]>([]);\n\n return (\n <div className=\"flex flex-col gap-4\">\n <Combobox.Root items={STATES} multiple value={selected} onValueChange={setSelected}>\n <Combobox.Input placeholder=\"Assign states\" aria-label=\"Assign states\" />\n <Combobox.Content>\n <Combobox.Empty>No matches found</Combobox.Empty>\n <Combobox.List aria-label=\"States\">\n {(state: string) => (\n <Combobox.Item key={state} value={state}>\n {state}\n </Combobox.Item>\n )}\n </Combobox.List>\n </Combobox.Content>\n </Combobox.Root>\n {selected.length > 0 && (\n <ul aria-label=\"Selected states\" className=\"flex flex-wrap gap-2\">\n {selected.map(state => (\n <li key={state}>\n <Tag size=\"sm\">{state}</Tag>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n};",
2792
+ "description": "Several values from one list, the way Lines of Authority or locations are\nassigned: each row leads with a checkbox-shaped indicator, picking a row toggles\nit, and the list stays open with the query intact so the next pick is one click\naway. The input never shows the selection; the field renders it alongside,\nhere as tags."
2793
+ },
2788
2794
  {
2789
2795
  "id": "components-combobox--async-search",
2790
2796
  "name": "Async Search",
2791
- "snippet": "const AsyncSearch = () => <SuspenseSearch />;",
2797
+ "snippet": "const AsyncSearch = () => {\n const [query, setQuery] = useState('');\n\n return (\n // No `items` and `filter={null}`: the server did the filtering, the child renders what came\n // back, and `isItemEqualToValue` keeps a re-fetched copy counting as the selected one.\n <Combobox.Root\n filter={null}\n itemToStringLabel={(agency: Agency) => agency.name}\n isItemEqualToValue={(a: Agency, b: Agency) => a.id === b.id}\n onInputValueChange={(value, { reason }) => {\n // Picking a row writes its label into the input; that is not a new search.\n if (reason !== 'item-press') setQuery(value);\n }}>\n <Combobox.Input placeholder=\"Search agencies\" aria-label=\"Search agencies\" />\n <Combobox.Content>\n <Suspense fallback={<SkeletonRows />}>\n <AgencyItems query={query} />\n </Suspense>\n </Combobox.Content>\n </Combobox.Root>\n );\n};",
2792
2798
  "description": "Options fetched as you type, the way the apps load data: the rows are a component\nthat suspends on the request, so a `Suspense` boundary inside `Content` shows\nplaceholder rows until they land. Nothing tracks `loading` by hand and no stale\nlist lingers under the wait: the data is read by query, which is also what makes\nout-of-order responses harmless. Drop a `useSuspenseQuery` where `use()` is."
2793
2799
  },
2794
2800
  {
2795
2801
  "id": "components-combobox--async-search-with-status",
2796
2802
  "name": "Async Search With Status",
2797
- "snippet": "const AsyncSearchWithStatus = () => <StatusSearch />;",
2803
+ "snippet": "const AsyncSearchWithStatus = () => {\n const [results, setResults] = useState<Agency[]>([]);\n const [isLoading, setIsLoading] = useState(false);\n // Only the latest request may land; fast typing returns responses out of order.\n const latestRequest = useRef(0);\n\n const onInputValueChange = async (query: string) => {\n const request = ++latestRequest.current;\n\n // The previous rows go before the wait starts: a list that no longer matches what is typed\n // must not sit under the loading state.\n setResults([]);\n setIsLoading(true);\n const agencies = await searchAgencies(query);\n if (request !== latestRequest.current) return;\n\n setResults(agencies);\n setIsLoading(false);\n };\n\n return (\n // `filter={null}` hands filtering to the server; `filteredItems` is what came back.\n <Combobox.Root\n items={results}\n filteredItems={results}\n filter={null}\n itemToStringLabel={(agency: Agency) => agency.name}\n isItemEqualToValue={(a: Agency, b: Agency) => a.id === b.id}\n onInputValueChange={onInputValueChange}>\n <Combobox.Input placeholder=\"Search agencies\" aria-label=\"Search agencies\" />\n <Combobox.Content aria-busy={isLoading || undefined}>\n {/* The rows show the wait; `Status` only announces it. */}\n <Combobox.Status className=\"sr-only\">\n {isLoading ? 'Searching…' : undefined}\n </Combobox.Status>\n {isLoading && <SkeletonRows />}\n <Combobox.Empty>{isLoading ? undefined : 'No agencies found'}</Combobox.Empty>\n <Combobox.List>\n {(agency: Agency) => (\n <Combobox.Item key={agency.id} value={agency}>\n {agency.name}\n </Combobox.Item>\n )}\n </Combobox.List>\n </Combobox.Content>\n </Combobox.Root>\n );\n};",
2798
2804
  "description": "The same search without Suspense, for a consumer that gets a `loading` flag from\nits data layer instead: `filter={null}`, the rows arrive through `filteredItems`,\n`Content` is marked busy and `Status` announces the wait. The list is cleared\nbefore each request and a request that is no longer the latest is dropped."
2799
2805
  },
2800
2806
  {
2801
2807
  "id": "components-combobox--inside-field-text",
2802
2808
  "name": "Inside Field Text",
2803
2809
  "snippet": "const InsideFieldText = () => (\n <FieldText invalid required>\n <Label>Agency</Label>\n <Combobox.Root items={STATES}>\n <Combobox.Input placeholder=\"Search agencies\" />\n <Combobox.Content>\n <Combobox.Empty>No matches found</Combobox.Empty>\n <Combobox.List>\n {(state: string) => (\n <Combobox.Item key={state} value={state}>\n {state}\n </Combobox.Item>\n )}\n </Combobox.List>\n </Combobox.Content>\n </Combobox.Root>\n <Field.Description>The agency this agent will be added to.</Field.Description>\n <Field.Error errors={[{ message: 'Pick an agency from the list' }]} />\n </FieldText>\n);",
2804
- "description": "Dropped into a `FieldText`, the input takes the label, the description and\nthe error from the field with nothing wired by hand: `Combobox.Input` reads the\nsame `InputContext` the plain `Input` does."
2810
+ "description": "Dropped into a `FieldText`, the input takes the label, the description and\nthe error from the field with nothing wired by hand: `Combobox.Input` reads the\nsame `InputContext` the plain `Input` does. It also points `aria-labelledby`\nat the label, so the name survives the list opening: Base UI then hides the\nrest of the page from assistive technology, label included, and a\n`<label for>` alone would stop naming the input."
2805
2811
  },
2806
2812
  {
2807
2813
  "id": "components-combobox--with-leading-icon",
2808
2814
  "name": "With Leading Icon",
2809
- "snippet": "const WithLeadingIcon = () => (\n <Combobox.Root items={STATES}>\n <div className=\"relative\">\n <IconSearch\n aria-hidden\n className=\"pointer-events-none absolute top-1/2 left-3 z-1 size-6 -translate-y-1/2 [&>path]:fill-icon-default-base-tertiary\"\n />\n <Combobox.Input className=\"pl-11\" placeholder=\"Search states\" aria-label=\"Search states\" />\n </div>\n <Combobox.Content>\n <Combobox.Empty>No matches found</Combobox.Empty>\n <Combobox.List>\n {(state: string) => (\n <Combobox.Item key={state} value={state}>\n {state}\n </Combobox.Item>\n )}\n </Combobox.List>\n </Combobox.Content>\n </Combobox.Root>\n);",
2815
+ "snippet": "const WithLeadingIcon = () => (\n <Combobox.Root items={STATES}>\n <div className=\"relative\">\n <IconSearch\n aria-hidden\n className=\"pointer-events-none absolute top-1/2 left-3 -z-hide size-6 -translate-y-1/2 [&>path]:fill-icon-default-base-tertiary\"\n />\n <Combobox.Input className=\"pl-11\" placeholder=\"Search states\" aria-label=\"Search states\" />\n </div>\n <Combobox.Content>\n <Combobox.Empty>No matches found</Combobox.Empty>\n <Combobox.List>\n {(state: string) => (\n <Combobox.Item key={state} value={state}>\n {state}\n </Combobox.Item>\n )}\n </Combobox.List>\n </Combobox.Content>\n </Combobox.Root>\n);",
2810
2816
  "description": "A leading icon is composed around the input, not baked in: not every combobox\nis a search (a FEIN lookup or a template picker are not), so the component\nships no icon of its own. Until `InputGroup` lands in the design system, the\nmarketplace search fields get theirs like this — the icon overlaid, the input\npadded past it."
2811
2817
  },
2812
2818
  {
@@ -2816,7 +2822,7 @@
2816
2822
  "description": "Inside a `Modal`, which is a Radix Dialog. The surface portals to the body, so\nthis checks that the dialog's modal layer neither blocks pointer events on the\nlist nor treats a click in it as a click outside and closes."
2817
2823
  }
2818
2824
  ],
2819
- "import": "import { Button, Combobox, Field, FieldText, IconSearch, Label, Modal, Skeleton } from \"@agentero/design-system\";\nimport { Suspense } from \"react\";"
2825
+ "import": "import {\n Button,\n Combobox,\n Field,\n FieldText,\n IconSearch,\n Label,\n Modal,\n Skeleton,\n Tag,\n} from \"@agentero/design-system\";\nimport { Suspense } from \"react\";"
2820
2826
  },
2821
2827
  "components-command": {
2822
2828
  "id": "components-command",
@@ -2834,13 +2840,13 @@
2834
2840
  "```tsx\n// Filtering handled upstream (async search): let every item through.\n<Command.Root label=\"Search options\" shouldFilter={false}>\n <Command.Input placeholder=\"Search...\" />\n <Command.List>...</Command.List>\n</Command.Root>\n```"
2835
2841
  ]
2836
2842
  },
2837
- "description": "Command is a filterable list built on `cmdk`. Type to narrow the options,\nnavigate with the arrow keys, select with Enter. It carries its own panel\nchrome, so dropping it inside a `Popover.Content` yields a combobox.",
2843
+ "description": "Command is a filterable list built on `cmdk`. Type to narrow the options,\nnavigate with the arrow keys, select with Enter. It carries its own panel\nchrome, so dropping it inside a `Popover.Content` yields a combobox.\n`useCommandState` reads the search text and results of the surrounding\n`Command.Root` from any component rendered inside it.",
2838
2844
  "summary": "Filterable command palette container",
2839
2845
  "reactComponentMeta": {
2840
2846
  "displayName": "Command.Root",
2841
2847
  "exportName": "Command",
2842
2848
  "filePath": "/home/runner/work/design-system/design-system/src/command/index.ts",
2843
- "description": "Surface of a filterable list built on `cmdk`: type to narrow the options,\nnavigate with the arrow keys, select with Enter. Compose `Root` with `Input` /\n`List` / `Empty` / `Item`, plus `Group` when the options need sections.\n\n`Root` owns the search state, the filtering and the keyboard navigation, and\ncarries the panel chrome (border, radius, shadow), so it can be dropped\nstraight into a `Popover.Content` without extra wrapping to yield a combobox.\nReach for `DropdownMenu` instead when the options are actions and there is\nnothing to search.\n\n`label` names the search input — it is the only prop that can, see `Input`.",
2849
+ "description": "Surface of a filterable list built on `cmdk`: type to narrow the options,\nnavigate with the arrow keys, select with Enter. Compose `Root` with `Input` /\n`List` / `Empty` / `Item`, plus `Group` when the options need sections.\n\n`Root` owns the search state, the filtering and the keyboard navigation, and\ncarries the panel chrome (border, radius, shadow), so it can be dropped\nstraight into a `Popover.Content` without extra wrapping to yield a combobox.\nReach for `DropdownMenu` instead when the options are actions and there is\nnothing to search.\n\n`label` names the search input — it is the only prop that can, see `Input`.\nA component rendered inside `Root` reads its search text and results with\n`useCommandState`, exported next to `Command`.",
2844
2850
  "jsDocTags": {
2845
2851
  "summary": [
2846
2852
  "Filterable command palette container"
@@ -3049,6 +3055,12 @@
3049
3055
  "name": "Disabled Item",
3050
3056
  "snippet": "const DisabledItem = () => <Command.Root label=\"Search agencies\">\n <Command.Input placeholder=\"Search...\" />\n <Command.List>\n <Command.Item value=\"pinnacle\" onSelect={handleDisabledSelect}>Pinnacle Shield Insurance\n </Command.Item>\n <Command.Item value=\"horizon\" disabled onSelect={handleDisabledSelect}>Horizon Risk Solutions\n </Command.Item>\n </Command.List>\n <Command.Empty>No results found.</Command.Empty>\n</Command.Root>;",
3051
3057
  "description": "A `disabled` row stays visible and searchable but cannot be chosen. cmdk skips it with the\narrow keys and swallows the click, and the row drops its hover and active styling so the\nlist does not invite the pointer."
3058
+ },
3059
+ {
3060
+ "id": "components-command--reading-state",
3061
+ "name": "Reading State",
3062
+ "snippet": "const ReadingState = () => <Command.Root label=\"Search agencies\">\n <Command.Input placeholder=\"Search agencies...\" />\n <Command.List>\n <Command.Item>Pinnacle Shield Insurance</Command.Item>\n <Command.Item>Horizon Risk Solutions</Command.Item>\n <CreateItem />\n </Command.List>\n</Command.Root>;",
3063
+ "description": "`useCommandState` reads the surrounding `Root` from a component inside it. Here the row\noffering to create an agency appears only when the search text matches no existing one."
3052
3064
  }
3053
3065
  ],
3054
3066
  "import": "import { Command } from \"@agentero/design-system\";"
@@ -5438,7 +5450,7 @@
5438
5450
  {
5439
5451
  "id": "components-fieldtext--controlled-from-context",
5440
5452
  "name": "Controlled From Context",
5441
- "snippet": "const ControlledFromContext = () => <ControlledField />;",
5453
+ "snippet": "const ControlledFromContext = () => {\n const [value, setValue] = useState('');\n const [touched, setTouched] = useState(false);\n\n const errors = touched && !value ? [{ message: 'Required' }] : undefined;\n\n return (\n <FieldText invalid={!!errors} required>\n <ContextExtension\n inputProps={{\n name: 'email',\n value,\n onChange: event => setValue(event.target.value),\n onBlur: () => setTouched(true)\n }}\n errors={errors}>\n <Label>Email</Label>\n <Input type=\"email\" onChange={onOwnChange} />\n <Field.Error />\n </ContextExtension>\n </FieldText>\n );\n};",
5442
5454
  "description": "The contexts are an extension point: a provider nested in the field adds\n`name`, `value`, `onChange` and `onBlur` to `InputContext`, so a bare\n`<Input />` becomes controlled, and the errors to `FieldContext`, so a bare\n`<Field.Error />` renders them — here with `useState` and no form library.\nThe input's own `onChange` still runs, chained after the provider's.",
5443
5455
  "summary": "Input controlled and errors supplied from outside the field"
5444
5456
  }
@@ -5463,7 +5475,7 @@
5463
5475
  "displayName": "Field.Root",
5464
5476
  "exportName": "Field",
5465
5477
  "filePath": "/home/runner/work/design-system/design-system/src/field/index.ts",
5466
- "description": "Wraps one label, control, description and error into a single field and\nwires the accessibility relationships between them. It generates the ids\nand shares them through context: `Label` reads `LabelContext`,\n`Field.Description` and `Field.Error` read `FieldContext` and register\nthemselves so the control's `aria-describedby` lists exactly the messages on\nscreen, and a `Field<X>` such as `FieldText` provides the control's context so\nthe control associates itself too. Nobody passes an `id` by hand.\n\nPresentational and form-library agnostic: pass `invalid` and the error\nmessages from whatever validates the form. Deliberately not a `role=\"group\"`:\none field is not a set of related controls, and an unnamed group only adds\nscreen-reader verbosity. Real groupings belong to a fieldset with a legend.",
5478
+ "description": "Wraps one label, control, description and error into a single field and\nwires the accessibility relationships between them. It generates the ids\nand shares them through context: `Label` reads `LabelContext`,\n`Field.Description` and `Field.Error` read `FieldContext` and register\nthemselves so the control's `aria-describedby` lists exactly the messages on\nscreen, and a `Field<X>` such as `FieldText` provides the control's context so\nthe control associates itself too. Nobody passes an `id` by hand. The label\ngets an id as well (`labelId` in `FieldContext`) for a control that must\nname itself with `aria-labelledby`, as `Combobox.Input` does.\n\nPresentational and form-library agnostic: pass `invalid` and the error\nmessages from whatever validates the form. Deliberately not a `role=\"group\"`:\none field is not a set of related controls, and an unnamed group only adds\nscreen-reader verbosity. Real groupings belong to a fieldset with a legend.",
5467
5479
  "jsDocTags": {
5468
5480
  "summary": [
5469
5481
  "Wraps a label, control, description and error into one field"
@@ -5685,14 +5697,14 @@
5685
5697
  {
5686
5698
  "id": "components-field--horizontal",
5687
5699
  "name": "Horizontal",
5688
- "snippet": "const Horizontal = () => (\n <Field.Group className=\"w-[40rem]\">\n <Field.Root orientation=\"horizontal\" data-testid=\"short\">\n <Label>Full name</Label>\n <DemoInput defaultValue=\"Rafa Moro\" className=\"w-72\" />\n </Field.Root>\n\n <Field.Root orientation=\"horizontal\" data-testid=\"long\">\n <Label>Title</Label>\n <Field.Content className=\"w-72\">\n <DemoInput placeholder=\"Software engineer\" />\n <Field.Description>Shown on your public profile.</Field.Description>\n </Field.Content>\n </Field.Root>\n\n <Field.Root orientation=\"horizontal\" data-testid=\"switch\">\n <Field.Content>\n <Label>Auto-renew</Label>\n <Field.Description>Renews the policy automatically before it expires.</Field.Description>\n </Field.Content>\n <DemoInput type=\"checkbox\" className=\"size-5\" />\n </Field.Root>\n\n <div className=\"w-[20rem]\" data-testid=\"narrow\">\n <Field.Root orientation=\"horizontal\">\n <Label>Email</Label>\n <DemoInput type=\"email\" placeholder=\"you@example.com\" className=\"w-40\" />\n </Field.Root>\n </div>\n </Field.Group>\n);",
5700
+ "snippet": "const Horizontal = () => (\n <Field.Group className=\"w-160\">\n <Field.Root orientation=\"horizontal\" data-testid=\"short\">\n <Label>Full name</Label>\n <DemoInput defaultValue=\"Rafa Moro\" className=\"w-72\" />\n </Field.Root>\n\n <Field.Root orientation=\"horizontal\" data-testid=\"long\">\n <Label>Title</Label>\n <Field.Content className=\"w-72\">\n <DemoInput placeholder=\"Software engineer\" />\n <Field.Description>Shown on your public profile.</Field.Description>\n </Field.Content>\n </Field.Root>\n\n <Field.Root orientation=\"horizontal\" data-testid=\"switch\">\n <Field.Content>\n <Label>Auto-renew</Label>\n <Field.Description>Renews the policy automatically before it expires.</Field.Description>\n </Field.Content>\n <DemoSwitch className=\"self-center\" />\n </Field.Root>\n\n <div className=\"w-[20rem]\" data-testid=\"narrow\">\n <Field.Root orientation=\"horizontal\">\n <Label>Email</Label>\n <DemoInput type=\"email\" placeholder=\"you@example.com\" className=\"w-40\" />\n </Field.Root>\n </div>\n </Field.Group>\n);",
5689
5701
  "description": "`horizontal` is one row at every width. The first child fills the row and\neverything after it keeps its natural width, aligned to the right edge, so\nthe controls of stacked fields line up whatever their labels measure. Wrap a\nlabel with its description, or a control with its messages, in\n`Field.Content`. A control sets its own width: `Input` is `w-full`, so it\ngets one here.",
5690
5702
  "summary": "Horizontal field, one row at every width, controls right-aligned"
5691
5703
  },
5692
5704
  {
5693
5705
  "id": "components-field--responsive",
5694
5706
  "name": "Responsive",
5695
- "snippet": "const Responsive = () => (\n <div className=\"flex flex-col gap-8\">\n <div className=\"w-full max-w-[40rem]\" data-testid=\"wide\">\n <Field.Root orientation=\"responsive\">\n <Label>Email</Label>\n <Field.Content className=\"@md/field:w-72\">\n <DemoInput type=\"email\" placeholder=\"you@example.com\" />\n <Field.Description>\n We only use this to send policy documents, and never to contact you about anything\n else.\n </Field.Description>\n </Field.Content>\n </Field.Root>\n </div>\n\n <div className=\"w-full max-w-[20rem]\" data-testid=\"narrow\">\n <Field.Root orientation=\"responsive\">\n <Label>Email</Label>\n <Field.Content>\n <DemoInput type=\"email\" placeholder=\"you@example.com\" />\n <Field.Description>We only use this to send policy documents.</Field.Description>\n </Field.Content>\n </Field.Root>\n </div>\n </div>\n);",
5707
+ "snippet": "const Responsive = () => (\n <div className=\"flex flex-col gap-8\">\n <div className=\"w-full max-w-160\" data-testid=\"wide\">\n <Field.Root orientation=\"responsive\">\n <Label>Email</Label>\n <Field.Content className=\"@md/field:w-72\">\n <DemoInput type=\"email\" placeholder=\"you@example.com\" />\n <Field.Description>\n We only use this to send policy documents, and never to contact you about anything\n else.\n </Field.Description>\n </Field.Content>\n </Field.Root>\n </div>\n\n <div className=\"w-full max-w-[20rem]\" data-testid=\"narrow\">\n <Field.Root orientation=\"responsive\">\n <Label>Email</Label>\n <Field.Content>\n <DemoInput type=\"email\" placeholder=\"you@example.com\" />\n <Field.Description>We only use this to send policy documents.</Field.Description>\n </Field.Content>\n </Field.Root>\n </div>\n </div>\n);",
5696
5708
  "description": "`responsive` stacks the parts below `28rem` and behaves like `horizontal`\nfrom there: first child fills, the rest keep their width on the right. It\nmeasures the field itself, so it needs no particular wrapper. A width that\nshould only apply once horizontal goes behind `@md/field:`.",
5697
5709
  "summary": "Responsive field that stacks when narrow"
5698
5710
  },
@@ -5720,12 +5732,12 @@
5720
5732
  {
5721
5733
  "id": "components-field--errors-from-context",
5722
5734
  "name": "Errors From Context",
5723
- "snippet": "const ErrorsFromContext = () => <FieldWithSuppliedErrors />;",
5735
+ "snippet": "const ErrorsFromContext = () => {\n const [value, setValue] = useState('');\n const [touched, setTouched] = useState(false);\n\n const errors = touched && !value ? [{ message: 'Required' }] : undefined;\n\n return (\n <Field.Root invalid={!!errors} required>\n <Label>Email</Label>\n <DemoInput\n type=\"email\"\n value={value}\n onChange={event => setValue(event.target.value)}\n onBlur={() => setTouched(true)}\n />\n <ErrorProvider errors={errors}>\n <Field.Error />\n </ErrorProvider>\n </Field.Root>\n );\n};",
5724
5736
  "description": "`FieldContext` is an extension point: re-provided under the root with the\nerrors, a bare `<Field.Error />` renders them — here with `useState` and no\nform library. `InputContext` extends the same way with `value`, `onChange`,\n`onBlur` and `name`; see `FieldText`.",
5725
5737
  "summary": "Errors supplied to a bare Field.Error through context"
5726
5738
  }
5727
5739
  ],
5728
- "import": "import { Field, FieldContext, Label } from \"@agentero/design-system\";"
5740
+ "import": "import { Field, FieldContext, Label, Switch } from \"@agentero/design-system\";"
5729
5741
  },
5730
5742
  "components-formtext": {
5731
5743
  "id": "components-formtext",
@@ -5893,7 +5905,7 @@
5893
5905
  "name": "control",
5894
5906
  "required": false,
5895
5907
  "type": {
5896
- "name": "Control<FieldValues, any, FieldValues>"
5908
+ "name": "Control<FieldValues, any, unknown>"
5897
5909
  },
5898
5910
  "description": "The form's `control`, from `useForm()`. Defaults to the one the\nsurrounding `Form.Root` provides; pass it explicitly to get `name`\ninference without writing the generic, or when the field renders outside\na `Form.Root`. A `control` whose resolver transforms the values is\naccepted as is.",
5899
5911
  "defaultValue": null,
@@ -6081,63 +6093,63 @@
6081
6093
  {
6082
6094
  "id": "components-formtext--default",
6083
6095
  "name": "Default",
6084
- "snippet": "const Default = () => <AgencyNameForm\n name=\"agencyName\"\n label=\"Agency name\"\n orientation=\"vertical\"\n required\n optional={false}\n disabled={false}\n readOnly={false} />;",
6096
+ "snippet": "const Default = () => {\n const methods = useForm<AgencyValues>({ defaultValues: { agencyName: '' } });\n\n return (\n <Form.Root\n methods={methods}\n onSubmit={onSubmit}\n aria-label=\"Agency\"\n className=\"flex flex-col items-start gap-7\">\n <FormText\n rules={{ required: 'Enter the agency name.' }}\n inputProps={{ placeholder: 'Acme Insurance' }}\n label=\"Agency name\"\n orientation=\"vertical\"\n required\n optional={false}\n disabled={false}\n readOnly={false}\n name=\"agencyName\" />\n <Button type=\"submit\">Save</Button>\n </Form.Root>\n );\n};",
6085
6097
  "description": "The field consumers write: a `name`, a `label` and the validation rule.\nSubmitting empty renders the error and marks the input invalid; typing\nclears both and the value reaches `onSubmit`.",
6086
6098
  "summary": "Required text field validating on submit"
6087
6099
  },
6088
6100
  {
6089
6101
  "id": "components-formtext--with-description-and-tooltip",
6090
6102
  "name": "With Description And Tooltip",
6091
- "snippet": "const WithDescriptionAndTooltip = () => <TaxIdForm />;",
6103
+ "snippet": "const WithDescriptionAndTooltip = () => {\n const methods = useForm<TaxValues>({ defaultValues: { taxId: '' } });\n\n return (\n <Form.Root methods={methods} aria-label=\"Tax details\">\n <FormText\n name=\"taxId\"\n label=\"Tax ID\"\n tooltip=\"The EIN the IRS issued to the agency.\"\n description=\"Nine digits, with or without the dash.\"\n inputProps={{ inputMode: 'numeric', autoComplete: 'off' }}\n />\n </Form.Root>\n );\n};",
6092
6104
  "description": "`description` is helper text under the input, announced through\n`aria-describedby`. `tooltip` is an info button beside the label for details\nworth a click; it is a sibling of the `<label>`, so the field's accessible\nname stays the label text.",
6093
6105
  "summary": "Helper text under the input and an info tooltip beside the label"
6094
6106
  },
6095
6107
  {
6096
6108
  "id": "components-formtext--optional",
6097
6109
  "name": "Optional",
6098
- "snippet": "const Optional = () => <PhoneForm />;",
6110
+ "snippet": "const Optional = () => {\n const methods = useForm<PhoneValues>({ defaultValues: { phone: '' } });\n\n return (\n <Form.Root methods={methods} aria-label=\"Contact\">\n <FormText name=\"phone\" label=\"Phone number\" optional inputProps={{ type: 'tel' }} />\n </Form.Root>\n );\n};",
6099
6111
  "description": "The forms mark optional fields rather than required ones: `optional`\nappends a muted suffix to the label and leaves the input without the\n`required` attribute.",
6100
6112
  "summary": "Optional field with the \" (optional)\" label suffix"
6101
6113
  },
6102
6114
  {
6103
6115
  "id": "components-formtext--with-rules",
6104
6116
  "name": "With Rules",
6105
- "snippet": "const WithRules = () => <NpnForm />;",
6117
+ "snippet": "const WithRules = () => {\n const methods = useForm<LicenseValues>({ defaultValues: { agency: { npn: '' } } });\n\n return (\n <Form.Root\n methods={methods}\n onSubmit={onSubmit}\n aria-label=\"License\"\n className=\"flex flex-col items-start gap-7\">\n <FormText<LicenseValues>\n name=\"agency.npn\"\n label=\"NPN\"\n rules={{\n required: 'Enter your NPN.',\n pattern: { value: /^\\d{6,10}$/, message: 'An NPN is 6 to 10 digits.' }\n }}\n inputProps={{ inputMode: 'numeric' }}\n />\n <Button type=\"submit\">Save</Button>\n </Form.Root>\n );\n};",
6106
6118
  "description": "`rules` are react-hook-form's, and `name` accepts nested paths typed against\nthe form's values through the generic. `rules.required` validates but does\nnot mark the field required: that stays an explicit `required` prop.",
6107
6119
  "summary": "Nested path with validation rules, typed through the generic"
6108
6120
  },
6109
6121
  {
6110
6122
  "id": "components-formtext--all-errors",
6111
6123
  "name": "All Errors",
6112
- "snippet": "const AllErrors = () => <PasswordRulesForm />;",
6124
+ "snippet": "const AllErrors = () => {\n const methods = useForm<PasswordValues>({\n defaultValues: { password: '' },\n criteriaMode: 'all'\n });\n\n return (\n <Form.Root\n methods={methods}\n onSubmit={onSubmit}\n aria-label=\"Account security\"\n className=\"flex flex-col items-start gap-7\">\n <FormText\n name=\"password\"\n label=\"Password\"\n required\n inputProps={{ type: 'password' }}\n rules={{\n required: 'Enter a password.',\n validate: {\n uppercase: value => /[A-Z]/.test(value) || 'At least one uppercase letter.',\n number: value => /[0-9]/.test(value) || 'At least one number.',\n length: value => value.length >= 10 || 'At least 10 characters.'\n }\n }}\n />\n <Button type=\"submit\">Save</Button>\n </Form.Root>\n );\n};",
6113
6125
  "description": "A form set to react-hook-form's `criteriaMode: 'all'` collects every rule\nthat failed instead of stopping at the first, and the field lists them all.\nWith the default `'firstError'` a single message is rendered, as in the other\nstories.",
6114
6126
  "summary": "Every failed rule listed under criteriaMode 'all'"
6115
6127
  },
6116
6128
  {
6117
6129
  "id": "components-formtext--input-props-chaining",
6118
6130
  "name": "Input Props Chaining",
6119
- "snippet": "const InputPropsChaining = () => <MobileForm />;",
6131
+ "snippet": "const InputPropsChaining = () => {\n const methods = useForm<PhoneValues>({ defaultValues: { phone: '' } });\n\n return (\n <Form.Root methods={methods} aria-label=\"Mobile\">\n <FormText\n name=\"phone\"\n label=\"Mobile\"\n inputProps={{\n type: 'tel',\n placeholder: '5550000000',\n maxLength: 10,\n size: 'lg',\n onChange: event => onOwnChange(methods.getValues('phone'), event.target.value)\n }}\n />\n </Form.Root>\n );\n};",
6120
6132
  "description": "`inputProps` carries the input's own attributes — `type`, `placeholder`,\n`maxLength`, `size`… — and its handlers. A consumer `onChange` runs after\nreact-hook-form's: by the time it fires, `getValues` already returns the new\nvalue.",
6121
6133
  "summary": "Input attributes and a consumer onChange chained after the form's"
6122
6134
  },
6123
6135
  {
6124
6136
  "id": "components-formtext--horizontal",
6125
6137
  "name": "Horizontal",
6126
- "snippet": "const Horizontal = () => <ProfileForm />;",
6138
+ "snippet": "const Horizontal = () => {\n const methods = useForm<ProfileValues>({\n defaultValues: { fullName: 'Rafa Moro', title: '' }\n });\n\n return (\n <Form.Root methods={methods} aria-label=\"Profile\" className=\"w-[40rem]\">\n <Field.Group>\n <FormText\n name=\"fullName\"\n label=\"Full name\"\n orientation=\"horizontal\"\n inputProps={{ className: 'w-72' }}\n data-testid=\"short\"\n />\n <FormText\n name=\"title\"\n label=\"Title\"\n description=\"Shown on your public profile.\"\n orientation=\"horizontal\"\n inputProps={{ className: 'w-72', placeholder: 'Software engineer' }}\n data-testid=\"long\"\n />\n </Field.Group>\n </Form.Root>\n );\n};",
6127
6139
  "description": "Root props pass through: `orientation=\"horizontal\"` lays the label and the\ncontrol in a row, and `className` on the root or in `inputProps` sizes them.\nThe `Input` is full-width by default, so a settings row gives it a width.\n`responsive` stacks the field below 28rem and rows it from there.",
6128
6140
  "summary": "Horizontal orientation and class names through the root props"
6129
6141
  },
6130
6142
  {
6131
6143
  "id": "components-formtext--disabled",
6132
6144
  "name": "Disabled",
6133
- "snippet": "const Disabled = () => <AccountForm />;",
6145
+ "snippet": "const Disabled = () => {\n const methods = useForm<AccountValues>({\n defaultValues: { agencyId: 'AG-48213', email: 'ops@acme-insurance.com' }\n });\n\n return (\n <Form.Root\n methods={methods}\n onSubmit={onSubmit}\n aria-label=\"Account\"\n className=\"flex flex-col items-start gap-7\">\n <Field.Group>\n <FormText name=\"agencyId\" label=\"Agency ID\" disabled />\n <FormText name=\"email\" label=\"Email\" inputProps={{ type: 'email' }} />\n </Field.Group>\n <Button type=\"submit\">Save</Button>\n </Form.Root>\n );\n};",
6134
6146
  "description": "`disabled` disables the input and marks the field, and the value still\nreaches `onSubmit` — unlike react-hook-form's own `disabled` option, which\ndrops it. Use `readOnly` for a value the user can copy but not edit.",
6135
6147
  "summary": "Disabled field whose value is still submitted"
6136
6148
  },
6137
6149
  {
6138
6150
  "id": "components-formtext--set-focus",
6139
6151
  "name": "Set Focus",
6140
- "snippet": "const SetFocus = () => <SetFocusForm />;",
6152
+ "snippet": "const SetFocus = () => {\n const methods = useForm<AgencyValues & PhoneValues>({\n defaultValues: { agencyName: '', phone: '' }\n });\n\n return (\n <Form.Root\n methods={methods}\n aria-label=\"Agency contact\"\n className=\"flex flex-col items-start gap-7\">\n <Field.Group>\n <FormText name=\"agencyName\" label=\"Agency name\" />\n <FormText name=\"phone\" label=\"Phone number\" inputProps={{ type: 'tel' }} />\n </Field.Group>\n <Button type=\"button\" variant=\"secondary\" onClick={() => methods.setFocus('phone')}>\n Focus phone\n </Button>\n </Form.Root>\n );\n};",
6141
6153
  "description": "react-hook-form's `ref` reaches the `<input>`, so `setFocus` and the focus\non the first invalid field after submit both work with no ref written by\nhand.",
6142
6154
  "summary": "setFocus reaches the input through the merged ref"
6143
6155
  }
@@ -6239,14 +6251,14 @@
6239
6251
  {
6240
6252
  "id": "components-form--default",
6241
6253
  "name": "Default",
6242
- "snippet": "const Default = () => <AgencyForm />;",
6254
+ "snippet": "const Default = () => {\n const methods = useForm<AgencyValues>({ defaultValues: { agencyName: '', email: '' } });\n\n return (\n <Form.Root\n methods={methods}\n onSubmit={onSubmit}\n aria-label=\"Agency profile\"\n className=\"flex flex-col items-start gap-7\">\n <Field.Group>\n <FormText\n name=\"agencyName\"\n label=\"Agency name\"\n required\n rules={{ required: 'Enter the agency name.' }}\n />\n <FormText\n name=\"email\"\n label=\"Email\"\n required\n rules={{ required: 'Enter the agency email.' }}\n inputProps={{ type: 'email', autoComplete: 'email' }}\n />\n </Field.Group>\n <Button type=\"submit\">Save</Button>\n </Form.Root>\n );\n};",
6243
6255
  "description": "A form with two required fields and a submit button. Submitting empty shows\nboth errors, marks both inputs invalid and focuses the first one — proof that\nreact-hook-form's `ref` reached the inputs. Once filled in, `onSubmit`\nreceives the typed values.",
6244
6256
  "summary": "Form.Root with FormText fields validating on submit"
6245
6257
  },
6246
6258
  {
6247
6259
  "id": "components-form--no-validate",
6248
6260
  "name": "No Validate",
6249
- "snippet": "const NoValidate = () => <NicknameForm />;",
6261
+ "snippet": "const NoValidate = () => {\n const methods = useForm<NicknameValues>({ defaultValues: { nickname: '' } });\n\n return (\n <Form.Root\n methods={methods}\n onSubmit={onSubmit}\n aria-label=\"Display name\"\n className=\"flex flex-col items-start gap-7\">\n <FormText name=\"nickname\" label=\"Display name\" required />\n <Button type=\"submit\">Save</Button>\n </Form.Root>\n );\n};",
6250
6262
  "description": "`required` on a field reaches the native attribute, so assistive technology\nannounces it, but the form renders `noValidate`: without `rules`, submitting\nthe field empty is not blocked by the browser and `onSubmit` still runs.\nValidation is react-hook-form's alone.",
6251
6263
  "summary": "Native required never triggers the browser's validation bubble"
6252
6264
  }
@@ -6967,7 +6979,7 @@
6967
6979
  {
6968
6980
  "id": "components-modal--controlled",
6969
6981
  "name": "Controlled",
6970
- "snippet": "const Controlled = () => <ControlledExample />;",
6982
+ "snippet": "const Controlled = () => {\n const [isOpen, setIsOpen] = useState(false);\n\n return (\n <>\n <Button variant=\"secondary\" onClick={() => setIsOpen(true)}>\n Open controlled\n </Button>\n <Modal.Root open={isOpen} onOpenChange={setIsOpen}>\n <Modal.Content>\n <Modal.Title>Controlled modal</Modal.Title>\n <Modal.Body>\n <p>The open state lives in the consumer.</p>\n </Modal.Body>\n <Modal.Footer>\n <Button variant=\"primary\" onClick={() => setIsOpen(false)}>\n Done\n </Button>\n </Modal.Footer>\n </Modal.Content>\n </Modal.Root>\n </>\n );\n};",
6971
6983
  "description": "Controlled with `open`/`onOpenChange` — the dominant pattern in both apps. Also closes on `Escape`."
6972
6984
  },
6973
6985
  {
@@ -7940,7 +7952,7 @@
7940
7952
  {
7941
7953
  "id": "components-table--expandable-rows",
7942
7954
  "name": "Expandable Rows",
7943
- "snippet": "const ExpandableRows = args => {\n const Demo = () => {\n const [openId, setOpenId] = useState<string | null>(ROWS[0]!.id);\n return (\n <Table.Root {...args}>\n <Table.Head>\n <Table.Row>\n <Table.Header>\n <span className=\"sr-only\">Expand</span>\n </Table.Header>\n <Table.Header>Name</Table.Header>\n <Table.Header>Email</Table.Header>\n <Table.Header>Role</Table.Header>\n </Table.Row>\n </Table.Head>\n <Table.Body>\n {ROWS.slice(0, 4).map(row => (\n <Fragment key={row.id}>\n <Table.Row>\n <Table.Cell>\n <Table.ExpandButton\n isExpanded={openId === row.id}\n toggleExpanded={() => setOpenId(openId === row.id ? null : row.id)}\n aria-label={`Toggle ${row.name}`}\n />\n </Table.Cell>\n <Table.Cell className=\"font-bold\">{row.name}</Table.Cell>\n <Table.Cell className=\"text-text-default-base-secondary\">{row.email}</Table.Cell>\n <Table.Cell>{row.role}</Table.Cell>\n </Table.Row>\n {openId === row.id && (\n <Table.ExpandedRow>\n <Table.Cell colSpan={4}>\n <div className=\"py-2 text-text-default-base-secondary\">\n Extended details for {row.name} — {row.amount} lifetime value.\n </div>\n </Table.Cell>\n </Table.ExpandedRow>\n )}\n </Fragment>\n ))}\n </Table.Body>\n </Table.Root>\n );\n };\n return <Demo />;\n};",
7955
+ "snippet": "const ExpandableRows = () => {\n const [openId, setOpenId] = useState<string | null>(ROWS[0]!.id);\n\n return (\n <Table.Root size=\"md\" embed={false}>\n <Table.Head>\n <Table.Row>\n <Table.Header>\n <span className=\"sr-only\">Expand</span>\n </Table.Header>\n <Table.Header>Name</Table.Header>\n <Table.Header>Email</Table.Header>\n <Table.Header>Role</Table.Header>\n </Table.Row>\n </Table.Head>\n <Table.Body>\n {ROWS.slice(0, 4).map(row => (\n <Fragment key={row.id}>\n <Table.Row>\n <Table.Cell>\n <Table.ExpandButton\n isExpanded={openId === row.id}\n toggleExpanded={() => setOpenId(openId === row.id ? null : row.id)}\n aria-label={`Toggle ${row.name}`}\n />\n </Table.Cell>\n <Table.Cell className=\"font-bold\">{row.name}</Table.Cell>\n <Table.Cell className=\"text-text-default-base-secondary\">{row.email}</Table.Cell>\n <Table.Cell>{row.role}</Table.Cell>\n </Table.Row>\n {openId === row.id && (\n <Table.ExpandedRow>\n <Table.Cell colSpan={4}>\n <div className=\"py-2 text-text-default-base-secondary\">\n Extended details for {row.name} — {row.amount} lifetime value.\n </div>\n </Table.Cell>\n </Table.ExpandedRow>\n )}\n </Fragment>\n ))}\n </Table.Body>\n </Table.Root>\n );\n};",
7944
7956
  "description": "`Table.ExpandButton` toggles a `Table.ExpandedRow` detail panel under its row.",
7945
7957
  "summary": "Expandable rows with a detail panel"
7946
7958
  }
@@ -9414,6 +9426,6 @@
9414
9426
  },
9415
9427
  "meta": {
9416
9428
  "docgen": "react-component-meta",
9417
- "durationMs": 2741
9429
+ "durationMs": 3936
9418
9430
  }
9419
9431
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentero/design-system",
3
- "version": "0.31.4",
3
+ "version": "0.31.6",
4
4
  "description": "A React component library built with Tailwind CSS v4 and Radix UI primitives",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -14,8 +14,9 @@ export declare const comboboxRecipe: import('tailwind-variants').TVReturnType<{
14
14
  list?: import('tailwind-merge').ClassNameValue;
15
15
  clear?: import('tailwind-merge').ClassNameValue;
16
16
  item?: import('tailwind-merge').ClassNameValue;
17
- field?: import('tailwind-merge').ClassNameValue;
17
+ indicator?: import('tailwind-merge').ClassNameValue;
18
18
  message?: import('tailwind-merge').ClassNameValue;
19
+ field?: import('tailwind-merge').ClassNameValue;
19
20
  };
20
21
  };
21
22
  } | {
@@ -25,8 +26,9 @@ export declare const comboboxRecipe: import('tailwind-variants').TVReturnType<{
25
26
  list?: import('tailwind-merge').ClassNameValue;
26
27
  clear?: import('tailwind-merge').ClassNameValue;
27
28
  item?: import('tailwind-merge').ClassNameValue;
28
- field?: import('tailwind-merge').ClassNameValue;
29
+ indicator?: import('tailwind-merge').ClassNameValue;
29
30
  message?: import('tailwind-merge').ClassNameValue;
31
+ field?: import('tailwind-merge').ClassNameValue;
30
32
  };
31
33
  };
32
34
  } | {}, {
@@ -35,6 +37,7 @@ export declare const comboboxRecipe: import('tailwind-variants').TVReturnType<{
35
37
  content: string[];
36
38
  list: string[];
37
39
  item: string[];
40
+ indicator: string[];
38
41
  message: string[];
39
42
  }, undefined, {
40
43
  [key: string]: {
@@ -43,8 +46,9 @@ export declare const comboboxRecipe: import('tailwind-variants').TVReturnType<{
43
46
  list?: import('tailwind-merge').ClassNameValue;
44
47
  clear?: import('tailwind-merge').ClassNameValue;
45
48
  item?: import('tailwind-merge').ClassNameValue;
46
- field?: import('tailwind-merge').ClassNameValue;
49
+ indicator?: import('tailwind-merge').ClassNameValue;
47
50
  message?: import('tailwind-merge').ClassNameValue;
51
+ field?: import('tailwind-merge').ClassNameValue;
48
52
  };
49
53
  };
50
54
  } | {}, {
@@ -53,6 +57,7 @@ export declare const comboboxRecipe: import('tailwind-variants').TVReturnType<{
53
57
  content: string[];
54
58
  list: string[];
55
59
  item: string[];
60
+ indicator: string[];
56
61
  message: string[];
57
62
  }, import('tailwind-variants').TVReturnType<unknown, {
58
63
  field: string;
@@ -60,6 +65,7 @@ export declare const comboboxRecipe: import('tailwind-variants').TVReturnType<{
60
65
  content: string[];
61
66
  list: string[];
62
67
  item: string[];
68
+ indicator: string[];
63
69
  message: string[];
64
70
  }, undefined, unknown, unknown, undefined>>;
65
71
  /**
@@ -71,6 +77,12 @@ export declare const comboboxRecipe: import('tailwind-variants').TVReturnType<{
71
77
  * `filter={null}`, hand the already-filtered rows to `filteredItems`, and request
72
78
  * them from `onInputValueChange`; `Status` then announces the wait.
73
79
  *
80
+ * With `multiple`, the value is an array and picking a row toggles it. The list
81
+ * stays open and keeps the typed query between picks, so several rows can be
82
+ * ticked from one search; closing the list (Escape, a click outside) still resets
83
+ * the query. Each row then shows whether it is selected — see `Item`. Nothing is
84
+ * written back into the input: render the selection next to the field.
85
+ *
74
86
  * @summary Root provider for an input-triggered filterable list
75
87
  * @see {@link https://base-ui.com/react/components/combobox|Base UI Combobox}
76
88
  *
@@ -90,7 +102,7 @@ export declare const comboboxRecipe: import('tailwind-variants').TVReturnType<{
90
102
  * ```
91
103
  */
92
104
  export declare const Root: {
93
- <Value, Multiple extends boolean | undefined = false, Item = Value>(props: ComboboxPrimitive.Root.Props<Value, Multiple, Item>): import("react/jsx-runtime").JSX.Element;
105
+ <Value, Multiple extends boolean | undefined = false, Item = Value>({ onOpenChange, ...props }: ComboboxPrimitive.Root.Props<Value, Multiple, Item>): import("react/jsx-runtime").JSX.Element;
94
106
  displayName: string;
95
107
  };
96
108
  type InputProps = Omit<ComponentPropsWithRef<typeof ComboboxPrimitive.Input>, 'size'> & {
@@ -108,9 +120,19 @@ type InputProps = Omit<ComponentPropsWithRef<typeof ComboboxPrimitive.Input>, 's
108
120
  * `InputContext`, so inside a `FieldText` the label, the hint and the error point
109
121
  * at it with nothing passed by hand. Its own props win over the context.
110
122
  *
111
- * It always carries a clear button, which Base UI mounts only once there is
112
- * something to clear, so an untouched field shows nothing. Name it with
113
- * `clearLabel` when a page has more than one combobox.
123
+ * Inside a field it also names itself with `aria-labelledby` pointing at the
124
+ * label, on top of the label's own `for`. While the list is open, Base UI hides
125
+ * everything but the input and the list from assistive technology, label
126
+ * included, and a `<label for>` alone then stops naming the input; a direct
127
+ * reference keeps the name. Outside a field, give it `aria-label` or an
128
+ * `aria-labelledby` of your own.
129
+ *
130
+ * It carries a clear button, which Base UI mounts only once there is something
131
+ * to clear, so an untouched field shows nothing. Name it with `clearLabel` when
132
+ * a page has more than one combobox. Under a `multiple` root there is no clear
133
+ * button: Base UI's would drop the whole selection, while a cross inside a search
134
+ * field reads as "clear what I typed" — and the selection is rendered outside
135
+ * the field, with its own way to remove each entry.
114
136
  *
115
137
  * Pass `render` to swap the element for a richer control — a field with a leading
116
138
  * search icon, for instance — and the combobox wiring rides along.
@@ -163,11 +185,17 @@ type ItemProps = ComponentPropsWithRef<typeof ComboboxPrimitive.Item>;
163
185
  * A selectable row. `value` is what the combobox stores and what
164
186
  * `itemToStringLabel` turns into the text written back into the input.
165
187
  *
188
+ * Under a `multiple` root it leads with a checkbox-shaped indicator that fills
189
+ * while the row is selected, and the row announces `aria-selected` to assistive
190
+ * technology. The indicator is decoration, not a control: clicking anywhere on
191
+ * the row, or pressing Enter on it, is what toggles the selection.
192
+ *
166
193
  * @summary Selectable row inside the list
167
194
  * @dataAttribute {string} data-slot - Always set to "combobox-item"
195
+ * @dataAttribute {string} data-selected - Present while the row is selected
168
196
  */
169
197
  export declare const Item: {
170
- ({ className, ...props }: ItemProps): import("react/jsx-runtime").JSX.Element;
198
+ ({ className, children, ...props }: ItemProps): import("react/jsx-runtime").JSX.Element;
171
199
  displayName: string;
172
200
  };
173
201
  type EmptyProps = ComponentPropsWithRef<typeof ComboboxPrimitive.Empty>;