@turtleclub/ui 0.7.0-beta.1 → 0.7.0-beta.10
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/.turbo/turbo-build.log +122 -122
- package/CHANGELOG.md +46 -0
- package/dist/index.cjs +23 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +6622 -6581
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/components/features/data-table/data-table.d.ts.map +1 -1
- package/dist/types/components/features/data-table/sort-dropdown.d.ts.map +1 -1
- package/dist/types/components/molecules/swap-input.d.ts +2 -0
- package/dist/types/components/molecules/swap-input.d.ts.map +1 -1
- package/dist/types/components/molecules/token-selector.d.ts.map +1 -1
- package/dist/types/components/ui/avatar.d.ts +2 -2
- package/dist/types/components/ui/avatar.d.ts.map +1 -1
- package/dist/types/components/ui/combobox.d.ts +21 -0
- package/dist/types/components/ui/combobox.d.ts.map +1 -1
- package/dist/types/components/ui/dialog.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/components/features/data-table/data-table.tsx +120 -114
- package/src/components/features/data-table/sort-dropdown.tsx +8 -11
- package/src/components/molecules/swap-input.tsx +41 -30
- package/src/components/molecules/token-selector.tsx +7 -1
- package/src/components/ui/avatar.tsx +8 -15
- package/src/components/ui/combobox.tsx +121 -40
- package/src/components/ui/dialog.tsx +9 -23
- package/src/components/ui/tooltip.tsx +1 -1
|
@@ -6,8 +6,10 @@ import { Chip } from "@/components/ui/chip";
|
|
|
6
6
|
import { TokenSelector, type Token } from "./token-selector";
|
|
7
7
|
import { BaseSelector } from "./widget/base-selector";
|
|
8
8
|
|
|
9
|
-
interface SwapInputProps
|
|
10
|
-
|
|
9
|
+
interface SwapInputProps extends Omit<
|
|
10
|
+
React.HTMLAttributes<HTMLDivElement>,
|
|
11
|
+
"onChange"
|
|
12
|
+
> {
|
|
11
13
|
value?: string;
|
|
12
14
|
tokens: Token[];
|
|
13
15
|
selectedToken?: string;
|
|
@@ -24,6 +26,8 @@ interface SwapInputProps
|
|
|
24
26
|
useCustomTokenSelector?: boolean;
|
|
25
27
|
selectedTokenData?: Token | null;
|
|
26
28
|
onCustomTokenSelectorClick?: () => void;
|
|
29
|
+
showTokenSelectorBalance?: boolean;
|
|
30
|
+
showInputBalance?: boolean;
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
const SwapInput = React.forwardRef<HTMLDivElement, SwapInputProps>(
|
|
@@ -46,6 +50,8 @@ const SwapInput = React.forwardRef<HTMLDivElement, SwapInputProps>(
|
|
|
46
50
|
useCustomTokenSelector = false,
|
|
47
51
|
selectedTokenData,
|
|
48
52
|
onCustomTokenSelectorClick,
|
|
53
|
+
showTokenSelectorBalance = true,
|
|
54
|
+
showInputBalance = true,
|
|
49
55
|
...props
|
|
50
56
|
},
|
|
51
57
|
ref,
|
|
@@ -127,40 +133,45 @@ const SwapInput = React.forwardRef<HTMLDivElement, SwapInputProps>(
|
|
|
127
133
|
size="sm"
|
|
128
134
|
variant="default"
|
|
129
135
|
className="rounded-full"
|
|
136
|
+
showBalance={showTokenSelectorBalance}
|
|
130
137
|
/>
|
|
131
138
|
))}
|
|
132
139
|
</div>
|
|
133
140
|
|
|
134
141
|
{/* Bottom row: USD value and balance with MAX */}
|
|
135
|
-
|
|
136
|
-
<div>
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
+
{showInputBalance && (
|
|
143
|
+
<div className="text-muted-foreground flex items-center justify-between text-sm">
|
|
144
|
+
<div>
|
|
145
|
+
{usdValue ? (
|
|
146
|
+
<span>≈ {usdValue}</span>
|
|
147
|
+
) : value ? (
|
|
148
|
+
<span className="text-destructive/50">
|
|
149
|
+
Insufficient balance
|
|
150
|
+
</span>
|
|
151
|
+
) : null}
|
|
152
|
+
</div>
|
|
153
|
+
<div className="flex items-center gap-2">
|
|
154
|
+
{balance && (
|
|
155
|
+
<span className="text-sm">
|
|
156
|
+
Balance: {balance} {currentToken?.symbol || ""}
|
|
157
|
+
</span>
|
|
158
|
+
)}
|
|
159
|
+
{onMaxClick && (
|
|
160
|
+
<Chip
|
|
161
|
+
variant="default"
|
|
162
|
+
size="xs"
|
|
163
|
+
onClick={onMaxClick}
|
|
164
|
+
className={cn(
|
|
165
|
+
"hover:bg-primary hover:text-primary-foreground h-5 cursor-pointer px-2 py-0.5 text-xs transition-colors",
|
|
166
|
+
disabled && "cursor-not-allowed opacity-50",
|
|
167
|
+
)}
|
|
168
|
+
>
|
|
169
|
+
MAX
|
|
170
|
+
</Chip>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
142
173
|
</div>
|
|
143
|
-
|
|
144
|
-
{balance && (
|
|
145
|
-
<span className="text-sm">
|
|
146
|
-
Balance: {balance} {currentToken?.symbol || ""}
|
|
147
|
-
</span>
|
|
148
|
-
)}
|
|
149
|
-
{onMaxClick && (
|
|
150
|
-
<Chip
|
|
151
|
-
variant="default"
|
|
152
|
-
size="xs"
|
|
153
|
-
onClick={onMaxClick}
|
|
154
|
-
className={cn(
|
|
155
|
-
"hover:bg-primary hover:text-primary-foreground h-5 cursor-pointer px-2 py-0.5 text-xs transition-colors",
|
|
156
|
-
disabled && "cursor-not-allowed opacity-50",
|
|
157
|
-
)}
|
|
158
|
-
>
|
|
159
|
-
MAX
|
|
160
|
-
</Chip>
|
|
161
|
-
)}
|
|
162
|
-
</div>
|
|
163
|
-
</div>
|
|
174
|
+
)}
|
|
164
175
|
</Card>
|
|
165
176
|
);
|
|
166
177
|
},
|
|
@@ -54,7 +54,13 @@ const TokenSelector = ({
|
|
|
54
54
|
const getTokenIcon = (token: Token) => {
|
|
55
55
|
if (token.icon) return token.icon;
|
|
56
56
|
if (token.logoUrl) {
|
|
57
|
-
return
|
|
57
|
+
return (
|
|
58
|
+
<img
|
|
59
|
+
src={token.logoUrl}
|
|
60
|
+
alt={token.symbol}
|
|
61
|
+
className="size-full rounded-full"
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
58
64
|
}
|
|
59
65
|
return null;
|
|
60
66
|
};
|
|
@@ -21,19 +21,13 @@ function Avatar({
|
|
|
21
21
|
return (
|
|
22
22
|
<AvatarPrimitive.Root
|
|
23
23
|
data-slot="avatar"
|
|
24
|
-
className={cn(
|
|
25
|
-
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
|
|
26
|
-
className,
|
|
27
|
-
)}
|
|
24
|
+
className={cn("relative flex size-8 shrink-0 overflow-hidden rounded-full", className)}
|
|
28
25
|
{...props}
|
|
29
26
|
/>
|
|
30
27
|
);
|
|
31
28
|
}
|
|
32
29
|
|
|
33
|
-
function AvatarImage({
|
|
34
|
-
className,
|
|
35
|
-
...props
|
|
36
|
-
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
|
30
|
+
function AvatarImage({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
|
37
31
|
return (
|
|
38
32
|
<AvatarPrimitive.Image
|
|
39
33
|
data-slot="avatar-image"
|
|
@@ -50,10 +44,7 @@ function AvatarFallback({
|
|
|
50
44
|
return (
|
|
51
45
|
<AvatarPrimitive.Fallback
|
|
52
46
|
data-slot="avatar-fallback"
|
|
53
|
-
className={cn(
|
|
54
|
-
"bg-muted flex size-full items-center justify-center rounded-full",
|
|
55
|
-
className,
|
|
56
|
-
)}
|
|
47
|
+
className={cn("bg-muted flex size-full items-center justify-center rounded-full", className)}
|
|
57
48
|
{...props}
|
|
58
49
|
/>
|
|
59
50
|
);
|
|
@@ -65,14 +56,16 @@ function TurtleAvatar({
|
|
|
65
56
|
fallback,
|
|
66
57
|
...rest
|
|
67
58
|
}: AvatarPrimitive.AvatarProps & {
|
|
68
|
-
src
|
|
69
|
-
alt
|
|
59
|
+
src?: string;
|
|
60
|
+
alt?: string;
|
|
70
61
|
fallback?: React.ReactNode;
|
|
71
62
|
}) {
|
|
72
63
|
return (
|
|
73
64
|
<Avatar {...rest}>
|
|
74
65
|
<AvatarImage src={src} alt={alt} />
|
|
75
|
-
<AvatarFallback>
|
|
66
|
+
<AvatarFallback className="border-gradient-primary bg-primary/10 opacity-50">
|
|
67
|
+
{fallback}
|
|
68
|
+
</AvatarFallback>
|
|
76
69
|
</Avatar>
|
|
77
70
|
);
|
|
78
71
|
}
|
|
@@ -12,7 +12,11 @@ import {
|
|
|
12
12
|
CommandItem,
|
|
13
13
|
CommandList,
|
|
14
14
|
} from "@/components/ui/command";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
Popover,
|
|
17
|
+
PopoverContent,
|
|
18
|
+
PopoverTrigger,
|
|
19
|
+
} from "@/components/ui/popover";
|
|
16
20
|
import { Separator } from "@/components/ui/separator";
|
|
17
21
|
import { cn } from "@/lib/utils";
|
|
18
22
|
|
|
@@ -85,20 +89,43 @@ interface ComboboxOption<T = string> {
|
|
|
85
89
|
* />
|
|
86
90
|
*/
|
|
87
91
|
interface ComboboxProps<T = string>
|
|
88
|
-
extends Omit<
|
|
92
|
+
extends Omit<
|
|
93
|
+
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
94
|
+
"animationConfig" | "defaultValue"
|
|
95
|
+
> {
|
|
89
96
|
/**
|
|
90
97
|
* An array of option objects or groups to be displayed in the multi-select component.
|
|
91
98
|
*/
|
|
92
99
|
options: ComboboxOption<T>[];
|
|
100
|
+
|
|
93
101
|
/**
|
|
94
102
|
* Callback function triggered when the selected values change.
|
|
95
103
|
* Receives an array of the new selected values.
|
|
96
104
|
*/
|
|
97
105
|
onValueChange: (value: T) => void;
|
|
98
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Custom renderer for the selected value displayed in the input.
|
|
109
|
+
* @param option The currently selected option object.
|
|
110
|
+
*/
|
|
111
|
+
renderValue?: (option: ComboboxOption<T>) => React.ReactNode;
|
|
112
|
+
|
|
99
113
|
/** The default selected values when the component mounts. */
|
|
100
114
|
defaultValue?: T;
|
|
101
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Callback function triggered when the search input value changes.
|
|
118
|
+
* Useful for server-side filtering or handling large datasets externally.
|
|
119
|
+
*/
|
|
120
|
+
onInputValueChange?: (value: string) => void;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* If true, disables the built-in filtering logic.
|
|
124
|
+
* Use this when you filter options externally (e.g. server-side search).
|
|
125
|
+
* Optional, defaults to false.
|
|
126
|
+
*/
|
|
127
|
+
disableLocalFiltering?: boolean;
|
|
128
|
+
|
|
102
129
|
/**
|
|
103
130
|
* Placeholder text to be displayed when no values are selected.
|
|
104
131
|
* Optional, defaults to "Select options".
|
|
@@ -137,6 +164,12 @@ interface ComboboxProps<T = string>
|
|
|
137
164
|
*/
|
|
138
165
|
searchable?: boolean;
|
|
139
166
|
|
|
167
|
+
/**
|
|
168
|
+
* If true, searching will also check the 'value' field in addition to 'label'.
|
|
169
|
+
* Optional, defaults to false.
|
|
170
|
+
*/
|
|
171
|
+
searchByValue?: boolean;
|
|
172
|
+
|
|
140
173
|
/**
|
|
141
174
|
* Custom empty state message when no options match search.
|
|
142
175
|
* Optional, defaults to "No results found."
|
|
@@ -230,7 +263,10 @@ export interface ComboboxRef {
|
|
|
230
263
|
focus: () => void;
|
|
231
264
|
}
|
|
232
265
|
|
|
233
|
-
const ComboboxComponent = <T = string,>(
|
|
266
|
+
const ComboboxComponent = <T = string,>(
|
|
267
|
+
props: ComboboxProps<T>,
|
|
268
|
+
ref: React.Ref<ComboboxRef>,
|
|
269
|
+
) => {
|
|
234
270
|
const {
|
|
235
271
|
options,
|
|
236
272
|
onValueChange,
|
|
@@ -248,6 +284,10 @@ const ComboboxComponent = <T = string,>(props: ComboboxProps<T>, ref: React.Ref<
|
|
|
248
284
|
minWidth,
|
|
249
285
|
maxWidth,
|
|
250
286
|
closeOnSelect = false,
|
|
287
|
+
renderValue,
|
|
288
|
+
searchByValue = false,
|
|
289
|
+
onInputValueChange,
|
|
290
|
+
disableLocalFiltering = false,
|
|
251
291
|
...restProps
|
|
252
292
|
} = props;
|
|
253
293
|
|
|
@@ -283,7 +323,7 @@ const ComboboxComponent = <T = string,>(props: ComboboxProps<T>, ref: React.Ref<
|
|
|
283
323
|
setTimeout(() => setPoliteMessage(""), 100);
|
|
284
324
|
}
|
|
285
325
|
},
|
|
286
|
-
[]
|
|
326
|
+
[],
|
|
287
327
|
);
|
|
288
328
|
|
|
289
329
|
const multiSelectId = React.useId();
|
|
@@ -324,10 +364,12 @@ const ComboboxComponent = <T = string,>(props: ComboboxProps<T>, ref: React.Ref<
|
|
|
324
364
|
}
|
|
325
365
|
},
|
|
326
366
|
}),
|
|
327
|
-
[resetToDefault,
|
|
367
|
+
[resetToDefault, onValueChange],
|
|
328
368
|
);
|
|
329
369
|
|
|
330
|
-
const [screenSize, setScreenSize] = React.useState<
|
|
370
|
+
const [screenSize, setScreenSize] = React.useState<
|
|
371
|
+
"mobile" | "tablet" | "desktop"
|
|
372
|
+
>("desktop");
|
|
331
373
|
|
|
332
374
|
React.useEffect(() => {
|
|
333
375
|
if (typeof window === "undefined") return;
|
|
@@ -380,42 +422,53 @@ const ComboboxComponent = <T = string,>(props: ComboboxProps<T>, ref: React.Ref<
|
|
|
380
422
|
|
|
381
423
|
const getAllOptions = React.useCallback((): ComboboxOption<T>[] => {
|
|
382
424
|
if (options.length === 0) return [];
|
|
383
|
-
|
|
425
|
+
|
|
384
426
|
const valueSet = new Set<T>();
|
|
385
|
-
const duplicates: T[] = [];
|
|
386
427
|
const uniqueOptions: ComboboxOption<T>[] = [];
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
} else {
|
|
428
|
+
|
|
429
|
+
options.forEach((option) => {
|
|
430
|
+
if (!valueSet.has(option.value)) {
|
|
391
431
|
valueSet.add(option.value);
|
|
392
432
|
uniqueOptions.push(option);
|
|
393
433
|
}
|
|
394
434
|
});
|
|
395
|
-
|
|
435
|
+
|
|
436
|
+
return uniqueOptions;
|
|
396
437
|
}, [options]);
|
|
397
438
|
|
|
398
439
|
const getOptionByValue = React.useCallback(
|
|
399
440
|
(value: T): ComboboxOption<T> | undefined => {
|
|
400
441
|
const option = getAllOptions().find((option) => option.value === value);
|
|
401
442
|
if (!option && process.env.NODE_ENV === "development") {
|
|
402
|
-
console.warn(
|
|
443
|
+
console.warn(
|
|
444
|
+
`Combobox: Option with value "${value}" not found in options list`,
|
|
445
|
+
);
|
|
403
446
|
}
|
|
404
447
|
return option;
|
|
405
448
|
},
|
|
406
|
-
[getAllOptions]
|
|
449
|
+
[getAllOptions],
|
|
407
450
|
);
|
|
408
451
|
|
|
409
452
|
const filteredOptions = React.useMemo(() => {
|
|
453
|
+
if (disableLocalFiltering) return options;
|
|
410
454
|
if (!searchable || !searchValue) return options;
|
|
411
455
|
if (options.length === 0) return [];
|
|
412
456
|
|
|
413
|
-
return options.filter(
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
457
|
+
return options.filter((option) => {
|
|
458
|
+
const labelMatch = option.label
|
|
459
|
+
.toLowerCase()
|
|
460
|
+
.includes(searchValue.toLowerCase());
|
|
461
|
+
if (!searchByValue) return labelMatch;
|
|
462
|
+
|
|
463
|
+
const val = option.value;
|
|
464
|
+
const valueMatch =
|
|
465
|
+
typeof val === "string" || typeof val === "number"
|
|
466
|
+
? String(val).toLowerCase().includes(searchValue.toLowerCase())
|
|
467
|
+
: false;
|
|
468
|
+
|
|
469
|
+
return labelMatch || valueMatch;
|
|
470
|
+
});
|
|
471
|
+
}, [disableLocalFiltering, options, searchable, searchValue, searchByValue]);
|
|
419
472
|
|
|
420
473
|
// const handleInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
421
474
|
// if (event.key === "Enter") {
|
|
@@ -464,7 +517,9 @@ const ComboboxComponent = <T = string,>(props: ComboboxProps<T>, ref: React.Ref<
|
|
|
464
517
|
|
|
465
518
|
if (isPopoverOpen !== prevIsOpen.current) {
|
|
466
519
|
if (isPopoverOpen) {
|
|
467
|
-
announce(
|
|
520
|
+
announce(
|
|
521
|
+
`Dropdown opened. ${totalOptions} options available. Use arrow keys to navigate.`,
|
|
522
|
+
);
|
|
468
523
|
} else {
|
|
469
524
|
announce("Dropdown closed.");
|
|
470
525
|
}
|
|
@@ -476,11 +531,11 @@ const ComboboxComponent = <T = string,>(props: ComboboxProps<T>, ref: React.Ref<
|
|
|
476
531
|
const filteredCount = allOptions.filter(
|
|
477
532
|
(opt) =>
|
|
478
533
|
opt.label.toLowerCase().includes(searchValue.toLowerCase()) ||
|
|
479
|
-
String(opt.value).toLowerCase().includes(searchValue.toLowerCase())
|
|
534
|
+
String(opt.value).toLowerCase().includes(searchValue.toLowerCase()),
|
|
480
535
|
).length;
|
|
481
536
|
|
|
482
537
|
announce(
|
|
483
|
-
`${filteredCount} option${filteredCount === 1 ? "" : "s"} found for "${searchValue}"
|
|
538
|
+
`${filteredCount} option${filteredCount === 1 ? "" : "s"} found for "${searchValue}"`,
|
|
484
539
|
);
|
|
485
540
|
}
|
|
486
541
|
prevSearchValue.current = searchValue;
|
|
@@ -498,9 +553,14 @@ const ComboboxComponent = <T = string,>(props: ComboboxProps<T>, ref: React.Ref<
|
|
|
498
553
|
</div>
|
|
499
554
|
</div>
|
|
500
555
|
|
|
501
|
-
<Popover
|
|
556
|
+
<Popover
|
|
557
|
+
open={isPopoverOpen}
|
|
558
|
+
onOpenChange={setIsPopoverOpen}
|
|
559
|
+
modal={modalPopover}
|
|
560
|
+
>
|
|
502
561
|
<div id={triggerDescriptionId} className="sr-only">
|
|
503
|
-
Multi-select dropdown. Use arrow keys to navigate, Enter to select,
|
|
562
|
+
Multi-select dropdown. Use arrow keys to navigate, Enter to select,
|
|
563
|
+
and Escape to close.
|
|
504
564
|
</div>
|
|
505
565
|
<PopoverTrigger asChild>
|
|
506
566
|
<Button
|
|
@@ -521,7 +581,7 @@ const ComboboxComponent = <T = string,>(props: ComboboxProps<T>, ref: React.Ref<
|
|
|
521
581
|
responsiveSettings.compactMode && "h-8 text-sm",
|
|
522
582
|
screenSize === "mobile" && "h-12 text-base",
|
|
523
583
|
disabled && "cursor-not-allowed opacity-50",
|
|
524
|
-
className
|
|
584
|
+
className,
|
|
525
585
|
)}
|
|
526
586
|
style={{
|
|
527
587
|
...widthConstraints,
|
|
@@ -532,7 +592,14 @@ const ComboboxComponent = <T = string,>(props: ComboboxProps<T>, ref: React.Ref<
|
|
|
532
592
|
<div className="mx-auto flex w-full items-center justify-between">
|
|
533
593
|
<div className="flex items-center gap-2">
|
|
534
594
|
{(() => {
|
|
535
|
-
const selectedOption = options.find(
|
|
595
|
+
const selectedOption = options.find(
|
|
596
|
+
(op) => op.value === selectedValue,
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
if (renderValue && selectedOption) {
|
|
600
|
+
return renderValue(selectedOption);
|
|
601
|
+
}
|
|
602
|
+
|
|
536
603
|
return (
|
|
537
604
|
<>
|
|
538
605
|
{selectedOption?.icon && (
|
|
@@ -541,7 +608,9 @@ const ComboboxComponent = <T = string,>(props: ComboboxProps<T>, ref: React.Ref<
|
|
|
541
608
|
aria-hidden="true"
|
|
542
609
|
/>
|
|
543
610
|
)}
|
|
544
|
-
<span className="text-foreground text-sm">
|
|
611
|
+
<span className="text-foreground text-sm">
|
|
612
|
+
{selectedOption?.label}
|
|
613
|
+
</span>
|
|
545
614
|
</>
|
|
546
615
|
);
|
|
547
616
|
})()}
|
|
@@ -565,7 +634,10 @@ const ComboboxComponent = <T = string,>(props: ComboboxProps<T>, ref: React.Ref<
|
|
|
565
634
|
>
|
|
566
635
|
<XIcon className="size-3.5" />
|
|
567
636
|
</div>
|
|
568
|
-
<Separator
|
|
637
|
+
<Separator
|
|
638
|
+
orientation="vertical"
|
|
639
|
+
className="flex h-full min-h-6"
|
|
640
|
+
/>
|
|
569
641
|
<ChevronDown
|
|
570
642
|
className="text-muted-foreground mx-2 h-4 cursor-pointer"
|
|
571
643
|
aria-hidden="true"
|
|
@@ -574,7 +646,9 @@ const ComboboxComponent = <T = string,>(props: ComboboxProps<T>, ref: React.Ref<
|
|
|
574
646
|
</div>
|
|
575
647
|
) : (
|
|
576
648
|
<div className="mx-auto flex w-full items-center justify-between">
|
|
577
|
-
<span className="text-muted-foreground text-sm">
|
|
649
|
+
<span className="text-muted-foreground text-sm">
|
|
650
|
+
{placeholder}
|
|
651
|
+
</span>
|
|
578
652
|
<ChevronDown className="text-muted-foreground mx-2 h-4 cursor-pointer" />
|
|
579
653
|
</div>
|
|
580
654
|
)}
|
|
@@ -590,7 +664,7 @@ const ComboboxComponent = <T = string,>(props: ComboboxProps<T>, ref: React.Ref<
|
|
|
590
664
|
screenSize === "mobile" && "w-[85vw] max-w-[280px]",
|
|
591
665
|
screenSize === "tablet" && "w-[70vw] max-w-md",
|
|
592
666
|
screenSize === "desktop" && "min-w-[300px]",
|
|
593
|
-
popoverClassName
|
|
667
|
+
popoverClassName,
|
|
594
668
|
)}
|
|
595
669
|
style={{
|
|
596
670
|
maxWidth: `min(${widthConstraints.maxWidth}, 85vw)`,
|
|
@@ -600,13 +674,16 @@ const ComboboxComponent = <T = string,>(props: ComboboxProps<T>, ref: React.Ref<
|
|
|
600
674
|
align="start"
|
|
601
675
|
onEscapeKeyDown={() => setIsPopoverOpen(false)}
|
|
602
676
|
>
|
|
603
|
-
<Command>
|
|
677
|
+
<Command shouldFilter={false}>
|
|
604
678
|
{searchable && (
|
|
605
679
|
<CommandInput
|
|
606
680
|
placeholder="Search options..."
|
|
607
681
|
// onKeyDown={handleInputKeyDown}
|
|
608
682
|
value={searchValue}
|
|
609
|
-
onValueChange={
|
|
683
|
+
onValueChange={(val) => {
|
|
684
|
+
setSearchValue(val);
|
|
685
|
+
onInputValueChange?.(val);
|
|
686
|
+
}}
|
|
610
687
|
aria-label="Search through available options"
|
|
611
688
|
aria-describedby={`${multiSelectId}-search-help`}
|
|
612
689
|
/>
|
|
@@ -620,10 +697,12 @@ const ComboboxComponent = <T = string,>(props: ComboboxProps<T>, ref: React.Ref<
|
|
|
620
697
|
className={cn(
|
|
621
698
|
"multiselect-scrollbar max-h-[40vh] overflow-y-auto",
|
|
622
699
|
screenSize === "mobile" && "max-h-[50vh]",
|
|
623
|
-
"overscroll-behavior-y-contain"
|
|
700
|
+
"overscroll-behavior-y-contain",
|
|
624
701
|
)}
|
|
625
702
|
>
|
|
626
|
-
<CommandEmpty>
|
|
703
|
+
<CommandEmpty>
|
|
704
|
+
{emptyIndicator || "No results found."}
|
|
705
|
+
</CommandEmpty>{" "}
|
|
627
706
|
<CommandGroup>
|
|
628
707
|
{filteredOptions.map((option) => {
|
|
629
708
|
const isSelected = selectedValue === option.value;
|
|
@@ -639,7 +718,7 @@ const ComboboxComponent = <T = string,>(props: ComboboxProps<T>, ref: React.Ref<
|
|
|
639
718
|
}${option.disabled ? ", disabled" : ""}`}
|
|
640
719
|
className={cn(
|
|
641
720
|
"cursor-pointer",
|
|
642
|
-
option.disabled && "cursor-not-allowed opacity-50"
|
|
721
|
+
option.disabled && "cursor-not-allowed opacity-50",
|
|
643
722
|
)}
|
|
644
723
|
disabled={option.disabled}
|
|
645
724
|
>
|
|
@@ -650,7 +729,9 @@ const ComboboxComponent = <T = string,>(props: ComboboxProps<T>, ref: React.Ref<
|
|
|
650
729
|
/>
|
|
651
730
|
)}
|
|
652
731
|
<span className="grow">{option.label}</span>
|
|
653
|
-
{isSelected ?
|
|
732
|
+
{isSelected ? (
|
|
733
|
+
<CheckIcon className="text-muted-foreground size-3.5" />
|
|
734
|
+
) : null}
|
|
654
735
|
</CommandItem>
|
|
655
736
|
);
|
|
656
737
|
})}
|
|
@@ -662,7 +743,7 @@ const ComboboxComponent = <T = string,>(props: ComboboxProps<T>, ref: React.Ref<
|
|
|
662
743
|
<WandSparkles
|
|
663
744
|
className={cn(
|
|
664
745
|
"text-foreground bg-background my-2 h-3 w-3 cursor-pointer",
|
|
665
|
-
isAnimating ? "" : "text-muted-foreground"
|
|
746
|
+
isAnimating ? "" : "text-muted-foreground",
|
|
666
747
|
)}
|
|
667
748
|
onClick={() => setIsAnimating(!isAnimating)}
|
|
668
749
|
/>
|
|
@@ -674,7 +755,7 @@ const ComboboxComponent = <T = string,>(props: ComboboxProps<T>, ref: React.Ref<
|
|
|
674
755
|
|
|
675
756
|
// Create the forwardRef wrapper with proper generic typing
|
|
676
757
|
const ComboboxForwardRef = React.forwardRef(ComboboxComponent) as <T = string>(
|
|
677
|
-
props: ComboboxProps<T> & { ref?: React.Ref<ComboboxRef> }
|
|
758
|
+
props: ComboboxProps<T> & { ref?: React.Ref<ComboboxRef> },
|
|
678
759
|
) => React.ReactElement;
|
|
679
760
|
|
|
680
761
|
// Set displayName on a mutable object
|
|
@@ -6,27 +6,19 @@ import { XIcon } from "lucide-react";
|
|
|
6
6
|
|
|
7
7
|
import { cn } from "@/lib/utils";
|
|
8
8
|
|
|
9
|
-
function Dialog({
|
|
10
|
-
...props
|
|
11
|
-
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
|
9
|
+
function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
|
12
10
|
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
|
|
13
11
|
}
|
|
14
12
|
|
|
15
|
-
function DialogTrigger({
|
|
16
|
-
...props
|
|
17
|
-
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
|
13
|
+
function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
|
18
14
|
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
|
|
19
15
|
}
|
|
20
16
|
|
|
21
|
-
function DialogPortal({
|
|
22
|
-
...props
|
|
23
|
-
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
|
17
|
+
function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
|
24
18
|
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
|
|
25
19
|
}
|
|
26
20
|
|
|
27
|
-
function DialogClose({
|
|
28
|
-
...props
|
|
29
|
-
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
|
21
|
+
function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
|
30
22
|
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
|
|
31
23
|
}
|
|
32
24
|
|
|
@@ -38,8 +30,8 @@ function DialogOverlay({
|
|
|
38
30
|
<DialogPrimitive.Overlay
|
|
39
31
|
data-slot="dialog-overlay"
|
|
40
32
|
className={cn(
|
|
41
|
-
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
|
42
|
-
className
|
|
33
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 grid place-items-center overflow-y-auto bg-black/50",
|
|
34
|
+
className
|
|
43
35
|
)}
|
|
44
36
|
{...props}
|
|
45
37
|
/>
|
|
@@ -61,7 +53,7 @@ function DialogContent({
|
|
|
61
53
|
data-slot="dialog-content"
|
|
62
54
|
className={cn(
|
|
63
55
|
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 border-gradient-soft fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg p-6 shadow-lg duration-200 sm:max-w-lg",
|
|
64
|
-
className
|
|
56
|
+
className
|
|
65
57
|
)}
|
|
66
58
|
{...props}
|
|
67
59
|
>
|
|
@@ -94,19 +86,13 @@ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
94
86
|
return (
|
|
95
87
|
<div
|
|
96
88
|
data-slot="dialog-footer"
|
|
97
|
-
className={cn(
|
|
98
|
-
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
|
99
|
-
className,
|
|
100
|
-
)}
|
|
89
|
+
className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
|
|
101
90
|
{...props}
|
|
102
91
|
/>
|
|
103
92
|
);
|
|
104
93
|
}
|
|
105
94
|
|
|
106
|
-
function DialogTitle({
|
|
107
|
-
className,
|
|
108
|
-
...props
|
|
109
|
-
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
|
95
|
+
function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
|
110
96
|
return (
|
|
111
97
|
<DialogPrimitive.Title
|
|
112
98
|
data-slot="dialog-title"
|
|
@@ -50,7 +50,7 @@ function TooltipContent({
|
|
|
50
50
|
data-slot="tooltip-content"
|
|
51
51
|
sideOffset={sideOffset}
|
|
52
52
|
className={cn(
|
|
53
|
-
"animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 bg-neutral-alpha-10 border-border z-50 origin-(--radix-tooltip-content-transform-origin) rounded-lg border p-2 backdrop-blur-lg transition-all",
|
|
53
|
+
"animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 bg-neutral-alpha-10 border-border z-50 max-w-xs origin-(--radix-tooltip-content-transform-origin) rounded-lg border p-2 text-xs backdrop-blur-lg transition-all",
|
|
54
54
|
gradient === "primary" && "border-gradient-primary",
|
|
55
55
|
gradient === "white" && "border-gradient-white",
|
|
56
56
|
className,
|