torch-glare 2.5.4 → 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.
Files changed (60) hide show
  1. package/apps/lib/components/BadgeField.tsx +138 -69
  2. package/apps/lib/components/Button.tsx +10 -2
  3. package/apps/lib/components/Card.tsx +2 -1
  4. package/apps/lib/components/ContextMenu.tsx +65 -22
  5. package/apps/lib/components/DataViews/context.ts +2 -2
  6. package/apps/lib/components/DataViews/data-views.tsx +20 -8
  7. package/apps/lib/components/DataViews/filters/filters.tsx +0 -2
  8. package/apps/lib/components/DataViews/index.ts +8 -4
  9. package/apps/lib/components/DataViews/slots.ts +9 -0
  10. package/apps/lib/components/DataViews/states.tsx +43 -8
  11. package/apps/lib/components/DataViews/views/table-view.tsx +184 -176
  12. package/apps/lib/components/Drawer.tsx +70 -39
  13. package/apps/lib/components/DropdownMenu.tsx +79 -22
  14. package/apps/lib/components/FormBuilder/context.ts +12 -0
  15. package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +38 -19
  16. package/apps/lib/components/FormBuilder/fields/SelectField.tsx +31 -8
  17. package/apps/lib/components/FormBuilder/submit.tsx +21 -1
  18. package/apps/lib/components/FormBuilder/types.ts +21 -0
  19. package/apps/lib/components/FormRenderer/FormDrawer.tsx +139 -17
  20. package/apps/lib/components/FormRenderer/detail.tsx +57 -8
  21. package/apps/lib/components/FormRenderer/form-renderer.tsx +82 -10
  22. package/apps/lib/components/FormRenderer/index.ts +2 -0
  23. package/apps/lib/components/FormRenderer/notch-action.tsx +64 -0
  24. package/apps/lib/components/FormRenderer/stepper.tsx +56 -2
  25. package/apps/lib/components/FormRenderer/types.ts +37 -0
  26. package/apps/lib/components/HeaderBar.tsx +51 -53
  27. package/apps/lib/components/InputField.tsx +46 -47
  28. package/apps/lib/components/Popover.tsx +23 -9
  29. package/apps/lib/components/SearchableSelect.tsx +10 -6
  30. package/apps/lib/components/SearchableTree.tsx +23 -6
  31. package/apps/lib/components/SearchableTreeDialog.tsx +11 -1
  32. package/apps/lib/components/SectionBlock.tsx +24 -3
  33. package/apps/lib/components/Select.tsx +64 -56
  34. package/apps/lib/components/SlideDatePicker.tsx +5 -5
  35. package/apps/lib/components/TabSwitch.tsx +18 -12
  36. package/apps/lib/components/Table.tsx +15 -28
  37. package/apps/lib/hooks/useActiveTreeItem.ts +4 -1
  38. package/apps/lib/hooks/useHtmlDir.ts +31 -0
  39. package/apps/lib/hooks/useTagSelection.ts +95 -9
  40. package/apps/lib/layouts/FieldSection.tsx +28 -2
  41. package/apps/lib/registry.json +20 -5
  42. package/apps/lib/utils/scroller.ts +26 -0
  43. package/docs/components/badge-field.md +30 -4
  44. package/docs/components/context-menu.md +3 -1
  45. package/docs/components/data-views/examples/filters.md +0 -1
  46. package/docs/components/data-views/index.md +32 -22
  47. package/docs/components/data-views/migration.md +7 -5
  48. package/docs/components/drawer.md +5 -5
  49. package/docs/components/dropdown-menu.md +3 -0
  50. package/docs/components/form-builder.md +36 -2
  51. package/docs/components/form-renderer.md +71 -1
  52. package/docs/components/header-bar.md +3 -2
  53. package/docs/components/input-field.md +3 -3
  54. package/docs/components/section-block.md +6 -0
  55. package/docs/components/select.md +1 -1
  56. package/docs/migration/changelog.md +19 -0
  57. package/docs/reference/hooks.md +23 -0
  58. package/docs/reference/utilities.md +22 -0
  59. package/package.json +1 -1
  60. package/apps/lib/components/DataViews/filters/summary.tsx +0 -65
