@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.
- package/package.json +179 -0
- package/src/components/atoms/README.md +11 -0
- package/src/components/atoms/aspect-ratio.tsx +32 -0
- package/src/components/atoms/avatar.tsx +98 -0
- package/src/components/atoms/badge.tsx +44 -0
- package/src/components/atoms/brand-mark.tsx +74 -0
- package/src/components/atoms/button.tsx +104 -0
- package/src/components/atoms/checkbox.tsx +63 -0
- package/src/components/atoms/flex-box.tsx +105 -0
- package/src/components/atoms/icon.tsx +34 -0
- package/src/components/atoms/input.tsx +91 -0
- package/src/components/atoms/label.tsx +41 -0
- package/src/components/atoms/logo.tsx +89 -0
- package/src/components/atoms/progress.tsx +55 -0
- package/src/components/atoms/radio-group.tsx +122 -0
- package/src/components/atoms/scroll-area.tsx +106 -0
- package/src/components/atoms/section.tsx +48 -0
- package/src/components/atoms/separator.tsx +45 -0
- package/src/components/atoms/skeleton.tsx +17 -0
- package/src/components/atoms/slider.tsx +93 -0
- package/src/components/atoms/switch.tsx +60 -0
- package/src/components/atoms/textarea.tsx +78 -0
- package/src/components/atoms/toggle.tsx +80 -0
- package/src/components/charts/activity-heatmap.tsx +96 -0
- package/src/components/charts/axes.tsx +21 -0
- package/src/components/charts/chart-container.tsx +195 -0
- package/src/components/charts/chart-legend.tsx +67 -0
- package/src/components/charts/chart-tooltip.tsx +161 -0
- package/src/components/charts/chart-types.tsx +49 -0
- package/src/components/charts/containers.tsx +11 -0
- package/src/components/charts/data.tsx +16 -0
- package/src/components/charts/details.tsx +25 -0
- package/src/components/charts/gauge.tsx +106 -0
- package/src/components/charts/grids.tsx +8 -0
- package/src/components/charts/index.ts +62 -0
- package/src/components/charts/labeled-bar-list.tsx +85 -0
- package/src/components/charts/references.tsx +8 -0
- package/src/components/charts/service-health-list.tsx +73 -0
- package/src/components/charts/sparkline.tsx +52 -0
- package/src/components/charts/stacked-bar.tsx +104 -0
- package/src/components/charts/text.tsx +10 -0
- package/src/components/charts/world-heat-map-inner.tsx +317 -0
- package/src/components/charts/world-heat-map.tsx +184 -0
- package/src/components/molecules/README.md +12 -0
- package/src/components/molecules/action-bar.tsx +73 -0
- package/src/components/molecules/activity-item.tsx +74 -0
- package/src/components/molecules/alert.tsx +80 -0
- package/src/components/molecules/animated-background.tsx +92 -0
- package/src/components/molecules/brand-lockup.tsx +48 -0
- package/src/components/molecules/breadcrumb.tsx +161 -0
- package/src/components/molecules/button-group.tsx +104 -0
- package/src/components/molecules/calendar.tsx +216 -0
- package/src/components/molecules/card.tsx +101 -0
- package/src/components/molecules/code-block.tsx +48 -0
- package/src/components/molecules/empty-state.tsx +55 -0
- package/src/components/molecules/error-state.tsx +42 -0
- package/src/components/molecules/field-display.tsx +35 -0
- package/src/components/molecules/input-otp.tsx +74 -0
- package/src/components/molecules/loading-state.tsx +36 -0
- package/src/components/molecules/notification-item.tsx +67 -0
- package/src/components/molecules/notification-list.tsx +45 -0
- package/src/components/molecules/number-badge.tsx +53 -0
- package/src/components/molecules/page-header.tsx +88 -0
- package/src/components/molecules/page-tabs.tsx +94 -0
- package/src/components/molecules/pagination.tsx +150 -0
- package/src/components/molecules/phone-input.tsx +200 -0
- package/src/components/molecules/search-bar.tsx +64 -0
- package/src/components/molecules/secret-field.tsx +158 -0
- package/src/components/molecules/section-card.tsx +91 -0
- package/src/components/molecules/stat-card.tsx +96 -0
- package/src/components/molecules/status-badge.tsx +42 -0
- package/src/components/molecules/stepper.tsx +96 -0
- package/src/components/molecules/table.tsx +157 -0
- package/src/components/molecules/toggle-group.tsx +145 -0
- package/src/components/molecules/tooltip.tsx +150 -0
- package/src/components/molecules/user-avatar-chip.tsx +71 -0
- package/src/components/organisms/README.md +14 -0
- package/src/components/organisms/accordion.tsx +149 -0
- package/src/components/organisms/alert-dialog.tsx +269 -0
- package/src/components/organisms/carousel.tsx +244 -0
- package/src/components/organisms/collapsible.tsx +69 -0
- package/src/components/organisms/command.tsx +143 -0
- package/src/components/organisms/context-menu.tsx +333 -0
- package/src/components/organisms/dashboard-grid.tsx +360 -0
- package/src/components/organisms/data-table.tsx +330 -0
- package/src/components/organisms/dialog.tsx +304 -0
- package/src/components/organisms/drawer.tsx +100 -0
- package/src/components/organisms/dropdown-menu.tsx +434 -0
- package/src/components/organisms/editors/code-editor.tsx +144 -0
- package/src/components/organisms/editors/index.ts +4 -0
- package/src/components/organisms/editors/markdown-editor.tsx +153 -0
- package/src/components/organisms/editors/markdown-renderer.ts +27 -0
- package/src/components/organisms/editors/prose-canvas-classes.ts +45 -0
- package/src/components/organisms/editors/rich-text-editor.tsx +126 -0
- package/src/components/organisms/editors/toolbar/md-toolbar.tsx +129 -0
- package/src/components/organisms/editors/toolbar/rte-toolbar.tsx +211 -0
- package/src/components/organisms/editors/toolbar/toolbar-shell.tsx +45 -0
- package/src/components/organisms/editors/use-codemirror-theme.ts +61 -0
- package/src/components/organisms/error-boundary.tsx +61 -0
- package/src/components/organisms/form.tsx +174 -0
- package/src/components/organisms/hover-card.tsx +114 -0
- package/src/components/organisms/menubar.tsx +491 -0
- package/src/components/organisms/navbar.tsx +101 -0
- package/src/components/organisms/navigation-menu.tsx +234 -0
- package/src/components/organisms/popover.tsx +144 -0
- package/src/components/organisms/resizable.tsx +39 -0
- package/src/components/organisms/schema-form.tsx +232 -0
- package/src/components/organisms/select.tsx +303 -0
- package/src/components/organisms/sheet.tsx +256 -0
- package/src/components/organisms/sidebar.tsx +1037 -0
- package/src/components/organisms/sonner.tsx +96 -0
- package/src/components/organisms/tabs.tsx +132 -0
- package/src/components/organisms/theme-provider.tsx +101 -0
- package/src/hooks/use-mobile.tsx +19 -0
- package/src/index.ts +547 -0
- package/src/lib/portal-container.tsx +35 -0
- package/src/lib/utils.ts +6 -0
- package/src/native.ts +23 -0
- package/src/tokens/colors.ts +91 -0
- package/src/tokens/index.ts +3 -0
- package/src/tokens/spacing.ts +55 -0
- package/src/tokens/typography.ts +27 -0
- package/styles/canvas.css +55 -0
- package/styles/dashboard-grid.css +47 -0
- package/styles/leaflet.css +13 -0
- package/styles/tokens.css +234 -0
- package/tailwind.config.ts +70 -0
- 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 };
|