torch-glare 2.4.2 → 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 (46) 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 +15 -28
  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/form-renderer.tsx +9 -11
  28. package/apps/lib/components/FormRenderer/index.ts +1 -6
  29. package/apps/lib/components/FormRenderer/types.ts +0 -2
  30. package/apps/lib/components/Popover.tsx +6 -2
  31. package/apps/lib/components/SearchableSelect.tsx +7 -2
  32. package/apps/lib/layouts/FieldSection.tsx +25 -22
  33. package/apps/lib/registry.json +1 -1
  34. package/apps/lib/tsconfig.tsbuildinfo +1 -1
  35. package/dist/src/shared/tailwindInit.d.ts.map +1 -1
  36. package/dist/src/shared/tailwindInit.js +3 -0
  37. package/dist/src/shared/tailwindInit.js.map +1 -1
  38. package/docs/components/form-builder.md +19 -29
  39. package/docs/components/form-renderer.md +16 -10
  40. package/docs/components/searchable-select.md +50 -46
  41. package/docs/how-to/forms-with-form-builder.md +14 -11
  42. package/docs/reference/tailwind-plugins.md +11 -1
  43. package/docs/tutorials/getting-started.md +9 -0
  44. package/package.json +1 -1
  45. package/apps/lib/components/FormBuilder/DisplayField.tsx +0 -40
  46. 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">
@@ -251,15 +243,13 @@ const form = useForm({ resolver: r, defaultValues: d })
251
243
  `form` makes FormBuilder use an existing react-hook-form instance instead of creating its
252
244
  own — that's what lets something outside the form read the same live values.
253
245
 
254
- ## Edit & view modes
246
+ ## Editing a record
255
247
 
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.
248
+ Pass `values` (not just `defaultValues`); the form repopulates when the data loads. Use a
249
+ remount `key` so initial-only inputs (date, rich text) re-seed.
260
250
 
261
251
  ```tsx
262
- <FormBuilder mode="view" values={entity} onSubmit={() => {}} defaultValues={d}>
252
+ <FormBuilder key={entity.id} values={entity} onSubmit={save} resolver={r} defaultValues={d}>
263
253
  …the same fields…
264
254
  </FormBuilder>
265
255
  ```
@@ -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
@@ -63,7 +63,6 @@ import { FormBuilder } from "@/components/FormBuilder";
63
63
  | `onSubmit` / `onInvalid` | fns | Submit / validation-fail callbacks. |
64
64
  | `resolver` | `Resolver` | Any react-hook-form resolver, e.g. `zodResolver(schema)`. |
65
65
  | `defaultValues` / `values` | `DefaultValues` / `T` | Initial values; `values` re-syncs on change (edit). |
66
- | `mode` | `'edit' \| 'view'` | `'view'` renders read-only, no Submit. |
67
66
  | `loading` / `resetOnSuccess` | `boolean` | Forwarded to `FormBuilder`. |
68
67
  | `fieldDirection` | `'horizontal' \| 'vertical'` | Defaults to vertical inside a drawer. |
69
68
  | `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 +73,23 @@ import { FormBuilder } from "@/components/FormBuilder";
74
73
  | `id` | `string` | `id` on the underlying `<form>`. Optional — FormRenderer generates and wires one otherwise. |
75
74
  | `open` / `onOpenChange` / `title` / `badge` / `onOpenInNewTab` | — | Drawer control (when `display="drawer"`). `title` / `badge` are strings that override `header.title` / `header.label`. |
76
75
 
77
- ## Drawer & view
76
+ ## Drawer
78
77
 
79
78
  ```tsx
80
79
  // drawer — pass the Save via `actions`; it renders in the drawer header
81
80
  <FormRenderer
82
- display="drawer" open={open} onOpenChange={setOpen} title="New item" badge="New"
83
- onSubmit={save} resolver={r} defaultValues={d}
81
+ display="drawer"
82
+ open={open}
83
+ onOpenChange={setOpen}
84
+ title="New item"
85
+ badge="New"
86
+ onSubmit={save}
87
+ resolver={r}
88
+ defaultValues={d}
84
89
  actions={<FormBuilder.Submit>Save</FormBuilder.Submit>}
85
90
  >
86
91
  {fields}
87
92
  </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
93
  ```
94
94
 
95
95
  ## Stepper
@@ -97,6 +97,12 @@ import { FormBuilder } from "@/components/FormBuilder";
97
97
  Drop a `FormBuilder.Stepper` in as the child. The Save lives in the header `actions` and
98
98
  submits every step's registered fields at once (steps stay mounted, so the whole form is live).
99
99
 
100
+ For a stepper, FormRenderer **automatically prepends chevron Back/Next nav + a divider** before
101
+ your Submit, so the action bar reads `[◀] [▶] │ Save`. Back is disabled on the first step; Next
102
+ validates the current step, then advances (disabled on the last step). A step that passes
103
+ validation **stays checked** in the rail — even after you navigate back — while a live validation
104
+ error overrides it to red. You still pass just the Submit; the nav is wired for you:
105
+
100
106
  ```tsx
101
107
  <FormRenderer
102
108
  onSubmit={save}
