@agentero/design-system 0.31.3 → 0.31.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -588,13 +588,13 @@
588
588
  <header>
589
589
  <div class="wrap">
590
590
  <h1>Manifest Debugger</h1>
591
- <div class="summary"><a class="filter-pill all" data-k="all" href="#filter-all">All</a><span class="filter-pill ok" aria-disabled="true">33 components ok</span><span class="filter-pill ok" aria-disabled="true">264 stories ok</span></div>
591
+ <div class="summary"><a class="filter-pill all" data-k="all" href="#filter-all">All</a><span class="filter-pill ok" aria-disabled="true">33 components ok</span><span class="filter-pill ok" aria-disabled="true">265 stories ok</span></div>
592
592
  </div>
593
593
  </header>
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.7s</strong>.
597
+ Using <code>react-component-meta</code>. Generation took <strong>4.8s</strong>.
598
598
  </div>
599
599
  <h2 class="section-title">Components</h2>
600
600
  <div class="grid" role="list">
@@ -720,7 +720,12 @@ value?: string | string[] | undefined</code></pre>
720
720
 
721
721
  <div class="hint">Exclusive accordion: one item open at a time, `collapsible` lets it close.</div>
722
722
  <pre><code>const Default = () =&gt; &lt;Accordion.Root type=&quot;single&quot; collapsible enclosed={false} disabled={false}&gt;
723
- &lt;FaqItems /&gt;
723
+ {FAQS.map(({ value, question, answer }) =&gt; (
724
+ &lt;Accordion.Item key={value} value={value}&gt;
725
+ &lt;Accordion.Trigger&gt;{question}&lt;/Accordion.Trigger&gt;
726
+ &lt;Accordion.Content&gt;{answer}&lt;/Accordion.Content&gt;
727
+ &lt;/Accordion.Item&gt;
728
+ ))}
724
729
  &lt;/Accordion.Root&gt;;</code></pre>
725
730
  </div>
726
731
  <div class="note ok">
@@ -732,7 +737,12 @@ value?: string | string[] | undefined</code></pre>
732
737
  <div class="hint">`type=&quot;multiple&quot;` lets several sections stay open at once.</div>
