torch-glare 2.4.1 → 2.4.3

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.
Files changed (85) hide show
  1. package/apps/lib/components/Button.tsx +1 -1
  2. package/apps/lib/components/Card.tsx +47 -20
  3. package/apps/lib/components/ColorPicker.tsx +441 -0
  4. package/apps/lib/components/ConclusionHeader.tsx +148 -0
  5. package/apps/lib/components/DatePicker.tsx +2 -0
  6. package/apps/lib/components/Drawer.tsx +66 -24
  7. package/apps/lib/components/FormBuilder/context.ts +68 -0
  8. package/apps/lib/components/FormBuilder/fields/ChoiceFields.tsx +60 -0
  9. package/apps/lib/components/FormBuilder/fields/ColorField.tsx +59 -0
  10. package/apps/lib/components/FormBuilder/fields/CustomField.tsx +11 -0
  11. package/apps/lib/components/FormBuilder/fields/DateField.tsx +32 -0
  12. package/apps/lib/components/FormBuilder/fields/FieldArray.tsx +74 -0
  13. package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +148 -0
  14. package/apps/lib/components/FormBuilder/fields/FileField.tsx +39 -0
  15. package/apps/lib/components/FormBuilder/fields/OptionListFields.tsx +132 -0
  16. package/apps/lib/components/FormBuilder/fields/OtpField.tsx +31 -0
  17. package/apps/lib/components/FormBuilder/fields/PhoneField.tsx +83 -0
  18. package/apps/lib/components/FormBuilder/fields/RichTextEditorField.tsx +31 -0
  19. package/apps/lib/components/FormBuilder/fields/SelectField.tsx +91 -0
  20. package/apps/lib/components/FormBuilder/fields/SignatureField.tsx +157 -0
  21. package/apps/lib/components/FormBuilder/fields/SliderField.tsx +67 -0
  22. package/apps/lib/components/FormBuilder/fields/SwitchBoxField.tsx +41 -0
  23. package/apps/lib/components/FormBuilder/fields/TableField.tsx +308 -0
  24. package/apps/lib/components/FormBuilder/fields/TextField.tsx +213 -0
  25. package/apps/lib/components/FormBuilder/fields/TreeSelectField.tsx +43 -0
  26. package/apps/lib/components/FormBuilder/fields/countries.ts +303 -0
  27. package/apps/lib/components/FormBuilder/fields/index.ts +25 -0
  28. package/apps/lib/components/FormBuilder/form-builder.tsx +279 -0
  29. package/apps/lib/components/FormBuilder/header.tsx +101 -0
  30. package/apps/lib/components/FormBuilder/index.ts +33 -0
  31. package/apps/lib/components/FormBuilder/numberFormat.ts +16 -0
  32. package/apps/lib/components/FormBuilder/stepper.tsx +290 -0
  33. package/apps/lib/components/FormBuilder/submit.tsx +38 -0
  34. package/apps/lib/components/FormBuilder/types.ts +261 -0
  35. package/apps/lib/components/FormRenderer/FormDrawer.tsx +128 -0
  36. package/apps/lib/components/FormRenderer/form-renderer.tsx +111 -0
  37. package/apps/lib/components/FormRenderer/index.ts +4 -0
  38. package/apps/lib/components/FormRenderer/types.ts +77 -0
  39. package/apps/lib/components/FormSummary.tsx +282 -0
  40. package/apps/lib/components/ImageAttachment.tsx +36 -61
  41. package/apps/lib/components/Label.tsx +49 -42
  42. package/apps/lib/components/Popover.tsx +6 -2
  43. package/apps/lib/components/RadioCard.tsx +2 -0
  44. package/apps/lib/components/SearchableSelect.tsx +16 -5
  45. package/apps/lib/components/SectionBlock.tsx +16 -8
  46. package/apps/lib/components/Select.tsx +41 -120
  47. package/apps/lib/components/TextEditor/RichTextField.tsx +46 -0
  48. package/apps/lib/components/{TextEditor.tsx → TextEditor/TextEditor.tsx} +63 -9
  49. package/apps/lib/components/TextEditor/TextEditorToolbar.tsx +429 -0
  50. package/apps/lib/components/TextEditor/editor-tools/AlignmentTune.ts +70 -0
  51. package/apps/lib/components/TextEditor/editor-tools/ColorInlineTool.ts +50 -0
  52. package/apps/lib/components/TextEditor/editor-tools/StrikethroughInlineTool.ts +48 -0
  53. package/apps/lib/components/TextEditor/editor-tools/inlineFormat.ts +98 -0
  54. package/apps/lib/{types → components/TextEditor}/editorjs.d.ts +19 -0
  55. package/apps/lib/components/TextEditor/index.ts +7 -0
  56. package/apps/lib/components/Textarea.tsx +1 -1
  57. package/apps/lib/layouts/FieldSection.tsx +25 -22
  58. package/apps/lib/registry.json +51 -58
  59. package/apps/lib/tsconfig.tsbuildinfo +1 -0
  60. package/apps/lib/utils/color.ts +175 -0
  61. package/dist/src/shared/tailwindInit.d.ts.map +1 -1
  62. package/dist/src/shared/tailwindInit.js +3 -0
  63. package/dist/src/shared/tailwindInit.js.map +1 -1
  64. package/docs/components/card.md +4 -2
  65. package/docs/components/chart-block-tool.md +5 -4
  66. package/docs/components/color-picker.md +101 -0
  67. package/docs/components/conclusion-header.md +80 -0
  68. package/docs/components/drawer.md +153 -102
  69. package/docs/components/form-builder.md +260 -0
  70. package/docs/components/form-renderer.md +234 -0
  71. package/docs/components/form-summary.md +123 -0
  72. package/docs/components/image-attachment.md +10 -4
  73. package/docs/components/searchable-select.md +50 -46
  74. package/docs/components/table-dnd-wrapper.md +5 -3
  75. package/docs/components/text-editor.md +18 -0
  76. package/docs/how-to/form-and-list-recipes.md +26 -19
  77. package/docs/how-to/forms-with-form-builder.md +411 -0
  78. package/docs/how-to/guides.md +153 -179
  79. package/docs/reference/tailwind-plugins.md +11 -1
  80. package/docs/tutorials/building-first-form.md +150 -159
  81. package/docs/tutorials/getting-started.md +9 -0
  82. package/package.json +1 -1
  83. /package/apps/lib/components/{ChartBlockTool.ts → TextEditor/ChartBlockTool.ts} +0 -0
  84. /package/apps/lib/components/{TableDnDWrapper.ts → TextEditor/TableDnDWrapper.ts} +0 -0
  85. /package/apps/lib/{utils → components/TextEditor}/markdownParser.ts +0 -0
