torch-glare 2.1.5 → 2.2.0
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 +131 -12
- package/apps/lib/components/ContextMenu.tsx +524 -0
- package/apps/lib/components/DataViews/DataViewsConfigPanel.tsx +1 -1
- package/apps/lib/components/Drawer.tsx +23 -4
- package/apps/lib/components/DropdownMenu.tsx +254 -102
- package/apps/lib/components/SearchableSelect.tsx +308 -0
- package/apps/lib/components/SearchableTable.tsx +363 -0
- package/apps/lib/components/Table.tsx +6 -6
- package/dist/bin/index.js +2 -1
- package/dist/bin/index.js.map +1 -1
- package/docs/components/context-menu.md +455 -0
- package/docs/components/data-views-layout.md +16 -1
- package/docs/components/drawer.md +527 -668
- package/docs/components/dropdown-menu.md +37 -34
- package/docs/components/searchable-select.md +359 -0
- package/docs/components/searchable-table.md +419 -0
- package/docs/reference/tailwind-plugins.md +21 -1
- package/docs/tutorials/getting-started.md +15 -1
- package/package.json +1 -1
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cva, VariantProps } from "class-variance-authority";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
|
|
6
|
+
import { cn } from "../utils/cn";
|
|
7
|
+
import { Themes } from "../utils/types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* ContextMenu — a right-click (or long-press) menu.
|
|
11
|
+
*
|
|
12
|
+
* Same surface as DropdownMenu (items, groups, the boxed look, auto-grouping),
|
|
13
|
+
* built on @radix-ui/react-context-menu so it opens at the pointer on
|
|
14
|
+
* right-click instead of anchoring to a clicked trigger button. The styling and
|
|
15
|
+
* auto-group logic are kept self-contained here (mirrored in DropdownMenu).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
interface ContextMenuContentProps {
|
|
19
|
+
variant?: "PresentationStyle";
|
|
20
|
+
className?: string;
|
|
21
|
+
theme?: Themes;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// A second right-click while the menu is open should simply close it. Radix
|
|
25
|
+
// would otherwise keep it open (re-anchoring is unreliable), so we make the Root
|
|
26
|
+
// controlled, track `open` in context, and let the Trigger close + swallow the
|
|
27
|
+
// event on re-click.
|
|
28
|
+
const ContextMenuOpenContext = React.createContext<{
|
|
29
|
+
open: boolean;
|
|
30
|
+
setOpen: (open: boolean) => void;
|
|
31
|
+
} | null>(null);
|
|
32
|
+
|
|
33
|
+
const ContextMenu = ({
|
|
34
|
+
open: openProp,
|
|
35
|
+
onOpenChange,
|
|
36
|
+
children,
|
|
37
|
+
...props
|
|
38
|
+
}: React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Root>) => {
|
|
39
|
+
const [open, setOpenState] = React.useState(false);
|
|
40
|
+
const isControlled = openProp !== undefined;
|
|
41
|
+
const actualOpen = isControlled ? openProp : open;
|
|
42
|
+
|
|
43
|
+
const setOpen = React.useCallback(
|
|
44
|
+
(next: boolean) => {
|
|
45
|
+
if (!isControlled) setOpenState(next);
|
|
46
|
+
onOpenChange?.(next);
|
|
47
|
+
},
|
|
48
|
+
[isControlled, onOpenChange]
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<ContextMenuOpenContext.Provider value={{ open: actualOpen, setOpen }}>
|
|
53
|
+
<ContextMenuPrimitive.Root
|
|
54
|
+
open={actualOpen}
|
|
55
|
+
onOpenChange={setOpen}
|
|
56
|
+
{...props}
|
|
57
|
+
>
|
|
58
|
+
{children}
|
|
59
|
+
</ContextMenuPrimitive.Root>
|
|
60
|
+
</ContextMenuOpenContext.Provider>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
ContextMenu.displayName = "ContextMenu";
|
|
64
|
+
|
|
65
|
+
// The right-click zone. Wrap it around the target the menu should open from.
|
|
66
|
+
const ContextMenuTrigger = React.forwardRef<
|
|
67
|
+
React.ElementRef<typeof ContextMenuPrimitive.Trigger>,
|
|
68
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Trigger>
|
|
69
|
+
>(({ onContextMenuCapture, ...props }, ref) => {
|
|
70
|
+
const ctx = React.useContext(ContextMenuOpenContext);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<ContextMenuPrimitive.Trigger
|
|
74
|
+
ref={ref}
|
|
75
|
+
// Capture phase runs before Radix's bubble-phase open handler. If the menu
|
|
76
|
+
// is already open, close it and swallow the event so Radix doesn't reopen
|
|
77
|
+
// it — a second right-click just dismisses the menu.
|
|
78
|
+
onContextMenuCapture={(event) => {
|
|
79
|
+
onContextMenuCapture?.(event);
|
|
80
|
+
if (ctx?.open) {
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
event.stopPropagation();
|
|
83
|
+
ctx.setOpen(false);
|
|
84
|
+
}
|
|
85
|
+
}}
|
|
86
|
+
{...props}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
ContextMenuTrigger.displayName = ContextMenuPrimitive.Trigger.displayName;
|
|
91
|
+
|
|
92
|
+
const ContextMenuPortal = ContextMenuPrimitive.Portal;
|
|
93
|
+
|
|
94
|
+
const ContextMenuSub = ContextMenuPrimitive.Sub;
|
|
95
|
+
|
|
96
|
+
const ContextMenuGroup = React.forwardRef<
|
|
97
|
+
React.ElementRef<typeof ContextMenuPrimitive.Group>,
|
|
98
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Group> &
|
|
99
|
+
VariantProps<typeof menuGroupStyles>
|
|
100
|
+
>(({ className, variant = "Boxed", ...props }, ref) => (
|
|
101
|
+
<ContextMenuPrimitive.Group
|
|
102
|
+
ref={ref}
|
|
103
|
+
className={cn(menuGroupStyles({ variant }), className)}
|
|
104
|
+
{...props}
|
|
105
|
+
/>
|
|
106
|
+
));
|
|
107
|
+
ContextMenuGroup.displayName = ContextMenuPrimitive.Group.displayName;
|
|
108
|
+
|
|
109
|
+
const ContextMenuRadioGroup = React.forwardRef<
|
|
110
|
+
React.ElementRef<typeof ContextMenuPrimitive.RadioGroup>,
|
|
111
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioGroup> &
|
|
112
|
+
VariantProps<typeof menuGroupStyles>
|
|
113
|
+
>(({ className, variant = "Boxed", ...props }, ref) => (
|
|
114
|
+
<ContextMenuPrimitive.RadioGroup
|
|
115
|
+
ref={ref}
|
|
116
|
+
className={cn(menuGroupStyles({ variant }), className)}
|
|
117
|
+
{...props}
|
|
118
|
+
/>
|
|
119
|
+
));
|
|
120
|
+
ContextMenuRadioGroup.displayName = ContextMenuPrimitive.RadioGroup.displayName;
|
|
121
|
+
|
|
122
|
+
const ContextMenuContent = React.forwardRef<
|
|
123
|
+
React.ElementRef<typeof ContextMenuPrimitive.Content>,
|
|
124
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content> &
|
|
125
|
+
ContextMenuContentProps & { autoGroup?: boolean }
|
|
126
|
+
>(
|
|
127
|
+
(
|
|
128
|
+
{
|
|
129
|
+
theme,
|
|
130
|
+
className,
|
|
131
|
+
variant = "PresentationStyle",
|
|
132
|
+
autoGroup = true,
|
|
133
|
+
collisionPadding = 8,
|
|
134
|
+
children,
|
|
135
|
+
...props
|
|
136
|
+
},
|
|
137
|
+
ref
|
|
138
|
+
) => (
|
|
139
|
+
<ContextMenuPrimitive.Portal>
|
|
140
|
+
<ContextMenuPrimitive.Content
|
|
141
|
+
data-theme={theme}
|
|
142
|
+
ref={ref}
|
|
143
|
+
collisionPadding={collisionPadding}
|
|
144
|
+
className={cn(
|
|
145
|
+
menuContentStyles({ variant }),
|
|
146
|
+
// Cap to the space Radix has after collision handling so a tall menu
|
|
147
|
+
// scrolls instead of overflowing off-screen.
|
|
148
|
+
"max-h-[var(--radix-context-menu-content-available-height)]",
|
|
149
|
+
className
|
|
150
|
+
)}
|
|
151
|
+
{...props}
|
|
152
|
+
>
|
|
153
|
+
{autoGroup ? autoGroupChildren(children) : children}
|
|
154
|
+
</ContextMenuPrimitive.Content>
|
|
155
|
+
</ContextMenuPrimitive.Portal>
|
|
156
|
+
)
|
|
157
|
+
);
|
|
158
|
+
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
|
|
159
|
+
|
|
160
|
+
const ContextMenuSubTrigger = React.forwardRef<
|
|
161
|
+
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
|
|
162
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
|
|
163
|
+
inset?: boolean;
|
|
164
|
+
} & VariantProps<typeof MenuItemStyles>
|
|
165
|
+
>(
|
|
166
|
+
({ className, inset, children, variant = "Default", size = "M", ...props }, ref) => (
|
|
167
|
+
<ContextMenuPrimitive.SubTrigger
|
|
168
|
+
ref={ref}
|
|
169
|
+
className={cn(MenuItemStyles({ variant, size }), "justify-between", className)}
|
|
170
|
+
{...props}
|
|
171
|
+
>
|
|
172
|
+
<div className="justify-between">
|
|
173
|
+
<div className="flex gap-2">{children}</div>
|
|
174
|
+
<i className="ri-arrow-right-s-line text-[16px] rtl:rotate-180"></i>
|
|
175
|
+
</div>
|
|
176
|
+
</ContextMenuPrimitive.SubTrigger>
|
|
177
|
+
)
|
|
178
|
+
);
|
|
179
|
+
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
|
|
180
|
+
|
|
181
|
+
const ContextMenuSubContent = React.forwardRef<
|
|
182
|
+
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
|
|
183
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent> & {
|
|
184
|
+
variant?: "PresentationStyle";
|
|
185
|
+
autoGroup?: boolean;
|
|
186
|
+
}
|
|
187
|
+
>(
|
|
188
|
+
({ className, variant = "PresentationStyle", autoGroup = true, children, ...props }, ref) => (
|
|
189
|
+
<ContextMenuPrimitive.Portal>
|
|
190
|
+
<ContextMenuPrimitive.SubContent
|
|
191
|
+
ref={ref}
|
|
192
|
+
className={cn(menuContentStyles({ variant }), className)}
|
|
193
|
+
{...props}
|
|
194
|
+
>
|
|
195
|
+
{autoGroup ? autoGroupChildren(children) : children}
|
|
196
|
+
</ContextMenuPrimitive.SubContent>
|
|
197
|
+
</ContextMenuPrimitive.Portal>
|
|
198
|
+
)
|
|
199
|
+
);
|
|
200
|
+
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
|
|
201
|
+
|
|
202
|
+
const ContextMenuItem = React.forwardRef<
|
|
203
|
+
React.ElementRef<typeof ContextMenuPrimitive.Item>,
|
|
204
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
|
|
205
|
+
inset?: boolean;
|
|
206
|
+
} & VariantProps<typeof MenuItemStyles>
|
|
207
|
+
>(
|
|
208
|
+
({ className, inset, children, variant = "Default", size = "M", active, ...props }, ref) => (
|
|
209
|
+
<ContextMenuPrimitive.Item
|
|
210
|
+
{...props}
|
|
211
|
+
ref={ref}
|
|
212
|
+
className={cn(MenuItemStyles({ variant, size, active }), className)}
|
|
213
|
+
>
|
|
214
|
+
<div>{children}</div>
|
|
215
|
+
</ContextMenuPrimitive.Item>
|
|
216
|
+
)
|
|
217
|
+
);
|
|
218
|
+
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
|
|
219
|
+
|
|
220
|
+
const ContextMenuCheckboxItem = React.forwardRef<
|
|
221
|
+
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
|
|
222
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem> &
|
|
223
|
+
VariantProps<typeof MenuItemStyles>
|
|
224
|
+
>(
|
|
225
|
+
({ className, children, checked, variant = "Default", size = "M", onSelect, ...props }, ref) => (
|
|
226
|
+
<ContextMenuPrimitive.CheckboxItem
|
|
227
|
+
ref={ref}
|
|
228
|
+
className={cn(MenuItemStyles({ variant, size }), "relative", className)}
|
|
229
|
+
checked={checked}
|
|
230
|
+
// Keep the menu open when toggling; preventDefault stops Radix's auto-close.
|
|
231
|
+
onSelect={(event) => {
|
|
232
|
+
event.preventDefault();
|
|
233
|
+
onSelect?.(event);
|
|
234
|
+
}}
|
|
235
|
+
{...props}
|
|
236
|
+
>
|
|
237
|
+
<div className="relative flex items-center">
|
|
238
|
+
<span className="h-full flex items-center justify-center">
|
|
239
|
+
{/* Unchecked box; hidden once the item is checked. */}
|
|
240
|
+
<div className="flex justify-center items-center h-full [[data-state=checked]_&]:hidden">
|
|
241
|
+
<div className="w-[16px] h-[16px] rounded-[3px] border border-white-alpha-40 bg-black-alpha-15 group-hover:border-white-700 group-hover:bg-black-alpha-075"></div>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
{/* Checked indicator only renders when checked. */}
|
|
245
|
+
<ContextMenuPrimitive.ItemIndicator>
|
|
246
|
+
<div className="bg-blue-sparkle-600 flex justify-center items-center w-[16px] h-[16px] rounded-[3px]">
|
|
247
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="none">
|
|
248
|
+
<path d="M5.8339 8.84977L11.1961 3.48755L12.0211 4.3125L5.8339 10.4997L2.12158 6.7874L2.94654 5.96245L5.8339 8.84977Z" fill="#F9F9F9" />
|
|
249
|
+
</svg>
|
|
250
|
+
</div>
|
|
251
|
+
</ContextMenuPrimitive.ItemIndicator>
|
|
252
|
+
</span>{" "}
|
|
253
|
+
{children}
|
|
254
|
+
</div>
|
|
255
|
+
</ContextMenuPrimitive.CheckboxItem>
|
|
256
|
+
)
|
|
257
|
+
);
|
|
258
|
+
ContextMenuCheckboxItem.displayName = ContextMenuPrimitive.CheckboxItem.displayName;
|
|
259
|
+
|
|
260
|
+
const ContextMenuRadioItem = React.forwardRef<
|
|
261
|
+
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
|
|
262
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem> &
|
|
263
|
+
VariantProps<typeof MenuItemStyles>
|
|
264
|
+
>(({ className, children, variant = "Default", size = "M", onSelect, ...props }, ref) => (
|
|
265
|
+
<ContextMenuPrimitive.RadioItem
|
|
266
|
+
ref={ref}
|
|
267
|
+
className={cn(MenuItemStyles({ variant, size }), "relative", className)}
|
|
268
|
+
// Keep the menu open when selecting; preventDefault stops Radix's auto-close.
|
|
269
|
+
onSelect={(event) => {
|
|
270
|
+
event.preventDefault();
|
|
271
|
+
onSelect?.(event);
|
|
272
|
+
}}
|
|
273
|
+
{...props}
|
|
274
|
+
>
|
|
275
|
+
<div className="relative flex items-center">
|
|
276
|
+
<span className="h-full left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
277
|
+
{/* Unselected dot; hidden once the item is selected. */}
|
|
278
|
+
<div className="flex justify-center items-center h-full [[data-state=checked]_&]:hidden">
|
|
279
|
+
<div className="w-[14px] h-[14px] rounded-[100px] border border-white-alpha-40 bg-black-alpha-15 group-hover:border-white-700 group-hover:bg-black-alpha-075"></div>
|
|
280
|
+
</div>
|
|
281
|
+
<ContextMenuPrimitive.ItemIndicator>
|
|
282
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="16" viewBox="0 0 14 14" fill="none">
|
|
283
|
+
<rect width="14" height="14" rx="7" fill="#005ECC" />
|
|
284
|
+
<rect x="5" y="5" width="4" height="4" rx="2" fill="white" />
|
|
285
|
+
</svg>
|
|
286
|
+
</ContextMenuPrimitive.ItemIndicator>
|
|
287
|
+
</span>
|
|
288
|
+
{children}
|
|
289
|
+
</div>
|
|
290
|
+
</ContextMenuPrimitive.RadioItem>
|
|
291
|
+
));
|
|
292
|
+
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
|
|
293
|
+
|
|
294
|
+
// Item types that should be boxed together when sitting loose in the menu.
|
|
295
|
+
// ContextMenuSub is included because its trigger renders as an inline row.
|
|
296
|
+
const GROUPABLE_TYPES = [
|
|
297
|
+
ContextMenuItem,
|
|
298
|
+
ContextMenuCheckboxItem,
|
|
299
|
+
ContextMenuRadioItem,
|
|
300
|
+
ContextMenuSub,
|
|
301
|
+
] as const;
|
|
302
|
+
|
|
303
|
+
const isGroupable = (child: React.ReactNode): child is React.ReactElement =>
|
|
304
|
+
React.isValidElement(child) &&
|
|
305
|
+
(GROUPABLE_TYPES as readonly React.ElementType[]).includes(
|
|
306
|
+
child.type as React.ElementType
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
// Wraps consecutive runs of loose items in a Boxed ContextMenuGroup so items
|
|
310
|
+
// render inside a container even when the consumer doesn't write one. Labels,
|
|
311
|
+
// separators and explicit groups act as boundaries and pass through unchanged.
|
|
312
|
+
function autoGroupChildren(children: React.ReactNode): React.ReactNode {
|
|
313
|
+
const out: React.ReactNode[] = [];
|
|
314
|
+
let run: React.ReactElement[] = [];
|
|
315
|
+
|
|
316
|
+
const flush = (key: string) => {
|
|
317
|
+
if (run.length === 0) return;
|
|
318
|
+
out.push(
|
|
319
|
+
<ContextMenuGroup key={key} variant="Boxed">
|
|
320
|
+
{run}
|
|
321
|
+
</ContextMenuGroup>
|
|
322
|
+
);
|
|
323
|
+
run = [];
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
React.Children.toArray(children).forEach((child, index) => {
|
|
327
|
+
if (isGroupable(child)) {
|
|
328
|
+
run.push(child);
|
|
329
|
+
} else {
|
|
330
|
+
flush(`auto-group-${index}`);
|
|
331
|
+
out.push(child);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
flush("auto-group-last");
|
|
335
|
+
|
|
336
|
+
return out;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const ContextMenuLabel = React.forwardRef<
|
|
340
|
+
React.ElementRef<typeof ContextMenuPrimitive.Label>,
|
|
341
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
|
|
342
|
+
inset?: boolean;
|
|
343
|
+
}
|
|
344
|
+
>(({ className, inset, ...props }, ref) => (
|
|
345
|
+
<ContextMenuPrimitive.Label
|
|
346
|
+
ref={ref}
|
|
347
|
+
className={cn(
|
|
348
|
+
"text-content-presentation-global-primary-light typography-body-small-medium px-[12px] pt-1 flex justify-start items-center",
|
|
349
|
+
className
|
|
350
|
+
)}
|
|
351
|
+
{...props}
|
|
352
|
+
/>
|
|
353
|
+
));
|
|
354
|
+
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
|
|
355
|
+
|
|
356
|
+
const ContextMenuShortcut = ({
|
|
357
|
+
className,
|
|
358
|
+
...props
|
|
359
|
+
}: React.HTMLAttributes<HTMLSpanElement>) => (
|
|
360
|
+
<span
|
|
361
|
+
className={cn("ml-auto ltr:ml-0 rtl:mr-auto text-xs tracking-widest opacity-60", className)}
|
|
362
|
+
{...props}
|
|
363
|
+
/>
|
|
364
|
+
);
|
|
365
|
+
ContextMenuShortcut.displayName = "ContextMenuShortcut";
|
|
366
|
+
|
|
367
|
+
export {
|
|
368
|
+
ContextMenu,
|
|
369
|
+
ContextMenuTrigger,
|
|
370
|
+
ContextMenuContent,
|
|
371
|
+
ContextMenuItem,
|
|
372
|
+
ContextMenuGroup,
|
|
373
|
+
ContextMenuPortal,
|
|
374
|
+
ContextMenuSub,
|
|
375
|
+
ContextMenuSubTrigger,
|
|
376
|
+
ContextMenuSubContent,
|
|
377
|
+
ContextMenuRadioGroup,
|
|
378
|
+
ContextMenuCheckboxItem,
|
|
379
|
+
ContextMenuRadioItem,
|
|
380
|
+
ContextMenuLabel,
|
|
381
|
+
ContextMenuShortcut,
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const MenuItemStyles = cva(
|
|
385
|
+
[
|
|
386
|
+
"text-content-presentation-global-primary-light typography-body-medium-regular",
|
|
387
|
+
"outline-none",
|
|
388
|
+
"border",
|
|
389
|
+
"border-transparent",
|
|
390
|
+
"flex",
|
|
391
|
+
"items-center",
|
|
392
|
+
"justify-start",
|
|
393
|
+
"text-overflow",
|
|
394
|
+
"overflow-hidden",
|
|
395
|
+
"p-[2px]",
|
|
396
|
+
"transition-all",
|
|
397
|
+
"bg-[rgba(184,192,204,0.36)]",
|
|
398
|
+
"ease-in-out",
|
|
399
|
+
"duration-300",
|
|
400
|
+
"[&>div]:flex",
|
|
401
|
+
"[&>div]:px-[12px]",
|
|
402
|
+
"[&>div]:py-[4px]",
|
|
403
|
+
"[&>div]:gap-2",
|
|
404
|
+
"[&>div]:w-full",
|
|
405
|
+
"[&>div]:rounded-[8px]",
|
|
406
|
+
"[&>div]:items-center ",
|
|
407
|
+
"group",
|
|
408
|
+
],
|
|
409
|
+
{
|
|
410
|
+
variants: {
|
|
411
|
+
variant: {
|
|
412
|
+
Default: [
|
|
413
|
+
"text-content-presentation-global-primary-light",
|
|
414
|
+
"[&>div]:hover:bg-white-50 [&>div]:hover:shadow-[0_0_16px_0_rgba(0,0,0,0.36)]",
|
|
415
|
+
"[&>div]:hover:text-black-1000",
|
|
416
|
+
"[&[data-highlighted]>div]:bg-white-alpha-75",
|
|
417
|
+
"[&[data-highlighted]>div]:text-black-1000",
|
|
418
|
+
"[&[data-disabled]>div]:text-content-presentation-global-primary-light",
|
|
419
|
+
"[&[data-disabled]>div]:opacity-50",
|
|
420
|
+
// Disabled items are pointer-events:none by default (Radix), so
|
|
421
|
+
// re-enable them to allow hover styling without making them selectable.
|
|
422
|
+
"[&[data-disabled]]:pointer-events-auto",
|
|
423
|
+
"[&[data-disabled]>div]:hover:text-content-presentation-global-primary-light",
|
|
424
|
+
"[&[data-disabled]>div]:hover:bg-transparent",
|
|
425
|
+
"[&[data-disabled]>div]:hover:shadow-none",
|
|
426
|
+
],
|
|
427
|
+
info: [
|
|
428
|
+
"text-blue-sparkle-200",
|
|
429
|
+
"[&>div]:hover:bg-white-50 [&>div]:hover:shadow-[0_0_16px_0_rgba(0,0,0,0.36)]",
|
|
430
|
+
"[&>div]:hover:text-blue-sparkle-700",
|
|
431
|
+
"[&[data-highlighted]>div]:bg-white-alpha-75",
|
|
432
|
+
"[&[data-highlighted]>div]:text-blue-sparkle-700",
|
|
433
|
+
"[&[data-disabled]>div]:text-content-presentation-global-primary-light",
|
|
434
|
+
"[&[data-disabled]>div]:opacity-50",
|
|
435
|
+
"[&[data-disabled]]:pointer-events-auto",
|
|
436
|
+
"[&[data-disabled]>div]:hover:text-content-presentation-global-primary-light",
|
|
437
|
+
"[&[data-disabled]>div]:hover:bg-transparent",
|
|
438
|
+
"[&[data-disabled]>div]:hover:shadow-none",
|
|
439
|
+
],
|
|
440
|
+
Negative: [
|
|
441
|
+
"text-medium-red-200",
|
|
442
|
+
"[&>div]:hover:bg-white-50 [&>div]:hover:shadow-[0_0_16px_0_rgba(0,0,0,0.36)]",
|
|
443
|
+
"[&>div]:hover:text-medium-red-600",
|
|
444
|
+
"[&[data-highlighted]>div]:bg-white-alpha-75",
|
|
445
|
+
"[&[data-highlighted]>div]:text-medium-red-600",
|
|
446
|
+
"[&[data-disabled]>div]:text-content-presentation-global-primary-light",
|
|
447
|
+
"[&[data-disabled]>div]:opacity-50",
|
|
448
|
+
"[&[data-disabled]]:pointer-events-auto",
|
|
449
|
+
"[&[data-disabled]>div]:hover:text-content-presentation-global-primary-light",
|
|
450
|
+
"[&[data-disabled]>div]:hover:bg-transparent",
|
|
451
|
+
"[&[data-disabled]>div]:hover:shadow-none",
|
|
452
|
+
],
|
|
453
|
+
},
|
|
454
|
+
size: {
|
|
455
|
+
S: ["typography-body-small-regular", "h-[24px]"],
|
|
456
|
+
M: ["typography-body-medium-regular", "h-[32px]"],
|
|
457
|
+
},
|
|
458
|
+
active: {
|
|
459
|
+
true: [
|
|
460
|
+
"bg-background-presentation-action-selected",
|
|
461
|
+
"text-content-presentation-action-light-primary",
|
|
462
|
+
],
|
|
463
|
+
},
|
|
464
|
+
defaultVariants: {
|
|
465
|
+
variant: "Default",
|
|
466
|
+
size: "M",
|
|
467
|
+
active: false,
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
compoundVariants: [
|
|
471
|
+
{
|
|
472
|
+
active: true,
|
|
473
|
+
variant: "info",
|
|
474
|
+
className: ["text-content-presentation-state-negative"],
|
|
475
|
+
},
|
|
476
|
+
],
|
|
477
|
+
}
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
const menuContentStyles = cva(
|
|
481
|
+
[
|
|
482
|
+
"p-1",
|
|
483
|
+
"rounded-[14px]",
|
|
484
|
+
"min-w-[240px]",
|
|
485
|
+
"outline-none",
|
|
486
|
+
"overflow-scroll",
|
|
487
|
+
// Only animate the OPEN (enter) state. An exit animation on [data-state=closed]
|
|
488
|
+
// holds the old DOM node during close, which breaks close/reposition on a
|
|
489
|
+
// second right-click (Radix issue #2572).
|
|
490
|
+
"data-[state=open]:animate-in",
|
|
491
|
+
"data-[state=open]:fade-in-0",
|
|
492
|
+
"overflow-x-hidden",
|
|
493
|
+
"scrollbar-hide",
|
|
494
|
+
"backdrop-blur-[21px]",
|
|
495
|
+
"flex gap-1 flex-col",
|
|
496
|
+
],
|
|
497
|
+
{
|
|
498
|
+
variants: {
|
|
499
|
+
variant: {
|
|
500
|
+
PresentationStyle: [
|
|
501
|
+
"bg-[rgba(61,64,69,0.72)]",
|
|
502
|
+
"shadow-[0_0_32px_2px_rgba(0,0,0,0.20),0_0_48px_2px_rgba(0,0,0,0.05)]",
|
|
503
|
+
],
|
|
504
|
+
},
|
|
505
|
+
defaultVariants: {
|
|
506
|
+
variant: "PresentationStyle",
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
}
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
const menuGroupStyles = cva(["flex", "flex-col"], {
|
|
513
|
+
variants: {
|
|
514
|
+
variant: {
|
|
515
|
+
// Visually contains its items in a bordered card.
|
|
516
|
+
Boxed: ["gap-[1px]", "rounded-[10px]", "bg-bldue-500", "overflow-hidden"],
|
|
517
|
+
// No container — semantic grouping only (Radix default behavior).
|
|
518
|
+
Plain: [],
|
|
519
|
+
},
|
|
520
|
+
},
|
|
521
|
+
defaultVariants: {
|
|
522
|
+
variant: "Boxed",
|
|
523
|
+
},
|
|
524
|
+
});
|
|
@@ -114,6 +114,12 @@ const DrawerContent = React.forwardRef<
|
|
|
114
114
|
)}
|
|
115
115
|
>
|
|
116
116
|
<div
|
|
117
|
+
// The content surface is always light (#F0F0F0) regardless of the
|
|
118
|
+
// page theme, so pin a light theme here. This makes theme-aware
|
|
119
|
+
// content tokens (DrawerTitle/Description and any consumer content)
|
|
120
|
+
// resolve to their dark-on-light values instead of following a dark
|
|
121
|
+
// page theme (which would render white-on-light = invisible).
|
|
122
|
+
data-theme="light"
|
|
117
123
|
className={cn(
|
|
118
124
|
"flex flex-1 flex-col gap-2 rounded-t-[16px] p-1.5 bg-[#F0F0F0] min-h-0",
|
|
119
125
|
framed && "border border-[#D4D4D4] shadow-[inset_0_-4px_16px_rgba(0,0,0,0.1)]",
|
|
@@ -148,14 +154,18 @@ const DrawerHeader = ({
|
|
|
148
154
|
DrawerHeader.displayName = "DrawerHeader";
|
|
149
155
|
|
|
150
156
|
const drawerHeaderPane = cva(
|
|
151
|
-
|
|
157
|
+
// Dark pill. Force any DrawerTitle/Description inside it back to light text
|
|
158
|
+
// (their defaults are dark for the light content surface).
|
|
159
|
+
"flex items-center gap-2 rounded-[14px] border p-2 bg-[#131415] border-[#2C2D2E] shadow-[0_0_32px_2px_rgba(0,0,0,0.05)] [&_[data-slot=drawer-title]]:text-white [&_[data-slot=drawer-description]]:text-[#9FA0A1]",
|
|
152
160
|
);
|
|
153
161
|
|
|
154
162
|
const DrawerHeaderTitle = ({
|
|
155
163
|
className,
|
|
156
164
|
...props
|
|
157
165
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
158
|
-
|
|
166
|
+
// Dark surface — pin a dark theme so any buttons/content inside resolve their
|
|
167
|
+
// theme tokens for dark (the surrounding content surface is data-theme=light).
|
|
168
|
+
<div data-theme="dark" className={cn(drawerHeaderPane(), className)} {...props} />
|
|
159
169
|
);
|
|
160
170
|
DrawerHeaderTitle.displayName = "DrawerHeaderTitle";
|
|
161
171
|
|
|
@@ -164,6 +174,7 @@ const DrawerHeaderActions = ({
|
|
|
164
174
|
...props
|
|
165
175
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
166
176
|
<div
|
|
177
|
+
data-theme="dark"
|
|
167
178
|
className={cn(drawerHeaderPane(), "justify-end", className)}
|
|
168
179
|
{...props}
|
|
169
180
|
/>
|
|
@@ -358,8 +369,12 @@ const DrawerTitle = React.forwardRef<
|
|
|
358
369
|
>(({ className, ...props }, ref) => (
|
|
359
370
|
<DrawerPrimitive.Title
|
|
360
371
|
ref={ref}
|
|
372
|
+
data-slot="drawer-title"
|
|
373
|
+
// Dark text by default — the title usually sits on the light content
|
|
374
|
+
// surface. Inside the dark DrawerHeaderTitle pill it is flipped back to
|
|
375
|
+
// white via a descendant selector on `drawerHeaderPane`.
|
|
361
376
|
className={cn(
|
|
362
|
-
"typography-display-medium-medium uppercase text-
|
|
377
|
+
"typography-display-medium-medium uppercase text-content-presentation-global-primary leading-none",
|
|
363
378
|
className,
|
|
364
379
|
)}
|
|
365
380
|
{...props}
|
|
@@ -373,7 +388,11 @@ const DrawerDescription = React.forwardRef<
|
|
|
373
388
|
>(({ className, ...props }, ref) => (
|
|
374
389
|
<DrawerPrimitive.Description
|
|
375
390
|
ref={ref}
|
|
376
|
-
|
|
391
|
+
data-slot="drawer-description"
|
|
392
|
+
className={cn(
|
|
393
|
+
"typography-body-small-regular text-content-presentation-action-light-secondary",
|
|
394
|
+
className,
|
|
395
|
+
)}
|
|
377
396
|
{...props}
|
|
378
397
|
/>
|
|
379
398
|
));
|