torch-glare 2.5.3 → 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/ActionButton.tsx +39 -6
- package/apps/lib/components/Badge.tsx +5 -2
- package/apps/lib/components/BadgeField.tsx +71 -67
- package/apps/lib/components/Button.tsx +4 -4
- package/apps/lib/components/Calendar.tsx +7 -17
- package/apps/lib/components/Card.tsx +2 -1
- package/apps/lib/components/ColorPicker.tsx +1 -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 +190 -153
- package/apps/lib/components/Drawer.tsx +18 -1
- 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/FormRenderer/stepper.tsx +22 -16
- package/apps/lib/components/HeaderBar.tsx +51 -53
- package/apps/lib/components/Input.tsx +19 -4
- package/apps/lib/components/InputField.tsx +46 -47
- package/apps/lib/components/Popover.tsx +108 -56
- package/apps/lib/components/SearchableSelect.tsx +22 -17
- package/apps/lib/components/SearchableTree.tsx +33 -17
- package/apps/lib/components/SearchableTreeDialog.tsx +21 -12
- package/apps/lib/components/SectionBlock.tsx +3 -1
- package/apps/lib/components/Select.tsx +86 -54
- package/apps/lib/components/SimpleSelect.tsx +2 -2
- package/apps/lib/components/SlideDatePicker.tsx +15 -4
- package/apps/lib/components/Stepper.tsx +329 -180
- package/apps/lib/components/Switch.tsx +2 -2
- package/apps/lib/components/TabSwitch.tsx +18 -12
- package/apps/lib/components/Table.tsx +1 -1
- package/apps/lib/components/Textarea.tsx +29 -4
- package/apps/lib/components/TreeDropDown.tsx +18 -6
- package/apps/lib/layouts/FieldSection.tsx +28 -2
- package/apps/lib/registry.json +7 -17
- package/apps/lib/tsconfig.tsbuildinfo +1 -1
- package/docs/components/action-button.md +3 -1
- 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/components/stepper.md +119 -24
- package/docs/migration/changelog.md +13 -0
- package/docs/reference/tailwind-plugins.md +42 -0
- package/docs/tutorials/getting-started.md +32 -11
- package/package.json +1 -1
- package/apps/lib/components/DataViews/filters/summary.tsx +0 -65
- package/apps/lib/components/FormStepper.tsx +0 -272
- package/docs/components/form-stepper.md +0 -250
|
@@ -4,12 +4,24 @@ import { Button } from "./Button";
|
|
|
4
4
|
import { cn } from "../utils/cn";
|
|
5
5
|
import { ButtonVariant, Themes } from "../utils/types";
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* The button that sits at the end of an input field — Figma `ActionButton` (Type=DropDown).
|
|
9
|
+
*
|
|
10
|
+
* Sizes are Figma's exactly: Xs 18, S 22, M 32, with `radius/sm` on the two small ones and
|
|
11
|
+
* `radius/lg` on M.
|
|
12
|
+
*
|
|
13
|
+
* The `!` on the radius is load-bearing. `twMerge` does not dedupe `rounded-radius-*` — they are
|
|
14
|
+
* custom scale keys, not values it recognises — so this radius and the one coming from `Button`'s
|
|
15
|
+
* own `size` variant both survive, and CSS source order picks the winner. Tailwind emits the scale
|
|
16
|
+
* in config order (sm before md before lg), so without `!` size S would render Button M's 6px
|
|
17
|
+
* instead of its own 4px.
|
|
18
|
+
*/
|
|
19
|
+
const buttonVariants = cva("", {
|
|
8
20
|
variants: {
|
|
9
21
|
size: {
|
|
10
|
-
XS: "h-[18px] w-[18px] text-[12px]",
|
|
11
|
-
S: "h-[22px] w-[22px] text-[12px]",
|
|
12
|
-
M: "h-[32px] w-[32px] text-[18px]",
|
|
22
|
+
XS: "h-[18px] w-[18px] text-[12px] !rounded-radius-sm",
|
|
23
|
+
S: "h-[22px] w-[22px] text-[12px] !rounded-radius-sm",
|
|
24
|
+
M: "h-[32px] w-[32px] text-[18px] !rounded-radius-lg",
|
|
13
25
|
},
|
|
14
26
|
},
|
|
15
27
|
defaultVariants: {
|
|
@@ -17,6 +29,23 @@ const buttonVariants = cva("!rounded-[4px]", {
|
|
|
17
29
|
},
|
|
18
30
|
});
|
|
19
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Default/hover/disabled colours as Figma binds them. Applied only when the caller passes no
|
|
34
|
+
* `variant`, so anything asking for a specific Button variant still gets it.
|
|
35
|
+
*
|
|
36
|
+
* `action-secondary` and `action-disabled` already match `Button`'s `PrimeStyle` equivalents; the
|
|
37
|
+
* real difference is hover, where `PrimeStyle` uses `button-hover` and the design uses
|
|
38
|
+
* `action-hover`.
|
|
39
|
+
*/
|
|
40
|
+
const actionTokens = [
|
|
41
|
+
"bg-background-presentation-action-secondary",
|
|
42
|
+
"text-content-presentation-action-light-primary",
|
|
43
|
+
"hover:bg-background-presentation-action-hover",
|
|
44
|
+
"hover:text-content-presentation-global-hover",
|
|
45
|
+
"disabled:bg-background-presentation-action-disabled",
|
|
46
|
+
"disabled:text-content-presentation-state-disabled",
|
|
47
|
+
];
|
|
48
|
+
|
|
20
49
|
interface Props
|
|
21
50
|
extends ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
|
22
51
|
is_loading?: boolean;
|
|
@@ -29,7 +58,6 @@ interface Props
|
|
|
29
58
|
export const ActionButton = function ({
|
|
30
59
|
size,
|
|
31
60
|
asChild,
|
|
32
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- excluded from {...props} spread
|
|
33
61
|
as: Tag = "button",
|
|
34
62
|
className,
|
|
35
63
|
variant,
|
|
@@ -42,14 +70,19 @@ export const ActionButton = function ({
|
|
|
42
70
|
<Button
|
|
43
71
|
theme={theme}
|
|
44
72
|
asChild={asChild}
|
|
73
|
+
// Forwarded, not swallowed: the chevron inside a field renders `as="span"` because it is
|
|
74
|
+
// nested in a clickable trigger, and a <button> cannot contain a <button>.
|
|
75
|
+
as={Tag}
|
|
45
76
|
buttonType="icon"
|
|
46
|
-
type
|
|
77
|
+
// `type` is only meaningful on a real button; on a span it would be an invalid attribute.
|
|
78
|
+
type={Tag === "button" ? type : undefined}
|
|
47
79
|
size={size == "XS" ? "S" : size == "S" ? "M" : size == "M" ? "L" : "S"}
|
|
48
80
|
variant={variant}
|
|
49
81
|
className={cn(
|
|
50
82
|
buttonVariants({
|
|
51
83
|
size,
|
|
52
84
|
}),
|
|
85
|
+
!variant && actionTokens,
|
|
53
86
|
className,
|
|
54
87
|
)}
|
|
55
88
|
{...props}
|
|
@@ -118,7 +118,10 @@ const subtleCompoundVariants = [
|
|
|
118
118
|
export const badgeStyles = cva(
|
|
119
119
|
[
|
|
120
120
|
"inline-flex items-center justify-center w-fit",
|
|
121
|
-
|
|
121
|
+
// `radius/md` (6px), read off the Badge master component in Figma. Note the badge instances
|
|
122
|
+
// nested inside InnerLabelField render at `radius/lg` — that is an instance override, not the
|
|
123
|
+
// component's own value, and reading it as the spec is how this briefly shipped as 8px.
|
|
124
|
+
"rounded-radius-md",
|
|
122
125
|
"transition-all duration-200 ease-in-out",
|
|
123
126
|
"whitespace-nowrap",
|
|
124
127
|
"[&_i]:!leading-none",
|
|
@@ -215,7 +218,7 @@ export const Badge: React.FC<BadgeProps> = ({
|
|
|
215
218
|
}
|
|
216
219
|
}}
|
|
217
220
|
className={cn(
|
|
218
|
-
"rounded-
|
|
221
|
+
"rounded-radius-sm",
|
|
219
222
|
"flex items-center justify-center cursor-pointer",
|
|
220
223
|
"hover:bg-background-presentation-action-secondary",
|
|
221
224
|
"transition-colors duration-150",
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
FocusEvent,
|
|
10
10
|
} from "react";
|
|
11
11
|
import { cn } from "../utils/cn";
|
|
12
|
-
import {
|
|
12
|
+
import { ToolTipSide } from "./Tooltip";
|
|
13
13
|
import { Popover, PopoverContent, PopoverTrigger } from "./Popover";
|
|
14
14
|
import { Themes } from "../utils/types";
|
|
15
15
|
import { Icon, Input, Group, Trilling } from "./Input";
|
|
@@ -22,8 +22,13 @@ interface Props extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "va
|
|
|
22
22
|
size?: "XS" | "S" | "M"; // this is used to change the size style of the component
|
|
23
23
|
variant?: "SystemStyle" | "PresentationStyle";
|
|
24
24
|
icon?: ReactNode; // to add left side icon if you pass it
|
|
25
|
-
|
|
25
|
+
/** Marks the field invalid: any non-undefined value turns on the negative border. */
|
|
26
|
+
errorMessage?: string;
|
|
26
27
|
onTable?: boolean; // to change the border style of the component when it is on table
|
|
28
|
+
/**
|
|
29
|
+
* @deprecated Ignored. The error tooltip was removed — an invalid field is shown by its negative
|
|
30
|
+
* border alone. Kept so existing call sites keep compiling; it will go in a future major.
|
|
31
|
+
*/
|
|
27
32
|
toolTipSide?: ToolTipSide;
|
|
28
33
|
label?: string;
|
|
29
34
|
required?: boolean;
|
|
@@ -45,6 +50,7 @@ export const BadgeField = forwardRef<HTMLInputElement, Props>(
|
|
|
45
50
|
errorMessage,
|
|
46
51
|
onTable,
|
|
47
52
|
variant = "PresentationStyle",
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- deprecated no-op, destructured to keep it out of the {...props} spread
|
|
48
54
|
toolTipSide,
|
|
49
55
|
className,
|
|
50
56
|
actionButton,
|
|
@@ -102,75 +108,73 @@ export const BadgeField = forwardRef<HTMLInputElement, Props>(
|
|
|
102
108
|
|
|
103
109
|
return (
|
|
104
110
|
<Popover open={isPopoverOpen}>
|
|
105
|
-
<
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
111
|
+
<PopoverTrigger asChild>
|
|
112
|
+
<Group
|
|
113
|
+
dir={dir}
|
|
114
|
+
error={errorMessage !== undefined}
|
|
115
|
+
onTable={onTable}
|
|
116
|
+
data-theme={theme}
|
|
117
|
+
variant={variant}
|
|
118
|
+
tabIndex={isPopoverOpen ? 0 : -1}
|
|
119
|
+
onKeyDown={handleKeyDown}
|
|
120
|
+
size={size === "XS" ? "S" : size}
|
|
121
|
+
ref={inputGroupRef}
|
|
122
|
+
onFocus={(e: FocusEvent<HTMLDivElement>) => {
|
|
123
|
+
setDropDownListWidth(e.currentTarget.offsetWidth);
|
|
124
|
+
}}
|
|
125
|
+
className={cn(
|
|
126
|
+
"flex gap-1 flex-row w-full relative p-1 flex-nowrap overflow-hidden justify-end items-center",
|
|
127
|
+
{
|
|
128
|
+
"flex-wrap justify-start": isPopoverOpen,
|
|
129
|
+
"h-fit": isPopoverOpen,
|
|
130
|
+
},
|
|
131
|
+
className,
|
|
132
|
+
)}
|
|
133
|
+
>
|
|
134
|
+
{icon && <Icon>{icon}</Icon>}
|
|
135
|
+
|
|
136
|
+
{selectedTagsStack.map((tag, index) => (
|
|
137
|
+
<Badge
|
|
138
|
+
key={tag.id}
|
|
139
|
+
size={size}
|
|
140
|
+
color={tag.variant as VariantProps<typeof badgeStyles>["color"]}
|
|
141
|
+
label={tag.name}
|
|
142
|
+
isClosable={true}
|
|
143
|
+
onClose={() => handleUnselectTag(tag.id)}
|
|
144
|
+
className={focusedTagIndex === index ? "ring-2 ring-blue-500" : ""}
|
|
145
|
+
tabIndex={focusedTagIndex === index ? 0 : -1}
|
|
146
|
+
/>
|
|
147
|
+
))}
|
|
148
|
+
|
|
149
|
+
<Input
|
|
150
|
+
{...props}
|
|
151
|
+
value={searchTags}
|
|
152
|
+
onChange={(e) => {
|
|
153
|
+
filterTagsBySearch(e.target.value);
|
|
119
154
|
}}
|
|
155
|
+
onFocus={(e) => {
|
|
156
|
+
props.onFocus?.(e);
|
|
157
|
+
setFocusedTagIndex(null);
|
|
158
|
+
setIsPopoverOpen(true);
|
|
159
|
+
}}
|
|
160
|
+
ref={inputRef}
|
|
120
161
|
className={cn(
|
|
121
|
-
"
|
|
162
|
+
"min-w-[100px] w-full", // Added w-full to Input
|
|
122
163
|
{
|
|
123
|
-
"
|
|
124
|
-
"h-
|
|
164
|
+
"!h-[18px]": size === "XS",
|
|
165
|
+
"!h-[22px]": size === "S",
|
|
166
|
+
"!h-[24px]": size === "M",
|
|
125
167
|
},
|
|
126
|
-
className,
|
|
127
|
-
)}
|
|
128
|
-
>
|
|
129
|
-
{icon && <Icon>{icon}</Icon>}
|
|
130
|
-
|
|
131
|
-
{selectedTagsStack.map((tag, index) => (
|
|
132
|
-
<Badge
|
|
133
|
-
key={tag.id}
|
|
134
|
-
size={size}
|
|
135
|
-
color={tag.variant as VariantProps<typeof badgeStyles>["color"]}
|
|
136
|
-
label={tag.name}
|
|
137
|
-
isClosable={true}
|
|
138
|
-
onClose={() => handleUnselectTag(tag.id)}
|
|
139
|
-
className={focusedTagIndex === index ? "ring-2 ring-blue-500" : ""}
|
|
140
|
-
tabIndex={focusedTagIndex === index ? 0 : -1}
|
|
141
|
-
/>
|
|
142
|
-
))}
|
|
143
|
-
|
|
144
|
-
<Input
|
|
145
|
-
{...props}
|
|
146
|
-
value={searchTags}
|
|
147
|
-
onChange={(e) => {
|
|
148
|
-
filterTagsBySearch(e.target.value);
|
|
149
|
-
}}
|
|
150
|
-
onFocus={(e) => {
|
|
151
|
-
props.onFocus?.(e);
|
|
152
|
-
setFocusedTagIndex(null);
|
|
153
|
-
setIsPopoverOpen(true);
|
|
154
|
-
}}
|
|
155
|
-
ref={inputRef}
|
|
156
|
-
className={cn(
|
|
157
|
-
"min-w-[100px] w-full", // Added w-full to Input
|
|
158
|
-
{
|
|
159
|
-
"!h-[18px]": size === "XS",
|
|
160
|
-
"!h-[22px]": size === "S",
|
|
161
|
-
"!h-[24px]": size === "M",
|
|
162
|
-
},
|
|
163
|
-
)}
|
|
164
|
-
/>
|
|
165
|
-
{actionButton && (
|
|
166
|
-
<Trilling className="py-0">
|
|
167
|
-
{/* Keep the ActionButton right aligned */}
|
|
168
|
-
{actionButton}
|
|
169
|
-
</Trilling>
|
|
170
168
|
)}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
169
|
+
/>
|
|
170
|
+
{actionButton && (
|
|
171
|
+
<Trilling className="py-0">
|
|
172
|
+
{/* Keep the ActionButton right aligned */}
|
|
173
|
+
{actionButton}
|
|
174
|
+
</Trilling>
|
|
175
|
+
)}
|
|
176
|
+
</Group>
|
|
177
|
+
</PopoverTrigger>
|
|
174
178
|
|
|
175
179
|
<PopoverContent
|
|
176
180
|
dir={dir}
|
|
@@ -222,7 +226,7 @@ export const BadgeField = forwardRef<HTMLInputElement, Props>(
|
|
|
222
226
|
fill="black"
|
|
223
227
|
/>
|
|
224
228
|
</svg>
|
|
225
|
-
<p className="text-black-1000 text-
|
|
229
|
+
<p className="text-black-1000 text-end text-[12px] font-[510] leading-[148%]">
|
|
226
230
|
{addLabel}
|
|
227
231
|
</p>
|
|
228
232
|
</div>
|
|
@@ -248,10 +248,10 @@ export const buttonVariants = cva(
|
|
|
248
248
|
],
|
|
249
249
|
},
|
|
250
250
|
size: {
|
|
251
|
-
S: "h-[22px] py-[2px] px-[6px] typography-body-small-medium rounded-
|
|
252
|
-
M: "h-[28px] py-[2px] px-[14px] typography-body-large-medium rounded-
|
|
253
|
-
L: "h-[34px] py-[5px] px-[22px] typography-body-large-medium rounded-
|
|
254
|
-
XL: "h-[40px] py-[8px] px-[30px] typography-headers-medium-medium rounded-
|
|
251
|
+
S: "h-[22px] py-[2px] px-[6px] typography-body-small-medium rounded-radius-sm [&_i]:text-[12px]",
|
|
252
|
+
M: "h-[28px] py-[2px] px-[14px] typography-body-large-medium rounded-radius-md [&_i]:text-[18px]",
|
|
253
|
+
L: "h-[34px] py-[5px] px-[22px] typography-body-large-medium rounded-radius-lg [&_i]:text-[20px]",
|
|
254
|
+
XL: "h-[40px] py-[8px] px-[30px] typography-headers-medium-medium rounded-radius-lg [&_i]:text-[22px]",
|
|
255
255
|
},
|
|
256
256
|
is_loading: {
|
|
257
257
|
true: "[&_i]:hidden",
|
|
@@ -4,7 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import { DayPicker } from "react-day-picker";
|
|
5
5
|
import "react-day-picker/style.css";
|
|
6
6
|
import { cn } from "../utils/cn";
|
|
7
|
-
import {
|
|
7
|
+
import { ActionButton } from "./ActionButton";
|
|
8
8
|
import { SimpleSelectValue, SimpleSelectItem } from "./SimpleSelect";
|
|
9
9
|
|
|
10
10
|
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
|
|
@@ -30,24 +30,14 @@ const Calendar = ({
|
|
|
30
30
|
Nav: (e) => {
|
|
31
31
|
return (
|
|
32
32
|
<div className="w-full flex items-center justify-between absolute top-0 left-0 p-[6px]">
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
variant="PrimeStyle"
|
|
37
|
-
size="M"
|
|
38
|
-
className="w-[26px] h-[26px]"
|
|
39
|
-
>
|
|
33
|
+
{/* Popover navigation rather than an in-field button, so it keeps its own 26px box
|
|
34
|
+
and only adopts the shared action colours and radius. */}
|
|
35
|
+
<ActionButton onClick={e.onPreviousClick} size="S" className="w-[26px] h-[26px]">
|
|
40
36
|
<i className="ri-arrow-left-s-line"></i>
|
|
41
|
-
</
|
|
42
|
-
<
|
|
43
|
-
onClick={e.onNextClick}
|
|
44
|
-
buttonType="icon"
|
|
45
|
-
variant="PrimeStyle"
|
|
46
|
-
size="M"
|
|
47
|
-
className="w-[26px] h-[26px]"
|
|
48
|
-
>
|
|
37
|
+
</ActionButton>
|
|
38
|
+
<ActionButton onClick={e.onNextClick} size="S" className="w-[26px] h-[26px]">
|
|
49
39
|
<i className="ri-arrow-right-s-line"></i>
|
|
50
|
-
</
|
|
40
|
+
</ActionButton>
|
|
51
41
|
</div>
|
|
52
42
|
);
|
|
53
43
|
},
|
|
@@ -6,7 +6,8 @@ import { cva, type VariantProps } from "class-variance-authority";
|
|
|
6
6
|
export const cardStyles = cva(
|
|
7
7
|
[
|
|
8
8
|
"flex flex-col justify-start",
|
|
9
|
-
|
|
9
|
+
// `radius/xl` is 12px — the same value this literal had, now taken from the shared scale.
|
|
10
|
+
"gap-2 rounded-radius-xl border",
|
|
10
11
|
"transition-all ease-in-out duration-200",
|
|
11
12
|
"p-[16px]",
|
|
12
13
|
"border-border-presentation-global-primary",
|
|
@@ -404,7 +404,7 @@ export const ColorPicker = forwardRef<HTMLElement, ColorPickerProps>(
|
|
|
404
404
|
}}
|
|
405
405
|
className={NUM_INPUT_CLS}
|
|
406
406
|
/>
|
|
407
|
-
<span className="
|
|
407
|
+
<span className="pe-1 typography-body-small-regular text-content-presentation-action-light-secondary">
|
|
408
408
|
%
|
|
409
409
|
</span>
|
|
410
410
|
</Group>
|
|
@@ -139,16 +139,27 @@ const ContextMenuContent = React.forwardRef<
|
|
|
139
139
|
data-theme={theme}
|
|
140
140
|
ref={ref}
|
|
141
141
|
collisionPadding={collisionPadding}
|
|
142
|
-
// Cap at maxHeight, but never exceed the space Radix has after collision
|
|
143
|
-
//
|
|
142
|
+
// Cap at maxHeight, but never exceed the space Radix has after collision handling. The
|
|
143
|
+
// `100vh` fallback is load-bearing: an undefined var invalidates the whole `min()`, so
|
|
144
|
+
// `max-height` would resolve to `none` and the panel — which no longer scrolls itself —
|
|
145
|
+
// would grow unbounded with its rows clipped and unreachable.
|
|
144
146
|
style={{
|
|
145
|
-
maxHeight: `min(${maxHeight}px, var(--radix-context-menu-content-available-height))`,
|
|
147
|
+
maxHeight: `min(${maxHeight}px, var(--radix-context-menu-content-available-height, 100vh))`,
|
|
146
148
|
...style,
|
|
147
149
|
}}
|
|
148
150
|
className={cn(menuContentStyles({ variant }), className)}
|
|
149
151
|
{...props}
|
|
150
152
|
>
|
|
151
|
-
{
|
|
153
|
+
{/* Dedicated scroll viewport, matching Select's: the cap lives on the panel above, this
|
|
154
|
+
fills what is left and scrolls. `min-h-0` is what makes it work — a flex item will not
|
|
155
|
+
shrink below its content, so without it the list grows past the panel and the panel's
|
|
156
|
+
`overflow-hidden` just clips the rows with no scrollbar.
|
|
157
|
+
|
|
158
|
+
`gap-1` is re-declared here because `autoGroupChildren` emits several siblings (a group,
|
|
159
|
+
a label, a separator…) and this is now the element they are siblings within. */}
|
|
160
|
+
<div className="flex flex-col gap-1 flex-1 min-h-0 overflow-y-auto overflow-x-hidden rounded-[10px] scrollbar-hide">
|
|
161
|
+
{autoGroup ? autoGroupChildren(children) : children}
|
|
162
|
+
</div>
|
|
152
163
|
</ContextMenuPrimitive.Content>
|
|
153
164
|
</ContextMenuPrimitive.Portal>
|
|
154
165
|
),
|
|
@@ -180,18 +191,45 @@ const ContextMenuSubContent = React.forwardRef<
|
|
|
180
191
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent> & {
|
|
181
192
|
variant?: "PresentationStyle";
|
|
182
193
|
autoGroup?: boolean;
|
|
194
|
+
maxHeight?: number;
|
|
183
195
|
}
|
|
184
|
-
>(
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
196
|
+
>(
|
|
197
|
+
(
|
|
198
|
+
{
|
|
199
|
+
className,
|
|
200
|
+
variant = "PresentationStyle",
|
|
201
|
+
autoGroup = true,
|
|
202
|
+
collisionPadding = 8,
|
|
203
|
+
maxHeight = 320,
|
|
204
|
+
// Destructured out of `{...props}` so the spread below cannot clobber the cap. Radix
|
|
205
|
+
// re-publishes the namespaced available-height var on SubContent, so the same expression
|
|
206
|
+
// Content uses works here unchanged.
|
|
207
|
+
style,
|
|
208
|
+
children,
|
|
209
|
+
...props
|
|
210
|
+
},
|
|
211
|
+
ref,
|
|
212
|
+
) => (
|
|
213
|
+
<ContextMenuPrimitive.Portal>
|
|
214
|
+
<ContextMenuPrimitive.SubContent
|
|
215
|
+
ref={ref}
|
|
216
|
+
collisionPadding={collisionPadding}
|
|
217
|
+
style={{
|
|
218
|
+
maxHeight: `min(${maxHeight}px, var(--radix-context-menu-content-available-height, 100vh))`,
|
|
219
|
+
...style,
|
|
220
|
+
}}
|
|
221
|
+
className={cn(menuContentStyles({ variant }), className)}
|
|
222
|
+
{...props}
|
|
223
|
+
>
|
|
224
|
+
{/* Same panel-clips / viewport-scrolls split as Content. A submenu is a peer surface, so it
|
|
225
|
+
shares the 320px default rather than getting a smaller one of its own. */}
|
|
226
|
+
<div className="flex flex-col gap-1 flex-1 min-h-0 overflow-y-auto overflow-x-hidden rounded-[10px] scrollbar-hide">
|
|
227
|
+
{autoGroup ? autoGroupChildren(children) : children}
|
|
228
|
+
</div>
|
|
229
|
+
</ContextMenuPrimitive.SubContent>
|
|
230
|
+
</ContextMenuPrimitive.Portal>
|
|
231
|
+
),
|
|
232
|
+
);
|
|
195
233
|
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
|
|
196
234
|
|
|
197
235
|
const ContextMenuItem = React.forwardRef<
|
|
@@ -487,16 +525,19 @@ const menuContentStyles = cva(
|
|
|
487
525
|
"rounded-[14px]",
|
|
488
526
|
"min-w-[240px]",
|
|
489
527
|
"outline-none",
|
|
490
|
-
|
|
491
|
-
|
|
528
|
+
// The panel clips; the inner viewport below owns scrolling. Height is capped inline on the
|
|
529
|
+
// Content element from `min(maxHeight, available-height)` — no `max-h-*` class here on
|
|
530
|
+
// purpose, so the inline value governs.
|
|
531
|
+
"overflow-hidden",
|
|
492
532
|
// Only animate the OPEN (enter) state. An exit animation on [data-state=closed]
|
|
493
533
|
// holds the old DOM node during close, which breaks close/reposition on a
|
|
494
534
|
// second right-click (Radix issue #2572).
|
|
495
535
|
"data-[state=open]:animate-in",
|
|
496
536
|
"data-[state=open]:fade-in-0",
|
|
497
|
-
"scrollbar-hide",
|
|
498
537
|
"backdrop-blur-[21px]",
|
|
499
|
-
|
|
538
|
+
// No `gap` here: the panel has exactly one child (the scroll viewport), so a gap between
|
|
539
|
+
// siblings has nothing to act on. The 4px between groups/labels lives on that viewport.
|
|
540
|
+
"flex flex-col",
|
|
500
541
|
],
|
|
501
542
|
{
|
|
502
543
|
variants: {
|
|
@@ -506,9 +547,11 @@ const menuContentStyles = cva(
|
|
|
506
547
|
"shadow-[0_0_32px_2px_rgba(0,0,0,0.20),0_0_48px_2px_rgba(0,0,0,0.05)]",
|
|
507
548
|
],
|
|
508
549
|
},
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
550
|
+
},
|
|
551
|
+
// Was nested inside `variants`, where cva reads it as a variant group named
|
|
552
|
+
// "defaultVariants" and no default is ever applied. `menuGroupStyles` below has it right.
|
|
553
|
+
defaultVariants: {
|
|
554
|
+
variant: "PresentationStyle",
|
|
512
555
|
},
|
|
513
556
|
},
|
|
514
557
|
);
|
|
@@ -129,8 +129,8 @@ export interface FiltersContextValue {
|
|
|
129
129
|
setFilters: (filters: FilterState) => void;
|
|
130
130
|
/**
|
|
131
131
|
* What each filter control is, read off the `FormBuilder` children of `DataViews.Filters` —
|
|
132
|
-
* never derived from the rows. The root collects them as well, so
|
|
133
|
-
*
|
|
132
|
+
* never derived from the rows. The root collects them as well, so a consumer such as
|
|
133
|
+
* `Filters.Presets` can resolve a field by path even when it is rendered outside `Filters`.
|
|
134
134
|
*/
|
|
135
135
|
filterFields: readonly FilterFieldDescriptor[];
|
|
136
136
|
}
|
|
@@ -264,9 +264,9 @@ function DataViewsRoot({
|
|
|
264
264
|
[isPanelOpen, setPanelOpen],
|
|
265
265
|
);
|
|
266
266
|
|
|
267
|
-
// The descriptors as well as the value: `Filters.
|
|
268
|
-
//
|
|
269
|
-
// the context `Filters` provides to its own children.
|
|
267
|
+
// The descriptors as well as the value: a consumer such as `Filters.Presets` resolves a field by
|
|
268
|
+
// path, and may be rendered outside `Filters` — above the table, in a toolbar — where it cannot
|
|
269
|
+
// reach the context `Filters` provides to its own children.
|
|
270
270
|
const filterFields = useMemo(() => collectFilterFields(children), [children]);
|
|
271
271
|
|
|
272
272
|
const setFilters = useCallback(
|
|
@@ -10,7 +10,6 @@ import { FiltersContext, useDataViewsFilters } from "../context";
|
|
|
10
10
|
import { collectFilterFields, renderFields } from "./children";
|
|
11
11
|
import { Custom } from "./custom";
|
|
12
12
|
import { Presets } from "./presets";
|
|
13
|
-
import { Summary } from "./summary";
|
|
14
13
|
import { Sync } from "./sync";
|
|
15
14
|
import { toFormValues } from "./values";
|
|
16
15
|
import type { FiltersProps } from "../types";
|
|
@@ -155,5 +154,4 @@ function FiltersRoot({
|
|
|
155
154
|
export const Filters = Object.assign(FiltersRoot, {
|
|
156
155
|
Presets,
|
|
157
156
|
Custom,
|
|
158
|
-
Summary,
|
|
159
157
|
});
|