@evolution-soft/ui 1.0.0 → 1.0.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.
Files changed (78) hide show
  1. package/cli/index.mjs +386 -0
  2. package/components/button-icon-lottie/index.tsx +46 -0
  3. package/components/fullscreen-mode/index.tsx +82 -0
  4. package/components/header/components/buttons.tsx +102 -0
  5. package/components/header/index.tsx +146 -0
  6. package/components/loading-default/index.tsx +90 -0
  7. package/components/lottie-icon/index.tsx +78 -0
  8. package/components/not-found-default/index.tsx +68 -0
  9. package/components/settings-modal/index.tsx +225 -0
  10. package/components/sidebar/index.tsx +645 -0
  11. package/components/subtitle/index.tsx +60 -0
  12. package/components/theme-transition/index.tsx +142 -0
  13. package/components/title/index.tsx +66 -0
  14. package/components/tooltip-indicator/index.tsx +30 -0
  15. package/components/ui/accordion.tsx +66 -0
  16. package/components/ui/alert-dialog.tsx +157 -0
  17. package/components/ui/alert.tsx +66 -0
  18. package/components/ui/aspect-ratio.tsx +11 -0
  19. package/components/ui/avatar.tsx +53 -0
  20. package/components/ui/badge.tsx +46 -0
  21. package/components/ui/breadcrumb.tsx +109 -0
  22. package/components/ui/button.tsx +58 -0
  23. package/components/ui/calendar.tsx +78 -0
  24. package/components/ui/card.tsx +92 -0
  25. package/components/ui/carousel.tsx +241 -0
  26. package/components/ui/chart.tsx +360 -0
  27. package/components/ui/checkbox.tsx +32 -0
  28. package/components/ui/collapsible.tsx +33 -0
  29. package/components/ui/command.tsx +177 -0
  30. package/components/ui/context-menu.tsx +252 -0
  31. package/components/ui/dialog.tsx +135 -0
  32. package/components/ui/divisor.tsx +9 -0
  33. package/components/ui/drawer.tsx +132 -0
  34. package/components/ui/dropdown-menu.tsx +257 -0
  35. package/components/ui/emoji-picker.tsx +76 -0
  36. package/components/ui/form.tsx +168 -0
  37. package/components/ui/hover-card.tsx +44 -0
  38. package/components/ui/input-mask.tsx +46 -0
  39. package/components/ui/input-otp.tsx +77 -0
  40. package/components/ui/input.tsx +61 -0
  41. package/components/ui/label.tsx +24 -0
  42. package/components/ui/menubar.tsx +276 -0
  43. package/components/ui/multiselect.tsx +105 -0
  44. package/components/ui/navigation-menu.tsx +168 -0
  45. package/components/ui/pagination.tsx +127 -0
  46. package/components/ui/popover.tsx +48 -0
  47. package/components/ui/progress.tsx +31 -0
  48. package/components/ui/radio-group.tsx +45 -0
  49. package/components/ui/resizable.tsx +65 -0
  50. package/components/ui/scroll-area.tsx +58 -0
  51. package/components/ui/searchable-select.tsx +211 -0
  52. package/components/ui/select.tsx +189 -0
  53. package/components/ui/separator.tsx +28 -0
  54. package/components/ui/sheet.tsx +139 -0
  55. package/components/ui/sidebar.tsx +727 -0
  56. package/components/ui/skeleton.tsx +144 -0
  57. package/components/ui/slider.tsx +63 -0
  58. package/components/ui/sonner.tsx +26 -0
  59. package/components/ui/switch.tsx +31 -0
  60. package/components/ui/table.tsx +116 -0
  61. package/components/ui/tabs.tsx +76 -0
  62. package/components/ui/textarea.tsx +18 -0
  63. package/components/ui/theme-toggle.tsx +89 -0
  64. package/components/ui/toggle-group.tsx +73 -0
  65. package/components/ui/toggle.tsx +47 -0
  66. package/components/ui/tooltip.tsx +61 -0
  67. package/components/ui/use-mobile.ts +21 -0
  68. package/components/ui/utils.ts +6 -0
  69. package/contexts/AnimationSettingsContext.tsx +85 -0
  70. package/contexts/AuthContext.tsx +80 -0
  71. package/contexts/ThemeContext.tsx +70 -0
  72. package/hooks/useAnimationSettings.ts +2 -0
  73. package/hooks/usePermissions.ts +4 -0
  74. package/lib/persistentFilters.ts +120 -0
  75. package/lib/utils.ts +2 -0
  76. package/package.json +11 -2
  77. package/stores/theme.ts +30 -0
  78. package/stores/useThemeStore.ts +32 -0
