torch-glare 2.4.2 → 2.4.4

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 (47) hide show
  1. package/apps/lib/components/FormBuilder/context.ts +2 -5
  2. package/apps/lib/components/FormBuilder/fields/ChoiceFields.tsx +2 -7
  3. package/apps/lib/components/FormBuilder/fields/ColorField.tsx +1 -18
  4. package/apps/lib/components/FormBuilder/fields/CustomField.tsx +1 -6
  5. package/apps/lib/components/FormBuilder/fields/DateField.tsx +1 -3
  6. package/apps/lib/components/FormBuilder/fields/FieldArray.tsx +10 -15
  7. package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +6 -32
  8. package/apps/lib/components/FormBuilder/fields/FileField.tsx +1 -2
  9. package/apps/lib/components/FormBuilder/fields/OptionListFields.tsx +2 -9
  10. package/apps/lib/components/FormBuilder/fields/OtpField.tsx +1 -2
  11. package/apps/lib/components/FormBuilder/fields/PhoneField.tsx +24 -23
  12. package/apps/lib/components/FormBuilder/fields/RichTextEditorField.tsx +1 -7
  13. package/apps/lib/components/FormBuilder/fields/SelectField.tsx +3 -13
  14. package/apps/lib/components/FormBuilder/fields/SignatureField.tsx +1 -19
  15. package/apps/lib/components/FormBuilder/fields/SliderField.tsx +1 -6
  16. package/apps/lib/components/FormBuilder/fields/SwitchBoxField.tsx +1 -2
  17. package/apps/lib/components/FormBuilder/fields/TableField.tsx +22 -26
  18. package/apps/lib/components/FormBuilder/fields/TextField.tsx +5 -14
  19. package/apps/lib/components/FormBuilder/fields/TreeSelectField.tsx +1 -7
  20. package/apps/lib/components/FormBuilder/form-builder.tsx +22 -36
  21. package/apps/lib/components/FormBuilder/header.tsx +2 -6
  22. package/apps/lib/components/FormBuilder/index.ts +1 -5
  23. package/apps/lib/components/FormBuilder/stepper.tsx +88 -15
  24. package/apps/lib/components/FormBuilder/submit.tsx +1 -4
  25. package/apps/lib/components/FormBuilder/types.ts +2 -5
  26. package/apps/lib/components/FormRenderer/FormDrawer.tsx +9 -2
  27. package/apps/lib/components/FormRenderer/detail.tsx +207 -0
  28. package/apps/lib/components/FormRenderer/form-renderer.tsx +55 -14
  29. package/apps/lib/components/FormRenderer/index.ts +7 -5
  30. package/apps/lib/components/FormRenderer/types.ts +2 -3
  31. package/apps/lib/components/Popover.tsx +6 -2
  32. package/apps/lib/components/SearchableSelect.tsx +7 -2
  33. package/apps/lib/layouts/FieldSection.tsx +25 -22
  34. package/apps/lib/registry.json +1 -1
  35. package/apps/lib/tsconfig.tsbuildinfo +1 -1
  36. package/dist/src/shared/tailwindInit.d.ts.map +1 -1
  37. package/dist/src/shared/tailwindInit.js +3 -0
  38. package/dist/src/shared/tailwindInit.js.map +1 -1
  39. package/docs/components/form-builder.md +22 -29
  40. package/docs/components/form-renderer.md +72 -11
  41. package/docs/components/searchable-select.md +50 -46
  42. package/docs/how-to/forms-with-form-builder.md +68 -11
  43. package/docs/reference/tailwind-plugins.md +11 -1
  44. package/docs/tutorials/getting-started.md +9 -0
  45. package/package.json +1 -1
  46. package/apps/lib/components/FormBuilder/DisplayField.tsx +0 -40
  47. package/apps/lib/components/FormBuilder/viewFormat.tsx +0 -137
@@ -1 +1 @@
1
- {"version":3,"file":"tailwindInit.d.ts","sourceRoot":"","sources":["../../../cli/src/shared/tailwindInit.ts"],"names":[],"mappings":"AAIA,wBAAgB,YAAY,IAAI,IAAI,CAUnC"}
1
+ {"version":3,"file":"tailwindInit.d.ts","sourceRoot":"","sources":["../../../cli/src/shared/tailwindInit.ts"],"names":[],"mappings":"AAIA,wBAAgB,YAAY,IAAI,IAAI,CAanC"}
@@ -11,6 +11,9 @@ export function tailwindInit() {
11
11
  LessThanV4 ? "mapping-color-system" : "mapping-color-system-v4",
12
12
  "glare-torch-mode",
13
13
  ];
