@mzc-fe/design-system 0.0.1-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/.husky/pre-push +21 -0
  2. package/.storybook/main.ts +11 -0
  3. package/.storybook/preview.tsx +30 -0
  4. package/.vscode/settings.json +12 -0
  5. package/.vscode/tailwind.json +105 -0
  6. package/README.md +136 -0
  7. package/bitbucket-pipelines.yml +52 -0
  8. package/components.json +21 -0
  9. package/eslint.config.js +38 -0
  10. package/package.json +98 -0
  11. package/public/vite.svg +1 -0
  12. package/src/components/accordion.stories.tsx +258 -0
  13. package/src/components/accordion.test.tsx +390 -0
  14. package/src/components/accordion.tsx +64 -0
  15. package/src/components/alert-dialog.stories.tsx +213 -0
  16. package/src/components/alert-dialog.test.tsx +80 -0
  17. package/src/components/alert-dialog.tsx +155 -0
  18. package/src/components/alert.stories.tsx +84 -0
  19. package/src/components/alert.test.tsx +35 -0
  20. package/src/components/alert.tsx +66 -0
  21. package/src/components/aspect-ratio.stories.tsx +97 -0
  22. package/src/components/aspect-ratio.test.tsx +47 -0
  23. package/src/components/aspect-ratio.tsx +11 -0
  24. package/src/components/avatar.stories.tsx +76 -0
  25. package/src/components/avatar.test.tsx +50 -0
  26. package/src/components/avatar.tsx +51 -0
  27. package/src/components/badge.stories.tsx +64 -0
  28. package/src/components/badge.test.tsx +34 -0
  29. package/src/components/badge.tsx +46 -0
  30. package/src/components/breadcrumb.stories.tsx +86 -0
  31. package/src/components/breadcrumb.test.tsx +74 -0
  32. package/src/components/breadcrumb.tsx +109 -0
  33. package/src/components/button-group.stories.tsx +62 -0
  34. package/src/components/button-group.tsx +83 -0
  35. package/src/components/button.stories.tsx +118 -0
  36. package/src/components/button.test.tsx +64 -0
  37. package/src/components/button.tsx +62 -0
  38. package/src/components/calendar.stories.tsx +81 -0
  39. package/src/components/calendar.tsx +220 -0
  40. package/src/components/card.stories.tsx +110 -0
  41. package/src/components/card.test.tsx +56 -0
  42. package/src/components/card.tsx +92 -0
  43. package/src/components/carousel.stories.tsx +90 -0
  44. package/src/components/carousel.tsx +239 -0
  45. package/src/components/chart.tsx +357 -0
  46. package/src/components/checkbox.stories.tsx +108 -0
  47. package/src/components/checkbox.test.tsx +67 -0
  48. package/src/components/checkbox.tsx +32 -0
  49. package/src/components/collapsible.stories.tsx +106 -0
  50. package/src/components/collapsible.test.tsx +92 -0
  51. package/src/components/collapsible.tsx +31 -0
  52. package/src/components/command.stories.tsx +90 -0
  53. package/src/components/command.tsx +182 -0
  54. package/src/components/context-menu.stories.tsx +63 -0
  55. package/src/components/context-menu.tsx +252 -0
  56. package/src/components/dialog.stories.tsx +128 -0
  57. package/src/components/dialog.tsx +141 -0
  58. package/src/components/drawer.stories.tsx +104 -0
  59. package/src/components/drawer.tsx +135 -0
  60. package/src/components/dropdown-menu.stories.tsx +97 -0
  61. package/src/components/dropdown-menu.tsx +255 -0
  62. package/src/components/empty.stories.tsx +90 -0
  63. package/src/components/empty.test.tsx +55 -0
  64. package/src/components/empty.tsx +104 -0
  65. package/src/components/field.tsx +246 -0
  66. package/src/components/form.tsx +168 -0
  67. package/src/components/hover-card.stories.tsx +66 -0
  68. package/src/components/hover-card.tsx +44 -0
  69. package/src/components/input-group.stories.tsx +57 -0
  70. package/src/components/input-group.test.tsx +40 -0
  71. package/src/components/input-group.tsx +170 -0
  72. package/src/components/input-otp.stories.tsx +94 -0
  73. package/src/components/input-otp.test.tsx +60 -0
  74. package/src/components/input-otp.tsx +75 -0
  75. package/src/components/input.stories.tsx +94 -0
  76. package/src/components/input.test.tsx +53 -0
  77. package/src/components/input.tsx +21 -0
  78. package/src/components/item.tsx +193 -0
  79. package/src/components/kbd.stories.tsx +100 -0
  80. package/src/components/kbd.test.tsx +28 -0
  81. package/src/components/kbd.tsx +28 -0
  82. package/src/components/label.stories.tsx +48 -0
  83. package/src/components/label.test.tsx +28 -0
  84. package/src/components/label.tsx +24 -0
  85. package/src/components/menubar.tsx +274 -0
  86. package/src/components/navigation-menu.tsx +168 -0
  87. package/src/components/pagination.stories.tsx +107 -0
  88. package/src/components/pagination.tsx +127 -0
  89. package/src/components/popover.stories.tsx +102 -0
  90. package/src/components/popover.tsx +48 -0
  91. package/src/components/progress.stories.tsx +76 -0
  92. package/src/components/progress.test.tsx +36 -0
  93. package/src/components/progress.tsx +29 -0
  94. package/src/components/radio-group.stories.tsx +73 -0
  95. package/src/components/radio-group.test.tsx +74 -0
  96. package/src/components/radio-group.tsx +45 -0
  97. package/src/components/resizable.stories.tsx +120 -0
  98. package/src/components/resizable.tsx +54 -0
  99. package/src/components/scroll-area.stories.tsx +64 -0
  100. package/src/components/scroll-area.test.tsx +46 -0
  101. package/src/components/scroll-area.tsx +58 -0
  102. package/src/components/select.stories.tsx +111 -0
  103. package/src/components/select.test.tsx +90 -0
  104. package/src/components/select.tsx +188 -0
  105. package/src/components/separator.stories.tsx +76 -0
  106. package/src/components/separator.test.tsx +24 -0
  107. package/src/components/separator.tsx +28 -0
  108. package/src/components/sheet.stories.tsx +122 -0
  109. package/src/components/sheet.tsx +137 -0
  110. package/src/components/sidebar.tsx +726 -0
  111. package/src/components/skeleton.stories.tsx +53 -0
  112. package/src/components/skeleton.test.tsx +24 -0
  113. package/src/components/skeleton.tsx +13 -0
  114. package/src/components/slider.stories.tsx +97 -0
  115. package/src/components/slider.test.tsx +49 -0
  116. package/src/components/slider.tsx +63 -0
  117. package/src/components/sonner.stories.tsx +96 -0
  118. package/src/components/sonner.tsx +38 -0
  119. package/src/components/spinner.stories.tsx +54 -0
  120. package/src/components/spinner.test.tsx +30 -0
  121. package/src/components/spinner.tsx +16 -0
  122. package/src/components/switch.stories.tsx +108 -0
  123. package/src/components/switch.test.tsx +62 -0
  124. package/src/components/switch.tsx +31 -0
  125. package/src/components/table.stories.tsx +139 -0
  126. package/src/components/table.test.tsx +85 -0
  127. package/src/components/table.tsx +114 -0
  128. package/src/components/tabs.stories.tsx +99 -0
  129. package/src/components/tabs.test.tsx +64 -0
  130. package/src/components/tabs.tsx +66 -0
  131. package/src/components/textarea.stories.tsx +89 -0
  132. package/src/components/textarea.test.tsx +53 -0
  133. package/src/components/textarea.tsx +18 -0
  134. package/src/components/toggle-group.stories.tsx +108 -0
  135. package/src/components/toggle-group.test.tsx +66 -0
  136. package/src/components/toggle-group.tsx +81 -0
  137. package/src/components/toggle.stories.tsx +98 -0
  138. package/src/components/toggle.test.tsx +42 -0
  139. package/src/components/toggle.tsx +45 -0
  140. package/src/components/tooltip.stories.tsx +111 -0
  141. package/src/components/tooltip.tsx +61 -0
  142. package/src/foundations/README.md +141 -0
  143. package/src/foundations/ThemeProvider.tsx +77 -0
  144. package/src/foundations/color.css +232 -0
  145. package/src/foundations/color.stories.tsx +719 -0
  146. package/src/foundations/palette.css +249 -0
  147. package/src/foundations/spacing.css +8 -0
  148. package/src/foundations/typography.css +143 -0
  149. package/src/foundations/typography.stories.tsx +17 -0
  150. package/src/hooks/use-mobile.ts +19 -0
  151. package/src/index.css +176 -0
  152. package/src/index.ts +336 -0
  153. package/src/lib/utils.ts +6 -0
  154. package/src/test/setup.ts +8 -0
  155. package/src/vite-env.d.ts +1 -0
  156. package/tsconfig.app.json +33 -0
  157. package/tsconfig.json +13 -0
  158. package/tsconfig.node.json +25 -0
  159. package/vite.config.ts +30 -0
  160. package/vitest.config.ts +25 -0
