torch-glare 2.5.1 → 2.5.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.
- package/apps/lib/components/DataViews/data-views.tsx +1 -1
- package/apps/lib/components/DataViews/filters/filters.tsx +8 -12
- package/apps/lib/components/DataViews/header.tsx +5 -2
- package/apps/lib/components/DataViews/panel/section.tsx +3 -1
- package/apps/lib/components/DataViews/views/board-view.tsx +3 -1
- package/apps/lib/components/DataViews/views/inbox-view.tsx +4 -1
- package/apps/lib/components/DataViews/views/pane-views.tsx +5 -1
- package/apps/lib/components/DataViews/views/table-view.tsx +34 -4
- package/apps/lib/components/DataViews/views/tree-view.tsx +5 -3
- package/apps/lib/components/FormBuilder/context.ts +17 -26
- package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +1 -1
- package/apps/lib/components/FormBuilder/form-builder.tsx +32 -172
- package/apps/lib/components/FormBuilder/index.ts +0 -4
- package/apps/lib/components/FormBuilder/submit.tsx +11 -2
- package/apps/lib/components/FormBuilder/types.ts +10 -16
- package/apps/lib/components/FormRenderer/FormDrawer.tsx +2 -2
- package/apps/lib/components/FormRenderer/detail.tsx +6 -6
- package/apps/lib/components/FormRenderer/form-renderer.tsx +167 -35
- package/apps/lib/components/{FormBuilder → FormRenderer}/header.tsx +12 -25
- package/apps/lib/components/FormRenderer/index.ts +4 -0
- package/apps/lib/components/FormRenderer/section.tsx +39 -0
- package/apps/lib/components/{FormBuilder → FormRenderer}/stepper.tsx +65 -22
- package/apps/lib/components/FormRenderer/types.ts +7 -7
- package/apps/lib/components/SectionBlock.tsx +11 -1
- package/apps/lib/components/Table.tsx +54 -29
- package/apps/lib/components/TreeFolder/TreeFolder.tsx +61 -61
- package/apps/lib/registry.json +5 -3
- package/apps/lib/tsconfig.tsbuildinfo +1 -1
- package/docs/components/data-views/index.md +7 -0
- package/docs/components/form-builder.md +65 -89
- package/docs/components/form-renderer.md +62 -26
- package/docs/components/form-summary.md +18 -3
- package/docs/components/section-block.md +1 -1
- package/docs/components/table.md +33 -0
- package/docs/how-to/forms-with-form-builder.md +47 -42
- package/docs/migration/changelog.md +3 -0
- package/docs/migration/form-builder-2.5.2.md +113 -0
- package/package.json +1 -1
|
@@ -188,6 +188,13 @@ Each takes `id`, `label` and `icon` to control how it appears in the switcher, s
|
|
|
188
188
|
be registered twice with different data. Full props are under
|
|
189
189
|
[API Reference](#api-reference) — one heading per part.
|
|
190
190
|
|
|
191
|
+
Two things `DataViews.Table` does for you that you would otherwise wire by hand: its column header
|
|
192
|
+
**stays put while the rows scroll under it**, and the view draws **its own border and radius**, so it
|
|
193
|
+
reads as a separated surface like the inbox and tree panels rather than filling the shell edge to
|
|
194
|
+
edge. Long column labels truncate with an ellipsis instead of wrapping the header row onto a second
|
|
195
|
+
line. Inside `DataViews.Tree`, the same table drops that border — the tree's pane already draws one,
|
|
196
|
+
and two would nest a pixel apart.
|
|
197
|
+
|
|
191
198
|
### The tree's pane
|
|
192
199
|
|
|
193
200
|
Pick a node and the pane beside it lists what that node holds. Its header names the selected node
|
|
@@ -1,31 +1,63 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: FormBuilder
|
|
3
|
-
description: A compound, composition-based form
|
|
3
|
+
description: The fields. A compound, composition-based form — author fields as JSX children (FormBuilder.Text, FormBuilder.Select, …) wired to any react-hook-form resolver. All chrome around the fields lives in FormRenderer.
|
|
4
4
|
component: true
|
|
5
5
|
group: Forms
|
|
6
6
|
keywords:
|
|
7
|
-
[
|
|
7
|
+
[
|
|
8
|
+
form-builder,
|
|
9
|
+
form,
|
|
10
|
+
compound,
|
|
11
|
+
composition,
|
|
12
|
+
react-hook-form,
|
|
13
|
+
resolver,
|
|
14
|
+
fields,
|
|
15
|
+
validation,
|
|
16
|
+
table,
|
|
17
|
+
field-array,
|
|
18
|
+
submit,
|
|
19
|
+
]
|
|
8
20
|
---
|
|
9
21
|
|
|
10
22
|
# FormBuilder
|
|
11
23
|
|
|
12
24
|
A **compound, composition-based** form. You author a form as JSX children — each
|
|
13
25
|
field is a `FormBuilder.*` component wired to [react-hook-form](https://react-hook-form.com/)
|
|
14
|
-
for you.
|
|
26
|
+
for you.
|
|
15
27
|
|
|
16
28
|
```tsx
|
|
17
29
|
<FormBuilder onSubmit={save} resolver={zodResolver(schema)} defaultValues={d}>
|
|
18
|
-
<FormBuilder.
|
|
19
|
-
|
|
20
|
-
<FormBuilder.Currency name="price" label="Price" currencySymbol="$" />
|
|
21
|
-
</FormBuilder.Section>
|
|
22
|
-
<FormBuilder.Submit>Save</FormBuilder.Submit>
|
|
30
|
+
<FormBuilder.Text name="name" label="Name" required />
|
|
31
|
+
<FormBuilder.Currency name="price" label="Price" currencySymbol="$" />
|
|
23
32
|
</FormBuilder>
|
|
24
33
|
```
|
|
25
34
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
## FormBuilder is the fields — nothing else
|
|
36
|
+
|
|
37
|
+
FormBuilder renders a `<form>`, its react-hook-form context, and your fields. Each field draws
|
|
38
|
+
its own row (label, required marker, hint). Beyond that it draws **no frame at all**: no titled
|
|
39
|
+
section cards, no page gutters, no scroll shell, no title header, no stepper rail, no summary
|
|
40
|
+
column. Rendered bare it simply fills its container — which is what an embedded form wants (a
|
|
41
|
+
settings rail, a `DataViews` filter panel).
|
|
42
|
+
|
|
43
|
+
Everything drawn *around* the fields lives in **[FormRenderer](./form-renderer.md)**:
|
|
44
|
+
|
|
45
|
+
| You want | Use |
|
|
46
|
+
| ------------------------------------- | ---------------------------------------------------- |
|
|
47
|
+
| A titled card grouping fields | `FormRenderer.Section` |
|
|
48
|
+
| A page title header + Save action bar | `FormRenderer`'s `header` and `actions` props |
|
|
49
|
+
| A wizard | `FormRenderer.Stepper` + `FormRenderer.Step` |
|
|
50
|
+
| A drawer | `FormRenderer` with `display="drawer"` |
|
|
51
|
+
| Live totals beside the form | `FormRenderer`'s `summary` prop + [FormSummary](./form-summary.md) |
|
|
52
|
+
| A read-only detail page | `FormRenderer.Sidebar` + `FormRenderer.Tab` |
|
|
53
|
+
|
|
54
|
+
> **For real forms, reach for [FormRenderer](./form-renderer.md), not raw FormBuilder.** You still
|
|
55
|
+
> author the fields as `FormBuilder.*` children — FormRenderer just wraps them.
|
|
56
|
+
|
|
57
|
+
`FormBuilder.Submit` is the one non-field part that stays here — see
|
|
58
|
+
[Field components](#field-components) for what it does. It renders the **primary action**: the blue
|
|
59
|
+
filled button. There is deliberately no `variant` prop — a form has one primary action, and letting
|
|
60
|
+
each call site pick a style is how that stops being true.
|
|
29
61
|
|
|
30
62
|
## Installation
|
|
31
63
|
|
|
@@ -52,20 +84,14 @@ import { FormBuilder } from "@/components/FormBuilder";
|
|
|
52
84
|
| `defaultValues` | `DefaultValues` | Initial values (create). |
|
|
53
85
|
| `values` | `T` | Controlled values (edit) — the form re-syncs when this changes. |
|
|
54
86
|
| `loading` | `boolean` | Submit shows a spinner; inputs disable. |
|
|
55
|
-
| `fieldDirection` | `'horizontal' \| 'vertical'`
|
|
87
|
+
| `fieldDirection` | `'horizontal' \| 'vertical' \| 'flexible'` | Row layout. Unset is responsive; a `FormRenderer` drawer pins `'vertical'`, and `'flexible'` asks for the responsive layout back. |
|
|
56
88
|
| `resetOnSuccess` | `boolean` | Reset to defaults after a successful submit. |
|
|
57
|
-
| `form` | `UseFormReturn` | A hoisted `useForm` to bind to — pass when
|
|
58
|
-
| `conclusion` | `ReactNode` | A live panel (e.g. `FormSummary`) rendered **outside** the `<form>` as the grid's right column. |
|
|
89
|
+
| `form` | `UseFormReturn` | A hoisted `useForm` to bind to — pass when something outside the form must read the same values. |
|
|
59
90
|
| `id` | `string` | Sets the underlying `<form id>`, so a button outside the form can submit it via `form={id}` (e.g. a drawer header's Save). |
|
|
60
|
-
| `className` | `string` | Lands on the form
|
|
91
|
+
| `className` | `string` | Lands on the `<form>` element itself — e.g. `"min-w-0 flex-1"` to fill a flex parent. |
|
|
61
92
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
`conclusion` renders as its own panel **outside** the form surface, beside it — the same structure
|
|
65
|
-
`FormDrawer` uses in the drawer tray (6px gutter). So a stepper + conclusion reads as three columns
|
|
66
|
-
(nav · fields · conclusion), a conclusion alone as two, and a plain form as one. The fields column
|
|
67
|
-
caps at 1100px and centers. The conclusion is outside the `<form>` and reads values via its own
|
|
68
|
-
`form` prop.
|
|
93
|
+
There is no `layout` prop and no `conclusion` prop: the fields always fill their container, and a
|
|
94
|
+
panel beside the form is `FormRenderer`'s `summary`.
|
|
69
95
|
|
|
70
96
|
## Field components
|
|
71
97
|
|
|
@@ -109,7 +135,7 @@ are named `${rowName}.field`.
|
|
|
109
135
|
`FormBuilder.Table` is the **table-shaped** counterpart of `FieldArray` (value `object[]`): an editable
|
|
110
136
|
grid where each row is a record and each column cell is any `FormBuilder.*` field. It renders its own
|
|
111
137
|
`SectionBlock` (`variant="Table"` — the full-bleed table shell), so place it as a **top-level child** of
|
|
112
|
-
the form, **not** inside a `
|
|
138
|
+
the form, **not** inside a `FormRenderer.Section` — nesting produces a card inside a card. When you need a
|
|
113
139
|
table that isn't a form field, compose the shell yourself with `SectionBlock variant="Table"` (see
|
|
114
140
|
[SectionBlock → Table Variant](./section-block.md#table-variant)).
|
|
115
141
|
|
|
@@ -168,77 +194,27 @@ clickable. Multi-select is also available as `.MultiSelect` / `.Tags` (a tag-chi
|
|
|
168
194
|
renders like any other field — the `label` sits in the normal label column — and the box holds
|
|
169
195
|
an optional inline `subLabel`, a vertical divider, and the switch.
|
|
170
196
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
full-bleed table shell (what `FormBuilder.Table` uses internally). `FormBuilder.Submit` is a loading-aware submit button. It
|
|
174
|
-
**auto-associates with the enclosing form** (via context), so it submits even when placed in a
|
|
175
|
-
header / action bar that renders _outside_ the `<form>` — no manual `form={id}` wiring.
|
|
176
|
-
|
|
177
|
-
## Title header + action bar
|
|
178
|
-
|
|
179
|
-
`FormBuilder.Header` renders a **title pill** (Glare `HeaderBar`) on the left and an
|
|
180
|
-
**action bar** (your buttons) on the right, **absolutely positioned** over a
|
|
181
|
-
scrollable form body — the products-services item-edit look. Place it as a direct
|
|
182
|
-
child of `<FormBuilder>`; the root then switches to the scroll-shell layout.
|
|
183
|
-
|
|
184
|
-
```tsx
|
|
185
|
-
<FormBuilder onSubmit={save} resolver={r} defaultValues={d}>
|
|
186
|
-
<FormBuilder.Header title="Item" variant="new">
|
|
187
|
-
<FormBuilder.Submit>Save</FormBuilder.Submit>
|
|
188
|
-
{/* add more actions, e.g. a "Save as draft" Button */}
|
|
189
|
-
</FormBuilder.Header>
|
|
190
|
-
|
|
191
|
-
<FormBuilder.Section title="Identity" color="Blue">
|
|
192
|
-
<FormBuilder.Text name="name" label="Name" required />
|
|
193
|
-
</FormBuilder.Section>
|
|
194
|
-
</FormBuilder>
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
- `title` — plain text (uppercased). `label` — badge text (defaults from `variant`).
|
|
198
|
-
- `variant` — `"new" | "edit" | "detail"` (badge color; default `"new"`). `children` are
|
|
199
|
-
the action buttons; `FormBuilder.Submit` submits the form (it auto-associates with it, even
|
|
200
|
-
though the header sits outside `<form>`).
|
|
197
|
+
To group fields into a titled card, use [`FormRenderer.Section`](./form-renderer.md) — it is
|
|
198
|
+
presentation, so it lives there.
|
|
201
199
|
|
|
202
|
-
|
|
200
|
+
`FormBuilder.Submit` is a loading-aware submit button. It **auto-associates with the enclosing
|
|
201
|
+
form** (via context), so it submits even when placed in a header / action bar that renders
|
|
202
|
+
_outside_ the `<form>` — no manual `form={id}` wiring.
|
|
203
203
|
|
|
204
|
-
|
|
205
|
-
**Every step's fields are always mounted and registered** — the whole form is live
|
|
206
|
-
regardless of which step is showing; the stepper only toggles _visibility_.
|
|
207
|
-
**Navigation is the step buttons themselves**: click a step to go there. Backward is
|
|
208
|
-
free; clicking forward validates the steps in between and stops at the first one with
|
|
209
|
-
errors (shown with a red indicator). A step that **passes validation stays checked** even
|
|
210
|
-
after you navigate back to it. Put the **Submit** in the `FormBuilder.Header` (or the
|
|
211
|
-
`FormRenderer` `actions`) — it submits every step's fields at once, from any step.
|
|
204
|
+
## Moved to FormRenderer
|
|
212
205
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
`FormBuilder.Header`, or use `FormRenderer` — it prepends them before your Submit for a stepper
|
|
216
|
-
automatically.
|
|
217
|
-
|
|
218
|
-
```tsx
|
|
219
|
-
<FormBuilder onSubmit={save} resolver={r} defaultValues={d}>
|
|
220
|
-
<FormBuilder.Header title="Item" variant="new">
|
|
221
|
-
<FormBuilder.Submit>Save</FormBuilder.Submit>
|
|
222
|
-
</FormBuilder.Header>
|
|
223
|
-
|
|
224
|
-
<FormBuilder.Stepper>
|
|
225
|
-
<FormBuilder.Step title="Basics">
|
|
226
|
-
<FormBuilder.Text name="name" label="Name" required />
|
|
227
|
-
</FormBuilder.Step>
|
|
228
|
-
<FormBuilder.Step title="Details">
|
|
229
|
-
<FormBuilder.Select name="category" label="Category" required options={cats} />
|
|
230
|
-
</FormBuilder.Step>
|
|
231
|
-
</FormBuilder.Stepper>
|
|
232
|
-
</FormBuilder>
|
|
233
|
-
```
|
|
206
|
+
These all used to live here. They are chrome, so they now live on
|
|
207
|
+
[FormRenderer](./form-renderer.md):
|
|
234
208
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
209
|
+
| Was | Now |
|
|
210
|
+
| --- | --- |
|
|
211
|
+
| `FormBuilder.Section` | `FormRenderer.Section` |
|
|
212
|
+
| `FormBuilder.Stepper` / `.Step` / `.Back` / `.Next` | `FormRenderer.Stepper` / `.Step` / `.Back` / `.Next` |
|
|
213
|
+
| `FormBuilder.Header` | `FormRenderer`'s `header` prop |
|
|
214
|
+
| the `layout` prop | gone — the fields always fill their container |
|
|
215
|
+
| the `conclusion` prop | `FormRenderer`'s `summary` prop |
|
|
239
216
|
|
|
240
|
-
|
|
241
|
-
> (`FormRenderer.Sidebar` / `.Tab`) — see the FormRenderer docs. `FormBuilder` itself stays form-only.
|
|
217
|
+
See [the migration note](../migration/form-builder-2.5.2.md) for the full upgrade path.
|
|
242
218
|
|
|
243
219
|
## Calculation panel
|
|
244
220
|
|
|
@@ -1,33 +1,39 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: FormRenderer
|
|
3
|
-
description:
|
|
3
|
+
description: The chrome around a FormBuilder. Author fields as JSX children; FormRenderer owns page-vs-drawer display, the title header, the actions bar, titled Sections, the stepper, and the summary panel. Prefer it over raw FormBuilder for real forms.
|
|
4
4
|
component: true
|
|
5
5
|
group: Forms
|
|
6
|
-
keywords:
|
|
6
|
+
keywords:
|
|
7
|
+
[form-renderer, form, drawer, header, display, actions, section, stepper, summary, react-hook-form]
|
|
7
8
|
---
|
|
8
9
|
|
|
9
10
|
# FormRenderer
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
The **chrome** around a [FormBuilder](./form-builder.md). You author the fields as **JSX
|
|
13
|
+
children** (`FormBuilder.Text`, `FormBuilder.Select`, …); `FormRenderer` draws everything
|
|
14
|
+
around them:
|
|
14
15
|
|
|
15
16
|
- **page vs drawer** display (`display="drawer"` hosts the form in a `FormDrawer`),
|
|
16
|
-
- the **absolute title header** +
|
|
17
|
-
-
|
|
17
|
+
- the **absolute title header** + `actions` bar,
|
|
18
|
+
- **`FormRenderer.Section`** — the titled cards that group fields,
|
|
19
|
+
- the **stepper** (`FormRenderer.Stepper` / `.Step`) and its nav rail,
|
|
20
|
+
- the **`summary`** panel beside the form,
|
|
21
|
+
- the page gutters and scroll shell, and **vertical field layout** inside a drawer.
|
|
22
|
+
|
|
23
|
+
`FormBuilder` on its own is the fields and nothing else — no card, no header, no frame — so it
|
|
24
|
+
fills whatever it is placed in. **For real forms, use `FormRenderer`**; reach for bare
|
|
25
|
+
`FormBuilder` only when you are embedding fields inside something that already provides its own
|
|
26
|
+
chrome (a settings rail, a filter panel).
|
|
18
27
|
|
|
19
28
|
FormRenderer has **two modes**: a **form** (author fields as children, as below), or a display-only
|
|
20
29
|
**detail page** — give it `FormRenderer.Sidebar` + `FormRenderer.Tab` children instead of fields and the
|
|
21
|
-
sidebar swaps `
|
|
30
|
+
sidebar swaps `FormRenderer.Tab` panels, whose content is `FormRenderer.Section` blocks (no `<form>`,
|
|
31
|
+
no submit — see [Detail tabs](#detail-tabs-sidebar)).
|
|
22
32
|
|
|
23
33
|
FormRenderer never manufactures a Submit — **you compose the Save and hand it to `actions`**.
|
|
24
34
|
It renders in the form's header action pill (page) or the drawer header (drawer), and a bare
|
|
25
35
|
`<FormBuilder.Submit>` auto-targets this form (even though the header sits outside the `<form>`).
|
|
26
36
|
|
|
27
|
-
Reach for `FormBuilder` directly when you just want the form. Reach for
|
|
28
|
-
`FormRenderer` when you want the same form to render as a page _or_ a drawer with
|
|
29
|
-
the standard chrome.
|
|
30
|
-
|
|
31
37
|
```tsx
|
|
32
38
|
<FormRenderer
|
|
33
39
|
onSubmit={save}
|
|
@@ -36,13 +42,39 @@ the standard chrome.
|
|
|
36
42
|
header={{ title: "Item", variant: "new" }}
|
|
37
43
|
actions={<FormBuilder.Submit>Save</FormBuilder.Submit>}
|
|
38
44
|
>
|
|
39
|
-
<
|
|
45
|
+
<FormRenderer.Section title="Identity" color="Blue">
|
|
40
46
|
<FormBuilder.Text name="name" label="Name" required />
|
|
41
47
|
<FormBuilder.Currency name="price" label="Price" currencySymbol="$" />
|
|
42
|
-
</
|
|
48
|
+
</FormRenderer.Section>
|
|
43
49
|
</FormRenderer>
|
|
44
50
|
```
|
|
45
51
|
|
|
52
|
+
## Compound parts
|
|
53
|
+
|
|
54
|
+
| Part | What it is |
|
|
55
|
+
| ------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
56
|
+
| `FormRenderer.Section` | A titled `SectionBlock` card grouping fields (or display rows). |
|
|
57
|
+
| `FormRenderer.Stepper` / `.Step` | The wizard — see [Stepper](#stepper). |
|
|
58
|
+
| `FormRenderer.Back` / `.Next` | Chevron step controls. The header's action bar prepends them for you. |
|
|
59
|
+
| `FormRenderer.Sidebar` / `.Tab` | The display-only [detail-tabs](#detail-tabs-sidebar) view. |
|
|
60
|
+
| `FormRenderer.Grid` / `.Row` | Read-only display cells inside a detail tab. |
|
|
61
|
+
|
|
62
|
+
### `FormRenderer.Section`
|
|
63
|
+
|
|
64
|
+
Props `title`, `color`, `icon`, `action`, `variant`. It groups fields in a Glare `SectionBlock`:
|
|
65
|
+
`color` is one of `Blue`, `Yellow`, `Green`, `Red`, `Orange`, `Purple`, `Pink`, `Gray`; `action`
|
|
66
|
+
puts buttons on the title row; `variant="Table"` switches to the full-bleed table shell (what
|
|
67
|
+
`FormBuilder.Table` uses internally).
|
|
68
|
+
|
|
69
|
+
It holds no form state, which is why it lives here and not on `FormBuilder` — the same component
|
|
70
|
+
groups editable fields on a form and read-only `FormRenderer.Row` cells on a detail page.
|
|
71
|
+
|
|
72
|
+
Because it reads no context, it also works **outside** `<FormRenderer>` — e.g. wrapping fields in a
|
|
73
|
+
bare `<FormBuilder>` that you are laying out yourself. It is the one compound part that does.
|
|
74
|
+
|
|
75
|
+
> Not to be confused with `FieldSection`, the per-field row layout (label · control) that every
|
|
76
|
+
> field draws for itself. That one stays inside FormBuilder.
|
|
77
|
+
|
|
46
78
|
## Installation
|
|
47
79
|
|
|
48
80
|
```bash
|
|
@@ -63,16 +95,17 @@ import { FormBuilder } from "@/components/FormBuilder";
|
|
|
63
95
|
|
|
64
96
|
| Prop | Type | Notes |
|
|
65
97
|
| -------------------------------------------------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
66
|
-
| `children` | `ReactNode` | The form body — `
|
|
98
|
+
| `children` | `ReactNode` | The form body — `FormRenderer.Section` / field / `FormRenderer.Stepper` JSX. |
|
|
67
99
|
| `onSubmit?` / `onInvalid?` | fns | Submit / validation-fail callbacks. Optional — a detail-tabs view has no form, so omit them there. |
|
|
68
100
|
| `resolver` | `Resolver` | Any react-hook-form resolver, e.g. `zodResolver(schema)`. |
|
|
69
101
|
| `defaultValues` / `values` | `DefaultValues` / `T` | Initial values; `values` re-syncs on change (edit). |
|
|
70
102
|
| `loading` / `resetOnSuccess` | `boolean` | Forwarded to `FormBuilder`. |
|
|
71
|
-
| `fieldDirection` | `'horizontal' \| 'vertical'` |
|
|
103
|
+
| `fieldDirection` | `'horizontal' \| 'vertical' \| 'flexible'` | Unset means responsive: stacked, then label-beside-control once the field row passes the container `md` breakpoint. A drawer defaults to `'vertical'` — pass `'flexible'` there to get the responsive layout back. |
|
|
72
104
|
| `form` | `UseFormReturn<T>` | A hoisted `useForm` to bind to — pass when a sibling (e.g. a `summary` `FormSummary`) must read the same live values; the caller owns `resolver`/`defaultValues` on it. Omit to let FormRenderer create its own. |
|
|
105
|
+
| `className` | `string` | Lands on FormRenderer's outermost element (page display) — e.g. `"min-h-0 flex-1"` to fill a flex column. Note this is the page frame, not the `<form>`; `FormBuilder`'s own `className` lands on the `<form>` element itself. |
|
|
73
106
|
| `display` | `'page' \| 'drawer'` | `'drawer'` wraps the form in `FormDrawer`. |
|
|
74
107
|
| `header` | `{ title; label?; variant? }` | Absolute title header + action bar (page display); `actions` render in it. |
|
|
75
|
-
| `summary` | `ReactNode` | A live panel (typically `FormSummary`) rendered beside the form (page) or in the drawer tray (drawer). Give the same hoisted `form` so it reads live values. On a page, a `summary` **plus** a `
|
|
108
|
+
| `summary` | `ReactNode` | A live panel (typically `FormSummary`) rendered beside the form (page) or in the drawer tray (drawer). Give the same hoisted `form` so it reads live values. On a page, a `summary` **plus** a `FormRenderer.Stepper` lays out as three columns — stepper nav · fields · summary. |
|
|
76
109
|
| `actions` | `ReactNode` | The form's action bar — rendered in the header action pill (page) or drawer header (drawer). Put the Save here: `actions={<FormBuilder.Submit>Save</FormBuilder.Submit>}`. A bare `FormBuilder.Submit` auto-targets this form. |
|
|
77
110
|
| `id` | `string` | `id` on the underlying `<form>`. Optional — FormRenderer generates and wires one otherwise. |
|
|
78
111
|
| `open` / `onOpenChange` / `title` / `badge` / `onOpenInNewTab` | — | Drawer control (when `display="drawer"`). `title` / `badge` are strings that override `header.title` / `header.label`. |
|
|
@@ -98,7 +131,7 @@ import { FormBuilder } from "@/components/FormBuilder";
|
|
|
98
131
|
|
|
99
132
|
## Stepper
|
|
100
133
|
|
|
101
|
-
Drop a `
|
|
134
|
+
Drop a `FormRenderer.Stepper` in as the child. The Save lives in the header `actions` and
|
|
102
135
|
submits every step's registered fields at once (steps stay mounted, so the whole form is live).
|
|
103
136
|
|
|
104
137
|
For a stepper, FormRenderer **automatically prepends chevron Back/Next nav + a divider** before
|
|
@@ -115,11 +148,11 @@ error overrides it to red. You still pass just the Submit; the nav is wired for
|
|
|
115
148
|
header={{ title: "New item", variant: "new" }}
|
|
116
149
|
actions={<FormBuilder.Submit>Save</FormBuilder.Submit>}
|
|
117
150
|
>
|
|
118
|
-
<
|
|
119
|
-
<
|
|
151
|
+
<FormRenderer.Stepper>
|
|
152
|
+
<FormRenderer.Step title="Basics">
|
|
120
153
|
<FormBuilder.Text name="name" label="Name" required />
|
|
121
|
-
</
|
|
122
|
-
</
|
|
154
|
+
</FormRenderer.Step>
|
|
155
|
+
</FormRenderer.Stepper>
|
|
123
156
|
</FormRenderer>
|
|
124
157
|
```
|
|
125
158
|
|
|
@@ -131,7 +164,7 @@ tab panel — no `<form>`, no submit. The sidebar sits **where a stepper's rail
|
|
|
131
164
|
active panel shows (built on the same Radix Tabs primitive shadcn uses, so it's keyboard-accessible).
|
|
132
165
|
Pair it with `header` (`variant="detail"` → a "View" badge) + `actions` (Print / Approve / …).
|
|
133
166
|
|
|
134
|
-
Each `Tab` holds read-only `
|
|
167
|
+
Each `Tab` holds read-only `FormRenderer.Section` blocks; `FormRenderer.Grid` + `FormRenderer.Row` lay
|
|
135
168
|
out the label/value display cells (the display counterpart of form fields).
|
|
136
169
|
|
|
137
170
|
```tsx
|
|
@@ -151,12 +184,12 @@ out the label/value display cells (the display counterpart of form fields).
|
|
|
151
184
|
|
|
152
185
|
{/* One panel per tab — read-only Section blocks. */}
|
|
153
186
|
<FormRenderer.Tab value="overview">
|
|
154
|
-
<
|
|
187
|
+
<FormRenderer.Section title="Main Information" color="Blue">
|
|
155
188
|
<FormRenderer.Grid>
|
|
156
189
|
<FormRenderer.Row label="PO Number" value="PO-000123" />
|
|
157
190
|
<FormRenderer.Row label="Status" value={<Badge label="Submitted" color="yellow" />} />
|
|
158
191
|
</FormRenderer.Grid>
|
|
159
|
-
</
|
|
192
|
+
</FormRenderer.Section>
|
|
160
193
|
</FormRenderer.Tab>
|
|
161
194
|
|
|
162
195
|
<FormRenderer.Tab value="items">…</FormRenderer.Tab>
|
|
@@ -284,7 +317,10 @@ const form = useForm({ resolver, defaultValues })
|
|
|
284
317
|
</FormDrawer>
|
|
285
318
|
```
|
|
286
319
|
|
|
287
|
-
> `childrenOutside` is the deprecated former name for `summary`. It still
|
|
320
|
+
> `childrenOutside` is the deprecated former name for `FormDrawer`'s `summary`. It still
|
|
321
|
+
> works — it long predates the FormBuilder/FormRenderer split and is unrelated to it (that
|
|
322
|
+
> move ships no aliases; see the
|
|
323
|
+
> [migration note](../migration/form-builder-2.5.2.md)).
|
|
288
324
|
|
|
289
325
|
`FormRenderer` forwards `summary` straight into the drawer tray, so you rarely need
|
|
290
326
|
`FormDrawer` directly — reach for it only when you want the drawer without a form.
|
|
@@ -16,14 +16,19 @@ Each `FormSummary.Row` declares a **`compute(values)`** that runs against the **
|
|
|
16
16
|
form values, so every total recalculates as the user types.
|
|
17
17
|
|
|
18
18
|
The panel renders **outside** the form, beside it. Both read the same form, so
|
|
19
|
-
**hoist `useForm`** and hand the instance to each
|
|
19
|
+
**hoist `useForm`** and hand the instance to each.
|
|
20
|
+
|
|
21
|
+
> **Normally you do not lay this out by hand.** Pass the panel to
|
|
22
|
+
> [`FormRenderer`](./form-renderer.md)'s `summary` prop and it places it for you — beside the form
|
|
23
|
+
> on a page, in the tray in a drawer. The manual version below is what `summary` does, and is what
|
|
24
|
+
> you want only when you are composing a bare `<FormBuilder>` yourself.
|
|
20
25
|
|
|
21
26
|
```tsx
|
|
22
27
|
const form = useForm({ resolver: zodResolver(schema), defaultValues })
|
|
23
28
|
|
|
24
29
|
<div className="flex gap-4">
|
|
25
30
|
<FormBuilder form={form} onSubmit={save} className="min-w-0 flex-1">
|
|
26
|
-
<
|
|
31
|
+
<FormRenderer.Section title="Line items">…fields…</FormRenderer.Section>
|
|
27
32
|
</FormBuilder>
|
|
28
33
|
|
|
29
34
|
<FormSummary form={form} title="Invoice" subtitle="Summary">
|
|
@@ -71,6 +76,10 @@ npx torch-glare@latest add FormSummary
|
|
|
71
76
|
## Imports
|
|
72
77
|
|
|
73
78
|
```tsx
|
|
79
|
+
import { useForm } from 'react-hook-form'
|
|
80
|
+
|
|
81
|
+
import { FormBuilder } from '@/components/FormBuilder'
|
|
82
|
+
import { FormRenderer } from '@/components/FormRenderer'
|
|
74
83
|
import { FormSummary } from '@/components/FormSummary'
|
|
75
84
|
```
|
|
76
85
|
|
|
@@ -80,7 +89,7 @@ import { FormSummary } from '@/components/FormSummary'
|
|
|
80
89
|
|---|---|---|
|
|
81
90
|
| `title` | `ReactNode` | Panel title, e.g. `"Invoice"`. |
|
|
82
91
|
| `subtitle` | `ReactNode` | Muted text beside the title, e.g. `"Summary"`. |
|
|
83
|
-
| `form` | `UseFormReturn` | The form to read values from
|
|
92
|
+
| `form` | `UseFormReturn` | The form to read values from. The panel always renders outside the `<form>`, so pass the same hoisted instance you gave `FormRenderer` / `FormBuilder`. |
|
|
84
93
|
| `width` | `number \| string` | Panel width; default `228`. |
|
|
85
94
|
| `children` | `ReactNode` | `FormSummary.Group` children. |
|
|
86
95
|
|
|
@@ -121,3 +130,9 @@ const overallTotal = v => taxable(v) + totalTax(v)
|
|
|
121
130
|
```
|
|
122
131
|
|
|
123
132
|
The panel is **read-only** — it contributes nothing to the submitted values.
|
|
133
|
+
|
|
134
|
+
## Related
|
|
135
|
+
|
|
136
|
+
- [FormRenderer](./form-renderer.md) — its `summary` prop places this panel for you
|
|
137
|
+
- [FormBuilder](./form-builder.md) — the fields the totals read from
|
|
138
|
+
- [ConclusionHeader](./conclusion-header.md) — the header this panel uses
|
|
@@ -435,7 +435,7 @@ import {
|
|
|
435
435
|
```
|
|
436
436
|
|
|
437
437
|
For a table that edits form values, don't hand-compose this — use
|
|
438
|
-
[`FormBuilder.Table`](./form-builder.md#
|
|
438
|
+
[`FormBuilder.Table`](./form-builder.md#field-components), which renders exactly this shell
|
|
439
439
|
and wires rows to `react-hook-form`.
|
|
440
440
|
|
|
441
441
|
### Custom Layout (override defaults)
|
package/docs/components/table.md
CHANGED
|
@@ -363,6 +363,39 @@ function ResizableTable() {
|
|
|
363
363
|
| `className` | `string` | - | Additional CSS classes |
|
|
364
364
|
| `children` | `React.ReactNode` | - | Header rows |
|
|
365
365
|
|
|
366
|
+
#### Sticky header
|
|
367
|
+
|
|
368
|
+
`TableHeader` carries `position: sticky; top: 0`, so a table inside a vertically scrolling container
|
|
369
|
+
can keep its header in view while the rows move under it. Sticky keeps the element in normal flow,
|
|
370
|
+
so it changes nothing in a table that does not scroll.
|
|
371
|
+
|
|
372
|
+
It needs two things from you.
|
|
373
|
+
|
|
374
|
+
**Pass `overflow-visible`.** Sticky resolves against the **nearest scrollport**, and `Table` defaults
|
|
375
|
+
to `overflow-hidden` — which makes the table itself a scrollport, so the header pins to a box that
|
|
376
|
+
never scrolls and appears not to stick. Turning that clipping off binds it to your scroller instead.
|
|
377
|
+
|
|
378
|
+
Only do this when your scroller is `min-w-0` (or otherwise width-constrained). That default clipping
|
|
379
|
+
is also what stops a `w-auto` table wider than its container from pushing the whole page wide, so
|
|
380
|
+
opting out without a constrained scroller trades a sticky header for a horizontal scrollbar.
|
|
381
|
+
|
|
382
|
+
**Give the header an opaque background** — see below.
|
|
383
|
+
|
|
384
|
+
```tsx
|
|
385
|
+
{/* `min-w-0` so a wide table scrolls in here rather than widening the layout */}
|
|
386
|
+
<div className="min-w-0 max-h-[400px] overflow-auto">
|
|
387
|
+
<Table className="w-full overflow-visible">
|
|
388
|
+
<TableHeader className="bg-background-presentation-form-base">
|
|
389
|
+
...
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
The default header background is translucent, which was fine when nothing ever passed beneath it.
|
|
393
|
+
Once rows scroll under a stuck header they read through it, so give
|
|
394
|
+
the header the opaque colour of whatever surface it sits on — `Table` cannot pick one for you,
|
|
395
|
+
since forcing a surface would recolour every table not on it. `DataViews`' table view is the worked
|
|
396
|
+
example: it paints the surface token as the background colour and re-applies the header tint as a
|
|
397
|
+
`background-image`, which stacks above it, landing on the exact colour the header always had.
|
|
398
|
+
|
|
366
399
|
### TableBody Props
|
|
367
400
|
|
|
368
401
|
| Prop | Type | Default | Description |
|