@djangocfg/ui-core 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 (88) hide show
  1. package/README.md +135 -0
  2. package/package.json +111 -0
  3. package/src/components/accordion.tsx +56 -0
  4. package/src/components/alert-dialog.tsx +142 -0
  5. package/src/components/alert.tsx +59 -0
  6. package/src/components/aspect-ratio.tsx +7 -0
  7. package/src/components/avatar.tsx +50 -0
  8. package/src/components/badge.tsx +36 -0
  9. package/src/components/button-group.tsx +85 -0
  10. package/src/components/button.tsx +111 -0
  11. package/src/components/calendar.tsx +213 -0
  12. package/src/components/card.tsx +76 -0
  13. package/src/components/carousel.tsx +261 -0
  14. package/src/components/chart.tsx +369 -0
  15. package/src/components/checkbox.tsx +29 -0
  16. package/src/components/collapsible.tsx +11 -0
  17. package/src/components/combobox.tsx +182 -0
  18. package/src/components/command.tsx +170 -0
  19. package/src/components/context-menu.tsx +200 -0
  20. package/src/components/copy.tsx +144 -0
  21. package/src/components/dialog.tsx +122 -0
  22. package/src/components/drawer.tsx +137 -0
  23. package/src/components/empty.tsx +104 -0
  24. package/src/components/field.tsx +244 -0
  25. package/src/components/form.tsx +178 -0
  26. package/src/components/hover-card.tsx +29 -0
  27. package/src/components/image-with-fallback.tsx +170 -0
  28. package/src/components/index.ts +86 -0
  29. package/src/components/input-group.tsx +170 -0
  30. package/src/components/input-otp.tsx +81 -0
  31. package/src/components/input.tsx +22 -0
  32. package/src/components/item.tsx +195 -0
  33. package/src/components/kbd.tsx +28 -0
  34. package/src/components/label.tsx +26 -0
  35. package/src/components/multi-select.tsx +222 -0
  36. package/src/components/og-image.tsx +47 -0
  37. package/src/components/popover.tsx +33 -0
  38. package/src/components/portal.tsx +106 -0
  39. package/src/components/preloader.tsx +250 -0
  40. package/src/components/progress.tsx +28 -0
  41. package/src/components/radio-group.tsx +43 -0
  42. package/src/components/resizable.tsx +111 -0
  43. package/src/components/scroll-area.tsx +102 -0
  44. package/src/components/section.tsx +58 -0
  45. package/src/components/select.tsx +158 -0
  46. package/src/components/separator.tsx +31 -0
  47. package/src/components/sheet.tsx +140 -0
  48. package/src/components/skeleton.tsx +15 -0
  49. package/src/components/slider.tsx +28 -0
  50. package/src/components/spinner.tsx +16 -0
  51. package/src/components/sticky.tsx +117 -0
  52. package/src/components/switch.tsx +29 -0
  53. package/src/components/table.tsx +120 -0
  54. package/src/components/tabs.tsx +238 -0
  55. package/src/components/textarea.tsx +22 -0
  56. package/src/components/toast.tsx +129 -0
  57. package/src/components/toaster.tsx +41 -0
  58. package/src/components/toggle-group.tsx +61 -0
  59. package/src/components/toggle.tsx +45 -0
  60. package/src/components/token-icon.tsx +156 -0
  61. package/src/components/tooltip-provider-safe.tsx +43 -0
  62. package/src/components/tooltip.tsx +32 -0
  63. package/src/hooks/index.ts +15 -0
  64. package/src/hooks/useCopy.ts +41 -0
  65. package/src/hooks/useCountdown.ts +73 -0
  66. package/src/hooks/useDebounce.ts +25 -0
  67. package/src/hooks/useDebouncedCallback.ts +58 -0
  68. package/src/hooks/useDebugTools.ts +52 -0
  69. package/src/hooks/useEventsBus.ts +53 -0
  70. package/src/hooks/useImageLoader.ts +95 -0
  71. package/src/hooks/useMediaQuery.ts +40 -0
  72. package/src/hooks/useMobile.tsx +22 -0
  73. package/src/hooks/useToast.ts +194 -0
  74. package/src/index.ts +14 -0
  75. package/src/lib/index.ts +2 -0
  76. package/src/lib/og-image.ts +151 -0
  77. package/src/lib/utils.ts +6 -0
  78. package/src/styles/base.css +20 -0
  79. package/src/styles/globals.css +12 -0
  80. package/src/styles/index.css +25 -0
  81. package/src/styles/sources.css +11 -0
  82. package/src/styles/theme/animations.css +65 -0
  83. package/src/styles/theme/dark.css +49 -0
  84. package/src/styles/theme/light.css +50 -0
  85. package/src/styles/theme/tokens.css +134 -0
  86. package/src/styles/theme.css +22 -0
  87. package/src/styles/utilities.css +187 -0
  88. package/src/types/index.ts +0 -0
