torch-glare 2.5.2 → 2.5.4
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 +1 -1
- package/apps/lib/components/Button.tsx +4 -4
- package/apps/lib/components/Calendar.tsx +7 -17
- package/apps/lib/components/ColorPicker.tsx +1 -1
- package/apps/lib/components/DataViews/data-views.tsx +1 -1
- package/apps/lib/components/DataViews/filters/filters.tsx +1 -1
- package/apps/lib/components/DataViews/header.tsx +5 -2
- package/apps/lib/components/DataViews/panel/section.tsx +3 -1
- package/apps/lib/components/DataViews/views/board-view.tsx +3 -1
- package/apps/lib/components/DataViews/views/inbox-view.tsx +4 -1
- package/apps/lib/components/DataViews/views/pane-views.tsx +5 -1
- package/apps/lib/components/DataViews/views/table-view.tsx +67 -8
- package/apps/lib/components/DataViews/views/tree-view.tsx +5 -3
- package/apps/lib/components/Drawer.tsx +18 -1
- package/apps/lib/components/FormBuilder/context.ts +9 -1
- package/apps/lib/components/FormBuilder/submit.tsx +11 -2
- package/apps/lib/components/FormBuilder/types.ts +5 -1
- package/apps/lib/components/FormRenderer/form-renderer.tsx +15 -3
- package/apps/lib/components/FormRenderer/stepper.tsx +35 -14
- package/apps/lib/components/FormRenderer/types.ts +1 -1
- package/apps/lib/components/Input.tsx +19 -4
- package/apps/lib/components/Popover.tsx +93 -55
- package/apps/lib/components/SearchableSelect.tsx +12 -11
- package/apps/lib/components/SearchableTree.tsx +10 -11
- package/apps/lib/components/SearchableTreeDialog.tsx +10 -11
- package/apps/lib/components/SectionBlock.tsx +14 -2
- package/apps/lib/components/Select.tsx +35 -11
- package/apps/lib/components/SimpleSelect.tsx +2 -2
- package/apps/lib/components/SlideDatePicker.tsx +17 -4
- package/apps/lib/components/Stepper.tsx +329 -180
- package/apps/lib/components/Switch.tsx +2 -2
- package/apps/lib/components/Table.tsx +55 -30
- package/apps/lib/components/Textarea.tsx +29 -4
- package/apps/lib/components/TreeDropDown.tsx +18 -6
- package/apps/lib/components/TreeFolder/TreeFolder.tsx +61 -61
- package/apps/lib/registry.json +7 -16
- package/apps/lib/tsconfig.tsbuildinfo +1 -1
- package/docs/components/action-button.md +3 -1
- package/docs/components/data-views/index.md +7 -0
- package/docs/components/form-builder.md +4 -2
- package/docs/components/form-renderer.md +1 -1
- package/docs/components/stepper.md +119 -24
- package/docs/components/table.md +33 -0
- package/docs/how-to/forms-with-form-builder.md +3 -1
- 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/FormStepper.tsx +0 -272
- package/docs/components/form-stepper.md +0 -250
|
@@ -16,7 +16,7 @@ import type {
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
export type FormRendererDisplay = "page" | "drawer";
|
|
19
|
-
export type FieldDirection = "horizontal" | "vertical";
|
|
19
|
+
export type FieldDirection = "horizontal" | "vertical" | "flexible";
|
|
20
20
|
|
|
21
21
|
export interface FormRendererProps<T extends FieldValues = FieldValues> {
|
|
22
22
|
/** The form body — `FormRenderer.Section` / field / `FormRenderer.Stepper` JSX. */
|
|
@@ -62,7 +62,16 @@ interface TrillingProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
62
62
|
}
|
|
63
63
|
export const Trilling = ({ children, className, ...props }: TrillingProps) => {
|
|
64
64
|
return (
|
|
65
|
-
|
|
65
|
+
// `ps-1` mirrors the inner side of Figma's `EndField-Container` (`p-[4px]`), keeping the field's
|
|
66
|
+
// text clear of the end button instead of running straight into it.
|
|
67
|
+
//
|
|
68
|
+
// Logical, not physical: this slot flips to the left edge in RTL, so a `pl-1` would put the
|
|
69
|
+
// padding on the outside — measured as 0px of clearance and the button 8px from the edge
|
|
70
|
+
// instead of 4px. `padding-inline-start` follows the slot.
|
|
71
|
+
<div
|
|
72
|
+
{...props}
|
|
73
|
+
className={cn("flex items-center justify-center h-full gap-1 py-1 ps-1", className)}
|
|
74
|
+
>
|
|
66
75
|
{children}
|
|
67
76
|
</div>
|
|
68
77
|
);
|
|
@@ -135,7 +144,10 @@ input:-webkit-autofill:active {
|
|
|
135
144
|
|
|
136
145
|
export const GroupStyles = cva(
|
|
137
146
|
[
|
|
138
|
-
|
|
147
|
+
// 3px, not 4: Figma's `EndField-Container` is `p-[4px]` and its stroke sits *inside* that
|
|
148
|
+
// padding, so the end button lands 4px from the field's outer edge. CSS `border-box` adds the
|
|
149
|
+
// 1px border on top of the padding, so 4px here would render 5px. 1 + 3 = 4.
|
|
150
|
+
"flex w-full min-w-0 px-[3px] justify-center items-center",
|
|
139
151
|
"typography-body-small-regular",
|
|
140
152
|
"border",
|
|
141
153
|
"transition-all duration-200 ease-in-out",
|
|
@@ -180,14 +192,17 @@ export const GroupStyles = cva(
|
|
|
180
192
|
"[&_input]:text-white",
|
|
181
193
|
],
|
|
182
194
|
},
|
|
195
|
+
// Radius per Figma's `radius/*` collection: S is `radius/lg` (8px) and M is `radius/xl`
|
|
196
|
+
// (12px) — a deliberate jump, not consecutive steps. This `Group` is the shared field root,
|
|
197
|
+
// so it is also what gives `InputField` and `InnerLabelField` their corners.
|
|
183
198
|
size: {
|
|
184
199
|
S: [
|
|
185
200
|
"h-[30px] min-h-[30px]",
|
|
186
|
-
"rounded-
|
|
201
|
+
"rounded-radius-lg [&_input]:h-[30px] [&_div[data-role='icon']]:text-[16px]",
|
|
187
202
|
],
|
|
188
203
|
M: [
|
|
189
204
|
"h-[40px] min-h-[40px]",
|
|
190
|
-
"rounded-
|
|
205
|
+
"rounded-radius-xl [&_input]:h-[40px] [&_div[data-role='icon']]:text-[18px] [&_div[data-role='icon']]:px-[2px]",
|
|
191
206
|
],
|
|
192
207
|
},
|
|
193
208
|
error: {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cva, VariantProps } from "class-variance-authority";
|
|
3
3
|
import { cn } from "../utils/cn";
|
|
4
|
-
import React, { useEffect, useRef } from "react";
|
|
4
|
+
import React, { cloneElement, isValidElement, useEffect, useRef } from "react";
|
|
5
5
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
6
6
|
import { Slot } from "@radix-ui/react-slot";
|
|
7
7
|
import { Themes } from "../utils/types";
|
|
8
8
|
|
|
9
|
+
|
|
9
10
|
interface LocalPopOverProps extends VariantProps<typeof popoverStyles> {
|
|
10
11
|
variant?: "SystemStyle" | "PresentationStyle";
|
|
11
12
|
className?: string;
|
|
@@ -32,18 +33,19 @@ PopoverTrigger.displayName = PopoverPrimitive.Trigger.displayName;
|
|
|
32
33
|
const PopoverContent = React.forwardRef<
|
|
33
34
|
React.ElementRef<typeof PopoverPrimitive.Content>,
|
|
34
35
|
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> &
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
LocalPopOverProps & {
|
|
37
|
+
theme?: Themes;
|
|
38
|
+
}
|
|
38
39
|
>(
|
|
39
40
|
(
|
|
40
41
|
{
|
|
41
42
|
className,
|
|
42
43
|
align = "center",
|
|
43
44
|
sideOffset = 4,
|
|
44
|
-
variant = "
|
|
45
|
+
variant = "PresentationStyle",
|
|
45
46
|
overlayBlur = false,
|
|
46
47
|
theme,
|
|
48
|
+
children,
|
|
47
49
|
...props
|
|
48
50
|
},
|
|
49
51
|
ref,
|
|
@@ -58,7 +60,9 @@ const PopoverContent = React.forwardRef<
|
|
|
58
60
|
sideOffset={sideOffset}
|
|
59
61
|
className={cn(popoverStyles({ variant, overlayBlur }), className)}
|
|
60
62
|
{...props}
|
|
61
|
-
|
|
63
|
+
>
|
|
64
|
+
{children}
|
|
65
|
+
</PopoverPrimitive.Content>
|
|
62
66
|
</div>
|
|
63
67
|
) : (
|
|
64
68
|
<PopoverPrimitive.Content
|
|
@@ -68,7 +72,9 @@ const PopoverContent = React.forwardRef<
|
|
|
68
72
|
sideOffset={sideOffset}
|
|
69
73
|
className={cn(popoverStyles({ variant, overlayBlur }), className)}
|
|
70
74
|
{...props}
|
|
71
|
-
|
|
75
|
+
>
|
|
76
|
+
{children}
|
|
77
|
+
</PopoverPrimitive.Content>
|
|
72
78
|
)}
|
|
73
79
|
</PopoverPrimitive.Portal>
|
|
74
80
|
),
|
|
@@ -84,7 +90,7 @@ interface Props<T extends React.ElementType = "li">
|
|
|
84
90
|
|
|
85
91
|
// Define the PopoverItem component with a generic type parameter
|
|
86
92
|
const PopoverItem = <T extends React.ElementType = "li">({
|
|
87
|
-
variant = "
|
|
93
|
+
variant = "Default",
|
|
88
94
|
size = "M",
|
|
89
95
|
asChild,
|
|
90
96
|
className,
|
|
@@ -103,72 +109,95 @@ const PopoverItem = <T extends React.ElementType = "li">({
|
|
|
103
109
|
}
|
|
104
110
|
}, [active]);
|
|
105
111
|
|
|
112
|
+
const itemClassName = cn(PopoverItemStyles({ variant, size, active }), className);
|
|
113
|
+
|
|
114
|
+
// The styles are a two-layer chip (see `PopoverItemStyles`): this element is the container and
|
|
115
|
+
// the inner div is the row that lights up, which every `[&>div]:` rule targets. It therefore has
|
|
116
|
+
// to exist in both branches.
|
|
117
|
+
if (asChild && isValidElement(children)) {
|
|
118
|
+
// Slot merges the item's props onto the consumer's element (e.g. a Link), so the row div has to
|
|
119
|
+
// be injected as that element's child — wrapping the Link instead would move the class onto the
|
|
120
|
+
// wrapper and leave the anchor nested inside the item rather than being it.
|
|
121
|
+
const child = children as React.ReactElement<{ children?: React.ReactNode }>;
|
|
122
|
+
return (
|
|
123
|
+
<Slot className={itemClassName} ref={ref as React.Ref<HTMLElement>}>
|
|
124
|
+
{cloneElement(child, {}, <div>{child.props.children}</div>)}
|
|
125
|
+
</Slot>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
106
129
|
return (
|
|
107
130
|
<Component
|
|
108
131
|
{...(props as React.ComponentPropsWithoutRef<T>)} // Spread the props dynamically
|
|
109
|
-
className={
|
|
110
|
-
PopoverItemStyles({
|
|
111
|
-
variant,
|
|
112
|
-
size,
|
|
113
|
-
active,
|
|
114
|
-
}),
|
|
115
|
-
className,
|
|
116
|
-
)}
|
|
117
|
-
/// <reference path="" />
|
|
132
|
+
className={itemClassName}
|
|
118
133
|
ref={ref}
|
|
119
134
|
>
|
|
120
|
-
{children}
|
|
135
|
+
<div>{children}</div>
|
|
121
136
|
</Component>
|
|
122
137
|
);
|
|
123
138
|
};
|
|
124
139
|
|
|
125
140
|
export { Popover, PopoverTrigger, PopoverContent, PopoverItem };
|
|
126
141
|
|
|
142
|
+
|
|
127
143
|
const PopoverItemStyles = cva(
|
|
144
|
+
// Ported from `MenuItemStyles` (ContextMenu/DropdownMenu) so a popover row and a menu row are the
|
|
145
|
+
// same object, minus the menu's grey `rgba(184,192,204,0.36)` container — a popover row sits
|
|
146
|
+
// directly on the panel. The inner `<div>` is still the row that lights up on hover/focus, and
|
|
147
|
+
// `PopoverItem` always renders it, including through `asChild`.
|
|
128
148
|
[
|
|
129
|
-
"text-content-presentation-
|
|
149
|
+
"text-content-presentation-global-primary-light typography-body-medium-regular",
|
|
130
150
|
"outline-none",
|
|
131
151
|
"border",
|
|
132
152
|
"border-transparent",
|
|
133
153
|
"flex w-full",
|
|
134
|
-
"
|
|
154
|
+
"shrink-0", // keep full row height so the popover scrolls instead of squishing
|
|
135
155
|
"items-center",
|
|
136
156
|
"justify-start",
|
|
137
157
|
"text-overflow",
|
|
138
158
|
"overflow-hidden",
|
|
139
|
-
"
|
|
140
|
-
"rounded-[4px]",
|
|
159
|
+
"p-[2px]",
|
|
141
160
|
"transition-all",
|
|
142
161
|
"ease-in-out",
|
|
143
162
|
"duration-300",
|
|
163
|
+
"[&>div]:flex",
|
|
164
|
+
"[&>div]:px-[12px]",
|
|
165
|
+
"[&>div]:py-[4px]",
|
|
166
|
+
"[&>div]:gap-2",
|
|
167
|
+
"[&>div]:w-full",
|
|
168
|
+
"[&>div]:rounded-[8px]",
|
|
169
|
+
"[&>div]:items-center",
|
|
170
|
+
"group",
|
|
144
171
|
],
|
|
145
172
|
{
|
|
146
173
|
variants: {
|
|
147
174
|
variant: {
|
|
175
|
+
// The menu uses Radix's `data-highlighted`; a PopoverItem is a plain button, so the
|
|
176
|
+
// keyboard-highlight equivalent here is `:focus`.
|
|
148
177
|
Default: [
|
|
149
|
-
"text-content-presentation-
|
|
150
|
-
"bg-
|
|
151
|
-
"hover:
|
|
152
|
-
"
|
|
153
|
-
"focus:
|
|
154
|
-
"
|
|
155
|
-
"disabled:
|
|
156
|
-
"disabled:bg-
|
|
178
|
+
"text-content-presentation-global-primary-light",
|
|
179
|
+
"[&>div]:hover:bg-white-50 [&>div]:hover:shadow-[0_0_16px_0_rgba(0,0,0,0.36)]",
|
|
180
|
+
"[&>div]:hover:text-black-1000",
|
|
181
|
+
"[&:focus>div]:bg-white-alpha-75",
|
|
182
|
+
"[&:focus>div]:text-black-1000",
|
|
183
|
+
"[&:disabled>div]:text-content-presentation-global-primary-light",
|
|
184
|
+
"[&:disabled>div]:opacity-50",
|
|
185
|
+
"[&:disabled>div]:hover:bg-transparent",
|
|
186
|
+
"[&:disabled>div]:hover:shadow-none",
|
|
157
187
|
],
|
|
158
188
|
Warning: [
|
|
159
|
-
"text-
|
|
160
|
-
"hover:bg-
|
|
161
|
-
"
|
|
162
|
-
"focus:
|
|
163
|
-
"
|
|
189
|
+
"text-blue-sparkle-200",
|
|
190
|
+
"[&>div]:hover:bg-white-50 [&>div]:hover:shadow-[0_0_16px_0_rgba(0,0,0,0.36)]",
|
|
191
|
+
"[&>div]:hover:text-blue-sparkle-700",
|
|
192
|
+
"[&:focus>div]:bg-white-alpha-75",
|
|
193
|
+
"[&:focus>div]:text-blue-sparkle-700",
|
|
164
194
|
],
|
|
165
195
|
Negative: [
|
|
166
|
-
"text-
|
|
167
|
-
"hover:bg-
|
|
168
|
-
"hover:text-
|
|
169
|
-
"focus:bg-
|
|
170
|
-
"focus:text-
|
|
171
|
-
"active:text-content-presentation-state-negative",
|
|
196
|
+
"text-medium-red-200",
|
|
197
|
+
"[&>div]:hover:bg-white-50 [&>div]:hover:shadow-[0_0_16px_0_rgba(0,0,0,0.36)]",
|
|
198
|
+
"[&>div]:hover:text-medium-red-600",
|
|
199
|
+
"[&:focus>div]:bg-white-alpha-75",
|
|
200
|
+
"[&:focus>div]:text-medium-red-600",
|
|
172
201
|
],
|
|
173
202
|
SystemStyle: [
|
|
174
203
|
"bg-background-system-body-primary",
|
|
@@ -198,13 +227,13 @@ const PopoverItemStyles = cva(
|
|
|
198
227
|
"text-content-presentation-action-light-primary",
|
|
199
228
|
],
|
|
200
229
|
},
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
230
|
+
},
|
|
231
|
+
// Same misplacement as `popoverStyles` — a sibling of `variants`, not one of them.
|
|
232
|
+
defaultVariants: {
|
|
233
|
+
variant: "Default",
|
|
234
|
+
size: "M",
|
|
235
|
+
active: false,
|
|
236
|
+
disabled: false,
|
|
208
237
|
},
|
|
209
238
|
compoundVariants: [
|
|
210
239
|
{
|
|
@@ -239,22 +268,31 @@ const popoverStyles = cva(
|
|
|
239
268
|
"bg-background-system-body-primary",
|
|
240
269
|
"shadow-[0px_0px_18px_0px_rgba(0,0,0,0.75)]",
|
|
241
270
|
],
|
|
242
|
-
//
|
|
243
|
-
//
|
|
271
|
+
// Figma `Dropdown-Menu-1.0` (1735:159246) — the same surface `menuContentStyles` renders,
|
|
272
|
+
// so a popover and a dropdown menu are indistinguishable panels.
|
|
244
273
|
PresentationStyle: [
|
|
245
|
-
|
|
274
|
+
// `border-0`, not `border-transparent`: the base sets `border`, and with `border-box`
|
|
275
|
+
// that 1px sits on top of the 4px padding, making the panel 2px wider than the design.
|
|
276
|
+
"border-0",
|
|
246
277
|
"rounded-[14px]",
|
|
247
278
|
"backdrop-blur-[21px]",
|
|
248
|
-
|
|
279
|
+
// The design's fill is a raw `rgba(61,64,69,0.72)` with no Figma variable behind it, so it
|
|
280
|
+
// is spelled out exactly as DropdownMenu/ContextMenu already spell it. It has to be
|
|
281
|
+
// translucent: over the previous opaque `form-base` the backdrop-blur painted nothing.
|
|
282
|
+
"bg-[rgba(61,64,69,0.72)]",
|
|
249
283
|
"shadow-[0_0_32px_2px_rgba(0,0,0,0.20),0_0_48px_2px_rgba(0,0,0,0.05)]",
|
|
284
|
+
// Figma's group-container stacks its rows with a 4px gutter.
|
|
285
|
+
"flex flex-col gap-1",
|
|
250
286
|
],
|
|
251
287
|
},
|
|
252
288
|
overlayBlur: {
|
|
253
289
|
true: ["h-fit"],
|
|
254
290
|
},
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
291
|
+
},
|
|
292
|
+
// Was nested inside `variants`, where cva reads it as a variant group called
|
|
293
|
+
// "defaultVariants" and no default is ever applied.
|
|
294
|
+
defaultVariants: {
|
|
295
|
+
variant: "PresentationStyle",
|
|
258
296
|
},
|
|
259
297
|
},
|
|
260
298
|
);
|
|
@@ -6,7 +6,8 @@ import { cn } from "../utils/cn";
|
|
|
6
6
|
import { Themes } from "../utils/types";
|
|
7
7
|
import { Popover, PopoverContent, PopoverTrigger } from "./Popover";
|
|
8
8
|
import { Icon, Input, Group } from "./Input";
|
|
9
|
-
import {
|
|
9
|
+
import { LoadingIcon } from "./Button";
|
|
10
|
+
import { ActionButton } from "./ActionButton";
|
|
10
11
|
import { useClickOutside } from "../hooks/useClickOutside";
|
|
11
12
|
// Reuse the exact menu-item styling so rows look identical to DropdownMenuItem.
|
|
12
13
|
import { MenuItemStyles } from "./DropdownMenu";
|
|
@@ -189,7 +190,11 @@ export function SearchableSelect({
|
|
|
189
190
|
setDropdownWidth(e.currentTarget.offsetWidth)
|
|
190
191
|
}
|
|
191
192
|
// Trigger styling mirrors the `Select` component's trigger so the two read identically.
|
|
192
|
-
|
|
193
|
+
// `p-[3px]` so the end button lands 4px from the outer edge once the 1px border is added.
|
|
194
|
+
// The old `rounded-[6px]` here was dead but dangerous: `twMerge` cannot dedupe it against
|
|
195
|
+
// the Group's `rounded-radius-xl` (custom scale key vs arbitrary value), so both shipped
|
|
196
|
+
// and the 12px only won on CSS source order.
|
|
197
|
+
className={cn("flex w-full items-center gap-1 p-[3px]", className)}
|
|
193
198
|
>
|
|
194
199
|
{icon && <Icon>{icon}</Icon>}
|
|
195
200
|
<Input
|
|
@@ -212,10 +217,10 @@ export function SearchableSelect({
|
|
|
212
217
|
"!h-[24px]": size === "M",
|
|
213
218
|
})}
|
|
214
219
|
/>
|
|
215
|
-
{/* Chevron toggle —
|
|
216
|
-
<
|
|
220
|
+
{/* Chevron toggle — the shared in-field action button (Figma `ActionButton` M). */}
|
|
221
|
+
<ActionButton
|
|
217
222
|
as="span"
|
|
218
|
-
|
|
223
|
+
size="M"
|
|
219
224
|
tabIndex={-1}
|
|
220
225
|
aria-label={open ? "Close" : "Open"}
|
|
221
226
|
onMouseDown={(e: React.MouseEvent) => {
|
|
@@ -227,11 +232,7 @@ export function SearchableSelect({
|
|
|
227
232
|
setOpen(true);
|
|
228
233
|
}
|
|
229
234
|
}}
|
|
230
|
-
className={cn(
|
|
231
|
-
// Same box + open-state fill as the Select trigger's chevron.
|
|
232
|
-
"h-[32px] w-[32px] shrink-0",
|
|
233
|
-
open && "bg-background-presentation-action-hover text-white",
|
|
234
|
-
)}
|
|
235
|
+
className={cn("shrink-0", open && "bg-background-presentation-action-hover text-white")}
|
|
235
236
|
>
|
|
236
237
|
<i
|
|
237
238
|
className={cn(
|
|
@@ -239,7 +240,7 @@ export function SearchableSelect({
|
|
|
239
240
|
open && "rotate-180",
|
|
240
241
|
)}
|
|
241
242
|
/>
|
|
242
|
-
</
|
|
243
|
+
</ActionButton>
|
|
243
244
|
</Group>
|
|
244
245
|
</PopoverTrigger>
|
|
245
246
|
|
|
@@ -5,7 +5,8 @@ import { cn } from "../utils/cn";
|
|
|
5
5
|
import { Themes } from "../utils/types";
|
|
6
6
|
import { Popover, PopoverContent, PopoverTrigger } from "./Popover";
|
|
7
7
|
import { Icon, Input, Group } from "./Input";
|
|
8
|
-
import {
|
|
8
|
+
import { LoadingIcon } from "./Button";
|
|
9
|
+
import { ActionButton } from "./ActionButton";
|
|
9
10
|
import { useClickOutside } from "../hooks/useClickOutside";
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -236,7 +237,8 @@ export function SearchableTree<T>({
|
|
|
236
237
|
onFocus={(e: React.FocusEvent<HTMLDivElement>) =>
|
|
237
238
|
setDropdownWidth(e.currentTarget.offsetWidth)
|
|
238
239
|
}
|
|
239
|
-
|
|
240
|
+
// 3px + the 1px border = Figma's 4px from the field's outer edge.
|
|
241
|
+
className={cn("flex w-full items-center gap-1 p-[3px]", className)}
|
|
240
242
|
>
|
|
241
243
|
{icon && <Icon>{icon}</Icon>}
|
|
242
244
|
{/* The field itself is the search input — typing filters the tree. */}
|
|
@@ -262,10 +264,10 @@ export function SearchableTree<T>({
|
|
|
262
264
|
"!h-[24px]": size === "M",
|
|
263
265
|
})}
|
|
264
266
|
/>
|
|
265
|
-
{/* Chevron toggle —
|
|
266
|
-
<
|
|
267
|
+
{/* Chevron toggle — the shared in-field action button (Figma `ActionButton` M). */}
|
|
268
|
+
<ActionButton
|
|
267
269
|
as="span"
|
|
268
|
-
|
|
270
|
+
size="M"
|
|
269
271
|
tabIndex={-1}
|
|
270
272
|
aria-label={open ? "Close" : "Open"}
|
|
271
273
|
onMouseDown={(e: React.MouseEvent) => {
|
|
@@ -278,18 +280,15 @@ export function SearchableTree<T>({
|
|
|
278
280
|
setOpen(true);
|
|
279
281
|
}
|
|
280
282
|
}}
|
|
281
|
-
className={cn(
|
|
282
|
-
"shrink-0 h-[32px] w-[32px] rounded-[4px]",
|
|
283
|
-
open && "bg-background-presentation-action-hover text-white",
|
|
284
|
-
)}
|
|
283
|
+
className={cn("shrink-0", open && "bg-background-presentation-action-hover text-white")}
|
|
285
284
|
>
|
|
286
285
|
<i
|
|
287
286
|
className={cn(
|
|
288
|
-
"ri-arrow-down-s-line text-[
|
|
287
|
+
"ri-arrow-down-s-line !text-[26px] transition-all duration-100 ease-in-out",
|
|
289
288
|
open && "rotate-180",
|
|
290
289
|
)}
|
|
291
290
|
/>
|
|
292
|
-
</
|
|
291
|
+
</ActionButton>
|
|
293
292
|
</Group>
|
|
294
293
|
</PopoverTrigger>
|
|
295
294
|
|
|
@@ -13,7 +13,8 @@ import { cn } from "../utils/cn";
|
|
|
13
13
|
import { Themes } from "../utils/types";
|
|
14
14
|
import { Dialog, DialogTrigger, DialogContent, DialogTitle } from "./Dialog";
|
|
15
15
|
import { Icon, Input, Group } from "./Input";
|
|
16
|
-
import {
|
|
16
|
+
import { LoadingIcon } from "./Button";
|
|
17
|
+
import { ActionButton } from "./ActionButton";
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* SearchableTreeDialog — a field that opens a modal Dialog to pick a node from a tree.
|
|
@@ -224,7 +225,8 @@ export function SearchableTreeDialog<T>({
|
|
|
224
225
|
size={size === "XS" ? "S" : size}
|
|
225
226
|
role="button"
|
|
226
227
|
tabIndex={0}
|
|
227
|
-
|
|
228
|
+
// 3px + the 1px border = Figma's 4px from the field's outer edge.
|
|
229
|
+
className={cn("flex w-full items-center gap-1 p-[3px] cursor-pointer", className)}
|
|
228
230
|
>
|
|
229
231
|
{icon && <Icon>{icon}</Icon>}
|
|
230
232
|
<span
|
|
@@ -237,24 +239,21 @@ export function SearchableTreeDialog<T>({
|
|
|
237
239
|
>
|
|
238
240
|
{triggerLabel}
|
|
239
241
|
</span>
|
|
240
|
-
{/* Chevron toggle —
|
|
241
|
-
<
|
|
242
|
+
{/* Chevron toggle — the shared in-field action button (Figma `ActionButton` M). */}
|
|
243
|
+
<ActionButton
|
|
242
244
|
as="span"
|
|
243
|
-
|
|
245
|
+
size="M"
|
|
244
246
|
tabIndex={-1}
|
|
245
247
|
aria-label={open ? "Close" : "Open"}
|
|
246
|
-
className={cn(
|
|
247
|
-
"shrink-0 h-[32px] w-[32px] rounded-[4px]",
|
|
248
|
-
open && "bg-background-presentation-action-hover text-white",
|
|
249
|
-
)}
|
|
248
|
+
className={cn("shrink-0", open && "bg-background-presentation-action-hover text-white")}
|
|
250
249
|
>
|
|
251
250
|
<i
|
|
252
251
|
className={cn(
|
|
253
|
-
"ri-arrow-down-s-line text-[
|
|
252
|
+
"ri-arrow-down-s-line !text-[26px] transition-all duration-100 ease-in-out",
|
|
254
253
|
open && "rotate-180",
|
|
255
254
|
)}
|
|
256
255
|
/>
|
|
257
|
-
</
|
|
256
|
+
</ActionButton>
|
|
258
257
|
</Group>
|
|
259
258
|
</DialogTrigger>
|
|
260
259
|
|
|
@@ -14,7 +14,17 @@ const titleBadge = cva(
|
|
|
14
14
|
Orange: "bg-red-orange-900",
|
|
15
15
|
Purple: "bg-violet-900",
|
|
16
16
|
Pink: "bg-medium-violet-red-900",
|
|
17
|
-
Gray
|
|
17
|
+
// Per Figma (`FormHeader10`, Color=Gray), which pairs two *variables*: the pill is
|
|
18
|
+
// `background/presentation/button/primary` and its label `content/presentation/global/
|
|
19
|
+
// primary-inverse`. They invert together — a light pill with dark text in the dark theme,
|
|
20
|
+
// dark pill with white text in the light one — so the text token has to ride along on the
|
|
21
|
+
// variant. It overrides the base `text-[#F4F4F4]` because `titleBadge` goes through `cn`
|
|
22
|
+
// (tailwind-merge) below; the other seven are dark palette chips that still want that base.
|
|
23
|
+
//
|
|
24
|
+
// This previously read `bg-background-presentation-badge-gray`, which is not a token at all:
|
|
25
|
+
// every badge colour has `-solid`/`-subtle` and none has a bare name, so the utility was
|
|
26
|
+
// never generated and a Gray badge painted no background whatsoever.
|
|
27
|
+
Gray: "bg-background-presentation-button-primary text-content-presentation-global-primary-inverse",
|
|
18
28
|
},
|
|
19
29
|
},
|
|
20
30
|
defaultVariants: { color: "Blue" },
|
|
@@ -61,7 +71,9 @@ const rail = cva("flex w-full min-w-[300px] flex-col items-start", {
|
|
|
61
71
|
variants: {
|
|
62
72
|
variant: {
|
|
63
73
|
// Hairline between each direct child (form rows).
|
|
64
|
-
|
|
74
|
+
// `divide-gray-300` was a hardcoded light gray, so in the dark theme every row separator
|
|
75
|
+
// rendered as a bright rgb(209,213,219) line. The token follows the theme.
|
|
76
|
+
Default: "divide-y divide-border-presentation-global-primary",
|
|
65
77
|
// The table draws its own row borders — a divide rule would double up on the
|
|
66
78
|
// table / scroller / end-action siblings.
|
|
67
79
|
Table: "",
|
|
@@ -3,7 +3,7 @@ import React from "react";
|
|
|
3
3
|
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
4
4
|
import { cn } from "../utils/cn";
|
|
5
5
|
import { cva, VariantProps } from "class-variance-authority";
|
|
6
|
-
import {
|
|
6
|
+
import { ActionButton } from "./ActionButton";
|
|
7
7
|
import { Tooltip } from "./Tooltip";
|
|
8
8
|
import { MenuItemStyles } from "./DropdownMenu";
|
|
9
9
|
import { Themes } from "../utils/types";
|
|
@@ -61,10 +61,11 @@ const SelectTrigger = React.forwardRef<
|
|
|
61
61
|
{children}
|
|
62
62
|
</p>
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
{/* The shared in-field action button. Its box tracks the trigger height the same way
|
|
65
|
+
InputField's does — a 30px field takes the 22px button, a 40px field the 32px one. */}
|
|
66
|
+
<ActionButton
|
|
65
67
|
as={"span"}
|
|
66
|
-
|
|
67
|
-
size={"L"}
|
|
68
|
+
size={size === "XL" ? "M" : size === "S" ? "XS" : "S"}
|
|
68
69
|
className={cn([
|
|
69
70
|
"group-aria-expanded:bg-background-presentation-action-hover",
|
|
70
71
|
"group-aria-expanded:text-white",
|
|
@@ -80,7 +81,7 @@ const SelectTrigger = React.forwardRef<
|
|
|
80
81
|
{ icon: icon },
|
|
81
82
|
)}
|
|
82
83
|
/>
|
|
83
|
-
</
|
|
84
|
+
</ActionButton>
|
|
84
85
|
</SelectPrimitive.Trigger>
|
|
85
86
|
</Tooltip>
|
|
86
87
|
);
|
|
@@ -176,7 +177,7 @@ const SelectItem = React.forwardRef<
|
|
|
176
177
|
>
|
|
177
178
|
<div>
|
|
178
179
|
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
179
|
-
<SelectPrimitive.ItemIndicator className="
|
|
180
|
+
<SelectPrimitive.ItemIndicator className="ms-auto flex shrink-0">
|
|
180
181
|
<i className="ri-check-line text-[16px]" />
|
|
181
182
|
</SelectPrimitive.ItemIndicator>
|
|
182
183
|
</div>
|
|
@@ -253,7 +254,11 @@ const SelectContentStyles = cva(
|
|
|
253
254
|
|
|
254
255
|
const PopoverTriggerStyles = cva(
|
|
255
256
|
[
|
|
256
|
-
|
|
257
|
+
// No radius here — every `size` variant sets its own. `twMerge` does NOT dedupe the
|
|
258
|
+
// `rounded-radius-*` classes (they are custom scale keys, not values it recognises), so a
|
|
259
|
+
// radius on the base would survive alongside the variant's and let CSS source order pick the
|
|
260
|
+
// winner: `md` is generated before `lg`, so a base `lg` would silently beat size S's `md`.
|
|
261
|
+
"flex flex-row justify-between items-center outline-none",
|
|
257
262
|
"[&_span]:text-content-presentation-action-light-primary",
|
|
258
263
|
"typography-body-small-regular",
|
|
259
264
|
"[&_p]:px-[10px] [&_p]:whitespace-nowrap",
|
|
@@ -299,12 +304,31 @@ const PopoverTriggerStyles = cva(
|
|
|
299
304
|
onTable: {
|
|
300
305
|
true: ["border-transparent", "bg-transparent"],
|
|
301
306
|
},
|
|
307
|
+
// No `[&_span]:h-/w-` here: the chevron is an `ActionButton` and sizes itself from its own
|
|
308
|
+
// `size` prop. Those descendant rules used to force its box, and being descendant selectors
|
|
309
|
+
// they outranked the button's own classes — they also hit the SelectValue text span, which
|
|
310
|
+
// only escaped being squashed because an inline element ignores width/height.
|
|
302
311
|
size: {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
312
|
+
// 24px tall — below the field scale's smallest step (30px), so it keeps its own
|
|
313
|
+
// `radius/md` rather than being rounded up to the 8px a real field would use.
|
|
314
|
+
// Heights are explicit because the trigger used to be sized by whichever chevron box the
|
|
315
|
+
// `[&_span]` rules imposed; now that the chevron sizes itself, the field must state its own.
|
|
316
|
+
//
|
|
317
|
+
// Every size states its padding so that `2×border + 2×padding + chevron` is exactly the
|
|
318
|
+
// trigger height — the chevron then sits an equal distance from all four edges instead of
|
|
319
|
+
// being flush to the border horizontally and centred vertically. Only XL had any padding
|
|
320
|
+
// before, which is why it was the only size that looked right.
|
|
321
|
+
//
|
|
322
|
+
// L and XL are the real field heights (30/40) and land on Figma's 4px. S and M have no
|
|
323
|
+
// Figma counterpart, so they take 3px — the most that still fits their chevron exactly.
|
|
324
|
+
S: ["h-[24px] p-[2px] rounded-radius-md [&_p]:typography-body-small-medium"],
|
|
325
|
+
// 28px and 30px — the field scale's S step (30px) is `radius/lg`.
|
|
326
|
+
M: ["h-[28px] p-[2px] rounded-radius-lg [&_p]:typography-body-medium-medium"],
|
|
327
|
+
L: ["h-[30px] p-[3px] rounded-radius-lg [&_p]:typography-body-large-medium"],
|
|
328
|
+
// 40px tall, so it is exactly InputField M and takes that size's `radius/xl` (12px).
|
|
329
|
+
// It sat at 8px before — the one place a 40px field in the system rounded like a 30px one.
|
|
306
330
|
XL: [
|
|
307
|
-
"h-[40px] p-[
|
|
331
|
+
"h-[40px] p-[3px] rounded-radius-xl [&_p]:typography-body-large-regular [&_p]:px-[4px]",
|
|
308
332
|
],
|
|
309
333
|
},
|
|
310
334
|
},
|
|
@@ -13,7 +13,7 @@ const SimpleSelectDropDown = ({ className, children, onClick }: SimpleSelectDrop
|
|
|
13
13
|
return (
|
|
14
14
|
<div
|
|
15
15
|
className={cn(
|
|
16
|
-
"absolute min-w-[100px] z-[20] top-[27px]
|
|
16
|
+
"absolute min-w-[100px] z-[20] top-[27px] start-0",
|
|
17
17
|
dropdownMenuStyles({ variant: "SystemStyle" }),
|
|
18
18
|
className,
|
|
19
19
|
)}
|
|
@@ -97,7 +97,7 @@ const SimpleSelectValue = ({
|
|
|
97
97
|
"w-fit",
|
|
98
98
|
"rounded-[6px]",
|
|
99
99
|
"outline-none",
|
|
100
|
-
"
|
|
100
|
+
"ps-[8px]",
|
|
101
101
|
className,
|
|
102
102
|
])}
|
|
103
103
|
>
|