@@ -0,0 +1,46 @@
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+
5
+ import { cn } from "./utils";
6
+
7
+ const badgeVariants = cva(
8
+ "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default:
13
+ "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
14
+ secondary:
15
+ "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
16
+ destructive:
17
+ "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
18
+ outline:
19
+ "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
20
+ },
21
+ },
22
+ defaultVariants: {
23
+ variant: "default",
24
+ },
25
+ },
26
+ );
27
+
28
+ function Badge({
29
+ className,
30
+ variant,
31
+ asChild = false,
32
+ ...props
33
+ }: React.ComponentProps<"span"> &
34
+ VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
35
+ const Comp = asChild ? Slot : "span";
36
+
37
+ return (
38
+ <Comp
39
+ data-slot="badge"
40
+ className={cn(badgeVariants({ variant }), className)}
41
+ {...props}
42
+ />
43
+ );
44
+ }
45
+
46
+ export { Badge, badgeVariants };
@@ -0,0 +1,109 @@
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { ChevronRight, MoreHorizontal } from "lucide-react";
4
+
5
+ import { cn } from "./utils";
6
+
7
+ function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
8
+ return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />;
9
+ }
10
+
11
+ function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
12
+ return (
13
+ <ol
14
+ data-slot="breadcrumb-list"
15
+ className={cn(
16
+ "text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm wrap-break-word sm:gap-2.5",
17
+ className,
18
+ )}
19
+ {...props}
20
+ />
21
+ );
22
+ }
23
+
24
+ function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
25
+ return (
26
+ <li
27
+ data-slot="breadcrumb-item"
28
+ className={cn("inline-flex items-center gap-1.5", className)}
29
+ {...props}
30
+ />
31
+ );
32
+ }
33
+
34
+ function BreadcrumbLink({
35
+ asChild,
36
+ className,
37
+ ...props
38
+ }: React.ComponentProps<"a"> & {
39
+ asChild?: boolean;
40
+ }) {
41
+ const Comp = asChild ? Slot : "a";
42
+
43
+ return (
44
+ <Comp
45
+ data-slot="breadcrumb-link"
46
+ className={cn("hover:text-foreground transition-colors", className)}
47
+ {...props}
48
+ />
49
+ );
50
+ }
51
+
52
+ function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
53
+ return (
54
+ <span
55
+ data-slot="breadcrumb-page"
56
+ role="link"
57
+ aria-disabled="true"
58
+ aria-current="page"
59
+ className={cn("text-foreground font-normal", className)}
60
+ {...props}
61
+ />
62
+ );
63
+ }
64
+
65
+ function BreadcrumbSeparator({
66
+ children,
67
+ className,
68
+ ...props
69
+ }: React.ComponentProps<"li">) {
70
+ return (
71
+ <li
72
+ data-slot="breadcrumb-separator"
73
+ role="presentation"
74
+ aria-hidden="true"
75
+ className={cn("[&>svg]:size-3.5", className)}
76
+ {...props}
77
+ >
78
+ {children ?? <ChevronRight />}
79
+ </li>
80
+ );
81
+ }
82
+
83
+ function BreadcrumbEllipsis({
84
+ className,
85
+ ...props
86
+ }: React.ComponentProps<"span">) {
87
+ return (
88
+ <span
89
+ data-slot="breadcrumb-ellipsis"
90
+ role="presentation"
91
+ aria-hidden="true"
92
+ className={cn("flex size-9 items-center justify-center", className)}
93
+ {...props}
94
+ >
95
+ <MoreHorizontal className="size-4" />
96
+ <span className="sr-only">More</span>
97
+ </span>
98
+ );
99
+ }
100
+
101
+ export {
102
+ Breadcrumb,
103
+ BreadcrumbList,
104
+ BreadcrumbItem,
105
+ BreadcrumbLink,
106
+ BreadcrumbPage,
107
+ BreadcrumbSeparator,
108
+ BreadcrumbEllipsis,
109
+ };
@@ -0,0 +1,58 @@
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+
5
+ import { cn } from "./utils";
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center cursor-pointer justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
13
+ destructive:
14
+ "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
15
+ outline:
16
+ "border bg-background text-foreground hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
17
+ secondary:
18
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19
+ ghost:
20
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
21
+ link: "text-primary underline-offset-4 hover:underline",
22
+ },
23
+ size: {
24
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
25
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
26
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
27
+ icon: "size-9 rounded-md",
28
+ },
29
+ },
30
+ defaultVariants: {
31
+ variant: "default",
32
+ size: "default",
33
+ },
34
+ },
35
+ );
36
+
37
+ function Button({
38
+ className,
39
+ variant,
40
+ size,
41
+ asChild = false,
42
+ ...props
43
+ }: React.ComponentProps<"button"> &
44
+ VariantProps<typeof buttonVariants> & {
45
+ asChild?: boolean;
46
+ }) {
47
+ const Comp = asChild ? Slot : "button";
48
+
49
+ return (
50
+ <Comp
51
+ data-slot="button"
52
+ className={cn(buttonVariants({ variant, size, className }))}
53
+ {...props}
54
+ />
55
+ );
56
+ }
57
+
58
+ export { Button, buttonVariants };
@@ -0,0 +1,78 @@
1
+
2
+ import * as React from "react";
3
+ import { ChevronLeft, ChevronRight } from "lucide-react";
4
+ import { DayPicker } from "react-day-picker";
5
+ import { pt } from "date-fns/locale";
6
+
7
+ import { cn } from "./utils";
8
+ import { buttonVariants } from "./button";
9
+
10
+ export type CalendarProps = React.ComponentProps<typeof DayPicker>;
11
+
12
+ function Calendar({
13
+ className,
14
+ classNames,
15
+ showOutsideDays = true,
16
+ locale = pt,
17
+ ...props
18
+ }: CalendarProps) {
19
+ return (
20
+ <DayPicker
21
+ showOutsideDays={showOutsideDays}
22
+ locale={locale}
23
+ className={cn("p-3", className)}
24
+ classNames={{
25
+ months: "flex flex-col sm:flex-row gap-2",
26
+ month: "flex flex-col gap-4",
27
+ month_caption: "flex justify-center pt-1 relative items-center w-full",
28
+ caption_label: "text-sm font-medium",
29
+ nav: "flex items-center gap-1",
30
+ button_previous: cn(
31
+ buttonVariants({ variant: "outline" }),
32
+ "size-7 bg-transparent p-0 opacity-50 hover:opacity-100 absolute left-1",
33
+ ),
34
+ button_next: cn(
35
+ buttonVariants({ variant: "outline" }),
36
+ "size-7 bg-transparent p-0 opacity-50 hover:opacity-100 absolute right-1",
37
+ ),
38
+ month_grid: "w-full border-collapse space-x-1",
39
+ weekdays: "flex",
40
+ weekday: "text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]",
41
+ week: "flex w-full mt-2",
42
+ day: cn(
43
+ "relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-range-end)]:rounded-r-md",
44
+ props.mode === "range"
45
+ ? "[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
46
+ : "[&:has([aria-selected])]:rounded-md",
47
+ ),
48
+ day_button: cn(
49
+ buttonVariants({ variant: "ghost" }),
50
+ "size-8 p-0 font-normal aria-selected:opacity-100",
51
+ ),
52
+ range_start: "day-range-start",
53
+ range_end: "day-range-end",
54
+ selected:
55
+ "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
56
+ today: "bg-accent text-accent-foreground",
57
+ outside:
58
+ "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
59
+ disabled: "text-muted-foreground opacity-50",
60
+ range_middle:
61
+ "aria-selected:bg-accent aria-selected:text-accent-foreground",
62
+ hidden: "invisible",
63
+ ...classNames,
64
+ }}
65
+ components={{
66
+ Chevron: ({ orientation, ...rest }: { orientation?: "left" | "right" | "up" | "down"; className?: string }) => {
67
+ const Icon = orientation === "left" ? ChevronLeft : ChevronRight;
68
+ return <Icon className="size-4" {...rest} />;
69
+ },
70
+ }}
71
+ {...props}
72
+ />
73
+ );
74
+ }
75
+ Calendar.displayName = "Calendar";
76
+
77
+ export { Calendar };
78
+
@@ -0,0 +1,92 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "./utils";
4
+
5
+ function Card({ className, ...props }: React.ComponentProps<"div">) {
6
+ return (
7
+ <div
8
+ data-slot="card"
9
+ className={cn(
10
+ "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border",
11
+ className,
12
+ )}
13
+ {...props}
14
+ />
15
+ );
16
+ }
17
+
18
+ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
19
+ return (
20
+ <div
21
+ data-slot="card-header"
22
+ className={cn(
23
+ "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 pt-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
24
+ className,
25
+ )}
26
+ {...props}
27
+ />
28
+ );
29
+ }
30
+
31
+ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
32
+ return (
33
+ <h4
34
+ data-slot="card-title"
35
+ className={cn("leading-none", className)}
36
+ {...props}
37
+ />
38
+ );
39
+ }
40
+
41
+ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
42
+ return (
43
+ <p
44
+ data-slot="card-description"
45
+ className={cn("text-muted-foreground", className)}
46
+ {...props}
47
+ />
48
+ );
49
+ }
50
+
51
+ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
52
+ return (
53
+ <div
54
+ data-slot="card-action"
55
+ className={cn(
56
+ "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
57
+ className,
58
+ )}
59
+ {...props}
60
+ />
61
+ );
62
+ }
63
+
64
+ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
65
+ return (
66
+ <div
67
+ data-slot="card-content"
68
+ className={cn("px-6 last:pb-6", className)}
69
+ {...props}
70
+ />
71
+ );
72
+ }
73
+
74
+ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
75
+ return (
76
+ <div
77
+ data-slot="card-footer"
78
+ className={cn("flex items-center px-6 pb-6 [.border-t]:pt-6", className)}
79
+ {...props}
80
+ />
81
+ );
82
+ }
83
+
84
+ export {
85
+ Card,
86
+ CardHeader,
87
+ CardFooter,
88
+ CardTitle,
89
+ CardAction,
90
+ CardDescription,
91
+ CardContent,
92
+ };
@@ -0,0 +1,241 @@
1
+
2
+
3
+ import * as React from "react";
4
+ import useEmblaCarousel, {
5
+ type UseEmblaCarouselType,
6
+ } from "embla-carousel-react";
7
+ import { ArrowLeft, ArrowRight } from "lucide-react";
8
+
9
+ import { cn } from "./utils";
10
+ import { Button } from "./button";
11
+
12
+ type CarouselApi = UseEmblaCarouselType[1];
13
+ type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
14
+ type CarouselOptions = UseCarouselParameters[0];
15
+ type CarouselPlugin = UseCarouselParameters[1];
16
+
17
+ type CarouselProps = {
18
+ opts?: CarouselOptions;
19
+ plugins?: CarouselPlugin;
20
+ orientation?: "horizontal" | "vertical";
21
+ setApi?: (api: CarouselApi) => void;
22
+ };
23
+
24
+ type CarouselContextProps = {
25
+ carouselRef: ReturnType<typeof useEmblaCarousel>[0];
26
+ api: ReturnType<typeof useEmblaCarousel>[1];
27
+ scrollPrev: () => void;
28
+ scrollNext: () => void;
29
+ canScrollPrev: boolean;
30
+ canScrollNext: boolean;
31
+ } & CarouselProps;
32
+
33
+ const CarouselContext = React.createContext<CarouselContextProps | null>(null);
34
+
35
+ function useCarousel() {
36
+ const context = React.useContext(CarouselContext);
37
+
38
+ if (!context) {
39
+ throw new Error("useCarousel must be used within a <Carousel />");
40
+ }
41
+
42
+ return context;
43
+ }
44
+
45
+ function Carousel({
46
+ orientation = "horizontal",
47
+ opts,
48
+ setApi,
49
+ plugins,
50
+ className,
51
+ children,
52
+ ...props
53
+ }: React.ComponentProps<"div"> & CarouselProps) {
54
+ const [carouselRef, api] = useEmblaCarousel(
55
+ {
56
+ ...opts,
57
+ axis: orientation === "horizontal" ? "x" : "y",
58
+ },
59
+ plugins,
60
+ );
61
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false);
62
+ const [canScrollNext, setCanScrollNext] = React.useState(false);
63
+
64
+ const onSelect = React.useCallback((api: CarouselApi) => {
65
+ if (!api) return;
66
+ setCanScrollPrev(api.canScrollPrev());
67
+ setCanScrollNext(api.canScrollNext());
68
+ }, []);
69
+
70
+ const scrollPrev = React.useCallback(() => {
71
+ api?.scrollPrev();
72
+ }, [api]);
73
+
74
+ const scrollNext = React.useCallback(() => {
75
+ api?.scrollNext();
76
+ }, [api]);
77
+
78
+ const handleKeyDown = React.useCallback(
79
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
80
+ if (event.key === "ArrowLeft") {
81
+ event.preventDefault();
82
+ scrollPrev();
83
+ } else if (event.key === "ArrowRight") {
84
+ event.preventDefault();
85
+ scrollNext();
86
+ }
87
+ },
88
+ [scrollPrev, scrollNext],
89
+ );
90
+
91
+ React.useEffect(() => {
92
+ if (!api || !setApi) return;
93
+ setApi(api);
94
+ }, [api, setApi]);
95
+
96
+ React.useEffect(() => {
97
+ if (!api) return;
98
+ onSelect(api);
99
+ api.on("reInit", onSelect);
100
+ api.on("select", onSelect);
101
+
102
+ return () => {
103
+ api?.off("select", onSelect);
104
+ };
105
+ }, [api, onSelect]);
106
+
107
+ return (
108
+ <CarouselContext.Provider
109
+ value={{
110
+ carouselRef,
111
+ api: api,
112
+ opts,
113
+ orientation:
114
+ orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
115
+ scrollPrev,
116
+ scrollNext,
117
+ canScrollPrev,
118
+ canScrollNext,
119
+ }}
120
+ >
121
+ <div
122
+ onKeyDownCapture={handleKeyDown}
123
+ className={cn("relative", className)}
124
+ role="region"
125
+ aria-roledescription="carousel"
126
+ data-slot="carousel"
127
+ {...props}
128
+ >
129
+ {children}
130
+ </div>
131
+ </CarouselContext.Provider>
132
+ );
133
+ }
134
+
135
+ function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
136
+ const { carouselRef, orientation } = useCarousel();
137
+
138
+ return (
139
+ <div
140
+ ref={carouselRef}
141
+ className="overflow-hidden"
142
+ data-slot="carousel-content"
143
+ >
144
+ <div
145
+ className={cn(
146
+ "flex",
147
+ orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
148
+ className,
149
+ )}
150
+ {...props}
151
+ />
152
+ </div>
153
+ );
154
+ }
155
+
156
+ function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
157
+ const { orientation } = useCarousel();
158
+
159
+ return (
160
+ <div
161
+ role="group"
162
+ aria-roledescription="slide"
163
+ data-slot="carousel-item"
164
+ className={cn(
165
+ "min-w-0 shrink-0 grow-0 basis-full",
166
+ orientation === "horizontal" ? "pl-4" : "pt-4",
167
+ className,
168
+ )}
169
+ {...props}
170
+ />
171
+ );
172
+ }
173
+
174
+ function CarouselPrevious({
175
+ className,
176
+ variant = "outline",
177
+ size = "icon",
178
+ ...props
179
+ }: React.ComponentProps<typeof Button>) {
180
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel();
181
+
182
+ return (
183
+ <Button
184
+ data-slot="carousel-previous"
185
+ variant={variant}
186
+ size={size}
187
+ className={cn(
188
+ "absolute size-8 rounded-full",
189
+ orientation === "horizontal"
190
+ ? "top-1/2 -left-12 -translate-y-1/2"
191
+ : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
192
+ className,
193
+ )}
194
+ disabled={!canScrollPrev}
195
+ onClick={scrollPrev}
196
+ {...props}
197
+ >
198
+ <ArrowLeft />
199
+ <span className="sr-only">Previous slide</span>
200
+ </Button>
201
+ );
202
+ }
203
+
204
+ function CarouselNext({
205
+ className,
206
+ variant = "outline",
207
+ size = "icon",
208
+ ...props
209
+ }: React.ComponentProps<typeof Button>) {
210
+ const { orientation, scrollNext, canScrollNext } = useCarousel();
211
+
212
+ return (
213
+ <Button
214
+ data-slot="carousel-next"
215
+ variant={variant}
216
+ size={size}
217
+ className={cn(
218
+ "absolute size-8 rounded-full",
219
+ orientation === "horizontal"
220
+ ? "top-1/2 -right-12 -translate-y-1/2"
221
+ : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
222
+ className,
223
+ )}
224
+ disabled={!canScrollNext}
225
+ onClick={scrollNext}
226
+ {...props}
227
+ >
228
+ <ArrowRight />
229
+ <span className="sr-only">Next slide</span>
230
+ </Button>
231
+ );
232
+ }
233
+
234
+ export {
235
+ type CarouselApi,
236
+ Carousel,
237
+ CarouselContent,
238
+ CarouselItem,
239
+ CarouselPrevious,
240
+ CarouselNext,
241
+ };