@agentero/design-system 0.34.0 → 0.34.2

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.
@@ -594,7 +594,7 @@
594
594
  <main>
595
595
  <div class="wrap">
596
596
  <div class="note ok" style="margin-bottom: 16px;">
597
- Using <code>react-component-meta</code>. Generation took <strong>3.8s</strong>.
597
+ Using <code>react-component-meta</code>. Generation took <strong>3.1s</strong>.
598
598
  </div>
599
599
  <h2 class="section-title">Components</h2>
600
600
  <div class="grid" role="list">
@@ -2855,7 +2855,7 @@ import { CheckList } from &#39;@agentero/design-system/check-list&#39;;
2855
2855
  <div class="title">
2856
2856
  <h2><span class="status-dot dot-ok"></span> Combobox.Root</h2>
2857
2857
  <div class="badges">
2858
- <label for="c-7-components-combobox-props" class="badge ok as-toggle">39 prop types</label>
2858
+ <label for="c-7-components-combobox-props" class="badge ok as-toggle">41 prop types</label>
2859
2859
 
2860
2860
  <label for="c-7-components-combobox-stories" class="badge ok as-toggle">10 stories</label>
2861
2861
 
@@ -2900,7 +2900,7 @@ import { Combobox } from &#39;@agentero/design-system/combobox&#39;;
2900
2900
  <div class="note ok">
2901
2901
  <div class="row">
2902
2902
  <span class="ex-name">Prop types <small>(react-component-meta)</small></span>
2903
- <span class="badge ok">39 prop types</span>
2903
+ <span class="badge ok">41 prop types</span>
2904
2904
  </div>
2905
2905
  <pre><code>Component: src/combobox/index.ts::Combobox</code></pre>
2906
2906
  <pre><code>Props:</code></pre>
@@ -3047,6 +3047,28 @@ itemToStringValue?: (itemValue: unknown) =&gt; string
3047
3047
  */
3048
3048
  limit?: number = -1
3049
3049
 