@@ -0,0 +1,111 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Slot } from "@radix-ui/react-slot"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+ import { Loader2 } from "lucide-react"
7
+
8
+ import { cn } from "../lib/utils"
9
+
10
+ const buttonVariants = cva(
11
+ "inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-semibold transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
12
+ {
13
+ variants: {
14
+ variant: {
15
+ default:
16
+ "bg-primary text-primary-foreground shadow hover:bg-primary/90",
17
+ destructive:
18
+ "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
19
+ outline:
20
+ "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
21
+ secondary:
22
+ "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
23
+ ghost: "hover:bg-accent hover:text-accent-foreground",
24
+ link: "text-primary underline-offset-4 hover:underline",
25
+ },
26
+ size: {
27
+ default: "h-9 px-4 py-2",
28
+ sm: "h-8 rounded-md px-3 text-sm",
29
+ xs: "h-7 rounded px-2 py-1 text-xs",
30
+ lg: "h-11 rounded-md px-8",
31
+ huge: "h-14 rounded-md px-10 text-lg",
32
+ icon: "h-9 w-9",
33
+ },
34
+ },
35
+ defaultVariants: {
36
+ variant: "default",
37
+ size: "default",
38
+ },
39
+ }
40
+ )
41
+
42
+ export interface ButtonProps
43
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
44
+ VariantProps<typeof buttonVariants> {
45
+ asChild?: boolean
46
+ loading?: boolean
47
+ }
48
+
49
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
50
+ ({ className, variant, size, asChild = false, loading = false, onClick, children, disabled, ...props }, ref) => {
51
+ const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
52
+ if (!loading) {
53
+ onClick?.(e);
54
+ }
55
+ };
56
+
57
+ // When asChild is true, Slot expects exactly ONE child element
58
+ // Don't render loading spinner with asChild to avoid "Children.only" error
59
+ if (asChild) {
60
+ return (
61
+ <Slot
62
+ className={cn(buttonVariants({ variant, size, className }))}
63
+ ref={ref}
64
+ onClick={onClick}
65
+ {...props}
66
+ >
67
+ {children}
68
+ </Slot>
69
+ )
70
+ }
71
+
72
+ return (
73
+ <button
74
+ className={cn(buttonVariants({ variant, size, className }))}
75
+ ref={ref}
76
+ onClick={handleClick}
77
+ disabled={disabled || loading}
78
+ {...props}
79
+ >
80
+ {loading && <Loader2 className="animate-spin" />}
81
+ {children}
82
+ </button>
83
+ )
84
+ }
85
+ )
86
+ Button.displayName = "Button"
87
+
88
+ // ButtonLink using native anchor tag (no Next.js dependency)
89
+ export interface ButtonLinkProps
90
+ extends React.AnchorHTMLAttributes<HTMLAnchorElement>,
91
+ VariantProps<typeof buttonVariants> {
92
+ href: string
93
+ }
94
+
95
+ const ButtonLink = React.forwardRef<HTMLAnchorElement, ButtonLinkProps>(
96
+ ({ className, variant, size, href, children, ...props }, ref) => {
97
+ return (
98
+ <a
99
+ href={href}
100
+ className={cn(buttonVariants({ variant, size, className }))}
101
+ ref={ref}
102
+ {...props}
103
+ >
104
+ {children}
105
+ </a>
106
+ )
107
+ }
108
+ )
109
+ ButtonLink.displayName = "ButtonLink"
110
+
111
+ export { Button, ButtonLink, buttonVariants }
@@ -0,0 +1,213 @@
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 "../lib/utils"
12
+ import { Button, buttonVariants } from "./button"
13
+
14
+ function Calendar({
15
+ className,
16
+ classNames,
17
+ showOutsideDays = true,
18
+ captionLayout = "label",
19
+ buttonVariant = "ghost",
20
+ formatters,
21
+ components,
22
+ ...props
23
+ }: React.ComponentProps<typeof DayPicker> & {
24
+ buttonVariant?: React.ComponentProps<typeof Button>["variant"]
25
+ }) {
26
+ const defaultClassNames = getDefaultClassNames()
27
+
28
+ return (
29
+ <DayPicker
30
+ showOutsideDays={showOutsideDays}
31
+ className={cn(
32
+ "bg-background group/calendar p-3 [--cell-size:2rem] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
33
+ String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
34
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
35
+ className
36
+ )}
37
+ captionLayout={captionLayout}
38
+ formatters={{
39
+ formatMonthDropdown: (date) =>
40
+ date.toLocaleString("default", { month: "short" }),
41
+ ...formatters,
42
+ }}
43
+ classNames={{
44
+ root: cn("w-full", defaultClassNames.root),
45
+ months: cn(
46
+ "relative flex flex-col gap-4 md:flex-row",
47
+ defaultClassNames.months
48
+ ),
49
+ month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
50
+ nav: cn(
51
+ "absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
52
+ defaultClassNames.nav
53
+ ),
54
+ button_previous: cn(
55
+ buttonVariants({ variant: buttonVariant }),
56
+ "h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
57
+ defaultClassNames.button_previous
58
+ ),
59
+ button_next: cn(
60
+ buttonVariants({ variant: buttonVariant }),
61
+ "h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
62
+ defaultClassNames.button_next
63
+ ),
64
+ month_caption: cn(
65
+ "flex h-[--cell-size] w-full items-center justify-center px-[--cell-size]",
66
+ defaultClassNames.month_caption
67
+ ),
68
+ dropdowns: cn(
69
+ "flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-sm font-medium",
70
+ defaultClassNames.dropdowns
71
+ ),
72
+ dropdown_root: cn(
73
+ "has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border",
74
+ defaultClassNames.dropdown_root
75
+ ),
76
+ dropdown: cn(
77
+ "bg-popover absolute inset-0 opacity-0",
78
+ defaultClassNames.dropdown
79
+ ),
80
+ caption_label: cn(
81
+ "select-none font-medium",
82
+ captionLayout === "label"
83
+ ? "text-sm"
84
+ : "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5",
85
+ defaultClassNames.caption_label
86
+ ),
87
+ table: "w-full border-collapse",
88
+ weekdays: cn("flex", defaultClassNames.weekdays),
89
+ weekday: cn(
90
+ "text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal",
91
+ defaultClassNames.weekday
92
+ ),
93
+ week: cn("mt-2 flex w-full", defaultClassNames.week),
94
+ week_number_header: cn(
95
+ "w-[--cell-size] select-none",
96
+ defaultClassNames.week_number_header
97
+ ),
98
+ week_number: cn(
99
+ "text-muted-foreground select-none text-[0.8rem]",
100
+ defaultClassNames.week_number
101
+ ),
102
+ day: cn(
103
+ "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",
104
+ defaultClassNames.day
105
+ ),
106
+ range_start: cn(
107
+ "bg-accent rounded-l-md",
108
+ defaultClassNames.range_start
109
+ ),
110
+ range_middle: cn("rounded-none", defaultClassNames.range_middle),
111
+ range_end: cn("bg-accent rounded-r-md", defaultClassNames.range_end),
112
+ today: cn(
113
+ "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
114
+ defaultClassNames.today
115
+ ),
116
+ outside: cn(
117
+ "text-muted-foreground aria-selected:text-muted-foreground",
118
+ defaultClassNames.outside
119
+ ),
120
+ disabled: cn(
121
+ "text-muted-foreground opacity-50",
122
+ defaultClassNames.disabled
123
+ ),
124
+ hidden: cn("invisible", defaultClassNames.hidden),
125
+ ...classNames,
126
+ }}
127
+ components={{
128
+ Root: ({ className, rootRef, ...props }) => {
129
+ return (
130
+ <div
131
+ data-slot="calendar"
132
+ ref={rootRef}
133
+ className={cn(className)}
134
+ {...props}
135
+ />
136
+ )
137
+ },
138
+ Chevron: ({ className, orientation, ...props }) => {
139
+ if (orientation === "left") {
140
+ return (
141
+ <ChevronLeftIcon className={cn("size-4", className)} {...props} />
142
+ )
143
+ }
144
+
145
+ if (orientation === "right") {
146
+ return (
147
+ <ChevronRightIcon
148
+ className={cn("size-4", className)}
149
+ {...props}
150
+ />
151
+ )
152
+ }
153
+
154
+ return (
155
+ <ChevronDownIcon className={cn("size-4", className)} {...props} />
156
+ )
157
+ },
158
+ DayButton: CalendarDayButton,
159
+ WeekNumber: ({ children, ...props }) => {
160
+ return (
161
+ <td {...props}>
162
+ <div className="flex size-[--cell-size] items-center justify-center text-center">
163
+ {children}
164
+ </div>
165
+ </td>
166
+ )
167
+ },
168
+ ...components,
169
+ }}
170
+ {...props}
171
+ />
172
+ )
173
+ }
174
+
175
+ function CalendarDayButton({
176
+ className,
177
+ day,
178
+ modifiers,
179
+ ...props
180
+ }: React.ComponentProps<typeof DayButton>) {
181
+ const defaultClassNames = getDefaultClassNames()
182
+
183
+ const ref = React.useRef<HTMLButtonElement>(null)
184
+ React.useEffect(() => {
185
+ if (modifiers.focused) ref.current?.focus()
186
+ }, [modifiers.focused])
187
+
188
+ return (
189
+ <Button
190
+ ref={ref}
191
+ variant="ghost"
192
+ size="icon"
193
+ data-day={day.date.toLocaleDateString()}
194
+ data-selected-single={
195
+ modifiers.selected &&
196
+ !modifiers.range_start &&
197
+ !modifiers.range_end &&
198
+ !modifiers.range_middle
199
+ }
200
+ data-range-start={modifiers.range_start}
201
+ data-range-end={modifiers.range_end}
202
+ data-range-middle={modifiers.range_middle}
203
+ className={cn(
204
+ "data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground 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",
205
+ defaultClassNames.day,
206
+ className
207
+ )}
208
+ {...props}
209
+ />
210
+ )
211
+ }
212
+
213
+ export { Calendar, CalendarDayButton }
@@ -0,0 +1,76 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "../lib/utils"
4
+
5
+ const Card = React.forwardRef<
6
+ HTMLDivElement,
7
+ React.HTMLAttributes<HTMLDivElement>
8
+ >(({ className, ...props }, ref) => (
9
+ <div
10
+ ref={ref}
11
+ className={cn(
12
+ "rounded-xl border bg-card text-card-foreground shadow",
13
+ className
14
+ )}
15
+ {...props}
16
+ />
17
+ ))
18
+ Card.displayName = "Card"
19
+
20
+ const CardHeader = React.forwardRef<
21
+ HTMLDivElement,
22
+ React.HTMLAttributes<HTMLDivElement>
23
+ >(({ className, ...props }, ref) => (
24
+ <div
25
+ ref={ref}
26
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
27
+ {...props}
28
+ />
29
+ ))
30
+ CardHeader.displayName = "CardHeader"
31
+
32
+ const CardTitle = React.forwardRef<
33
+ HTMLDivElement,
34
+ React.HTMLAttributes<HTMLDivElement>
35
+ >(({ className, ...props }, ref) => (
36
+ <div
37
+ ref={ref}
38
+ className={cn("font-semibold leading-none tracking-tight", className)}
39
+ {...props}
40
+ />
41
+ ))
42
+ CardTitle.displayName = "CardTitle"
43
+
44
+ const CardDescription = React.forwardRef<
45
+ HTMLDivElement,
46
+ React.HTMLAttributes<HTMLDivElement>
47
+ >(({ className, ...props }, ref) => (
48
+ <div
49
+ ref={ref}
50
+ className={cn("text-sm text-muted-foreground", className)}
51
+ {...props}
52
+ />
53
+ ))
54
+ CardDescription.displayName = "CardDescription"
55
+
56
+ const CardContent = React.forwardRef<
57
+ HTMLDivElement,
58
+ React.HTMLAttributes<HTMLDivElement>
59
+ >(({ className, ...props }, ref) => (
60
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
61
+ ))
62
+ CardContent.displayName = "CardContent"
63
+
64
+ const CardFooter = React.forwardRef<
65
+ HTMLDivElement,
66
+ React.HTMLAttributes<HTMLDivElement>
67
+ >(({ className, ...props }, ref) => (
68
+ <div
69
+ ref={ref}
70
+ className={cn("flex items-center p-6 pt-0", className)}
71
+ {...props}
72
+ />
73
+ ))
74
+ CardFooter.displayName = "CardFooter"
75
+
76
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
@@ -0,0 +1,261 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import useEmblaCarousel, {
5
+ type UseEmblaCarouselType,
6
+ } from "embla-carousel-react"
7
+ import { cn } from "../lib/utils"
8
+ import { Button } from "./button"
9
+ import { ArrowLeftIcon, ArrowRightIcon } from "@radix-ui/react-icons"
10
+
11
+ type CarouselApi = UseEmblaCarouselType[1]
12
+ type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
13
+ type CarouselOptions = UseCarouselParameters[0]
14
+ type CarouselPlugin = UseCarouselParameters[1]
15
+
16
+ type CarouselProps = {
17
+ opts?: CarouselOptions
18
+ plugins?: CarouselPlugin
19
+ orientation?: "horizontal" | "vertical"
20
+ setApi?: (api: CarouselApi) => void
21
+ }
22
+
23
+ type CarouselContextProps = {
24
+ carouselRef: ReturnType<typeof useEmblaCarousel>[0]
25
+ api: ReturnType<typeof useEmblaCarousel>[1]
26
+ scrollPrev: () => void
27
+ scrollNext: () => void
28
+ canScrollPrev: boolean
29
+ canScrollNext: boolean
30
+ } & CarouselProps
31
+
32
+ const CarouselContext = React.createContext<CarouselContextProps | null>(null)
33
+
34
+ function useCarousel() {
35
+ const context = React.useContext(CarouselContext)
36
+
37
+ if (!context) {
38
+ throw new Error("useCarousel must be used within a <Carousel />")
39
+ }
40
+
41
+ return context
42
+ }
43
+
44
+ const Carousel = React.forwardRef<
45
+ HTMLDivElement,
46
+ React.HTMLAttributes<HTMLDivElement> & CarouselProps
47
+ >(
48
+ (
49
+ {
50
+ orientation = "horizontal",
51
+ opts,
52
+ setApi,
53
+ plugins,
54
+ className,
55
+ children,
56
+ ...props
57
+ },
58
+ ref
59
+ ) => {
60
+ const [carouselRef, api] = useEmblaCarousel(
61
+ {
62
+ ...opts,
63
+ axis: orientation === "horizontal" ? "x" : "y",
64
+ },
65
+ plugins
66
+ )
67
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false)
68
+ const [canScrollNext, setCanScrollNext] = React.useState(false)
69
+
70
+ const onSelect = React.useCallback((api: CarouselApi) => {
71
+ if (!api) {
72
+ return
73
+ }
74
+
75
+ setCanScrollPrev(api.canScrollPrev())
76
+ setCanScrollNext(api.canScrollNext())
77
+ }, [])
78
+
79
+ const scrollPrev = React.useCallback(() => {
80
+ api?.scrollPrev()
81
+ }, [api])
82
+
83
+ const scrollNext = React.useCallback(() => {
84
+ api?.scrollNext()
85
+ }, [api])
86
+
87
+ const handleKeyDown = React.useCallback(
88
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
89
+ if (event.key === "ArrowLeft") {
90
+ event.preventDefault()
91
+ scrollPrev()
92
+ } else if (event.key === "ArrowRight") {
93
+ event.preventDefault()
94
+ scrollNext()
95
+ }
96
+ },
97
+ [scrollPrev, scrollNext]
98
+ )
99
+
100
+ React.useEffect(() => {
101
+ if (!api || !setApi) {
102
+ return
103
+ }
104
+
105
+ setApi(api)
106
+ }, [api, setApi])
107
+
108
+ React.useEffect(() => {
109
+ if (!api) {
110
+ return
111
+ }
112
+
113
+ onSelect(api)
114
+ api.on("reInit", onSelect)
115
+ api.on("select", onSelect)
116
+
117
+ return () => {
118
+ api?.off("select", onSelect)
119
+ }
120
+ }, [api, onSelect])
121
+
122
+ return (
123
+ <CarouselContext.Provider
124
+ value={{
125
+ carouselRef,
126
+ api: api,
127
+ opts,
128
+ orientation:
129
+ orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
130
+ scrollPrev,
131
+ scrollNext,
132
+ canScrollPrev,
133
+ canScrollNext,
134
+ }}
135
+ >
136
+ <div
137
+ ref={ref}
138
+ onKeyDownCapture={handleKeyDown}
139
+ className={cn("relative", className)}
140
+ role="region"
141
+ aria-roledescription="carousel"
142
+ {...props}
143
+ >
144
+ {children}
145
+ </div>
146
+ </CarouselContext.Provider>
147
+ )
148
+ }
149
+ )
150
+ Carousel.displayName = "Carousel"
151
+
152
+ const CarouselContent = React.forwardRef<
153
+ HTMLDivElement,
154
+ React.HTMLAttributes<HTMLDivElement>
155
+ >(({ className, ...props }, ref) => {
156
+ const { carouselRef, orientation } = useCarousel()
157
+
158
+ return (
159
+ <div ref={carouselRef} className="overflow-hidden">
160
+ <div
161
+ ref={ref}
162
+ className={cn(
163
+ "flex",
164
+ orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
165
+ className
166
+ )}
167
+ {...props}
168
+ />
169
+ </div>
170
+ )
171
+ })
172
+ CarouselContent.displayName = "CarouselContent"
173
+
174
+ const CarouselItem = React.forwardRef<
175
+ HTMLDivElement,
176
+ React.HTMLAttributes<HTMLDivElement>
177
+ >(({ className, ...props }, ref) => {
178
+ const { orientation } = useCarousel()
179
+
180
+ return (
181
+ <div
182
+ ref={ref}
183
+ role="group"
184
+ aria-roledescription="slide"
185
+ className={cn(
186
+ "min-w-0 shrink-0 grow-0 basis-full",
187
+ orientation === "horizontal" ? "pl-4" : "pt-4",
188
+ className
189
+ )}
190
+ {...props}
191
+ />
192
+ )
193
+ })
194
+ CarouselItem.displayName = "CarouselItem"
195
+
196
+ const CarouselPrevious = React.forwardRef<
197
+ HTMLButtonElement,
198
+ React.ComponentProps<typeof Button>
199
+ >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
200
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel()
201
+
202
+ return (
203
+ <Button
204
+ ref={ref}
205
+ variant={variant}
206
+ size={size}
207
+ className={cn(
208
+ "absolute h-8 w-8 rounded-full",
209
+ orientation === "horizontal"
210
+ ? "-left-12 top-1/2 -translate-y-1/2"
211
+ : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
212
+ className
213
+ )}
214
+ disabled={!canScrollPrev}
215
+ onClick={scrollPrev}
216
+ {...props}
217
+ >
218
+ <ArrowLeftIcon className="h-4 w-4" />
219
+ <span className="sr-only">Previous slide</span>
220
+ </Button>
221
+ )
222
+ })
223
+ CarouselPrevious.displayName = "CarouselPrevious"
224
+
225
+ const CarouselNext = React.forwardRef<
226
+ HTMLButtonElement,
227
+ React.ComponentProps<typeof Button>
228
+ >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
229
+ const { orientation, scrollNext, canScrollNext } = useCarousel()
230
+
231
+ return (
232
+ <Button
233
+ ref={ref}
234
+ variant={variant}
235
+ size={size}
236
+ className={cn(
237
+ "absolute h-8 w-8 rounded-full",
238
+ orientation === "horizontal"
239
+ ? "-right-12 top-1/2 -translate-y-1/2"
240
+ : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
241
+ className
242
+ )}
243
+ disabled={!canScrollNext}
244
+ onClick={scrollNext}
245
+ {...props}
246
+ >
247
+ <ArrowRightIcon className="h-4 w-4" />
248
+ <span className="sr-only">Next slide</span>
249
+ </Button>
250
+ )
251
+ })
252
+ CarouselNext.displayName = "CarouselNext"
253
+
254
+ export {
255
+ type CarouselApi,
256
+ Carousel,
257
+ CarouselContent,
258
+ CarouselItem,
259
+ CarouselPrevious,
260
+ CarouselNext,
261
+ }