ui-arreya-components 0.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.
- package/.storybook/main.ts +18 -0
- package/.storybook/preview.ts +16 -0
- package/.storybook/vitest.setup.ts +9 -0
- package/README.md +54 -0
- package/components.json +21 -0
- package/dist/styles.css +3 -0
- package/dist/ui.cjs.js +31 -0
- package/dist/ui.es.js +3060 -0
- package/dist/ui.umd.js +31 -0
- package/eslint.config.js +28 -0
- package/package.json +120 -0
- package/postcss.config.js +6 -0
- package/scripts/template.sh +57 -0
- package/src/components/feature/login-form.stories.tsx +35 -0
- package/src/components/feature/login-form.tsx +97 -0
- package/src/components/index.ts +1 -0
- package/src/components/ui/accordion.stories.tsx.disabled +36 -0
- package/src/components/ui/accordion.tsx +55 -0
- package/src/components/ui/alert-dialog.stories.tsx +46 -0
- package/src/components/ui/alert-dialog.tsx +139 -0
- package/src/components/ui/alert.stories.tsx +45 -0
- package/src/components/ui/alert.tsx +59 -0
- package/src/components/ui/aspect-ratio.stories.tsx +24 -0
- package/src/components/ui/aspect-ratio.tsx +5 -0
- package/src/components/ui/avatar.stories.tsx +29 -0
- package/src/components/ui/avatar.tsx +48 -0
- package/src/components/ui/badge.stories.tsx +43 -0
- package/src/components/ui/badge.tsx +36 -0
- package/src/components/ui/breadcrumb.stories.tsx +146 -0
- package/src/components/ui/breadcrumb.tsx +115 -0
- package/src/components/ui/button.stories.tsx +87 -0
- package/src/components/ui/button.tsx +57 -0
- package/src/components/ui/card.stories.tsx +99 -0
- package/src/components/ui/card.tsx +76 -0
- package/src/components/ui/carousel.stories.tsx +47 -0
- package/src/components/ui/carousel.tsx +260 -0
- package/src/components/ui/chart.tsx +363 -0
- package/src/components/ui/checkbox.tsx +28 -0
- package/src/components/ui/collapsible.tsx +9 -0
- package/src/components/ui/context-menu.tsx +198 -0
- package/src/components/ui/dialog.tsx +120 -0
- package/src/components/ui/drawer.tsx +116 -0
- package/src/components/ui/dropdown-menu.stories.tsx +92 -0
- package/src/components/ui/dropdown-menu.tsx +199 -0
- package/src/components/ui/form.tsx +176 -0
- package/src/components/ui/hover-card.tsx +27 -0
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/input-otp.tsx +69 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/label.tsx +24 -0
- package/src/components/ui/menubar.tsx +254 -0
- package/src/components/ui/navigation-menu.tsx +128 -0
- package/src/components/ui/pagination.tsx +117 -0
- package/src/components/ui/popover.tsx +31 -0
- package/src/components/ui/progress.tsx +26 -0
- package/src/components/ui/radio-group.tsx +42 -0
- package/src/components/ui/resizable.tsx +43 -0
- package/src/components/ui/scroll-area.tsx +46 -0
- package/src/components/ui/select.tsx +157 -0
- package/src/components/ui/separator.tsx +29 -0
- package/src/components/ui/sheet.tsx +140 -0
- package/src/components/ui/sidebar.tsx +771 -0
- package/src/components/ui/skeleton.tsx +15 -0
- package/src/components/ui/slider.tsx +26 -0
- package/src/components/ui/sonner.tsx +29 -0
- package/src/components/ui/switch.tsx +27 -0
- package/src/components/ui/table.tsx +120 -0
- package/src/components/ui/tabs.tsx +53 -0
- package/src/components/ui/textarea.tsx +22 -0
- package/src/components/ui/toast.tsx +127 -0
- package/src/components/ui/toaster.tsx +33 -0
- package/src/components/ui/toggle-group.tsx +59 -0
- package/src/components/ui/toggle.tsx +43 -0
- package/src/components/ui/tooltip.tsx +30 -0
- package/src/hooks/use-mobile.tsx +19 -0
- package/src/hooks/use-toast.ts +194 -0
- package/src/index.css +3484 -0
- package/src/index.ts +1 -0
- package/src/lib/types.ts +5 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles/tailwind.css +86 -0
- package/tailwind.config.js +96 -0
- package/tsconfig.app.json +30 -0
- package/tsconfig.json +13 -0
- package/tsconfig.node.json +24 -0
- package/vite.config.ts +33 -0
- package/vitest.workspace.ts +32 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/react"
|
|
2
|
+
|
|
3
|
+
import { ChevronRight, Loader2, MailOpen } from 'lucide-react'
|
|
4
|
+
import {
|
|
5
|
+
Button,
|
|
6
|
+
} from "./button"
|
|
7
|
+
|
|
8
|
+
const meta = {
|
|
9
|
+
title: "Components/Button",
|
|
10
|
+
component: Button,
|
|
11
|
+
parameters: {
|
|
12
|
+
layout: "centered",
|
|
13
|
+
},
|
|
14
|
+
tags: ["autodocs"],
|
|
15
|
+
} satisfies Meta<typeof Button>
|
|
16
|
+
|
|
17
|
+
export default meta
|
|
18
|
+
type Story = StoryObj<typeof meta>
|
|
19
|
+
|
|
20
|
+
export const Default: Story = {
|
|
21
|
+
render: () => (
|
|
22
|
+
<Button>Button</Button>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const Secondary: Story = {
|
|
27
|
+
render: () => (
|
|
28
|
+
<Button variant="secondary">Secondary</Button>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const Destructive: Story = {
|
|
33
|
+
render: () => (
|
|
34
|
+
<Button variant="destructive">Secondary</Button>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const Outline: Story = {
|
|
39
|
+
render: () => (
|
|
40
|
+
<Button variant="outline">Secondary</Button>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const Ghost: Story = {
|
|
45
|
+
render: () => (
|
|
46
|
+
<Button variant="ghost">Secondary</Button>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const Link: Story = {
|
|
51
|
+
render: () => (
|
|
52
|
+
<Button variant="link">Secondary</Button>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const Icon: Story = {
|
|
57
|
+
render: () => (
|
|
58
|
+
<Button variant="outline" size="icon">
|
|
59
|
+
<ChevronRight />
|
|
60
|
+
</Button>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const WithIcon: Story = {
|
|
65
|
+
render: () => (
|
|
66
|
+
<Button>
|
|
67
|
+
<MailOpen /> Login with Email
|
|
68
|
+
</Button>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const Loading: Story = {
|
|
73
|
+
render: () => (
|
|
74
|
+
<Button disabled>
|
|
75
|
+
<Loader2 className="animate-spin" />
|
|
76
|
+
Please wait
|
|
77
|
+
</Button>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const ButtonAsChild: Story = {
|
|
82
|
+
render: () => (
|
|
83
|
+
<Button asChild>
|
|
84
|
+
<p>This is a button</p>
|
|
85
|
+
</Button>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Slot } from "@radix-ui/react-slot"
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
const buttonVariants = cva(
|
|
8
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium 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",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default:
|
|
13
|
+
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
|
14
|
+
destructive:
|
|
15
|
+
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
|
16
|
+
outline:
|
|
17
|
+
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
|
18
|
+
secondary:
|
|
19
|
+
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
|
20
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
21
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
22
|
+
},
|
|
23
|
+
size: {
|
|
24
|
+
default: "h-9 px-4 py-2",
|
|
25
|
+
sm: "h-8 rounded-md px-3 text-xs",
|
|
26
|
+
lg: "h-10 rounded-md px-8",
|
|
27
|
+
icon: "h-9 w-9",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
defaultVariants: {
|
|
31
|
+
variant: "default",
|
|
32
|
+
size: "default",
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
export interface ButtonProps
|
|
38
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
39
|
+
VariantProps<typeof buttonVariants> {
|
|
40
|
+
asChild?: boolean
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
44
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
45
|
+
const Comp = asChild ? Slot : "button"
|
|
46
|
+
return (
|
|
47
|
+
<Comp
|
|
48
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
49
|
+
ref={ref}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
Button.displayName = "Button"
|
|
56
|
+
|
|
57
|
+
export { Button, buttonVariants }
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/react"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Card,
|
|
5
|
+
CardHeader,
|
|
6
|
+
CardFooter,
|
|
7
|
+
CardTitle,
|
|
8
|
+
CardDescription,
|
|
9
|
+
CardContent
|
|
10
|
+
} from "./card"
|
|
11
|
+
import { Button } from './button'
|
|
12
|
+
import { Switch } from './switch'
|
|
13
|
+
|
|
14
|
+
import { BellRing, Check } from 'lucide-react'
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
const meta = {
|
|
18
|
+
title: "Components/Card",
|
|
19
|
+
component: Card,
|
|
20
|
+
parameters: {
|
|
21
|
+
layout: "centered",
|
|
22
|
+
},
|
|
23
|
+
tags: ["autodocs"],
|
|
24
|
+
} satisfies Meta<typeof Card>
|
|
25
|
+
|
|
26
|
+
export default meta
|
|
27
|
+
type Story = StoryObj<typeof meta>
|
|
28
|
+
|
|
29
|
+
export const Default: Story = {
|
|
30
|
+
render: () => (
|
|
31
|
+
<Card>
|
|
32
|
+
<CardHeader>
|
|
33
|
+
<CardTitle>Card Title</CardTitle>
|
|
34
|
+
<CardDescription>Card Description</CardDescription>
|
|
35
|
+
</CardHeader>
|
|
36
|
+
<CardContent>
|
|
37
|
+
<p>Card Content</p>
|
|
38
|
+
</CardContent>
|
|
39
|
+
<CardFooter>
|
|
40
|
+
<p>Card Footer</p>
|
|
41
|
+
</CardFooter>
|
|
42
|
+
</Card>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type notificationProps = {
|
|
47
|
+
title: string,
|
|
48
|
+
description: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const NotificationsCard = {
|
|
52
|
+
args: {
|
|
53
|
+
notifications: [
|
|
54
|
+
{
|
|
55
|
+
title: "Your call has been confirmed.",
|
|
56
|
+
description: "1 hour ago",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
title: "You have a new message!",
|
|
60
|
+
description: "1 hour ago",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
title: "Your subscription is expiring soon!",
|
|
64
|
+
description: "2 hours ago",
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
className: '',
|
|
68
|
+
props: ''
|
|
69
|
+
},
|
|
70
|
+
render: () => (
|
|
71
|
+
<Card className="w-[380px]">
|
|
72
|
+
<CardHeader>
|
|
73
|
+
<CardTitle>Notifications</CardTitle>
|
|
74
|
+
<CardDescription>You have 3 unread messages.</CardDescription>
|
|
75
|
+
</CardHeader>
|
|
76
|
+
<CardContent className="grid gap-4">
|
|
77
|
+
<div className=" flex items-center space-x-4 rounded-md border p-4">
|
|
78
|
+
<BellRing />
|
|
79
|
+
<div className="flex-1 space-y-1">
|
|
80
|
+
<p className="text-sm font-medium leading-none">
|
|
81
|
+
Push Notifications
|
|
82
|
+
</p>
|
|
83
|
+
<p className="text-sm text-muted-foreground">
|
|
84
|
+
Send notifications to device.
|
|
85
|
+
</p>
|
|
86
|
+
</div>
|
|
87
|
+
<Switch />
|
|
88
|
+
</div>
|
|
89
|
+
<div>
|
|
90
|
+
</div>
|
|
91
|
+
</CardContent>
|
|
92
|
+
<CardFooter>
|
|
93
|
+
<Button className="w-full">
|
|
94
|
+
<Check /> Mark all as read
|
|
95
|
+
</Button>
|
|
96
|
+
</CardFooter>
|
|
97
|
+
</Card>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
@@ -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,47 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/react"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Carousel,
|
|
5
|
+
CarouselContent,
|
|
6
|
+
CarouselItem,
|
|
7
|
+
CarouselNext,
|
|
8
|
+
CarouselPrevious
|
|
9
|
+
} from "./carousel"
|
|
10
|
+
import {
|
|
11
|
+
Card,
|
|
12
|
+
CardContent
|
|
13
|
+
} from './card'
|
|
14
|
+
|
|
15
|
+
const meta = {
|
|
16
|
+
title: "Components/Carousel",
|
|
17
|
+
component: Carousel,
|
|
18
|
+
parameters: {
|
|
19
|
+
layout: "centered",
|
|
20
|
+
},
|
|
21
|
+
tags: ["autodocs"],
|
|
22
|
+
} satisfies Meta<typeof Carousel>
|
|
23
|
+
|
|
24
|
+
export default meta
|
|
25
|
+
type Story = StoryObj<typeof meta>
|
|
26
|
+
|
|
27
|
+
export const Default: Story = {
|
|
28
|
+
render: () => (
|
|
29
|
+
<Carousel className="w-full max-w-xs">
|
|
30
|
+
<CarouselContent>
|
|
31
|
+
{[0,1,2,3,4].map((_, index) => (
|
|
32
|
+
<CarouselItem key={index}>
|
|
33
|
+
<div className="p-1">
|
|
34
|
+
<Card>
|
|
35
|
+
<CardContent className="flex aspect-square items-center justify-center p-6">
|
|
36
|
+
<span className="text-4xl font-semibold">{index + 1}</span>
|
|
37
|
+
</CardContent>
|
|
38
|
+
</Card>
|
|
39
|
+
</div>
|
|
40
|
+
</CarouselItem>
|
|
41
|
+
))}
|
|
42
|
+
</CarouselContent>
|
|
43
|
+
<CarouselPrevious />
|
|
44
|
+
<CarouselNext />
|
|
45
|
+
</Carousel>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import useEmblaCarousel, {
|
|
3
|
+
type UseEmblaCarouselType,
|
|
4
|
+
} from "embla-carousel-react"
|
|
5
|
+
import { ArrowLeft, ArrowRight } from "lucide-react"
|
|
6
|
+
|
|
7
|
+
import { cn } from "@/lib/utils"
|
|
8
|
+
import { Button } from "@/components/ui/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
|
+
>(
|
|
47
|
+
(
|
|
48
|
+
{
|
|
49
|
+
orientation = "horizontal",
|
|
50
|
+
opts,
|
|
51
|
+
setApi,
|
|
52
|
+
plugins,
|
|
53
|
+
className,
|
|
54
|
+
children,
|
|
55
|
+
...props
|
|
56
|
+
},
|
|
57
|
+
ref
|
|
58
|
+
) => {
|
|
59
|
+
const [carouselRef, api] = useEmblaCarousel(
|
|
60
|
+
{
|
|
61
|
+
...opts,
|
|
62
|
+
axis: orientation === "horizontal" ? "x" : "y",
|
|
63
|
+
},
|
|
64
|
+
plugins
|
|
65
|
+
)
|
|
66
|
+
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
|
|
67
|
+
const [canScrollNext, setCanScrollNext] = React.useState(false)
|
|
68
|
+
|
|
69
|
+
const onSelect = React.useCallback((api: CarouselApi) => {
|
|
70
|
+
if (!api) {
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
setCanScrollPrev(api.canScrollPrev())
|
|
75
|
+
setCanScrollNext(api.canScrollNext())
|
|
76
|
+
}, [])
|
|
77
|
+
|
|
78
|
+
const scrollPrev = React.useCallback(() => {
|
|
79
|
+
api?.scrollPrev()
|
|
80
|
+
}, [api])
|
|
81
|
+
|
|
82
|
+
const scrollNext = React.useCallback(() => {
|
|
83
|
+
api?.scrollNext()
|
|
84
|
+
}, [api])
|
|
85
|
+
|
|
86
|
+
const handleKeyDown = React.useCallback(
|
|
87
|
+
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
88
|
+
if (event.key === "ArrowLeft") {
|
|
89
|
+
event.preventDefault()
|
|
90
|
+
scrollPrev()
|
|
91
|
+
} else if (event.key === "ArrowRight") {
|
|
92
|
+
event.preventDefault()
|
|
93
|
+
scrollNext()
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
[scrollPrev, scrollNext]
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
React.useEffect(() => {
|
|
100
|
+
if (!api || !setApi) {
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
setApi(api)
|
|
105
|
+
}, [api, setApi])
|
|
106
|
+
|
|
107
|
+
React.useEffect(() => {
|
|
108
|
+
if (!api) {
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
onSelect(api)
|
|
113
|
+
api.on("reInit", onSelect)
|
|
114
|
+
api.on("select", onSelect)
|
|
115
|
+
|
|
116
|
+
return () => {
|
|
117
|
+
api?.off("select", onSelect)
|
|
118
|
+
}
|
|
119
|
+
}, [api, onSelect])
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<CarouselContext.Provider
|
|
123
|
+
value={{
|
|
124
|
+
carouselRef,
|
|
125
|
+
api: api,
|
|
126
|
+
opts,
|
|
127
|
+
orientation:
|
|
128
|
+
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
|
|
129
|
+
scrollPrev,
|
|
130
|
+
scrollNext,
|
|
131
|
+
canScrollPrev,
|
|
132
|
+
canScrollNext,
|
|
133
|
+
}}
|
|
134
|
+
>
|
|
135
|
+
<div
|
|
136
|
+
ref={ref}
|
|
137
|
+
onKeyDownCapture={handleKeyDown}
|
|
138
|
+
className={cn("relative", className)}
|
|
139
|
+
role="region"
|
|
140
|
+
aria-roledescription="carousel"
|
|
141
|
+
{...props}
|
|
142
|
+
>
|
|
143
|
+
{children}
|
|
144
|
+
</div>
|
|
145
|
+
</CarouselContext.Provider>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
Carousel.displayName = "Carousel"
|
|
150
|
+
|
|
151
|
+
const CarouselContent = React.forwardRef<
|
|
152
|
+
HTMLDivElement,
|
|
153
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
154
|
+
>(({ className, ...props }, ref) => {
|
|
155
|
+
const { carouselRef, orientation } = useCarousel()
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<div ref={carouselRef} className="overflow-hidden">
|
|
159
|
+
<div
|
|
160
|
+
ref={ref}
|
|
161
|
+
className={cn(
|
|
162
|
+
"flex",
|
|
163
|
+
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
|
164
|
+
className
|
|
165
|
+
)}
|
|
166
|
+
{...props}
|
|
167
|
+
/>
|
|
168
|
+
</div>
|
|
169
|
+
)
|
|
170
|
+
})
|
|
171
|
+
CarouselContent.displayName = "CarouselContent"
|
|
172
|
+
|
|
173
|
+
const CarouselItem = React.forwardRef<
|
|
174
|
+
HTMLDivElement,
|
|
175
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
176
|
+
>(({ className, ...props }, ref) => {
|
|
177
|
+
const { orientation } = useCarousel()
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<div
|
|
181
|
+
ref={ref}
|
|
182
|
+
role="group"
|
|
183
|
+
aria-roledescription="slide"
|
|
184
|
+
className={cn(
|
|
185
|
+
"min-w-0 shrink-0 grow-0 basis-full",
|
|
186
|
+
orientation === "horizontal" ? "pl-4" : "pt-4",
|
|
187
|
+
className
|
|
188
|
+
)}
|
|
189
|
+
{...props}
|
|
190
|
+
/>
|
|
191
|
+
)
|
|
192
|
+
})
|
|
193
|
+
CarouselItem.displayName = "CarouselItem"
|
|
194
|
+
|
|
195
|
+
const CarouselPrevious = React.forwardRef<
|
|
196
|
+
HTMLButtonElement,
|
|
197
|
+
React.ComponentProps<typeof Button>
|
|
198
|
+
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
|
199
|
+
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<Button
|
|
203
|
+
ref={ref}
|
|
204
|
+
variant={variant}
|
|
205
|
+
size={size}
|
|
206
|
+
className={cn(
|
|
207
|
+
"absolute h-8 w-8 rounded-full",
|
|
208
|
+
orientation === "horizontal"
|
|
209
|
+
? "-left-12 top-1/2 -translate-y-1/2"
|
|
210
|
+
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
|
211
|
+
className
|
|
212
|
+
)}
|
|
213
|
+
disabled={!canScrollPrev}
|
|
214
|
+
onClick={scrollPrev}
|
|
215
|
+
{...props}
|
|
216
|
+
>
|
|
217
|
+
<ArrowLeft className="h-4 w-4" />
|
|
218
|
+
<span className="sr-only">Previous slide</span>
|
|
219
|
+
</Button>
|
|
220
|
+
)
|
|
221
|
+
})
|
|
222
|
+
CarouselPrevious.displayName = "CarouselPrevious"
|
|
223
|
+
|
|
224
|
+
const CarouselNext = React.forwardRef<
|
|
225
|
+
HTMLButtonElement,
|
|
226
|
+
React.ComponentProps<typeof Button>
|
|
227
|
+
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
|
228
|
+
const { orientation, scrollNext, canScrollNext } = useCarousel()
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<Button
|
|
232
|
+
ref={ref}
|
|
233
|
+
variant={variant}
|
|
234
|
+
size={size}
|
|
235
|
+
className={cn(
|
|
236
|
+
"absolute h-8 w-8 rounded-full",
|
|
237
|
+
orientation === "horizontal"
|
|
238
|
+
? "-right-12 top-1/2 -translate-y-1/2"
|
|
239
|
+
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
|
240
|
+
className
|
|
241
|
+
)}
|
|
242
|
+
disabled={!canScrollNext}
|
|
243
|
+
onClick={scrollNext}
|
|
244
|
+
{...props}
|
|
245
|
+
>
|
|
246
|
+
<ArrowRight className="h-4 w-4" />
|
|
247
|
+
<span className="sr-only">Next slide</span>
|
|
248
|
+
</Button>
|
|
249
|
+
)
|
|
250
|
+
})
|
|
251
|
+
CarouselNext.displayName = "CarouselNext"
|
|
252
|
+
|
|
253
|
+
export {
|
|
254
|
+
type CarouselApi,
|
|
255
|
+
Carousel,
|
|
256
|
+
CarouselContent,
|
|
257
|
+
CarouselItem,
|
|
258
|
+
CarouselPrevious,
|
|
259
|
+
CarouselNext,
|
|
260
|
+
}
|