3050
+ /**
3051
+ * Whether the options are being fetched. Once the list has been open and
3052
+ * loading for half a second, `loadingLabel` is announced once: sooner, a
3053
+ * screen reader would cut it off to echo the key just typed, and a faster
3054
+ * answer has no wait to announce. `Content` is marked `aria-busy`, and
3055
+ * `Empty` shows nothing, so &quot;no results&quot; is not read out before the results
3056
+ * arrive. The announcement comes from a visually hidden live region the root
3057
+ * renders next to the field, so it is in place before the list opens. On a search
3058
+ * against an API, pass it from the first render, as `false` while idle: a root
3059
+ * that leaves it out gets no live region. A list filtered in memory never
3060
+ * waits and leaves it out. While loading with no rows yet, the surface shows
3061
+ * whatever placeholder `Content` holds — `Empty` stays quiet.
3062
+ */
3063
+ loading?: boolean
3064
+
3065
+ /**
3066
+ * What a screen reader hears when `loading` turns `true`. Announced only; any
3067
+ * loading visuals — placeholder rows, a spinner — are yours to render inside
3068
+ * `Content`, hidden from assistive technology. Defaults to `&#39;Loading…&#39;`.
3069
+ */
3070
+ loadingLabel?: string = &#39;Loading…&#39;
3071
+
3050
3072
  /**
3051
3073
  * The locale to use for string comparison.
3052
3074
  * Defaults to the user&#39;s runtime locale.
@@ -3340,9 +3362,10 @@ here as tags.</div>
3340
3362
  <div class="hint">Options fetched as you type, the way the apps load data: the rows are a component
3341
3363
  that suspends on the request, so a `Suspense` boundary inside `Content` shows
3342
3364
  placeholder rows until the first results land. The rows read a deferred copy of
3343
- the query, so a new search keeps the previous rows on screen while it loads and
3344
- `Content` is marked busy until they are replaced; the placeholders never come
3345
- back. Nothing tracks `loading` by hand, and reading the data by query is what
3365
+ the query, so a new search keeps the previous rows on screen while it loads, and
3366
+ passing that staleness as `loading` announces the wait and marks `Content` busy
3367
+ until they are replaced; the placeholders never come back. Nothing tracks a
3368
+ request by hand, and reading the data by query is what
3346
3369
  makes out-of-order responses harmless. With react-query, drop a
3347
3370
  `useSuspenseQuery` where `use()` is, or use `placeholderData: keepPreviousData`
3348
3371
  with a plain `useQuery` for the same behaviour.</div>
@@ -3360,12 +3383,14 @@ with a plain `useQuery` for the same behaviour.</div>
3360
3383
  filter={null}
3361
3384
  itemToStringLabel={(agency: Agency) =&gt; agency.name}
3362
3385
  isItemEqualToValue={(a: Agency, b: Agency) =&gt; a.id === b.id}
3386
+ loading={isStale}
3387
+ loadingLabel=&quot;Searching agencies…&quot;
3363
3388
  onInputValueChange={(value, { reason }) =&gt; {
3364
3389
  // Picking a row writes its label into the input; that is not a new search.
3365
3390
  if (reason !== &#39;item-press&#39;) setQuery(value);
3366
3391
  }}&gt;
3367
3392
  &lt;Combobox.Input placeholder=&quot;Search agencies&quot; aria-label=&quot;Search agencies&quot; /&gt;
3368
- &lt;Combobox.Content aria-busy={isStale || undefined}&gt;
3393
+ &lt;Combobox.Content&gt;
3369
3394
  &lt;Suspense fallback={&lt;SkeletonRows /&gt;}&gt;
3370
3395
  &lt;AgencyItems query={deferredQuery} /&gt;
3371
3396
  &lt;/Suspense&gt;
@@ -3376,21 +3401,24 @@ with a plain `useQuery` for the same behaviour.</div>
3376
3401
  </div>
3377
3402
  <div class="note ok">
3378
3403
  <div class="row">
3379
- <span class="ex-name">Async Search With Status</span>
3404
+ <span class="ex-name">Async Search With Loading</span>
3380
3405
  <span class="badge ok">story ok</span>
3381
3406
  </div>
3382
- <div>API search that waits for two characters, with loading status</div>
3407
+ <div>API search that waits for two characters and announces its loading state</div>
3383
3408
  <div class="hint">The same search without Suspense, for a consumer that gets a `loading` flag from
3384
3409
  its data layer instead: `filter={null}`, the rows arrive through `filteredItems`,
3385
- `Content` is marked busy and `Status` announces the wait. Placeholder rows show
3386
- only while there is nothing to show yet; after that, the previous rows stay
3387
- until the new ones replace them. A request that is no longer the latest is
3388
- dropped.
3410
+ and the flag goes to `Root` as `loading`. That is all the wiring the wait needs:
3411
+ `Content` is marked busy, `loadingLabel` is announced once when the request
3412
+ starts, and `Empty` holds back &quot;no results&quot; until the answer is in. Placeholder
3413
+ rows show only while there is nothing to show yet, hidden from assistive
3414
+ technology since the label already speaks for them; after that, the previous
3415
+ rows stay until the new ones replace them. A request that is no longer the
3416
+ latest is dropped.
3389
3417
 
3390
3418
  `minInputLength` keeps the list shut until two characters are typed — focusing
3391
3419
  the field opens nothing, and deleting back under the minimum closes it — and
3392
3420
  no request goes out for a shorter query either.</div>
3393
- <pre><code>const AsyncSearchWithStatus = () =&gt; {
3421
+ <pre><code>const AsyncSearchWithLoading = () =&gt; {
3394
3422
  const [results, setResults] = useState&lt;Agency[]&gt;([]);
3395
3423
  const [isLoading, setIsLoading] = useState(false);
3396
3424
  // Only the latest request may land; fast typing returns responses out of order.
@@ -3420,17 +3448,15 @@ no request goes out for a shorter query either.</div>
3420
3448
  filteredItems={results}
3421
3449
  filter={null}
3422
3450
  minInputLength={MIN_QUERY_LENGTH}
3451
+ loading={isLoading}
3452
+ loadingLabel=&quot;Searching agencies…&quot;
3423
3453
  itemToStringLabel={(agency: Agency) =&gt; agency.name}
3424
3454
  isItemEqualToValue={(a: Agency, b: Agency) =&gt; a.id === b.id}
3425
3455
  onInputValueChange={onInputValueChange}&gt;
3426
3456
  &lt;Combobox.Input placeholder=&quot;Search agencies&quot; aria-label=&quot;Search agencies&quot; /&gt;
3427
- &lt;Combobox.Content aria-busy={isLoading || undefined}&gt;
3428
- {/* The rows show the wait; `Status` only announces it. */}
3429
- &lt;Combobox.Status className=&quot;sr-only&quot;&gt;
3430
- {isLoading ? &#39;Searching…&#39; : undefined}
3431
- &lt;/Combobox.Status&gt;
3457
+ &lt;Combobox.Content&gt;
3432
3458
  {isLoading &amp;&amp; results.length === 0 &amp;&amp; &lt;SkeletonRows /&gt;}
3433
- &lt;Combobox.Empty&gt;{isLoading ? undefined : &#39;No agencies found&#39;}&lt;/Combobox.Empty&gt;
3459
+ &lt;Combobox.Empty&gt;No agencies found&lt;/Combobox.Empty&gt;
3434
3460
  &lt;Combobox.List&gt;
3435
3461
  {(agency: Agency) =&gt; (
3436
3462
  &lt;Combobox.Item key={agency.id} value={agency}&gt;
@@ -7972,8 +7998,12 @@ required?: boolean = false</code></pre>
7972
7998
  <span class="badge ok">story ok</span>
7973
7999
  </div>
7974
8000
  <div>Optional field with a muted suffix</div>
7975
- <div class="hint">Appends a muted &quot; (optional)&quot; suffix for fields that can be left blank.</div>
7976
- <pre><code>const Optional = () =&gt; &lt;Label optional&gt;Phone number&lt;/Label&gt;;</code></pre>
8001
+ <div class="hint">Appends a muted &quot; (optional)&quot; suffix for fields that can be left blank. The
8002
+ suffix is real text, so it is part of the control&#39;s accessible name.</div>
8003
+ <pre><code>const Optional = () =&gt; &lt;&gt;
8004
+ &lt;Label optional htmlFor=&quot;phone&quot;&gt;Phone number&lt;/Label&gt;
8005
+ &lt;input id=&quot;phone&quot; type=&quot;tel&quot; className={inputRecipe()} /&gt;
8006
+ &lt;/&gt;;</code></pre>
7977
8007
  </div>
7978
8008
  <div class="note ok">
7979
8009
  <div class="row">
@@ -2007,7 +2007,7 @@
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. The list opens on focus, on the first keystroke\nor from the chevron of `Input`'s `select` variant.\n\nFor a search against an API, set `minInputLength` so the list stays shut until\nthe query is worth sending: a field that opens an empty list on focus reads as\nbroken. For a pick from a short fixed list with no typing at all, prefer a\nselect.\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\nEvery Base UI root prop is accepted and forwarded — `items`, `filter`,\n`value`, `onValueChange` and so on. `open` stays the consumer's to control;\n`minInputLength` only refuses to open below the minimum.\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.",
2010
+ "description": "Root of a combobox: a text input that filters a list of options and writes the\nchosen one back into itself. The list opens on focus, on the first keystroke\nor from the chevron of `Input`'s `select` variant.\n\nFor a search against an API, set `minInputLength` so the list stays shut until\nthe query is worth sending: a field that opens an empty list on focus reads as\nbroken. For a pick from a short fixed list with no typing at all, prefer a\nselect.\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`, request\nthem from `onInputValueChange`, and pass the request's state as `loading`:\nthe combobox announces the wait and marks the list busy on its own.\n\nEvery Base UI root prop is accepted and forwarded — `items`, `filter`,\n`value`, `onValueChange` and so on. `open` stays the consumer's to control;\n`minInputLength` only refuses to open below the minimum.\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"
@@ -2790,6 +2790,46 @@
2790
2790
  "fileName": "src/combobox/combobox.tsx"
2791
2791
  }
2792
2792
  ]
2793
+ },
2794
+ "loading": {
2795
+ "name": "loading",
2796
+ "required": false,
2797
+ "type": {
2798
+ "name": "boolean"
2799
+ },
2800
+ "description": "Whether the options are being fetched. Once the list has been open and\nloading for half a second, `loadingLabel` is announced once: sooner, a\nscreen reader would cut it off to echo the key just typed, and a faster\nanswer has no wait to announce. `Content` is marked `aria-busy`, and\n`Empty` shows nothing, so \"no results\" is not read out before the results\narrive. The announcement comes from a visually hidden live region the root\nrenders next to the field, so it is in place before the list opens. On a search\nagainst an API, pass it from the first render, as `false` while idle: a root\nthat leaves it out gets no live region. A list filtered in memory never\nwaits and leaves it out. While loading with no rows yet, the surface shows\nwhatever placeholder `Content` holds — `Empty` stays quiet.",
2801
+ "defaultValue": null,
2802
+ "parent": {
2803
+ "name": "ComboboxRootProps",
2804
+ "fileName": "src/combobox/combobox.tsx"
2805
+ },
2806
+ "declarations": [
2807
+ {
2808
+ "name": "TypeLiteral",
2809
+ "fileName": "src/combobox/combobox.tsx"
2810
+ }
2811
+ ]
2812
+ },
2813
+ "loadingLabel": {
2814
+ "name": "loadingLabel",
2815
+ "required": false,
2816
+ "type": {
2817
+ "name": "string"
2818
+ },
2819
+ "description": "What a screen reader hears when `loading` turns `true`. Announced only; any\nloading visuals — placeholder rows, a spinner — are yours to render inside\n`Content`, hidden from assistive technology. Defaults to `'Loading…'`.",
2820
+ "defaultValue": {
2821
+ "value": "'Loading…'"
2822
+ },
2823
+ "parent": {
2824
+ "name": "ComboboxRootProps",
2825
+ "fileName": "src/combobox/combobox.tsx"
2826
+ },
2827
+ "declarations": [
2828
+ {
2829
+ "name": "TypeLiteral",
2830
+ "fileName": "src/combobox/combobox.tsx"
2831
+ }
2832
+ ]
2793
2833
  }
2794
2834
  }