@@ -0,0 +1,220 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import {
5
+ ChevronDownIcon,
6
+ ChevronLeftIcon,
7
+ ChevronRightIcon,
8
+ } from "lucide-react";
9
+ import {
10
+ DayPicker,
11
+ getDefaultClassNames,
12
+ type DayButton,
13
+ } from "react-day-picker";
14
+
15
+ import { cn } from "@/lib/utils";
16
+ import { Button, buttonVariants } from "@/components/button";
17
+
18
+ function Calendar({
19
+ className,
20
+ classNames,
21
+ showOutsideDays = true,
22
+ captionLayout = "label",
23
+ buttonVariant = "ghost",
24
+ formatters,
25
+ components,
26
+ ...props
27
+ }: React.ComponentProps<typeof DayPicker> & {
28
+ buttonVariant?: React.ComponentProps<typeof Button>["variant"];
29
+ }) {
30
+ const defaultClassNames = getDefaultClassNames();
31
+
32
+ return (
33
+ <DayPicker
34
+ showOutsideDays={showOutsideDays}
35
+ className={cn(
36
+ "bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
37
+ String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
38
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
39
+ className
40
+ )}
41
+ captionLayout={captionLayout}
42
+ formatters={{
43
+ formatMonthDropdown: (date) =>
44
+ date.toLocaleString("default", { month: "short" }),
45
+ ...formatters,
46
+ }}
47
+ classNames={{
48
+ root: cn("w-fit", defaultClassNames.root),
49
+ months: cn(
50
+ "flex gap-4 flex-col md:flex-row relative",
51
+ defaultClassNames.months
52
+ ),
53
+ month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
54
+ nav: cn(
55
+ "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
56
+ defaultClassNames.nav
57
+ ),
58
+ button_previous: cn(
59
+ buttonVariants({ variant: buttonVariant }),
60
+ "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
61
+ defaultClassNames.button_previous
62
+ ),
63
+ button_next: cn(
64
+ buttonVariants({ variant: buttonVariant }),
65
+ "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
66
+ defaultClassNames.button_next
67
+ ),
68
+ month_caption: cn(
69
+ "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
70
+ defaultClassNames.month_caption
71
+ ),
72
+ dropdowns: cn(
73
+ "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
74
+ defaultClassNames.dropdowns
75
+ ),
76
+ dropdown_root: cn(
77
+ "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md",
78
+ defaultClassNames.dropdown_root
79
+ ),
80
+ dropdown: cn(
81
+ "absolute bg-popover inset-0 opacity-0",
82
+ defaultClassNames.dropdown
83
+ ),
84
+ caption_label: cn(
85
+ "select-none font-medium",
86
+ captionLayout === "label"
87
+ ? "text-sm"
88
+ : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
89
+ defaultClassNames.caption_label
90
+ ),
91
+ table: "w-full border-collapse",
92
+ weekdays: cn("flex", defaultClassNames.weekdays),
93
+ weekday: cn(
94
+ "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none",
95
+ defaultClassNames.weekday
96
+ ),
97
+ week: cn("flex w-full mt-2", defaultClassNames.week),
98
+ week_number_header: cn(
99
+ "select-none w-(--cell-size)",
100
+ defaultClassNames.week_number_header
101
+ ),
102
+ week_number: cn(
103
+ "text-[0.8rem] select-none text-muted-foreground",
104
+ defaultClassNames.week_number
105
+ ),
106
+ day: cn(
107
+ "relative w-full h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none",
108
+ props.showWeekNumber
109
+ ? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md"
110
+ : "[&:first-child[data-selected=true]_button]:rounded-l-md",
111
+ defaultClassNames.day
112
+ ),
113
+ range_start: cn(
114
+ "rounded-l-md bg-accent",
115
+ defaultClassNames.range_start
116
+ ),
117
+ range_middle: cn("rounded-none", defaultClassNames.range_middle),
118
+ range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
119
+ today: cn(
120
+ "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
121
+ defaultClassNames.today
122
+ ),
123
+ outside: cn(
124
+ "text-muted-foreground aria-selected:text-muted-foreground",
125
+ defaultClassNames.outside
126
+ ),
127
+ disabled: cn(
128
+ "text-muted-foreground opacity-50",
129
+ defaultClassNames.disabled
130
+ ),
131
+ hidden: cn("invisible", defaultClassNames.hidden),
132
+ ...classNames,
133
+ }}
134
+ components={{
135
+ Root: ({ className, rootRef, ...props }) => {
136
+ return (
137
+ <div
138
+ data-slot="calendar"
139
+ ref={rootRef}
140
+ className={cn(className)}
141
+ {...props}
142
+ />
143
+ );
144
+ },
145
+ Chevron: ({ className, orientation, ...props }) => {
146
+ if (orientation === "left") {
147
+ return (
148
+ <ChevronLeftIcon className={cn("size-4", className)} {...props} />
149
+ );
150
+ }
151
+
152
+ if (orientation === "right") {
153
+ return (
154
+ <ChevronRightIcon
155
+ className={cn("size-4", className)}
156
+ {...props}
157
+ />
158
+ );
159
+ }
160
+
161
+ return (
162
+ <ChevronDownIcon className={cn("size-4", className)} {...props} />
163
+ );
164
+ },
165
+ DayButton: CalendarDayButton,
166
+ WeekNumber: ({ children, ...props }) => {
167
+ return (
168
+ <td {...props}>
169
+ <div className="flex size-(--cell-size) items-center justify-center text-center">
170
+ {children}
171
+ </div>
172
+ </td>
173
+ );
174
+ },
175
+ ...components,
176
+ }}
177
+ {...props}
178
+ />
179
+ );
180
+ }
181
+
182
+ function CalendarDayButton({
183
+ className,
184
+ day,
185
+ modifiers,
186
+ ...props
187
+ }: React.ComponentProps<typeof DayButton>) {
188
+ const defaultClassNames = getDefaultClassNames();
189
+
190
+ const ref = React.useRef<HTMLButtonElement>(null);
191
+ React.useEffect(() => {
192
+ if (modifiers.focused) ref.current?.focus();
193
+ }, [modifiers.focused]);
194
+
195
+ return (
196
+ <Button
197
+ ref={ref}
198
+ variant="ghost"
199
+ size="icon"
200
+ data-day={day.date.toLocaleDateString()}
201
+ data-selected-single={
202
+ modifiers.selected &&
203
+ !modifiers.range_start &&
204
+ !modifiers.range_end &&
205
+ !modifiers.range_middle
206
+ }
207
+ data-range-start={modifiers.range_start}
208
+ data-range-end={modifiers.range_end}
209
+ data-range-middle={modifiers.range_middle}
210
+ className={cn(
211
+ "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 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70",
212
+ defaultClassNames.day,
213
+ className
214
+ )}
215
+ {...props}
216
+ />
217
+ );
218
+ }
219
+
220
+ export { Calendar, CalendarDayButton };
@@ -0,0 +1,110 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import {
3
+ Card,
4
+ CardHeader,
5
+ CardFooter,
6
+ CardTitle,
7
+ CardDescription,
8
+ CardContent,
9
+ } from "./card";
10
+ import { Button } from "./button";
11
+
12
+ const meta = {
13
+ title: "Components/Card",
14
+ component: Card,
15
+ parameters: {
16
+ layout: "padded",
17
+ },
18
+ tags: ["autodocs"],
19
+ } satisfies Meta<typeof Card>;
20
+
21
+ export default meta;
22
+ type Story = StoryObj<typeof meta>;
23
+
24
+ export const Default: Story = {
25
+ render: () => (
26
+ <Card className="w-[350px]">
27
+ <CardHeader>
28
+ <CardTitle>Card Title</CardTitle>
29
+ <CardDescription>Card description goes here.</CardDescription>
30
+ </CardHeader>
31
+ <CardContent>
32
+ <p>Card content area. You can put any content here.</p>
33
+ </CardContent>
34
+ <CardFooter>
35
+ <Button>Action</Button>
36
+ </CardFooter>
37
+ </Card>
38
+ ),
39
+ };
40
+
41
+ export const WithoutFooter: Story = {
42
+ render: () => (
43
+ <Card className="w-[350px]">
44
+ <CardHeader>
45
+ <CardTitle>Simple Card</CardTitle>
46
+ <CardDescription>This card doesn't have a footer.</CardDescription>
47
+ </CardHeader>
48
+ <CardContent>
49
+ <p>Just the header and content sections.</p>
50
+ </CardContent>
51
+ </Card>
52
+ ),
53
+ };
54
+
55
+ export const LongContent: Story = {
56
+ render: () => (
57
+ <Card className="w-[400px]">
58
+ <CardHeader>
59
+ <CardTitle>Article Title</CardTitle>
60
+ <CardDescription>Published on January 1, 2024</CardDescription>
61
+ </CardHeader>
62
+ <CardContent>
63
+ <div className="space-y-2">
64
+ <p>
65
+ This is a longer card content that demonstrates how the card
66
+ component handles extended text and multiple paragraphs.
67
+ </p>
68
+ <p>
69
+ The card maintains proper spacing and layout even with longer
70
+ content. You can include images, lists, or any other content within
71
+ the card content area.
72
+ </p>
73
+ </div>
74
+ </CardContent>
75
+ <CardFooter className="flex justify-between">
76
+ <Button variant="outline">Cancel</Button>
77
+ <Button>Save</Button>
78
+ </CardFooter>
79
+ </Card>
80
+ ),
81
+ };
82
+
83
+ export const MultipleCards: Story = {
84
+ render: () => (
85
+ <div className="grid grid-cols-3 gap-4">
86
+ <Card>
87
+ <CardHeader>
88
+ <CardTitle>Card 1</CardTitle>
89
+ <CardDescription>First card</CardDescription>
90
+ </CardHeader>
91
+ <CardContent>Content for card 1</CardContent>
92
+ </Card>
93
+ <Card>
94
+ <CardHeader>
95
+ <CardTitle>Card 2</CardTitle>
96
+ <CardDescription>Second card</CardDescription>
97
+ </CardHeader>
98
+ <CardContent>Content for card 2</CardContent>
99
+ </Card>
100
+ <Card>
101
+ <CardHeader>
102
+ <CardTitle>Card 3</CardTitle>
103
+ <CardDescription>Third card</CardDescription>
104
+ </CardHeader>
105
+ <CardContent>Content for card 3</CardContent>
106
+ </Card>
107
+ </div>
108
+ ),
109
+ };
110
+
@@ -0,0 +1,56 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { render } from "@testing-library/react";
3
+ import {
4
+ Card,
5
+ CardHeader,
6
+ CardTitle,
7
+ CardDescription,
8
+ CardContent,
9
+ CardFooter,
10
+ } from "./card";
11
+
12
+ describe("Card", () => {
13
+ it("should render card", () => {
14
+ const { container } = render(<Card>Card content</Card>);
15
+ const card = container.querySelector('[data-slot="card"]');
16
+ expect(card).toBeInTheDocument();
17
+ });
18
+
19
+ it("should render card with header, content, and footer", () => {
20
+ const { getByText } = render(
21
+ <Card>
22
+ <CardHeader>
23
+ <CardTitle>Card Title</CardTitle>
24
+ <CardDescription>Card Description</CardDescription>
25
+ </CardHeader>
26
+ <CardContent>Card Content</CardContent>
27
+ <CardFooter>Card Footer</CardFooter>
28
+ </Card>
29
+ );
30
+ expect(getByText("Card Title")).toBeInTheDocument();
31
+ expect(getByText("Card Description")).toBeInTheDocument();
32
+ expect(getByText("Card Content")).toBeInTheDocument();
33
+ expect(getByText("Card Footer")).toBeInTheDocument();
34
+ });
35
+
36
+ it("should render card header", () => {
37
+ const { container } = render(
38
+ <Card>
39
+ <CardHeader>Header</CardHeader>
40
+ </Card>
41
+ );
42
+ const header = container.querySelector('[data-slot="card-header"]');
43
+ expect(header).toBeInTheDocument();
44
+ });
45
+
46
+ it("should render card content", () => {
47
+ const { container } = render(
48
+ <Card>
49
+ <CardContent>Content</CardContent>
50
+ </Card>
51
+ );
52
+ const content = container.querySelector('[data-slot="card-content"]');
53
+ expect(content).toBeInTheDocument();
54
+ });
55
+ });
56
+
@@ -0,0 +1,92 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ function Card({ className, ...props }: React.ComponentProps<"div">) {
6
+ return (
7
+ <div
8
+ data-slot="card"
9
+ className={cn(
10
+ "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
11
+ className
12
+ )}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
19
+ return (
20
+ <div
21
+ data-slot="card-header"
22
+ className={cn(
23
+ "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
24
+ className
25
+ )}
26
+ {...props}
27
+ />
28
+ )
29
+ }
30
+
31
+ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
32
+ return (
33
+ <div
34
+ data-slot="card-title"
35
+ className={cn("leading-none font-semibold", className)}
36
+ {...props}
37
+ />
38
+ )
39
+ }
40
+
41
+ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
42
+ return (
43
+ <div
44
+ data-slot="card-description"
45
+ className={cn("text-muted-foreground text-sm", className)}
46
+ {...props}
47
+ />
48
+ )
49
+ }
50
+
51
+ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
52
+ return (
53
+ <div
54
+ data-slot="card-action"
55
+ className={cn(
56
+ "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
57
+ className
58
+ )}
59
+ {...props}
60
+ />
61
+ )
62
+ }
63
+
64
+ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
65
+ return (
66
+ <div
67
+ data-slot="card-content"
68
+ className={cn("px-6", className)}
69
+ {...props}
70
+ />
71
+ )
72
+ }
73
+
74
+ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
75
+ return (
76
+ <div
77
+ data-slot="card-footer"
78
+ className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
79
+ {...props}
80
+ />
81
+ )
82
+ }
83
+
84
+ export {
85
+ Card,
86
+ CardHeader,
87
+ CardFooter,
88
+ CardTitle,
89
+ CardAction,
90
+ CardDescription,
91
+ CardContent,
92
+ }
@@ -0,0 +1,90 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import {
3
+ Carousel,
4
+ CarouselContent,
5
+ CarouselItem,
6
+ CarouselNext,
7
+ CarouselPrevious,
8
+ } from "./carousel";
9
+ import { Card, CardContent } from "./card";
10
+
11
+ const meta = {
12
+ title: "Components/Carousel",
13
+ component: Carousel,
14
+ parameters: {
15
+ layout: "padded",
16
+ },
17
+ tags: ["autodocs"],
18
+ } satisfies Meta<typeof Carousel>;
19
+
20
+ export default meta;
21
+ type Story = StoryObj<typeof meta>;
22
+
23
+ export const Default: Story = {
24
+ render: () => (
25
+ <Carousel className="w-full max-w-xs">
26
+ <CarouselContent>
27
+ {Array.from({ length: 5 }).map((_, index) => (
28
+ <CarouselItem key={index}>
29
+ <div className="p-1">
30
+ <Card>
31
+ <CardContent className="flex aspect-square items-center justify-center p-6">
32
+ <span className="text-4xl font-semibold">{index + 1}</span>
33
+ </CardContent>
34
+ </Card>
35
+ </div>
36
+ </CarouselItem>
37
+ ))}
38
+ </CarouselContent>
39
+ <CarouselPrevious />
40
+ <CarouselNext />
41
+ </Carousel>
42
+ ),
43
+ };
44
+
45
+ export const MultipleItems: Story = {
46
+ render: () => (
47
+ <Carousel className="w-full max-w-xs">
48
+ <CarouselContent className="-ml-1">
49
+ {Array.from({ length: 5 }).map((_, index) => (
50
+ <CarouselItem key={index} className="pl-1 md:basis-1/2 lg:basis-1/3">
51
+ <div className="p-1">
52
+ <Card>
53
+ <CardContent className="flex aspect-square items-center justify-center p-6">
54
+ <span className="text-2xl font-semibold">{index + 1}</span>
55
+ </CardContent>
56
+ </Card>
57
+ </div>
58
+ </CarouselItem>
59
+ ))}
60
+ </CarouselContent>
61
+ <CarouselPrevious />
62
+ <CarouselNext />
63
+ </Carousel>
64
+ ),
65
+ };
66
+
67
+ export const WithImages: Story = {
68
+ render: () => (
69
+ <Carousel className="w-full max-w-xs">
70
+ <CarouselContent>
71
+ {Array.from({ length: 3 }).map((_, index) => (
72
+ <CarouselItem key={index}>
73
+ <div className="p-1">
74
+ <Card>
75
+ <CardContent className="flex aspect-square items-center justify-center p-0">
76
+ <div className="flex h-full w-full items-center justify-center bg-muted">
77
+ <span className="text-4xl">Image {index + 1}</span>
78
+ </div>
79
+ </CardContent>
80
+ </Card>
81
+ </div>
82
+ </CarouselItem>
83
+ ))}
84
+ </CarouselContent>
85
+ <CarouselPrevious />
86
+ <CarouselNext />
87
+ </Carousel>
88
+ ),
89
+ };
90
+