@bearmenu/ui 0.5.1 → 0.6.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/dist/{chunk-ERMIA7NP.js → chunk-7PPCH42H.js} +3 -3
- package/dist/{chunk-HLQ2IRE7.js → chunk-BAZFVSSG.js} +3 -3
- package/dist/chunk-D5QHEHXJ.js +26 -0
- package/dist/{chunk-UISA4CIG.js → chunk-N2KNBPAQ.js} +2 -2
- package/dist/{chunk-7H54PTVH.js → chunk-PSR7N36A.js} +38 -10
- package/dist/chunk-RIDXFY6M.js +38 -0
- package/dist/chunk-UUJVXN2O.js +82 -0
- package/dist/chunk-WKMLZDUT.js +122 -0
- package/dist/chunk-XK2ZDQVE.js +122 -0
- package/dist/{chunk-4KOMHDZD.js → chunk-Y5D5LP4A.js} +18 -3
- package/dist/chunk-YJ6OBKZJ.js +32 -0
- package/dist/{chunk-ESO2EM3L.js → chunk-ZBBUKXM5.js} +2 -2
- package/dist/chunk-ZFNOCP6W.js +106 -0
- package/dist/components/accordion.js +11 -5
- package/dist/components/alert-dialog.js +5 -3
- package/dist/components/alert.d.ts +5 -2
- package/dist/components/alert.js +25 -9
- package/dist/components/avatar.d.ts +17 -2
- package/dist/components/avatar.js +91 -12
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/badge.js +1 -1
- package/dist/components/breadcrumb.js +5 -2
- package/dist/components/button-group.d.ts +14 -0
- package/dist/components/button-group.js +39 -0
- package/dist/components/button.d.ts +4 -2
- package/dist/components/button.js +1 -1
- package/dist/components/calendar.js +3 -3
- package/dist/components/card.d.ts +13 -5
- package/dist/components/card.js +2 -2
- package/dist/components/carousel.d.ts +20 -3
- package/dist/components/carousel.js +72 -12
- package/dist/components/checkbox.js +1 -1
- package/dist/components/circular-progress.d.ts +16 -0
- package/dist/components/circular-progress.js +105 -0
- package/dist/components/combobox.d.ts +27 -0
- package/dist/components/combobox.js +102 -0
- package/dist/components/command.d.ts +29 -16
- package/dist/components/command.js +2 -2
- package/dist/components/currency-input.d.ts +1 -0
- package/dist/components/currency-input.js +73 -52
- package/dist/components/date-input.d.ts +10 -0
- package/dist/components/date-input.js +40 -0
- package/dist/components/description-list.d.ts +15 -0
- package/dist/components/description-list.js +67 -0
- package/dist/components/dialog.d.ts +13 -2
- package/dist/components/dialog.js +1 -1
- package/dist/components/drawer.d.ts +5 -2
- package/dist/components/drawer.js +47 -9
- package/dist/components/dropdown-menu.d.ts +7 -2
- package/dist/components/dropdown-menu.js +22 -6
- package/dist/components/empty-state.js +2 -2
- package/dist/components/field.d.ts +22 -0
- package/dist/components/field.js +130 -0
- package/dist/components/form.d.ts +2 -1
- package/dist/components/form.js +11 -1
- package/dist/components/haptic-button.js +3 -3
- package/dist/components/input-group.d.ts +18 -0
- package/dist/components/input-group.js +92 -0
- package/dist/components/input.d.ts +8 -2
- package/dist/components/input.js +1 -1
- package/dist/components/item.d.ts +23 -0
- package/dist/components/item.js +120 -0
- package/dist/components/kbd.d.ts +13 -0
- package/dist/components/kbd.js +49 -0
- package/dist/components/lazy-markdown.d.ts +3 -1
- package/dist/components/lazy-markdown.js +7 -7
- package/dist/components/link-button.js +1 -1
- package/dist/components/multi-select-combobox.js +92 -83
- package/dist/components/multi-select-menu.d.ts +0 -4
- package/dist/components/multi-select-menu.js +28 -17
- package/dist/components/native-select.d.ts +12 -0
- package/dist/components/native-select.js +49 -0
- package/dist/components/pagination.d.ts +5 -2
- package/dist/components/pagination.js +66 -7
- package/dist/components/place-about-tab.js +4 -4
- package/dist/components/place-card.js +1 -1
- package/dist/components/place-details-mobile.js +4 -4
- package/dist/components/popover.d.ts +7 -1
- package/dist/components/popover.js +1 -1
- package/dist/components/progress.d.ts +5 -1
- package/dist/components/progress.js +17 -3
- package/dist/components/radio-group.js +12 -3
- package/dist/components/rating.d.ts +2 -0
- package/dist/components/rating.js +88 -70
- package/dist/components/search-bar.d.ts +1 -2
- package/dist/components/search-bar.js +37 -60
- package/dist/components/searchable-select.js +76 -72
- package/dist/components/select.d.ts +5 -1
- package/dist/components/select.js +30 -14
- package/dist/components/selection-bar.d.ts +11 -0
- package/dist/components/selection-bar.js +65 -0
- package/dist/components/separator.js +3 -2
- package/dist/components/sheet.d.ts +1 -1
- package/dist/components/sheet.js +1 -1
- package/dist/components/skeleton-card.js +1 -1
- package/dist/components/skeleton.js +1 -1
- package/dist/components/slider.js +21 -4
- package/dist/components/sliding-panels.js +15 -13
- package/dist/components/spinner.d.ts +17 -0
- package/dist/components/spinner.js +50 -0
- package/dist/components/stepper.d.ts +18 -0
- package/dist/components/stepper.js +199 -0
- package/dist/components/sticky-container.js +1 -0
- package/dist/components/switch.js +4 -3
- package/dist/components/table.d.ts +23 -5
- package/dist/components/table.js +121 -35
- package/dist/components/tabs.d.ts +3 -1
- package/dist/components/tabs.js +37 -9
- package/dist/components/tag-group.d.ts +9 -0
- package/dist/components/tag-group.js +26 -0
- package/dist/components/tag.d.ts +17 -0
- package/dist/components/tag.js +61 -0
- package/dist/components/text.d.ts +3 -3
- package/dist/components/text.js +1 -1
- package/dist/components/textarea.js +2 -1
- package/dist/components/toast.js +1 -1
- package/dist/components/toaster.js +1 -1
- package/dist/components/toggle-group.js +1 -1
- package/dist/components/toggle.js +1 -1
- package/dist/components/tooltip.d.ts +3 -2
- package/dist/components/tooltip.js +16 -2
- package/package.json +44 -26
- package/src/components/accordion.tsx +11 -6
- package/src/components/alert-dialog.tsx +4 -2
- package/src/components/alert.tsx +20 -8
- package/src/components/avatar.tsx +106 -13
- package/src/components/badge.tsx +12 -11
- package/src/components/breadcrumb.tsx +5 -2
- package/src/components/button-group.stories.tsx +52 -0
- package/src/components/button-group.tsx +40 -0
- package/src/components/button.test.tsx +174 -0
- package/src/components/button.tsx +42 -19
- package/src/components/calendar.tsx +2 -2
- package/src/components/card.tsx +55 -13
- package/src/components/carousel.tsx +85 -13
- package/src/components/checkbox.test.tsx +58 -0
- package/src/components/checkbox.tsx +8 -3
- package/src/components/circular-progress.stories.tsx +39 -0
- package/src/components/circular-progress.tsx +102 -0
- package/src/components/collapsible.stories.tsx +7 -3
- package/src/components/combobox.stories.tsx +82 -0
- package/src/components/combobox.test.tsx +118 -0
- package/src/components/combobox.tsx +136 -0
- package/src/components/command.test.tsx +91 -0
- package/src/components/command.tsx +38 -15
- package/src/components/currency-input.test.tsx +63 -0
- package/src/components/currency-input.tsx +84 -66
- package/src/components/date-input.stories.tsx +31 -0
- package/src/components/date-input.tsx +43 -0
- package/src/components/description-list.stories.tsx +62 -0
- package/src/components/description-list.tsx +68 -0
- package/src/components/dialog.test.tsx +84 -0
- package/src/components/dialog.tsx +64 -24
- package/src/components/drawer.tsx +72 -23
- package/src/components/dropdown-menu.test.tsx +180 -0
- package/src/components/dropdown-menu.tsx +31 -6
- package/src/components/field.stories.tsx +36 -0
- package/src/components/field.test.tsx +157 -0
- package/src/components/field.tsx +146 -0
- package/src/components/form.stories.tsx +3 -2
- package/src/components/form.test.tsx +95 -0
- package/src/components/form.tsx +9 -0
- package/src/components/haptic-button.tsx +1 -1
- package/src/components/input-group.stories.tsx +61 -0
- package/src/components/input-group.tsx +99 -0
- package/src/components/input.test.tsx +103 -0
- package/src/components/input.tsx +24 -7
- package/src/components/item.stories.tsx +52 -0
- package/src/components/item.tsx +115 -0
- package/src/components/kbd.stories.tsx +33 -0
- package/src/components/kbd.tsx +46 -0
- package/src/components/lazy-markdown.tsx +10 -7
- package/src/components/multi-select-combobox.test.tsx +101 -0
- package/src/components/multi-select-combobox.tsx +85 -82
- package/src/components/multi-select-menu.tsx +49 -41
- package/src/components/native-select.stories.tsx +52 -0
- package/src/components/native-select.tsx +45 -0
- package/src/components/pagination.tsx +64 -5
- package/src/components/popover.tsx +66 -3
- package/src/components/progress.tsx +27 -4
- package/src/components/radio-group.test.tsx +64 -0
- package/src/components/radio-group.tsx +11 -3
- package/src/components/rating.test.tsx +112 -0
- package/src/components/rating.tsx +97 -82
- package/src/components/search-bar.stories.tsx +1 -32
- package/src/components/search-bar.tsx +22 -59
- package/src/components/searchable-select.tsx +69 -73
- package/src/components/select.tsx +34 -19
- package/src/components/selection-bar.stories.tsx +37 -0
- package/src/components/selection-bar.tsx +65 -0
- package/src/components/separator.tsx +5 -2
- package/src/components/sheet.tsx +1 -1
- package/src/components/skeleton.tsx +2 -2
- package/src/components/slider.test.tsx +45 -0
- package/src/components/slider.tsx +31 -12
- package/src/components/sliding-panels.tsx +14 -12
- package/src/components/spinner.stories.tsx +35 -0
- package/src/components/spinner.tsx +50 -0
- package/src/components/stepper.stories.tsx +71 -0
- package/src/components/stepper.tsx +220 -0
- package/src/components/sticky-container.tsx +1 -0
- package/src/components/switch.test.tsx +52 -0
- package/src/components/switch.tsx +4 -3
- package/src/components/table.tsx +140 -35
- package/src/components/tabs.test.tsx +107 -0
- package/src/components/tabs.tsx +70 -23
- package/src/components/tag-group.stories.tsx +39 -0
- package/src/components/tag-group.tsx +28 -0
- package/src/components/tag.stories.tsx +50 -0
- package/src/components/tag.tsx +63 -0
- package/src/components/text.test.tsx +1 -1
- package/src/components/text.tsx +19 -3
- package/src/components/textarea.test.tsx +48 -0
- package/src/components/textarea.tsx +2 -1
- package/src/components/toast.tsx +4 -3
- package/src/components/toggle.tsx +2 -2
- package/src/components/tooltip.tsx +12 -2
- package/src/globals.css +142 -90
- package/dist/chunk-2BMJRXQX.js +0 -80
- package/dist/chunk-4AVS6CV4.js +0 -89
- package/dist/chunk-4UO73SKC.js +0 -26
- package/dist/chunk-CW24PLDW.js +0 -24
- package/dist/chunk-IQASCH2V.js +0 -25
- package/dist/chunk-NKHH23YK.js +0 -31
- package/dist/chunk-QJ54Z7TC.js +0 -83
|
@@ -46,11 +46,9 @@ export function SearchableSelect({
|
|
|
46
46
|
onSearchChange,
|
|
47
47
|
}: SearchableSelectProps) {
|
|
48
48
|
const [open, setOpen] = React.useState(false);
|
|
49
|
+
const listboxId = React.useId();
|
|
49
50
|
|
|
50
51
|
const selectedOption = options.find((opt) => opt.value === value);
|
|
51
|
-
|
|
52
|
-
// Fallback: if we have a value but no matching option, display the value itself
|
|
53
|
-
// This handles the case where the selected concept hasn't been fetched yet
|
|
54
52
|
const displayLabel = selectedOption?.label || (value ? value : undefined);
|
|
55
53
|
|
|
56
54
|
const handleSelect = (selectedValue: string) => {
|
|
@@ -58,77 +56,75 @@ export function SearchableSelect({
|
|
|
58
56
|
setOpen(false);
|
|
59
57
|
};
|
|
60
58
|
|
|
61
|
-
const handleClear = (e: React.MouseEvent | React.KeyboardEvent) => {
|
|
62
|
-
e.stopPropagation();
|
|
63
|
-
onValueChange(undefined);
|
|
64
|
-
};
|
|
65
|
-
|
|
66
59
|
return (
|
|
67
|
-
<
|
|
68
|
-
<
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
className
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<span className="truncate">{displayLabel || placeholder}</span>
|
|
81
|
-
<div className="flex items-center gap-1">
|
|
82
|
-
{value && (
|
|
83
|
-
<div
|
|
84
|
-
role="button"
|
|
85
|
-
tabIndex={0}
|
|
86
|
-
className="rounded-full p-0.5 hover:bg-accent cursor-pointer"
|
|
87
|
-
onClick={handleClear}
|
|
88
|
-
onKeyDown={(e) => {
|
|
89
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
90
|
-
e.preventDefault();
|
|
91
|
-
handleClear(e);
|
|
92
|
-
}
|
|
93
|
-
}}
|
|
94
|
-
>
|
|
95
|
-
<X className="h-3 w-3" />
|
|
96
|
-
</div>
|
|
60
|
+
<div className={cn("relative", className)}>
|
|
61
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
62
|
+
<PopoverTrigger asChild>
|
|
63
|
+
<Button
|
|
64
|
+
variant="outline"
|
|
65
|
+
role="combobox"
|
|
66
|
+
aria-expanded={open}
|
|
67
|
+
aria-haspopup="listbox"
|
|
68
|
+
aria-controls={open ? listboxId : undefined}
|
|
69
|
+
className={cn(
|
|
70
|
+
"w-full justify-between font-normal",
|
|
71
|
+
value && "pr-16",
|
|
72
|
+
!value && "text-muted-foreground"
|
|
97
73
|
)}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
)}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
74
|
+
disabled={disabled}
|
|
75
|
+
>
|
|
76
|
+
<span className="truncate">{displayLabel || placeholder}</span>
|
|
77
|
+
<ChevronsUpDown className="size-4 shrink-0 opacity-50" />
|
|
78
|
+
</Button>
|
|
79
|
+
</PopoverTrigger>
|
|
80
|
+
<PopoverContent
|
|
81
|
+
id={listboxId}
|
|
82
|
+
className="w-[var(--radix-popover-trigger-width)] p-0"
|
|
83
|
+
align="start"
|
|
84
|
+
>
|
|
85
|
+
<Command shouldFilter={!onSearchChange}>
|
|
86
|
+
<CommandInput placeholder={searchPlaceholder} onValueChange={onSearchChange} />
|
|
87
|
+
<CommandList>
|
|
88
|
+
<CommandEmpty>{loading ? "Loading…" : emptyText}</CommandEmpty>
|
|
89
|
+
<CommandGroup>
|
|
90
|
+
{options.map((option) => (
|
|
91
|
+
<CommandItem
|
|
92
|
+
key={option.value}
|
|
93
|
+
value={option.value}
|
|
94
|
+
onSelect={() => handleSelect(option.value)}
|
|
95
|
+
>
|
|
96
|
+
<Check
|
|
97
|
+
className={cn(
|
|
98
|
+
"mr-2 size-4",
|
|
99
|
+
value === option.value ? "opacity-100" : "opacity-0"
|
|
100
|
+
)}
|
|
101
|
+
/>
|
|
102
|
+
<div className="flex flex-col">
|
|
103
|
+
<span>{option.label}</span>
|
|
104
|
+
{option.description && (
|
|
105
|
+
<span className="text-xs text-muted-foreground">{option.description}</span>
|
|
106
|
+
)}
|
|
107
|
+
</div>
|
|
108
|
+
</CommandItem>
|
|
109
|
+
))}
|
|
110
|
+
</CommandGroup>
|
|
111
|
+
</CommandList>
|
|
112
|
+
</Command>
|
|
113
|
+
</PopoverContent>
|
|
114
|
+
</Popover>
|
|
115
|
+
{value && !disabled && (
|
|
116
|
+
<button
|
|
117
|
+
type="button"
|
|
118
|
+
aria-label="Clear selection"
|
|
119
|
+
onClick={(e) => {
|
|
120
|
+
e.stopPropagation();
|
|
121
|
+
onValueChange(undefined);
|
|
122
|
+
}}
|
|
123
|
+
className="absolute right-9 top-1/2 -translate-y-1/2 inline-flex size-6 items-center justify-center rounded-full text-muted-foreground hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 transition-colors"
|
|
124
|
+
>
|
|
125
|
+
<X className="size-3.5" />
|
|
126
|
+
</button>
|
|
127
|
+
)}
|
|
128
|
+
</div>
|
|
133
129
|
);
|
|
134
130
|
}
|
|
@@ -2,30 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
5
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
6
|
import { Check, ChevronDown, ChevronUp } from "lucide-react";
|
|
6
7
|
import { cn } from "../lib/utils";
|
|
7
8
|
|
|
8
9
|
const Select = SelectPrimitive.Root;
|
|
9
|
-
|
|
10
10
|
const SelectGroup = SelectPrimitive.Group;
|
|
11
|
-
|
|
12
11
|
const SelectValue = SelectPrimitive.Value;
|
|
13
12
|
|
|
13
|
+
const selectTriggerVariants = cva(
|
|
14
|
+
"flex w-full items-center justify-between rounded-xl bg-background border border-border text-sm font-medium text-foreground transition-colors focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[placeholder]:text-muted-foreground [&>span]:line-clamp-1 hover:border-foreground/30 aria-invalid:border-destructive aria-invalid:focus-visible:ring-destructive/40",
|
|
15
|
+
{
|
|
16
|
+
variants: {
|
|
17
|
+
size: {
|
|
18
|
+
sm: "h-9 px-3",
|
|
19
|
+
default: "h-11 px-4 py-2",
|
|
20
|
+
lg: "h-12 px-5",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
defaultVariants: {
|
|
24
|
+
size: "default",
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
|
|
14
29
|
const SelectTrigger = React.forwardRef<
|
|
15
30
|
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
|
16
|
-
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
|
17
|
-
>
|
|
31
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> &
|
|
32
|
+
VariantProps<typeof selectTriggerVariants>
|
|
33
|
+
>(({ className, size, children, ...props }, ref) => (
|
|
18
34
|
<SelectPrimitive.Trigger
|
|
19
35
|
ref={ref}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
className
|
|
23
|
-
)}
|
|
36
|
+
data-slot="select-trigger"
|
|
37
|
+
className={cn(selectTriggerVariants({ size }), className)}
|
|
24
38
|
{...props}
|
|
25
39
|
>
|
|
26
40
|
{children}
|
|
27
41
|
<SelectPrimitive.Icon asChild>
|
|
28
|
-
<ChevronDown className="
|
|
42
|
+
<ChevronDown className="size-4 opacity-60" />
|
|
29
43
|
</SelectPrimitive.Icon>
|
|
30
44
|
</SelectPrimitive.Trigger>
|
|
31
45
|
));
|
|
@@ -40,7 +54,7 @@ const SelectScrollUpButton = React.forwardRef<
|
|
|
40
54
|
className={cn("flex cursor-default items-center justify-center py-1", className)}
|
|
41
55
|
{...props}
|
|
42
56
|
>
|
|
43
|
-
<ChevronUp className="
|
|
57
|
+
<ChevronUp className="size-4" />
|
|
44
58
|
</SelectPrimitive.ScrollUpButton>
|
|
45
59
|
));
|
|
46
60
|
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
|
|
@@ -54,7 +68,7 @@ const SelectScrollDownButton = React.forwardRef<
|
|
|
54
68
|
className={cn("flex cursor-default items-center justify-center py-1", className)}
|
|
55
69
|
{...props}
|
|
56
70
|
>
|
|
57
|
-
<ChevronDown className="
|
|
71
|
+
<ChevronDown className="size-4" />
|
|
58
72
|
</SelectPrimitive.ScrollDownButton>
|
|
59
73
|
));
|
|
60
74
|
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
|
|
@@ -66,8 +80,9 @@ const SelectContent = React.forwardRef<
|
|
|
66
80
|
<SelectPrimitive.Portal>
|
|
67
81
|
<SelectPrimitive.Content
|
|
68
82
|
ref={ref}
|
|
83
|
+
data-slot="select-content"
|
|
69
84
|
className={cn(
|
|
70
|
-
"relative z-50 max-h-96 min-w-
|
|
85
|
+
"relative z-50 max-h-96 min-w-32 overflow-hidden rounded-xl border border-border bg-popover text-popover-foreground shadow-lg 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 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",
|
|
71
86
|
position === "popper" &&
|
|
72
87
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
|
73
88
|
className
|
|
@@ -78,7 +93,7 @@ const SelectContent = React.forwardRef<
|
|
|
78
93
|
<SelectScrollUpButton />
|
|
79
94
|
<SelectPrimitive.Viewport
|
|
80
95
|
className={cn(
|
|
81
|
-
"p-1 flex flex-col gap-
|
|
96
|
+
"p-1 flex flex-col gap-0.5",
|
|
82
97
|
position === "popper" &&
|
|
83
98
|
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
|
84
99
|
)}
|
|
@@ -97,7 +112,7 @@ const SelectLabel = React.forwardRef<
|
|
|
97
112
|
>(({ className, ...props }, ref) => (
|
|
98
113
|
<SelectPrimitive.Label
|
|
99
114
|
ref={ref}
|
|
100
|
-
className={cn("py-1.5 pl-8 pr-2 text-
|
|
115
|
+
className={cn("py-1.5 pl-8 pr-2 text-xs font-semibold text-muted-foreground", className)}
|
|
101
116
|
{...props}
|
|
102
117
|
/>
|
|
103
118
|
));
|
|
@@ -109,18 +124,18 @@ const SelectItem = React.forwardRef<
|
|
|
109
124
|
>(({ className, children, ...props }, ref) => (
|
|
110
125
|
<SelectPrimitive.Item
|
|
111
126
|
ref={ref}
|
|
127
|
+
data-slot="select-item"
|
|
112
128
|
className={cn(
|
|
113
|
-
"relative flex w-full cursor-default select-none items-center rounded-
|
|
129
|
+
"relative flex w-full cursor-default select-none items-center rounded-md py-2 pl-8 pr-2 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[state=checked]:font-medium",
|
|
114
130
|
className
|
|
115
131
|
)}
|
|
116
132
|
{...props}
|
|
117
133
|
>
|
|
118
|
-
<span className="absolute left-2 flex
|
|
134
|
+
<span className="absolute left-2 flex size-3.5 items-center justify-center">
|
|
119
135
|
<SelectPrimitive.ItemIndicator>
|
|
120
|
-
<Check className="
|
|
136
|
+
<Check className="size-4" />
|
|
121
137
|
</SelectPrimitive.ItemIndicator>
|
|
122
138
|
</span>
|
|
123
|
-
|
|
124
139
|
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
125
140
|
</SelectPrimitive.Item>
|
|
126
141
|
));
|
|
@@ -132,7 +147,7 @@ const SelectSeparator = React.forwardRef<
|
|
|
132
147
|
>(({ className, ...props }, ref) => (
|
|
133
148
|
<SelectPrimitive.Separator
|
|
134
149
|
ref={ref}
|
|
135
|
-
className={cn("-mx-1 my-1 h-px bg-
|
|
150
|
+
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
|
136
151
|
{...props}
|
|
137
152
|
/>
|
|
138
153
|
));
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { SelectionBar } from "./selection-bar";
|
|
3
|
+
import { Button } from "./button";
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: "Components/SelectionBar",
|
|
7
|
+
component: SelectionBar,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
} satisfies Meta<typeof SelectionBar>;
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj<typeof meta>;
|
|
13
|
+
|
|
14
|
+
export const Default: Story = {
|
|
15
|
+
render: () => (
|
|
16
|
+
<div className="w-[36rem]">
|
|
17
|
+
<SelectionBar count={3} onClear={() => {}} itemNoun="row">
|
|
18
|
+
<Button size="sm" variant="outline">
|
|
19
|
+
Archive
|
|
20
|
+
</Button>
|
|
21
|
+
<Button size="sm" variant="destructive">
|
|
22
|
+
Delete
|
|
23
|
+
</Button>
|
|
24
|
+
</SelectionBar>
|
|
25
|
+
</div>
|
|
26
|
+
),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const SingleSelection: Story = {
|
|
30
|
+
render: () => (
|
|
31
|
+
<div className="w-[36rem]">
|
|
32
|
+
<SelectionBar count={1} onClear={() => {}} itemNoun="file">
|
|
33
|
+
<Button size="sm">Download</Button>
|
|
34
|
+
</SelectionBar>
|
|
35
|
+
</div>
|
|
36
|
+
),
|
|
37
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { X } from "lucide-react";
|
|
5
|
+
import { cn } from "../lib/utils";
|
|
6
|
+
|
|
7
|
+
type SelectionBarProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
8
|
+
count: number;
|
|
9
|
+
/** Hide bar when count is 0. Defaults to true. */
|
|
10
|
+
hideWhenEmpty?: boolean;
|
|
11
|
+
onClear?: () => void;
|
|
12
|
+
itemNoun?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const SelectionBar = React.forwardRef<HTMLDivElement, SelectionBarProps>(
|
|
16
|
+
(
|
|
17
|
+
{
|
|
18
|
+
className,
|
|
19
|
+
count,
|
|
20
|
+
hideWhenEmpty = true,
|
|
21
|
+
onClear,
|
|
22
|
+
itemNoun = "item",
|
|
23
|
+
children,
|
|
24
|
+
...props
|
|
25
|
+
},
|
|
26
|
+
ref
|
|
27
|
+
) => {
|
|
28
|
+
if (hideWhenEmpty && count <= 0) return null;
|
|
29
|
+
const noun = count === 1 ? itemNoun : `${itemNoun}s`;
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
ref={ref}
|
|
34
|
+
role="region"
|
|
35
|
+
aria-label={`${count} ${noun} selected`}
|
|
36
|
+
data-slot="selection-bar"
|
|
37
|
+
className={cn(
|
|
38
|
+
"flex w-full items-center gap-3 rounded-2xl border border-border bg-card px-4 py-3 shadow-lg",
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
<div className="flex items-center gap-2 text-sm font-medium text-foreground">
|
|
44
|
+
{onClear && (
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
aria-label="Clear selection"
|
|
48
|
+
onClick={onClear}
|
|
49
|
+
className="inline-flex size-6 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
50
|
+
>
|
|
51
|
+
<X className="size-4" />
|
|
52
|
+
</button>
|
|
53
|
+
)}
|
|
54
|
+
<span aria-live="polite">
|
|
55
|
+
{count} {noun} selected
|
|
56
|
+
</span>
|
|
57
|
+
</div>
|
|
58
|
+
{children && <div className="ml-auto flex items-center gap-2">{children}</div>}
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
SelectionBar.displayName = "SelectionBar";
|
|
64
|
+
|
|
65
|
+
export { SelectionBar };
|
|
@@ -10,11 +10,14 @@ const Separator = React.forwardRef<
|
|
|
10
10
|
>(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
|
|
11
11
|
<SeparatorPrimitive.Root
|
|
12
12
|
ref={ref}
|
|
13
|
+
data-slot="separator"
|
|
13
14
|
decorative={decorative}
|
|
14
15
|
orientation={orientation}
|
|
15
16
|
className={cn(
|
|
16
|
-
"shrink-0
|
|
17
|
-
orientation === "horizontal"
|
|
17
|
+
"shrink-0",
|
|
18
|
+
orientation === "horizontal"
|
|
19
|
+
? "h-px w-full bg-gradient-to-r from-transparent via-border to-transparent"
|
|
20
|
+
: "h-full w-px bg-gradient-to-b from-transparent via-border to-transparent",
|
|
18
21
|
className
|
|
19
22
|
)}
|
|
20
23
|
{...props}
|
package/src/components/sheet.tsx
CHANGED
|
@@ -30,7 +30,7 @@ const SheetOverlay = React.forwardRef<
|
|
|
30
30
|
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
|
|
31
31
|
|
|
32
32
|
const sheetVariants = cva(
|
|
33
|
-
"fixed z-[1000] gap-4
|
|
33
|
+
"fixed z-[1000] gap-4 bg-background border-border p-6 shadow-2xl transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
|
34
34
|
{
|
|
35
35
|
variants: {
|
|
36
36
|
side: {
|
|
@@ -6,8 +6,8 @@ function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>)
|
|
|
6
6
|
<div
|
|
7
7
|
data-slot="skeleton"
|
|
8
8
|
className={cn(
|
|
9
|
-
"
|
|
10
|
-
"before:absolute before:inset-0 before:-translate-x-full before:animate-[shimmer_2s_infinite] before:bg-gradient-to-r before:from-transparent before:via-
|
|
9
|
+
"relative overflow-hidden rounded-xl bg-muted",
|
|
10
|
+
"before:absolute before:inset-0 before:-translate-x-full before:animate-[shimmer_2s_infinite] before:bg-gradient-to-r before:from-transparent before:via-foreground/[0.04] before:to-transparent",
|
|
11
11
|
className
|
|
12
12
|
)}
|
|
13
13
|
{...props}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
import { Slider } from "./slider";
|
|
5
|
+
|
|
6
|
+
describe("Slider", () => {
|
|
7
|
+
it("renders a single thumb for a scalar default value", () => {
|
|
8
|
+
render(<Slider defaultValue={[50]} aria-label="volume" />);
|
|
9
|
+
expect(screen.getAllByRole("slider")).toHaveLength(1);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("renders N thumbs for an N-length value array (range slider)", () => {
|
|
13
|
+
render(<Slider defaultValue={[20, 80]} aria-label="range" />);
|
|
14
|
+
expect(screen.getAllByRole("slider")).toHaveLength(2);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("renders thumbs equal to length of controlled value", () => {
|
|
18
|
+
render(<Slider value={[10, 40, 70]} onValueChange={() => {}} aria-label="three" />);
|
|
19
|
+
expect(screen.getAllByRole("slider")).toHaveLength(3);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("fires onValueChange via ArrowRight", async () => {
|
|
23
|
+
const user = userEvent.setup();
|
|
24
|
+
const onValueChange = vi.fn();
|
|
25
|
+
render(<Slider defaultValue={[50]} onValueChange={onValueChange} aria-label="x" />);
|
|
26
|
+
screen.getByRole("slider").focus();
|
|
27
|
+
await user.keyboard("{ArrowRight}");
|
|
28
|
+
expect(onValueChange).toHaveBeenCalled();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("sets data-disabled when disabled", () => {
|
|
32
|
+
const { container } = render(
|
|
33
|
+
<Slider defaultValue={[50]} disabled aria-label="x" />
|
|
34
|
+
);
|
|
35
|
+
expect(container.querySelector("[data-slot=slider]")).toHaveAttribute("data-disabled");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("respects min/max bounds in aria attributes", () => {
|
|
39
|
+
render(<Slider defaultValue={[5]} min={0} max={10} aria-label="x" />);
|
|
40
|
+
const thumb = screen.getByRole("slider");
|
|
41
|
+
expect(thumb).toHaveAttribute("aria-valuemin", "0");
|
|
42
|
+
expect(thumb).toHaveAttribute("aria-valuemax", "10");
|
|
43
|
+
expect(thumb).toHaveAttribute("aria-valuenow", "5");
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -7,18 +7,37 @@ import { cn } from "../lib/utils";
|
|
|
7
7
|
const Slider = React.forwardRef<
|
|
8
8
|
React.ElementRef<typeof SliderPrimitive.Root>,
|
|
9
9
|
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
|
|
10
|
-
>(({ className, ...props }, ref) =>
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
10
|
+
>(({ className, value, defaultValue, ...props }, ref) => {
|
|
11
|
+
const thumbCount = React.useMemo(() => {
|
|
12
|
+
if (Array.isArray(value)) return value.length;
|
|
13
|
+
if (Array.isArray(defaultValue)) return defaultValue.length;
|
|
14
|
+
return 1;
|
|
15
|
+
}, [value, defaultValue]);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<SliderPrimitive.Root
|
|
19
|
+
ref={ref}
|
|
20
|
+
data-slot="slider"
|
|
21
|
+
value={value}
|
|
22
|
+
defaultValue={defaultValue}
|
|
23
|
+
className={cn(
|
|
24
|
+
"relative flex w-full touch-none select-none items-center data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
25
|
+
className
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
>
|
|
29
|
+
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-muted">
|
|
30
|
+
<SliderPrimitive.Range className="absolute h-full bg-primary" />
|
|
31
|
+
</SliderPrimitive.Track>
|
|
32
|
+
{Array.from({ length: thumbCount }, (_, i) => (
|
|
33
|
+
<SliderPrimitive.Thumb
|
|
34
|
+
key={i}
|
|
35
|
+
className="block size-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-transform focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 hover:scale-110"
|
|
36
|
+
/>
|
|
37
|
+
))}
|
|
38
|
+
</SliderPrimitive.Root>
|
|
39
|
+
);
|
|
40
|
+
});
|
|
22
41
|
Slider.displayName = SliderPrimitive.Root.displayName;
|
|
23
42
|
|
|
24
43
|
export { Slider };
|
|
@@ -46,20 +46,22 @@ export const SlidingPanels = ({
|
|
|
46
46
|
}, [currentIndex]);
|
|
47
47
|
|
|
48
48
|
const handleBack = () => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
49
|
+
const previousPanel = panels[currentIndex - 1];
|
|
50
|
+
if (!previousPanel) return;
|
|
51
|
+
if (currentPanelData?.onBack) {
|
|
52
|
+
currentPanelData.onBack();
|
|
53
|
+
} else {
|
|
54
|
+
onPanelChange(previousPanel.id);
|
|
56
55
|
}
|
|
57
56
|
};
|
|
58
57
|
|
|
59
58
|
return (
|
|
60
59
|
<div
|
|
61
|
-
className={cn(
|
|
62
|
-
|
|
60
|
+
className={cn(
|
|
61
|
+
"relative overflow-clip transition-[height] duration-500 ease-[cubic-bezier(0.32,0.72,0,1)]",
|
|
62
|
+
className
|
|
63
|
+
)}
|
|
64
|
+
style={{ height: containerHeight, overflowClipMargin: "8px" }}
|
|
63
65
|
>
|
|
64
66
|
{panels.map((panel, index) => {
|
|
65
67
|
const isActive = panel.id === currentPanel;
|
|
@@ -73,10 +75,10 @@ export const SlidingPanels = ({
|
|
|
73
75
|
panelRefs.current[index] = el;
|
|
74
76
|
}}
|
|
75
77
|
className={cn(
|
|
76
|
-
"absolute top-0 left-0 w-full transition-transform duration-
|
|
78
|
+
"absolute top-0 left-0 w-full transition-transform duration-500 ease-[cubic-bezier(0.32,0.72,0,1)]",
|
|
77
79
|
isActive && "translate-x-0",
|
|
78
|
-
isLeft && "-translate-x-
|
|
79
|
-
isRight && "translate-x-
|
|
80
|
+
isLeft && "-translate-x-[calc(100%+8px)]",
|
|
81
|
+
isRight && "translate-x-[calc(100%+8px)]"
|
|
80
82
|
)}
|
|
81
83
|
aria-hidden={!isActive}
|
|
82
84
|
>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Spinner } from "./spinner";
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: "Components/Spinner",
|
|
6
|
+
component: Spinner,
|
|
7
|
+
tags: ["autodocs"],
|
|
8
|
+
} satisfies Meta<typeof Spinner>;
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
type Story = StoryObj<typeof meta>;
|
|
12
|
+
|
|
13
|
+
export const Default: Story = {};
|
|
14
|
+
|
|
15
|
+
export const Sizes: Story = {
|
|
16
|
+
render: () => (
|
|
17
|
+
<div className="flex items-center gap-4">
|
|
18
|
+
<Spinner size="xs" />
|
|
19
|
+
<Spinner size="sm" />
|
|
20
|
+
<Spinner size="default" />
|
|
21
|
+
<Spinner size="lg" />
|
|
22
|
+
<Spinner size="xl" />
|
|
23
|
+
</div>
|
|
24
|
+
),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const Tones: Story = {
|
|
28
|
+
render: () => (
|
|
29
|
+
<div className="flex items-center gap-4">
|
|
30
|
+
<Spinner tone="default" />
|
|
31
|
+
<Spinner tone="primary" />
|
|
32
|
+
<Spinner tone="destructive" />
|
|
33
|
+
</div>
|
|
34
|
+
),
|
|
35
|
+
};
|