torch-glare 2.4.3 → 2.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -106,17 +106,26 @@ renders a repeating list — its `children` is a render fn `(rowName, index, rem
106
106
  are named `${rowName}.field`.
107
107
 
108
108
  `FormBuilder.Table` is the **table-shaped** counterpart of `FieldArray` (value `object[]`): an editable
109
- grid where each row is a record and each column cell is any `FormBuilder.*` field. It renders **inside a
110
- `SectionBlock`**, so place it as a **top-level child** of the form (not inside a `FormBuilder.Section`).
109
+ grid where each row is a record and each column cell is any `FormBuilder.*` field. It renders its own
110
+ `SectionBlock` (`variant="Table"` — the full-bleed table shell), so place it as a **top-level child** of
111
+ the form, **not** inside a `FormBuilder.Section` — nesting produces a card inside a card. When you need a
112
+ table that isn't a form field, compose the shell yourself with `SectionBlock variant="Table"` (see
113
+ [SectionBlock → Table Variant](./section-block.md#table-variant)).
114
+
111
115
  Each column pairs a `header` with a `cell(rowName, index)` renderer — name the cell's field
112
- `${rowName}.<key>`. The section header carries the actions: a **Add New** button (also repeated as the
113
- bottom footer) and a **Delete Row** button that's disabled until rows are checkbox-selected. Rows support
116
+ `${rowName}.<key>`. Set `width` to size a column; it is applied to both the header and the body cells.
117
+ The section header carries the actions: an **Add New** button and a **Delete Row** button that's disabled
118
+ until rows are checkbox-selected. A second **+ Add New** sits below the grid. Both live *outside* the
119
+ horizontal scroll container, so they stay put when a wide table is scrolled sideways. Rows support
114
120
  **checkbox selection** (+ select-all), **drag-drop reordering**, and — per column, via `sortKey` — a
115
121
  sort toggle in the header. Cells render
116
122
  "bare" (control only) with validation errors shown as a tooltip on the control, so a row stays one line
117
123
  tall, and each field passes `onTable` so it's borderless and blends into the grid. Practical cell fields
118
124
  are the compact ones — `Text`, `Number`, `Currency`, `Select`, `SearchableSelect`, `Date`, `Phone`,
119
125
  `Checkbox`, `SwitchBox`; wide fields (`RichText`, `Signature`, `File`) work but aren't suited to a cell.
126
+ `FormBuilder.Phone` collapses to a **plain number input** in a cell — two controls in one cell is not a
127
+ table column, so add a separate column (e.g. a `FormBuilder.Select` of dial codes) if you need the
128
+ country code.
120
129
 
121
130
  ```tsx
122
131
  <FormBuilder.Table
