@mrigx/ui 0.1.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.
Files changed (93) hide show
  1. package/package.json +73 -0
  2. package/src/blocks/ContactPanel.tsx +99 -0
  3. package/src/blocks/FeatureGrid.tsx +38 -0
  4. package/src/blocks/OpticalCTA.tsx +33 -0
  5. package/src/blocks/OpticalHero.tsx +51 -0
  6. package/src/blocks/styles.ts +175 -0
  7. package/src/components/accordion/optical-accordion.tsx +199 -0
  8. package/src/components/alert/optical-alert.tsx +59 -0
  9. package/src/components/alert-dialog/optical-alert-dialog.tsx +143 -0
  10. package/src/components/ambient-light.tsx +27 -0
  11. package/src/components/aspect-ratio/optical-aspect-ratio.tsx +7 -0
  12. package/src/components/attachment/optical-attachment.tsx +25 -0
  13. package/src/components/avatar/optical-avatar.tsx +50 -0
  14. package/src/components/badge/optical-badge.tsx +36 -0
  15. package/src/components/breadcrumb/optical-breadcrumb.tsx +115 -0
  16. package/src/components/bubble/optical-bubble.tsx +25 -0
  17. package/src/components/button/optical-button.tsx +67 -0
  18. package/src/components/button-group/optical-button-group.tsx +83 -0
  19. package/src/components/calendar/optical-calendar.tsx +214 -0
  20. package/src/components/card/optical-card.tsx +44 -0
  21. package/src/components/carousel/optical-carousel.tsx +262 -0
  22. package/src/components/chart/optical-chart.tsx +369 -0
  23. package/src/components/checkbox/optical-checkbox.tsx +42 -0
  24. package/src/components/collapsible/optical-collapsible.tsx +11 -0
  25. package/src/components/combobox/optical-combobox.tsx +25 -0
  26. package/src/components/command/optical-command.tsx +153 -0
  27. package/src/components/context-menu/optical-context-menu.tsx +201 -0
  28. package/src/components/cursor/optical-cursor.tsx +10 -0
  29. package/src/components/data-table/optical-data-table.tsx +25 -0
  30. package/src/components/date-picker/optical-date-picker.tsx +25 -0
  31. package/src/components/dialog/optical-dialog.tsx +110 -0
  32. package/src/components/direction/optical-direction.tsx +25 -0
  33. package/src/components/drawer/optical-drawer.tsx +120 -0
  34. package/src/components/dropdown-menu/optical-dropdown-menu.tsx +194 -0
  35. package/src/components/empty/optical-empty.tsx +104 -0
  36. package/src/components/field/optical-field.tsx +244 -0
  37. package/src/components/glass-card.tsx +13 -0
  38. package/src/components/hover-card/optical-hover-card.tsx +30 -0
  39. package/src/components/icon-button/optical-icon-button.tsx +22 -0
  40. package/src/components/input/optical-input.tsx +29 -0
  41. package/src/components/input-group/optical-input-group.tsx +170 -0
  42. package/src/components/input-otp/optical-input-otp.tsx +71 -0
  43. package/src/components/item/optical-item.tsx +193 -0
  44. package/src/components/kbd/optical-kbd.tsx +28 -0
  45. package/src/components/label/optical-label.tsx +25 -0
  46. package/src/components/marker/optical-marker.tsx +25 -0
  47. package/src/components/menubar/optical-menubar.tsx +257 -0
  48. package/src/components/message/optical-message.tsx +25 -0
  49. package/src/components/message-scroller/optical-message-scroller.tsx +25 -0
  50. package/src/components/native-select/optical-native-select.tsx +25 -0
  51. package/src/components/navigation-menu/optical-navigation-menu.tsx +68 -0
  52. package/src/components/pagination/optical-pagination.tsx +118 -0
  53. package/src/components/panel/optical-panel.tsx +26 -0
  54. package/src/components/popover/optical-popover.tsx +32 -0
  55. package/src/components/prism-button.tsx +17 -0
  56. package/src/components/progress/optical-progress.tsx +28 -0
  57. package/src/components/questionnaire/optical-questionnaire.tsx +25 -0
  58. package/src/components/radio-group/optical-radio-group.tsx +25 -0
  59. package/src/components/resizable/optical-resizable.tsx +45 -0
  60. package/src/components/scroll-area/optical-scroll-area.tsx +48 -0
  61. package/src/components/select/optical-select.tsx +110 -0
  62. package/src/components/separator/optical-separator.tsx +31 -0
  63. package/src/components/sheet/optical-sheet.tsx +143 -0
  64. package/src/components/sidebar/optical-sidebar.tsx +762 -0
  65. package/src/components/skeleton/optical-skeleton.tsx +15 -0
  66. package/src/components/slider/optical-slider.tsx +34 -0
  67. package/src/components/sonner/optical-toast.tsx +45 -0
  68. package/src/components/spinner/optical-spinner.tsx +16 -0
  69. package/src/components/stained-glass-card.tsx +93 -0
  70. package/src/components/switch/optical-switch.tsx +31 -0
  71. package/src/components/table/optical-table.tsx +83 -0
  72. package/src/components/tabs/optical-tabs.tsx +56 -0
  73. package/src/components/text/liquid-tension-text.tsx +65 -0
  74. package/src/components/text/optical-tension-text.tsx +65 -0
  75. package/src/components/text/optical-text.tsx +20 -0
  76. package/src/components/text/prismatic-selection.tsx +5 -0
  77. package/src/components/textarea/optical-textarea.tsx +22 -0
  78. package/src/components/theme-toggle/optical-theme-toggle.tsx +45 -0
  79. package/src/components/toast/optical-toast.tsx +25 -0
  80. package/src/components/toggle/optical-toggle.tsx +45 -0
  81. package/src/components/toggle-group/optical-toggle-group.tsx +61 -0
  82. package/src/components/tooltip/optical-tooltip.tsx +33 -0
  83. package/src/components/typography/optical-typography.tsx +49 -0
  84. package/src/hooks/use-mobile.ts +19 -0
  85. package/src/index.ts +88 -0
  86. package/src/templates/ContactPageTemplate.tsx +42 -0
  87. package/src/templates/LandingPageTemplate.tsx +29 -0
  88. package/src/templates/ProductPageTemplate.tsx +46 -0
  89. package/src/templates/styles.ts +23 -0
  90. package/src/utils/button-variants.ts +26 -0
  91. package/src/utils/cn.ts +6 -0
  92. package/tsconfig.json +27 -0
  93. package/vite.config.ts +42 -0