@@ -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 | top-left (`notchSide="left"`) | top-left | mirror to `notchSide="right"` |
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="right"` so the tab mirrors correctly.
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="right"
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` | `"left" \| "right"` | `"left"` | Which side the notch attaches to (and which corner stays square). Use `"right"` for left-anchored drawers. |
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`: `"left" \| "right"`. |
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. |
@@ -294,6 +294,8 @@ function Example() {
294
294
 
295
295
  Tall menus scroll instead of overflowing off-screen. The surface caps at `maxHeight` (default `320`px) and never exceeds the space available after collision handling. Pass `maxHeight` to change the cap.
296
296
 
297
+ The panel itself does not scroll — it clips, and an inner viewport inside it does the scrolling. That keeps the panel's 4px frosted gutter fixed instead of scrolling away with the rows. Submenus behave identically and take their own `maxHeight`.
298
+
297
299
  ```typescript
298
300
  import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel } from "@/components/DropdownMenu";
299
301
  import { Button } from "@/components/Button";
@@ -396,6 +398,7 @@ function LongMenu() {
396
398
  |------|------|---------|-------------|
397
399
  | `variant` | `'PresentationStyle'` | `'PresentationStyle'` | Visual style variant |
398
400
  | `autoGroup` | `boolean` | `true` | Auto-wrap loose items in boxed groups |
401
+ | `maxHeight` | `number` | `320` | Max height (px) before the submenu scrolls. Same `min(maxHeight, available-height)` cap as `DropdownMenuContent` |
399
402
  | `className` | `string` | - | Additional CSS classes |
400
403
 
401
404
  ### DropdownMenuLabel
@@ -96,7 +96,33 @@ panel beside the form is `FormRenderer`'s `summary`.
96
96
  ## Field components
97
97
 
98
98
  Each is a JSX child taking at least `name`, plus `label`, `placeholder`,
99
- `description`, `required`, `disabled`, `hidden`, `fullWidth`.
99
+ `description`, `required`, `disabled`, `hidden`, `fullWidth`, `hints`.
100
+
101
+ ### Hints
102
+
103
+ `hints` stacks alerts under a field — helper text, a caveat, a confirmation. Each entry mirrors
104
+ `FieldHint`: `label`, an optional `state` of `info` (default) / `warning` / `error` / `success`, and
105
+ an optional `icon`.
106
+
107
+ ```tsx
108
+ <FormBuilder.Text
109
+ name="sku"
110
+ label="SKU"
111
+ required
112
+ hints={[
113
+ { state: "info", label: "Must be unique across the catalogue." },
114
+ { state: "warning", label: "Changing this breaks existing links." },
115
+ ]}
116
+ />
117
+ ```
118
+
119
+ The validation error is not one of these — it is rendered automatically and always comes **first**,
120
+ because it is the actionable message; your hints follow in the order given. A field with no `hints`
121
+ renders exactly as before.
122
+
123
+ Hints are ignored inside a `FormBuilder.Table` cell (and any other `bare` context): there errors
124
+ surface as a tooltip on the control so a row stays one line tall, and stacking alerts would defeat
125
+ that.
100
126
 
101
127
  | Component | Input | Value |
102
128
  | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
@@ -110,6 +136,7 @@ Each is a JSX child taking at least `name`, plus `label`, `placeholder`,
110
136
  | `FormBuilder.Select` (`options`) | `Select` | `string` |
111
137
  | `FormBuilder.SearchableSelect` (`options`, async: `onSearchChange`/`onLoadMore`/`hasMore`) | `SearchableSelect` | `string` |
112
138
  | `FormBuilder.MultiSelect` / `.Tags` (`options`) | `BadgeField` | `string[]` |
139
+ | `FormBuilder.MultiSelect` / `.Tags` (`creatable`) | `BadgeField` with free text | `string[]` — values not in `options` survive |
113
140
  | `FormBuilder.RadioList` (`options`, each with optional `description`) | boxed radio list | `string` |
114
141
  | `FormBuilder.CheckboxGroup` (`options`, each with optional `description`) | boxed checkbox list | `string[]` |
115
142
  | `FormBuilder.RadioCards` (`options` with `description`) | `RadioCard` | `string` |
@@ -188,7 +215,10 @@ country code.
188
215
  `FormBuilder.RadioList` (single-select, `string`) and `FormBuilder.CheckboxGroup` (multi-select,
189
216
  `string[]`) render their `options` as a boxed, divided list — control on the left, primary
190
217
  label, and an optional per-option `description` shown as a secondary label. The whole row is
191
- 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.)
192
222
 
193
223
  `FormBuilder.SwitchBox` (value `boolean`) is a switch wrapped in a `#f9f9f9` field box. It
194
224
  renders like any other field — the `label` sits in the normal label column — and the box holds
