@olympusoss/canvas 2.6.19

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 (128) hide show
  1. package/package.json +179 -0
  2. package/src/components/atoms/README.md +11 -0
  3. package/src/components/atoms/aspect-ratio.tsx +32 -0
  4. package/src/components/atoms/avatar.tsx +98 -0
  5. package/src/components/atoms/badge.tsx +44 -0
  6. package/src/components/atoms/brand-mark.tsx +74 -0
  7. package/src/components/atoms/button.tsx +104 -0
  8. package/src/components/atoms/checkbox.tsx +63 -0
  9. package/src/components/atoms/flex-box.tsx +105 -0
  10. package/src/components/atoms/icon.tsx +34 -0
  11. package/src/components/atoms/input.tsx +91 -0
  12. package/src/components/atoms/label.tsx +41 -0
  13. package/src/components/atoms/logo.tsx +89 -0
  14. package/src/components/atoms/progress.tsx +55 -0
  15. package/src/components/atoms/radio-group.tsx +122 -0
  16. package/src/components/atoms/scroll-area.tsx +106 -0
  17. package/src/components/atoms/section.tsx +48 -0
  18. package/src/components/atoms/separator.tsx +45 -0
  19. package/src/components/atoms/skeleton.tsx +17 -0
  20. package/src/components/atoms/slider.tsx +93 -0
  21. package/src/components/atoms/switch.tsx +60 -0
  22. package/src/components/atoms/textarea.tsx +78 -0
  23. package/src/components/atoms/toggle.tsx +80 -0
  24. package/src/components/charts/activity-heatmap.tsx +96 -0
  25. package/src/components/charts/axes.tsx +21 -0
  26. package/src/components/charts/chart-container.tsx +195 -0
  27. package/src/components/charts/chart-legend.tsx +67 -0
  28. package/src/components/charts/chart-tooltip.tsx +161 -0
  29. package/src/components/charts/chart-types.tsx +49 -0
  30. package/src/components/charts/containers.tsx +11 -0
  31. package/src/components/charts/data.tsx +16 -0
  32. package/src/components/charts/details.tsx +25 -0
  33. package/src/components/charts/gauge.tsx +106 -0
  34. package/src/components/charts/grids.tsx +8 -0
  35. package/src/components/charts/index.ts +62 -0
  36. package/src/components/charts/labeled-bar-list.tsx +85 -0
  37. package/src/components/charts/references.tsx +8 -0
  38. package/src/components/charts/service-health-list.tsx +73 -0
  39. package/src/components/charts/sparkline.tsx +52 -0
  40. package/src/components/charts/stacked-bar.tsx +104 -0
  41. package/src/components/charts/text.tsx +10 -0
  42. package/src/components/charts/world-heat-map-inner.tsx +317 -0
  43. package/src/components/charts/world-heat-map.tsx +184 -0
  44. package/src/components/molecules/README.md +12 -0
  45. package/src/components/molecules/action-bar.tsx +73 -0
  46. package/src/components/molecules/activity-item.tsx +74 -0
  47. package/src/components/molecules/alert.tsx +80 -0
  48. package/src/components/molecules/animated-background.tsx +92 -0
  49. package/src/components/molecules/brand-lockup.tsx +48 -0
  50. package/src/components/molecules/breadcrumb.tsx +161 -0
  51. package/src/components/molecules/button-group.tsx +104 -0
  52. package/src/components/molecules/calendar.tsx +216 -0
  53. package/src/components/molecules/card.tsx +101 -0
  54. package/src/components/molecules/code-block.tsx +48 -0
  55. package/src/components/molecules/empty-state.tsx +55 -0
  56. package/src/components/molecules/error-state.tsx +42 -0
  57. package/src/components/molecules/field-display.tsx +35 -0
  58. package/src/components/molecules/input-otp.tsx +74 -0
  59. package/src/components/molecules/loading-state.tsx +36 -0
  60. package/src/components/molecules/notification-item.tsx +67 -0
  61. package/src/components/molecules/notification-list.tsx +45 -0
  62. package/src/components/molecules/number-badge.tsx +53 -0
  63. package/src/components/molecules/page-header.tsx +88 -0
  64. package/src/components/molecules/page-tabs.tsx +94 -0
  65. package/src/components/molecules/pagination.tsx +150 -0
  66. package/src/components/molecules/phone-input.tsx +200 -0
  67. package/src/components/molecules/search-bar.tsx +64 -0
  68. package/src/components/molecules/secret-field.tsx +158 -0
  69. package/src/components/molecules/section-card.tsx +91 -0
  70. package/src/components/molecules/stat-card.tsx +96 -0
  71. package/src/components/molecules/status-badge.tsx +42 -0
  72. package/src/components/molecules/stepper.tsx +96 -0
  73. package/src/components/molecules/table.tsx +157 -0
  74. package/src/components/molecules/toggle-group.tsx +145 -0
  75. package/src/components/molecules/tooltip.tsx +150 -0
  76. package/src/components/molecules/user-avatar-chip.tsx +71 -0
  77. package/src/components/organisms/README.md +14 -0
  78. package/src/components/organisms/accordion.tsx +149 -0
  79. package/src/components/organisms/alert-dialog.tsx +269 -0
  80. package/src/components/organisms/carousel.tsx +244 -0
  81. package/src/components/organisms/collapsible.tsx +69 -0
  82. package/src/components/organisms/command.tsx +143 -0
  83. package/src/components/organisms/context-menu.tsx +333 -0
  84. package/src/components/organisms/dashboard-grid.tsx +360 -0
  85. package/src/components/organisms/data-table.tsx +330 -0
  86. package/src/components/organisms/dialog.tsx +304 -0
  87. package/src/components/organisms/drawer.tsx +100 -0
  88. package/src/components/organisms/dropdown-menu.tsx +434 -0
  89. package/src/components/organisms/editors/code-editor.tsx +144 -0
  90. package/src/components/organisms/editors/index.ts +4 -0
  91. package/src/components/organisms/editors/markdown-editor.tsx +153 -0
  92. package/src/components/organisms/editors/markdown-renderer.ts +27 -0
  93. package/src/components/organisms/editors/prose-canvas-classes.ts +45 -0
  94. package/src/components/organisms/editors/rich-text-editor.tsx +126 -0
  95. package/src/components/organisms/editors/toolbar/md-toolbar.tsx +129 -0
  96. package/src/components/organisms/editors/toolbar/rte-toolbar.tsx +211 -0
  97. package/src/components/organisms/editors/toolbar/toolbar-shell.tsx +45 -0
  98. package/src/components/organisms/editors/use-codemirror-theme.ts +61 -0
  99. package/src/components/organisms/error-boundary.tsx +61 -0
  100. package/src/components/organisms/form.tsx +174 -0
  101. package/src/components/organisms/hover-card.tsx +114 -0
  102. package/src/components/organisms/menubar.tsx +491 -0
  103. package/src/components/organisms/navbar.tsx +101 -0
  104. package/src/components/organisms/navigation-menu.tsx +234 -0
  105. package/src/components/organisms/popover.tsx +144 -0
  106. package/src/components/organisms/resizable.tsx +39 -0
  107. package/src/components/organisms/schema-form.tsx +232 -0
  108. package/src/components/organisms/select.tsx +303 -0
  109. package/src/components/organisms/sheet.tsx +256 -0
  110. package/src/components/organisms/sidebar.tsx +1037 -0
  111. package/src/components/organisms/sonner.tsx +96 -0
  112. package/src/components/organisms/tabs.tsx +132 -0
  113. package/src/components/organisms/theme-provider.tsx +101 -0
  114. package/src/hooks/use-mobile.tsx +19 -0
  115. package/src/index.ts +547 -0
  116. package/src/lib/portal-container.tsx +35 -0
  117. package/src/lib/utils.ts +6 -0
  118. package/src/native.ts +23 -0
  119. package/src/tokens/colors.ts +91 -0
  120. package/src/tokens/index.ts +3 -0
  121. package/src/tokens/spacing.ts +55 -0
  122. package/src/tokens/typography.ts +27 -0
  123. package/styles/canvas.css +55 -0
  124. package/styles/dashboard-grid.css +47 -0
  125. package/styles/leaflet.css +13 -0
  126. package/styles/tokens.css +234 -0
  127. package/tailwind.config.ts +70 -0
  128. package/tsconfig.json +23 -0
