torch-glare 2.4.4 → 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.
- package/apps/lib/components/FormBuilder/fields/PhoneField.tsx +25 -0
- package/apps/lib/components/FormBuilder/fields/TableField.tsx +79 -51
- package/apps/lib/components/FormBuilder/form-builder.tsx +11 -3
- package/apps/lib/components/Input.tsx +3 -0
- package/apps/lib/components/SearchableTable.tsx +5 -4
- package/apps/lib/components/SectionBlock.tsx +58 -11
- package/apps/lib/components/Select.tsx +3 -1
- package/apps/lib/components/Table.tsx +265 -67
- package/apps/lib/registry.json +1 -1
- package/apps/lib/tsconfig.tsbuildinfo +1 -1
- package/docs/components/form-builder.md +16 -6
- package/docs/components/section-block.md +78 -2
- package/docs/components/table.md +44 -7
- package/docs/how-to/forms-with-form-builder.md +4 -2
- package/package.json +1 -1
|
@@ -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
|
|
110
|
-
`SectionBlock
|
|
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>`.
|
|
113
|
-
|
|
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`. `
|
|
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
|
|
|
@@ -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
|
-
- **
|
|
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 |
|
|
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). |
|
package/docs/components/table.md
CHANGED
|
@@ -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` | - |
|
|
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-
|
|
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
|
-
|
|
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
|
|
853
|
+
Key overrides:
|
|
820
854
|
|
|
821
|
-
- `flex flex-col` overrides the default `flex-row`
|
|
822
|
-
- `items-center justify-center` overrides `justify-start`
|
|
823
|
-
-
|
|
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`).
|
|
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[]` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "torch-glare",
|
|
3
|
-
"version": "2.4.
|
|
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.",
|