torch-glare 2.5.4 → 2.5.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/BadgeField.tsx +70 -66
- package/apps/lib/components/Card.tsx +2 -1
- package/apps/lib/components/ContextMenu.tsx +65 -22
- package/apps/lib/components/DataViews/context.ts +2 -2
- package/apps/lib/components/DataViews/data-views.tsx +3 -3
- package/apps/lib/components/DataViews/filters/filters.tsx +0 -2
- package/apps/lib/components/DataViews/views/table-view.tsx +184 -176
- package/apps/lib/components/DropdownMenu.tsx +65 -22
- package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +19 -5
- package/apps/lib/components/FormBuilder/types.ts +16 -0
- package/apps/lib/components/FormRenderer/form-renderer.tsx +16 -5
- package/apps/lib/components/HeaderBar.tsx +51 -53
- package/apps/lib/components/InputField.tsx +46 -47
- package/apps/lib/components/Popover.tsx +23 -9
- package/apps/lib/components/SearchableSelect.tsx +10 -6
- package/apps/lib/components/SearchableTree.tsx +23 -6
- package/apps/lib/components/SearchableTreeDialog.tsx +11 -1
- package/apps/lib/components/Select.tsx +56 -48
- package/apps/lib/components/SlideDatePicker.tsx +5 -7
- package/apps/lib/components/TabSwitch.tsx +18 -12
- package/apps/lib/layouts/FieldSection.tsx +28 -2
- package/apps/lib/registry.json +1 -2
- package/docs/components/badge-field.md +4 -4
- package/docs/components/context-menu.md +3 -1
- package/docs/components/data-views/examples/filters.md +0 -1
- package/docs/components/data-views/index.md +1 -17
- package/docs/components/dropdown-menu.md +3 -0
- package/docs/components/form-builder.md +27 -1
- package/docs/components/header-bar.md +3 -2
- package/docs/components/input-field.md +3 -3
- package/docs/components/select.md +1 -1
- package/docs/migration/changelog.md +13 -0
- package/package.json +1 -1
- package/apps/lib/components/DataViews/filters/summary.tsx +0 -65
|
@@ -149,11 +149,11 @@ export const SlideDatePicker = forwardRef<HTMLInputElement, SlideDatePickerProps
|
|
|
149
149
|
|
|
150
150
|
return (
|
|
151
151
|
<Popover onOpenChange={setIsOpen}>
|
|
152
|
-
{/*
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
152
|
+
{/* `theme` is deliberately NOT passed to the field: only the picker panel below is pinned
|
|
153
|
+
dark. The field follows the surrounding page instead, so it matches the other inputs
|
|
154
|
+
next to it. Note it must not be handed down as `data-theme` either — `asChild` merges
|
|
155
|
+
trigger props onto the child, which would land the attribute on the bare <input> and
|
|
156
|
+
leave its text resolving dark tokens inside a light field (white on white). */}
|
|
157
157
|
<PopoverTrigger ref={triggerRef} asChild className="w-full flex-1">
|
|
158
158
|
{isValidElement(children) ? (
|
|
159
159
|
cloneElement(children as React.ReactElement<HTMLInputElement & { theme?: string }>, {
|
|
@@ -161,13 +161,11 @@ export const SlideDatePicker = forwardRef<HTMLInputElement, SlideDatePickerProps
|
|
|
161
161
|
(children as React.ReactElement<HTMLInputElement>).props.value ?? formattedValue,
|
|
162
162
|
type: "input",
|
|
163
163
|
readOnly: true,
|
|
164
|
-
theme,
|
|
165
164
|
})
|
|
166
165
|
) : (
|
|
167
166
|
/* If the children is not a valid element, Show the default input */
|
|
168
167
|
<InputField
|
|
169
168
|
readOnly
|
|
170
|
-
theme={theme}
|
|
171
169
|
type="input"
|
|
172
170
|
{...props}
|
|
173
171
|
childrenSide={
|
|
@@ -31,13 +31,16 @@ const trackStyles = cva(
|
|
|
31
31
|
"rounded-[10px]",
|
|
32
32
|
"bg-background-presentation-body-primary",
|
|
33
33
|
"shadow-[inset_0_0_4px_0_rgba(0,0,0,0.08)]",
|
|
34
|
+
// Contains the active pill's drop-shadow: it is clipped at the track's rounded edge instead
|
|
35
|
+
// of bleeding onto whatever sits behind the switcher.
|
|
36
|
+
"overflow-hidden",
|
|
34
37
|
],
|
|
35
38
|
{
|
|
36
39
|
variants: {
|
|
37
40
|
size: {
|
|
38
|
-
S: ["
|
|
39
|
-
M: ["
|
|
40
|
-
L: ["
|
|
41
|
+
S: ["p-[2px]"],
|
|
42
|
+
M: ["p-[2px]"],
|
|
43
|
+
L: ["p-[3px] rounded-[12px]"],
|
|
41
44
|
},
|
|
42
45
|
},
|
|
43
46
|
defaultVariants: { size: "M" },
|
|
@@ -48,7 +51,7 @@ const trackStyles = cva(
|
|
|
48
51
|
// transparent and lighten on hover.
|
|
49
52
|
const optionStyles = cva(
|
|
50
53
|
[
|
|
51
|
-
"flex items-center justify-center
|
|
54
|
+
"flex items-center justify-center",
|
|
52
55
|
"rounded-[8px]",
|
|
53
56
|
"font-[510] leading-none",
|
|
54
57
|
"transition-all duration-200 ease-in-out",
|
|
@@ -61,7 +64,7 @@ const optionStyles = cva(
|
|
|
61
64
|
size: {
|
|
62
65
|
S: ["h-5 px-2 text-[12px]", "[&_svg]:h-3 [&_svg]:w-3", "[&_i]:text-[12px]"],
|
|
63
66
|
M: ["h-6 px-3 text-[14px]", "[&_svg]:h-[14px] [&_svg]:w-[14px]", "[&_i]:text-[14px]"],
|
|
64
|
-
L: ["h-8 px-4 text-[16px]", "[&_svg]:h-4 [&_svg]:w-4", "[&_i]:text-[16px]"],
|
|
67
|
+
L: ["h-8 px-4 text-[16px] rounded-[10px]", "[&_svg]:h-4 [&_svg]:w-4", "[&_i]:text-[16px]"],
|
|
65
68
|
},
|
|
66
69
|
active: {
|
|
67
70
|
// Active option = a solid raised WHITE pill with dark text, in every
|
|
@@ -78,6 +81,8 @@ const optionStyles = cva(
|
|
|
78
81
|
// keeps both states the same width — without it, selecting a tab resizes the pill and
|
|
79
82
|
// shoves its neighbours. The design's own 4px gap absorbs the 1px.
|
|
80
83
|
"border border-black/5",
|
|
84
|
+
// The pill casts outward to look raised; the track clips it (`overflow-hidden` on
|
|
85
|
+
// `trackStyles`) so the shadow never spills past the container.
|
|
81
86
|
"drop-shadow-[0px_0px_5px_rgba(0,0,0,0.25)]",
|
|
82
87
|
],
|
|
83
88
|
false: [
|
|
@@ -116,22 +121,23 @@ function TabSwitchInner<T extends string = string>(
|
|
|
116
121
|
>
|
|
117
122
|
{options.map((option, idx) => {
|
|
118
123
|
const active = option.value === value;
|
|
124
|
+
const isDisabled = disabled || option.disabled;
|
|
125
|
+
// A divider is drawn only between two INACTIVE options — the raised pill is never flanked
|
|
126
|
+
// by a rule, so the two dividers touching it fade out.
|
|
119
127
|
const prevActive = idx > 0 && options[idx - 1].value === value;
|
|
120
|
-
// A divider sits between two inactive options only — the active pill is
|
|
121
|
-
// never flanked by one.
|
|
122
128
|
const showDivider = idx > 0 && !active && !prevActive;
|
|
123
|
-
const isDisabled = disabled || option.disabled;
|
|
124
129
|
|
|
125
130
|
return (
|
|
126
131
|
<div key={option.value} className="flex items-center">
|
|
127
132
|
{idx > 0 && (
|
|
128
133
|
<div
|
|
129
134
|
aria-hidden
|
|
135
|
+
// The slot is ALWAYS rendered and only its ink changes. Unmounting it instead cost
|
|
136
|
+
// the track 7px (`w-px` + `mx-[3px]`) every time one went away, so picking a tab
|
|
137
|
+
// resized the track and shoved its neighbours — with three options, selecting the
|
|
138
|
+
// middle one dropped both dividers and moved the track 14px.
|
|
130
139
|
className={cn(
|
|
131
|
-
|
|
132
|
-
// the track 7px every time the rule below hid one, so picking a tab resized the
|
|
133
|
-
// track and shoved its neighbours sideways.
|
|
134
|
-
"mx-[3px] h-3 w-px",
|
|
140
|
+
"h-3 w-px",
|
|
135
141
|
showDivider ? "bg-border-presentation-action-disabled" : "bg-transparent",
|
|
136
142
|
)}
|
|
137
143
|
/>
|
|
@@ -27,6 +27,14 @@ export function FieldSection({
|
|
|
27
27
|
childrenUnderLabel,
|
|
28
28
|
...props
|
|
29
29
|
}: Props) {
|
|
30
|
+
// Which copy of `childrenUnderLabel` is visible, driven purely by the `@md` container
|
|
31
|
+
// breakpoint (this section's own width reaching 650px) — deliberately not by `direction`.
|
|
32
|
+
// Pinning it per-direction breaks the common case: `FormRenderer` forces `direction="vertical"`
|
|
33
|
+
// inside a drawer and FormBuilder puts the field's validation error in this slot, so a vertical
|
|
34
|
+
// form would strand every error under the label at any width.
|
|
35
|
+
const underLabelSlot = direction === "vertical" ? "hidden" : "hidden @md:block";
|
|
36
|
+
const underChildrenSlot = direction === "vertical" ? "block" : "block @md:hidden";
|
|
37
|
+
|
|
30
38
|
return (
|
|
31
39
|
<section
|
|
32
40
|
{...props}
|
|
@@ -54,11 +62,29 @@ export function FieldSection({
|
|
|
54
62
|
)}
|
|
55
63
|
|
|
56
64
|
{secondaryLabel && <Label size={size} secondaryLabel={secondaryLabel} />}
|
|
57
|
-
|
|
65
|
+
|
|
66
|
+
{/* Stacked (below `@md`): the label column IS the full width, so under the label is the
|
|
67
|
+
natural spot. Hidden once `@md` splits the row into two columns — the copy in the
|
|
68
|
+
children column takes over there. Exactly one of the two is ever displayed. */}
|
|
69
|
+
{childrenUnderLabel && (
|
|
70
|
+
<div data-slot="under-label" className={cn(underLabelSlot)}>
|
|
71
|
+
{childrenUnderLabel}
|
|
72
|
+
</div>
|
|
73
|
+
)}
|
|
58
74
|
</div>
|
|
59
75
|
|
|
60
76
|
{/* Flexible section that takes up the remaining space */}
|
|
61
|
-
<div className="grid grid-cols-1 place-items-end gap-[12px]">
|
|
77
|
+
<div className="grid grid-cols-1 place-items-end gap-[12px]">
|
|
78
|
+
{children}
|
|
79
|
+
|
|
80
|
+
{/* Two-column (`@md` and up): the hint belongs under the control it describes, not
|
|
81
|
+
stranded at the bottom of the 350px label column. */}
|
|
82
|
+
{childrenUnderLabel && (
|
|
83
|
+
<div data-slot="under-children" className={cn("w-full", underChildrenSlot)}>
|
|
84
|
+
{childrenUnderLabel}
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
</div>
|
|
62
88
|
</div>
|
|
63
89
|
</section>
|
|
64
90
|
);
|
package/apps/lib/registry.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.5.
|
|
2
|
+
"version": "2.5.5",
|
|
3
3
|
"generatedBy": "scripts/bin/generateRegistry",
|
|
4
4
|
"npmVersions": {
|
|
5
5
|
"@dnd-kit/core": "^6.3.1",
|
|
@@ -786,7 +786,6 @@
|
|
|
786
786
|
"registryDependencies": [
|
|
787
787
|
"components/ActionButton",
|
|
788
788
|
"components/DropdownMenu",
|
|
789
|
-
"components/Tooltip",
|
|
790
789
|
"utils/cn",
|
|
791
790
|
"utils/types"
|
|
792
791
|
]
|
|
@@ -335,7 +335,7 @@ export function BadgeFieldWithIcon() {
|
|
|
335
335
|
|
|
336
336
|
### With Error State
|
|
337
337
|
|
|
338
|
-
Display validation errors
|
|
338
|
+
Display validation errors. `errorMessage` turns on the field's negative border.
|
|
339
339
|
|
|
340
340
|
```tsx
|
|
341
341
|
export function BadgeFieldWithError() {
|
|
@@ -467,9 +467,9 @@ Extends all Input element props (except size and variant).
|
|
|
467
467
|
| size | `'XS' \| 'S' \| 'M'` | `'M'` | Field size |
|
|
468
468
|
| variant | `'PresentationStyle'` | `'PresentationStyle'` | Visual variant |
|
|
469
469
|
| icon | `ReactNode` | - | Leading icon |
|
|
470
|
-
| errorMessage | `string` | - |
|
|
470
|
+
| errorMessage | `string` | - | Marks the field invalid — any non-undefined value turns on the negative border |
|
|
471
471
|
| onTable | `boolean` | `false` | Table-specific styling |
|
|
472
|
-
| toolTipSide | `ToolTipSide` | - |
|
|
472
|
+
| toolTipSide | `ToolTipSide` | - | **Deprecated, ignored.** The error tooltip was removed; an invalid field is shown by its border alone |
|
|
473
473
|
| label | `string` | - | Field label |
|
|
474
474
|
| required | `boolean` | `false` | Required indicator |
|
|
475
475
|
| theme | `Themes` | - | Theme override |
|
|
@@ -674,7 +674,7 @@ describe('BadgeField', () => {
|
|
|
674
674
|
- **ARIA Labels**: Proper labels for screen readers
|
|
675
675
|
- **Focus Management**: Clear focus indicators
|
|
676
676
|
- **Screen Reader**: Announces selected/removed tags
|
|
677
|
-
- **Error Messages**:
|
|
677
|
+
- **Error Messages**: Invalid fields are marked with the negative border token
|
|
678
678
|
- **Tab Order**: Logical tab navigation
|
|
679
679
|
|
|
680
680
|
## Performance
|
|
@@ -230,6 +230,8 @@ function RtlMenu() {
|
|
|
230
230
|
|
|
231
231
|
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.
|
|
232
232
|
|
|
233
|
+
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` (same `320`px default).
|
|
234
|
+
|
|
233
235
|
```typescript
|
|
234
236
|
import { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, ContextMenuLabel } from "@/components/ContextMenu";
|
|
235
237
|
|
|
@@ -425,7 +427,7 @@ export const ContextMenuRadioItem: React.ForwardRefExoticComponent<ContextMenuRa
|
|
|
425
427
|
- **Opens at the pointer**: the menu opens on right-click (`contextmenu`) at the exact cursor position, not anchored to a fixed trigger button.
|
|
426
428
|
- **Second right-click closes it**: the Root is made controlled and tracks `open` in context. The Trigger listens in the capture phase, and when the menu is already open it `preventDefault()` / `stopPropagation()` and closes — so a second right-click dismisses instead of re-anchoring (which Radix handles unreliably).
|
|
427
429
|
- **Auto-grouping**: by default (`autoGroup` on `ContextMenuContent`, default `true`) consecutive loose items (`ContextMenuItem`, `ContextMenuCheckboxItem`, `ContextMenuRadioItem`, and `ContextMenuSub`) are automatically wrapped in a `Boxed` `ContextMenuGroup`, so they render inside a boxed container like DropdownMenu even when you do not write a group. Labels and explicit groups act as boundaries and pass through unchanged. Set `autoGroup={false}` to render children verbatim.
|
|
428
|
-
- **Max height & scrolling**: the surface caps its height at `min(maxHeight, available-height)` (where `maxHeight` defaults to `320`px and `available-height` is the space Radix has after collision handling). A taller menu scrolls vertically instead of overflowing off-screen — items and groups keep their full height rather than squishing. Pass `maxHeight={N}` to change the cap.
|
|
430
|
+
- **Max height & scrolling**: the surface caps its height at `min(maxHeight, available-height)` (where `maxHeight` defaults to `320`px and `available-height` is the space Radix has after collision handling). A taller menu scrolls vertically instead of overflowing off-screen — items and groups keep their full height rather than squishing. The panel clips and an inner viewport scrolls, so the panel's frosted gutter stays put. Pass `maxHeight={N}` to change the cap; `ContextMenuSubContent` accepts it too.
|
|
429
431
|
- **Checkbox / radio keep the menu open**: `ContextMenuCheckboxItem` and `ContextMenuRadioItem` call `event.preventDefault()` inside `onSelect`, stopping Radix's default auto-close so users can toggle multiple options in one pass.
|
|
430
432
|
- **Open-only animation**: only the open (enter) state animates (`fade-in`). There is intentionally no exit animation — holding the old DOM node during close breaks close/reposition on a second right-click, so it is omitted to keep repositioning reliable.
|
|
431
433
|
- **Submenus and RTL**: nested `ContextMenuSub` / `ContextMenuSubTrigger` / `ContextMenuSubContent` are supported, and `dir="rtl"` on the Root mirrors the layout (including the submenu chevron).
|
|
@@ -231,7 +231,6 @@ export default function FiltersExample() {
|
|
|
231
231
|
|
|
232
232
|
{/* The active query, as removable chips — including the search term. */}
|
|
233
233
|
<div className="border-border-presentation-global-primary border-b px-4 py-2 empty:hidden">
|
|
234
|
-
<DataViews.Filters.Summary />
|
|
235
234
|
</div>
|
|
236
235
|
|
|
237
236
|
<DataViews.Table />
|
|
@@ -406,21 +406,13 @@ control at its neutral position emits no key at all, is under
|
|
|
406
406
|
</DataViews.Filters>
|
|
407
407
|
```
|
|
408
408
|
|
|
409
|
-
`Filters.Summary` paints whatever is active as removable chips. It reads the same context, so it
|
|
410
|
-
works anywhere — most usefully **outside** the rail, where it tells the user what is filtering the
|
|
411
|
-
rows they are looking at:
|
|
412
|
-
|
|
413
|
-
```tsx
|
|
414
|
-
<DataViews.Filters.Summary className="px-4 py-2" />
|
|
415
|
-
```
|
|
416
|
-
|
|
417
409
|
### Questions this design gets asked
|
|
418
410
|
|
|
419
411
|
| Question | Answer |
|
|
420
412
|
| --- | --- |
|
|
421
413
|
| Is there an in-view filter panel *and* a Filters tab — which is canonical? | **One surface.** `DataViews.Filters` is a single component. Render it inside a `Panel.Tab` or as a standalone bar; author against the component, not against a tab. |
|
|
422
414
|
| What orders the sections? | **The order you write the children.** There is no `order` prop for filters. |
|
|
423
|
-
|
|
|
415
|
+
| Is there an applied-count badge or a chip summary of active filters? | **No.** `PanelToggle` carries no count, and there is no summary component — the controls themselves show what is set. Render your own above the rows if you want one. |
|
|
424
416
|
| Do `BadgeField` chip colours and `FieldConfig.variants` share a token set? | **They never meet.** Chips come from the field's own `options`; `variants` (`BadgeVariant`) styles `enum-badge` **columns**. Filters and columns are independent. |
|
|
425
417
|
|
|
426
418
|
And the behaviours worth stating because they are easy to assume wrongly:
|
|
@@ -771,14 +763,6 @@ A filter no FormBuilder field covers.
|
|
|
771
763
|
| `render` | `(args: { value: FilterValue \| undefined; setValue: (v: FilterValue \| undefined) => void }) => ReactNode` | — | **yes** | |
|
|
772
764
|
| `label` | `ReactNode` | derived from `path` | no | |
|
|
773
765
|
|
|
774
|
-
### DataViews.Filters.Summary
|
|
775
|
-
|
|
776
|
-
| Prop | Type | Default | Required | Notes |
|
|
777
|
-
| --- | --- | --- | --- | --- |
|
|
778
|
-
| `className` | `string` | — | no | |
|
|
779
|
-
|
|
780
|
-
Active filters — and the search term — as removable chips. Renders `null` when there are none.
|
|
781
|
-
|
|
782
766
|
### Cell
|
|
783
767
|
|
|
784
768
|
Paint one field of one row exactly as the views paint it.
|
|
@@ -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
|
| --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
|
|
@@ -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-
|
|
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
|
|
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
|
|
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` | - |
|
|
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'` | - |
|
|
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 |
|
|
@@ -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` | - |
|
|
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,19 @@ 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
|
+
Also, every `FormBuilder.*` field takes a `hints` array, so one field can
|
|
32
|
+
carry several alerts. The validation error renders first, your hints follow. See
|
|
33
|
+
[FormBuilder](../components/form-builder.md#hints). Dropdown panels (`Select`,
|
|
34
|
+
`SearchableSelect`, `SearchableTree`, `Popover`, `DropdownMenu`, `ContextMenu`) now cap to the
|
|
35
|
+
space actually on screen and scroll their list in an inner viewport instead of being clipped —
|
|
36
|
+
note `SearchableTree`'s `maxBodyHeight` default drops 320 → 200, and menu submenus gained a
|
|
37
|
+
`maxHeight` (they were previously uncapped). `InputField`, `BadgeField` and `Select` no longer
|
|
38
|
+
pop an error tooltip — an invalid control is shown by its negative border alone, and
|
|
39
|
+
`toolTipSide` is deprecated and ignored. Plus fixes to `TabSwitch` dividers,
|
|
40
|
+
`FieldSection` hint placement, the
|
|
41
|
+
`FormRenderer` stepper grid, `SlideDatePicker` theming and `PopoverItem`'s disabled state.
|
|
29
42
|
- **v2.5.2** — **breaking**: `FormBuilder` now holds only the fields; the section cards, title
|
|
30
43
|
header and stepper moved to `FormRenderer`. See
|
|
31
44
|
[FormBuilder 2.5.2](./form-builder-2.5.2.md) for the rename table.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "torch-glare",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.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.",
|
|
@@ -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
|
-
}
|