@@ -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>
@@ -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.3",
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.",
@@ -1,40 +0,0 @@
1
- import { ReactNode } from "react";
2
-
3
- import { cn } from "../../utils/cn";
4
-
5
- export interface DisplayFieldProps {
6
- label?: ReactNode;
7
- /** Plain string value — falls back to an em-dash when empty. */
8
- value?: string | null;
9
- /** Rich value (badges, chips, formatted nodes) — takes precedence over `value`. */
10
- valueNode?: ReactNode;
11
- className?: string;
12
- }
13
-
14
- /**
15
- * Read-only field: a label above its value. Used by FormBuilder's `mode: "view"`
16
- * so the same config renders as a detail view. Mirrors the products-services
17
- * `DisplayField` (label + value/valueNode, em-dash fallback).
18
- */
19
- export function DisplayField({ label, value, valueNode, className }: DisplayFieldProps) {
20
- return (
21
- <div className={cn("flex w-full flex-col gap-1.5", className)}>
22
- {label != null && (
23
- <p className="typography-body-small-regular text-content-presentation-global-secondary">
24
- {label}
25
- </p>
26
- )}
27
- {valueNode != null ? (
28
- <div className="typography-body-medium-medium text-content-presentation-global-primary">
29
- {valueNode}
30
- </div>
31
- ) : (
32
- <span className="typography-body-medium-medium text-content-presentation-global-primary">
33
- {value != null && value !== "" ? value : "—"}
34
- </span>
35
- )}
36
- </div>
37
- );
38
- }
39
-
40
- DisplayField.displayName = "DisplayField";
@@ -1,137 +0,0 @@
1
- import { ReactNode } from "react";
2
- import type { OutputData } from "@editorjs/editorjs";
3
-
4
- import { Badge } from "../Badge";
5
- import { RichTextField } from "../TextEditor/RichTextField";
6
- import { formatNumber } from "./numberFormat";
7
-
8
- /** A read-only value for `DisplayField`: a plain string, or a rich node. */
9
- export interface FieldView {
10
- value?: string;
11
- valueNode?: ReactNode;
12
- }
13
-
14
- export interface OptionItem {
15
- label: string;
16
- value: string;
17
- icon?: ReactNode;
18
- }
19
-
20
- /** The read-only "kind" a field formats as (a superset grouping of field types). */
21
- export type ViewKind =
22
- | "text"
23
- | "number"
24
- | "currency"
25
- | "option"
26
- | "boolean"
27
- | "date"
28
- | "daterange"
29
- | "dates"
30
- | "multi"
31
- | "richtext"
32
- | "file";
33
-
34
- export interface ViewFormatOptions {
35
- kind: ViewKind;
36
- value: unknown;
37
- options?: OptionItem[];
38
- currencySymbol?: ReactNode;
39
- }
40
-
41
- function optionLabel(options: OptionItem[] | undefined, value: unknown): string {
42
- const found = options?.find((o) => o.value === value);
43
- return found ? found.label : value == null ? "" : String(value);
44
- }
45
-
46
- function formatDate(value: unknown): string {
47
- if (!(value instanceof Date) || Number.isNaN(value.getTime())) return "";
48
- const y = value.getFullYear();
49
- const m = String(value.getMonth() + 1).padStart(2, "0");
50
- const d = String(value.getDate()).padStart(2, "0");
51
- return `${y}/${m}/${d}`;
52
- }
53
-
54
- function multiLabels(options: OptionItem[] | undefined, value: unknown): string[] {
55
- if (!Array.isArray(value)) return [];
56
- return value.map((v) => {
57
- if (v != null && typeof v === "object") {
58
- const o = v as { name?: string; label?: string; value?: string };
59
- return o.name ?? o.label ?? optionLabel(options, o.value);
60
- }
61
- return optionLabel(options, v);
62
- });
63
- }
64
-
65
- /** Formats a field value as read-only content for `mode: "view"`. */
66
- export function formatFieldView({
67
- kind,
68
- value,
69
- options,
70
- currencySymbol,
71
- }: ViewFormatOptions): FieldView {
72
- switch (kind) {
73
- case "number":
74
- return { value: formatNumber(value) };
75
-
76
- case "currency":
77
- return {
78
- value:
79
- value == null || value === ""
80
- ? ""
81
- : `${currencySymbol ? `${currencySymbol} ` : ""}${formatNumber(value)}`,
82
- };
83
-
84
- case "option":
85
- return { value: optionLabel(options, value) };
86
-
87
- case "boolean":
88
- return {
89
- valueNode: <Badge size="S" color={value ? "green" : "gray"} label={value ? "Yes" : "No"} />,
90
- };
91
-
92
- case "date":
93
- return { value: formatDate(value) };
94
-
95
- case "daterange": {
96
- const r = value as { from?: unknown; to?: unknown } | null | undefined;
97
- if (!r || (!r.from && !r.to)) return { value: "" };
98
- return { value: `${formatDate(r.from)} – ${formatDate(r.to)}` };
99
- }
100
-
101
- case "dates": {
102
- const arr = Array.isArray(value) ? value : [];
103
- return { value: arr.map(formatDate).filter(Boolean).join(", ") };
104
- }
105
-
106
- case "multi": {
107
- const labels = multiLabels(options, value);
108
- if (labels.length === 0) return { value: "" };
109
- return {
110
- valueNode: (
111
- <div className="flex flex-wrap gap-1">
112
- {labels.map((label, i) => (
113
- <Badge key={`${label}-${i}`} size="S" color="ocean" label={label} />
114
- ))}
115
- </div>
116
- ),
117
- };
118
- }
119
-
120
- case "richtext":
121
- if (!value) return { value: "" };
122
- return { valueNode: <RichTextField readOnly data={value as OutputData} /> };
123
-
124
- case "file": {
125
- const files = Array.isArray(value) ? value : value ? [value] : [];
126
- if (files.length === 0) return { value: "" };
127
- const names = files.map((f) =>
128
- f instanceof File ? f.name : typeof f === "string" ? f : "file",
129
- );
130
- return { value: names.join(", ") };
131
- }
132
-
133
- case "text":
134
- default:
135
- return { value: value == null ? "" : String(value) };
136
- }
137
- }