@@ -158,8 +167,9 @@ clickable. Multi-select is also available as `.MultiSelect` / `.Tags` (a tag-chi
158
167
  renders like any other field — the `label` sits in the normal label column — and the box holds
159
168
  an optional inline `subLabel`, a vertical divider, and the switch.
160
169
 
161
- `FormBuilder.Section` (props `title`, `color`, `icon`) groups fields in a Glare
162
- `SectionBlock`. `FormBuilder.Submit` is a loading-aware submit button. It
170
+ `FormBuilder.Section` (props `title`, `color`, `icon`, `action`, `variant`) groups fields in a Glare
171
+ `SectionBlock`. `action` puts buttons on the title row; `variant="Table"` switches to the
172
+ full-bleed table shell (what `FormBuilder.Table` uses internally). `FormBuilder.Submit` is a loading-aware submit button. It
163
173
  **auto-associates with the enclosing form** (via context), so it submits even when placed in a
164
174
  header / action bar that renders _outside_ the `<form>` — no manual `form={id}` wiring.
165
175
 
@@ -226,6 +236,9 @@ automatically.
226
236
  > [FormRenderer](./form-renderer.md) (pass `fieldDirection="vertical"` to the
227
237
  > form), or use `FormRenderer` with `display="drawer"`.
228
238
 
239
+ > Building a **detail page** with a sidebar of tabs (not a form)? That lives on `FormRenderer`
240
+ > (`FormRenderer.Sidebar` / `.Tab`) — see the FormRenderer docs. `FormBuilder` itself stays form-only.
241
+
229
242
  ## Calculation panel
230
243
 
231
244
  To show computed totals **beside** the form, render a [FormSummary](./form-summary.md)
@@ -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,7 +64,7 @@ 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
70
  | `loading` / `resetOnSuccess` | `boolean` | Forwarded to `FormBuilder`. |
@@ -119,6 +123,57 @@ error overrides it to red. You still pass just the Submit; the nav is wired for
119
123
  </FormRenderer>
120
124
  ```
121
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
+
122
177
  ## Summary panel
123
178
 
124
179
  Pass a hoisted `useForm` as `form` and a `FormSummary` as `summary` — FormRenderer binds
@@ -59,6 +59,9 @@ export function Example() {
59
59
  |------|------|---------|-------------|
60
60
  | `color` | `SectionColor` | `"Blue"` | Color of the title badge. One of `Blue`, `Yellow`, `Green`, `Red`, `Orange`, `Purple`, `Pink`, `Gray`. |
61
61
  | `title` | `ReactNode` | — | Title rendered inside the colored badge. Optional — when omitted, the header is hidden entirely. Accepts any ReactNode (string, JSX with icons, links, etc.). |
62
+ | `icon` | `ReactNode` | — | Rendered inside the badge, to the left of `title`. |
63
+ | `action` | `ReactNode` | — | Right-aligned content on the title row — typically action buttons. |
64
+ | `variant` | `SectionVariant` | `"Default"` | `"Default"` is the padded form card. `"Table"` is the full-bleed table shell: no body padding, a rule under the header, no bottom padding, and the card clipped to its radius. See [Table Variant](#table-variant). |
62
65
  | `containerClassName` | `string` | — | Class name applied to the outer container (alongside `className`). |
63
66
  | `headerClassName` | `string` | — | Class name applied to the header wrapper around the title badge. |
64
67
  | `bodyClassName` | `string` | — | Class name applied to the body wrapper holding `children`. |
@@ -79,10 +82,15 @@ export type SectionColor =
79
82
  | "Pink"
80
83
  | "Gray";
81
84
 
85
+ export type SectionVariant = "Default" | "Table";
86
+
82
87
  export interface SectionBlockProps
83
88
  extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
84
89
  color?: SectionColor;
90
+ variant?: SectionVariant;
85
91
  title?: ReactNode;
92
+ icon?: ReactNode;
93
+ action?: ReactNode;
86
94
  containerClassName?: string;
87
95
  headerClassName?: string;
88
96
  bodyClassName?: string;
@@ -362,6 +370,74 @@ function RowDivider() {
362
370
  }
363
371
  ```
364
372
 
373
+ ### Table Variant
374
+
375
+ `variant="Table"` swaps the padded form body for the full-bleed table shell: the body loses
376
+ its `px-[42px]` gutter, gains a rule under the header, and the card is clipped to its 16px
377
+ radius with no bottom padding — so an end-action row meets the card edge.
378
+
379
+ The layout has three stacked parts, and the order matters. Only the **scroller** scrolls
380
+ horizontally; the header actions above it and the end-action below it stay put, which is
381
+ what keeps `Add New` reachable on a wide table.
382
+
383
+ ```tsx
384
+ import { SectionBlock } from "@/components/SectionBlock";
385
+ import { Button } from "@/components/Button";
386
+ import {
387
+ Table,
388
+ TableBody,
389
+ TableCell,
390
+ TableEndAction,
391
+ TableHead,
392
+ TableHeader,
393
+ TableRow,
394
+ TableScroller,
395
+ } from "@/components/Table";
396
+
397
+ <SectionBlock
398
+ variant="Table"
399
+ color="Purple"
400
+ title="Items Table"
401
+ icon={<i className="ri-box-3-line text-[18px]" />}
402
+ action={
403
+ <>
404
+ <Button type="button" size="M" variant="BorderStyle" disabled>
405
+ Delete Row
406
+ </Button>
407
+ <Button type="button" size="M" variant="BluColStyle">
408
+ Add New
409
+ </Button>
410
+ </>
411
+ }
412
+ >
413
+ <TableScroller>
414
+ <Table className="min-w-full">
415
+ <TableHeader>
416
+ <TableRow>
417
+ <TableHead style={{ width: 200 }}>Items</TableHead>
418
+ <TableHead style={{ width: 200 }}>Label</TableHead>
419
+ </TableRow>
420
+ </TableHeader>
421
+ <TableBody>
422
+ <TableRow>
423
+ <TableCell minWidth={0}>Product name</TableCell>
424
+ <TableCell minWidth={0}>Label</TableCell>
425
+ </TableRow>
426
+ </TableBody>
427
+ </Table>
428
+ </TableScroller>
429
+
430
+ <TableEndAction>
431
+ <i className="ri-add-line" />
432
+ Add New
433
+ </TableEndAction>
434
+ </SectionBlock>
435
+ ```
436
+
437
+ For a table that edits form values, don't hand-compose this — use
438
+ [`FormBuilder.Table`](./form-builder.md#formbuildertable), which renders exactly this shell
439
+ and wires rows to `react-hook-form`.
440
+
365
441
  ### Custom Layout (override defaults)
366
442
 
367
443
  Use `containerClassName`, `headerClassName`, and `bodyClassName` to override the built-in spacing and width without losing the title/body structure.
@@ -384,7 +460,7 @@ Use `containerClassName`, `headerClassName`, and `bodyClassName` to override the
384
460
  - **Color coding**: Use distinct colors to help users scan a page of multiple sections (e.g., Blue for primary forms, Yellow for warnings, Red for destructive zones).
385
461
  - **No-title sections**: Drop the `title` prop entirely when the section's purpose is obvious from context — keeps the body padding without the visual weight of a header.
386
462
  - **Composing with form fields**: `SectionBlock` does not impose any inner layout — pair it with helpers like the `FieldRow` pattern above, or with `InputField`, `Form`, or `FieldSection` for more structured forms.
387
- - **Default width**: The component ships with `w-[1100px]`. Override via `containerClassName="w-full"` (or any specific width) for narrower containers.
463
+ - **Width**: The component is `w-full` it fills its parent. Constrain it from the outside, or with `containerClassName="max-w-[1100px]"`.
388
464
 
389
465
  ## Accessibility
390
466
 
@@ -395,7 +471,7 @@ Use `containerClassName`, `headerClassName`, and `bodyClassName` to override the
395
471
 
396
472
  | Issue | Fix |
397
473
  |-------|-----|
398
- | Card overflows on small screens | Default width is `w-[1100px]`. Override with `containerClassName="w-full"` or a smaller fixed width. |
474
+ | Card overflows on small screens | The card is `w-full`; the overflow is coming from its content. Give the content `min-w-0`, or cap the card with `containerClassName="max-w-[…]"`. |
399
475
  | Title not showing | The `title` prop is optional — omitting it hides the entire header. Pass any non-null `ReactNode` to render it. |
400
476
  | Badge color looks wrong | `color` only accepts the predefined `SectionColor` values. For custom badge colors, override via `headerClassName` and a custom child node. |
401
477
  | Need a different background | The body uses `bg-background-presentation-form-base`. Override via `containerClassName` (your class wins via `cn()` merging). |
@@ -386,18 +386,41 @@ function ResizableTable() {
386
386
  | `disabled` | `boolean` | `false` | Disables interactions |
387
387
  | `sortType` | `'asc' \| 'desc' \| undefined` | - | Sort indicator |
388
388
  | `onSort` | `() => void` | - | Sort handler |
389
+ | `sortLabel` | `string` | - | Column name for the sort button's accessible label |
390
+ | `onResize` | `(width: number) => void` | - | Fires while the column is drag-resized. Passing it makes the width **controlled** — you own the value and feed it back via `style.width`. Required whenever the table needs a definite width (`table-layout: fixed`), since only the owner of every column width can total them. Omit for uncontrolled resizing. |
389
391
  | `isDummy` | `boolean` | `false` | Non-interactive header |
390
- | `className` | `string` | - | Additional CSS classes |
392
+ | `className` / `style` | `string` / `CSSProperties` | - | Applied to the `<th>` — this is how you size a column (`style={{ width: 200 }}`) |
393
+ | `contentClassName` | `string` | - | Classes for the inner layout box (the flex row holding the label and sort toggle). Use it for typography or colour; `className` styles the `<th>` itself. |
394
+
395
+ **Every** prop you pass — `className`, `style`, `aria-*`, `role`, `tabIndex` and event
396
+ handlers alike — lands on the `<th>`. They deliberately share one element: libraries like
397
+ dnd-kit hand back accessibility attributes and event listeners as a matched pair, and
398
+ splitting them across two nodes leaves the focusable element without its handlers. Reach the
399
+ inner layout box with `contentClassName`.
391
400
 
392
401
  ### TableCell Props
393
402
 
394
403
  | Prop | Type | Default | Description |
395
404
  |------|------|---------|-------------|
396
405
  | `isDummy` | `boolean` | `false` | Non-interactive cell |
406
+ | `minWidth` | `number` | `200` | Minimum width of the content box, in px. Pass `0` when the column width is driven by the caller — otherwise this floor silently overrides any narrower column. |
407
+ | `fade` | `boolean` | `true` | Whether the cell **crops** its content and fades the last 25% of it, signalling text clipped by the column width. Set `false` for cells holding a **control**: the fade washes out whatever sits at the right edge (a Select's chevron, a date button), and the crop — the content box hugs the control exactly — would clip every side of the control's hover/focus drop shadow. `isDummy` cells never fade. |
397
408
  | `childrenClassName` | `string` | - | Classes for content wrapper |
398
409
  | `className` | `string` | - | Additional CSS classes |
399
410
  | `children` | `React.ReactNode` | - | Cell content |
400
411
 
412
+ ### TableScroller Props
413
+
414
+ Horizontal scroll container for a table, with the thin design-system scrollbar (4px track
415
+ that thickens and turns blue on hover). Accepts any `div` props. Keep header actions and
416
+ `TableEndAction` **outside** it so they don't scroll away on a wide table.
417
+
418
+ ### TableEndAction Props
419
+
420
+ The full-width action bar that sits **below** a table — e.g. `+ Add New`. A `<button>`, not
421
+ a `<tr>`, so it is a sibling of `TableScroller` and stays put while the columns scroll. Accepts
422
+ any `button` props. Use `TableFooterButton` instead when the action should scroll with the grid.
423
+
401
424
  ### TableCheckbox Props
402
425
 
403
426
  | Prop | Type | Default | Description |
@@ -434,15 +457,24 @@ interface TableHeadProps extends ThHTMLAttributes<HTMLTableCellElement> {
434
457
  disabled?: boolean
435
458
  sortType?: 'asc' | 'desc' | undefined
436
459
  onSort?: () => void
460
+ sortLabel?: string
461
+ onResize?: (width: number) => void
462
+ contentClassName?: string
437
463
  isDummy?: boolean
438
464
  }
439
465
 
440
466
  // TableCell types
441
467
  interface TableCellProps extends TdHTMLAttributes<HTMLTableCellElement> {
442
468
  isDummy?: boolean
469
+ minWidth?: number
470
+ fade?: boolean
443
471
  childrenClassName?: string
444
472
  }
445
473
 
474
+ // TableScroller / TableEndAction types
475
+ type TableScrollerProps = HTMLAttributes<HTMLDivElement>
476
+ type TableEndActionProps = ButtonHTMLAttributes<HTMLButtonElement>
477
+
446
478
  // TableCheckbox types
447
479
  interface TableCheckboxProps extends ButtonHTMLAttributes<HTMLButtonElement> {
448
480
  id: string
@@ -795,7 +827,7 @@ Dates and amounts wrap awkwardly when the column is narrow. Add `whitespace-nowr
795
827
 
796
828
  ### `TableCell` force-wraps children — use `childrenClassName` to override
797
829
 
798
- `TableCell` unconditionally wraps children in an inner `<div>` with `flex justify-start items-center gap-1 min-w-[200px] overflow-hidden` plus a fade-out gradient mask. Three common breakages:
830
+ `TableCell` unconditionally wraps children in an inner `<div>` with `flex justify-start items-center gap-1`, a `min-width` of 200px, `overflow-hidden`, and a fade-out gradient mask. Three common breakages:
799
831
 
800
832
  1. **Empty-state rows don't center.** A `flex flex-col items-center` empty state ends up flush left because the outer wrapper's `justify-start` already decided alignment.
801
833
  2. **Multi-line content is clipped** by `overflow-hidden` + the gradient mask.
@@ -807,7 +839,9 @@ Dates and amounts wrap awkwardly when the column is narrow. Add `whitespace-nowr
807
839
  <TableRow>
808
840
  <TableCell
809
841
  colSpan={7}
810
- childrenClassName="flex flex-col items-center justify-center gap-3 py-12 w-full min-w-0 text-content-presentation-global-secondary"
842
+ minWidth={0}
843
+ fade={false}
844
+ childrenClassName="flex flex-col items-center justify-center gap-3 py-12 w-full text-content-presentation-global-secondary"
811
845
  >
812
846
  <i className="ri-inbox-line text-4xl opacity-60" />
813
847
  <p className="typography-body-medium-regular">No data</p>
@@ -816,11 +850,14 @@ Dates and amounts wrap awkwardly when the column is narrow. Add `whitespace-nowr
816
850
  </TableRow>
817
851
  ```
818
852
 
819
- Key overrides on `childrenClassName`:
853
+ Key overrides:
820
854
 
821
- - `flex flex-col` overrides the default `flex-row`
822
- - `items-center justify-center` overrides `justify-start`
823
- - `w-full min-w-0` overrides the hardcoded `min-w-[200px]`
855
+ - `flex flex-col` on `childrenClassName` overrides the default `flex-row`
856
+ - `items-center justify-center` on `childrenClassName` overrides `justify-start`
857
+ - **`minWidth={0}`** clears the 200px floor. It is an inline style, so a class such as
858
+ `min-w-0` on `childrenClassName` cannot beat it — use the prop.
859
+ - **`fade={false}`** drops both the gradient mask and the `overflow-hidden` that clips
860
+ multi-line content.
824
861
 
825
862
  ## Accessibility
826
863
 
@@ -116,7 +116,9 @@ export function ItemForm({
116
116
  ```
117
117
 
118
118
  `FormBuilder.Section` groups fields in a `SectionBlock` (`color` is one of `Blue`, `Yellow`,
119
- `Green`, `Red`, `Orange`, `Purple`, `Pink`, `Gray`). Pass the Save via `actions` — a
119
+ `Green`, `Red`, `Orange`, `Purple`, `Pink`, `Gray`). It also takes `icon`, `action`
120
+ (right-aligned buttons on the title row) and `variant` — `variant="Table"` switches to the
121
+ full-bleed table shell that `FormBuilder.Table` uses internally. Pass the Save via `actions` — a
120
122
  `FormBuilder.Submit`, which is loading-aware; it renders in the header action pill. (With raw
121
123
  `FormBuilder`, put the same `FormBuilder.Submit` in a `FormBuilder.Header`.)
122
124
 
@@ -140,7 +142,7 @@ Every field, its underlying control, and the value your `onSubmit` receives:
140
142
  | `.Otp` (`length`) | `string` |
141
143
  | `.Slider` (`min`, `max`, `step`, `range`, `suffix`) | `number` (or `[number, number]` with `range`) |
142
144
  | `.Color` (`presets`, `alpha`) | hex `string` |
143
- | `.Phone` (`defaultCountry`, defaults to `+964`) | `string` (`"+<dial> <number>"`) |
145
+ | `.Phone` (`defaultCountry`, defaults to `+964`) | `string` (`"+<dial> <number>"`) — collapses to a plain number input inside a `.Table` cell, where `defaultCountry` does not apply |
144
146
  | `.Date` | `Date` |
145
147
  | `.DateRange` | `{ from, to }` |
146
148
  | `.DateMultiple` | `Date[]` |
@@ -391,6 +393,60 @@ drawer's tray, beside the form:
391
393
 
392
394
  ---
393
395
 
396
+ ## 8. A detail (view) page — sidebar tabs, not a form
397
+
398
+ Sometimes you want to **display** a record, not edit it. Give `FormRenderer` `FormRenderer.Sidebar`
399
+
400
+ - `FormRenderer.Tab` children (instead of fields) and it switches to a display-only detail page: a
401
+ left **sidebar** where each item swaps in its matching tab panel — no `<form>`, no submit. The
402
+ sidebar sits where a stepper's rail would; only the active panel shows.
403
+
404
+ **Every tab's content is `FormBuilder.Section` blocks.** Inside a Section, use the default
405
+ `FormRenderer.Grid` + `FormRenderer.Row` display cells (the read-only counterpart of form fields), or
406
+ render **your own component** — anything goes inside a Section.
407
+
408
+ ```tsx
409
+ <FormRenderer
410
+ header={{ title: "Order DE-344", variant: "detail" }} // "View" badge
411
+ actions={<Button variant="BorderStyle">Print</Button>}
412
+ >
413
+ {/* Rail — each Item's `value` ties to a Tab. */}
414
+ <FormRenderer.Sidebar>
415
+ <FormRenderer.Sidebar.Item value="overview" icon={<i className="ri-layout-grid-line" />}>
416
+ Overview
417
+ </FormRenderer.Sidebar.Item>
418
+ <FormRenderer.Sidebar.Item value="activity" icon={<i className="ri-pulse-line" />}>
419
+ Activity log
420
+ </FormRenderer.Sidebar.Item>
421
+ </FormRenderer.Sidebar>
422
+
423
+ {/* Default display cells */}
424
+ <FormRenderer.Tab value="overview">
425
+ <FormBuilder.Section title="Main Information" color="Blue">
426
+ <FormRenderer.Grid columns={2}>
427
+ <FormRenderer.Row label="PO Number" value={record.poNumber} />
428
+ <FormRenderer.Row label="Status" value={<Badge label="Submitted" color="yellow" />} />
429
+ </FormRenderer.Grid>
430
+ </FormBuilder.Section>
431
+ </FormRenderer.Tab>
432
+
433
+ {/* Or bring your own component — still inside a Section */}
434
+ <FormRenderer.Tab value="activity">
435
+ <FormBuilder.Section title="Activity log" color="Green">
436
+ <YourTimeline items={record.activity} />
437
+ </FormBuilder.Section>
438
+ </FormRenderer.Tab>
439
+ </FormRenderer>
440
+ ```
441
+
442
+ `FormRenderer.Grid` takes `columns` (1–3, default 2) and spans the full section width; `FormRenderer.Row`
443
+ takes `label` + `value` (any node). The first `Tab` is active by default.
444
+
445
+ > Generating one? Call the `create-form` tool with `layout="detail"` — it returns this exact wiring
446
+ > with your fields pre-filled as display rows.
447
+
448
+ ---
449
+
394
450
  ## Gotchas
395
451
 
396
452
  - **Hoisting `useForm` disables the remount-`key` reset.** Once the form instance lives in
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "torch-glare",
3
- "version": "2.4.3",
3
+ "version": "2.4.5",
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.",