create-auto-app 1.34.0 → 1.36.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.
- package/package.json +2 -2
- package/templates/typical/.gitignore +1 -1
- package/templates/typical/auto.config.ts +85 -80
- package/templates/typical/client/.gitignore +0 -4
- package/templates/typical/client/.storybook/main.ts +0 -66
- package/templates/typical/client/.storybook/manager-head.html +0 -154
- package/templates/typical/client/.storybook/manager.ts +0 -164
- package/templates/typical/client/.storybook/preview-head.html +0 -31
- package/templates/typical/client/.storybook/preview.tsx +0 -120
- package/templates/typical/client/codegen.ts +0 -17
- package/templates/typical/client/components.json +0 -29
- package/templates/typical/client/index.html +0 -12
- package/templates/typical/client/package.json +0 -69
- package/templates/typical/client/pnpm-lock.yaml +0 -7868
- package/templates/typical/client/public/blank.svg +0 -1
- package/templates/typical/client/public/mockServiceWorker.js +0 -336
- package/templates/typical/client/src/App.tsx +0 -29
- package/templates/typical/client/src/components/.gitkeep +0 -0
- package/templates/typical/client/src/components/ui/Accordion.stories.tsx +0 -49
- package/templates/typical/client/src/components/ui/Accordion.tsx +0 -55
- package/templates/typical/client/src/components/ui/Alert.stories.tsx +0 -29
- package/templates/typical/client/src/components/ui/Alert.tsx +0 -56
- package/templates/typical/client/src/components/ui/AlertDialog.stories.tsx +0 -67
- package/templates/typical/client/src/components/ui/AlertDialog.tsx +0 -178
- package/templates/typical/client/src/components/ui/AspectRatio.stories.tsx +0 -35
- package/templates/typical/client/src/components/ui/AspectRatio.tsx +0 -13
- package/templates/typical/client/src/components/ui/Avatar.stories.tsx +0 -45
- package/templates/typical/client/src/components/ui/Avatar.tsx +0 -98
- package/templates/typical/client/src/components/ui/Badge.stories.tsx +0 -41
- package/templates/typical/client/src/components/ui/Badge.tsx +0 -45
- package/templates/typical/client/src/components/ui/Breadcrumb.stories.tsx +0 -54
- package/templates/typical/client/src/components/ui/Breadcrumb.tsx +0 -104
- package/templates/typical/client/src/components/ui/Button.stories.tsx +0 -102
- package/templates/typical/client/src/components/ui/Button.tsx +0 -67
- package/templates/typical/client/src/components/ui/ButtonGroup.stories.tsx +0 -32
- package/templates/typical/client/src/components/ui/ButtonGroup.tsx +0 -81
- package/templates/typical/client/src/components/ui/Calendar.stories.tsx +0 -40
- package/templates/typical/client/src/components/ui/Calendar.tsx +0 -165
- package/templates/typical/client/src/components/ui/Card.stories.tsx +0 -44
- package/templates/typical/client/src/components/ui/Card.tsx +0 -66
- package/templates/typical/client/src/components/ui/Carousel.stories.tsx +0 -56
- package/templates/typical/client/src/components/ui/Carousel.tsx +0 -225
- package/templates/typical/client/src/components/ui/Chart.stories.tsx +0 -39
- package/templates/typical/client/src/components/ui/Chart.tsx +0 -305
- package/templates/typical/client/src/components/ui/Checkbox.stories.tsx +0 -35
- package/templates/typical/client/src/components/ui/Checkbox.tsx +0 -30
- package/templates/typical/client/src/components/ui/Collapsible.stories.tsx +0 -58
- package/templates/typical/client/src/components/ui/Collapsible.tsx +0 -18
- package/templates/typical/client/src/components/ui/Combobox.stories.tsx +0 -75
- package/templates/typical/client/src/components/ui/Combobox.tsx +0 -296
- package/templates/typical/client/src/components/ui/Command.stories.tsx +0 -71
- package/templates/typical/client/src/components/ui/Command.tsx +0 -157
- package/templates/typical/client/src/components/ui/ContextMenu.stories.tsx +0 -68
- package/templates/typical/client/src/components/ui/ContextMenu.tsx +0 -231
- package/templates/typical/client/src/components/ui/DesignSystem-Colors.mdx +0 -68
- package/templates/typical/client/src/components/ui/DesignSystem-Colors.stories.tsx +0 -117
- package/templates/typical/client/src/components/ui/DesignSystem-Layout.mdx +0 -64
- package/templates/typical/client/src/components/ui/DesignSystem-Layout.stories.tsx +0 -167
- package/templates/typical/client/src/components/ui/DesignSystem-Overview.stories.tsx +0 -748
- package/templates/typical/client/src/components/ui/DesignSystem-Typography.mdx +0 -31
- package/templates/typical/client/src/components/ui/DesignSystem-Typography.stories.tsx +0 -80
- package/templates/typical/client/src/components/ui/Dialog.stories.tsx +0 -74
- package/templates/typical/client/src/components/ui/Dialog.tsx +0 -154
- package/templates/typical/client/src/components/ui/Direction.stories.tsx +0 -38
- package/templates/typical/client/src/components/ui/Direction.tsx +0 -24
- package/templates/typical/client/src/components/ui/Drawer.stories.tsx +0 -70
- package/templates/typical/client/src/components/ui/Drawer.tsx +0 -124
- package/templates/typical/client/src/components/ui/DropdownMenu.stories.tsx +0 -74
- package/templates/typical/client/src/components/ui/DropdownMenu.tsx +0 -239
- package/templates/typical/client/src/components/ui/Empty.stories.tsx +0 -37
- package/templates/typical/client/src/components/ui/Empty.tsx +0 -98
- package/templates/typical/client/src/components/ui/Field.stories.tsx +0 -50
- package/templates/typical/client/src/components/ui/Field.tsx +0 -251
- package/templates/typical/client/src/components/ui/Form.stories.tsx +0 -45
- package/templates/typical/client/src/components/ui/Form.tsx +0 -148
- package/templates/typical/client/src/components/ui/HoverCard.stories.tsx +0 -49
- package/templates/typical/client/src/components/ui/HoverCard.tsx +0 -39
- package/templates/typical/client/src/components/ui/Input.stories.tsx +0 -42
- package/templates/typical/client/src/components/ui/Input.tsx +0 -22
- package/templates/typical/client/src/components/ui/InputGroup.stories.tsx +0 -53
- package/templates/typical/client/src/components/ui/InputGroup.tsx +0 -153
- package/templates/typical/client/src/components/ui/InputOTP.stories.tsx +0 -42
- package/templates/typical/client/src/components/ui/InputOTP.tsx +0 -72
- package/templates/typical/client/src/components/ui/Item.stories.tsx +0 -64
- package/templates/typical/client/src/components/ui/Item.tsx +0 -168
- package/templates/typical/client/src/components/ui/Kbd.stories.tsx +0 -59
- package/templates/typical/client/src/components/ui/Kbd.tsx +0 -22
- package/templates/typical/client/src/components/ui/Label.stories.tsx +0 -90
- package/templates/typical/client/src/components/ui/Label.tsx +0 -44
- package/templates/typical/client/src/components/ui/Menubar.stories.tsx +0 -78
- package/templates/typical/client/src/components/ui/Menubar.tsx +0 -251
- package/templates/typical/client/src/components/ui/NativeSelect.stories.tsx +0 -45
- package/templates/typical/client/src/components/ui/NativeSelect.tsx +0 -50
- package/templates/typical/client/src/components/ui/NavigationMenu.stories.tsx +0 -80
- package/templates/typical/client/src/components/ui/NavigationMenu.tsx +0 -152
- package/templates/typical/client/src/components/ui/Pagination.stories.tsx +0 -77
- package/templates/typical/client/src/components/ui/Pagination.tsx +0 -108
- package/templates/typical/client/src/components/ui/Popover.stories.tsx +0 -53
- package/templates/typical/client/src/components/ui/Popover.tsx +0 -57
- package/templates/typical/client/src/components/ui/Progress.stories.tsx +0 -32
- package/templates/typical/client/src/components/ui/Progress.tsx +0 -25
- package/templates/typical/client/src/components/ui/RadioGroup.stories.tsx +0 -50
- package/templates/typical/client/src/components/ui/RadioGroup.tsx +0 -36
- package/templates/typical/client/src/components/ui/Resizable.stories.tsx +0 -72
- package/templates/typical/client/src/components/ui/Resizable.tsx +0 -54
- package/templates/typical/client/src/components/ui/ScrollArea.stories.tsx +0 -45
- package/templates/typical/client/src/components/ui/ScrollArea.tsx +0 -51
- package/templates/typical/client/src/components/ui/Select.stories.tsx +0 -59
- package/templates/typical/client/src/components/ui/Select.tsx +0 -171
- package/templates/typical/client/src/components/ui/Separator.stories.tsx +0 -42
- package/templates/typical/client/src/components/ui/Separator.tsx +0 -27
- package/templates/typical/client/src/components/ui/Sheet.stories.tsx +0 -68
- package/templates/typical/client/src/components/ui/Sheet.tsx +0 -115
- package/templates/typical/client/src/components/ui/Sidebar.stories.tsx +0 -96
- package/templates/typical/client/src/components/ui/Sidebar.tsx +0 -695
- package/templates/typical/client/src/components/ui/Skeleton.stories.tsx +0 -40
- package/templates/typical/client/src/components/ui/Skeleton.tsx +0 -11
- package/templates/typical/client/src/components/ui/Slider.stories.tsx +0 -24
- package/templates/typical/client/src/components/ui/Slider.tsx +0 -55
- package/templates/typical/client/src/components/ui/Sonner.stories.tsx +0 -45
- package/templates/typical/client/src/components/ui/Sonner.tsx +0 -38
- package/templates/typical/client/src/components/ui/Spinner.stories.tsx +0 -26
- package/templates/typical/client/src/components/ui/Spinner.tsx +0 -13
- package/templates/typical/client/src/components/ui/Switch.stories.tsx +0 -39
- package/templates/typical/client/src/components/ui/Switch.tsx +0 -35
- package/templates/typical/client/src/components/ui/Table.stories.tsx +0 -67
- package/templates/typical/client/src/components/ui/Table.tsx +0 -86
- package/templates/typical/client/src/components/ui/Tabs.stories.tsx +0 -53
- package/templates/typical/client/src/components/ui/Tabs.tsx +0 -75
- package/templates/typical/client/src/components/ui/Textarea.stories.tsx +0 -27
- package/templates/typical/client/src/components/ui/Textarea.tsx +0 -22
- package/templates/typical/client/src/components/ui/Toast.stories.tsx +0 -116
- package/templates/typical/client/src/components/ui/Toast.tsx +0 -123
- package/templates/typical/client/src/components/ui/Toaster.tsx +0 -32
- package/templates/typical/client/src/components/ui/Toggle.stories.tsx +0 -44
- package/templates/typical/client/src/components/ui/Toggle.tsx +0 -42
- package/templates/typical/client/src/components/ui/ToggleGroup.stories.tsx +0 -61
- package/templates/typical/client/src/components/ui/ToggleGroup.tsx +0 -83
- package/templates/typical/client/src/components/ui/Tooltip.stories.tsx +0 -42
- package/templates/typical/client/src/components/ui/Tooltip.tsx +0 -48
- package/templates/typical/client/src/gql/execute.ts +0 -11
- package/templates/typical/client/src/gql/fragment-masking.ts +0 -83
- package/templates/typical/client/src/gql/gql.ts +0 -9
- package/templates/typical/client/src/gql/graphql.ts +0 -182
- package/templates/typical/client/src/gql/index.ts +0 -2
- package/templates/typical/client/src/graphql/mutations.ts +0 -0
- package/templates/typical/client/src/graphql/queries.ts +0 -0
- package/templates/typical/client/src/hooks/.gitkeep +0 -0
- package/templates/typical/client/src/hooks/use-mobile.ts +0 -19
- package/templates/typical/client/src/hooks/use-toast.ts +0 -186
- package/templates/typical/client/src/index.css +0 -121
- package/templates/typical/client/src/lib/utils.ts +0 -6
- package/templates/typical/client/src/main.tsx +0 -5
- package/templates/typical/client/tsconfig.app.json +0 -26
- package/templates/typical/client/tsconfig.json +0 -10
- package/templates/typical/client/vite.config.ts +0 -50
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
-
import { Button } from '@/components/ui/Button';
|
|
3
|
-
|
|
4
|
-
const meta: Meta<typeof Button> = {
|
|
5
|
-
title: 'UI Components/Button',
|
|
6
|
-
component: Button,
|
|
7
|
-
};
|
|
8
|
-
export default meta;
|
|
9
|
-
type Story = StoryObj<typeof Button>;
|
|
10
|
-
|
|
11
|
-
/** Shows the primary filled button variant. */
|
|
12
|
-
export const Default: Story = {
|
|
13
|
-
args: {
|
|
14
|
-
children: 'Button',
|
|
15
|
-
},
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
/** Shows the secondary button variant with muted styling. */
|
|
19
|
-
export const Secondary: Story = {
|
|
20
|
-
args: {
|
|
21
|
-
variant: 'secondary',
|
|
22
|
-
children: 'Secondary',
|
|
23
|
-
},
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
/** Shows the destructive button variant for dangerous actions like delete. */
|
|
27
|
-
export const Destructive: Story = {
|
|
28
|
-
args: {
|
|
29
|
-
variant: 'destructive',
|
|
30
|
-
children: 'Destructive',
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
/** Shows the outline button variant with a border and transparent background. */
|
|
35
|
-
export const Outline: Story = {
|
|
36
|
-
args: {
|
|
37
|
-
variant: 'outline',
|
|
38
|
-
children: 'Outline',
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
/** Shows the ghost button variant with no background until hovered. */
|
|
43
|
-
export const Ghost: Story = {
|
|
44
|
-
args: {
|
|
45
|
-
variant: 'ghost',
|
|
46
|
-
children: 'Ghost',
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
/** Shows the link button variant styled as an inline text link with underline on hover. */
|
|
51
|
-
export const Link: Story = {
|
|
52
|
-
args: {
|
|
53
|
-
variant: 'link',
|
|
54
|
-
children: 'Link',
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
/** Shows a compact small-sized button. */
|
|
59
|
-
export const Small: Story = {
|
|
60
|
-
args: {
|
|
61
|
-
size: 'sm',
|
|
62
|
-
children: 'Small',
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
/** Shows a larger button with more padding. */
|
|
67
|
-
export const Large: Story = {
|
|
68
|
-
args: {
|
|
69
|
-
size: 'lg',
|
|
70
|
-
children: 'Large',
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
/** Shows an icon-only square button. */
|
|
75
|
-
export const Icon: Story = {
|
|
76
|
-
args: {
|
|
77
|
-
size: 'icon',
|
|
78
|
-
children: 'X',
|
|
79
|
-
},
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
/** Displays all button variants and sizes side by side for visual comparison. */
|
|
83
|
-
export const AllVariants: Story = {
|
|
84
|
-
render: () => (
|
|
85
|
-
<div className="flex flex-col gap-4">
|
|
86
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
87
|
-
<Button variant="default">Default</Button>
|
|
88
|
-
<Button variant="secondary">Secondary</Button>
|
|
89
|
-
<Button variant="destructive">Destructive</Button>
|
|
90
|
-
<Button variant="outline">Outline</Button>
|
|
91
|
-
<Button variant="ghost">Ghost</Button>
|
|
92
|
-
<Button variant="link">Link</Button>
|
|
93
|
-
</div>
|
|
94
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
95
|
-
<Button size="sm">Small</Button>
|
|
96
|
-
<Button size="default">Default</Button>
|
|
97
|
-
<Button size="lg">Large</Button>
|
|
98
|
-
<Button size="icon">X</Button>
|
|
99
|
-
</div>
|
|
100
|
-
</div>
|
|
101
|
-
),
|
|
102
|
-
};
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
-
import { Slot } from 'radix-ui';
|
|
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-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
9
|
-
{
|
|
10
|
-
variants: {
|
|
11
|
-
variant: {
|
|
12
|
-
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
13
|
-
destructive:
|
|
14
|
-
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
|
15
|
-
outline:
|
|
16
|
-
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
|
|
17
|
-
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
18
|
-
ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
|
19
|
-
link: 'text-primary underline-offset-4 hover:underline',
|
|
20
|
-
},
|
|
21
|
-
size: {
|
|
22
|
-
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
|
|
23
|
-
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
24
|
-
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
|
|
25
|
-
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
|
|
26
|
-
icon: 'size-9',
|
|
27
|
-
'icon-xs': "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
|
|
28
|
-
'icon-sm': 'size-8',
|
|
29
|
-
'icon-lg': 'size-10',
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
defaultVariants: {
|
|
33
|
-
variant: 'default',
|
|
34
|
-
size: 'default',
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* A clickable element that triggers an action or navigation.
|
|
41
|
-
* Supports six visual variants (default, secondary, destructive, outline, ghost, link) and multiple sizes including icon-only modes.
|
|
42
|
-
*/
|
|
43
|
-
function Button({
|
|
44
|
-
className,
|
|
45
|
-
variant = 'default',
|
|
46
|
-
size = 'default',
|
|
47
|
-
/** Merges props onto the child element instead of rendering a button. Use with Link or anchor components for navigation. */
|
|
48
|
-
asChild = false,
|
|
49
|
-
...props
|
|
50
|
-
}: React.ComponentProps<'button'> &
|
|
51
|
-
VariantProps<typeof buttonVariants> & {
|
|
52
|
-
asChild?: boolean;
|
|
53
|
-
}) {
|
|
54
|
-
const Comp = asChild ? Slot.Root : 'button';
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
<Comp
|
|
58
|
-
data-slot="button"
|
|
59
|
-
data-variant={variant}
|
|
60
|
-
data-size={size}
|
|
61
|
-
className={cn(buttonVariants({ variant, size, className }))}
|
|
62
|
-
{...props}
|
|
63
|
-
/>
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export { Button, buttonVariants };
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
-
import { ButtonGroup } from '@/components/ui/ButtonGroup';
|
|
3
|
-
import { Button } from '@/components/ui/Button';
|
|
4
|
-
|
|
5
|
-
const meta: Meta<typeof ButtonGroup> = {
|
|
6
|
-
title: 'UI Components/ButtonGroup',
|
|
7
|
-
component: ButtonGroup,
|
|
8
|
-
};
|
|
9
|
-
export default meta;
|
|
10
|
-
type Story = StoryObj<typeof ButtonGroup>;
|
|
11
|
-
|
|
12
|
-
/** Shows a horizontal group of three outline buttons with connected borders. */
|
|
13
|
-
export const Default: Story = {
|
|
14
|
-
render: () => (
|
|
15
|
-
<ButtonGroup>
|
|
16
|
-
<Button variant="outline">Left</Button>
|
|
17
|
-
<Button variant="outline">Center</Button>
|
|
18
|
-
<Button variant="outline">Right</Button>
|
|
19
|
-
</ButtonGroup>
|
|
20
|
-
),
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/** Shows a vertically stacked button group. */
|
|
24
|
-
export const Vertical: Story = {
|
|
25
|
-
render: () => (
|
|
26
|
-
<ButtonGroup orientation="vertical">
|
|
27
|
-
<Button variant="outline">Top</Button>
|
|
28
|
-
<Button variant="outline">Middle</Button>
|
|
29
|
-
<Button variant="outline">Bottom</Button>
|
|
30
|
-
</ButtonGroup>
|
|
31
|
-
),
|
|
32
|
-
};
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { cva, type VariantProps } from 'class-variance-authority';
|
|
2
|
-
import { Slot } from 'radix-ui';
|
|
3
|
-
|
|
4
|
-
import { cn } from '@/lib/utils';
|
|
5
|
-
import { Separator } from '@/components/ui/Separator';
|
|
6
|
-
|
|
7
|
-
const buttonGroupVariants = cva(
|
|
8
|
-
"flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2",
|
|
9
|
-
{
|
|
10
|
-
variants: {
|
|
11
|
-
orientation: {
|
|
12
|
-
horizontal:
|
|
13
|
-
'[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none',
|
|
14
|
-
vertical:
|
|
15
|
-
'flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none',
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
defaultVariants: {
|
|
19
|
-
orientation: 'horizontal',
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Groups multiple Buttons together with connected borders, removing inner border-radius between siblings.
|
|
26
|
-
* Supports horizontal (default) and vertical orientation.
|
|
27
|
-
*/
|
|
28
|
-
function ButtonGroup({
|
|
29
|
-
className,
|
|
30
|
-
orientation,
|
|
31
|
-
...props
|
|
32
|
-
}: React.ComponentProps<'div'> & VariantProps<typeof buttonGroupVariants>) {
|
|
33
|
-
return (
|
|
34
|
-
<div
|
|
35
|
-
role="group"
|
|
36
|
-
data-slot="button-group"
|
|
37
|
-
data-orientation={orientation}
|
|
38
|
-
className={cn(buttonGroupVariants({ orientation }), className)}
|
|
39
|
-
{...props}
|
|
40
|
-
/>
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** A non-interactive text label or addon segment within a ButtonGroup, styled to match adjacent buttons. */
|
|
45
|
-
function ButtonGroupText({
|
|
46
|
-
className,
|
|
47
|
-
asChild = false,
|
|
48
|
-
...props
|
|
49
|
-
}: React.ComponentProps<'div'> & {
|
|
50
|
-
asChild?: boolean;
|
|
51
|
-
}) {
|
|
52
|
-
const Comp = asChild ? Slot.Root : 'div';
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<Comp
|
|
56
|
-
className={cn(
|
|
57
|
-
"bg-muted flex items-center gap-2 rounded-md border px-4 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
|
58
|
-
className,
|
|
59
|
-
)}
|
|
60
|
-
{...props}
|
|
61
|
-
/>
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** A visual divider between buttons in a ButtonGroup, rendered as a Separator. */
|
|
66
|
-
function ButtonGroupSeparator({
|
|
67
|
-
className,
|
|
68
|
-
orientation = 'vertical',
|
|
69
|
-
...props
|
|
70
|
-
}: React.ComponentProps<typeof Separator>) {
|
|
71
|
-
return (
|
|
72
|
-
<Separator
|
|
73
|
-
data-slot="button-group-separator"
|
|
74
|
-
orientation={orientation}
|
|
75
|
-
className={cn('bg-input relative !m-0 self-stretch data-[orientation=vertical]:h-auto', className)}
|
|
76
|
-
{...props}
|
|
77
|
-
/>
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export { ButtonGroup, ButtonGroupSeparator, ButtonGroupText, buttonGroupVariants };
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
3
|
-
import { Calendar } from '@/components/ui/Calendar';
|
|
4
|
-
|
|
5
|
-
const meta: Meta<typeof Calendar> = {
|
|
6
|
-
title: 'UI Components/Calendar',
|
|
7
|
-
component: Calendar,
|
|
8
|
-
};
|
|
9
|
-
export default meta;
|
|
10
|
-
type Story = StoryObj<typeof Calendar>;
|
|
11
|
-
|
|
12
|
-
function CalendarDemo() {
|
|
13
|
-
const [date, setDate] = useState<Date | undefined>(new Date());
|
|
14
|
-
return <Calendar mode="single" selected={date} onSelect={setDate} className="rounded-md border" />;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** Shows a single-date selection calendar with controlled state. */
|
|
18
|
-
export const Default: Story = {
|
|
19
|
-
render: () => <CalendarDemo />,
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
function CalendarRangeDemo() {
|
|
23
|
-
const [range, setRange] = useState<{ from: Date; to?: Date } | undefined>({
|
|
24
|
-
from: new Date(),
|
|
25
|
-
});
|
|
26
|
-
return (
|
|
27
|
-
<Calendar
|
|
28
|
-
mode="range"
|
|
29
|
-
selected={range}
|
|
30
|
-
onSelect={setRange as (value: unknown) => void}
|
|
31
|
-
numberOfMonths={2}
|
|
32
|
-
className="rounded-md border"
|
|
33
|
-
/>
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** Demonstrates date range selection across two side-by-side months. */
|
|
38
|
-
export const RangeSelection: Story = {
|
|
39
|
-
render: () => <CalendarRangeDemo />,
|
|
40
|
-
};
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';
|
|
3
|
-
import { DayPicker, getDefaultClassNames, type DayButton } from 'react-day-picker';
|
|
4
|
-
|
|
5
|
-
import { cn } from '@/lib/utils';
|
|
6
|
-
import { Button, buttonVariants } from '@/components/ui/Button';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* A date picker component built on react-day-picker supporting single date, multiple date, and date range selection.
|
|
10
|
-
* Supports month/year dropdowns, outside days visibility, keyboard navigation, and multi-month layouts.
|
|
11
|
-
*/
|
|
12
|
-
function Calendar({
|
|
13
|
-
className,
|
|
14
|
-
classNames,
|
|
15
|
-
showOutsideDays = true,
|
|
16
|
-
captionLayout = 'label',
|
|
17
|
-
/** The Button variant used for navigation arrows. Defaults to `"ghost"`. */
|
|
18
|
-
buttonVariant = 'ghost',
|
|
19
|
-
formatters,
|
|
20
|
-
components,
|
|
21
|
-
...props
|
|
22
|
-
}: React.ComponentProps<typeof DayPicker> & {
|
|
23
|
-
buttonVariant?: React.ComponentProps<typeof Button>['variant'];
|
|
24
|
-
}) {
|
|
25
|
-
const defaultClassNames = getDefaultClassNames();
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<DayPicker
|
|
29
|
-
showOutsideDays={showOutsideDays}
|
|
30
|
-
className={cn(
|
|
31
|
-
'bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent',
|
|
32
|
-
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
|
33
|
-
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
|
34
|
-
className,
|
|
35
|
-
)}
|
|
36
|
-
captionLayout={captionLayout}
|
|
37
|
-
formatters={{
|
|
38
|
-
formatMonthDropdown: (date) => date.toLocaleString('default', { month: 'short' }),
|
|
39
|
-
...formatters,
|
|
40
|
-
}}
|
|
41
|
-
classNames={{
|
|
42
|
-
root: cn('w-fit', defaultClassNames.root),
|
|
43
|
-
months: cn('flex gap-4 flex-col md:flex-row relative', defaultClassNames.months),
|
|
44
|
-
month: cn('flex flex-col w-full gap-4', defaultClassNames.month),
|
|
45
|
-
nav: cn('flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between', defaultClassNames.nav),
|
|
46
|
-
button_previous: cn(
|
|
47
|
-
buttonVariants({ variant: buttonVariant }),
|
|
48
|
-
'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none',
|
|
49
|
-
defaultClassNames.button_previous,
|
|
50
|
-
),
|
|
51
|
-
button_next: cn(
|
|
52
|
-
buttonVariants({ variant: buttonVariant }),
|
|
53
|
-
'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none',
|
|
54
|
-
defaultClassNames.button_next,
|
|
55
|
-
),
|
|
56
|
-
month_caption: cn(
|
|
57
|
-
'flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)',
|
|
58
|
-
defaultClassNames.month_caption,
|
|
59
|
-
),
|
|
60
|
-
dropdowns: cn(
|
|
61
|
-
'w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5',
|
|
62
|
-
defaultClassNames.dropdowns,
|
|
63
|
-
),
|
|
64
|
-
dropdown_root: cn(
|
|
65
|
-
'relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md',
|
|
66
|
-
defaultClassNames.dropdown_root,
|
|
67
|
-
),
|
|
68
|
-
dropdown: cn('absolute bg-popover inset-0 opacity-0', defaultClassNames.dropdown),
|
|
69
|
-
caption_label: cn(
|
|
70
|
-
'select-none font-medium',
|
|
71
|
-
captionLayout === 'label'
|
|
72
|
-
? 'text-sm'
|
|
73
|
-
: 'rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5',
|
|
74
|
-
defaultClassNames.caption_label,
|
|
75
|
-
),
|
|
76
|
-
table: 'w-full border-collapse',
|
|
77
|
-
weekdays: cn('flex', defaultClassNames.weekdays),
|
|
78
|
-
weekday: cn(
|
|
79
|
-
'text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none',
|
|
80
|
-
defaultClassNames.weekday,
|
|
81
|
-
),
|
|
82
|
-
week: cn('flex w-full mt-2', defaultClassNames.week),
|
|
83
|
-
week_number_header: cn('select-none w-(--cell-size)', defaultClassNames.week_number_header),
|
|
84
|
-
week_number: cn('text-[0.8rem] select-none text-muted-foreground', defaultClassNames.week_number),
|
|
85
|
-
day: cn(
|
|
86
|
-
'relative w-full h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none',
|
|
87
|
-
props.showWeekNumber
|
|
88
|
-
? '[&:nth-child(2)[data-selected=true]_button]:rounded-l-md'
|
|
89
|
-
: '[&:first-child[data-selected=true]_button]:rounded-l-md',
|
|
90
|
-
defaultClassNames.day,
|
|
91
|
-
),
|
|
92
|
-
range_start: cn('rounded-l-md bg-accent', defaultClassNames.range_start),
|
|
93
|
-
range_middle: cn('rounded-none', defaultClassNames.range_middle),
|
|
94
|
-
range_end: cn('rounded-r-md bg-accent', defaultClassNames.range_end),
|
|
95
|
-
today: cn(
|
|
96
|
-
'bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none',
|
|
97
|
-
defaultClassNames.today,
|
|
98
|
-
),
|
|
99
|
-
outside: cn('text-muted-foreground aria-selected:text-muted-foreground', defaultClassNames.outside),
|
|
100
|
-
disabled: cn('text-muted-foreground opacity-50', defaultClassNames.disabled),
|
|
101
|
-
hidden: cn('invisible', defaultClassNames.hidden),
|
|
102
|
-
...classNames,
|
|
103
|
-
}}
|
|
104
|
-
components={{
|
|
105
|
-
Root: ({ className, rootRef, ...props }) => {
|
|
106
|
-
return <div data-slot="calendar" ref={rootRef} className={cn(className)} {...props} />;
|
|
107
|
-
},
|
|
108
|
-
Chevron: ({ className, orientation, ...props }) => {
|
|
109
|
-
if (orientation === 'left') {
|
|
110
|
-
return <ChevronLeftIcon className={cn('size-4', className)} {...props} />;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (orientation === 'right') {
|
|
114
|
-
return <ChevronRightIcon className={cn('size-4', className)} {...props} />;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return <ChevronDownIcon className={cn('size-4', className)} {...props} />;
|
|
118
|
-
},
|
|
119
|
-
DayButton: CalendarDayButton,
|
|
120
|
-
WeekNumber: ({ children, ...props }) => {
|
|
121
|
-
return (
|
|
122
|
-
<td {...props}>
|
|
123
|
-
<div className="flex size-(--cell-size) items-center justify-center text-center">{children}</div>
|
|
124
|
-
</td>
|
|
125
|
-
);
|
|
126
|
-
},
|
|
127
|
-
...components,
|
|
128
|
-
}}
|
|
129
|
-
{...props}
|
|
130
|
-
/>
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/** Internal button rendered for each day cell, handling selection states, range highlighting, and focus management. */
|
|
135
|
-
function CalendarDayButton({ className, day, modifiers, ...props }: React.ComponentProps<typeof DayButton>) {
|
|
136
|
-
const defaultClassNames = getDefaultClassNames();
|
|
137
|
-
|
|
138
|
-
const ref = React.useRef<HTMLButtonElement>(null);
|
|
139
|
-
React.useEffect(() => {
|
|
140
|
-
if (modifiers.focused) ref.current?.focus();
|
|
141
|
-
}, [modifiers.focused]);
|
|
142
|
-
|
|
143
|
-
return (
|
|
144
|
-
<Button
|
|
145
|
-
ref={ref}
|
|
146
|
-
variant="ghost"
|
|
147
|
-
size="icon"
|
|
148
|
-
data-day={day.date.toLocaleDateString()}
|
|
149
|
-
data-selected-single={
|
|
150
|
-
modifiers.selected && !modifiers.range_start && !modifiers.range_end && !modifiers.range_middle
|
|
151
|
-
}
|
|
152
|
-
data-range-start={modifiers.range_start}
|
|
153
|
-
data-range-end={modifiers.range_end}
|
|
154
|
-
data-range-middle={modifiers.range_middle}
|
|
155
|
-
className={cn(
|
|
156
|
-
'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',
|
|
157
|
-
defaultClassNames.day,
|
|
158
|
-
className,
|
|
159
|
-
)}
|
|
160
|
-
{...props}
|
|
161
|
-
/>
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export { Calendar, CalendarDayButton };
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
-
import { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } from '@/components/ui/Card';
|
|
3
|
-
import { Button } from '@/components/ui/Button';
|
|
4
|
-
|
|
5
|
-
const meta: Meta<typeof Card> = {
|
|
6
|
-
title: 'UI Components/Card',
|
|
7
|
-
component: Card,
|
|
8
|
-
};
|
|
9
|
-
export default meta;
|
|
10
|
-
type Story = StoryObj<typeof Card>;
|
|
11
|
-
|
|
12
|
-
/** Shows a full card with header, content, and footer containing action buttons. */
|
|
13
|
-
export const Default: Story = {
|
|
14
|
-
render: () => (
|
|
15
|
-
<Card className="w-[350px]">
|
|
16
|
-
<CardHeader>
|
|
17
|
-
<CardTitle>Card Title</CardTitle>
|
|
18
|
-
<CardDescription>Card description goes here.</CardDescription>
|
|
19
|
-
</CardHeader>
|
|
20
|
-
<CardContent>
|
|
21
|
-
<p>Card content with some example text to demonstrate the layout.</p>
|
|
22
|
-
</CardContent>
|
|
23
|
-
<CardFooter className="flex justify-between">
|
|
24
|
-
<Button variant="outline">Cancel</Button>
|
|
25
|
-
<Button>Deploy</Button>
|
|
26
|
-
</CardFooter>
|
|
27
|
-
</Card>
|
|
28
|
-
),
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
/** Shows a minimal card with only a header and content, without a footer. */
|
|
32
|
-
export const Simple: Story = {
|
|
33
|
-
render: () => (
|
|
34
|
-
<Card className="w-[350px]">
|
|
35
|
-
<CardHeader>
|
|
36
|
-
<CardTitle>Notifications</CardTitle>
|
|
37
|
-
<CardDescription>You have 3 unread messages.</CardDescription>
|
|
38
|
-
</CardHeader>
|
|
39
|
-
<CardContent>
|
|
40
|
-
<p className="text-sm text-muted-foreground">Check your inbox for the latest updates.</p>
|
|
41
|
-
</CardContent>
|
|
42
|
-
</Card>
|
|
43
|
-
),
|
|
44
|
-
};
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
|
|
3
|
-
import { cn } from '@/lib/utils';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* A flexible container for grouping related content with a border, shadow, and rounded corners.
|
|
7
|
-
* Compose with CardHeader, CardTitle, CardDescription, CardContent, CardFooter, and CardAction.
|
|
8
|
-
*/
|
|
9
|
-
function Card({ className, ...props }: React.ComponentProps<'div'>) {
|
|
10
|
-
return (
|
|
11
|
-
<div
|
|
12
|
-
data-slot="card"
|
|
13
|
-
className={cn('bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm', className)}
|
|
14
|
-
{...props}
|
|
15
|
-
/>
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** The top section of a Card, containing the title, description, and optional action slot. */
|
|
20
|
-
function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
21
|
-
return (
|
|
22
|
-
<div
|
|
23
|
-
data-slot="card-header"
|
|
24
|
-
className={cn(
|
|
25
|
-
'@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',
|
|
26
|
-
className,
|
|
27
|
-
)}
|
|
28
|
-
{...props}
|
|
29
|
-
/>
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** The primary heading of a Card, rendered as semibold text. */
|
|
34
|
-
function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
|
|
35
|
-
return <div data-slot="card-title" className={cn('leading-none font-semibold', className)} {...props} />;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** A short summary below the CardTitle, styled in muted foreground. */
|
|
39
|
-
function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
|
|
40
|
-
return <div data-slot="card-description" className={cn('text-muted-foreground text-sm', className)} {...props} />;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** An optional action element (e.g., a button or menu) aligned to the top-right of the CardHeader. */
|
|
44
|
-
function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
|
|
45
|
-
return (
|
|
46
|
-
<div
|
|
47
|
-
data-slot="card-action"
|
|
48
|
-
className={cn('col-start-2 row-span-2 row-start-1 self-start justify-self-end', className)}
|
|
49
|
-
{...props}
|
|
50
|
-
/>
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** The main body area of a Card for arbitrary content. */
|
|
55
|
-
function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
|
|
56
|
-
return <div data-slot="card-content" className={cn('px-6', className)} {...props} />;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** The bottom section of a Card, typically used for action buttons or metadata. */
|
|
60
|
-
function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
|
|
61
|
-
return (
|
|
62
|
-
<div data-slot="card-footer" className={cn('flex items-center px-6 [.border-t]:pt-6', className)} {...props} />
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent };
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
-
import { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext } from '@/components/ui/Carousel';
|
|
3
|
-
import { Card, CardContent } from '@/components/ui/Card';
|
|
4
|
-
|
|
5
|
-
const meta: Meta<typeof Carousel> = {
|
|
6
|
-
title: 'UI Components/Carousel',
|
|
7
|
-
component: Carousel,
|
|
8
|
-
};
|
|
9
|
-
export default meta;
|
|
10
|
-
type Story = StoryObj<typeof Carousel>;
|
|
11
|
-
|
|
12
|
-
/** Shows a basic carousel with one full-width slide visible at a time and navigation arrows. */
|
|
13
|
-
export const Default: Story = {
|
|
14
|
-
render: () => (
|
|
15
|
-
<div className="w-full max-w-xs mx-auto">
|
|
16
|
-
<Carousel>
|
|
17
|
-
<CarouselContent>
|
|
18
|
-
{Array.from({ length: 5 }).map((_, index) => (
|
|
19
|
-
<CarouselItem key={index}>
|
|
20
|
-
<Card>
|
|
21
|
-
<CardContent className="flex aspect-square items-center justify-center p-6">
|
|
22
|
-
<span className="text-4xl font-semibold">{index + 1}</span>
|
|
23
|
-
</CardContent>
|
|
24
|
-
</Card>
|
|
25
|
-
</CarouselItem>
|
|
26
|
-
))}
|
|
27
|
-
</CarouselContent>
|
|
28
|
-
<CarouselPrevious />
|
|
29
|
-
<CarouselNext />
|
|
30
|
-
</Carousel>
|
|
31
|
-
</div>
|
|
32
|
-
),
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/** Demonstrates showing three slides simultaneously using `basis-1/3` on each CarouselItem. */
|
|
36
|
-
export const ThirdWidth: Story = {
|
|
37
|
-
render: () => (
|
|
38
|
-
<div className="w-full max-w-sm mx-auto">
|
|
39
|
-
<Carousel>
|
|
40
|
-
<CarouselContent>
|
|
41
|
-
{Array.from({ length: 5 }).map((_, index) => (
|
|
42
|
-
<CarouselItem key={index} className="basis-1/3">
|
|
43
|
-
<Card>
|
|
44
|
-
<CardContent className="flex aspect-square items-center justify-center p-2">
|
|
45
|
-
<span className="text-2xl font-semibold">{index + 1}</span>
|
|
46
|
-
</CardContent>
|
|
47
|
-
</Card>
|
|
48
|
-
</CarouselItem>
|
|
49
|
-
))}
|
|
50
|
-
</CarouselContent>
|
|
51
|
-
<CarouselPrevious />
|
|
52
|
-
<CarouselNext />
|
|
53
|
-
</Carousel>
|
|
54
|
-
</div>
|
|
55
|
-
),
|
|
56
|
-
};
|