14
+ if (LessThanV4) {
15
+ dependencies.push("@tailwindcss/container-queries");
16
+ }
14
17
  installDependencies(dependencies);
15
18
  }
16
19
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"tailwindInit.js","sourceRoot":"","sources":["../../../cli/src/shared/tailwindInit.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tailwindInit.js","sourceRoot":"","sources":["../../../cli/src/shared/tailwindInit.ts"],"names":[],"mappings":"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"}
@@ -1,29 +1,17 @@
1
1
  ---
2
2
  title: FormBuilder
3
- description: A compound, composition-based form. Author forms as JSX children (FormBuilder.Text, FormBuilder.Select, …) with steps-as-components, a drawer wrapper, edit + read-only view modes, and any react-hook-form resolver.
3
+ description: A compound, composition-based form. Author forms as JSX children (FormBuilder.Text, FormBuilder.Select, …) with steps-as-components, a drawer wrapper, and any react-hook-form resolver.
4
4
  component: true
5
5
  group: Forms
6
6
  keywords:
7
- [
8
- form-builder,
9
- form,
10
- compound,
11
- composition,
12
- react-hook-form,
13
- resolver,
14
- stepper,
15
- drawer,
16
- fields,
17
- view-mode,
18
- ]
7
+ [form-builder, form, compound, composition, react-hook-form, resolver, stepper, drawer, fields]
19
8
  ---
20
9
 
21
10
  # FormBuilder
22
11
 
23
12
  A **compound, composition-based** form. You author a form as JSX children — each