2795
2835
  },
@@ -2837,15 +2877,15 @@
2837
2877
  {
2838
2878
  "id": "components-combobox--async-search",
2839
2879
  "name": "Async Search",
2840
- "snippet": "const AsyncSearch = () => {\n const [query, setQuery] = useState('');\n // The rows read the deferred query: while the new one suspends, React keeps rendering the\n // old rows instead of falling back to the placeholders.\n const deferredQuery = useDeferredValue(query);\n const isStale = query !== deferredQuery;\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 aria-busy={isStale || undefined}>\n <Suspense fallback={<SkeletonRows />}>\n <AgencyItems query={deferredQuery} />\n </Suspense>\n </Combobox.Content>\n </Combobox.Root>\n );\n};",
2841
- "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 the first results land. The rows read a deferred copy of\nthe query, so a new search keeps the previous rows on screen while it loads and\n`Content` is marked busy until they are replaced; the placeholders never come\nback. Nothing tracks `loading` by hand, and reading the data by query is what\nmakes out-of-order responses harmless. With react-query, drop a\n`useSuspenseQuery` where `use()` is, or use `placeholderData: keepPreviousData`\nwith a plain `useQuery` for the same behaviour."
2880
+ "snippet": "const AsyncSearch = () => {\n const [query, setQuery] = useState('');\n // The rows read the deferred query: while the new one suspends, React keeps rendering the\n // old rows instead of falling back to the placeholders.\n const deferredQuery = useDeferredValue(query);\n const isStale = query !== deferredQuery;\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 loading={isStale}\n loadingLabel=\"Searching agencies…\"\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={deferredQuery} />\n </Suspense>\n </Combobox.Content>\n </Combobox.Root>\n );\n};",
2881
+ "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 the first results land. The rows read a deferred copy of\nthe query, so a new search keeps the previous rows on screen while it loads, and\npassing that staleness as `loading` announces the wait and marks `Content` busy\nuntil they are replaced; the placeholders never come back. Nothing tracks a\nrequest by hand, and reading the data by query is what\nmakes out-of-order responses harmless. With react-query, drop a\n`useSuspenseQuery` where `use()` is, or use `placeholderData: keepPreviousData`\nwith a plain `useQuery` for the same behaviour."
2842
2882
  },
2843
2883
  {
2844
- "id": "components-combobox--async-search-with-status",
2845
- "name": "Async Search With Status",
2846
- "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 if (query.trim().length < MIN_QUERY_LENGTH) {\n setResults([]);\n setIsLoading(false);\n return;\n }\n\n // The previous rows stay through the wait, marked busy, rather than blinking out and back.\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 minInputLength={MIN_QUERY_LENGTH}\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 && results.length === 0 && <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};",
2847
- "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. Placeholder rows show\nonly while there is nothing to show yet; after that, the previous rows stay\nuntil the new ones replace them. A request that is no longer the latest is\ndropped.\n\n`minInputLength` keeps the list shut until two characters are typed — focusing\nthe field opens nothing, and deleting back under the minimum closes it — and\nno request goes out for a shorter query either.",
2848
- "summary": "API search that waits for two characters, with loading status"
2884
+ "id": "components-combobox--async-search-with-loading",
2885
+ "name": "Async Search With Loading",
2886
+ "snippet": "const AsyncSearchWithLoading = () => {\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 if (query.trim().length < MIN_QUERY_LENGTH) {\n setResults([]);\n setIsLoading(false);\n return;\n }\n\n // The previous rows stay through the wait, marked busy, rather than blinking out and back.\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 minInputLength={MIN_QUERY_LENGTH}\n loading={isLoading}\n loadingLabel=\"Searching agencies…\"\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>\n {isLoading && results.length === 0 && <SkeletonRows />}\n <Combobox.Empty>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};",
2887
+ "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`,\nand the flag goes to `Root` as `loading`. That is all the wiring the wait needs:\n`Content` is marked busy, `loadingLabel` is announced once when the request\nstarts, and `Empty` holds back \"no results\" until the answer is in. Placeholder\nrows show only while there is nothing to show yet, hidden from assistive\ntechnology since the label already speaks for them; after that, the previous\nrows stay until the new ones replace them. A request that is no longer the\nlatest is dropped.\n\n`minInputLength` keeps the list shut until two characters are typed — focusing\nthe field opens nothing, and deleting back under the minimum closes it — and\nno request goes out for a shorter query either.",
2888
+ "summary": "API search that waits for two characters and announces its loading state"
2849
2889
  },
