@bearmenu/ui 0.5.2 → 0.6.1
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 +4 -1
- 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 +3 -1
- 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 +16 -3
- 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/scroll-fade.d.ts +15 -0
- package/dist/components/scroll-fade.js +116 -0
- 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 +1 -1
- 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 +45 -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/scroll-fade.stories.tsx +98 -0
- package/src/components/scroll-fade.tsx +127 -0
- 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
package/src/components/card.tsx
CHANGED
|
@@ -1,21 +1,42 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
3
|
import { cn } from "../lib/utils";
|
|
3
4
|
import { textVariants } from "./text";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
const cardVariants = cva("rounded-3xl text-card-foreground transition-colors", {
|
|
7
|
+
variants: {
|
|
8
|
+
variant: {
|
|
9
|
+
surface: "bg-card border border-border",
|
|
10
|
+
glass: "glass",
|
|
11
|
+
outline: "bg-transparent border border-border",
|
|
12
|
+
muted: "bg-muted border border-transparent",
|
|
13
|
+
},
|
|
14
|
+
size: {
|
|
15
|
+
sm: "[&_[data-slot=card-header]]:p-4 [&_[data-slot=card-content]]:p-4 [&_[data-slot=card-content]]:pt-0 [&_[data-slot=card-footer]]:p-4 [&_[data-slot=card-footer]]:pt-0",
|
|
16
|
+
default:
|
|
17
|
+
"[&_[data-slot=card-header]]:p-6 [&_[data-slot=card-content]]:p-6 [&_[data-slot=card-content]]:pt-0 [&_[data-slot=card-footer]]:p-6 [&_[data-slot=card-footer]]:pt-0",
|
|
18
|
+
lg: "[&_[data-slot=card-header]]:p-8 [&_[data-slot=card-content]]:p-8 [&_[data-slot=card-content]]:pt-0 [&_[data-slot=card-footer]]:p-8 [&_[data-slot=card-footer]]:pt-0",
|
|
19
|
+
},
|
|
20
|
+
interactive: {
|
|
21
|
+
true: "cursor-pointer hover:border-primary/40 hover:-translate-y-0.5 transition-[transform,border-color] duration-200",
|
|
22
|
+
false: "",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
variant: "surface",
|
|
27
|
+
size: "default",
|
|
28
|
+
interactive: false,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
6
31
|
|
|
7
32
|
const Card = React.forwardRef<
|
|
8
33
|
HTMLDivElement,
|
|
9
|
-
React.HTMLAttributes<HTMLDivElement> &
|
|
10
|
-
>(({ className, variant
|
|
34
|
+
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof cardVariants>
|
|
35
|
+
>(({ className, variant, size, interactive, ...props }, ref) => (
|
|
11
36
|
<div
|
|
12
37
|
ref={ref}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
? "glass rounded-[22px] border border-transparent text-card-foreground transition-[transform,border-color] duration-300 [@media(hover:hover)]:hover:-translate-y-0.5 hover:border-primary/40"
|
|
16
|
-
: "bg-surface rounded-[22px] border border-border text-card-foreground transition-all duration-200 hover:border-primary/40 hover:-translate-y-0.5",
|
|
17
|
-
className
|
|
18
|
-
)}
|
|
38
|
+
data-slot="card"
|
|
39
|
+
className={cn(cardVariants({ variant, size, interactive }), className)}
|
|
19
40
|
{...props}
|
|
20
41
|
/>
|
|
21
42
|
));
|
|
@@ -23,7 +44,12 @@ Card.displayName = "Card";
|
|
|
23
44
|
|
|
24
45
|
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
25
46
|
({ className, ...props }, ref) => (
|
|
26
|
-
<div
|
|
47
|
+
<div
|
|
48
|
+
ref={ref}
|
|
49
|
+
data-slot="card-header"
|
|
50
|
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
27
53
|
)
|
|
28
54
|
);
|
|
29
55
|
CardHeader.displayName = "CardHeader";
|
|
@@ -32,6 +58,7 @@ const CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivE
|
|
|
32
58
|
({ className, ...props }, ref) => (
|
|
33
59
|
<div
|
|
34
60
|
ref={ref}
|
|
61
|
+
data-slot="card-title"
|
|
35
62
|
className={cn(textVariants({ variant: "h4", wrap: "balance" }), className)}
|
|
36
63
|
{...props}
|
|
37
64
|
/>
|
|
@@ -43,6 +70,7 @@ const CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HT
|
|
|
43
70
|
({ className, ...props }, ref) => (
|
|
44
71
|
<div
|
|
45
72
|
ref={ref}
|
|
73
|
+
data-slot="card-description"
|
|
46
74
|
className={cn("text-sm", textVariants({ color: "muted", wrap: "pretty" }), className)}
|
|
47
75
|
{...props}
|
|
48
76
|
/>
|
|
@@ -52,7 +80,7 @@ CardDescription.displayName = "CardDescription";
|
|
|
52
80
|
|
|
53
81
|
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
54
82
|
({ className, ...props }, ref) => (
|
|
55
|
-
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
|
83
|
+
<div ref={ref} data-slot="card-content" className={cn("p-6 pt-0", className)} {...props} />
|
|
56
84
|
)
|
|
57
85
|
);
|
|
58
86
|
CardContent.displayName = "CardContent";
|
|
@@ -71,9 +99,23 @@ CardAction.displayName = "CardAction";
|
|
|
71
99
|
|
|
72
100
|
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
73
101
|
({ className, ...props }, ref) => (
|
|
74
|
-
<div
|
|
102
|
+
<div
|
|
103
|
+
ref={ref}
|
|
104
|
+
data-slot="card-footer"
|
|
105
|
+
className={cn("flex items-center p-6 pt-0", className)}
|
|
106
|
+
{...props}
|
|
107
|
+
/>
|
|
75
108
|
)
|
|
76
109
|
);
|
|
77
110
|
CardFooter.displayName = "CardFooter";
|
|
78
111
|
|
|
79
|
-
export {
|
|
112
|
+
export {
|
|
113
|
+
Card,
|
|
114
|
+
CardHeader,
|
|
115
|
+
CardFooter,
|
|
116
|
+
CardTitle,
|
|
117
|
+
CardAction,
|
|
118
|
+
CardDescription,
|
|
119
|
+
CardContent,
|
|
120
|
+
cardVariants,
|
|
121
|
+
};
|
|
@@ -16,6 +16,8 @@ type CarouselProps = {
|
|
|
16
16
|
opts?: CarouselOptions;
|
|
17
17
|
plugins?: CarouselPlugin;
|
|
18
18
|
orientation?: "horizontal" | "vertical";
|
|
19
|
+
/** Gap between slides in pixels. Defaults to 16. */
|
|
20
|
+
gap?: number;
|
|
19
21
|
setApi?: (api: CarouselApi) => void;
|
|
20
22
|
};
|
|
21
23
|
|
|
@@ -24,19 +26,21 @@ type CarouselContextProps = {
|
|
|
24
26
|
api: ReturnType<typeof useEmblaCarousel>[1];
|
|
25
27
|
scrollPrev: () => void;
|
|
26
28
|
scrollNext: () => void;
|
|
29
|
+
scrollTo: (index: number) => void;
|
|
27
30
|
canScrollPrev: boolean;
|
|
28
31
|
canScrollNext: boolean;
|
|
32
|
+
selectedIndex: number;
|
|
33
|
+
slideCount: number;
|
|
34
|
+
gap: number;
|
|
29
35
|
} & CarouselProps;
|
|
30
36
|
|
|
31
37
|
const CarouselContext = React.createContext<CarouselContextProps | null>(null);
|
|
32
38
|
|
|
33
39
|
function useCarousel() {
|
|
34
40
|
const context = React.useContext(CarouselContext);
|
|
35
|
-
|
|
36
41
|
if (!context) {
|
|
37
42
|
throw new Error("useCarousel must be used within a <Carousel />");
|
|
38
43
|
}
|
|
39
|
-
|
|
40
44
|
return context;
|
|
41
45
|
}
|
|
42
46
|
|
|
@@ -45,6 +49,7 @@ function Carousel({
|
|
|
45
49
|
opts,
|
|
46
50
|
setApi,
|
|
47
51
|
plugins,
|
|
52
|
+
gap = 16,
|
|
48
53
|
className,
|
|
49
54
|
children,
|
|
50
55
|
...props
|
|
@@ -58,11 +63,15 @@ function Carousel({
|
|
|
58
63
|
);
|
|
59
64
|
const [canScrollPrev, setCanScrollPrev] = React.useState(false);
|
|
60
65
|
const [canScrollNext, setCanScrollNext] = React.useState(false);
|
|
66
|
+
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
67
|
+
const [slideCount, setSlideCount] = React.useState(0);
|
|
61
68
|
|
|
62
69
|
const onSelect = React.useCallback((api: CarouselApi) => {
|
|
63
70
|
if (!api) return;
|
|
64
71
|
setCanScrollPrev(api.canScrollPrev());
|
|
65
72
|
setCanScrollNext(api.canScrollNext());
|
|
73
|
+
setSelectedIndex(api.selectedScrollSnap());
|
|
74
|
+
setSlideCount(api.scrollSnapList().length);
|
|
66
75
|
}, []);
|
|
67
76
|
|
|
68
77
|
const scrollPrev = React.useCallback(() => {
|
|
@@ -73,6 +82,13 @@ function Carousel({
|
|
|
73
82
|
api?.scrollNext();
|
|
74
83
|
}, [api]);
|
|
75
84
|
|
|
85
|
+
const scrollTo = React.useCallback(
|
|
86
|
+
(index: number) => {
|
|
87
|
+
api?.scrollTo(index);
|
|
88
|
+
},
|
|
89
|
+
[api]
|
|
90
|
+
);
|
|
91
|
+
|
|
76
92
|
const handleKeyDown = React.useCallback(
|
|
77
93
|
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
78
94
|
if (event.key === "ArrowLeft") {
|
|
@@ -96,7 +112,6 @@ function Carousel({
|
|
|
96
112
|
onSelect(api);
|
|
97
113
|
api.on("reInit", onSelect);
|
|
98
114
|
api.on("select", onSelect);
|
|
99
|
-
|
|
100
115
|
return () => {
|
|
101
116
|
api?.off("select", onSelect);
|
|
102
117
|
};
|
|
@@ -106,13 +121,17 @@ function Carousel({
|
|
|
106
121
|
<CarouselContext.Provider
|
|
107
122
|
value={{
|
|
108
123
|
carouselRef,
|
|
109
|
-
api
|
|
124
|
+
api,
|
|
110
125
|
opts,
|
|
111
126
|
orientation: orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
|
|
127
|
+
gap,
|
|
112
128
|
scrollPrev,
|
|
113
129
|
scrollNext,
|
|
130
|
+
scrollTo,
|
|
114
131
|
canScrollPrev,
|
|
115
132
|
canScrollNext,
|
|
133
|
+
selectedIndex,
|
|
134
|
+
slideCount,
|
|
116
135
|
}}
|
|
117
136
|
>
|
|
118
137
|
<div
|
|
@@ -130,31 +149,36 @@ function Carousel({
|
|
|
130
149
|
}
|
|
131
150
|
|
|
132
151
|
function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
133
|
-
const { carouselRef, orientation } = useCarousel();
|
|
152
|
+
const { carouselRef, orientation, gap } = useCarousel();
|
|
134
153
|
|
|
135
154
|
return (
|
|
136
155
|
<div ref={carouselRef} className="overflow-hidden" data-slot="carousel-content">
|
|
137
156
|
<div
|
|
138
|
-
className={cn("flex", orientation === "horizontal" ? "
|
|
157
|
+
className={cn("flex", orientation === "horizontal" ? "" : "flex-col", className)}
|
|
158
|
+
style={
|
|
159
|
+
orientation === "horizontal"
|
|
160
|
+
? { marginLeft: `-${gap}px` }
|
|
161
|
+
: { marginTop: `-${gap}px` }
|
|
162
|
+
}
|
|
139
163
|
{...props}
|
|
140
164
|
/>
|
|
141
165
|
</div>
|
|
142
166
|
);
|
|
143
167
|
}
|
|
144
168
|
|
|
145
|
-
function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
|
|
146
|
-
const { orientation } = useCarousel();
|
|
169
|
+
function CarouselItem({ className, style, ...props }: React.ComponentProps<"div">) {
|
|
170
|
+
const { orientation, gap } = useCarousel();
|
|
147
171
|
|
|
148
172
|
return (
|
|
149
173
|
<div
|
|
150
174
|
role="group"
|
|
151
175
|
aria-roledescription="slide"
|
|
152
176
|
data-slot="carousel-item"
|
|
153
|
-
className={cn(
|
|
154
|
-
|
|
155
|
-
orientation === "horizontal" ?
|
|
156
|
-
|
|
157
|
-
|
|
177
|
+
className={cn("min-w-0 shrink-0 grow-0 basis-full", className)}
|
|
178
|
+
style={{
|
|
179
|
+
...(orientation === "horizontal" ? { paddingLeft: `${gap}px` } : { paddingTop: `${gap}px` }),
|
|
180
|
+
...style,
|
|
181
|
+
}}
|
|
158
182
|
{...props}
|
|
159
183
|
/>
|
|
160
184
|
);
|
|
@@ -220,6 +244,51 @@ function CarouselNext({
|
|
|
220
244
|
);
|
|
221
245
|
}
|
|
222
246
|
|
|
247
|
+
function CarouselDots({ className, ...props }: React.ComponentProps<"div">) {
|
|
248
|
+
const { slideCount, selectedIndex, scrollTo } = useCarousel();
|
|
249
|
+
|
|
250
|
+
if (slideCount === 0) return null;
|
|
251
|
+
|
|
252
|
+
return (
|
|
253
|
+
<div
|
|
254
|
+
data-slot="carousel-dots"
|
|
255
|
+
className={cn("flex items-center justify-center gap-1.5", className)}
|
|
256
|
+
{...props}
|
|
257
|
+
>
|
|
258
|
+
{Array.from({ length: slideCount }).map((_, i) => (
|
|
259
|
+
<button
|
|
260
|
+
key={i}
|
|
261
|
+
type="button"
|
|
262
|
+
aria-label={`Go to slide ${i + 1}`}
|
|
263
|
+
aria-current={i === selectedIndex ? "true" : undefined}
|
|
264
|
+
onClick={() => scrollTo(i)}
|
|
265
|
+
className={cn(
|
|
266
|
+
"size-2 rounded-full transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
267
|
+
i === selectedIndex ? "w-6 bg-primary" : "bg-muted-foreground/30 hover:bg-muted-foreground/50"
|
|
268
|
+
)}
|
|
269
|
+
/>
|
|
270
|
+
))}
|
|
271
|
+
</div>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function CarouselCounter({ className, ...props }: React.ComponentProps<"div">) {
|
|
276
|
+
const { slideCount, selectedIndex } = useCarousel();
|
|
277
|
+
|
|
278
|
+
if (slideCount === 0) return null;
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<div
|
|
282
|
+
data-slot="carousel-counter"
|
|
283
|
+
aria-live="polite"
|
|
284
|
+
className={cn("text-xs tabular-nums text-muted-foreground", className)}
|
|
285
|
+
{...props}
|
|
286
|
+
>
|
|
287
|
+
{selectedIndex + 1} / {slideCount}
|
|
288
|
+
</div>
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
223
292
|
export {
|
|
224
293
|
type CarouselApi,
|
|
225
294
|
Carousel,
|
|
@@ -227,4 +296,7 @@ export {
|
|
|
227
296
|
CarouselItem,
|
|
228
297
|
CarouselPrevious,
|
|
229
298
|
CarouselNext,
|
|
299
|
+
CarouselDots,
|
|
300
|
+
CarouselCounter,
|
|
301
|
+
useCarousel,
|
|
230
302
|
};
|
|
@@ -0,0 +1,58 @@
|
|
|
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 { Checkbox } from "./checkbox";
|
|
5
|
+
|
|
6
|
+
describe("Checkbox", () => {
|
|
7
|
+
it("renders an unchecked checkbox by default", () => {
|
|
8
|
+
render(<Checkbox aria-label="Accept" />);
|
|
9
|
+
const cb = screen.getByRole("checkbox", { name: /accept/i });
|
|
10
|
+
expect(cb).toHaveAttribute("data-state", "unchecked");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("renders checked when `checked` is true", () => {
|
|
14
|
+
render(<Checkbox aria-label="Accept" checked onCheckedChange={() => {}} />);
|
|
15
|
+
expect(screen.getByRole("checkbox")).toHaveAttribute("data-state", "checked");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("renders indeterminate state", () => {
|
|
19
|
+
render(<Checkbox aria-label="x" checked="indeterminate" onCheckedChange={() => {}} />);
|
|
20
|
+
expect(screen.getByRole("checkbox")).toHaveAttribute("data-state", "indeterminate");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("calls onCheckedChange when clicked", async () => {
|
|
24
|
+
const user = userEvent.setup();
|
|
25
|
+
const onCheckedChange = vi.fn();
|
|
26
|
+
render(<Checkbox aria-label="x" onCheckedChange={onCheckedChange} />);
|
|
27
|
+
await user.click(screen.getByRole("checkbox"));
|
|
28
|
+
expect(onCheckedChange).toHaveBeenCalledWith(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("calls onCheckedChange via Space key", async () => {
|
|
32
|
+
const user = userEvent.setup();
|
|
33
|
+
const onCheckedChange = vi.fn();
|
|
34
|
+
render(<Checkbox aria-label="x" onCheckedChange={onCheckedChange} />);
|
|
35
|
+
const cb = screen.getByRole("checkbox");
|
|
36
|
+
cb.focus();
|
|
37
|
+
await user.keyboard(" ");
|
|
38
|
+
expect(onCheckedChange).toHaveBeenCalledWith(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("does not fire when disabled", async () => {
|
|
42
|
+
const user = userEvent.setup();
|
|
43
|
+
const onCheckedChange = vi.fn();
|
|
44
|
+
render(<Checkbox aria-label="x" disabled onCheckedChange={onCheckedChange} />);
|
|
45
|
+
await user.click(screen.getByRole("checkbox"));
|
|
46
|
+
expect(onCheckedChange).not.toHaveBeenCalled();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("forwards the aria-invalid attribute", () => {
|
|
50
|
+
render(<Checkbox aria-label="x" aria-invalid />);
|
|
51
|
+
expect(screen.getByRole("checkbox")).toHaveAttribute("aria-invalid", "true");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("merges className", () => {
|
|
55
|
+
render(<Checkbox aria-label="x" className="custom-class" />);
|
|
56
|
+
expect(screen.getByRole("checkbox").className).toContain("custom-class");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
|
5
|
-
import { Check } from "lucide-react";
|
|
5
|
+
import { Check, Minus } from "lucide-react";
|
|
6
6
|
import { cn } from "../lib/utils";
|
|
7
7
|
|
|
8
8
|
const Checkbox = React.forwardRef<
|
|
@@ -11,14 +11,19 @@ const Checkbox = React.forwardRef<
|
|
|
11
11
|
>(({ className, ...props }, ref) => (
|
|
12
12
|
<CheckboxPrimitive.Root
|
|
13
13
|
ref={ref}
|
|
14
|
+
data-slot="checkbox"
|
|
14
15
|
className={cn(
|
|
15
|
-
"peer
|
|
16
|
+
"peer size-5 shrink-0 rounded-md border border-border bg-background transition-all duration-150 ease-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=checked]:border-primary data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-primary-foreground data-[state=indeterminate]:border-primary hover:border-primary/50 aria-invalid:border-destructive aria-invalid:focus-visible:ring-destructive/40",
|
|
16
17
|
className
|
|
17
18
|
)}
|
|
18
19
|
{...props}
|
|
19
20
|
>
|
|
20
21
|
<CheckboxPrimitive.Indicator className={cn("flex items-center justify-center text-current")}>
|
|
21
|
-
|
|
22
|
+
{props.checked === "indeterminate" ? (
|
|
23
|
+
<Minus className="size-3.5 animate-in fade-in-0 zoom-in-50 duration-200 ease-out" strokeWidth={3} />
|
|
24
|
+
) : (
|
|
25
|
+
<Check className="size-3.5 animate-in fade-in-0 zoom-in-50 duration-200 ease-out" strokeWidth={3} />
|
|
26
|
+
)}
|
|
22
27
|
</CheckboxPrimitive.Indicator>
|
|
23
28
|
</CheckboxPrimitive.Root>
|
|
24
29
|
));
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { CircularProgress } from "./circular-progress";
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: "Components/CircularProgress",
|
|
6
|
+
component: CircularProgress,
|
|
7
|
+
tags: ["autodocs"],
|
|
8
|
+
} satisfies Meta<typeof CircularProgress>;
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
type Story = StoryObj<typeof meta>;
|
|
12
|
+
|
|
13
|
+
export const Default: Story = { args: { value: 60, showLabel: true } };
|
|
14
|
+
|
|
15
|
+
export const Sizes: Story = {
|
|
16
|
+
render: () => (
|
|
17
|
+
<div className="flex items-center gap-4">
|
|
18
|
+
<CircularProgress value={75} size={24} />
|
|
19
|
+
<CircularProgress value={75} size={40} />
|
|
20
|
+
<CircularProgress value={75} size={56} showLabel />
|
|
21
|
+
<CircularProgress value={75} size={80} showLabel strokeWidth={6} />
|
|
22
|
+
</div>
|
|
23
|
+
),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const Tones: Story = {
|
|
27
|
+
render: () => (
|
|
28
|
+
<div className="flex items-center gap-4">
|
|
29
|
+
<CircularProgress value={60} tone="primary" showLabel />
|
|
30
|
+
<CircularProgress value={60} tone="success" showLabel />
|
|
31
|
+
<CircularProgress value={60} tone="warning" showLabel />
|
|
32
|
+
<CircularProgress value={60} tone="destructive" showLabel />
|
|
33
|
+
</div>
|
|
34
|
+
),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const Indeterminate: Story = {
|
|
38
|
+
args: { indeterminate: true, size: 40 },
|
|
39
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
|
|
6
|
+
type CircularProgressProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
7
|
+
value?: number;
|
|
8
|
+
max?: number;
|
|
9
|
+
size?: number;
|
|
10
|
+
strokeWidth?: number;
|
|
11
|
+
tone?: "primary" | "success" | "warning" | "destructive";
|
|
12
|
+
showLabel?: boolean;
|
|
13
|
+
/** Indeterminate spinner mode. */
|
|
14
|
+
indeterminate?: boolean;
|
|
15
|
+
/** Accessible label. Defaults to "Progress". */
|
|
16
|
+
label?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const toneClasses = {
|
|
20
|
+
primary: "stroke-primary",
|
|
21
|
+
success: "stroke-success",
|
|
22
|
+
warning: "stroke-warning",
|
|
23
|
+
destructive: "stroke-destructive",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const CircularProgress = React.forwardRef<HTMLDivElement, CircularProgressProps>(
|
|
27
|
+
(
|
|
28
|
+
{
|
|
29
|
+
className,
|
|
30
|
+
value = 0,
|
|
31
|
+
max = 100,
|
|
32
|
+
size = 40,
|
|
33
|
+
strokeWidth = 4,
|
|
34
|
+
tone = "primary",
|
|
35
|
+
showLabel = false,
|
|
36
|
+
indeterminate = false,
|
|
37
|
+
label = "Progress",
|
|
38
|
+
...props
|
|
39
|
+
},
|
|
40
|
+
ref
|
|
41
|
+
) => {
|
|
42
|
+
const radius = (size - strokeWidth) / 2;
|
|
43
|
+
const circumference = 2 * Math.PI * radius;
|
|
44
|
+
const clampedValue = Math.min(Math.max(value, 0), max);
|
|
45
|
+
const percent = (clampedValue / max) * 100;
|
|
46
|
+
const dashOffset = circumference - (circumference * percent) / 100;
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
ref={ref}
|
|
51
|
+
data-slot="circular-progress"
|
|
52
|
+
role="progressbar"
|
|
53
|
+
aria-valuemin={0}
|
|
54
|
+
aria-valuemax={max}
|
|
55
|
+
aria-valuenow={indeterminate ? undefined : clampedValue}
|
|
56
|
+
aria-label={label}
|
|
57
|
+
className={cn("relative inline-flex shrink-0 items-center justify-center", className)}
|
|
58
|
+
style={{ width: size, height: size }}
|
|
59
|
+
{...props}
|
|
60
|
+
>
|
|
61
|
+
<svg
|
|
62
|
+
width={size}
|
|
63
|
+
height={size}
|
|
64
|
+
viewBox={`0 0 ${size} ${size}`}
|
|
65
|
+
className={cn(indeterminate && "animate-spin")}
|
|
66
|
+
>
|
|
67
|
+
<circle
|
|
68
|
+
cx={size / 2}
|
|
69
|
+
cy={size / 2}
|
|
70
|
+
r={radius}
|
|
71
|
+
strokeWidth={strokeWidth}
|
|
72
|
+
fill="none"
|
|
73
|
+
className="stroke-muted"
|
|
74
|
+
/>
|
|
75
|
+
<circle
|
|
76
|
+
cx={size / 2}
|
|
77
|
+
cy={size / 2}
|
|
78
|
+
r={radius}
|
|
79
|
+
strokeWidth={strokeWidth}
|
|
80
|
+
fill="none"
|
|
81
|
+
strokeLinecap="round"
|
|
82
|
+
className={cn("transition-[stroke-dashoffset] duration-300 ease-out", toneClasses[tone])}
|
|
83
|
+
style={{
|
|
84
|
+
strokeDasharray: circumference,
|
|
85
|
+
strokeDashoffset: indeterminate ? circumference * 0.75 : dashOffset,
|
|
86
|
+
transform: `rotate(-90deg)`,
|
|
87
|
+
transformOrigin: `${size / 2}px ${size / 2}px`,
|
|
88
|
+
}}
|
|
89
|
+
/>
|
|
90
|
+
</svg>
|
|
91
|
+
{showLabel && !indeterminate && (
|
|
92
|
+
<span className="absolute text-xs font-medium tabular-nums text-foreground">
|
|
93
|
+
{Math.round(percent)}%
|
|
94
|
+
</span>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
CircularProgress.displayName = "CircularProgress";
|
|
101
|
+
|
|
102
|
+
export { CircularProgress };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
2
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./collapsible";
|
|
3
3
|
import { Button } from "./button";
|
|
4
|
-
import {
|
|
4
|
+
import { ChevronDown } from "lucide-react";
|
|
5
5
|
|
|
6
6
|
const meta = {
|
|
7
7
|
title: "Components/Collapsible",
|
|
@@ -18,8 +18,12 @@ export const Default: Story = {
|
|
|
18
18
|
<div className="flex items-center justify-between space-x-4 px-4">
|
|
19
19
|
<h4 className="text-sm font-semibold">@peduarte starred 3 repositories</h4>
|
|
20
20
|
<CollapsibleTrigger asChild>
|
|
21
|
-
<Button
|
|
22
|
-
|
|
21
|
+
<Button
|
|
22
|
+
variant="ghost"
|
|
23
|
+
size="sm"
|
|
24
|
+
className="w-9 p-0 [&[data-state=open]>svg]:rotate-180"
|
|
25
|
+
>
|
|
26
|
+
<ChevronDown className="h-4 w-4 transition-transform duration-200" />
|
|
23
27
|
<span className="sr-only">Toggle</span>
|
|
24
28
|
</Button>
|
|
25
29
|
</CollapsibleTrigger>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Combobox, type ComboboxOption } from "./combobox";
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: "Components/Combobox",
|
|
7
|
+
component: Combobox,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
} satisfies Meta<typeof Combobox>;
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj<typeof meta>;
|
|
13
|
+
|
|
14
|
+
const frameworks: ComboboxOption[] = [
|
|
15
|
+
{ value: "next", label: "Next.js", description: "React framework" },
|
|
16
|
+
{ value: "remix", label: "Remix", description: "Full-stack web framework" },
|
|
17
|
+
{ value: "astro", label: "Astro", description: "Content-driven web framework" },
|
|
18
|
+
{ value: "vite", label: "Vite", description: "Frontend tooling" },
|
|
19
|
+
{ value: "nuxt", label: "Nuxt", description: "Vue framework" },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export const Default: Story = {
|
|
23
|
+
render: () => {
|
|
24
|
+
const [value, setValue] = useState<string | undefined>("next");
|
|
25
|
+
return (
|
|
26
|
+
<div className="w-72">
|
|
27
|
+
<Combobox
|
|
28
|
+
options={frameworks}
|
|
29
|
+
value={value}
|
|
30
|
+
onValueChange={setValue}
|
|
31
|
+
placeholder="Pick a framework"
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const Empty: Story = {
|
|
39
|
+
render: () => {
|
|
40
|
+
const [value, setValue] = useState<string | undefined>(undefined);
|
|
41
|
+
return (
|
|
42
|
+
<div className="w-72">
|
|
43
|
+
<Combobox
|
|
44
|
+
options={frameworks}
|
|
45
|
+
value={value}
|
|
46
|
+
onValueChange={setValue}
|
|
47
|
+
placeholder="No selection"
|
|
48
|
+
/>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const Disabled: Story = {
|
|
55
|
+
render: () => (
|
|
56
|
+
<div className="w-72">
|
|
57
|
+
<Combobox
|
|
58
|
+
options={frameworks}
|
|
59
|
+
value="next"
|
|
60
|
+
onValueChange={() => {}}
|
|
61
|
+
disabled
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const Loading: Story = {
|
|
68
|
+
render: () => {
|
|
69
|
+
const [value, setValue] = useState<string | undefined>(undefined);
|
|
70
|
+
return (
|
|
71
|
+
<div className="w-72">
|
|
72
|
+
<Combobox
|
|
73
|
+
options={[]}
|
|
74
|
+
value={value}
|
|
75
|
+
onValueChange={setValue}
|
|
76
|
+
loading
|
|
77
|
+
placeholder="Pick something…"
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
},
|
|
82
|
+
};
|