@@ -201,6 +231,10 @@ presentation, so it lives there.
201
231
  form** (via context), so it submits even when placed in a header / action bar that renders
202
232
  _outside_ the `<form>` — no manual `form={id}` wiring.
203
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
+
204
238
  ## Moved to FormRenderer
205
239
 
206
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`. It owns no form state.
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
 
@@ -146,13 +146,14 @@ All standard `HTMLAttributes<HTMLDivElement>` (for example `id`, `aria-*`, `data
146
146
  |---------|-----------|-----------|------------------------|
147
147
  | `new` | `bg-blue-sparkle-alpha-50` | `text-blue-sparkle-200` | pill (`label`) → title |
148
148
  | `edit` | `bg-orange-alpha-50` | `text-orange-200` | pill (`label`) → title |
149
- | `detail` | `bg-white-alpha-30` | `text-white-00` | title → pill (`label`) — positions swapped |
149
+ | `detail` | `bg-white-alpha-15` | `text-white-00` | title → pill (`label`) — positions swapped |
150
150
 
151
151
  ## Styling
152
152
 
153
153
  - **Fixed dark container**: `rounded-[14px]`, `border-black-600`, `bg-black-1000`, `p-1.5`, with a double soft shadow. The surface is always dark regardless of theme.
154
154
  - **Layout**: the root is `inline-flex`, so the chip hugs its content rather than stretching to fill its parent.
155
- - **Typography**: 28px, weight 510, uppercase, SF Pro with the `cv05` stylistic set. Both `label` and `title` render uppercase.
155
+ - **Typography**: `typography-display-medium-medium` (28px, weight 510) with the `cv05` stylistic set. Both `label` and `title` render uppercase.
156
+ - **Ordering**: `detail` swaps the two children in the DOM rather than reversing the row, so the pair stays correct under `dir="rtl"` and reads in order to assistive tech.
156
157
  - **Emphasis pill**: the colored badge background and text color are driven entirely by `variant` (see the Variants table). For `detail`, the pill also moves to the right side.
157
158
 
158
159
  ## TypeScript Types
@@ -15,7 +15,7 @@ dependencies:
15
15
 
16
16
  # InputField
17
17
 
18
- > An enhanced input component that combines Input with integrated popover dropdowns, error tooltips, icons, and trailing actions. Perfect for complex form fields requiring additional UI elements.
18
+ > An enhanced input component that combines Input with integrated popover dropdowns, error styling, icons, and trailing actions. Perfect for complex form fields requiring additional UI elements.
19
19
 
20
20
  ## Installation
21
21
 