2850
2890
  {
2851
2891
  "id": "components-combobox--inside-field-text",
@@ -7133,8 +7173,8 @@
7133
7173
  {
7134
7174
  "id": "components-label--optional",
7135
7175
  "name": "Optional",
7136
- "snippet": "const Optional = () => <Label optional>Phone number</Label>;",
7137
- "description": "Appends a muted \" (optional)\" suffix for fields that can be left blank.",
7176
+ "snippet": "const Optional = () => <>\n <Label optional htmlFor=\"phone\">Phone number</Label>\n <input id=\"phone\" type=\"tel\" className={inputRecipe()} />\n</>;",
7177
+ "description": "Appends a muted \" (optional)\" suffix for fields that can be left blank. The\nsuffix is real text, so it is part of the control's accessible name.",
7138
7178
  "summary": "Optional field with a muted suffix"
7139
7179
  },
7140
7180
  {
@@ -9867,6 +9907,6 @@
9867
9907
  },
9868
9908
  "meta": {
9869
9909
  "docgen": "react-component-meta",
9870
- "durationMs": 3754
9910
+ "durationMs": 3150
9871
9911
  }
9872
9912
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentero/design-system",
3
- "version": "0.34.0",
3
+ "version": "0.34.2",
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",
@@ -87,6 +87,26 @@ export type ComboboxRootProps<Value, Multiple extends boolean | undefined, Item>
87
87
  * focus.
88
88
  */
89
89
  minInputLength?: number;
90
+ /**
91
+ * Whether the options are being fetched. Once the list has been open and
92
+ * loading for half a second, `loadingLabel` is announced once: sooner, a
93
+ * screen reader would cut it off to echo the key just typed, and a faster
94
+ * answer has no wait to announce. `Content` is marked `aria-busy`, and
95
+ * `Empty` shows nothing, so "no results" is not read out before the results
96
+ * arrive. The announcement comes from a visually hidden live region the root
97
+ * renders next to the field, so it is in place before the list opens. On a search
98
+ * against an API, pass it from the first render, as `false` while idle: a root
99
+ * that leaves it out gets no live region. A list filtered in memory never
100
+ * waits and leaves it out. While loading with no rows yet, the surface shows
101
+ * whatever placeholder `Content` holds — `Empty` stays quiet.
102
+ */
103
+ loading?: boolean;
104
+ /**
105
+ * What a screen reader hears when `loading` turns `true`. Announced only; any
106
+ * loading visuals — placeholder rows, a spinner — are yours to render inside
107
+ * `Content`, hidden from assistive technology. Defaults to `'Loading…'`.
108
+ */
109
+ loadingLabel?: string;
90
110
  };
91
111
  /**
92
112
  * Root of a combobox: a text input that filters a list of options and writes the
@@ -99,8 +119,9 @@ export type ComboboxRootProps<Value, Multiple extends boolean | undefined, Item>
99
119
  * select.
100
120
  *
101
121
  * Pass the options as `items`. For a list that comes from the server, set
102
- * `filter={null}`, hand the already-filtered rows to `filteredItems`, and request
103
- * them from `onInputValueChange`; `Status` then announces the wait.
122
+ * `filter={null}`, hand the already-filtered rows to `filteredItems`, request
123
+ * them from `onInputValueChange`, and pass the request's state as `loading`:
124
+ * the combobox announces the wait and marks the list busy on its own.
104
125
  *
105
126
  * Every Base UI root prop is accepted and forwarded — `items`, `filter`,
106
127
  * `value`, `onValueChange` and so on. `open` stays the consumer's to control;
@@ -131,7 +152,7 @@ export type ComboboxRootProps<Value, Multiple extends boolean | undefined, Item>
131
152
  * ```
132
153
  */
133
154
  export declare const Root: {
134
- <Value, Multiple extends boolean | undefined = false, Item = Value>({ minInputLength, open: openProp, defaultOpen, onOpenChange, onOpenChangeComplete, onInputValueChange, ...props }: ComboboxRootProps<Value, Multiple, Item>): import("react/jsx-runtime").JSX.Element;
155
+ <Value, Multiple extends boolean | undefined = false, Item = Value>({ minInputLength, loading, loadingLabel, open: openProp, defaultOpen, onOpenChange, onOpenChangeComplete, onInputValueChange, children, ...props }: ComboboxRootProps<Value, Multiple, Item>): import("react/jsx-runtime").JSX.Element;
135
156
  displayName: string;
136
157
  };