@@ -0,0 +1,269 @@
1
+ "use client";
2
+
3
+ import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
4
+ import * as React from "react";
5
+
6
+ import { cn } from "../../lib/utils";
7
+ import { buttonVariants } from "../atoms/button";
8
+
9
+ export interface AlertDialogProps extends React.ComponentProps<typeof AlertDialogPrimitive.Root> {
10
+ /** Controlled open state. Pair with `onOpenChange`. */
11
+ open?: boolean;
12
+ /**
13
+ * Initial open state for uncontrolled usage.
14
+ * @default false
15
+ */
16
+ defaultOpen?: boolean;
17
+ /** Fires whenever the dialog opens or closes. */
18
+ onOpenChange?: (open: boolean) => void;
19
+ /** Trigger + Content. */
20
+ children?: React.ReactNode;
21
+ }
22
+
23
+ const AlertDialog = AlertDialogPrimitive.Root as React.FC<AlertDialogProps>;
24
+
25
+ export interface AlertDialogTriggerProps
26
+ extends React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Trigger> {
27
+ /**
28
+ * Render as a Radix Slot — forwards props onto the immediate child.
29
+ * @default false
30
+ */
31
+ asChild?: boolean;
32
+ /** The button (or slot) that opens the alert. */
33
+ children?: React.ReactNode;
34
+ /** Tailwind / CSS classes merged via `cn()`. */
35
+ className?: string;
36
+ }
37
+
38
+ const AlertDialogTrigger = AlertDialogPrimitive.Trigger as React.ForwardRefExoticComponent<
39
+ AlertDialogTriggerProps & React.RefAttributes<HTMLButtonElement>
40
+ >;
41
+
42
+ export interface AlertDialogPortalProps
43
+ extends React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Portal> {
44
+ /** DOM element to portal into. Defaults to `document.body`. */
45
+ container?: HTMLElement | null;
46
+ /**
47
+ * Force the portal to mount its children even when closed.
48
+ * @default false
49
+ */
50
+ forceMount?: true;
51
+ children?: React.ReactNode;
52
+ }
53
+
54
+ const AlertDialogPortal = AlertDialogPrimitive.Portal as React.FC<AlertDialogPortalProps>;
55
+
56
+ export interface AlertDialogOverlayProps
57
+ extends React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay> {
58
+ /**
59
+ * Render as a Radix Slot — forwards props onto the immediate child.
60
+ * @default false
61
+ */
62
+ asChild?: boolean;
63
+ /**
64
+ * Force the overlay to mount even when closed.
65
+ * @default false
66
+ */
67
+ forceMount?: true;
68
+ /** Tailwind / CSS classes merged via `cn()`. */
69
+ className?: string;
70
+ }
71
+
72
+ const AlertDialogOverlay = React.forwardRef<
73
+ React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
74
+ AlertDialogOverlayProps
75
+ >(({ className, ...props }, ref) => (
76
+ <AlertDialogPrimitive.Overlay
77
+ className={cn(
78
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
79
+ className,
80
+ )}
81
+ {...props}
82
+ ref={ref}
83
+ />
84
+ ));
85
+ AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
86
+
87
+ export interface AlertDialogContentProps
88
+ extends React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content> {
89
+ /**
90
+ * Render as a Radix Slot — forwards props onto the immediate child.
91
+ * @default false
92
+ */
93
+ asChild?: boolean;
94
+ /**
95
+ * Force the content to mount even when closed. Useful for
96
+ * exit animations.
97
+ * @default false
98
+ */
99
+ forceMount?: true;
100
+ /** Fires when focus enters the dialog after it opens. */
101
+ onOpenAutoFocus?: (event: Event) => void;
102
+ /** Fires when focus leaves the dialog after it closes. */
103
+ onCloseAutoFocus?: (event: Event) => void;
104
+ /** Fires when the Escape key is pressed. */
105
+ onEscapeKeyDown?: (event: KeyboardEvent) => void;
106
+ /**
107
+ * Title + Description + Footer (with `<AlertDialogAction>` and
108
+ * `<AlertDialogCancel>`).
109
+ */
110
+ children?: React.ReactNode;
111
+ /** Tailwind / CSS classes merged via `cn()`. */
112
+ className?: string;
113
+ }
114
+
115
+ const AlertDialogContent = React.forwardRef<
116
+ React.ElementRef<typeof AlertDialogPrimitive.Content>,
117
+ AlertDialogContentProps
118
+ >(({ className, ...props }, ref) => (
119
+ <AlertDialogPortal>
120
+ <AlertDialogOverlay />
121
+ <AlertDialogPrimitive.Content
122
+ ref={ref}
123
+ className={cn(
124
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
125
+ className,
126
+ )}
127
+ {...props}
128
+ />
129
+ </AlertDialogPortal>
130
+ ));
131
+ AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
132
+
133
+ export interface AlertDialogHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
134
+ /** Title + optional description. */
135
+ children?: React.ReactNode;
136
+ /** Tailwind / CSS classes merged via `cn()`. */
137
+ className?: string;
138
+ }
139
+
140
+ const AlertDialogHeader = ({ className, ...props }: AlertDialogHeaderProps) => (
141
+ <div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
142
+ );
143
+ AlertDialogHeader.displayName = "AlertDialogHeader";
144
+
145
+ export interface AlertDialogFooterProps extends React.HTMLAttributes<HTMLDivElement> {
146
+ /** Action + Cancel buttons. */
147
+ children?: React.ReactNode;
148
+ /** Tailwind / CSS classes merged via `cn()`. */
149
+ className?: string;
150
+ }
151
+
152
+ const AlertDialogFooter = ({ className, ...props }: AlertDialogFooterProps) => (
153
+ <div
154
+ className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
155
+ {...props}
156
+ />
157
+ );
158
+ AlertDialogFooter.displayName = "AlertDialogFooter";
159
+
160
+ export interface AlertDialogTitleProps
161
+ extends React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title> {
162
+ /**
163
+ * Render as a Radix Slot — useful to swap the heading level.
164
+ * @default false
165
+ */
166
+ asChild?: boolean;
167
+ /** Title text. */
168
+ children?: React.ReactNode;
169
+ /** Tailwind / CSS classes merged via `cn()`. */
170
+ className?: string;
171
+ }
172
+
173
+ const AlertDialogTitle = React.forwardRef<
174
+ React.ElementRef<typeof AlertDialogPrimitive.Title>,
175
+ AlertDialogTitleProps
176
+ >(({ className, ...props }, ref) => (
177
+ <AlertDialogPrimitive.Title
178
+ ref={ref}
179
+ className={cn("text-lg font-semibold", className)}
180
+ {...props}
181
+ />
182
+ ));
183
+ AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
184
+
185
+ export interface AlertDialogDescriptionProps
186
+ extends React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description> {
187
+ /**
188
+ * Render as a Radix Slot.
189
+ * @default false
190
+ */
191
+ asChild?: boolean;
192
+ /** Description text shown below the title. */
193
+ children?: React.ReactNode;
194
+ /** Tailwind / CSS classes merged via `cn()`. */
195
+ className?: string;
196
+ }
197
+
198
+ const AlertDialogDescription = React.forwardRef<
199
+ React.ElementRef<typeof AlertDialogPrimitive.Description>,
200
+ AlertDialogDescriptionProps
201
+ >(({ className, ...props }, ref) => (
202
+ <AlertDialogPrimitive.Description
203
+ ref={ref}
204
+ className={cn("text-sm text-muted-foreground", className)}
205
+ {...props}
206
+ />
207
+ ));
208
+ AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
209
+
210
+ export interface AlertDialogActionProps
211
+ extends React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action> {
212
+ /**
213
+ * Render as a Radix Slot — wrap a custom button while still hooking the
214
+ * dialog's commit behaviour.
215
+ * @default false
216
+ */
217
+ asChild?: boolean;
218
+ /** Action label (e.g. "Delete", "Confirm"). */
219
+ children?: React.ReactNode;
220
+ /** Tailwind / CSS classes merged via `cn()`. Defaults to canvas's primary button. */
221
+ className?: string;
222
+ }
223
+
224
+ const AlertDialogAction = React.forwardRef<
225
+ React.ElementRef<typeof AlertDialogPrimitive.Action>,
226
+ AlertDialogActionProps
227
+ >(({ className, ...props }, ref) => (
228
+ <AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
229
+ ));
230
+ AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
231
+
232
+ export interface AlertDialogCancelProps
233
+ extends React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel> {
234
+ /**
235
+ * Render as a Radix Slot.
236
+ * @default false
237
+ */
238
+ asChild?: boolean;
239
+ /** Cancel label (e.g. "Cancel", "Keep editing"). */
240
+ children?: React.ReactNode;
241
+ /** Tailwind / CSS classes merged via `cn()`. Defaults to canvas's outline button. */
242
+ className?: string;
243
+ }
244
+
245
+ const AlertDialogCancel = React.forwardRef<
246
+ React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
247
+ AlertDialogCancelProps
248
+ >(({ className, ...props }, ref) => (
249
+ <AlertDialogPrimitive.Cancel
250
+ ref={ref}
251
+ className={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
252
+ {...props}
253
+ />
254
+ ));
255
+ AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
256
+
257
+ export {
258
+ AlertDialog,
259
+ AlertDialogAction,
260
+ AlertDialogCancel,
261
+ AlertDialogContent,
262
+ AlertDialogDescription,
263
+ AlertDialogFooter,
264
+ AlertDialogHeader,
265
+ AlertDialogOverlay,
266
+ AlertDialogPortal,
267
+ AlertDialogTitle,
268
+ AlertDialogTrigger,
269
+ };
@@ -0,0 +1,244 @@
1
+ "use client";
2
+
3
+ import useEmblaCarousel, { type UseEmblaCarouselType } from "embla-carousel-react";
4
+ import { ArrowLeft, ArrowRight } from "lucide-react";
5
+ import * as React from "react";
6
+
7
+ import { cn } from "../../lib/utils";
8
+ import { Button } from "../atoms/button";
9
+
10
+ type CarouselApi = UseEmblaCarouselType[1];
11
+ type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
12
+ type CarouselOptions = UseCarouselParameters[0];
13
+ type CarouselPlugin = UseCarouselParameters[1];
14
+
15
+ type CarouselProps = {
16
+ opts?: CarouselOptions;
17
+ plugins?: CarouselPlugin;
18
+ orientation?: "horizontal" | "vertical";
19
+ setApi?: (api: CarouselApi) => void;
20
+ };
21
+
22
+ type CarouselContextProps = {
23
+ carouselRef: ReturnType<typeof useEmblaCarousel>[0];
24
+ api: ReturnType<typeof useEmblaCarousel>[1];
25
+ scrollPrev: () => void;
26
+ scrollNext: () => void;
27
+ canScrollPrev: boolean;
28
+ canScrollNext: boolean;
29
+ } & CarouselProps;
30
+
31
+ const CarouselContext = React.createContext<CarouselContextProps | null>(null);
32
+
33
+ function useCarousel() {
34
+ const context = React.useContext(CarouselContext);
35
+
36
+ if (!context) {
37
+ throw new Error("useCarousel must be used within a <Carousel />");
38
+ }
39
+
40
+ return context;
41
+ }
42
+
43
+ const Carousel = React.forwardRef<
44
+ HTMLDivElement,
45
+ React.HTMLAttributes<HTMLDivElement> & CarouselProps
46
+ >(({ orientation = "horizontal", opts, setApi, plugins, className, children, ...props }, ref) => {
47
+ const [carouselRef, api] = useEmblaCarousel(
48
+ {
49
+ ...opts,
50
+ axis: orientation === "horizontal" ? "x" : "y",
51
+ },
52
+ plugins,
53
+ );
54
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false);
55
+ const [canScrollNext, setCanScrollNext] = React.useState(false);
56
+
57
+ const onSelect = React.useCallback((api: CarouselApi) => {
58
+ /* c8 ignore next 3 -- defensive guard: Embla always provides api in the onSelect handler */
59
+ if (!api) {
60
+ return;
61
+ }
62
+
63
+ setCanScrollPrev(api.canScrollPrev());
64
+ setCanScrollNext(api.canScrollNext());
65
+ }, []);
66
+
67
+ const scrollPrev = React.useCallback(() => {
68
+ api?.scrollPrev();
69
+ }, [api]);
70
+
71
+ const scrollNext = React.useCallback(() => {
72
+ api?.scrollNext();
73
+ }, [api]);
74
+
75
+ const handleKeyDown = React.useCallback(
76
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
77
+ if (event.key === "ArrowLeft") {
78
+ event.preventDefault();
79
+ scrollPrev();
80
+ } else if (event.key === "ArrowRight") {
81
+ event.preventDefault();
82
+ scrollNext();
83
+ }
84
+ },
85
+ [scrollPrev, scrollNext],
86
+ );
87
+
88
+ React.useEffect(() => {
89
+ if (!api || !setApi) {
90
+ return;
91
+ }
92
+
93
+ setApi(api);
94
+ }, [api, setApi]);
95
+
96
+ React.useEffect(() => {
97
+ if (!api) {
98
+ return;
99
+ }
100
+
101
+ onSelect(api);
102
+ api.on("reInit", onSelect);
103
+ api.on("select", onSelect);
104
+
105
+ return () => {
106
+ api?.off("select", onSelect);
107
+ };
108
+ }, [api, onSelect]);
109
+
110
+ return (
111
+ <CarouselContext.Provider
112
+ value={{
113
+ carouselRef,
114
+ api: api,
115
+ opts,
116
+ // `orientation` is always defined (default: "horizontal"). No fallback needed.
117
+ orientation,
118
+ scrollPrev,
119
+ scrollNext,
120
+ canScrollPrev,
121
+ canScrollNext,
122
+ }}
123
+ >
124
+ <div
125
+ ref={ref}
126
+ onKeyDownCapture={handleKeyDown}
127
+ className={cn("relative", className)}
128
+ role="region"
129
+ aria-roledescription="carousel"
130
+ {...props}
131
+ >
132
+ {children}
133
+ </div>
134
+ </CarouselContext.Provider>
135
+ );
136
+ });
137
+ Carousel.displayName = "Carousel";
138
+
139
+ const CarouselContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
140
+ ({ className, ...props }, ref) => {
141
+ const { carouselRef, orientation } = useCarousel();
142
+
143
+ return (
144
+ <div ref={carouselRef} className="overflow-hidden">
145
+ <div
146
+ ref={ref}
147
+ className={cn(
148
+ "flex",
149
+ orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
150
+ className,
151
+ )}
152
+ {...props}
153
+ />
154
+ </div>
155
+ );
156
+ },
157
+ );
158
+ CarouselContent.displayName = "CarouselContent";
159
+
160
+ const CarouselItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
161
+ ({ className, ...props }, ref) => {
162
+ const { orientation } = useCarousel();
163
+
164
+ return (
165
+ <div
166
+ ref={ref}
167
+ role="group"
168
+ aria-roledescription="slide"
169
+ className={cn(
170
+ "min-w-0 shrink-0 grow-0 basis-full",
171
+ orientation === "horizontal" ? "pl-4" : "pt-4",
172
+ className,
173
+ )}
174
+ {...props}
175
+ />
176
+ );
177
+ },
178
+ );
179
+ CarouselItem.displayName = "CarouselItem";
180
+
181
+ const CarouselPrevious = React.forwardRef<HTMLButtonElement, React.ComponentProps<typeof Button>>(
182
+ ({ className, variant = "outline", size = "icon", ...props }, ref) => {
183
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel();
184
+
185
+ return (
186
+ <Button
187
+ ref={ref}
188
+ variant={variant}
189
+ size={size}
190
+ className={cn(
191
+ "absolute h-8 w-8 rounded-full",
192
+ orientation === "horizontal"
193
+ ? "-left-12 top-1/2 -translate-y-1/2"
194
+ : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
195
+ className,
196
+ )}
197
+ disabled={!canScrollPrev}
198
+ onClick={scrollPrev}
199
+ {...props}
200
+ >
201
+ <ArrowLeft className="h-4 w-4" />
202
+ <span className="sr-only">Previous slide</span>
203
+ </Button>
204
+ );
205
+ },
206
+ );
207
+ CarouselPrevious.displayName = "CarouselPrevious";
208
+
209
+ const CarouselNext = React.forwardRef<HTMLButtonElement, React.ComponentProps<typeof Button>>(
210
+ ({ className, variant = "outline", size = "icon", ...props }, ref) => {
211
+ const { orientation, scrollNext, canScrollNext } = useCarousel();
212
+
213
+ return (
214
+ <Button
215
+ ref={ref}
216
+ variant={variant}
217
+ size={size}
218
+ className={cn(
219
+ "absolute h-8 w-8 rounded-full",
220
+ orientation === "horizontal"
221
+ ? "-right-12 top-1/2 -translate-y-1/2"
222
+ : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
223
+ className,
224
+ )}
225
+ disabled={!canScrollNext}
226
+ onClick={scrollNext}
227
+ {...props}
228
+ >
229
+ <ArrowRight className="h-4 w-4" />
230
+ <span className="sr-only">Next slide</span>
231
+ </Button>
232
+ );
233
+ },
234
+ );
235
+ CarouselNext.displayName = "CarouselNext";
236
+
237
+ export {
238
+ Carousel,
239
+ type CarouselApi,
240
+ CarouselContent,
241
+ CarouselItem,
242
+ CarouselNext,
243
+ CarouselPrevious,
244
+ };
@@ -0,0 +1,69 @@
1
+ "use client";
2
+
3
+ import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
4
+ import type * as React from "react";
5
+
6
+ export interface CollapsibleProps extends React.ComponentProps<typeof CollapsiblePrimitive.Root> {
7
+ /** Controlled open state. Pair with `onOpenChange`. */
8
+ open?: boolean;
9
+ /**
10
+ * Initial open state for uncontrolled usage.
11
+ * @default false
12
+ */
13
+ defaultOpen?: boolean;
14
+ /** Fires whenever the collapsible opens or closes. */
15
+ onOpenChange?: (open: boolean) => void;
16
+ /**
17
+ * Disable the trigger.
18
+ * @default false
19
+ */
20
+ disabled?: boolean;
21
+ /** Trigger + Content. */
22
+ children?: React.ReactNode;
23
+ className?: string;
24
+ }
25
+
26
+ const Collapsible = CollapsiblePrimitive.Root as React.FC<CollapsibleProps>;
27
+
28
+ export interface CollapsibleTriggerProps
29
+ extends React.ComponentPropsWithoutRef<typeof CollapsiblePrimitive.CollapsibleTrigger> {
30
+ /**
31
+ * Render as a Radix Slot — wrap a custom button while keeping the
32
+ * collapsible's toggle behaviour.
33
+ * @default false
34
+ */
35
+ asChild?: boolean;
36
+ /** The button (or slot) that opens/closes the content. */
37
+ children?: React.ReactNode;
38
+ className?: string;
39
+ }
40
+
41
+ const CollapsibleTrigger =
42
+ CollapsiblePrimitive.CollapsibleTrigger as React.ForwardRefExoticComponent<
43
+ CollapsibleTriggerProps & React.RefAttributes<HTMLButtonElement>
44
+ >;
45
+
46
+ export interface CollapsibleContentProps
47
+ extends React.ComponentPropsWithoutRef<typeof CollapsiblePrimitive.CollapsibleContent> {
48
+ /**
49
+ * Force the content to mount even when collapsed. Useful for measuring
50
+ * height for animations.
51
+ * @default false
52
+ */
53
+ forceMount?: true;
54
+ /**
55
+ * Render as a Radix Slot.
56
+ * @default false
57
+ */
58
+ asChild?: boolean;
59
+ /** Content shown when expanded. */
60
+ children?: React.ReactNode;
61
+ className?: string;
62
+ }
63
+
64
+ const CollapsibleContent =
65
+ CollapsiblePrimitive.CollapsibleContent as React.ForwardRefExoticComponent<
66
+ CollapsibleContentProps & React.RefAttributes<HTMLDivElement>
67
+ >;
68
+
69
+ export { Collapsible, CollapsibleContent, CollapsibleTrigger };