@@ -206,9 +206,9 @@ function PasswordField() {
206
206
  | `icon` | `ReactNode` | - | Icon to display on the left side |
207
207
  | `childrenSide` | `ReactNode` | - | Content to display on the right side |
208
208
  | `popoverChildren` | `ReactNode` | - | Content for the dropdown popover |
209
- | `errorMessage` | `string` | - | Error message to display in tooltip |
209
+ | `errorMessage` | `string` | - | Marks the field invalid — any non-undefined value turns on the negative border |
210
210
  | `onTable` | `boolean` | `false` | Optimized styling for table context |
211
- | `toolTipSide` | `'top' \| 'right' \| 'bottom' \| 'left'` | - | Tooltip position |
211
+ | `toolTipSide` | `'top' \| 'right' \| 'bottom' \| 'left'` | - | **Deprecated, ignored.** The error tooltip was removed; an invalid field is shown by its border alone |
212
212
  | `theme` | `'dark' \| 'light' \| 'default'` | - | Theme variant |
213
213
  | `className` | `string` | - | Additional CSS classes |
214
214
  | `disabled` | `boolean` | `false` | Disables the input |
@@ -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";
@@ -241,7 +241,7 @@ function Uncontrolled() {
241
241
  |------|------|---------|-------------|
242
242
  | `size` | `'S' \| 'M' \| 'L' \| 'XL'` | `'M'` | Size of the trigger |
243
243
  | `variant` | `'PresentationStyle' \| 'SystemStyle'` | `'PresentationStyle'` | Visual variant |
244
- | `errors` | `string` | - | Error message (shows tooltip) |
244
+ | `errors` | `string` | - | Marks the trigger invalid — any non-undefined value turns on the negative border |
245
245
  | `theme` | `Themes` | - | Theme override |
246
246
  | `icon` | `string` | - | Custom icon class |
247
247
  | `className` | `string` | - | Additional CSS classes |
@@ -26,6 +26,25 @@ npx torch-glare@latest add Button
26
26
 
27
27
  ## Release notes
28
28
 
29
+ - **v2.5.5** — **breaking**: `DataViews.Filters.Summary` is removed with no shim; delete any
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`.
37
+ Also, every `FormBuilder.*` field takes a `hints` array, so one field can
38
+ carry several alerts. The validation error renders first, your hints follow. See
39
+ [FormBuilder](../components/form-builder.md#hints). Dropdown panels (`Select`,
40
+ `SearchableSelect`, `SearchableTree`, `Popover`, `DropdownMenu`, `ContextMenu`) now cap to the
41
+ space actually on screen and scroll their list in an inner viewport instead of being clipped —
42
+ note `SearchableTree`'s `maxBodyHeight` default drops 320 → 200, and menu submenus gained a
43
+ `maxHeight` (they were previously uncapped). `InputField`, `BadgeField` and `Select` no longer
44
+ pop an error tooltip — an invalid control is shown by its negative border alone, and
45
+ `toolTipSide` is deprecated and ignored. Plus fixes to `TabSwitch` dividers,
46
+ `FieldSection` hint placement, the
47
+ `FormRenderer` stepper grid, `SlideDatePicker` theming and `PopoverItem`'s disabled state.
29
48
  - **v2.5.2** — **breaking**: `FormBuilder` now holds only the fields; the section cards, title
30
49
  header and stepper moved to `FormRenderer`. See
31
50
  [FormBuilder 2.5.2](./form-builder-2.5.2.md) for the rename table.
@@ -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.4",
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.",
@@ -1,65 +0,0 @@
1
- "use client";
2
-
3
- import { cn } from "../../../utils/cn";
4
- import { formatPathLabel } from "../../../utils/dataViews/path";
5
- import type { FilterValue } from "../../../utils/dataViews/types";
6
- import { useDataViewsFilters, useDataViewsView } from "../context";
7
-
8
- /**
9
- * `DataViews.Filters.Summary` — the active filters as removable chips. Useful when the controls
10
- * themselves live in the panel and you still want the current query visible above the rows.
11
- *
12
- * Its labels come from the same descriptors `Filters` reads off its children — the **root** collects
13
- * those as well, so this works wherever it is rendered, including as `Filters`' sibling. A path no
14
- * control covers falls back to a label derived from the path.
15
- */
16
- export function Summary({ className }: { className?: string }) {
17
- const { filters, setFilters, filterFields } = useDataViewsFilters();
18
- const { search, setSearch } = useDataViewsView();
19
- const entries = Object.entries(filters);
20
-
21
- if (entries.length === 0 && !search) return null;
22
-
23
- const chip = cn(
24
- "typography-body-small-regular flex items-center gap-1 rounded-[6px] px-2 py-[2px]",
25
- "bg-background-presentation-action-selected text-content-presentation-global-primary",
26
- );
27
-
28
- const describe = (path: string, value: FilterValue) => {
29
- const label = filterFields.find((f) => f.path === path)?.label ?? formatPathLabel(path);
30
- if (Array.isArray(value)) return `${label}: ${value.join(", ")}`;
31
- if (value.kind === "number") return `${label}: ${value.min ?? "…"}–${value.max ?? "…"}`;
32
- return `${label}: ${value.from ?? "…"} → ${value.to ?? "…"}`;
33
- };
34
-
35
- return (
36
- <div className={cn("flex flex-wrap items-center gap-2", className)}>
37
- {/* The search term narrows the result set exactly as a filter does, so it belongs in the
38
- summary of what is currently narrowing it — and it has to be clearable from here too. */}
39
- {search && (
40
- <span className={chip}>
41
- {`Search: ${search}`}
42
- <button type="button" aria-label="Clear search" onClick={() => setSearch("")}>
43
- <i className="ri-close-line text-[12px]" />
44
- </button>
45
- </span>
46
- )}
47
- {entries.map(([path, value]) => (
48
- <span key={path} className={chip}>
49
- {describe(path, value)}
50
- <button
51
- type="button"
52
- aria-label={`Remove ${path} filter`}
53
- onClick={() => {
54
- const copy = { ...filters };
55
- delete copy[path];
56
- setFilters(copy);
57
- }}
58
- >
59
- <i className="ri-close-line text-[12px]" />
60
- </button>
61
- </span>
62
- ))}
63
- </div>
64
- );
65
- }