torch-glare 2.5.5 → 2.5.6
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/BadgeField.tsx +68 -3
- package/apps/lib/components/Button.tsx +10 -2
- package/apps/lib/components/DataViews/data-views.tsx +17 -5
- package/apps/lib/components/DataViews/index.ts +8 -4
- package/apps/lib/components/DataViews/slots.ts +9 -0
- package/apps/lib/components/DataViews/states.tsx +43 -8
- package/apps/lib/components/Drawer.tsx +70 -39
- package/apps/lib/components/DropdownMenu.tsx +14 -0
- package/apps/lib/components/FormBuilder/context.ts +12 -0
- package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +19 -14
- package/apps/lib/components/FormBuilder/fields/SelectField.tsx +31 -8
- package/apps/lib/components/FormBuilder/submit.tsx +21 -1
- package/apps/lib/components/FormBuilder/types.ts +5 -0
- package/apps/lib/components/FormRenderer/FormDrawer.tsx +139 -17
- package/apps/lib/components/FormRenderer/detail.tsx +57 -8
- package/apps/lib/components/FormRenderer/form-renderer.tsx +66 -5
- package/apps/lib/components/FormRenderer/index.ts +2 -0
- package/apps/lib/components/FormRenderer/notch-action.tsx +64 -0
- package/apps/lib/components/FormRenderer/stepper.tsx +56 -2
- package/apps/lib/components/FormRenderer/types.ts +37 -0
- package/apps/lib/components/SectionBlock.tsx +24 -3
- package/apps/lib/components/Select.tsx +9 -9
- package/apps/lib/components/SlideDatePicker.tsx +2 -0
- package/apps/lib/components/Table.tsx +15 -28
- package/apps/lib/hooks/useActiveTreeItem.ts +4 -1
- package/apps/lib/hooks/useHtmlDir.ts +31 -0
- package/apps/lib/hooks/useTagSelection.ts +95 -9
- package/apps/lib/registry.json +20 -4
- package/apps/lib/utils/scroller.ts +26 -0
- package/docs/components/badge-field.md +26 -0
- package/docs/components/data-views/index.md +31 -5
- package/docs/components/data-views/migration.md +7 -5
- package/docs/components/drawer.md +5 -5
- package/docs/components/form-builder.md +9 -1
- package/docs/components/form-renderer.md +71 -1
- package/docs/components/section-block.md +6 -0
- package/docs/migration/changelog.md +6 -0
- package/docs/reference/hooks.md +23 -0
- package/docs/reference/utilities.md +22 -0
- package/package.json +1 -1
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
useDataViewsFilters, useDataViewsPanel, useDataViewsPanelTabs,
|
|
40
40
|
useActiveRow, // the row behind `activeId`
|
|
41
41
|
Cell, // paint one field the way the views paint it
|
|
42
|
-
markView, markHeader, markPanel,
|
|
42
|
+
markView, markHeader, markPanel, markEmpty, // register a part of your own
|
|
43
43
|
SkeletonBar, skeletonKeys, // the loading pieces every view is built from
|
|
44
44
|
getByPath, formatPathLabel, defaultGetRowId, // read a value by dotted path
|
|
45
45
|
buildCardRows, resolveBadgeVariant,
|
|
@@ -135,10 +135,35 @@ see the change, "new filter" and "new page" have already become one object.
|
|
|
135
135
|
|
|
136
136
|
## Empty and loading
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
138
|
+
**Loading is not a part you render.** While `loading` is set, each view paints a **skeleton in its
|
|
139
|
+
own shape**: the table shimmers rows at the real row height and column widths, the board shimmers
|
|
140
|
+
cards inside its columns.
|
|
141
|
+
|
|
142
|
+
**Empty is opt-in.** By default the view simply shows nothing — the table keeps its header band and
|
|
143
|
+
has no rows, the board keeps its columns and has no cards. That default is deliberate: a centred
|
|
144
|
+
message in place of the view throws away the chrome, and it cannot tell "no results" from "not
|
|
145
|
+
fetched yet".
|
|
146
|
+
|
|
147
|
+
When you do want something there, render `DataViews.Empty` anywhere among the children. It is a
|
|
148
|
+
passthrough with a marker — it holds no opinion about what an empty state looks like, it only tells
|
|
149
|
+
the root to put its content where the view goes. The root swaps it in when the query has settled and
|
|
150
|
+
returned nothing (`!loading && rows.length === 0`):
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
<DataViews rows={rows} fields={fields} loading={loading}>
|
|
154
|
+
<DataViews.Table />
|
|
155
|
+
<DataViews.Empty>
|
|
156
|
+
<div className="flex flex-1 flex-col items-center justify-center gap-2">
|
|
157
|
+
<p>No invoices match these filters.</p>
|
|
158
|
+
<Button onClick={clearFilters}>Clear filters</Button>
|
|
159
|
+
</div>
|
|
160
|
+
</DataViews.Empty>
|
|
161
|
+
</DataViews>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Your content is what receives the body slot's height, so give it `flex-1` if it should centre.
|
|
165
|
+
Wrapping `DataViews.Empty` in a component of your own? Mark that wrapper with `markEmpty` so the
|
|
166
|
+
root still recognises it, the same way `markView` / `markHeader` / `markPanel` work.
|
|
142
167
|
|
|
143
168
|
## Large datasets
|
|
144
169
|
|
|
@@ -183,6 +208,7 @@ and that is not built.
|
|
|
183
208
|
| `DataViews.Board` | a kanban board | `groups` — it never groups rows itself | [`views`](./examples/views.md) |
|
|
184
209
|
| `DataViews.Inbox` | a master list beside a detail pane | the pane, as `children` | [`inbox-routing`](./examples/inbox-routing.md) |
|
|
185
210
|
| `DataViews.Tree` | a hierarchy, optionally beside a pane | `nodes` — it never builds one | [`tree-custom`](./examples/tree-custom.md) |
|
|
211
|
+
| `DataViews.Empty` | your content, in place of the view, once a settled query returns no rows | the content — it holds no opinion about what empty looks like | — |
|
|
186
212
|
|
|
187
213
|
Each takes `id`, `label` and `icon` to control how it appears in the switcher, so the same view can
|
|
188
214
|
be registered twice with different data. Full props are under
|
|
@@ -56,11 +56,13 @@ Three parts of `DataViews` itself went in the same release.
|
|
|
56
56
|
`rows`; whether there is more is derived from `rows.length < total`, so there is no `hasMore` prop.
|
|
57
57
|
See the *Large datasets* section of the [DataViews doc](./index.md).
|
|
58
58
|
|
|
59
|
-
**`DataViews.Empty`** —
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
**`DataViews.Empty`** — *removed, then re-added as opt-in.* It was dropped because rendering a
|
|
60
|
+
centred message in place of the view threw away the chrome, and it could not tell "no results" apart
|
|
61
|
+
from "not fetched yet" — so the first load of every page announced that nothing matched before
|
|
62
|
+
anything had been asked for. It is back on different terms: the default is still to show nothing,
|
|
63
|
+
and `DataViews.Empty` now renders **only** once the query has settled with no rows
|
|
64
|
+
(`!loading && rows.length === 0`). Nothing to migrate — omit it and behaviour is unchanged. See the
|
|
65
|
+
*Empty and loading* section of the [DataViews doc](./index.md).
|
|
64
66
|
|
|
65
67
|
**`DataViews.Loading`** — each view now paints its own skeleton, in its own shape, driven by the
|
|
66
68
|
`loading` prop. A custom view registered with `markView` gets the same thing: read `loading` from
|
|
@@ -433,7 +433,7 @@ There is **no automatic per-direction styling**. The anchor is set on the root w
|
|
|
433
433
|
| Drag handle | on — `<DrawerPanel showHandle>` | off (the default) | off (the default) |
|
|
434
434
|
| Frame / tray | usually off (`framed={false}`) for clean sheet | on (default) for the dark tray | on (default) |
|
|
435
435
|
| Rounded corners | top corners only | all/left corners | top-left + bottom corners |
|
|
436
|
-
| Notch side |
|
|
436
|
+
| Notch side | inline-start (`notchSide="start"`) | inline-start | mirror to `notchSide="end"` |
|
|
437
437
|
| Best for | mobile sheets, action sheets, comments | create/edit forms, filters, detail panels | RTL panels, side navigation |
|
|
438
438
|
|
|
439
439
|
### Bottom (default)
|
|
@@ -509,7 +509,7 @@ A floating panel anchored to the right edge — the canonical home for create/ed
|
|
|
509
509
|
|
|
510
510
|
### Left (RTL / navigation)
|
|
511
511
|
|
|
512
|
-
Mirror of the right recipe: `direction="left"`, anchor to the left edge, and if you use a notch, set `notchSide="
|
|
512
|
+
Mirror of the right recipe: `direction="left"`, anchor to the left edge, and if you use a notch, set `notchSide="end"` so the tab sits on the panel's trailing edge.
|
|
513
513
|
|
|
514
514
|
```tsx
|
|
515
515
|
<Drawer direction="left">
|
|
@@ -517,7 +517,7 @@ Mirror of the right recipe: `direction="left"`, anchor to the left edge, and if
|
|
|
517
517
|
<Button variant="PrimeStyle">Open left drawer</Button>
|
|
518
518
|
</DrawerTrigger>
|
|
519
519
|
<DrawerContent
|
|
520
|
-
notchSide="
|
|
520
|
+
notchSide="end"
|
|
521
521
|
wrapperClassName="top-2 left-2 bottom-2 right-auto mt-0 h-auto w-[420px] max-w-[calc(100vw-16px)]"
|
|
522
522
|
notch={
|
|
523
523
|
<DrawerNotch>
|
|
@@ -589,7 +589,7 @@ brings its own background. This is where direction-specific styling is applied.
|
|
|
589
589
|
|---|---|---|---|
|
|
590
590
|
| `framed` | `boolean` | `true` | Show the dark "tray" frame (border + inset shadow) around the panel. Set `false` for clean bottom sheets. |
|
|
591
591
|
| `notch` | `ReactNode` | — | A `DrawerNotch` tab rendered on the top edge. |
|
|
592
|
-
| `notchSide` | `"
|
|
592
|
+
| `notchSide` | `"start" \| "end"` | `"start"` | Which **inline** edge the notch attaches to (and which corner stays square). Logical, so it mirrors under `dir="rtl"` without you computing a direction. Use `"end"` for left-anchored drawers. |
|
|
593
593
|
| `wrapperClassName` | `string` | — | Classes on the outer positioned element — this is how you anchor/size the panel per direction. |
|
|
594
594
|
| `className` | `string` | — | Classes on the **dark tray**. Add a `gap-*` here when the tray holds more than one child. |
|
|
595
595
|
| `trayClassName` | `string` | — | **Deprecated** — an alias for `className` (merged last, so it still wins). |
|
|
@@ -629,7 +629,7 @@ with each bringing its own background.
|
|
|
629
629
|
| `DrawerDescription` | Muted supporting text (maps to Vaul `Drawer.Description`). |
|
|
630
630
|
| `DrawerBadge` | Small uppercase status pill. `color`: `Blue \| Green \| Red \| Yellow \| Purple \| Gray` (default `Blue`). |
|
|
631
631
|
| `DrawerFooter` | Bottom action area (`mt-auto`, stacked). |
|
|
632
|
-
| `DrawerNotch` | The top-edge tab container. `side`: `"
|
|
632
|
+
| `DrawerNotch` | The top-edge tab container. `side`: `"start" \| "end"` (default `"start"`); normally cloned in by `DrawerContent` from `notchSide`. |
|
|
633
633
|
| `DrawerNotchClose` | Round close button for inside a notch. |
|
|
634
634
|
| `DrawerNotchPill` | Pill button for inside a notch. `color`: `Yellow \| Blue \| Gray` (default `Yellow`). Styled `<button>` only — wire navigation yourself via `onClick` (see ["What 'Open in new tab' does"](#what-open-in-new-tab-does)). |
|
|
635
635
|
| `DrawerNotchDivider` | Thin vertical divider between notch items. |
|
|
@@ -136,6 +136,7 @@ that.
|
|
|
136
136
|
| `FormBuilder.Select` (`options`) | `Select` | `string` |
|
|
137
137
|
| `FormBuilder.SearchableSelect` (`options`, async: `onSearchChange`/`onLoadMore`/`hasMore`) | `SearchableSelect` | `string` |
|
|
138
138
|
| `FormBuilder.MultiSelect` / `.Tags` (`options`) | `BadgeField` | `string[]` |
|
|
139
|
+
| `FormBuilder.MultiSelect` / `.Tags` (`creatable`) | `BadgeField` with free text | `string[]` — values not in `options` survive |
|
|
139
140
|
| `FormBuilder.RadioList` (`options`, each with optional `description`) | boxed radio list | `string` |
|
|
140
141
|
| `FormBuilder.CheckboxGroup` (`options`, each with optional `description`) | boxed checkbox list | `string[]` |
|
|
141
142
|
| `FormBuilder.RadioCards` (`options` with `description`) | `RadioCard` | `string` |
|
|
@@ -214,7 +215,10 @@ country code.
|
|
|
214
215
|
`FormBuilder.RadioList` (single-select, `string`) and `FormBuilder.CheckboxGroup` (multi-select,
|
|
215
216
|
`string[]`) render their `options` as a boxed, divided list — control on the left, primary
|
|
216
217
|
label, and an optional per-option `description` shown as a secondary label. The whole row is
|
|
217
|
-
clickable. Multi-select is also available as `.MultiSelect` / `.Tags` (a tag-chip picker).
|
|
218
|
+
clickable. Multi-select is also available as `.MultiSelect` / `.Tags` (a tag-chip picker). Add
|
|
219
|
+
`creatable` to either and the user can type a value that is not in `options` and commit it with
|
|
220
|
+
Enter or comma; pass `options={[]}` for a pure free-text list. (`createLabel` is a `BadgeField`
|
|
221
|
+
prop — it is not forwarded, so the create row keeps its default label inside a form.)
|
|
218
222
|
|
|
219
223
|
`FormBuilder.SwitchBox` (value `boolean`) is a switch wrapped in a `#f9f9f9` field box. It
|
|
220
224
|
renders like any other field — the `label` sits in the normal label column — and the box holds
|
|
@@ -227,6 +231,10 @@ presentation, so it lives there.
|
|
|
227
231
|
form** (via context), so it submits even when placed in a header / action bar that renders
|
|
228
232
|
_outside_ the `<form>` — no manual `form={id}` wiring.
|
|
229
233
|
|
|
234
|
+
It also takes `disabled`, for permission gating: someone with read access should still *see* a
|
|
235
|
+
record, so a Save they may not use is disabled rather than removed — a missing button looks broken,
|
|
236
|
+
a disabled one says "not yours to change". The server refuses the write either way.
|
|
237
|
+
|
|
230
238
|
## Moved to FormRenderer
|
|
231
239
|
|
|
232
240
|
These all used to live here. They are chrome, so they now live on
|
|
@@ -58,6 +58,7 @@ It renders in the form's header action pill (page) or the drawer header (drawer)
|
|
|
58
58
|
| `FormRenderer.Back` / `.Next` | Chevron step controls. The header's action bar prepends them for you. |
|
|
59
59
|
| `FormRenderer.Sidebar` / `.Tab` | The display-only [detail-tabs](#detail-tabs-sidebar) view. |
|
|
60
60
|
| `FormRenderer.Grid` / `.Row` | Read-only display cells inside a detail tab. |
|
|
61
|
+
| `FormRenderer.NotchAction` | A pill in the drawer's notch. You own the label, so it translates. |
|
|
61
62
|
|
|
62
63
|
### `FormRenderer.Section`
|
|
63
64
|
|
|
@@ -109,6 +110,10 @@ import { FormBuilder } from "@/components/FormBuilder";
|
|
|
109
110
|
| `actions` | `ReactNode` | The form's action bar — rendered in the header action pill (page) or drawer header (drawer). Put the Save here: `actions={<FormBuilder.Submit>Save</FormBuilder.Submit>}`. A bare `FormBuilder.Submit` auto-targets this form. |
|
|
110
111
|
| `id` | `string` | `id` on the underlying `<form>`. Optional — FormRenderer generates and wires one otherwise. |
|
|
111
112
|
| `open` / `onOpenChange` / `title` / `badge` / `onOpenInNewTab` | — | Drawer control (when `display="drawer"`). `title` / `badge` are strings that override `header.title` / `header.label`. |
|
|
113
|
+
| `embedded` | `boolean` | Render without the rounded body card, for a host that already draws one. Defaults to `true` in a drawer. |
|
|
114
|
+
| `activeTab` / `onTabChange` | `string` / `(tab) => void` | Make the [detail-tabs](#detail-tabs-sidebar) rail controlled, so the tab can live in the URL (`?tab=audit`) and survive a reload. Omit both for the uncontrolled default. Inert in form mode. |
|
|
115
|
+
| `activeStep` / `onStepChange` | `number` / `(index) => void` | External control of a `FormRenderer.Stepper`'s step, for a wizard owned by something other than form validity. In controlled mode internal advancement is suppressed and a click is *reported*, not applied. Omit both and the stepper behaves exactly as before. |
|
|
116
|
+
| `drawer` | `{ side?; nested?; framed?; hideHeader?; bareBody?; description?; wrapperClassName?; className? }` | Drawer layout, forwarded to `FormDrawer` — see [Drawer layout](#drawer-layout). One object rather than eight flat props, since none of it means anything on a page. |
|
|
112
117
|
|
|
113
118
|
## Drawer
|
|
114
119
|
|
|
@@ -129,6 +134,60 @@ import { FormBuilder } from "@/components/FormBuilder";
|
|
|
129
134
|
</FormRenderer>
|
|
130
135
|
```
|
|
131
136
|
|
|
137
|
+
### Drawer layout
|
|
138
|
+
|
|
139
|
+
Everything about how the drawer is *shaped* goes in one `drawer` object, because none of it means
|
|
140
|
+
anything on a page:
|
|
141
|
+
|
|
142
|
+
| Key | Type | Default | What it does |
|
|
143
|
+
| ----------------- | ----------------------------- | --------------- | -------------------------------------------------------------------------------------------------- |
|
|
144
|
+
| `side` | `'inline-end' \| 'bottom'` | `'inline-end'` | Which edge it slides from. `'bottom'` is a sheet and drops the notch — there is no inline edge to hang it from. The default follows document direction. |
|
|
145
|
+
| `nested` | `boolean` | `false` | **Required** when this drawer opens inside another one, or the two roots fight over the overlay and the scroll lock. Throws without a parent Drawer. |
|
|
146
|
+
| `framed` | `boolean` | `true` | The dark tray frame and the panel's border / inset shadow. |
|
|
147
|
+
| `hideHeader` | `boolean` | `false` | Skip the header bar, for a child that draws its own. Also drops the body's top padding. |
|
|
148
|
+
| `bareBody` | `boolean` | `false` | Skip the padded scroll wrapper, for a child that already scrolls and offsets for its own header. |
|
|
149
|
+
| `description` | `string` | — | Screen-reader-only description. vaul warns when a drawer has none. |
|
|
150
|
+
| `wrapperClassName`| `string` | per `side` | Lands on the positioner — width, height, insets. Replaces the default sizing. |
|
|
151
|
+
| `className` | `string` | — | Lands on the tray. |
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
// A bottom sheet, opened from inside another drawer.
|
|
155
|
+
<FormRenderer
|
|
156
|
+
display="drawer"
|
|
157
|
+
open={open}
|
|
158
|
+
onOpenChange={setOpen}
|
|
159
|
+
title="Quick add"
|
|
160
|
+
drawer={{ side: "bottom", nested: true, description: "Add a line item" }}
|
|
161
|
+
onSubmit={save}
|
|
162
|
+
actions={<FormBuilder.Submit>Save</FormBuilder.Submit>}
|
|
163
|
+
>
|
|
164
|
+
{fields}
|
|
165
|
+
</FormRenderer>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### `FormRenderer.NotchAction`
|
|
169
|
+
|
|
170
|
+
Buttons in the drawer's notch, authored by you. `onOpenInNewTab` still works, but it hardcodes an
|
|
171
|
+
English label and allows only one action — write `NotchAction` children instead and you own both.
|
|
172
|
+
They render nothing where you write them; the renderer lifts them into the notch.
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
<FormRenderer display="drawer" open={open} onOpenChange={setOpen} title={t("invoice")}>
|
|
176
|
+
<FormRenderer.NotchAction onClick={openFullPage}>
|
|
177
|
+
{t("openInNewTab")}
|
|
178
|
+
<i className="ri-arrow-right-up-line text-[12px]" />
|
|
179
|
+
</FormRenderer.NotchAction>
|
|
180
|
+
<FormRenderer.NotchAction color="Blue" onClick={print}>
|
|
181
|
+
{t("print")}
|
|
182
|
+
</FormRenderer.NotchAction>
|
|
183
|
+
|
|
184
|
+
{fields}
|
|
185
|
+
</FormRenderer>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`color` matches `DrawerNotchPill` and defaults to `"Yellow"`. Ignored on a page-display form, and a
|
|
189
|
+
`side: "bottom"` sheet has no notch to put them in.
|
|
190
|
+
|
|
132
191
|
## Stepper
|
|
133
192
|
|
|
134
193
|
Drop a `FormRenderer.Stepper` in as the child. The Save lives in the header `actions` and
|
|
@@ -140,6 +199,10 @@ validates the current step, then advances (disabled on the last step). A step th
|
|
|
140
199
|
validation **stays checked** in the rail — even after you navigate back — while a live validation
|
|
141
200
|
error overrides it to red. You still pass just the Submit; the nav is wired for you:
|
|
142
201
|
|
|
202
|
+
The rail **stays put while the fields scroll**. It is navigation, so it pins below the header rather
|
|
203
|
+
than scrolling out of view with the form; a rail taller than the form body scrolls to its end first,
|
|
204
|
+
as any sticky element does.
|
|
205
|
+
|
|
143
206
|
```tsx
|
|
144
207
|
<FormRenderer
|
|
145
208
|
onSubmit={save}
|
|
@@ -266,7 +329,14 @@ outside the `<form>`, wire the Save button to the form via `id` / `form={id}`:
|
|
|
266
329
|
```
|
|
267
330
|
|
|
268
331
|
`FormDrawer` props: `open`, `onOpenChange`, `title`, `badge`, `variant`, `actions`,
|
|
269
|
-
`onOpenInNewTab`, `children`, `summary
|
|
332
|
+
`onOpenInNewTab`, `notchActions`, `children`, `summary`, plus the layout flags listed under
|
|
333
|
+
[Drawer layout](#drawer-layout) — `side`, `nested`, `framed`, `hideHeader`, `bareBody`,
|
|
334
|
+
`description`, `wrapperClassName`, `className`. It owns no form state.
|
|
335
|
+
|
|
336
|
+
`notchActions` is the notch's button slot. Going through `FormRenderer` you rarely set it directly —
|
|
337
|
+
write `FormRenderer.NotchAction` children and they are lifted into it for you. Using `FormDrawer` on
|
|
338
|
+
its own, pass the buttons here. When both `notchActions` and `onOpenInNewTab` are given, the former
|
|
339
|
+
wins; `onOpenInNewTab` remains only for callers happy with its built-in English label.
|
|
270
340
|
|
|
271
341
|
### The title
|
|
272
342
|
|
|
@@ -380,6 +380,12 @@ The layout has three stacked parts, and the order matters. Only the **scroller**
|
|
|
380
380
|
horizontally; the header actions above it and the end-action below it stay put, which is
|
|
381
381
|
what keeps `Add New` reachable on a wide table.
|
|
382
382
|
|
|
383
|
+
The section body is itself the horizontal scrollport, so a table wider than the card scrolls
|
|
384
|
+
**inside** it rather than being clipped or widening the page. One consequence worth knowing: a
|
|
385
|
+
scrollport is the containing block for `position: sticky`, so a `Table`'s sticky header inside a
|
|
386
|
+
section now resolves against a box that never scrolls vertically — i.e. it stops sticking. If you
|
|
387
|
+
need a sticky header, give the table its own vertical scroller.
|
|
388
|
+
|
|
383
389
|
```tsx
|
|
384
390
|
import { SectionBlock } from "@/components/SectionBlock";
|
|
385
391
|
import { Button } from "@/components/Button";
|
|
@@ -28,6 +28,12 @@ npx torch-glare@latest add Button
|
|
|
28
28
|
|
|
29
29
|
- **v2.5.5** — **breaking**: `DataViews.Filters.Summary` is removed with no shim; delete any
|
|
30
30
|
`<DataViews.Filters.Summary />` (render your own from `useDataViewsFilters()` if you want one).
|
|
31
|
+
`DrawerContent.notchSide` / `DrawerNotch.side` become logical `"start" | "end"` (was
|
|
32
|
+
`"left" | "right"`), and `TreeFolder`'s drag wiring is internal — `dragHandlers`,
|
|
33
|
+
`TreeFolderRowDragHandlers`, `UseTreeFolderDnDResult`, `scrollContainerRef` and
|
|
34
|
+
`getRowDragHandlers` are gone. New, non-breaking: `BadgeField` `creatable` / `createLabel`,
|
|
35
|
+
`FormRenderer` `embedded` / controlled tabs and steps / `drawer` layout options /
|
|
36
|
+
`FormRenderer.NotchAction`, `FormBuilder.Submit` `disabled`, and `DataViews.Empty`.
|
|
31
37
|
Also, every `FormBuilder.*` field takes a `hints` array, so one field can
|
|
32
38
|
carry several alerts. The validation error renders first, your hints follow. See
|
|
33
39
|
[FormBuilder](../components/form-builder.md#hints). Dropdown panels (`Select`,
|
package/docs/reference/hooks.md
CHANGED
|
@@ -20,6 +20,7 @@ Custom React hooks that provide reusable functionality for common UI patterns. A
|
|
|
20
20
|
- **useClickOutside** - Detect clicks outside a referenced element
|
|
21
21
|
- **useResize** - Handle element resizing with RTL support
|
|
22
22
|
- **useTagSelection** - Manage tag selection state with keyboard navigation
|
|
23
|
+
- **useHtmlDir** - Track the document's text direction from `<html dir>`
|
|
23
24
|
|
|
24
25
|
---
|
|
25
26
|
|
|
@@ -1498,3 +1499,25 @@ All hooks support:
|
|
|
1498
1499
|
- Edge 90+
|
|
1499
1500
|
|
|
1500
1501
|
**IntersectionObserver** (useActiveTreeItem): Requires polyfill for older browsers.
|
|
1502
|
+
|
|
1503
|
+
---
|
|
1504
|
+
|
|
1505
|
+
## useHtmlDir
|
|
1506
|
+
|
|
1507
|
+
Tracks the document's text direction from `<html dir>`, re-reading it when it changes — a language
|
|
1508
|
+
switch, say. Returns `"ltr" | "rtl"`.
|
|
1509
|
+
|
|
1510
|
+
```tsx
|
|
1511
|
+
import { useHtmlDir } from "@/hooks/useHtmlDir";
|
|
1512
|
+
|
|
1513
|
+
const dir = useHtmlDir();
|
|
1514
|
+
```
|
|
1515
|
+
|
|
1516
|
+
Most mirroring should be done in CSS with logical properties, which need no JS at all. Reach for
|
|
1517
|
+
this only where a library wants the direction as a **value**: several Radix primitives default to
|
|
1518
|
+
`"ltr"` when given no `dir` prop and no `DirectionProvider`, and vaul computes an inline transform
|
|
1519
|
+
from its `direction` prop, which a stylesheet cannot override mid-drag.
|
|
1520
|
+
|
|
1521
|
+
| Returns | Notes |
|
|
1522
|
+
| --- | --- |
|
|
1523
|
+
| `"ltr" \| "rtl"` | SSR-safe — returns `"ltr"` when there is no `document`. Watches the attribute with a `MutationObserver`, so a runtime language switch updates every consumer. |
|
|
@@ -702,3 +702,25 @@ These utilities depend on the following external libraries:
|
|
|
702
702
|
- [Tailwind CSS Documentation](https://tailwindcss.com)
|
|
703
703
|
- [date-fns Format Reference](https://date-fns.org/docs/format)
|
|
704
704
|
- [TypeScript Utility Types](https://www.typescriptlang.org/docs/handbook/utility-types.html)
|
|
705
|
+
|
|
706
|
+
---
|
|
707
|
+
|
|
708
|
+
## Scroller Utilities
|
|
709
|
+
|
|
710
|
+
### horizontalScrollerStyles
|
|
711
|
+
|
|
712
|
+
The design's 14px horizontal scroller as a class string: a thin track that thickens and turns blue
|
|
713
|
+
on hover. Shared by `TableScroller` and `SectionBlock`'s body so the two cannot drift.
|
|
714
|
+
|
|
715
|
+
```tsx
|
|
716
|
+
import { horizontalScrollerStyles } from "@/utils/scroller";
|
|
717
|
+
import { cn } from "@/utils/cn";
|
|
718
|
+
|
|
719
|
+
<div className={cn(horizontalScrollerStyles, "rounded-lg")}>
|
|
720
|
+
<table className="w-max">…</table>
|
|
721
|
+
</div>
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
It includes `overflow-x-auto overflow-y-hidden`, so the element it lands on becomes the scrollport —
|
|
725
|
+
you do not add an overflow class yourself.
|
|
726
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "torch-glare",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.6",
|
|
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.",
|