24
13
  field is a `FormBuilder.*` component wired to [react-hook-form](https://react-hook-form.com/)
25
- for you. Steps are components, the drawer is a wrapper, and the same markup renders
26
- editable or read-only.
14
+ for you. Steps are components and the drawer is a wrapper.
27
15
 
28
16
  ```tsx
29
17
  <FormBuilder onSubmit={save} resolver={zodResolver(schema)} defaultValues={d}>
@@ -63,7 +51,6 @@ import { FormBuilder } from "@/components/FormBuilder";
63
51
  | `resolver` | `Resolver` | Any react-hook-form resolver, e.g. `zodResolver(schema)`. |
64
52
  | `defaultValues` | `DefaultValues` | Initial values (create). |
65
53
  | `values` | `T` | Controlled values (edit) — the form re-syncs when this changes. |
66
- | `mode` | `'edit' \| 'view'` | `'view'` renders every field read-only. |
67
54
  | `loading` | `boolean` | Submit shows a spinner; inputs disable. |
68
55
  | `fieldDirection` | `'horizontal' \| 'vertical'` | Row layout (auto-vertical inside a drawer). |
69
56
  | `resetOnSuccess` | `boolean` | Reset to defaults after a successful submit. |
@@ -125,7 +112,7 @@ Each column pairs a `header` with a `cell(rowName, index)` renderer — name the
125
112
  `${rowName}.<key>`. The section header carries the actions: a **Add New** button (also repeated as the
126
113
  bottom footer) and a **Delete Row** button that's disabled until rows are checkbox-selected. Rows support
127
114
  **checkbox selection** (+ select-all), **drag-drop reordering**, and — per column, via `sortKey` — a
128
- sort toggle in the header. Everything hides in `mode="view"`, where cells render read-only. Cells render
115
+ sort toggle in the header. Cells render
129
116
  "bare" (control only) with validation errors shown as a tooltip on the control, so a row stays one line
130
117
  tall, and each field passes `onTable` so it's borderless and blends into the grid. Practical cell fields
131
118
  are the compact ones — `Text`, `Number`, `Currency`, `Select`, `SearchableSelect`, `Date`, `Phone`,
@@ -172,7 +159,7 @@ renders like any other field — the `label` sits in the normal label column —
172
159
  an optional inline `subLabel`, a vertical divider, and the switch.
173
160
 
174
161
  `FormBuilder.Section` (props `title`, `color`, `icon`) groups fields in a Glare
175
- `SectionBlock`. `FormBuilder.Submit` is a loading-aware submit button (hidden in view mode). It
162
+ `SectionBlock`. `FormBuilder.Submit` is a loading-aware submit button. It
176
163
  **auto-associates with the enclosing form** (via context), so it submits even when placed in a
177
164
  header / action bar that renders _outside_ the `<form>` — no manual `form={id}` wiring.
178
165
 
@@ -197,10 +184,9 @@ child of `<FormBuilder>`; the root then switches to the scroll-shell layout.
197
184
  ```
198
185
 
199
186
  - `title` — plain text (uppercased). `label` — badge text (defaults from `variant`).
200
- - `variant` — `"new" | "edit" | "detail"` (badge color); defaults from `mode`
201
- (view → `detail`). `children` are the action buttons; `FormBuilder.Submit` submits
202
- the form (it auto-associates with it, even though the header sits outside `<form>`)
203
- and auto-hides in view mode.
187
+ - `variant` — `"new" | "edit" | "detail"` (badge color; default `"new"`). `children` are
188
+ the action buttons; `FormBuilder.Submit` submits the form (it auto-associates with it, even
189
+ though the header sits outside `<form>`).
204
190
 
205
191
  ## Stepper (steps are components)
206
192
 
@@ -209,9 +195,15 @@ Wrap steps in `FormBuilder.Stepper`; each `FormBuilder.Step` holds a step's fiel
209
195
  regardless of which step is showing; the stepper only toggles _visibility_.
210
196
  **Navigation is the step buttons themselves**: click a step to go there. Backward is
211
197
  free; clicking forward validates the steps in between and stops at the first one with
212
- errors (shown with a red indicator). Put the **Submit** in the `FormBuilder.Header` (or the
198
+ errors (shown with a red indicator). A step that **passes validation stays checked** even
199
+ after you navigate back to it. Put the **Submit** in the `FormBuilder.Header` (or the
213
200
  `FormRenderer` `actions`) — it submits every step's fields at once, from any step.
214
201
 
202
+ `FormBuilder.Back` / `FormBuilder.Next` are **chevron step-nav buttons** (previous / next,
203
+ disabled at the ends; `Next` validates the current step first). Place them in a
204
+ `FormBuilder.Header`, or use `FormRenderer` — it prepends them before your Submit for a stepper
205
+ automatically.
206
+
215
207
  ```tsx
216
208
  <FormBuilder onSubmit={save} resolver={r} defaultValues={d}>
217
209
  <FormBuilder.Header title="Item" variant="new">
@@ -234,6 +226,9 @@ errors (shown with a red indicator). Put the **Submit** in the `FormBuilder.Head
234
226
  > [FormRenderer](./form-renderer.md) (pass `fieldDirection="vertical"` to the
235
227
  > form), or use `FormRenderer` with `display="drawer"`.
236
228
 
229
+ > Building a **detail page** with a sidebar of tabs (not a form)? That lives on `FormRenderer`
230
+ > (`FormRenderer.Sidebar` / `.Tab`) — see the FormRenderer docs. `FormBuilder` itself stays form-only.
231
+
237
232
  ## Calculation panel
238
233
 
239
234
  To show computed totals **beside** the form, render a [FormSummary](./form-summary.md)
@@ -251,15 +246,13 @@ const form = useForm({ resolver: r, defaultValues: d })
251
246
  `form` makes FormBuilder use an existing react-hook-form instance instead of creating its
252
247
  own — that's what lets something outside the form read the same live values.
253
248
 
254
- ## Edit & view modes
249
+ ## Editing a record
255
250
 
256
- - **Edit**: pass `values` (not just `defaultValues`); the form repopulates when the
257
- data loads. Use a remount `key` so initial-only inputs (date, rich text) re-seed.
258
- - **View**: `mode="view"` renders the same children read-only (label + formatted value)
259
- with no submit — one markup drives both edit and detail views.
251
+ Pass `values` (not just `defaultValues`); the form repopulates when the data loads. Use a
252
+ remount `key` so initial-only inputs (date, rich text) re-seed.
260
253
 
261
254
  ```tsx
262
- <FormBuilder mode="view" values={entity} onSubmit={() => {}} defaultValues={d}>
255
+ <FormBuilder key={entity.id} values={entity} onSubmit={save} resolver={r} defaultValues={d}>
263
256
  …the same fields…
264
257
  </FormBuilder>
265
258
  ```
@@ -3,7 +3,7 @@ title: FormRenderer
3
3
  description: A thin wrapper around FormBuilder. Author fields as JSX children; FormRenderer owns page-vs-drawer display, the absolute title header, and drawer field layout. You compose the Save and pass it via the actions prop.
4
4
  component: true
5
5
  group: Forms
6
- keywords: [form-renderer, form, drawer, header, display, view-mode, react-hook-form]
6
+ keywords: [form-renderer, form, drawer, header, display, actions, react-hook-form]
7
7
  ---
8
8
 
9
9
  # FormRenderer
@@ -16,6 +16,10 @@ takes care of the surrounding concerns:
16
16
  - the **absolute title header** + action bar (page display),
17
17
  - **vertical field layout** inside a drawer.
18
18
 
19
+ FormRenderer has **two modes**: a **form** (author fields as children, as below), or a display-only
20
+ **detail page** — give it `FormRenderer.Sidebar` + `FormRenderer.Tab` children instead of fields and the
21
+ sidebar swaps `FormBuilder.Section` panels (no `<form>`, no submit — see [Detail tabs](#detail-tabs-sidebar)).
22
+
19
23
  FormRenderer never manufactures a Submit — **you compose the Save and hand it to `actions`**.
20
24
  It renders in the form's header action pill (page) or the drawer header (drawer), and a bare
21
25
  `<FormBuilder.Submit>` auto-targets this form (even though the header sits outside the `<form>`).
@@ -60,10 +64,9 @@ import { FormBuilder } from "@/components/FormBuilder";
60
64
  | Prop | Type | Notes |
61
65
  | -------------------------------------------------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
62
66
  | `children` | `ReactNode` | The form body — `FormBuilder.Section` / field / `FormBuilder.Stepper` JSX. |
63
- | `onSubmit` / `onInvalid` | fns | Submit / validation-fail callbacks. |
67
+ | `onSubmit?` / `onInvalid?` | fns | Submit / validation-fail callbacks. Optional — a detail-tabs view has no form, so omit them there. |
64
68
  | `resolver` | `Resolver` | Any react-hook-form resolver, e.g. `zodResolver(schema)`. |
65
69
  | `defaultValues` / `values` | `DefaultValues` / `T` | Initial values; `values` re-syncs on change (edit). |
66
- | `mode` | `'edit' \| 'view'` | `'view'` renders read-only, no Submit. |
67
70
  | `loading` / `resetOnSuccess` | `boolean` | Forwarded to `FormBuilder`. |
68
71
  | `fieldDirection` | `'horizontal' \| 'vertical'` | Defaults to vertical inside a drawer. |
69
72
  | `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. |
@@ -74,22 +77,23 @@ import { FormBuilder } from "@/components/FormBuilder";
74
77
  | `id` | `string` | `id` on the underlying `<form>`. Optional — FormRenderer generates and wires one otherwise. |
75
78
  | `open` / `onOpenChange` / `title` / `badge` / `onOpenInNewTab` | — | Drawer control (when `display="drawer"`). `title` / `badge` are strings that override `header.title` / `header.label`. |
76
79
 
77
- ## Drawer & view
80
+ ## Drawer
78
81
 
79
82
  ```tsx
80
83
  // drawer — pass the Save via `actions`; it renders in the drawer header
81
84
  <FormRenderer
82
- display="drawer" open={open} onOpenChange={setOpen} title="New item" badge="New"
83
- onSubmit={save} resolver={r} defaultValues={d}
85
+ display="drawer"
86
+ open={open}
87
+ onOpenChange={setOpen}
88
+ title="New item"
89
+ badge="New"
90
+ onSubmit={save}
91
+ resolver={r}
92
+ defaultValues={d}
84
93
  actions={<FormBuilder.Submit>Save</FormBuilder.Submit>}
85
94
  >
86
95
  {fields}
87
96
  </FormRenderer>
88
-
89
- // read-only view — same children, omit `actions` (a view has no Save)
90
- <FormRenderer mode="view" values={entity} onSubmit={() => {}}>
91
- {fields}
92
- </FormRenderer>
93
97
  ```
94
98
 
95
99
  ## Stepper
@@ -97,6 +101,12 @@ import { FormBuilder } from "@/components/FormBuilder";
97
101
  Drop a `FormBuilder.Stepper` in as the child. The Save lives in the header `actions` and
98
102
  submits every step's registered fields at once (steps stay mounted, so the whole form is live).
99
103
 
104
+ For a stepper, FormRenderer **automatically prepends chevron Back/Next nav + a divider** before
105
+ your Submit, so the action bar reads `[◀] [▶] │ Save`. Back is disabled on the first step; Next
106
+ validates the current step, then advances (disabled on the last step). A step that passes
107
+ validation **stays checked** in the rail — even after you navigate back — while a live validation
108
+ error overrides it to red. You still pass just the Submit; the nav is wired for you:
109
+
100
110
  ```tsx
101
111
  <FormRenderer
102
112
  onSubmit={save}
@@ -113,6 +123,57 @@ submits every step's registered fields at once (steps stay mounted, so the whole
113
123
  </FormRenderer>
114
124
  ```
115
125
 
126
+ ## Detail tabs (sidebar)
127
+
128
+ Give FormRenderer `FormRenderer.Sidebar` + `FormRenderer.Tab` children (instead of fields) and it
129
+ switches to a **display-only detail page**: a left **sidebar** where each item swaps in its matching
130
+ tab panel — no `<form>`, no submit. The sidebar sits **where a stepper's rail would**, and only the
131
+ active panel shows (built on the same Radix Tabs primitive shadcn uses, so it's keyboard-accessible).
132
+ Pair it with `header` (`variant="detail"` → a "View" badge) + `actions` (Print / Approve / …).
133
+
134
+ Each `Tab` holds read-only `FormBuilder.Section` blocks; `FormRenderer.Grid` + `FormRenderer.Row` lay
135
+ out the label/value display cells (the display counterpart of form fields).
136
+
137
+ ```tsx
138
+ <FormRenderer
139
+ header={{ title: "Order DE-344", variant: "detail" }}
140
+ actions={<Button variant="BorderStyle">Print</Button>}
141
+ >
142
+ {/* The rail — one Item per tab, tied to a Tab by `value`. */}
143
+ <FormRenderer.Sidebar>
144
+ <FormRenderer.Sidebar.Item value="overview" icon={<i className="ri-layout-grid-line" />}>
145
+ Overview
146
+ </FormRenderer.Sidebar.Item>
147
+ <FormRenderer.Sidebar.Item value="items" icon={<i className="ri-table-line" />}>
148
+ Items Table
149
+ </FormRenderer.Sidebar.Item>
150
+ </FormRenderer.Sidebar>
151
+
152
+ {/* One panel per tab — read-only Section blocks. */}
153
+ <FormRenderer.Tab value="overview">
154
+ <FormBuilder.Section title="Main Information" color="Blue">
155
+ <FormRenderer.Grid>
156
+ <FormRenderer.Row label="PO Number" value="PO-000123" />
157
+ <FormRenderer.Row label="Status" value={<Badge label="Submitted" color="yellow" />} />
158
+ </FormRenderer.Grid>
159
+ </FormBuilder.Section>
160
+ </FormRenderer.Tab>
161
+
162
+ <FormRenderer.Tab value="items">…</FormRenderer.Tab>
163
+ </FormRenderer>
164
+ ```
165
+
166
+ | Component | Props | Renders |
167
+ | -------------- | -------------------------- | ------------------------------------------------------------------ |
168
+ | `Sidebar` | `children` | The tab rail (a Radix `Tabs.List`), fixed at the stepper's place. |
169
+ | `Sidebar.Item` | `value`, `icon?`, children | A rail nav row (a `Tabs.Trigger`); the active one is a black pill. |
170
+ | `Tab` | `value`, children | A content panel (a `Tabs.Content`) shown when its tab is active. |
171
+ | `Grid` | `columns?` (1–3), children | A padded grid of display `Row`s (default 2 columns). |
172
+ | `Row` | `label`, `value` | A read-only label/value display cell. |
173
+
174
+ The first `Tab` is active by default. Only the active panel is visible; the rail stays fixed while the
175
+ content column scrolls.
176
+
116
177
  ## Summary panel
117
178
 
118
179
  Pass a hoisted `useForm` as `form` and a `FormSummary` as `summary` — FormRenderer binds
@@ -168,70 +168,73 @@ function ArabicPicker({ options, value, onValueChange }) {
168
168
 
169
169
  ### SearchableSelect
170
170
 
171
- | Prop | Type | Default | Description |
172
- |------|------|---------|-------------|
173
- | `options` | `SearchableSelectOption[]` | Required | The list of selectable options. In server mode (`filterClientSide={false}`) these are rendered as-is. |
174
- | `value` | `string \| null` | - | Controlled selected value. The matching option's label is shown as solid text in the input. |
175
- | `onValueChange` | `(value: string, option: SearchableSelectOption) => void` | - | Called when an option is selected. Receives the value and the full option object. |
176
- | `placeholder` | `string` | `'Search…'` | Placeholder text for the search input. |
177
- | `size` | `'XS' \| 'S' \| 'M'` | `'M'` | Field size. |
178
- | `variant` | `'PresentationStyle'` | `'PresentationStyle'` | Visual style variant for the field and dropdown surface. |
179
- | `icon` | `ReactNode` | - | Optional leading icon rendered inside the field. |
180
- | `theme` | `'dark' \| 'light' \| 'default'` | - | Theme variant, applied via `data-theme`. |
181
- | `dir` | `string` | - | Text direction (e.g. `'rtl'`) for the field and dropdown. |
182
- | `className` | `string` | - | Additional CSS classes for the field group. |
183
- | `filterClientSide` | `boolean` | `true` | When `true`, filters `options` locally by label. Set `false` for server-side search — `options` are rendered as-is and refetched via `onSearchChange`. |
184
- | `onSearchChange` | `(query: string) => void` | - | Called (debounced) with the trimmed query as the user types. Refetch your data here for server-side search. |
185
- | `searchDebounceMs` | `number` | `300` | Debounce delay (ms) before `onSearchChange` fires. |
186
- | `hasMore` | `boolean` | `false` | Whether more pages are available; gates the infinite-scroll loader. |
187
- | `loading` | `boolean` | `false` | Whether a fetch is in flight; shows a loading row and blocks `onLoadMore`. |
188
- | `onLoadMore` | `() => void` | - | Called when the scroll viewport nears the bottom and `hasMore && !loading`. |
189
- | `maxVisibleItems` | `number` | `5` | Maximum rows visible before the list scrolls internally. |
171
+ | Prop | Type | Default | Description |
172
+ | ------------------ | --------------------------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
173
+ | `options` | `SearchableSelectOption[]` | Required | The list of selectable options. In server mode (`filterClientSide={false}`) these are rendered as-is. |
174
+ | `value` | `string \| null` | - | Controlled selected value. The matching option's label is shown as solid text in the input. |
175
+ | `onValueChange` | `(value: string, option: SearchableSelectOption) => void` | - | Called when an option is selected. Receives the value and the full option object. |
176
+ | `placeholder` | `string` | `'Search…'` | Placeholder text for the search input. |
177
+ | `size` | `'XS' \| 'S' \| 'M'` | `'M'` | Field size. |
178
+ | `variant` | `'PresentationStyle'` | `'PresentationStyle'` | Visual style variant for the field and dropdown surface. |
179
+ | `icon` | `ReactNode` | - | Optional leading icon rendered inside the field. |
180
+ | `theme` | `'dark' \| 'light' \| 'default'` | - | Theme variant, applied via `data-theme`. |
181
+ | `dir` | `string` | - | Text direction (e.g. `'rtl'`) for the field and dropdown. |
182
+ | `className` | `string` | - | Additional CSS classes for the field group. |
183
+ | `filterClientSide` | `boolean` | `true` | When `true`, filters `options` locally by label. Set `false` for server-side search — `options` are rendered as-is and refetched via `onSearchChange`. |
184
+ | `onSearchChange` | `(query: string) => void` | - | Called (debounced) with the trimmed query as the user types. Refetch your data here for server-side search. |
185
+ | `searchDebounceMs` | `number` | `300` | Debounce delay (ms) before `onSearchChange` fires. |
186
+ | `hasMore` | `boolean` | `false` | Whether more pages are available; gates the infinite-scroll loader. |
187
+ | `loading` | `boolean` | `false` | Whether a fetch is in flight; shows a loading row and blocks `onLoadMore`. |
188
+ | `onLoadMore` | `() => void` | - | Called when the scroll viewport nears the bottom and `hasMore && !loading`. |
189
+ | `maxVisibleItems` | `number` | `5` | Maximum rows visible before the list scrolls internally. |
190
190
 
191
191
  ### SearchableSelectOption
192
192
 
193
- | Prop | Type | Default | Description |
194
- |------|------|---------|-------------|
195
- | `value` | `string` | Required | Unique value for the option; matched against the `value` prop. |
196
- | `label` | `string` | Required | Display text; also used for client-side label filtering. |
197
- | `icon` | `ReactNode` | - | Optional leading icon rendered in the row. |
193
+ | Prop | Type | Default | Description |
194
+ | ------------ | ----------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
195
+ | `value` | `string` | Required | Unique value for the option; matched against the `value` prop. |
196
+ | `label` | `string` | Required | Display text; also the default target for client-side filtering. |
197
+ | `icon` | `ReactNode` | - | Optional leading icon rendered in the row. |
198
+ | `searchText` | `string` | `label` | Text used for client-side filtering. Set it to match on a **subset** of the label — e.g. a country name when the label also shows a dial code, so typing the dial doesn't match. |
198
199
 
199
200
  ## TypeScript
200
201
 
201
202
  ### Type Definitions
202
203
 
203
204
  ```typescript
204
- import { ReactNode } from 'react'
205
+ import { ReactNode } from "react";
205
206
 
206
207
  export interface SearchableSelectOption {
207
- value: string
208
- label: string
209
- icon?: ReactNode
208
+ value: string;
209
+ label: string;
210
+ icon?: ReactNode;
211
+ /** Text used for client-side filtering. Defaults to `label`. */
212
+ searchText?: string;
210
213
  }
211
214
 
212
215
  interface SearchableSelectProps {
213
- options: SearchableSelectOption[]
214
- value?: string | null
215
- onValueChange?: (value: string, option: SearchableSelectOption) => void
216
- placeholder?: string
217
- size?: 'XS' | 'S' | 'M'
218
- variant?: 'PresentationStyle'
219
- icon?: ReactNode
220
- theme?: 'dark' | 'light' | 'default'
221
- dir?: string
222
- className?: string
216
+ options: SearchableSelectOption[];
217
+ value?: string | null;
218
+ onValueChange?: (value: string, option: SearchableSelectOption) => void;
219
+ placeholder?: string;
220
+ size?: "XS" | "S" | "M";
221
+ variant?: "PresentationStyle";
222
+ icon?: ReactNode;
223
+ theme?: "dark" | "light" | "default";
224
+ dir?: string;
225
+ className?: string;
223
226
 
224
227
  // Async / backend pagination (all optional; static `options` still work)
225
- filterClientSide?: boolean
226
- onSearchChange?: (query: string) => void
227
- searchDebounceMs?: number
228
- hasMore?: boolean
229
- loading?: boolean
230
- onLoadMore?: () => void
231
- maxVisibleItems?: number
228
+ filterClientSide?: boolean;
229
+ onSearchChange?: (query: string) => void;
230
+ searchDebounceMs?: number;
231
+ hasMore?: boolean;
232
+ loading?: boolean;
233
+ onLoadMore?: () => void;
234
+ maxVisibleItems?: number;
232
235
  }
233
236
 
234
- export function SearchableSelect(props: SearchableSelectProps): JSX.Element
237
+ export function SearchableSelect(props: SearchableSelectProps): JSX.Element;
235
238
  ```
236
239
 
237
240
  ## Common Patterns
@@ -340,6 +343,7 @@ function UserPicker() {
340
343
  ## Best Practices
341
344
 
342
345
  1. **Always control `value`** — pass `value` and `onValueChange` so the input can display the selected label.
346
+
343
347
  ```typescript
344
348
  <SearchableSelect value={value} onValueChange={setValue} options={options} />
345
349
  ```
@@ -1,6 +1,6 @@
1
1
  ---
2
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, edit/view, and a full invoice example combining all three.
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
4
  keywords:
5
5
  [
6
6
  form,
@@ -33,7 +33,7 @@ Three components, layered:
33
33
 
34
34
  | Component | Use it for | When |
35
35
  | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
36
- | **`FormBuilder`** | The form itself — fields authored as JSX children. Owns react-hook-form, validation, edit/view, sections, steppers. | Always. This is the base. |
36
+ | **`FormBuilder`** | The form itself — fields authored as JSX children. Owns react-hook-form, validation, sections, steppers. | Always. This is the base. |
37
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
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
39
 
@@ -117,9 +117,8 @@ export function ItemForm({
117
117
 
118
118
  `FormBuilder.Section` groups fields in a `SectionBlock` (`color` is one of `Blue`, `Yellow`,
119
119
  `Green`, `Red`, `Orange`, `Purple`, `Pink`, `Gray`). Pass the Save via `actions` — a
120
- `FormBuilder.Submit`, which is loading-aware and auto-hides in view mode; it renders in the
121
- header action pill. (With raw `FormBuilder`, put the same `FormBuilder.Submit` in a
122
- `FormBuilder.Header`.)
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`.)
123
122
 
124
123
  ### Field types
125
124
 
@@ -180,6 +179,11 @@ themselves**: backward is free, clicking forward validates the steps in between
180
179
  the first one with errors. The Save is the header `actions` — it submits every step's fields at
181
180
  once, from any step.
182
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
+
183
187
  ```tsx
184
188
  <FormRenderer<Values>
185
189
  onSubmit={save}
@@ -206,21 +210,20 @@ once, from any step.
206
210
 
207
211
  ---
208
212
 
209
- ## 4. Edit and read-only view — one markup, both modes
213
+ ## 4. Editing a record
210
214
 
211
- - **Edit**: pass `values` (not just `defaultValues`) — the form repopulates when the data
212
- loads. Add a remount `key` so initial-only inputs (date, rich text) re-seed.
213
- - **View**: `mode="view"` renders the same children read-only, with no Submit.
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.
214
217
 
215
218
  ```tsx
216
219
  <FormRenderer<Values>
217
220
  key={record?.id ?? "new"}
218
- mode={mode} // 'edit' | 'view'
219
221
  values={record} // arrives async
220
222
  onSubmit={save}
221
223
  resolver={zodResolver(schema)}
222
224
  defaultValues={DEFAULTS}
223
- header={{ title: record?.name ?? "New", variant: mode === "view" ? "detail" : "edit" }}
225
+ header={{ title: record?.name ?? "New", variant: record ? "edit" : "new" }}
226
+ actions={<FormBuilder.Submit>Save</FormBuilder.Submit>}
224
227
  >
225
228
  …the same fields…
226
229
  </FormRenderer>
@@ -388,6 +391,60 @@ drawer's tray, beside the form:
388
391
 
389
392
  ---
390
393
 
394
+ ## 8. A detail (view) page — sidebar tabs, not a form
395
+
396
+ Sometimes you want to **display** a record, not edit it. Give `FormRenderer` `FormRenderer.Sidebar`
397
+
398
+ - `FormRenderer.Tab` children (instead of fields) and it switches to a display-only detail page: a
399
+ left **sidebar** where each item swaps in its matching tab panel — no `<form>`, no submit. The
400
+ sidebar sits where a stepper's rail would; only the active panel shows.
401
+
402
+ **Every tab's content is `FormBuilder.Section` blocks.** Inside a Section, use the default
403
+ `FormRenderer.Grid` + `FormRenderer.Row` display cells (the read-only counterpart of form fields), or
404
+ render **your own component** — anything goes inside a Section.
405
+
406
+ ```tsx
407
+ <FormRenderer
408
+ header={{ title: "Order DE-344", variant: "detail" }} // "View" badge
409
+ actions={<Button variant="BorderStyle">Print</Button>}
410
+ >
411
+ {/* Rail — each Item's `value` ties to a Tab. */}
412
+ <FormRenderer.Sidebar>
413
+ <FormRenderer.Sidebar.Item value="overview" icon={<i className="ri-layout-grid-line" />}>
414
+ Overview
415
+ </FormRenderer.Sidebar.Item>
416
+ <FormRenderer.Sidebar.Item value="activity" icon={<i className="ri-pulse-line" />}>
417
+ Activity log
418
+ </FormRenderer.Sidebar.Item>
419
+ </FormRenderer.Sidebar>
420
+
421
+ {/* Default display cells */}
422
+ <FormRenderer.Tab value="overview">
423
+ <FormBuilder.Section title="Main Information" color="Blue">
424
+ <FormRenderer.Grid columns={2}>
425
+ <FormRenderer.Row label="PO Number" value={record.poNumber} />
426
+ <FormRenderer.Row label="Status" value={<Badge label="Submitted" color="yellow" />} />
427
+ </FormRenderer.Grid>
428
+ </FormBuilder.Section>
429
+ </FormRenderer.Tab>
430
+
431
+ {/* Or bring your own component — still inside a Section */}
432
+ <FormRenderer.Tab value="activity">
433
+ <FormBuilder.Section title="Activity log" color="Green">
434
+ <YourTimeline items={record.activity} />
435
+ </FormBuilder.Section>
436
+ </FormRenderer.Tab>
437
+ </FormRenderer>
438
+ ```
439
+
440
+ `FormRenderer.Grid` takes `columns` (1–3, default 2) and spans the full section width; `FormRenderer.Row`
441
+ takes `label` + `value` (any node). The first `Tab` is active by default.
442
+
443
+ > Generating one? Call the `create-form` tool with `layout="detail"` — it returns this exact wiring
444
+ > with your fields pre-filled as display rows.
445
+
446
+ ---
447
+
391
448
  ## Gotchas
392
449
 
393
450
  - **Hoisting `useForm` disables the remount-`key` reset.** Once the form instance lives in
@@ -599,7 +599,16 @@ export default {
599
599
  "./src/**/*.{js,ts,jsx,tsx}",
600
600
  ],
601
601
  theme: {
602
- extend: {},
602
+ extend: {
603
+ containers: {
604
+ xs: "320px",
605
+ sm: "600px",
606
+ md: "768px",
607
+ lg: "1024px",
608
+ xl: "1280px",
609
+ "2xl": "1536px",
610
+ },
611
+ },
603
612
  },
604
613
  plugins: [
605
614
  // Color system (required first)
@@ -612,6 +621,7 @@ export default {
612
621
  require('glare-typography'),
613
622
 
614
623
  // Additional Tailwind plugins
624
+ require('@tailwindcss/container-queries'),
615
625
  require('tailwindcss-animate'),
616
626
  require('tailwind-scrollbar-hide'),
617
627
  ],
@@ -132,10 +132,19 @@ module.exports = {
132
132
  theme: {
133
133
  extend: {
134
134
  colors: mappingVars,
135
+ containers: {
136
+ xs: "320px",
137
+ sm: "600px",
138
+ md: "768px",
139
+ lg: "1024px",
140
+ xl: "1280px",
141
+ "2xl": "1536px",
142
+ },
135
143
  },
136
144
  },
137
145
  plugins: [
138
146
  plugin,
147
+ require('@tailwindcss/container-queries'),
139
148
  require('tailwindcss-animate'),
140
149
  require('tailwind-scrollbar-hide'),
141
150
  require('glare-typography'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "torch-glare",
3
- "version": "2.4.2",
3
+ "version": "2.4.4",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "A copy-in React component library (TypeScript + Radix UI + Tailwind CSS). Its CLI copies component source directly into your project — you own the code.",