@@ -0,0 +1,411 @@
1
+ ---
2
+ title: Forms with FormBuilder, FormRenderer & FormSummary
3
+ description: The canonical way to build forms in TORCH Glare. Author fields as JSX with FormBuilder, add page/drawer chrome with FormRenderer, and show live computed totals with FormSummary. Covers single, stepper, drawer, editing a record, and a full invoice example combining all three.
4
+ keywords:
5
+ [
6
+ form,
7
+ forms,
8
+ formbuilder,
9
+ formrenderer,
10
+ formsummary,
11
+ form-builder,
12
+ form-renderer,
13
+ form-summary,
14
+ validation,
15
+ zod,
16
+ resolver,
17
+ stepper,
18
+ drawer,
19
+ totals,
20
+ invoice,
21
+ calculation,
22
+ react-hook-form,
23
+ ]
24
+ ---
25
+
26
+ # Forms with FormBuilder, FormRenderer & FormSummary
27
+
28
+ **This is the way to build forms in TORCH Glare.** Do not hand-wire
29
+ `FormField` → `FormItem` → `FormControl` → `InputField` rows, and never track field state
30
+ with `useState` — that boilerplate is exactly what `FormBuilder` exists to remove.
31
+
32
+ Three components, layered:
33
+
34
+ | Component | Use it for | When |
35
+ | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
36
+ | **`FormBuilder`** | The form itself — fields authored as JSX children. Owns react-hook-form, validation, sections, steppers. | Always. This is the base. |
37
+ | **`FormRenderer`** | Wraps `FormBuilder` to add **chrome**: page-vs-drawer display, the title header, an `actions` slot for the Save, and a `summary` slot. | Real forms — prefer it over raw `FormBuilder`. |
38
+ | **`FormSummary`** | A read-only **calculation panel beside the form** — totals that recompute live as the user types. | Invoices, orders, anything with a "conclusion". |
39
+
40
+ Validation is **resolver-agnostic**: pass any react-hook-form resolver
41
+ (`zodResolver(schema)` is typical). The library never depends on zod.
42
+
43
+ ---
44
+
45
+ ## 1. A single-page form
46
+
47
+ Each `FormBuilder.*` field is one JSX child taking at least a `name`, plus `label`,
48
+ `placeholder`, `required`, `disabled`, `hidden`, `fullWidth`. Wrap them in `FormRenderer`,
49
+ give it a `header` for the title bar, and pass the Save via `actions`.
50
+
51
+ ```tsx
52
+ "use client";
53
+
54
+ import { z } from "zod";
55
+ import { zodResolver } from "@hookform/resolvers/zod";
56
+ import { FormBuilder } from "@/components/FormBuilder";
57
+ import { FormRenderer } from "@/components/FormRenderer";
58
+
59
+ const schema = z.object({
60
+ name: z.string().min(1, "Name is required"),
61
+ description: z.string(),
62
+ category: z.string().min(1, "Pick a category"),
63
+ price: z.number().positive("Must be positive").optional(),
64
+ active: z.boolean(),
65
+ agree: z.boolean().refine((v) => v === true, "You must agree to continue"),
66
+ });
67
+ type Values = z.infer<typeof schema>;
68
+
69
+ const DEFAULTS: Values = {
70
+ name: "",
71
+ description: "",
72
+ category: "",
73
+ price: undefined,
74
+ active: true,
75
+ agree: false,
76
+ };
77
+
78
+ const CATEGORY = [
79
+ { label: "Hardware", value: "hardware" },
80
+ { label: "Software", value: "software" },
81
+ ];
82
+
83
+ export function ItemForm({
84
+ onSave,
85
+ saving,
86
+ }: {
87
+ onSave: (v: Values) => Promise<void>;
88
+ saving?: boolean;
89
+ }) {
90
+ return (
91
+ <FormRenderer<Values>
92
+ onSubmit={onSave}
93
+ loading={saving}
94
+ resolver={zodResolver(schema)}
95
+ defaultValues={DEFAULTS}
96
+ header={{ title: "New item", variant: "new" }}
97
+ actions={<FormBuilder.Submit>Save</FormBuilder.Submit>}
98
+ >
99
+ <FormBuilder.Section title="Identity" color="Blue">
100
+ <FormBuilder.Text name="name" label="Name" required placeholder="e.g. Acme Widget" />
101
+ <FormBuilder.Textarea name="description" label="Description" fullWidth />
102
+ </FormBuilder.Section>
103
+
104
+ <FormBuilder.Section title="Classification" color="Red">
105
+ <FormBuilder.Select name="category" label="Category" required options={CATEGORY} />
106
+ <FormBuilder.Currency name="price" label="Base price" currencySymbol="$" />
107
+ </FormBuilder.Section>
108
+
109
+ <FormBuilder.Section title="Settings" color="Purple">
110
+ <FormBuilder.SwitchBox name="active" label="Active" subLabel="Enabled" />
111
+ <FormBuilder.Checkbox name="agree" label="I agree to the terms" required />
112
+ </FormBuilder.Section>
113
+ </FormRenderer>
114
+ );
115
+ }
116
+ ```
117
+
118
+ `FormBuilder.Section` groups fields in a `SectionBlock` (`color` is one of `Blue`, `Yellow`,
119
+ `Green`, `Red`, `Orange`, `Purple`, `Pink`, `Gray`). Pass the Save via `actions` — a
120
+ `FormBuilder.Submit`, which is loading-aware; it renders in the header action pill. (With raw
121
+ `FormBuilder`, put the same `FormBuilder.Submit` in a `FormBuilder.Header`.)
122
+
123
+ ### Field types
124
+
125
+ Every field, its underlying control, and the value your `onSubmit` receives:
126
+
127
+ | Component | Value |
128
+ | ---------------------------------------------------------------- | -------------------------------------------------------------- |
129
+ | `.Text` / `.Email` / `.Password` (`strengthMeter`) | `string` |
130
+ | `.Number` | `number` |
131
+ | `.Currency` (`currencySymbol`) | `number` |
132
+ | `.Textarea` | `string` |
133
+ | `.Select` / `.SearchableSelect` (`options`) | `string` |
134
+ | `.MultiSelect` / `.Tags` (`options`) | `string[]` |
135
+ | `.RadioList` (`options`, optional per-option `description`) | `string` |
136
+ | `.CheckboxGroup` (`options`) | `string[]` |
137
+ | `.RadioCards` (`options` with `description`) | `string` |
138
+ | `.Checkbox` (`subLabel`) | `boolean` |
139
+ | `.SwitchBox` (`subLabel`) | `boolean` |
140
+ | `.Otp` (`length`) | `string` |
141
+ | `.Slider` (`min`, `max`, `step`, `range`, `suffix`) | `number` (or `[number, number]` with `range`) |
142
+ | `.Color` (`presets`, `alpha`) | hex `string` |
143
+ | `.Phone` (`defaultCountry`, defaults to `+964`) | `string` (`"+<dial> <number>"`) |
144
+ | `.Date` | `Date` |
145
+ | `.DateRange` | `{ from, to }` |
146
+ | `.DateMultiple` | `Date[]` |
147
+ | `.DateTime` | `Date` |
148
+ | `.TreeSelect` (`nodes`, `getNodeId`, `getNodeLabel`) | node id (`string`) |
149
+ | `.File` / `.Image` (`accept`, `multiple`) | `File \| File[]` |
150
+ | `.RichText` | EditorJS `OutputData` |
151
+ | `.Signature` (`penColor`) | PNG data-URL `string` |
152
+ | `.FieldArray` (`children` render fn, `defaultItem`) | `object[]` |
153
+ | `.Table` (`columns`, `selectable`, `reorderable`, `defaultItem`) | `object[]` — editable grid; top-level child (not in a Section) |
154
+ | `.Custom` (`render`, `formatView`) | anything |
155
+
156
+ See the [FormBuilder](../components/form-builder.md) doc for the full prop tables.
157
+
158
+ ---
159
+
160
+ ## 2. Title header + action bar
161
+
162
+ `FormRenderer`'s `header` prop renders a title pill on the left: `header={{ title: 'Acme Widget
163
+ Pro', variant: 'edit' }}` (variants: `new`, `edit`, `detail`). The **action pill** on the right is
164
+ whatever you pass to `actions` — put the Save there:
165
+ `actions={<FormBuilder.Submit>Save invoice</FormBuilder.Submit>}`. A bare `FormBuilder.Submit`
166
+ auto-targets the form (via a form-id context), so it submits even though the header renders
167
+ _outside_ the `<form>`.
168
+
169
+ If you use raw `FormBuilder`, the same bar is `FormBuilder.Header` with a `FormBuilder.Submit`
170
+ child.
171
+
172
+ ---
173
+
174
+ ## 3. Stepper (steps are components)
175
+
176
+ Every step's fields stay **mounted and registered** — the whole form is live regardless of
177
+ which step shows; the stepper only toggles visibility. **Navigation is the step buttons
178
+ themselves**: backward is free, clicking forward validates the steps in between and stops at
179
+ the first one with errors. The Save is the header `actions` — it submits every step's fields at
180
+ once, from any step.
181
+
182
+ You pass just the Submit as `actions`; for a stepper, FormRenderer **auto-prepends chevron
183
+ Back/Next controls + a divider** before it (`[◀] [▶] │ Save`). Back is disabled on the first
184
+ step; Next validates then advances (disabled on the last). A step that **passes validation stays
185
+ checked** in the rail even after you navigate back — a live error still shows it red.
186
+
187
+ ```tsx
188
+ <FormRenderer<Values>
189
+ onSubmit={save}
190
+ resolver={zodResolver(schema)}
191
+ defaultValues={DEFAULTS}
192
+ header={{ title: "New item", variant: "new" }}
193
+ actions={<FormBuilder.Submit>Save</FormBuilder.Submit>}
194
+ >
195
+ <FormBuilder.Stepper>
196
+ <FormBuilder.Step title="Identity">
197
+ <FormBuilder.Section title="Identity" color="Blue">
198
+ <FormBuilder.Text name="name" label="Name" required />
199
+ </FormBuilder.Section>
200
+ </FormBuilder.Step>
201
+
202
+ <FormBuilder.Step title="Classification">
203
+ <FormBuilder.Section title="Classification" color="Red">
204
+ <FormBuilder.Select name="category" label="Category" required options={CATEGORY} />
205
+ </FormBuilder.Section>
206
+ </FormBuilder.Step>
207
+ </FormBuilder.Stepper>
208
+ </FormRenderer>
209
+ ```
210
+
211
+ ---
212
+
213
+ ## 4. Editing a record
214
+
215
+ Pass `values` (not just `defaultValues`) — the form repopulates when the data loads. Add a
216
+ remount `key` so initial-only inputs (date, rich text) re-seed.
217
+
218
+ ```tsx
219
+ <FormRenderer<Values>
220
+ key={record?.id ?? "new"}
221
+ values={record} // arrives async
222
+ onSubmit={save}
223
+ resolver={zodResolver(schema)}
224
+ defaultValues={DEFAULTS}
225
+ header={{ title: record?.name ?? "New", variant: record ? "edit" : "new" }}
226
+ actions={<FormBuilder.Submit>Save</FormBuilder.Submit>}
227
+ >
228
+ …the same fields…
229
+ </FormRenderer>
230
+ ```
231
+
232
+ ---
233
+
234
+ ## 5. A form in a drawer
235
+
236
+ Set `display="drawer"` and drive it with `open` / `onOpenChange`. Pass the Save via `actions` —
237
+ it renders in the drawer header, with no manual `id` / `form={id}` wiring:
238
+
239
+ ```tsx
240
+ <FormRenderer<Values>
241
+ display="drawer"
242
+ open={open}
243
+ onOpenChange={setOpen}
244
+ header={{ title: "New item", label: "New", variant: "new" }}
245
+ onSubmit={save}
246
+ resolver={zodResolver(schema)}
247
+ defaultValues={DEFAULTS}
248
+ actions={<FormBuilder.Submit>Save</FormBuilder.Submit>}
249
+ >
250
+ <FormBuilder.Section title="Identity" color="Blue">
251
+ …
252
+ </FormBuilder.Section>
253
+ </FormRenderer>
254
+ ```
255
+
256
+ Inside a drawer the fields default to a vertical (narrow) layout automatically.
257
+
258
+ ---
259
+
260
+ ## 6. The conclusion panel — live totals beside the form
261
+
262
+ `FormSummary` is a read-only panel of computed rows. Each `FormSummary.Row` declares a
263
+ **`compute(values)`** that runs against the **live** form values, so totals recalculate as the
264
+ user types. Pass it to `FormRenderer`'s `summary` prop.
265
+
266
+ Both the form and the panel must read the same values, so **hoist `useForm`** and hand the
267
+ instance to `FormRenderer` **and** `FormSummary` via their `form` prop:
268
+
269
+ ```tsx
270
+ "use client";
271
+
272
+ import { useForm } from "react-hook-form";
273
+ import { zodResolver } from "@hookform/resolvers/zod";
274
+ import { FormBuilder } from "@/components/FormBuilder";
275
+ import { FormRenderer } from "@/components/FormRenderer";
276
+ import { FormSummary } from "@/components/FormSummary";
277
+
278
+ // The calculations — plain functions of the form values. Keep them out of the JSX.
279
+ const subTotal = (v: Invoice) => v.items.reduce((s, i) => s + (i.qty ?? 0) * (i.price ?? 0), 0);
280
+ const totalDiscount = (v: Invoice) => v.items.reduce((s, i) => s + (i.discount ?? 0), 0);
281
+ const taxable = (v: Invoice) => Math.max(0, subTotal(v) - totalDiscount(v));
282
+ const totalTax = (v: Invoice) => taxable(v) * ((v.taxRate ?? 0) / 100);
283
+ const overallTotal = (v: Invoice) => taxable(v) + totalTax(v);
284
+
285
+ export function InvoiceForm({ save }: { save: (v: Invoice) => Promise<void> }) {
286
+ // Hoisted so both the form and the summary beside it share the same values.
287
+ const form = useForm<Invoice>({ resolver: zodResolver(schema), defaultValues: DEFAULTS });
288
+
289
+ return (
290
+ <FormRenderer<Invoice>
291
+ form={form}
292
+ onSubmit={save}
293
+ fieldDirection="vertical"
294
+ header={{ title: "Invoice", variant: "new" }}
295
+ actions={<FormBuilder.Submit>Save invoice</FormBuilder.Submit>}
296
+ summary={
297
+ <FormSummary form={form} title="Invoice" subtitle="Summary">
298
+ <FormSummary.Group title="Total">
299
+ <FormSummary.Row label="Total Discount" compute={totalDiscount} />
300
+ <FormSummary.Row label="Overall Total" emphasized compute={overallTotal} />
301
+ <FormSummary.Row
302
+ label="Overall Total"
303
+ currency="IQD"
304
+ tone="success"
305
+ decimals={0}
306
+ compute={(v) => overallTotal(v) * (v.iqdRate ?? 0)}
307
+ />
308
+ <FormSummary.Row
309
+ label="Overall Total"
310
+ currency="USD"
311
+ tone="info"
312
+ compute={overallTotal}
313
+ />
314
+ </FormSummary.Group>
315
+
316
+ <FormSummary.Group title="Tax">
317
+ <FormSummary.Row label="Sub Total" compute={subTotal} />
318
+ <FormSummary.Row label="Total Tax" compute={totalTax} />
319
+ </FormSummary.Group>
320
+ </FormSummary>
321
+ }
322
+ >
323
+ <FormBuilder.Section title="Line items" color="Green">
324
+ <FormBuilder.FieldArray
325
+ name="items"
326
+ label="Items"
327
+ addLabel="Add item"
328
+ defaultItem={{ name: "", qty: 1, price: 0, discount: 0 }}
329
+ >
330
+ {(rowName) => (
331
+ <>
332
+ <FormBuilder.Text name={`${rowName}.name`} label="Item" />
333
+ <FormBuilder.Number name={`${rowName}.qty`} label="Qty" />
334
+ <FormBuilder.Currency name={`${rowName}.price`} label="Price" currencySymbol="$" />
335
+ <FormBuilder.Currency
336
+ name={`${rowName}.discount`}
337
+ label="Discount"
338
+ currencySymbol="$"
339
+ />
340
+ </>
341
+ )}
342
+ </FormBuilder.FieldArray>
343
+ </FormBuilder.Section>
344
+
345
+ <FormBuilder.Section title="Rates" color="Purple">
346
+ <FormBuilder.Number name="taxRate" label="Tax rate (%)" />
347
+ <FormBuilder.Number name="iqdRate" label="USD → IQD rate" />
348
+ </FormBuilder.Section>
349
+ </FormRenderer>
350
+ );
351
+ }
352
+ ```
353
+
354
+ Row options: `emphasized` (the primary result), `currency` + `tone`
355
+ (`'neutral' | 'success' | 'info'`), `decimals` (default `2` → `0.00`), `action` (a trailing
356
+ button), `format` (override the number formatting), `value` (a static row).
357
+
358
+ The panel is **read-only** — it contributes nothing to the submitted values. On a page it sits
359
+ beside the form (a `summary` **plus** a `FormBuilder.Stepper` becomes three columns: nav ·
360
+ fields · summary).
361
+
362
+ ---
363
+
364
+ ## 7. Conclusion panel + drawer
365
+
366
+ The same `summary` prop works with `display="drawer"` — `FormRenderer` moves the panel into the
367
+ drawer's tray, beside the form:
368
+
369
+ ```tsx
370
+ <FormRenderer<Invoice>
371
+ display="drawer"
372
+ open={open}
373
+ onOpenChange={setOpen}
374
+ form={form}
375
+ onSubmit={save}
376
+ header={{ title: "New item", badge: "New", variant: "new" }}
377
+ summary={
378
+ <FormSummary form={form} title="Item" subtitle="Summary">
379
+ <FormSummary.Group title="Pricing">
380
+ <FormSummary.Row label="Base price" compute={basePrice} />
381
+ <FormSummary.Row label="Overall Total" emphasized compute={overallTotal} />
382
+ </FormSummary.Group>
383
+ </FormSummary>
384
+ }
385
+ >
386
+ <FormBuilder.Section title="Identity" color="Blue">
387
+ …
388
+ </FormBuilder.Section>
389
+ </FormRenderer>
390
+ ```
391
+
392
+ ---
393
+
394
+ ## Gotchas
395
+
396
+ - **Hoisting `useForm` disables the remount-`key` reset.** Once the form instance lives in
397
+ your component, a changing `key` no longer resets it. Call `form.reset(DEFAULTS)` instead
398
+ (e.g. when opening a drawer).
399
+ - **`FormSummary` needs the same hoisted `form`** — that's the react-hook-form context it reads
400
+ live values from. Pass the identical instance to `FormRenderer` and `FormSummary`.
401
+ - **Number/currency values are real numbers**, not strings — `onSubmit` receives
402
+ `price: 1299`, not `"1299"`.
403
+ - **`required` is cosmetic.** It renders the "(Required)" tag; actual enforcement is your
404
+ resolver — keep the two in sync.
405
+ - **Never use `variant="SystemStyle"` or `*-system-*` tokens** in app code.
406
+
407
+ ## Related
408
+
409
+ - [FormBuilder](../components/form-builder.md) — every field type and its value shape
410
+ - [FormRenderer](../components/form-renderer.md) — display, header, `actions`, `summary`
411
+ - [FormSummary](../components/form-summary.md) — the calculation panel