@@ -0,0 +1,214 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import {
5
+ ChevronDownIcon,
6
+ ChevronLeftIcon,
7
+ ChevronRightIcon,
8
+ } from "lucide-react"
9
+ import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
10
+
11
+ import { cn } from '../../utils/cn';
12
+ import { OpticalButton as Button } from '../button/optical-button';
13
+ import { buttonVariants } from '../../utils/button-variants';
14
+
15
+ function Calendar({
16
+ className,
17
+ classNames,
18
+ showOutsideDays = true,
19
+ captionLayout = "label",
20
+ buttonVariant = "ghost",
21
+ formatters,
22
+ components,
23
+ ...props
24
+ }: React.ComponentProps<typeof DayPicker> & {
25
+ buttonVariant?: React.ComponentProps<typeof Button>["variant"]
26
+ }) {
27
+ const defaultClassNames = getDefaultClassNames()
28
+
29
+ return (
30
+ <DayPicker
31
+ showOutsideDays={showOutsideDays}
32
+ className={cn(
33
+ "bg-transparent group/calendar p-3 [--cell-size:2rem] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
34
+ String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
35
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
36
+ className
37
+ )}
38
+ captionLayout={captionLayout}
39
+ formatters={{
40
+ formatMonthDropdown: (date) =>
41
+ date.toLocaleString("default", { month: "short" }),
42
+ ...formatters,
43
+ }}
44
+ classNames={{
45
+ root: cn("w-fit", defaultClassNames.root),
46
+ months: cn(
47
+ "relative flex flex-col gap-4 md:flex-row",
48
+ defaultClassNames.months
49
+ ),
50
+ month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
51
+ nav: cn(
52
+ "absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
53
+ defaultClassNames.nav
54
+ ),
55
+ button_previous: cn(
56
+ buttonVariants({ variant: buttonVariant }),
57
+ "h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
58
+ defaultClassNames.button_previous
59
+ ),
60
+ button_next: cn(
61
+ buttonVariants({ variant: buttonVariant }),
62
+ "h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
63
+ defaultClassNames.button_next
64
+ ),
65
+ month_caption: cn(
66
+ "flex h-[--cell-size] w-full items-center justify-center px-[--cell-size]",
67
+ defaultClassNames.month_caption
68
+ ),
69
+ dropdowns: cn(
70
+ "flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-sm font-medium",
71
+ defaultClassNames.dropdowns
72
+ ),
73
+ dropdown_root: cn(
74
+ "has-focus:border-ring border-white/10 shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border",
75
+ defaultClassNames.dropdown_root
76
+ ),
77
+ dropdown: cn(
78
+ "bg-transparent border-white/10 relative absolute inset-0 opacity-0",
79
+ defaultClassNames.dropdown
80
+ ),
81
+ caption_label: cn(
82
+ "select-none font-medium",
83
+ captionLayout === "label"
84
+ ? "text-sm"
85
+ : "[&>svg]:text-white/50 flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5",
86
+ defaultClassNames.caption_label
87
+ ),
88
+ month_grid: cn("w-full border-collapse", defaultClassNames.month_grid),
89
+ weekdays: cn("flex", defaultClassNames.weekdays),
90
+ weekday: cn(
91
+ "text-white/50 flex-1 select-none rounded-md text-[0.8rem] font-normal",
92
+ defaultClassNames.weekday
93
+ ),
94
+ week: cn("mt-2 flex w-full", defaultClassNames.week),
95
+ week_number_header: cn(
96
+ "w-[--cell-size] select-none",
97
+ defaultClassNames.week_number_header
98
+ ),
99
+ week_number: cn(
100
+ "text-white/50 select-none text-[0.8rem]",
101
+ defaultClassNames.week_number
102
+ ),
103
+ day: cn(
104
+ "group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md",
105
+ defaultClassNames.day
106
+ ),
107
+ range_start: cn(
108
+ "bg-accent rounded-l-md",
109
+ defaultClassNames.range_start
110
+ ),
111
+ range_middle: cn("rounded-none", defaultClassNames.range_middle),
112
+ range_end: cn("bg-accent rounded-r-md", defaultClassNames.range_end),
113
+ today: cn(
114
+ "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
115
+ defaultClassNames.today
116
+ ),
117
+ outside: cn(
118
+ "text-white/50 aria-selected:text-white/50",
119
+ defaultClassNames.outside
120
+ ),
121
+ disabled: cn(
122
+ "text-white/50 opacity-50",
123
+ defaultClassNames.disabled
124
+ ),
125
+ hidden: cn("invisible", defaultClassNames.hidden),
126
+ ...classNames,
127
+ }}
128
+ components={{
129
+ Root: ({ className, rootRef, ...props }) => {
130
+ return (
131
+ <div
132
+ data-slot="calendar"
133
+ ref={rootRef}
134
+ className={cn(className)}
135
+ {...props}
136
+ />
137
+ )
138
+ },
139
+ Chevron: ({ className, orientation, ...props }) => {
140
+ if (orientation === "left") {
141
+ return (
142
+ <ChevronLeftIcon className={cn("size-4", className)} {...props} />
143
+ )
144
+ }
145
+
146
+ if (orientation === "right") {
147
+ return (
148
+ <ChevronRightIcon
149
+ className={cn("size-4", className)}
150
+ {...props}
151
+ />
152
+ )
153
+ }
154
+
155
+ return (
156
+ <ChevronDownIcon className={cn("size-4", className)} {...props} />
157
+ )
158
+ },
159
+ DayButton: CalendarDayButton,
160
+ WeekNumber: ({ children, ...props }) => {
161
+ return (
162
+ <td {...props}>
163
+ <div className="flex size-[--cell-size] items-center justify-center text-center">
164
+ {children}
165
+ </div>
166
+ </td>
167
+ )
168
+ },
169
+ ...components,
170
+ }}
171
+ {...props}
172
+ />
173
+ )
174
+ }
175
+
176
+ function CalendarDayButton({
177
+ className,
178
+ day,
179
+ modifiers,
180
+ ...props
181
+ }: React.ComponentProps<typeof DayButton>) {
182
+ const defaultClassNames = getDefaultClassNames()
183
+
184
+ const ref = React.useRef<HTMLButtonElement>(null)
185
+ React.useEffect(() => {
186
+ if (modifiers.focused) ref.current?.focus()
187
+ }, [modifiers.focused])
188
+
189
+ return (
190
+ <Button
191
+ ref={ref}
192
+ variant="ghost"
193
+ size="icon"
194
+ data-day={day.date.toLocaleDateString()}
195
+ data-selected-single={
196
+ modifiers.selected &&
197
+ !modifiers.range_start &&
198
+ !modifiers.range_end &&
199
+ !modifiers.range_middle
200
+ }
201
+ data-range-start={modifiers.range_start}
202
+ data-range-end={modifiers.range_end}
203
+ data-range-middle={modifiers.range_middle}
204
+ className={cn(
205
+ "data-[selected-single=true]:bg-white/20 data-[selected-single=true]:text-white data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-white/20 data-[range-start=true]:text-white data-[range-end=true]:bg-white/20 data-[range-end=true]:text-white group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 flex aspect-square h-auto w-full min-w-[--cell-size] flex-col gap-1 font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] [&>span]:text-xs [&>span]:opacity-70",
206
+ defaultClassNames.day,
207
+ className
208
+ )}
209
+ {...props}
210
+ />
211
+ )
212
+ }
213
+
214
+ export { Calendar, CalendarDayButton }
@@ -0,0 +1,44 @@
1
+ import { forwardRef } from 'react';
2
+ import { GlassSurface } from '@mrigx/fx';
3
+
4
+ export const OpticalCard = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement> & { material?: string, materialStrength?: number }>(({
5
+ className = "",
6
+ material = "glass",
7
+ materialStrength = 1.0,
8
+ ...props
9
+ }, ref) => (
10
+ <GlassSurface
11
+ ref={ref as any}
12
+ material={material}
13
+ materialStrength={materialStrength}
14
+ borderRadius={16}
15
+ className={`rounded-2xl border border-white/10 bg-black/20 text-white shadow-sm ${className}`}
16
+ {...props}
17
+ />
18
+ ));
19
+ OpticalCard.displayName = "OpticalCard";
20
+
21
+ export const OpticalCardHeader = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className = "", ...props }, ref) => (
22
+ <div ref={ref} className={`flex flex-col space-y-1.5 p-6 ${className}`} {...props} />
23
+ ));
24
+ OpticalCardHeader.displayName = "OpticalCardHeader";
25
+
26
+ export const OpticalCardTitle = forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(({ className = "", ...props }, ref) => (
27
+ <h3 ref={ref} className={`font-semibold leading-none tracking-tight text-white/90 ${className}`} {...props} />
28
+ ));
29
+ OpticalCardTitle.displayName = "OpticalCardTitle";
30
+
31
+ export const OpticalCardDescription = forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(({ className = "", ...props }, ref) => (
32
+ <p ref={ref} className={`text-sm text-white/50 ${className}`} {...props} />
33
+ ));
34
+ OpticalCardDescription.displayName = "OpticalCardDescription";
35
+
36
+ export const OpticalCardContent = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className = "", ...props }, ref) => (
37
+ <div ref={ref} className={`p-6 pt-0 ${className}`} {...props} />
38
+ ));
39
+ OpticalCardContent.displayName = "OpticalCardContent";
40
+
41
+ export const OpticalCardFooter = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className = "", ...props }, ref) => (
42
+ <div ref={ref} className={`flex items-center p-6 pt-0 ${className}`} {...props} />
43
+ ));
44
+ OpticalCardFooter.displayName = "OpticalCardFooter";
@@ -0,0 +1,262 @@
1
+ "use client"
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/cn';
10
+ import { OpticalButton as Button } from '../button/optical-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
+ const Carousel = React.forwardRef<
46
+ HTMLDivElement,
47
+ React.HTMLAttributes<HTMLDivElement> & CarouselProps
48
+ >(
49
+ (
50
+ {
51
+ orientation = "horizontal",
52
+ opts,
53
+ setApi,
54
+ plugins,
55
+ className,
56
+ children,
57
+ ...props
58
+ },
59
+ ref
60
+ ) => {
61
+ const [carouselRef, api] = useEmblaCarousel(
62
+ {
63
+ ...opts,
64
+ axis: orientation === "horizontal" ? "x" : "y",
65
+ },
66
+ plugins
67
+ )
68
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false)
69
+ const [canScrollNext, setCanScrollNext] = React.useState(false)
70
+
71
+ const onSelect = React.useCallback((api: CarouselApi) => {
72
+ if (!api) {
73
+ return
74
+ }
75
+
76
+ setCanScrollPrev(api.canScrollPrev())
77
+ setCanScrollNext(api.canScrollNext())
78
+ }, [])
79
+
80
+ const scrollPrev = React.useCallback(() => {
81
+ api?.scrollPrev()
82
+ }, [api])
83
+
84
+ const scrollNext = React.useCallback(() => {
85
+ api?.scrollNext()
86
+ }, [api])
87
+
88
+ const handleKeyDown = React.useCallback(
89
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
90
+ if (event.key === "ArrowLeft") {
91
+ event.preventDefault()
92
+ scrollPrev()
93
+ } else if (event.key === "ArrowRight") {
94
+ event.preventDefault()
95
+ scrollNext()
96
+ }
97
+ },
98
+ [scrollPrev, scrollNext]
99
+ )
100
+
101
+ React.useEffect(() => {
102
+ if (!api || !setApi) {
103
+ return
104
+ }
105
+
106
+ setApi(api)
107
+ }, [api, setApi])
108
+
109
+ React.useEffect(() => {
110
+ if (!api) {
111
+ return
112
+ }
113
+
114
+ onSelect(api)
115
+ api.on("reInit", onSelect)
116
+ api.on("select", onSelect)
117
+
118
+ return () => {
119
+ api?.off("select", onSelect)
120
+ }
121
+ }, [api, onSelect])
122
+
123
+ return (
124
+ <CarouselContext.Provider
125
+ value={{
126
+ carouselRef,
127
+ api: api,
128
+ opts,
129
+ orientation:
130
+ orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
131
+ scrollPrev,
132
+ scrollNext,
133
+ canScrollPrev,
134
+ canScrollNext,
135
+ }}
136
+ >
137
+ <div
138
+ ref={ref}
139
+ onKeyDownCapture={handleKeyDown}
140
+ className={cn("relative", className)}
141
+ role="region"
142
+ aria-roledescription="carousel"
143
+ {...props}
144
+ >
145
+ {children}
146
+ </div>
147
+ </CarouselContext.Provider>
148
+ )
149
+ }
150
+ )
151
+ Carousel.displayName = "Carousel"
152
+
153
+ const CarouselContent = React.forwardRef<
154
+ HTMLDivElement,
155
+ React.HTMLAttributes<HTMLDivElement>
156
+ >(({ className, ...props }, ref) => {
157
+ const { carouselRef, orientation } = useCarousel()
158
+
159
+ return (
160
+ <div ref={carouselRef} className="overflow-hidden">
161
+ <div
162
+ ref={ref}
163
+ className={cn(
164
+ "flex",
165
+ orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
166
+ className
167
+ )}
168
+ {...props}
169
+ />
170
+ </div>
171
+ )
172
+ })
173
+ CarouselContent.displayName = "CarouselContent"
174
+
175
+ const CarouselItem = React.forwardRef<
176
+ HTMLDivElement,
177
+ React.HTMLAttributes<HTMLDivElement>
178
+ >(({ className, ...props }, ref) => {
179
+ const { orientation } = useCarousel()
180
+
181
+ return (
182
+ <div
183
+ ref={ref}
184
+ role="group"
185
+ aria-roledescription="slide"
186
+ className={cn(
187
+ "min-w-0 shrink-0 grow-0 basis-full",
188
+ orientation === "horizontal" ? "pl-4" : "pt-4",
189
+ className
190
+ )}
191
+ {...props}
192
+ />
193
+ )
194
+ })
195
+ CarouselItem.displayName = "CarouselItem"
196
+
197
+ const CarouselPrevious = React.forwardRef<
198
+ HTMLButtonElement,
199
+ React.ComponentProps<typeof Button>
200
+ >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
201
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel()
202
+
203
+ return (
204
+ <Button
205
+ ref={ref}
206
+ variant={variant}
207
+ size={size}
208
+ className={cn(
209
+ "absolute h-8 w-8 rounded-full",
210
+ orientation === "horizontal"
211
+ ? "-left-12 top-1/2 -translate-y-1/2"
212
+ : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
213
+ className
214
+ )}
215
+ disabled={!canScrollPrev}
216
+ onClick={scrollPrev}
217
+ {...props}
218
+ >
219
+ <ArrowLeft className="h-4 w-4" />
220
+ <span className="sr-only">Previous slide</span>
221
+ </Button>
222
+ )
223
+ })
224
+ CarouselPrevious.displayName = "CarouselPrevious"
225
+
226
+ const CarouselNext = React.forwardRef<
227
+ HTMLButtonElement,
228
+ React.ComponentProps<typeof Button>
229
+ >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
230
+ const { orientation, scrollNext, canScrollNext } = useCarousel()
231
+
232
+ return (
233
+ <Button
234
+ ref={ref}
235
+ variant={variant}
236
+ size={size}
237
+ className={cn(
238
+ "absolute h-8 w-8 rounded-full",
239
+ orientation === "horizontal"
240
+ ? "-right-12 top-1/2 -translate-y-1/2"
241
+ : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
242
+ className
243
+ )}
244
+ disabled={!canScrollNext}
245
+ onClick={scrollNext}
246
+ {...props}
247
+ >
248
+ <ArrowRight className="h-4 w-4" />
249
+ <span className="sr-only">Next slide</span>
250
+ </Button>
251
+ )
252
+ })
253
+ CarouselNext.displayName = "CarouselNext"
254
+
255
+ export {
256
+ type CarouselApi,
257
+ Carousel,
258
+ CarouselContent,
259
+ CarouselItem,
260
+ CarouselPrevious,
261
+ CarouselNext,
262
+ }