733
738
  <pre><code>const Multiple = () =&gt; (
734
739
  &lt;Accordion.Root type=&quot;multiple&quot; defaultValue={[&#39;covered&#39;, &#39;payment&#39;]}&gt;
735
- &lt;FaqItems /&gt;
740
+ {FAQS.map(({ value, question, answer }) =&gt; (
741
+ &lt;Accordion.Item key={value} value={value}&gt;
742
+ &lt;Accordion.Trigger&gt;{question}&lt;/Accordion.Trigger&gt;
743
+ &lt;Accordion.Content&gt;{answer}&lt;/Accordion.Content&gt;
744
+ &lt;/Accordion.Item&gt;
745
+ ))}
736
746
  &lt;/Accordion.Root&gt;
737
747
  );</code></pre>
738
748
  </div>
@@ -746,10 +756,20 @@ value?: string | string[] | undefined</code></pre>
746
756
  <pre><code>const Enclosed = () =&gt; (
747
757
  &lt;div className=&quot;flex flex-col gap-8&quot;&gt;
748
758
  &lt;Accordion.Root type=&quot;multiple&quot; defaultValue={[&#39;covered&#39;]}&gt;
749
- &lt;FaqItems /&gt;
759
+ {FAQS.map(({ value, question, answer }) =&gt; (
760
+ &lt;Accordion.Item key={value} value={value}&gt;
761
+ &lt;Accordion.Trigger&gt;{question}&lt;/Accordion.Trigger&gt;
762
+ &lt;Accordion.Content&gt;{answer}&lt;/Accordion.Content&gt;
763
+ &lt;/Accordion.Item&gt;
764
+ ))}
750
765
  &lt;/Accordion.Root&gt;
751
766
  &lt;Accordion.Root type=&quot;multiple&quot; enclosed defaultValue={[&#39;claim&#39;]}&gt;
752
- &lt;FaqItems /&gt;
767
+ {FAQS.map(({ value, question, answer }) =&gt; (
768
+ &lt;Accordion.Item key={value} value={value}&gt;
769
+ &lt;Accordion.Trigger&gt;{question}&lt;/Accordion.Trigger&gt;
770
+ &lt;Accordion.Content&gt;{answer}&lt;/Accordion.Content&gt;
771
+ &lt;/Accordion.Item&gt;
772
+ ))}
753
773
  &lt;/Accordion.Root&gt;
754
774
  &lt;/div&gt;
755
775
  );</code></pre>
@@ -801,7 +821,12 @@ value?: string | string[] | undefined</code></pre>
801
821
 
802
822
  <div class="hint">`collapsible` lets clicking the open item close it (reach an all-closed state).</div>
803
823
  <pre><code>const ExpandCollapse = () =&gt; &lt;Accordion.Root type=&quot;single&quot; collapsible&gt;
804
- &lt;FaqItems /&gt;
824
+ {FAQS.map(({ value, question, answer }) =&gt; (
825
+ &lt;Accordion.Item key={value} value={value}&gt;
826
+ &lt;Accordion.Trigger&gt;{question}&lt;/Accordion.Trigger&gt;
827
+ &lt;Accordion.Content&gt;{answer}&lt;/Accordion.Content&gt;
828
+ &lt;/Accordion.Item&gt;
829
+ ))}
805
830
  &lt;/Accordion.Root&gt;;</code></pre>
806
831
  </div>
807
832
  <div class="note ok">
@@ -812,7 +837,12 @@ value?: string | string[] | undefined</code></pre>
812
837
 
813
838
  <div class="hint">Without `collapsible`, clicking the already-open item keeps it open.</div>
814
839
  <pre><code>const NonCollapsible = () =&gt; &lt;Accordion.Root type=&quot;single&quot; collapsible={false} defaultValue=&quot;covered&quot;&gt;
815
- &lt;FaqItems /&gt;
840
+ {FAQS.map(({ value, question, answer }) =&gt; (
841
+ &lt;Accordion.Item key={value} value={value}&gt;
842
+ &lt;Accordion.Trigger&gt;{question}&lt;/Accordion.Trigger&gt;
843
+ &lt;Accordion.Content&gt;{answer}&lt;/Accordion.Content&gt;
844
+ &lt;/Accordion.Item&gt;
845
+ ))}
816
846
  &lt;/Accordion.Root&gt;;</code></pre>
817
847
  </div>
818
848
  </div>
@@ -3182,7 +3212,29 @@ that suspends on the request, so a `Suspense` boundary inside `Content` shows
3182
3212
  placeholder rows until they land. Nothing tracks `loading` by hand and no stale
3183
3213
  list lingers under the wait: the data is read by query, which is also what makes
3184
3214
  out-of-order responses harmless. Drop a `useSuspenseQuery` where `use()` is.</div>
3185
- <pre><code>const AsyncSearch = () =&gt; &lt;SuspenseSearch /&gt;;</code></pre>
3215
+ <pre><code>const AsyncSearch = () =&gt; {
3216
+ const [query, setQuery] = useState(&#39;&#39;);
3217
+
3218
+ return (
3219
+ // No `items` and `filter={null}`: the server did the filtering, the child renders what came
3220
+ // back, and `isItemEqualToValue` keeps a re-fetched copy counting as the selected one.
3221
+ &lt;Combobox.Root
3222
+ filter={null}
3223
+ itemToStringLabel={(agency: Agency) =&gt; agency.name}
3224
+ isItemEqualToValue={(a: Agency, b: Agency) =&gt; a.id === b.id}
3225
+ onInputValueChange={(value, { reason }) =&gt; {
3226
+ // Picking a row writes its label into the input; that is not a new search.
3227
+ if (reason !== &#39;item-press&#39;) setQuery(value);
3228
+ }}&gt;
3229
+ &lt;Combobox.Input placeholder=&quot;Search agencies&quot; aria-label=&quot;Search agencies&quot; /&gt;
3230
+ &lt;Combobox.Content&gt;
3231
+ &lt;Suspense fallback={&lt;SkeletonRows /&gt;}&gt;
3232
+ &lt;AgencyItems query={query} /&gt;
3233
+ &lt;/Suspense&gt;
3234
+ &lt;/Combobox.Content&gt;
3235
+ &lt;/Combobox.Root&gt;
3236
+ );
3237
+ };</code></pre>
3186
3238
  </div>
3187
3239
  <div class="note ok">
3188
3240
  <div class="row">
@@ -3194,7 +3246,54 @@ out-of-order responses harmless. Drop a `useSuspenseQuery` where `use()` is.</di
3194
3246
  its data layer instead: `filter={null}`, the rows arrive through `filteredItems`,
3195
3247
  `Content` is marked busy and `Status` announces the wait. The list is cleared
3196
3248
  before each request and a request that is no longer the latest is dropped.</div>
3197
- <pre><code>const AsyncSearchWithStatus = () =&gt; &lt;StatusSearch /&gt;;</code></pre>
3249
+ <pre><code>const AsyncSearchWithStatus = () =&gt; {
3250
+ const [results, setResults] = useState&lt;Agency[]&gt;([]);
3251
+ const [isLoading, setIsLoading] = useState(false);
3252
+ // Only the latest request may land; fast typing returns responses out of order.
3253
+ const latestRequest = useRef(0);
3254
+
3255
+ const onInputValueChange = async (query: string) =&gt; {
3256
+ const request = ++latestRequest.current;
3257
+
3258
+ // The previous rows go before the wait starts: a list that no longer matches what is typed
3259
+ // must not sit under the loading state.
3260
+ setResults([]);
3261
+ setIsLoading(true);
3262
+ const agencies = await searchAgencies(query);
3263
+ if (request !== latestRequest.current) return;
3264
+
3265
+ setResults(agencies);
3266
+ setIsLoading(false);
3267
+ };
3268
+
3269
+ return (
3270
+ // `filter={null}` hands filtering to the server; `filteredItems` is what came back.
3271
+ &lt;Combobox.Root
3272
+ items={results}
3273
+ filteredItems={results}
3274
+ filter={null}
3275
+ itemToStringLabel={(agency: Agency) =&gt; agency.name}
3276
+ isItemEqualToValue={(a: Agency, b: Agency) =&gt; a.id === b.id}
3277
+ onInputValueChange={onInputValueChange}&gt;
3278
+ &lt;Combobox.Input placeholder=&quot;Search agencies&quot; aria-label=&quot;Search agencies&quot; /&gt;
3279
+ &lt;Combobox.Content aria-busy={isLoading || undefined}&gt;
3280
+ {/* The rows show the wait; `Status` only announces it. */}
3281
+ &lt;Combobox.Status className=&quot;sr-only&quot;&gt;
3282
+ {isLoading ? &#39;Searching…&#39; : undefined}
3283
+ &lt;/Combobox.Status&gt;
3284
+ {isLoading &amp;&amp; &lt;SkeletonRows /&gt;}
3285
+ &lt;Combobox.Empty&gt;{isLoading ? undefined : &#39;No agencies found&#39;}&lt;/Combobox.Empty&gt;
3286
+ &lt;Combobox.List&gt;
3287
+ {(agency: Agency) =&gt; (
3288
+ &lt;Combobox.Item key={agency.id} value={agency}&gt;
3289
+ {agency.name}
3290
+ &lt;/Combobox.Item&gt;
3291
+ )}
3292
+ &lt;/Combobox.List&gt;
3293
+ &lt;/Combobox.Content&gt;
3294
+ &lt;/Combobox.Root&gt;
3295
+ );
3296
+ };</code></pre>
3198
3297
  </div>
3199
3298
  <div class="note ok">
3200
3299
  <div class="row">
@@ -3242,7 +3341,7 @@ padded past it.</div>
3242
3341
  &lt;div className=&quot;relative&quot;&gt;
3243
3342
  &lt;IconSearch
3244
3343
  aria-hidden
3245
- className=&quot;pointer-events-none absolute top-1/2 left-3 z-1 size-6 -translate-y-1/2 [&amp;&gt;path]:fill-icon-default-base-tertiary&quot;
3344
+ className=&quot;pointer-events-none absolute top-1/2 left-3 -z-hide size-6 -translate-y-1/2 [&amp;&gt;path]:fill-icon-default-base-tertiary&quot;
3246
3345
  /&gt;
3247
3346
  &lt;Combobox.Input className=&quot;pl-11&quot; placeholder=&quot;Search states&quot; aria-label=&quot;Search states&quot; /&gt;
3248
3347
  &lt;/div&gt;
@@ -3313,7 +3412,7 @@ list nor treats a click in it as a click outside and closes.</div>
3313
3412
  <div class="badges">
3314
3413
  <label for="c-8-components-command-props" class="badge ok as-toggle">10 prop types</label>
3315
3414
 
3316
- <label for="c-8-components-command-stories" class="badge ok as-toggle">6 stories</label>
3415
+ <label for="c-8-components-command-stories" class="badge ok as-toggle">7 stories</label>
3317
3416
 
3318
3417
 
3319
3418
  </div>
@@ -3322,7 +3421,9 @@ list nor treats a click in it as a click outside and closes.</div>
3322
3421
  <div>Filterable command palette container</div>
3323
3422
  <div class="hint">Command is a filterable list built on `cmdk`. Type to narrow the options,
3324
3423
  navigate with the arrow keys, select with Enter. It carries its own panel
3325
- chrome, so dropping it inside a `Popover.Content` yields a combobox.</div>
3424
+ chrome, so dropping it inside a `Popover.Content` yields a combobox.
3425
+ `useCommandState` reads the search text and results of the surrounding
3426
+ `Command.Root` from any component rendered inside it.</div>
3326
3427
  <div class="kv"><span class="chip">summary: Filterable command palette container</span><span class="chip">see: {@link https://github.com/pacocoursey/cmdk cmdk}</span><span class="chip">example: ```tsx
3327
3428
  import { Command } from &#39;@agentero/design-system/command&#39;;
3328
3429
 
@@ -3544,6 +3645,23 @@ list does not invite the pointer.</div>
3544
3645
  &lt;/Command.Item&gt;
3545
3646
  &lt;/Command.List&gt;
3546
3647
  &lt;Command.Empty&gt;No results found.&lt;/Command.Empty&gt;
3648
+ &lt;/Command.Root&gt;;</code></pre>
3649
+ </div>
3650
+ <div class="note ok">
3651
+ <div class="row">
3652
+ <span class="ex-name">Reading State</span>
3653
+ <span class="badge ok">story ok</span>
3654
+ </div>
3655
+
3656
+ <div class="hint">`useCommandState` reads the surrounding `Root` from a component inside it. Here the row
3657
+ offering to create an agency appears only when the search text matches no existing one.</div>
3658
+ <pre><code>const ReadingState = () =&gt; &lt;Command.Root label=&quot;Search agencies&quot;&gt;
3659
+ &lt;Command.Input placeholder=&quot;Search agencies...&quot; /&gt;
3660
+ &lt;Command.List&gt;
3661
+ &lt;Command.Item&gt;Pinnacle Shield Insurance&lt;/Command.Item&gt;
3662
+ &lt;Command.Item&gt;Horizon Risk Solutions&lt;/Command.Item&gt;
3663
+ &lt;CreateItem /&gt;
3664
+ &lt;/Command.List&gt;
3547
3665
  &lt;/Command.Root&gt;;</code></pre>
3548
3666
  </div>
3549
3667
  </div>
@@ -5317,7 +5435,7 @@ label with its description, or a control with its messages, in
5317
5435
  `Field.Content`. A control sets its own width: `Input` is `w-full`, so it
5318
5436
  gets one here.</div>
5319
5437
  <pre><code>const Horizontal = () =&gt; (
5320
- &lt;Field.Group className=&quot;w-[40rem]&quot;&gt;
5438
+ &lt;Field.Group className=&quot;w-160&quot;&gt;
5321
5439
  &lt;Field.Root orientation=&quot;horizontal&quot; data-testid=&quot;short&quot;&gt;
5322
5440
  &lt;Label&gt;Full name&lt;/Label&gt;
5323
5441
  &lt;DemoInput defaultValue=&quot;Rafa Moro&quot; className=&quot;w-72&quot; /&gt;
@@ -5360,7 +5478,7 @@ measures the field itself, so it needs no particular wrapper. A width that
5360
5478
  should only apply once horizontal goes behind `@md/field:`.</div>
5361
5479
  <pre><code>const Responsive = () =&gt; (
5362
5480
  &lt;div className=&quot;flex flex-col gap-8&quot;&gt;
5363
- &lt;div className=&quot;w-full max-w-[40rem]&quot; data-testid=&quot;wide&quot;&gt;
5481
+ &lt;div className=&quot;w-full max-w-160&quot; data-testid=&quot;wide&quot;&gt;
5364
5482
  &lt;Field.Root orientation=&quot;responsive&quot;&gt;
5365
5483
  &lt;Label&gt;Email&lt;/Label&gt;
5366
5484
  &lt;Field.Content className=&quot;@md/field:w-72&quot;&gt;
@@ -5455,7 +5573,27 @@ part wins over what `Field.Root` provides.</div>
5455
5573
  errors, a bare `&lt;Field.Error /&gt;` renders them — here with `useState` and no
5456
5574
  form library. `InputContext` extends the same way with `value`, `onChange`,
5457
5575
  `onBlur` and `name`; see `FieldText`.</div>
5458
- <pre><code>const ErrorsFromContext = () =&gt; &lt;FieldWithSuppliedErrors /&gt;;</code></pre>
5576
+ <pre><code>const ErrorsFromContext = () =&gt; {
5577
+ const [value, setValue] = useState(&#39;&#39;);
5578
+ const [touched, setTouched] = useState(false);
5579
+
5580
+ const errors = touched &amp;&amp; !value ? [{ message: &#39;Required&#39; }] : undefined;
5581
+
5582
+ return (
5583
+ &lt;Field.Root invalid={!!errors} required&gt;
5584
+ &lt;Label&gt;Email&lt;/Label&gt;
5585
+ &lt;DemoInput
5586
+ type=&quot;email&quot;
5587
+ value={value}
5588
+ onChange={event =&gt; setValue(event.target.value)}
5589
+ onBlur={() =&gt; setTouched(true)}
5590
+ /&gt;
5591
+ &lt;ErrorProvider errors={errors}&gt;
5592
+ &lt;Field.Error /&gt;
5593
+ &lt;/ErrorProvider&gt;
5594
+ &lt;/Field.Root&gt;
5595
+ );
5596
+ };</code></pre>
5459
5597
  </div>
5460
5598
  </div>
5461
5599
 
@@ -5703,7 +5841,29 @@ through untouched.</div>
5703
5841
  `&lt;Input /&gt;` becomes controlled, and the errors to `FieldContext`, so a bare
5704
5842
  `&lt;Field.Error /&gt;` renders them — here with `useState` and no form library.
5705
5843
  The input&#39;s own `onChange` still runs, chained after the provider&#39;s.</div>
5706
- <pre><code>const ControlledFromContext = () =&gt; &lt;ControlledField /&gt;;</code></pre>
5844
+ <pre><code>const ControlledFromContext = () =&gt; {
5845
+ const [value, setValue] = useState(&#39;&#39;);
5846
+ const [touched, setTouched] = useState(false);
5847
+
5848
+ const errors = touched &amp;&amp; !value ? [{ message: &#39;Required&#39; }] : undefined;
5849
+
5850
+ return (
5851
+ &lt;FieldText invalid={!!errors} required&gt;
5852
+ &lt;ContextExtension
5853
+ inputProps={{
5854
+ name: &#39;email&#39;,
5855
+ value,
5856
+ onChange: event =&gt; setValue(event.target.value),
5857
+ onBlur: () =&gt; setTouched(true)
5858
+ }}
5859
+ errors={errors}&gt;
5860
+ &lt;Label&gt;Email&lt;/Label&gt;
5861
+ &lt;Input type=&quot;email&quot; onChange={onOwnChange} /&gt;
5862
+ &lt;Field.Error /&gt;
5863
+ &lt;/ContextExtension&gt;
5864
+ &lt;/FieldText&gt;
5865
+ );
5866
+ };</code></pre>
5707
5867
  </div>
5708
5868
  </div>
5709
5869
 
@@ -5820,7 +5980,34 @@ onSubmit?: SubmitHandler&lt;AgencyValues&gt;</code></pre>
5820
5980
  both errors, marks both inputs invalid and focuses the first one — proof that
5821
5981
  react-hook-form&#39;s `ref` reached the inputs. Once filled in, `onSubmit`
5822
5982
  receives the typed values.</div>
5823
- <pre><code>const Default = () =&gt; &lt;AgencyForm /&gt;;</code></pre>
5983
+ <pre><code>const Default = () =&gt; {
5984
+ const methods = useForm&lt;AgencyValues&gt;({ defaultValues: { agencyName: &#39;&#39;, email: &#39;&#39; } });
5985
+
5986
+ return (
5987
+ &lt;Form.Root
5988
+ methods={methods}
5989
+ onSubmit={onSubmit}
5990
+ aria-label=&quot;Agency profile&quot;
5991
+ className=&quot;flex flex-col items-start gap-7&quot;&gt;
5992
+ &lt;Field.Group&gt;
5993
+ &lt;FormText
5994
+ name=&quot;agencyName&quot;
5995
+ label=&quot;Agency name&quot;
5996
+ required
5997
+ rules={{ required: &#39;Enter the agency name.&#39; }}
5998
+ /&gt;
5999
+ &lt;FormText
6000
+ name=&quot;email&quot;
6001
+ label=&quot;Email&quot;
6002
+ required
6003
+ rules={{ required: &#39;Enter the agency email.&#39; }}
6004
+ inputProps={{ type: &#39;email&#39;, autoComplete: &#39;email&#39; }}
6005
+ /&gt;
6006
+ &lt;/Field.Group&gt;
6007
+ &lt;Button type=&quot;submit&quot;&gt;Save&lt;/Button&gt;
6008
+ &lt;/Form.Root&gt;
6009
+ );
6010
+ };</code></pre>
5824
6011
  </div>
5825
6012
  <div class="note ok">
5826
6013
  <div class="row">
@@ -5832,7 +6019,20 @@ receives the typed values.</div>
5832
6019
  announces it, but the form renders `noValidate`: without `rules`, submitting
5833
6020
  the field empty is not blocked by the browser and `onSubmit` still runs.
5834
6021
  Validation is react-hook-form&#39;s alone.</div>
5835
- <pre><code>const NoValidate = () =&gt; &lt;NicknameForm /&gt;;</code></pre>
6022
+ <pre><code>const NoValidate = () =&gt; {
6023
+ const methods = useForm&lt;NicknameValues&gt;({ defaultValues: { nickname: &#39;&#39; } });
6024
+
6025
+ return (
6026
+ &lt;Form.Root
6027
+ methods={methods}
6028
+ onSubmit={onSubmit}
6029
+ aria-label=&quot;Display name&quot;
6030
+ className=&quot;flex flex-col items-start gap-7&quot;&gt;
6031
+ &lt;FormText name=&quot;nickname&quot; label=&quot;Display name&quot; required /&gt;
6032
+ &lt;Button type=&quot;submit&quot;&gt;Save&lt;/Button&gt;
6033
+ &lt;/Form.Root&gt;
6034
+ );
6035
+ };</code></pre>
5836
6036
  </div>
5837
6037
  </div>
5838
6038
 
@@ -5907,7 +6107,7 @@ with the `FieldText`, `Label`, `Input` and `Field.*` primitives directly.</div>
5907
6107
  * a `Form.Root`. A `control` whose resolver transforms the values is
5908
6108
  * accepted as is.
5909
6109
  */
5910
- control?: Control&lt;FieldValues, any, FieldValues&gt;
6110
+ control?: Control&lt;FieldValues, any, unknown&gt;
5911
6111
 
5912
6112
  /**
5913
6113
  * `id` for the control. Generated when omitted. Set it here, not on the
@@ -6033,14 +6233,29 @@ tooltipSide?: &quot;top&quot; | &quot;bottom&quot; | &quot;left&quot; | &quot;ri
6033
6233
  <div class="hint">The field consumers write: a `name`, a `label` and the validation rule.
6034
6234
  Submitting empty renders the error and marks the input invalid; typing
6035
6235
  clears both and the value reaches `onSubmit`.</div>
6036
- <pre><code>const Default = () =&gt; &lt;AgencyNameForm
6037
- name=&quot;agencyName&quot;
6038
- label=&quot;Agency name&quot;
6039
- orientation=&quot;vertical&quot;
6040
- required
6041
- optional={false}
6042
- disabled={false}
6043
- readOnly={false} /&gt;;</code></pre>
6236
+ <pre><code>const Default = () =&gt; {
6237
+ const methods = useForm&lt;AgencyValues&gt;({ defaultValues: { agencyName: &#39;&#39; } });
6238
+
6239
+ return (
6240
+ &lt;Form.Root
6241
+ methods={methods}
6242
+ onSubmit={onSubmit}
6243
+ aria-label=&quot;Agency&quot;
6244
+ className=&quot;flex flex-col items-start gap-7&quot;&gt;
6245
+ &lt;FormText
6246
+ rules={{ required: &#39;Enter the agency name.&#39; }}
6247
+ inputProps={{ placeholder: &#39;Acme Insurance&#39; }}
6248
+ label=&quot;Agency name&quot;
6249
+ orientation=&quot;vertical&quot;
6250
+ required
6251
+ optional={false}
6252
+ disabled={false}
6253
+ readOnly={false}
6254
+ name=&quot;agencyName&quot; /&gt;
6255
+ &lt;Button type=&quot;submit&quot;&gt;Save&lt;/Button&gt;
6256
+ &lt;/Form.Root&gt;
6257
+ );
6258
+ };</code></pre>
6044
6259
  </div>
6045
6260
  <div class="note ok">
6046
6261
  <div class="row">
@@ -6052,7 +6267,21 @@ clears both and the value reaches `onSubmit`.</div>
6052
6267
  `aria-describedby`. `tooltip` is an info button beside the label for details
6053
6268
  worth a click; it is a sibling of the `&lt;label&gt;`, so the field&#39;s accessible
6054
6269
  name stays the label text.</div>
6055
- <pre><code>const WithDescriptionAndTooltip = () =&gt; &lt;TaxIdForm /&gt;;</code></pre>
6270
+ <pre><code>const WithDescriptionAndTooltip = () =&gt; {
6271
+ const methods = useForm&lt;TaxValues&gt;({ defaultValues: { taxId: &#39;&#39; } });
6272
+
6273
+ return (
6274
+ &lt;Form.Root methods={methods} aria-label=&quot;Tax details&quot;&gt;
6275
+ &lt;FormText
6276
+ name=&quot;taxId&quot;
6277
+ label=&quot;Tax ID&quot;
6278
+ tooltip=&quot;The EIN the IRS issued to the agency.&quot;
6279
+ description=&quot;Nine digits, with or without the dash.&quot;
6280
+ inputProps={{ inputMode: &#39;numeric&#39;, autoComplete: &#39;off&#39; }}
6281
+ /&gt;
6282
+ &lt;/Form.Root&gt;
6283
+ );
6284
+ };</code></pre>
6056
6285
  </div>
6057
6286
  <div class="note ok">
6058
6287
  <div class="row">
@@ -6063,7 +6292,15 @@ name stays the label text.</div>
6063
6292
  <div class="hint">The forms mark optional fields rather than required ones: `optional`
6064
6293
  appends a muted suffix to the label and leaves the input without the
6065
6294
  `required` attribute.</div>
6066
- <pre><code>const Optional = () =&gt; &lt;PhoneForm /&gt;;</code></pre>
6295
+ <pre><code>const Optional = () =&gt; {
6296
+ const methods = useForm&lt;PhoneValues&gt;({ defaultValues: { phone: &#39;&#39; } });
6297
+
6298
+ return (
6299
+ &lt;Form.Root methods={methods} aria-label=&quot;Contact&quot;&gt;
6300
+ &lt;FormText name=&quot;phone&quot; label=&quot;Phone number&quot; optional inputProps={{ type: &#39;tel&#39; }} /&gt;
6301
+ &lt;/Form.Root&gt;
6302
+ );
6303
+ };</code></pre>
6067
6304
  </div>
6068
6305
  <div class="note ok">
6069
6306
  <div class="row">
@@ -6074,7 +6311,28 @@ appends a muted suffix to the label and leaves the input without the
6074
6311
  <div class="hint">`rules` are react-hook-form&#39;s, and `name` accepts nested paths typed against
6075
6312
  the form&#39;s values through the generic. `rules.required` validates but does
6076
6313
  not mark the field required: that stays an explicit `required` prop.</div>
6077
- <pre><code>const WithRules = () =&gt; &lt;NpnForm /&gt;;</code></pre>
6314
+ <pre><code>const WithRules = () =&gt; {
6315
+ const methods = useForm&lt;LicenseValues&gt;({ defaultValues: { agency: { npn: &#39;&#39; } } });
6316
+
6317
+ return (
6318
+ &lt;Form.Root
6319
+ methods={methods}
6320
+ onSubmit={onSubmit}
6321
+ aria-label=&quot;License&quot;
6322
+ className=&quot;flex flex-col items-start gap-7&quot;&gt;
6323
+ &lt;FormText&lt;LicenseValues&gt;
6324
+ name=&quot;agency.npn&quot;
6325
+ label=&quot;NPN&quot;
6326
+ rules={{
6327
+ required: &#39;Enter your NPN.&#39;,
6328
+ pattern: { value: /^\d{6,10}$/, message: &#39;An NPN is 6 to 10 digits.&#39; }
6329
+ }}
6330
+ inputProps={{ inputMode: &#39;numeric&#39; }}
6331
+ /&gt;
6332
+ &lt;Button type=&quot;submit&quot;&gt;Save&lt;/Button&gt;
6333
+ &lt;/Form.Root&gt;
6334
+ );
6335
+ };</code></pre>
6078
6336
  </div>
6079
6337
  <div class="note ok">
6080
6338
  <div class="row">
@@ -6086,7 +6344,36 @@ not mark the field required: that stays an explicit `required` prop.</div>
6086
6344
  that failed instead of stopping at the first, and the field lists them all.
6087
6345
  With the default `&#39;firstError&#39;` a single message is rendered, as in the other
6088
6346
  stories.</div>
6089
- <pre><code>const AllErrors = () =&gt; &lt;PasswordRulesForm /&gt;;</code></pre>
6347
+ <pre><code>const AllErrors = () =&gt; {
6348
+ const methods = useForm&lt;PasswordValues&gt;({
6349
+ defaultValues: { password: &#39;&#39; },
6350
+ criteriaMode: &#39;all&#39;
6351
+ });
6352
+
6353
+ return (
6354
+ &lt;Form.Root
6355
+ methods={methods}
6356
+ onSubmit={onSubmit}
6357
+ aria-label=&quot;Account security&quot;
6358
+ className=&quot;flex flex-col items-start gap-7&quot;&gt;
6359
+ &lt;FormText
6360
+ name=&quot;password&quot;
6361
+ label=&quot;Password&quot;
6362
+ required
6363
+ inputProps={{ type: &#39;password&#39; }}
6364
+ rules={{
6365
+ required: &#39;Enter a password.&#39;,
6366
+ validate: {
6367
+ uppercase: value =&gt; /[A-Z]/.test(value) || &#39;At least one uppercase letter.&#39;,
6368
+ number: value =&gt; /[0-9]/.test(value) || &#39;At least one number.&#39;,
6369
+ length: value =&gt; value.length &gt;= 10 || &#39;At least 10 characters.&#39;
6370
+ }
6371
+ }}
6372
+ /&gt;
6373
+ &lt;Button type=&quot;submit&quot;&gt;Save&lt;/Button&gt;
6374
+ &lt;/Form.Root&gt;
6375
+ );
6376
+ };</code></pre>
6090
6377
  </div>
6091
6378
  <div class="note ok">
6092
6379
  <div class="row">
@@ -6098,7 +6385,25 @@ stories.</div>
6098
6385
  `maxLength`, `size`… — and its handlers. A consumer `onChange` runs after
6099
6386
  react-hook-form&#39;s: by the time it fires, `getValues` already returns the new
6100
6387
  value.</div>
6101
- <pre><code>const InputPropsChaining = () =&gt; &lt;MobileForm /&gt;;</code></pre>
6388
+ <pre><code>const InputPropsChaining = () =&gt; {
6389
+ const methods = useForm&lt;PhoneValues&gt;({ defaultValues: { phone: &#39;&#39; } });
6390
+
6391
+ return (
6392
+ &lt;Form.Root methods={methods} aria-label=&quot;Mobile&quot;&gt;
6393
+ &lt;FormText
6394
+ name=&quot;phone&quot;
6395
+ label=&quot;Mobile&quot;
6396
+ inputProps={{
6397
+ type: &#39;tel&#39;,
6398
+ placeholder: &#39;5550000000&#39;,
6399
+ maxLength: 10,
6400
+ size: &#39;lg&#39;,
6401
+ onChange: event =&gt; onOwnChange(methods.getValues(&#39;phone&#39;), event.target.value)
6402
+ }}
6403
+ /&gt;
6404
+ &lt;/Form.Root&gt;
6405
+ );
6406
+ };</code></pre>
6102
6407
  </div>
6103
6408
  <div class="note ok">
6104
6409
  <div class="row">
@@ -6110,7 +6415,33 @@ value.</div>
6110
6415
  control in a row, and `className` on the root or in `inputProps` sizes them.
6111
6416
  The `Input` is full-width by default, so a settings row gives it a width.
6112
6417
  `responsive` stacks the field below 28rem and rows it from there.</div>
6113
- <pre><code>const Horizontal = () =&gt; &lt;ProfileForm /&gt;;</code></pre>
6418
+ <pre><code>const Horizontal = () =&gt; {
6419
+ const methods = useForm&lt;ProfileValues&gt;({
6420
+ defaultValues: { fullName: &#39;Rafa Moro&#39;, title: &#39;&#39; }
6421
+ });
6422
+
6423
+ return (
6424
+ &lt;Form.Root methods={methods} aria-label=&quot;Profile&quot; className=&quot;w-[40rem]&quot;&gt;
6425
+ &lt;Field.Group&gt;
6426
+ &lt;FormText
6427
+ name=&quot;fullName&quot;
6428
+ label=&quot;Full name&quot;
6429
+ orientation=&quot;horizontal&quot;
6430
+ inputProps={{ className: &#39;w-72&#39; }}
6431
+ data-testid=&quot;short&quot;
6432
+ /&gt;
6433
+ &lt;FormText
6434
+ name=&quot;title&quot;
6435
+ label=&quot;Title&quot;
6436
+ description=&quot;Shown on your public profile.&quot;
6437
+ orientation=&quot;horizontal&quot;
6438
+ inputProps={{ className: &#39;w-72&#39;, placeholder: &#39;Software engineer&#39; }}
6439
+ data-testid=&quot;long&quot;
6440
+ /&gt;
6441
+ &lt;/Field.Group&gt;
6442
+ &lt;/Form.Root&gt;
6443
+ );
6444
+ };</code></pre>
6114
6445
  </div>
6115
6446
  <div class="note ok">
6116
6447
  <div class="row">
@@ -6121,7 +6452,25 @@ The `Input` is full-width by default, so a settings row gives it a width.
6121
6452
  <div class="hint">`disabled` disables the input and marks the field, and the value still
6122
6453
  reaches `onSubmit` — unlike react-hook-form&#39;s own `disabled` option, which
6123
6454
  drops it. Use `readOnly` for a value the user can copy but not edit.</div>
6124
- <pre><code>const Disabled = () =&gt; &lt;AccountForm /&gt;;</code></pre>
6455
+ <pre><code>const Disabled = () =&gt; {
6456
+ const methods = useForm&lt;AccountValues&gt;({
6457
+ defaultValues: { agencyId: &#39;AG-48213&#39;, email: &#39;ops@acme-insurance.com&#39; }
6458
+ });
6459
+
6460
+ return (
6461
+ &lt;Form.Root
6462
+ methods={methods}
6463
+ onSubmit={onSubmit}
6464
+ aria-label=&quot;Account&quot;
6465
+ className=&quot;flex flex-col items-start gap-7&quot;&gt;
6466
+ &lt;Field.Group&gt;
6467
+ &lt;FormText name=&quot;agencyId&quot; label=&quot;Agency ID&quot; disabled /&gt;
6468
+ &lt;FormText name=&quot;email&quot; label=&quot;Email&quot; inputProps={{ type: &#39;email&#39; }} /&gt;
6469
+ &lt;/Field.Group&gt;
6470
+ &lt;Button type=&quot;submit&quot;&gt;Save&lt;/Button&gt;
6471
+ &lt;/Form.Root&gt;
6472
+ );
6473
+ };</code></pre>
6125
6474
  </div>
6126
6475
  <div class="note ok">
6127
6476
  <div class="row">
@@ -6132,7 +6481,26 @@ drops it. Use `readOnly` for a value the user can copy but not edit.</div>
6132
6481
  <div class="hint">react-hook-form&#39;s `ref` reaches the `&lt;input&gt;`, so `setFocus` and the focus
6133
6482
  on the first invalid field after submit both work with no ref written by
6134
6483
  hand.</div>
6135
- <pre><code>const SetFocus = () =&gt; &lt;SetFocusForm /&gt;;</code></pre>
6484
+ <pre><code>const SetFocus = () =&gt; {
6485
+ const methods = useForm&lt;AgencyValues &amp; PhoneValues&gt;({
6486
+ defaultValues: { agencyName: &#39;&#39;, phone: &#39;&#39; }
6487
+ });
6488
+
6489
+ return (
6490
+ &lt;Form.Root
6491
+ methods={methods}
6492
+ aria-label=&quot;Agency contact&quot;
6493
+ className=&quot;flex flex-col items-start gap-7&quot;&gt;
6494
+ &lt;Field.Group&gt;
6495
+ &lt;FormText name=&quot;agencyName&quot; label=&quot;Agency name&quot; /&gt;
6496
+ &lt;FormText name=&quot;phone&quot; label=&quot;Phone number&quot; inputProps={{ type: &#39;tel&#39; }} /&gt;
6497
+ &lt;/Field.Group&gt;
6498
+ &lt;Button type=&quot;button&quot; variant=&quot;secondary&quot; onClick={() =&gt; methods.setFocus(&#39;phone&#39;)}&gt;
6499
+ Focus phone
6500
+ &lt;/Button&gt;
6501
+ &lt;/Form.Root&gt;
6502
+ );
6503
+ };</code></pre>
6136
6504
  </div>
6137
6505
  </div>
6138
6506
 
@@ -7101,7 +7469,30 @@ open?: boolean</code></pre>
7101
7469
  </div>
7102
7470
 
7103
7471
  <div class="hint">Controlled with `open`/`onOpenChange` — the dominant pattern in both apps. Also closes on `Escape`.</div>
7104
- <pre><code>const Controlled = () =&gt; &lt;ControlledExample /&gt;;</code></pre>
7472
+ <pre><code>const Controlled = () =&gt; {
7473
+ const [isOpen, setIsOpen] = useState(false);
7474
+
7475
+ return (
7476
+ &lt;&gt;
7477
+ &lt;Button variant=&quot;secondary&quot; onClick={() =&gt; setIsOpen(true)}&gt;
7478
+ Open controlled
7479
+ &lt;/Button&gt;
7480
+ &lt;Modal.Root open={isOpen} onOpenChange={setIsOpen}&gt;
7481
+ &lt;Modal.Content&gt;
7482
+ &lt;Modal.Title&gt;Controlled modal&lt;/Modal.Title&gt;
7483
+ &lt;Modal.Body&gt;
7484
+ &lt;p&gt;The open state lives in the consumer.&lt;/p&gt;
7485
+ &lt;/Modal.Body&gt;
7486
+ &lt;Modal.Footer&gt;
7487
+ &lt;Button variant=&quot;primary&quot; onClick={() =&gt; setIsOpen(false)}&gt;
7488
+ Done
7489
+ &lt;/Button&gt;
7490
+ &lt;/Modal.Footer&gt;
7491
+ &lt;/Modal.Content&gt;
7492
+ &lt;/Modal.Root&gt;
7493
+ &lt;/&gt;
7494
+ );
7495
+ };</code></pre>
7105
7496
  </div>
7106
7497
  <div class="note ok">
7107
7498
  <div class="row">
@@ -8273,52 +8664,50 @@ keeps its padding.</div>
8273
8664
  </div>
8274
8665
  <div>Expandable rows with a detail panel</div>
8275
8666
  <div class="hint">`Table.ExpandButton` toggles a `Table.ExpandedRow` detail panel under its row.</div>
8276
- <pre><code>const ExpandableRows = args =&gt; {
8277
- const Demo = () =&gt; {
8278
- const [openId, setOpenId] = useState&lt;string | null&gt;(ROWS[0]!.id);
8279
- return (
8280
- &lt;Table.Root {...args}&gt;
8281
- &lt;Table.Head&gt;
8282
- &lt;Table.Row&gt;
8283
- &lt;Table.Header&gt;
8284
- &lt;span className=&quot;sr-only&quot;&gt;Expand&lt;/span&gt;
8285
- &lt;/Table.Header&gt;
8286
- &lt;Table.Header&gt;Name&lt;/Table.Header&gt;
8287
- &lt;Table.Header&gt;Email&lt;/Table.Header&gt;
8288
- &lt;Table.Header&gt;Role&lt;/Table.Header&gt;
8289
- &lt;/Table.Row&gt;
8290
- &lt;/Table.Head&gt;
8291
- &lt;Table.Body&gt;
8292
- {ROWS.slice(0, 4).map(row =&gt; (
8293
- &lt;Fragment key={row.id}&gt;
8294
- &lt;Table.Row&gt;
8295
- &lt;Table.Cell&gt;
8296
- &lt;Table.ExpandButton
8297
- isExpanded={openId === row.id}
8298
- toggleExpanded={() =&gt; setOpenId(openId === row.id ? null : row.id)}
8299
- aria-label={`Toggle ${row.name}`}
8300
- /&gt;
8667
+ <pre><code>const ExpandableRows = () =&gt; {
8668
+ const [openId, setOpenId] = useState&lt;string | null&gt;(ROWS[0]!.id);
8669
+
8670
+ return (
8671
+ &lt;Table.Root size=&quot;md&quot; embed={false}&gt;
8672
+ &lt;Table.Head&gt;
8673
+ &lt;Table.Row&gt;
8674
+ &lt;Table.Header&gt;
8675
+ &lt;span className=&quot;sr-only&quot;&gt;Expand&lt;/span&gt;
8676
+ &lt;/Table.Header&gt;
8677
+ &lt;Table.Header&gt;Name&lt;/Table.Header&gt;
8678
+ &lt;Table.Header&gt;Email&lt;/Table.Header&gt;
8679
+ &lt;Table.Header&gt;Role&lt;/Table.Header&gt;
8680
+ &lt;/Table.Row&gt;
8681
+ &lt;/Table.Head&gt;
8682
+ &lt;Table.Body&gt;
8683
+ {ROWS.slice(0, 4).map(row =&gt; (
8684
+ &lt;Fragment key={row.id}&gt;
8685
+ &lt;Table.Row&gt;
8686
+ &lt;Table.Cell&gt;
8687
+ &lt;Table.ExpandButton
8688
+ isExpanded={openId === row.id}
8689
+ toggleExpanded={() =&gt; setOpenId(openId === row.id ? null : row.id)}
8690
+ aria-label={`Toggle ${row.name}`}
8691
+ /&gt;
8692
+ &lt;/Table.Cell&gt;
8693
+ &lt;Table.Cell className=&quot;font-bold&quot;&gt;{row.name}&lt;/Table.Cell&gt;
8694
+ &lt;Table.Cell className=&quot;text-text-default-base-secondary&quot;&gt;{row.email}&lt;/Table.Cell&gt;
8695
+ &lt;Table.Cell&gt;{row.role}&lt;/Table.Cell&gt;
8696
+ &lt;/Table.Row&gt;
8697
+ {openId === row.id &amp;&amp; (
8698
+ &lt;Table.ExpandedRow&gt;
8699
+ &lt;Table.Cell colSpan={4}&gt;
8700
+ &lt;div className=&quot;py-2 text-text-default-base-secondary&quot;&gt;
8701
+ Extended details for {row.name} — {row.amount} lifetime value.
8702
+ &lt;/div&gt;
8301
8703
  &lt;/Table.Cell&gt;
8302
- &lt;Table.Cell className=&quot;font-bold&quot;&gt;{row.name}&lt;/Table.Cell&gt;
8303
- &lt;Table.Cell className=&quot;text-text-default-base-secondary&quot;&gt;{row.email}&lt;/Table.Cell&gt;
8304
- &lt;Table.Cell&gt;{row.role}&lt;/Table.Cell&gt;
8305
- &lt;/Table.Row&gt;
8306
- {openId === row.id &amp;&amp; (
8307
- &lt;Table.ExpandedRow&gt;
8308
- &lt;Table.Cell colSpan={4}&gt;
8309
- &lt;div className=&quot;py-2 text-text-default-base-secondary&quot;&gt;
8310
- Extended details for {row.name} — {row.amount} lifetime value.
8311
- &lt;/div&gt;
8312
- &lt;/Table.Cell&gt;
8313
- &lt;/Table.ExpandedRow&gt;
8314
- )}
8315
- &lt;/Fragment&gt;
8316
- ))}
8317
- &lt;/Table.Body&gt;
8318
- &lt;/Table.Root&gt;
8319
- );
8320
- };
8321
- return &lt;Demo /&gt;;
8704
+ &lt;/Table.ExpandedRow&gt;
8705
+ )}
8706
+ &lt;/Fragment&gt;
8707
+ ))}
8708
+ &lt;/Table.Body&gt;
8709
+ &lt;/Table.Root&gt;
8710
+ );
8322
8711
  };</code></pre>
8323
8712
  </div>
8324
8713
  </div>