137
158
  type InputProps = Omit<ComponentPropsWithRef<typeof ComboboxPrimitive.Input>, 'size'> & {
@@ -223,11 +244,14 @@ type ContentProps = ComponentPropsWithRef<typeof ComboboxPrimitive.Popup> & Pick
223
244
  * one sits under a focused input whose ring is already two near-black pixels, and
224
245
  * at 8 the two borders fight.
225
246
  *
247
+ * Under a root that passes `loading`, the surface is `aria-busy` while `loading`
248
+ * is `true`; the root announces the wait. An `aria-busy` passed here wins.
249
+ *
226
250
  * @summary Floating surface anchored to the input, holding the list
227
251
  * @dataAttribute {string} data-slot - Always set to "combobox-content"
228
252
  */
229
253
  export declare const Content: {
230
- ({ className, side, align, sideOffset, alignOffset, anchor, collisionPadding, ...props }: ContentProps): import("react/jsx-runtime").JSX.Element;
254
+ ({ className, side, align, sideOffset, alignOffset, anchor, collisionPadding, children, ...props }: ContentProps): import("react/jsx-runtime").JSX.Element;
231
255
  displayName: string;
232
256
  };
233
257
  type ListProps = ComponentPropsWithRef<typeof ComboboxPrimitive.List>;
@@ -270,21 +294,26 @@ type EmptyProps = ComponentPropsWithRef<typeof ComboboxPrimitive.Empty>;
270
294
  /**
271
295
  * Shown in place of the list when nothing matches, and announced politely. It
272
296
  * needs `items` on `Root` to know the list is empty, and it must stay mounted —
273
- * change its children rather than rendering it conditionally.
297
+ * change its children rather than rendering it conditionally. While the root is
298
+ * `loading` it shows nothing: the list is empty because the results have not
299
+ * arrived yet, not because there are none. With no rows to show either, the
300
+ * surface would open as an empty frame, so render placeholder rows inside
301
+ * `Content` for the wait.
274
302
  *
275
303
  * @summary Message shown when no item matches the query
276
304
  * @dataAttribute {string} data-slot - Always set to "combobox-empty"
277
305
  */
278
306
  export declare const Empty: {
279
- ({ className, ...props }: EmptyProps): import("react/jsx-runtime").JSX.Element;
307
+ ({ className, children, ...props }: EmptyProps): import("react/jsx-runtime").JSX.Element;
280
308
  displayName: string;
281
309
  };
282
310
  type StatusProps = ComponentPropsWithRef<typeof ComboboxPrimitive.Status>;
283
311
  /**
284
- * Announces the state of a list that is still loading. Use it for the wait on a
285
- * remote search: keep it mounted and swap its children. It does not mark the
286
- * surface busy on its own — pass `aria-busy` to `Content` while the request is in
287
- * flight, as Base UI's own examples do.
312
+ * A politely announced message about the list. For the wait on a remote search,
313
+ * pass `loading` to `Root` instead: it announces and marks the surface busy with
314
+ * nothing else wired. Reach for `Status` for any other message — a count of
315
+ * results, an error from the request — and keep it mounted, swapping its
316
+ * children: a live region that appears already filled in is often skipped.
288
317
  *
289
318
  * @summary Politely announced status message for an async list
290
319
  * @dataAttribute {string} data-slot - Always set to "combobox-status"
@@ -8,11 +8,11 @@ import { InputGroup as a } from "../input-group/index.js";
8
8
  import { InputSearch as o } from "../input-search/input-search.js";
9
9
  import { IconCancel as s, IconCheck as c, IconKeyboardArrowDown as l, IconSearch as u } from "./icons.js";
10
10
  import { tv as d } from "tailwind-variants";
11
- import { createContext as f, use as p, useRef as m, useState as h } from "react";
12
- import { jsx as g, jsxs as _ } from "react/jsx-runtime";
13
- import { Combobox as v } from "@base-ui/react/combobox";
11
+ import { createContext as f, use as p, useEffect as m, useRef as h, useState as g } from "react";
12
+ import { jsx as _, jsxs as v } from "react/jsx-runtime";
13
+ import { Combobox as y } from "@base-ui/react/combobox";
14
14
  //#region src/combobox/combobox.tsx
15
- var y = d({ slots: {
15
+ var b = d({ slots: {
16
16
  action: [
17
17
  "flex size-6 shrink-0 cursor-pointer items-center justify-center self-center rounded-sm",
18
18
  "[&>svg>path]:fill-icon-input-default hover:[&>svg>path]:fill-icon-default-base-primary",
@@ -48,144 +48,170 @@ var y = d({ slots: {
48
48
  "group-data-disabled/item:data-selected:bg-bg-checkbox-disabled"
49
49
  ],
50
50
  message: ["mx-2 my-px px-3 py-2 text-sm text-text-default-base-tertiary", "empty:m-0 empty:p-0"]
51
- } }), b = y(), x = f(!1), S = ({ minInputLength: e = 0, open: t, defaultOpen: n = !1, onOpenChange: r, onOpenChangeComplete: i, onInputValueChange: a, ...o }) => {
52
- let s = !!o.multiple, [c, l] = h(n), u = m(String(o.inputValue ?? o.defaultInputValue ?? ""));
53
- o.inputValue !== void 0 && (u.current = String(o.inputValue));
54
- let d = (t) => t.trim().length < e, f = m(!1), p = m(!1), _ = (e, t) => {
55
- if (e && d(u.current)) {
51
+ } }), x = b(), S = f(!1), C = f(void 0), w = 500, T = (e, t) => {
52
+ let [n, r] = g(!1);
53
+ return m(() => {
54
+ if (!e) return;
55
+ let t = setTimeout(() => r(!0), w);
56
+ return () => {
57
+ clearTimeout(t), r(!1);
58
+ };
59
+ }, [e]), e && n ? t : null;
60
+ }, E = ({ minInputLength: e = 0, loading: t, loadingLabel: n = "Loading…", open: r, defaultOpen: i = !1, onOpenChange: a, onOpenChangeComplete: o, onInputValueChange: s, children: c, ...l }) => {
61
+ let u = !!l.multiple, [d, f] = g(i), p = h(String(l.inputValue ?? l.defaultInputValue ?? ""));
62
+ l.inputValue !== void 0 && (p.current = String(l.inputValue));
63
+ let m = (t) => t.trim().length < e, b = h(!1), x = h(!1), w = (e, t) => {
64
+ if (e && m(p.current)) {
56
65
  t.cancel();
57
66
  return;
58
67
  }
59
- f.current = !1, r?.(e, t), s && !e && t.reason === "item-press" && t.cancel(), t.isCanceled || l(e);
60
- }, y = (e) => {
61
- i?.(e), !e && f.current && (f.current = !1, p.current = !0, queueMicrotask(() => p.current = !1));
62
- }, b = (e, n) => {
63
- if (p.current) {
64
- p.current = !1, n.cancel();
68
+ b.current = !1, a?.(e, t), u && !e && t.reason === "item-press" && t.cancel(), t.isCanceled || f(e);
69
+ }, E = (e) => {
70
+ o?.(e), !e && b.current && (b.current = !1, x.current = !0, queueMicrotask(() => x.current = !1));
71
+ }, D = (e, t) => {
72
+ if (x.current) {
73
+ x.current = !1, t.cancel();
65
74
  return;
66
75
  }
67
- u.current = e, a?.(e, n), d(e) && t === void 0 && c && (f.current = !0, l(!1));
68
- }, S = t ?? (e > 0 ? c : void 0);
69
- return /* @__PURE__ */ g(x, {
70
- value: s,
71
- children: /* @__PURE__ */ g(v.Root, {
72
- open: S,
73
- defaultOpen: n,
74
- onOpenChange: _,
75
- onOpenChangeComplete: y,
76
- onInputValueChange: b,
77
- ...o
76
+ p.current = e, s?.(e, t), m(e) && r === void 0 && d && (b.current = !0, f(!1));
77
+ }, O = r ?? (e > 0 ? d : void 0), k = T(!!t && (r ?? d), n);
78
+ return /* @__PURE__ */ _(S, {
79
+ value: u,
80
+ children: /* @__PURE__ */ _(C, {
81
+ value: t,
82
+ children: /* @__PURE__ */ v(y.Root, {
83
+ open: O,
84
+ defaultOpen: i,
85
+ onOpenChange: w,
86
+ onOpenChangeComplete: E,
87
+ onInputValueChange: D,
88
+ ...l,
89
+ children: [c, t !== void 0 && /* @__PURE__ */ _(y.Status, {
90
+ "data-slot": "combobox-loading",
91
+ className: "sr-only",
92
+ children: k
93
+ })]
94
+ })
78
95
  })
79
96
  });
80
97
  };
81
- S.displayName = "Combobox.Root";
82
- var C = /* @__PURE__ */ new Set([
98
+ E.displayName = "Combobox.Root";
99
+ var D = /* @__PURE__ */ new Set([
83
100
  "value",
84
101
  "defaultValue",
85
102
  "onChange"
86
- ]), w = (e) => {
87
- let i = p(r), a = n(), o = i ? Object.fromEntries(Object.entries(i).filter(([e]) => !C.has(e))) : null, s = !("aria-label" in e || "aria-labelledby" in e) && a ? { "aria-labelledby": a.labelId } : null;
103
+ ]), O = (e) => {
104
+ let i = p(r), a = n(), o = i ? Object.fromEntries(Object.entries(i).filter(([e]) => !D.has(e))) : null, s = !("aria-label" in e || "aria-labelledby" in e) && a ? { "aria-labelledby": a.labelId } : null;
88
105
  return t({
89
106
  ...o,
90
107
  ...s
91
108
  }, e);
92
- }, T = ({ variant: t = "search", showClear: n = t === "search", clearLabel: c = "Clear", triggerLabel: d = "Show options", ...f }) => {
93
- let { className: m, size: h = "md", ...y } = w(f), S = p(x);
94
- return t === "search" && n && !S ? /* @__PURE__ */ g(v.InputGroup, {
95
- render: /* @__PURE__ */ g("div", {}),
109
+ }, k = ({ variant: t = "search", showClear: n = t === "search", clearLabel: c = "Clear", triggerLabel: d = "Show options", ...f }) => {
110
+ let { className: m, size: h = "md", ...g } = O(f), b = p(S);
111
+ return t === "search" && n && !b ? /* @__PURE__ */ _(y.InputGroup, {
112
+ render: /* @__PURE__ */ _("div", {}),
96
113
  "data-slot": "combobox-field",
97
- children: /* @__PURE__ */ g(r, {
114
+ children: /* @__PURE__ */ _(r, {
98
115
  value: null,
99
- children: /* @__PURE__ */ g(v.Input, {
116
+ children: /* @__PURE__ */ _(y.Input, {
100
117
  "data-slot": "combobox-input",
101
118
  className: m,
102
- ...y,
103
- render: (e) => /* @__PURE__ */ g(o, {
119
+ ...g,
120
+ render: (e) => /* @__PURE__ */ _(o, {
104
121
  ...e,
105
122
  size: h,
106
123
  clearLabel: c
107
124
  })
108
125
  })
109
126
  })
110
- }) : /* @__PURE__ */ _(v.InputGroup, {
111
- render: /* @__PURE__ */ g(a.Root, {}),
127
+ }) : /* @__PURE__ */ v(y.InputGroup, {
128
+ render: /* @__PURE__ */ _(a.Root, {}),
112
129
  "data-slot": "combobox-field",
113
130
  className: t === "plain" ? "px-4" : void 0,
114
131
  children: [
115
- t === "search" && /* @__PURE__ */ g(a.Addon, { children: /* @__PURE__ */ g(u, {}) }),
116
- /* @__PURE__ */ g(v.Input, {
132
+ t === "search" && /* @__PURE__ */ _(a.Addon, { children: /* @__PURE__ */ _(u, {}) }),
133
+ /* @__PURE__ */ _(y.Input, {
117
134
  "data-slot": "combobox-input",
118
135
  "data-size": h,
119
136
  className: e(i({ size: h }), m),
120
- ...y
137
+ ...g
121
138
  }),
122
- n && !S && /* @__PURE__ */ g(v.Clear, {
139
+ n && !b && /* @__PURE__ */ _(y.Clear, {
123
140
  "data-slot": "combobox-clear",
124
141
  "aria-label": c,
125
- className: b.action(),
126
- children: /* @__PURE__ */ g(s, {})
142
+ className: x.action(),
143
+ children: /* @__PURE__ */ _(s, {})
127
144
  }),
128
- t === "select" && !S && /* @__PURE__ */ g(v.Trigger, {
145
+ t === "select" && !b && /* @__PURE__ */ _(y.Trigger, {
129
146
  "data-slot": "combobox-trigger",
130
147
  "aria-label": d,
131
- className: e(b.action(), b.trigger(), n && b.triggerReplaced()),
132
- children: /* @__PURE__ */ g(l, {})
148
+ className: e(x.action(), x.trigger(), n && x.triggerReplaced()),
149
+ children: /* @__PURE__ */ _(l, {})
133
150
  })
134
151
  ]
135
152
  });
136
153
  };
137
- T.displayName = "Combobox.Input";
138
- var E = ({ className: t, side: n = "bottom", align: r = "start", sideOffset: i = 12, alignOffset: a, anchor: o, collisionPadding: s = 8, ...c }) => /* @__PURE__ */ g(v.Portal, { children: /* @__PURE__ */ g(v.Positioner, {
139
- side: n,
140
- align: r,
141
- sideOffset: i,
142
- alignOffset: a,
143
- anchor: o,
144
- collisionPadding: s,
145
- className: "isolate z-(--z-index-top-layer) pointer-events-auto",
146
- children: /* @__PURE__ */ g(v.Popup, {
147
- "data-slot": "combobox-content",
148
- className: e(b.content(), t),
149
- ...c
150
- })
151
- }) });
152
- E.displayName = "Combobox.Content";
153
- var D = ({ className: t, ...n }) => {
154
+ k.displayName = "Combobox.Input";
155
+ var A = ({ className: t, side: n = "bottom", align: r = "start", sideOffset: i = 12, alignOffset: a, anchor: o, collisionPadding: s = 8, children: c, ...l }) => {
156
+ let u = p(C);
157
+ return /* @__PURE__ */ _(y.Portal, { children: /* @__PURE__ */ _(y.Positioner, {
158
+ side: n,
159
+ align: r,
160
+ sideOffset: i,
161
+ alignOffset: a,
162
+ anchor: o,
163
+ collisionPadding: s,
164
+ className: "isolate z-(--z-index-top-layer) pointer-events-auto",
165
+ children: /* @__PURE__ */ _(y.Popup, {
166
+ "data-slot": "combobox-content",
167
+ "aria-busy": u || void 0,
168
+ className: e(x.content(), t),
169
+ ...l,
170
+ children: c
171
+ })
172
+ }) });
173
+ };
174
+ A.displayName = "Combobox.Content";
175
+ var j = ({ className: t, ...n }) => {
154
176
  let r = "aria-label" in n || "aria-labelledby" in n;
155
- return /* @__PURE__ */ g(v.List, {
177
+ return /* @__PURE__ */ _(y.List, {
156
178
  "data-slot": "combobox-list",
157
179
  "aria-label": r ? void 0 : "Suggestions",
158
- className: e(b.list(), t),
180
+ className: e(x.list(), t),
159
181
  ...n
160
182
  });
161
183
  };
162
- D.displayName = "Combobox.List";
163
- var O = ({ className: t, children: n, ...r }) => {
164
- let i = p(x);
165
- return /* @__PURE__ */ _(v.Item, {
184
+ j.displayName = "Combobox.List";
185
+ var M = ({ className: t, children: n, ...r }) => {
186
+ let i = p(S);
187
+ return /* @__PURE__ */ v(y.Item, {
166
188
  "data-slot": "combobox-item",
167
- className: e(b.item(), t),
189
+ className: e(x.item(), t),
168
190
  ...r,
169
- children: [i && /* @__PURE__ */ g(v.ItemIndicator, {
191
+ children: [i && /* @__PURE__ */ _(y.ItemIndicator, {
170
192
  keepMounted: !0,
171
193
  "data-slot": "combobox-item-indicator",
172
- className: b.indicator(),
173
- children: /* @__PURE__ */ g(c, { className: "size-3.5" })
194
+ className: x.indicator(),
195
+ children: /* @__PURE__ */ _(c, { className: "size-3.5" })
174
196
  }), n]
175
197
  });
176
198
  };
177
- O.displayName = "Combobox.Item";
178
- var k = ({ className: t, ...n }) => /* @__PURE__ */ g(v.Empty, {
179
- "data-slot": "combobox-empty",
180
- className: e(b.message(), t),
181
- ...n
182
- });
183
- k.displayName = "Combobox.Empty";
184
- var A = ({ className: t, ...n }) => /* @__PURE__ */ g(v.Status, {
199
+ M.displayName = "Combobox.Item";
200
+ var N = ({ className: t, children: n, ...r }) => {
201
+ let i = p(C);
202
+ return /* @__PURE__ */ _(y.Empty, {
203
+ "data-slot": "combobox-empty",
204
+ className: e(x.message(), t),
205
+ ...r,
206
+ children: i ? null : n
207
+ });
208
+ };
209
+ N.displayName = "Combobox.Empty";
210
+ var P = ({ className: t, ...n }) => /* @__PURE__ */ _(y.Status, {
185
211
  "data-slot": "combobox-status",
186
- className: e(b.message(), t),
212
+ className: e(x.message(), t),
187
213
  ...n
188
214
  });
189
- A.displayName = "Combobox.Status";
215
+ P.displayName = "Combobox.Status";
190
216
  //#endregion
191
- export { E as Content, k as Empty, T as Input, O as Item, D as List, S as Root, A as Status, y as comboboxRecipe };
217
+ export { A as Content, N as Empty, k as Input, M as Item, j as List, E as Root, P as Status, b as comboboxRecipe };
@@ -2,7 +2,7 @@ export { comboboxRecipe } from './combobox';
2
2
  export type { ComboboxRootProps } from './combobox';
3
3
  export declare const Combobox: {
4
4
  Root: {
5
- <Value, Multiple extends boolean | undefined = false, Item = Value>({ minInputLength, open: openProp, defaultOpen, onOpenChange, onOpenChangeComplete, onInputValueChange, ...props }: import('./combobox').ComboboxRootProps<Value, Multiple, Item>): import("react/jsx-runtime").JSX.Element;
5
+ <Value, Multiple extends boolean | undefined = false, Item = Value>({ minInputLength, loading, loadingLabel, open: openProp, defaultOpen, onOpenChange, onOpenChangeComplete, onInputValueChange, children, ...props }: import('./combobox').ComboboxRootProps<Value, Multiple, Item>): import("react/jsx-runtime").JSX.Element;
6
6
  displayName: string;
7
7
  };
8
8
  Input: {
@@ -16,7 +16,7 @@ export declare const Combobox: {
16
16
  displayName: string;
17
17
  };
18
18
  Content: {
19
- ({ className, side, align, sideOffset, alignOffset, anchor, collisionPadding, ...props }: Omit<import('@base-ui/react/combobox').ComboboxPopupProps, "ref"> & import('react').RefAttributes<HTMLDivElement> & Pick<Omit<import('@base-ui/react/combobox').ComboboxPositionerProps, "ref"> & import('react').RefAttributes<HTMLDivElement>, "anchor" | "align" | "side" | "sideOffset" | "alignOffset" | "collisionPadding">): import("react/jsx-runtime").JSX.Element;
19
+ ({ className, side, align, sideOffset, alignOffset, anchor, collisionPadding, children, ...props }: Omit<import('@base-ui/react/combobox').ComboboxPopupProps, "ref"> & import('react').RefAttributes<HTMLDivElement> & Pick<Omit<import('@base-ui/react/combobox').ComboboxPositionerProps, "ref"> & import('react').RefAttributes<HTMLDivElement>, "anchor" | "align" | "side" | "sideOffset" | "alignOffset" | "collisionPadding">): import("react/jsx-runtime").JSX.Element;
20
20
  displayName: string;
21
21
  };
22
22
  List: {
@@ -28,7 +28,7 @@ export declare const Combobox: {
28
28
  displayName: string;
29
29
  };
30
30
  Empty: {
31
- ({ className, ...props }: Omit<import('@base-ui/react/combobox').ComboboxEmptyProps, "ref"> & import('react').RefAttributes<HTMLDivElement>): import("react/jsx-runtime").JSX.Element;
31
+ ({ className, children, ...props }: Omit<import('@base-ui/react/combobox').ComboboxEmptyProps, "ref"> & import('react').RefAttributes<HTMLDivElement>): import("react/jsx-runtime").JSX.Element;
32
32
  displayName: string;
33
33
  };
34
34
  Status: {
@@ -1,36 +1,48 @@
1
1
  import { ComponentPropsWithRef } from 'react';
2
2
  import { VariantProps } from 'tailwind-variants';
3
3
  import * as LabelPrimitive from '@radix-ui/react-label';
4
- /** Style recipe for Label. Slots: `root`, `text`, `required`. */
4
+ /** Style recipe for Label. Slots: `root`, `text`, `optional`, `required`. */
5
5
  export declare const labelRecipe: import('tailwind-variants').TVReturnType<{
6
- optional: {
7
- true: {
8
- text: string;
6
+ [key: string]: {
7
+ [key: string]: import('tailwind-merge').ClassNameValue | {
8
+ text?: import('tailwind-merge').ClassNameValue;
9
+ root?: import('tailwind-merge').ClassNameValue;
10
+ required?: import('tailwind-merge').ClassNameValue;
11
+ optional?: import('tailwind-merge').ClassNameValue;
9
12
  };
10
13
  };
11
- }, {
14
+ } | {
15
+ [x: string]: {
16
+ [x: string]: import('tailwind-merge').ClassNameValue | {
17
+ text?: import('tailwind-merge').ClassNameValue;
18
+ root?: import('tailwind-merge').ClassNameValue;
19
+ required?: import('tailwind-merge').ClassNameValue;
20
+ optional?: import('tailwind-merge').ClassNameValue;
21
+ };
22
+ };
23
+ } | {}, {
12
24
  root: string;
13
25
  text: string;
26
+ optional: string;
14
27
  required: string;
15
28
  }, undefined, {
16
- optional: {
17
- true: {
18
- text: string;
29
+ [key: string]: {
30
+ [key: string]: import('tailwind-merge').ClassNameValue | {
31
+ text?: import('tailwind-merge').ClassNameValue;
32
+ root?: import('tailwind-merge').ClassNameValue;
33
+ required?: import('tailwind-merge').ClassNameValue;
34
+ optional?: import('tailwind-merge').ClassNameValue;
19
35
  };
20
36
  };
21
- }, {
37
+ } | {}, {
22
38
  root: string;
23
39
  text: string;
40
+ optional: string;
24
41
  required: string;
25
- }, import('tailwind-variants').TVReturnType<{
26
- optional: {
27
- true: {
28
- text: string;
29
- };
30
- };
31
- }, {
42
+ }, import('tailwind-variants').TVReturnType<unknown, {
32
43
  root: string;
33
44
  text: string;
45
+ optional: string;
34
46
  required: string;
35
47
  }, undefined, unknown, unknown, undefined>>;
36
48
  export type LabelVariants = VariantProps<typeof labelRecipe>;
@@ -2,33 +2,33 @@
2
2
  import { cn as e } from "../../lib/utils.js";
3
3
  import { useLabelContext as t } from "./context.js";
4
4
  import { tv as n } from "tailwind-variants";
5
- import { jsx as r, jsxs as i } from "react/jsx-runtime";
6
- import * as a from "@radix-ui/react-label";
5
+ import { Fragment as r, jsx as i, jsxs as a } from "react/jsx-runtime";
6
+ import * as o from "@radix-ui/react-label";
7
7
  //#region src/label/label.tsx
8
- var o = n({
9
- slots: {
10
- root: "flex flex-wrap gap-1 py-0.25 text-sm font-semibold text-text-input-normal",
11
- text: "align-middle",
12
- required: "align-middle text-text-input-destructive"
13
- },
14
- variants: { optional: { true: { text: "after:font-normal after:text-text-input-placeholder after:content-[\"_(optional)\"]" } } },
15
- defaultVariants: { optional: !1 }
16
- }), s = (n) => {
17
- let { className: s, children: c, optional: l = !1, required: u = !1, ...d } = t(n), f = o({ optional: l && !u });
18
- return /* @__PURE__ */ i(a.Root, {
8
+ var s = n({ slots: {
9
+ root: "flex flex-wrap gap-1 py-0.25 text-sm font-semibold text-text-input-normal",
10
+ text: "align-middle",
11
+ optional: "font-normal text-text-input-placeholder",
12
+ required: "align-middle text-text-input-destructive"
13
+ } }), c = (n) => {
14
+ let { className: c, children: l, optional: u = !1, required: d = !1, ...f } = t(n), p = u && !d, m = s();
15
+ return /* @__PURE__ */ a(o.Root, {
19
16
  "data-slot": "label",
20
- className: e(f.root(), s),
21
- ...d,
22
- children: [/* @__PURE__ */ r("span", {
23
- className: f.text(),
24
- children: c
25
- }), u && /* @__PURE__ */ r("span", {
17
+ className: e(m.root(), c),
18
+ ...f,
19
+ children: [/* @__PURE__ */ a("span", {
20
+ className: m.text(),
21
+ children: [l, p && /* @__PURE__ */ a(r, { children: [" ", /* @__PURE__ */ i("span", {
22
+ className: m.optional(),
23
+ children: "(optional)"
24
+ })] })]
25
+ }), d && /* @__PURE__ */ i("span", {
26
26
  "aria-hidden": !0,
27
- className: f.required(),
27
+ className: m.required(),
28
28
  children: "*"
29
29
  })]
30
30
  });
31
31
  };
32
- s.displayName = "Label";
32
+ c.displayName = "Label";
33
33
  //#endregion
34
- export { s as Label, o as labelRecipe };
34
+ export { c as Label, s as labelRecipe };