@bearmenu/ui 0.5.2 → 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 +9 -6
- 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/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 +8 -5
- 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/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 +126 -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
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import {
|
|
4
|
+
import { ChevronsUpDown, X } from "lucide-react";
|
|
5
5
|
import { cn } from "../lib/utils";
|
|
6
6
|
import { Button } from "./button";
|
|
7
7
|
import { Badge } from "./badge";
|
|
8
|
+
import { Checkbox } from "./checkbox";
|
|
8
9
|
import {
|
|
9
10
|
Command,
|
|
10
11
|
CommandEmpty,
|
|
@@ -50,6 +51,7 @@ export function MultiSelectCombobox({
|
|
|
50
51
|
shouldFilter = true,
|
|
51
52
|
}: MultiSelectComboboxProps) {
|
|
52
53
|
const [open, setOpen] = React.useState(false);
|
|
54
|
+
const listboxId = React.useId();
|
|
53
55
|
|
|
54
56
|
const handleSelect = (value: string) => {
|
|
55
57
|
const newSelected = selected.includes(value)
|
|
@@ -58,91 +60,92 @@ export function MultiSelectCombobox({
|
|
|
58
60
|
onSelectionChange(newSelected);
|
|
59
61
|
};
|
|
60
62
|
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
onSelectionChange(selected.filter((item) => item !== value));
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const selectedLabels = selected
|
|
67
|
-
.map((value) => options.find((opt) => opt.value === value)?.label || value)
|
|
68
|
-
.slice(0, maxDisplay);
|
|
69
|
-
|
|
70
|
-
const remainingCount = selected.length - maxDisplay;
|
|
63
|
+
const visible = selected.slice(0, maxDisplay);
|
|
64
|
+
const remaining = selected.length - maxDisplay;
|
|
71
65
|
|
|
72
66
|
return (
|
|
73
|
-
<
|
|
74
|
-
<
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
className
|
|
83
|
-
|
|
84
|
-
|
|
67
|
+
<div className={cn("flex flex-col gap-2", className)}>
|
|
68
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
69
|
+
<PopoverTrigger asChild>
|
|
70
|
+
<Button
|
|
71
|
+
variant="outline"
|
|
72
|
+
role="combobox"
|
|
73
|
+
aria-expanded={open}
|
|
74
|
+
aria-haspopup="listbox"
|
|
75
|
+
aria-controls={open ? listboxId : undefined}
|
|
76
|
+
className={cn(
|
|
77
|
+
"w-full justify-between font-normal",
|
|
78
|
+
selected.length === 0 && "text-muted-foreground"
|
|
79
|
+
)}
|
|
80
|
+
disabled={disabled}
|
|
81
|
+
>
|
|
82
|
+
<span className="truncate">
|
|
83
|
+
{selected.length === 0
|
|
84
|
+
? placeholder
|
|
85
|
+
: `${selected.length} selected`}
|
|
86
|
+
</span>
|
|
87
|
+
<ChevronsUpDown className="size-4 shrink-0 opacity-50" />
|
|
88
|
+
</Button>
|
|
89
|
+
</PopoverTrigger>
|
|
90
|
+
<PopoverContent
|
|
91
|
+
id={listboxId}
|
|
92
|
+
className="w-[var(--radix-popover-trigger-width)] p-0"
|
|
93
|
+
align="start"
|
|
85
94
|
>
|
|
86
|
-
<
|
|
87
|
-
{
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
{
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
onKeyDown={(e) => {
|
|
100
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
101
|
-
e.preventDefault();
|
|
102
|
-
handleRemove(selected[i], e);
|
|
103
|
-
}
|
|
104
|
-
}}
|
|
95
|
+
<Command shouldFilter={shouldFilter}>
|
|
96
|
+
<CommandInput placeholder={searchPlaceholder} onValueChange={onSearchChange} />
|
|
97
|
+
<CommandList>
|
|
98
|
+
<CommandEmpty>{loading ? "Loading…" : emptyText}</CommandEmpty>
|
|
99
|
+
<CommandGroup>
|
|
100
|
+
{options.map((option) => {
|
|
101
|
+
const isSelected = selected.includes(option.value);
|
|
102
|
+
return (
|
|
103
|
+
<CommandItem
|
|
104
|
+
key={option.value}
|
|
105
|
+
value={option.value}
|
|
106
|
+
onSelect={() => handleSelect(option.value)}
|
|
107
|
+
className="gap-2"
|
|
105
108
|
>
|
|
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
|
-
|
|
109
|
+
<Checkbox
|
|
110
|
+
checked={isSelected}
|
|
111
|
+
tabIndex={-1}
|
|
112
|
+
aria-hidden
|
|
113
|
+
className="pointer-events-none"
|
|
114
|
+
/>
|
|
115
|
+
{option.label}
|
|
116
|
+
</CommandItem>
|
|
117
|
+
);
|
|
118
|
+
})}
|
|
119
|
+
</CommandGroup>
|
|
120
|
+
</CommandList>
|
|
121
|
+
</Command>
|
|
122
|
+
</PopoverContent>
|
|
123
|
+
</Popover>
|
|
124
|
+
{selected.length > 0 && (
|
|
125
|
+
<div className="flex flex-wrap gap-1">
|
|
126
|
+
{visible.map((value) => {
|
|
127
|
+
const label = options.find((opt) => opt.value === value)?.label || value;
|
|
128
|
+
return (
|
|
129
|
+
<Badge key={value} variant="secondary" className="gap-1 pr-1">
|
|
130
|
+
{label}
|
|
131
|
+
<button
|
|
132
|
+
type="button"
|
|
133
|
+
aria-label={`Remove ${label}`}
|
|
134
|
+
onClick={() =>
|
|
135
|
+
onSelectionChange(selected.filter((item) => item !== value))
|
|
136
|
+
}
|
|
137
|
+
className="rounded-full p-0.5 hover:bg-foreground/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring transition-colors"
|
|
132
138
|
>
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
</Command>
|
|
145
|
-
</PopoverContent>
|
|
146
|
-
</Popover>
|
|
139
|
+
<X className="size-3" />
|
|
140
|
+
</button>
|
|
141
|
+
</Badge>
|
|
142
|
+
);
|
|
143
|
+
})}
|
|
144
|
+
{remaining > 0 && (
|
|
145
|
+
<Badge variant="secondary">+{remaining} more</Badge>
|
|
146
|
+
)}
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
147
150
|
);
|
|
148
151
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
|
+
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
4
5
|
import { cn } from "../lib/utils";
|
|
5
6
|
import { Checkbox } from "./checkbox";
|
|
6
|
-
import { Popover, PopoverContent, PopoverTrigger } from "./popover";
|
|
7
7
|
|
|
8
8
|
export interface MultiSelectMenuOption {
|
|
9
9
|
value: string;
|
|
@@ -15,14 +15,10 @@ export interface MultiSelectMenuProps {
|
|
|
15
15
|
options: MultiSelectMenuOption[];
|
|
16
16
|
selected: string[];
|
|
17
17
|
onSelectedChange: (values: string[]) => void;
|
|
18
|
-
/** Trigger element rendered via Radix `asChild` — typically a Button. */
|
|
19
18
|
trigger: React.ReactNode;
|
|
20
|
-
/** Optional heading rendered above the option list. */
|
|
21
19
|
label?: string;
|
|
22
20
|
align?: "start" | "center" | "end";
|
|
23
|
-
/** Pixel width of the popover content. Defaults to 220. */
|
|
24
21
|
width?: number;
|
|
25
|
-
/** className for the popover content. */
|
|
26
22
|
contentClassName?: string;
|
|
27
23
|
emptyText?: string;
|
|
28
24
|
}
|
|
@@ -51,46 +47,58 @@ export function MultiSelectMenu({
|
|
|
51
47
|
}
|
|
52
48
|
|
|
53
49
|
return (
|
|
54
|
-
<
|
|
55
|
-
<
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
50
|
+
<DropdownMenuPrimitive.Root>
|
|
51
|
+
<DropdownMenuPrimitive.Trigger asChild>{trigger}</DropdownMenuPrimitive.Trigger>
|
|
52
|
+
<DropdownMenuPrimitive.Portal>
|
|
53
|
+
<DropdownMenuPrimitive.Content
|
|
54
|
+
align={align}
|
|
55
|
+
sideOffset={4}
|
|
56
|
+
style={{ width }}
|
|
57
|
+
className={cn(
|
|
58
|
+
"z-50 overflow-hidden rounded-xl border border-border bg-popover p-2 text-popover-foreground shadow-xl",
|
|
59
|
+
"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",
|
|
60
|
+
"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",
|
|
61
|
+
contentClassName
|
|
62
|
+
)}
|
|
63
|
+
>
|
|
64
|
+
{label && (
|
|
65
|
+
<DropdownMenuPrimitive.Label className="px-2 py-1.5 text-xs font-semibold text-muted-foreground">
|
|
66
|
+
{label}
|
|
67
|
+
</DropdownMenuPrimitive.Label>
|
|
68
|
+
)}
|
|
67
69
|
{options.length === 0 ? (
|
|
68
|
-
<p className="
|
|
70
|
+
<p className="px-2 py-1.5 text-sm text-muted-foreground">{emptyText}</p>
|
|
69
71
|
) : (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
"flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm select-none",
|
|
77
|
-
"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
|
78
|
-
option.disabled && "pointer-events-none opacity-50",
|
|
79
|
-
)}
|
|
80
|
-
>
|
|
81
|
-
<Checkbox
|
|
72
|
+
<DropdownMenuPrimitive.Group className="flex flex-col gap-0.5">
|
|
73
|
+
{options.map((option) => {
|
|
74
|
+
const checked = selectedSet.has(option.value);
|
|
75
|
+
return (
|
|
76
|
+
<DropdownMenuPrimitive.CheckboxItem
|
|
77
|
+
key={option.value}
|
|
82
78
|
checked={checked}
|
|
79
|
+
disabled={option.disabled ?? false}
|
|
80
|
+
onSelect={(e) => e.preventDefault()}
|
|
83
81
|
onCheckedChange={() => handleToggle(option.value)}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
82
|
+
className={cn(
|
|
83
|
+
"relative flex cursor-pointer select-none items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none",
|
|
84
|
+
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground",
|
|
85
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
|
|
86
|
+
)}
|
|
87
|
+
>
|
|
88
|
+
<Checkbox
|
|
89
|
+
checked={checked}
|
|
90
|
+
tabIndex={-1}
|
|
91
|
+
aria-hidden
|
|
92
|
+
className="pointer-events-none"
|
|
93
|
+
/>
|
|
94
|
+
<span className="flex-1">{option.label}</span>
|
|
95
|
+
</DropdownMenuPrimitive.CheckboxItem>
|
|
96
|
+
);
|
|
97
|
+
})}
|
|
98
|
+
</DropdownMenuPrimitive.Group>
|
|
91
99
|
)}
|
|
92
|
-
</
|
|
93
|
-
</
|
|
94
|
-
</
|
|
100
|
+
</DropdownMenuPrimitive.Content>
|
|
101
|
+
</DropdownMenuPrimitive.Portal>
|
|
102
|
+
</DropdownMenuPrimitive.Root>
|
|
95
103
|
);
|
|
96
104
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { NativeSelect } from "./native-select";
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: "Components/NativeSelect",
|
|
6
|
+
component: NativeSelect,
|
|
7
|
+
tags: ["autodocs"],
|
|
8
|
+
} satisfies Meta<typeof NativeSelect>;
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
type Story = StoryObj<typeof meta>;
|
|
12
|
+
|
|
13
|
+
export const Default: Story = {
|
|
14
|
+
render: () => (
|
|
15
|
+
<NativeSelect className="w-64" defaultValue="">
|
|
16
|
+
<option value="" disabled>
|
|
17
|
+
Pick a fruit…
|
|
18
|
+
</option>
|
|
19
|
+
<option value="apple">Apple</option>
|
|
20
|
+
<option value="banana">Banana</option>
|
|
21
|
+
<option value="cherry">Cherry</option>
|
|
22
|
+
</NativeSelect>
|
|
23
|
+
),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const Sizes: Story = {
|
|
27
|
+
render: () => (
|
|
28
|
+
<div className="flex w-64 flex-col gap-3">
|
|
29
|
+
<NativeSelect size="sm" defaultValue="small">
|
|
30
|
+
<option value="small">Small</option>
|
|
31
|
+
</NativeSelect>
|
|
32
|
+
<NativeSelect size="default" defaultValue="default">
|
|
33
|
+
<option value="default">Default</option>
|
|
34
|
+
</NativeSelect>
|
|
35
|
+
<NativeSelect size="lg" defaultValue="large">
|
|
36
|
+
<option value="large">Large</option>
|
|
37
|
+
</NativeSelect>
|
|
38
|
+
</div>
|
|
39
|
+
),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const Invalid: Story = {
|
|
43
|
+
render: () => (
|
|
44
|
+
<NativeSelect className="w-64" aria-invalid defaultValue="">
|
|
45
|
+
<option value="" disabled>
|
|
46
|
+
Required field…
|
|
47
|
+
</option>
|
|
48
|
+
<option value="a">Option A</option>
|
|
49
|
+
<option value="b">Option B</option>
|
|
50
|
+
</NativeSelect>
|
|
51
|
+
),
|
|
52
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { ChevronDown } from "lucide-react";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
|
|
6
|
+
const nativeSelectVariants = cva(
|
|
7
|
+
"w-full appearance-none rounded-xl border border-border bg-background pr-9 text-sm text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:focus-visible:ring-destructive/40",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
size: {
|
|
11
|
+
sm: "h-9 pl-3",
|
|
12
|
+
default: "h-11 pl-4",
|
|
13
|
+
lg: "h-12 pl-5",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
size: "default",
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
type NativeSelectProps = Omit<React.SelectHTMLAttributes<HTMLSelectElement>, "size"> &
|
|
23
|
+
VariantProps<typeof nativeSelectVariants>;
|
|
24
|
+
|
|
25
|
+
const NativeSelect = React.forwardRef<HTMLSelectElement, NativeSelectProps>(
|
|
26
|
+
({ className, size, children, ...props }, ref) => (
|
|
27
|
+
<div className="relative inline-flex w-full">
|
|
28
|
+
<select
|
|
29
|
+
ref={ref}
|
|
30
|
+
data-slot="native-select"
|
|
31
|
+
className={cn(nativeSelectVariants({ size }), className)}
|
|
32
|
+
{...props}
|
|
33
|
+
>
|
|
34
|
+
{children}
|
|
35
|
+
</select>
|
|
36
|
+
<ChevronDown
|
|
37
|
+
aria-hidden
|
|
38
|
+
className="pointer-events-none absolute right-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground"
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
)
|
|
42
|
+
);
|
|
43
|
+
NativeSelect.displayName = "NativeSelect";
|
|
44
|
+
|
|
45
|
+
export { NativeSelect, nativeSelectVariants };
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
ChevronLeftIcon,
|
|
4
|
+
ChevronRightIcon,
|
|
5
|
+
ChevronsLeftIcon,
|
|
6
|
+
ChevronsRightIcon,
|
|
7
|
+
MoreHorizontalIcon,
|
|
8
|
+
} from "lucide-react";
|
|
3
9
|
|
|
4
10
|
import { cn } from "../lib/utils";
|
|
5
11
|
import { Button, buttonVariants } from "./button";
|
|
@@ -32,20 +38,33 @@ function PaginationItem({ ...props }: React.ComponentProps<"li">) {
|
|
|
32
38
|
|
|
33
39
|
type PaginationLinkProps = {
|
|
34
40
|
isActive?: boolean;
|
|
41
|
+
disabled?: boolean;
|
|
35
42
|
} & Pick<React.ComponentProps<typeof Button>, "size"> &
|
|
36
43
|
React.ComponentProps<"a">;
|
|
37
44
|
|
|
38
|
-
function PaginationLink({
|
|
45
|
+
function PaginationLink({
|
|
46
|
+
className,
|
|
47
|
+
isActive,
|
|
48
|
+
disabled,
|
|
49
|
+
size = "icon",
|
|
50
|
+
onClick,
|
|
51
|
+
...props
|
|
52
|
+
}: PaginationLinkProps) {
|
|
39
53
|
return (
|
|
40
54
|
<a
|
|
41
55
|
aria-current={isActive ? "page" : undefined}
|
|
56
|
+
aria-disabled={disabled || undefined}
|
|
42
57
|
data-slot="pagination-link"
|
|
43
58
|
data-active={isActive}
|
|
59
|
+
tabIndex={disabled ? -1 : undefined}
|
|
60
|
+
onClick={disabled ? (e) => e.preventDefault() : onClick}
|
|
44
61
|
className={cn(
|
|
45
62
|
buttonVariants({
|
|
46
|
-
variant: isActive ? "
|
|
63
|
+
variant: isActive ? "default" : "ghost",
|
|
47
64
|
size,
|
|
48
65
|
}),
|
|
66
|
+
isActive && "pointer-events-none",
|
|
67
|
+
disabled && "pointer-events-none opacity-50",
|
|
49
68
|
className
|
|
50
69
|
)}
|
|
51
70
|
{...props}
|
|
@@ -53,7 +72,26 @@ function PaginationLink({ className, isActive, size = "icon", ...props }: Pagina
|
|
|
53
72
|
);
|
|
54
73
|
}
|
|
55
74
|
|
|
56
|
-
function
|
|
75
|
+
function PaginationFirst({
|
|
76
|
+
className,
|
|
77
|
+
...props
|
|
78
|
+
}: React.ComponentProps<typeof PaginationLink>) {
|
|
79
|
+
return (
|
|
80
|
+
<PaginationLink
|
|
81
|
+
aria-label="Go to first page"
|
|
82
|
+
size="icon"
|
|
83
|
+
className={cn(className)}
|
|
84
|
+
{...props}
|
|
85
|
+
>
|
|
86
|
+
<ChevronsLeftIcon />
|
|
87
|
+
</PaginationLink>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function PaginationPrevious({
|
|
92
|
+
className,
|
|
93
|
+
...props
|
|
94
|
+
}: React.ComponentProps<typeof PaginationLink>) {
|
|
57
95
|
return (
|
|
58
96
|
<PaginationLink
|
|
59
97
|
aria-label="Go to previous page"
|
|
@@ -67,7 +105,10 @@ function PaginationPrevious({ className, ...props }: React.ComponentProps<typeof
|
|
|
67
105
|
);
|
|
68
106
|
}
|
|
69
107
|
|
|
70
|
-
function PaginationNext({
|
|
108
|
+
function PaginationNext({
|
|
109
|
+
className,
|
|
110
|
+
...props
|
|
111
|
+
}: React.ComponentProps<typeof PaginationLink>) {
|
|
71
112
|
return (
|
|
72
113
|
<PaginationLink
|
|
73
114
|
aria-label="Go to next page"
|
|
@@ -81,6 +122,22 @@ function PaginationNext({ className, ...props }: React.ComponentProps<typeof Pag
|
|
|
81
122
|
);
|
|
82
123
|
}
|
|
83
124
|
|
|
125
|
+
function PaginationLast({
|
|
126
|
+
className,
|
|
127
|
+
...props
|
|
128
|
+
}: React.ComponentProps<typeof PaginationLink>) {
|
|
129
|
+
return (
|
|
130
|
+
<PaginationLink
|
|
131
|
+
aria-label="Go to last page"
|
|
132
|
+
size="icon"
|
|
133
|
+
className={cn(className)}
|
|
134
|
+
{...props}
|
|
135
|
+
>
|
|
136
|
+
<ChevronsRightIcon />
|
|
137
|
+
</PaginationLink>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
84
141
|
function PaginationEllipsis({ className, ...props }: React.ComponentProps<"span">) {
|
|
85
142
|
return (
|
|
86
143
|
<span
|
|
@@ -100,7 +157,9 @@ export {
|
|
|
100
157
|
PaginationContent,
|
|
101
158
|
PaginationLink,
|
|
102
159
|
PaginationItem,
|
|
160
|
+
PaginationFirst,
|
|
103
161
|
PaginationPrevious,
|
|
104
162
|
PaginationNext,
|
|
163
|
+
PaginationLast,
|
|
105
164
|
PaginationEllipsis,
|
|
106
165
|
};
|
|
@@ -5,8 +5,9 @@ import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
|
5
5
|
import { cn } from "../lib/utils";
|
|
6
6
|
|
|
7
7
|
const Popover = PopoverPrimitive.Root;
|
|
8
|
-
|
|
9
8
|
const PopoverTrigger = PopoverPrimitive.Trigger;
|
|
9
|
+
const PopoverAnchor = PopoverPrimitive.Anchor;
|
|
10
|
+
const PopoverClose = PopoverPrimitive.Close;
|
|
10
11
|
|
|
11
12
|
const PopoverContent = React.forwardRef<
|
|
12
13
|
React.ElementRef<typeof PopoverPrimitive.Content>,
|
|
@@ -15,10 +16,12 @@ const PopoverContent = React.forwardRef<
|
|
|
15
16
|
<PopoverPrimitive.Portal>
|
|
16
17
|
<PopoverPrimitive.Content
|
|
17
18
|
ref={ref}
|
|
19
|
+
data-slot="popover-content"
|
|
18
20
|
align={align}
|
|
19
21
|
sideOffset={sideOffset}
|
|
20
22
|
className={cn(
|
|
21
|
-
"z-50 w-72 rounded-xl bg-
|
|
23
|
+
"z-50 w-72 rounded-xl border border-border bg-popover p-4 text-popover-foreground shadow-xl outline-none",
|
|
24
|
+
"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",
|
|
22
25
|
className
|
|
23
26
|
)}
|
|
24
27
|
{...props}
|
|
@@ -27,4 +30,64 @@ const PopoverContent = React.forwardRef<
|
|
|
27
30
|
));
|
|
28
31
|
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
const PopoverArrow = React.forwardRef<
|
|
34
|
+
React.ElementRef<typeof PopoverPrimitive.Arrow>,
|
|
35
|
+
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Arrow>
|
|
36
|
+
>(({ className, ...props }, ref) => (
|
|
37
|
+
<PopoverPrimitive.Arrow
|
|
38
|
+
ref={ref}
|
|
39
|
+
data-slot="popover-arrow"
|
|
40
|
+
className={cn("fill-popover stroke-border", className)}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
));
|
|
44
|
+
PopoverArrow.displayName = PopoverPrimitive.Arrow.displayName;
|
|
45
|
+
|
|
46
|
+
const PopoverHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
47
|
+
({ className, ...props }, ref) => (
|
|
48
|
+
<div
|
|
49
|
+
ref={ref}
|
|
50
|
+
data-slot="popover-header"
|
|
51
|
+
className={cn("flex flex-col space-y-1.5 pb-3", className)}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
)
|
|
55
|
+
);
|
|
56
|
+
PopoverHeader.displayName = "PopoverHeader";
|
|
57
|
+
|
|
58
|
+
const PopoverTitle = React.forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
|
59
|
+
({ className, ...props }, ref) => (
|
|
60
|
+
<h3
|
|
61
|
+
ref={ref}
|
|
62
|
+
data-slot="popover-title"
|
|
63
|
+
className={cn("text-sm font-semibold leading-none", className)}
|
|
64
|
+
{...props}
|
|
65
|
+
/>
|
|
66
|
+
)
|
|
67
|
+
);
|
|
68
|
+
PopoverTitle.displayName = "PopoverTitle";
|
|
69
|
+
|
|
70
|
+
const PopoverDescription = React.forwardRef<
|
|
71
|
+
HTMLParagraphElement,
|
|
72
|
+
React.HTMLAttributes<HTMLParagraphElement>
|
|
73
|
+
>(({ className, ...props }, ref) => (
|
|
74
|
+
<p
|
|
75
|
+
ref={ref}
|
|
76
|
+
data-slot="popover-description"
|
|
77
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
));
|
|
81
|
+
PopoverDescription.displayName = "PopoverDescription";
|
|
82
|
+
|
|
83
|
+
export {
|
|
84
|
+
Popover,
|
|
85
|
+
PopoverTrigger,
|
|
86
|
+
PopoverContent,
|
|
87
|
+
PopoverAnchor,
|
|
88
|
+
PopoverClose,
|
|
89
|
+
PopoverArrow,
|
|
90
|
+
PopoverHeader,
|
|
91
|
+
PopoverTitle,
|
|
92
|
+
PopoverDescription,
|
|
93
|
+
};
|
|
@@ -4,20 +4,43 @@ import * as React from "react";
|
|
|
4
4
|
import * as ProgressPrimitive from "@radix-ui/react-progress";
|
|
5
5
|
import { cn } from "../lib/utils";
|
|
6
6
|
|
|
7
|
+
type ProgressTone = "primary" | "success" | "warning" | "destructive";
|
|
8
|
+
|
|
9
|
+
type ProgressProps = React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> & {
|
|
10
|
+
tone?: ProgressTone;
|
|
11
|
+
size?: "sm" | "default" | "lg";
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const toneClasses: Record<ProgressTone, string> = {
|
|
15
|
+
primary: "bg-primary",
|
|
16
|
+
success: "bg-success",
|
|
17
|
+
warning: "bg-warning",
|
|
18
|
+
destructive: "bg-destructive",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const sizeClasses = {
|
|
22
|
+
sm: "h-1",
|
|
23
|
+
default: "h-2",
|
|
24
|
+
lg: "h-3",
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
7
27
|
const Progress = React.forwardRef<
|
|
8
28
|
React.ElementRef<typeof ProgressPrimitive.Root>,
|
|
9
|
-
|
|
10
|
-
>(({ className, value, ...props }, ref) => (
|
|
29
|
+
ProgressProps
|
|
30
|
+
>(({ className, value, tone = "primary", size = "default", ...props }, ref) => (
|
|
11
31
|
<ProgressPrimitive.Root
|
|
12
32
|
ref={ref}
|
|
33
|
+
data-slot="progress"
|
|
13
34
|
className={cn(
|
|
14
|
-
"relative
|
|
35
|
+
"relative w-full overflow-hidden rounded-full bg-muted",
|
|
36
|
+
sizeClasses[size],
|
|
15
37
|
className
|
|
16
38
|
)}
|
|
17
39
|
{...props}
|
|
18
40
|
>
|
|
19
41
|
<ProgressPrimitive.Indicator
|
|
20
|
-
|
|
42
|
+
data-slot="progress-indicator"
|
|
43
|
+
className={cn("h-full w-full flex-1 transition-transform duration-500 ease-out", toneClasses[tone])}
|
|
21
44
|
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
|
22
45
|
/>
|
|
23
46
|
</ProgressPrimitive.Root>
|