vinta-schedule-design-system 0.1.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/components.json +21 -0
- package/dist/styles.css +3452 -0
- package/package.json +103 -0
- package/src/layout/app-shell.tsx +115 -0
- package/src/layout/app-topbar.stories.tsx +74 -0
- package/src/layout/app-topbar.tsx +145 -0
- package/src/layout/auth-layout.tsx +45 -0
- package/src/layout/box.stories.tsx +86 -0
- package/src/layout/box.tsx +34 -0
- package/src/layout/center.tsx +24 -0
- package/src/layout/container.stories.tsx +44 -0
- package/src/layout/container.tsx +39 -0
- package/src/layout/divider.tsx +64 -0
- package/src/layout/flex.stories.tsx +109 -0
- package/src/layout/flex.test.tsx +71 -0
- package/src/layout/flex.tsx +112 -0
- package/src/layout/grid.stories.tsx +91 -0
- package/src/layout/grid.tsx +132 -0
- package/src/layout/heading.tsx +53 -0
- package/src/layout/index.ts +47 -0
- package/src/layout/layout-style.ts +291 -0
- package/src/layout/link.stories.tsx +54 -0
- package/src/layout/link.test.tsx +75 -0
- package/src/layout/link.tsx +100 -0
- package/src/layout/navbar.stories.tsx +70 -0
- package/src/layout/navbar.tsx +128 -0
- package/src/layout/page-header.stories.tsx +80 -0
- package/src/layout/page-header.tsx +59 -0
- package/src/layout/section.tsx +22 -0
- package/src/layout/spacer.tsx +21 -0
- package/src/layout/stack.stories.tsx +58 -0
- package/src/layout/stack.tsx +19 -0
- package/src/layout/text.tsx +139 -0
- package/src/layout/typography.stories.tsx +112 -0
- package/src/lib/utils.ts +6 -0
- package/src/story-types.ts +80 -0
- package/src/styles/build.css +33 -0
- package/src/styles/globals.css +9 -0
- package/src/styles/tokens.css +281 -0
- package/src/ui/accordion.stories.tsx +84 -0
- package/src/ui/accordion.tsx +57 -0
- package/src/ui/alert-dialog.stories.tsx +57 -0
- package/src/ui/alert-dialog.tsx +141 -0
- package/src/ui/alert.stories.tsx +46 -0
- package/src/ui/alert.tsx +59 -0
- package/src/ui/avatar.stories.tsx +56 -0
- package/src/ui/avatar.tsx +50 -0
- package/src/ui/badge.stories.tsx +105 -0
- package/src/ui/badge.tsx +57 -0
- package/src/ui/breadcrumb.stories.tsx +69 -0
- package/src/ui/breadcrumb.tsx +140 -0
- package/src/ui/button.stories.tsx +85 -0
- package/src/ui/button.tsx +60 -0
- package/src/ui/card.stories.tsx +62 -0
- package/src/ui/card.tsx +96 -0
- package/src/ui/carousel.stories.tsx +55 -0
- package/src/ui/carousel.tsx +262 -0
- package/src/ui/checkbox.stories.tsx +52 -0
- package/src/ui/checkbox.tsx +30 -0
- package/src/ui/combobox.stories.tsx +138 -0
- package/src/ui/combobox.tsx +217 -0
- package/src/ui/command.tsx +184 -0
- package/src/ui/dialog.stories.tsx +66 -0
- package/src/ui/dialog.tsx +163 -0
- package/src/ui/drawer.stories.tsx +80 -0
- package/src/ui/drawer.tsx +118 -0
- package/src/ui/dropdown-menu.stories.tsx +57 -0
- package/src/ui/dropdown-menu.tsx +201 -0
- package/src/ui/form.stories.tsx +70 -0
- package/src/ui/form.tsx +181 -0
- package/src/ui/input-otp.stories.tsx +52 -0
- package/src/ui/input-otp.tsx +71 -0
- package/src/ui/input.stories.tsx +68 -0
- package/src/ui/input.tsx +22 -0
- package/src/ui/label.stories.tsx +35 -0
- package/src/ui/label.tsx +26 -0
- package/src/ui/navigation-menu.stories.tsx +79 -0
- package/src/ui/navigation-menu.tsx +128 -0
- package/src/ui/pagination.stories.tsx +62 -0
- package/src/ui/pagination.tsx +137 -0
- package/src/ui/popover.tsx +89 -0
- package/src/ui/progress.stories.tsx +33 -0
- package/src/ui/progress.tsx +28 -0
- package/src/ui/radio-group.stories.tsx +52 -0
- package/src/ui/radio-group.tsx +44 -0
- package/src/ui/scheduling-chip.stories.tsx +117 -0
- package/src/ui/scheduling-chip.tsx +62 -0
- package/src/ui/scroll-area.stories.tsx +52 -0
- package/src/ui/scroll-area.tsx +48 -0
- package/src/ui/select.stories.tsx +56 -0
- package/src/ui/select.tsx +159 -0
- package/src/ui/sheet.stories.tsx +63 -0
- package/src/ui/sheet.tsx +141 -0
- package/src/ui/skeleton.stories.tsx +40 -0
- package/src/ui/skeleton.tsx +15 -0
- package/src/ui/sonner.stories.tsx +62 -0
- package/src/ui/sonner.tsx +34 -0
- package/src/ui/switch.stories.tsx +41 -0
- package/src/ui/switch.tsx +29 -0
- package/src/ui/table.stories.tsx +88 -0
- package/src/ui/table.tsx +132 -0
- package/src/ui/tabs.stories.tsx +63 -0
- package/src/ui/tabs.tsx +55 -0
- package/src/ui/textarea.stories.tsx +45 -0
- package/src/ui/textarea.tsx +22 -0
- package/src/ui/tooltip.stories.tsx +48 -0
- package/src/ui/tooltip.tsx +32 -0
package/src/ui/sheet.tsx
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import * as SheetPrimitive from '@radix-ui/react-dialog';
|
|
5
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
6
|
+
import { X } from 'lucide-react';
|
|
7
|
+
|
|
8
|
+
import { cn } from '../lib/utils';
|
|
9
|
+
|
|
10
|
+
const Sheet = SheetPrimitive.Root;
|
|
11
|
+
|
|
12
|
+
const SheetTrigger = SheetPrimitive.Trigger;
|
|
13
|
+
|
|
14
|
+
const SheetClose = SheetPrimitive.Close;
|
|
15
|
+
|
|
16
|
+
const SheetPortal = SheetPrimitive.Portal;
|
|
17
|
+
|
|
18
|
+
const SheetOverlay = React.forwardRef<
|
|
19
|
+
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
|
20
|
+
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
|
21
|
+
>(({ className, ...props }, ref) => (
|
|
22
|
+
<SheetPrimitive.Overlay
|
|
23
|
+
className={cn(
|
|
24
|
+
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80',
|
|
25
|
+
className
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
ref={ref}
|
|
29
|
+
/>
|
|
30
|
+
));
|
|
31
|
+
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
|
|
32
|
+
|
|
33
|
+
const sheetVariants = cva(
|
|
34
|
+
'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out',
|
|
35
|
+
{
|
|
36
|
+
variants: {
|
|
37
|
+
side: {
|
|
38
|
+
top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
|
|
39
|
+
bottom:
|
|
40
|
+
'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
|
|
41
|
+
left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
|
|
42
|
+
right:
|
|
43
|
+
'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
defaultVariants: {
|
|
47
|
+
side: 'right',
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
interface SheetContentProps
|
|
53
|
+
extends
|
|
54
|
+
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
|
55
|
+
VariantProps<typeof sheetVariants> {}
|
|
56
|
+
|
|
57
|
+
const SheetContent = React.forwardRef<
|
|
58
|
+
React.ElementRef<typeof SheetPrimitive.Content>,
|
|
59
|
+
SheetContentProps
|
|
60
|
+
>(({ side = 'right', className, children, ...props }, ref) => (
|
|
61
|
+
<SheetPortal>
|
|
62
|
+
<SheetOverlay />
|
|
63
|
+
<SheetPrimitive.Content
|
|
64
|
+
ref={ref}
|
|
65
|
+
className={cn(sheetVariants({ side }), className)}
|
|
66
|
+
{...props}
|
|
67
|
+
>
|
|
68
|
+
<SheetPrimitive.Close className='ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:pointer-events-none'>
|
|
69
|
+
<X className='h-4 w-4' />
|
|
70
|
+
<span className='sr-only'>Close</span>
|
|
71
|
+
</SheetPrimitive.Close>
|
|
72
|
+
{children}
|
|
73
|
+
</SheetPrimitive.Content>
|
|
74
|
+
</SheetPortal>
|
|
75
|
+
));
|
|
76
|
+
SheetContent.displayName = SheetPrimitive.Content.displayName;
|
|
77
|
+
|
|
78
|
+
const SheetHeader = ({
|
|
79
|
+
className,
|
|
80
|
+
...props
|
|
81
|
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
82
|
+
<div
|
|
83
|
+
className={cn(
|
|
84
|
+
'flex flex-col space-y-2 text-center sm:text-left',
|
|
85
|
+
className
|
|
86
|
+
)}
|
|
87
|
+
{...props}
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
SheetHeader.displayName = 'SheetHeader';
|
|
91
|
+
|
|
92
|
+
const SheetFooter = ({
|
|
93
|
+
className,
|
|
94
|
+
...props
|
|
95
|
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
96
|
+
<div
|
|
97
|
+
className={cn(
|
|
98
|
+
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
|
|
99
|
+
className
|
|
100
|
+
)}
|
|
101
|
+
{...props}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
SheetFooter.displayName = 'SheetFooter';
|
|
105
|
+
|
|
106
|
+
const SheetTitle = React.forwardRef<
|
|
107
|
+
React.ElementRef<typeof SheetPrimitive.Title>,
|
|
108
|
+
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
|
109
|
+
>(({ className, ...props }, ref) => (
|
|
110
|
+
<SheetPrimitive.Title
|
|
111
|
+
ref={ref}
|
|
112
|
+
className={cn('text-foreground text-lg font-semibold', className)}
|
|
113
|
+
{...props}
|
|
114
|
+
/>
|
|
115
|
+
));
|
|
116
|
+
SheetTitle.displayName = SheetPrimitive.Title.displayName;
|
|
117
|
+
|
|
118
|
+
const SheetDescription = React.forwardRef<
|
|
119
|
+
React.ElementRef<typeof SheetPrimitive.Description>,
|
|
120
|
+
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
|
121
|
+
>(({ className, ...props }, ref) => (
|
|
122
|
+
<SheetPrimitive.Description
|
|
123
|
+
ref={ref}
|
|
124
|
+
className={cn('text-muted-foreground text-sm', className)}
|
|
125
|
+
{...props}
|
|
126
|
+
/>
|
|
127
|
+
));
|
|
128
|
+
SheetDescription.displayName = SheetPrimitive.Description.displayName;
|
|
129
|
+
|
|
130
|
+
export {
|
|
131
|
+
Sheet,
|
|
132
|
+
SheetPortal,
|
|
133
|
+
SheetOverlay,
|
|
134
|
+
SheetTrigger,
|
|
135
|
+
SheetClose,
|
|
136
|
+
SheetContent,
|
|
137
|
+
SheetHeader,
|
|
138
|
+
SheetFooter,
|
|
139
|
+
SheetTitle,
|
|
140
|
+
SheetDescription,
|
|
141
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '../story-types';
|
|
2
|
+
|
|
3
|
+
import { Skeleton } from './skeleton';
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Components/Skeleton',
|
|
7
|
+
component: Skeleton,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
// Skeleton is a bare styled div (`React.HTMLAttributes<HTMLDivElement>`) — it
|
|
10
|
+
// has NO domain prop of its own: size/shape are driven purely by className,
|
|
11
|
+
// which must never be exposed (§6). Nothing was fabricated; the only real,
|
|
12
|
+
// forwarded scalar worth editing is the accessible loading label. It renders
|
|
13
|
+
// no composed content → no slot (§4).
|
|
14
|
+
argTypes: {
|
|
15
|
+
'aria-label': {
|
|
16
|
+
control: 'text',
|
|
17
|
+
description: 'Accessible label announced while content loads',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
args: { 'aria-label': 'Loading' },
|
|
21
|
+
} satisfies Meta<typeof Skeleton>;
|
|
22
|
+
|
|
23
|
+
export default meta;
|
|
24
|
+
type Story = StoryObj<typeof meta>;
|
|
25
|
+
|
|
26
|
+
export const Default: Story = {
|
|
27
|
+
render: (args) => <Skeleton className='h-6 w-48' {...args} />,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const Card: Story = {
|
|
31
|
+
render: () => (
|
|
32
|
+
<div className='flex w-80 items-center gap-4'>
|
|
33
|
+
<Skeleton className='h-12 w-12 rounded-full' />
|
|
34
|
+
<div className='flex-1 space-y-2'>
|
|
35
|
+
<Skeleton className='h-4 w-3/4' />
|
|
36
|
+
<Skeleton className='h-4 w-1/2' />
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
),
|
|
40
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { cn } from '../lib/utils';
|
|
2
|
+
|
|
3
|
+
function Skeleton({
|
|
4
|
+
className,
|
|
5
|
+
...props
|
|
6
|
+
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
7
|
+
return (
|
|
8
|
+
<div
|
|
9
|
+
className={cn('bg-primary/10 animate-pulse rounded-md', className)}
|
|
10
|
+
{...props}
|
|
11
|
+
/>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { Skeleton };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '../story-types';
|
|
2
|
+
import { toast } from 'sonner';
|
|
3
|
+
|
|
4
|
+
import { Toaster } from './sonner';
|
|
5
|
+
import { Button } from './button';
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
// Title leaf must match the real named export — the component is `Toaster`,
|
|
9
|
+
// so the old 'Components/Sonner (Toast)' leaf was invalid (§7).
|
|
10
|
+
title: 'Components/Toaster',
|
|
11
|
+
component: Toaster,
|
|
12
|
+
tags: ['autodocs'],
|
|
13
|
+
// Leaf: sonner's ToasterProps. It renders the toast viewport, never composed
|
|
14
|
+
// children → no slot (§4). `className`/`style` are excluded (§6).
|
|
15
|
+
argTypes: {
|
|
16
|
+
position: {
|
|
17
|
+
control: 'select',
|
|
18
|
+
options: [
|
|
19
|
+
'top-left',
|
|
20
|
+
'top-center',
|
|
21
|
+
'top-right',
|
|
22
|
+
'bottom-left',
|
|
23
|
+
'bottom-center',
|
|
24
|
+
'bottom-right',
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
theme: { control: 'inline-radio', options: ['light', 'dark', 'system'] },
|
|
28
|
+
richColors: { control: 'boolean' },
|
|
29
|
+
closeButton: { control: 'boolean' },
|
|
30
|
+
expand: { control: 'boolean' },
|
|
31
|
+
duration: { control: 'number', description: 'Toast lifetime in ms' },
|
|
32
|
+
},
|
|
33
|
+
args: { position: 'bottom-right', richColors: false, closeButton: false },
|
|
34
|
+
} satisfies Meta<typeof Toaster>;
|
|
35
|
+
|
|
36
|
+
export default meta;
|
|
37
|
+
type Story = StoryObj<typeof meta>;
|
|
38
|
+
|
|
39
|
+
export const Default: Story = {
|
|
40
|
+
render: (args) => (
|
|
41
|
+
<div className='flex flex-wrap gap-3'>
|
|
42
|
+
<Button onClick={() => toast('Booking saved')}>Default</Button>
|
|
43
|
+
<Button
|
|
44
|
+
variant='outline'
|
|
45
|
+
onClick={() =>
|
|
46
|
+
toast.success('Appointment confirmed', {
|
|
47
|
+
description: 'Mon, 9:00–9:30 AM with Dr. Lopez',
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
>
|
|
51
|
+
Success
|
|
52
|
+
</Button>
|
|
53
|
+
<Button
|
|
54
|
+
variant='destructive'
|
|
55
|
+
onClick={() => toast.error('Sync failed — retrying')}
|
|
56
|
+
>
|
|
57
|
+
Error
|
|
58
|
+
</Button>
|
|
59
|
+
<Toaster {...args} />
|
|
60
|
+
</div>
|
|
61
|
+
),
|
|
62
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useTheme } from 'next-themes';
|
|
4
|
+
import { Toaster as Sonner } from 'sonner';
|
|
5
|
+
|
|
6
|
+
type ToasterProps = React.ComponentProps<typeof Sonner>;
|
|
7
|
+
|
|
8
|
+
const Toaster = ({ ...props }: ToasterProps) => {
|
|
9
|
+
const { theme = 'system' } = useTheme();
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<Sonner
|
|
13
|
+
theme={theme as ToasterProps['theme']}
|
|
14
|
+
className='toaster group'
|
|
15
|
+
toastOptions={{
|
|
16
|
+
classNames: {
|
|
17
|
+
toast:
|
|
18
|
+
'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
|
|
19
|
+
description: 'group-[.toast]:text-muted-foreground',
|
|
20
|
+
actionButton:
|
|
21
|
+
'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
|
|
22
|
+
cancelButton:
|
|
23
|
+
'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',
|
|
24
|
+
},
|
|
25
|
+
}}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
// Explicit: bundlers rename this binding (it collides with sonner's own
|
|
31
|
+
// `Toaster`), so the inferred function name cannot be relied on for resolution.
|
|
32
|
+
Toaster.displayName = 'Toaster';
|
|
33
|
+
|
|
34
|
+
export { Toaster };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '../story-types';
|
|
2
|
+
|
|
3
|
+
import { Switch } from './switch';
|
|
4
|
+
import { Label } from './label';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Components/Switch',
|
|
8
|
+
component: Switch,
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
// Leaf control: Radix `Switch.Root` renders its own Thumb, so it takes no
|
|
11
|
+
// composed content — NO slot. Real Radix Root props only (§6).
|
|
12
|
+
argTypes: {
|
|
13
|
+
defaultChecked: { control: 'boolean' },
|
|
14
|
+
disabled: { control: 'boolean' },
|
|
15
|
+
required: { control: 'boolean' },
|
|
16
|
+
name: { control: 'text', description: 'Name submitted with the form' },
|
|
17
|
+
value: { control: 'text', description: 'Value submitted when checked' },
|
|
18
|
+
},
|
|
19
|
+
args: { defaultChecked: true },
|
|
20
|
+
} satisfies Meta<typeof Switch>;
|
|
21
|
+
|
|
22
|
+
export default meta;
|
|
23
|
+
type Story = StoryObj<typeof meta>;
|
|
24
|
+
|
|
25
|
+
export const Default: Story = {
|
|
26
|
+
render: (args) => (
|
|
27
|
+
<div className='flex items-center gap-2'>
|
|
28
|
+
<Switch id='avail' {...args} />
|
|
29
|
+
<Label htmlFor='avail'>Accepting bookings</Label>
|
|
30
|
+
</div>
|
|
31
|
+
),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const Disabled: Story = {
|
|
35
|
+
render: () => (
|
|
36
|
+
<div className='flex items-center gap-2'>
|
|
37
|
+
<Switch id='dis' disabled />
|
|
38
|
+
<Label htmlFor='dis'>Disabled</Label>
|
|
39
|
+
</div>
|
|
40
|
+
),
|
|
41
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import * as SwitchPrimitives from '@radix-ui/react-switch';
|
|
5
|
+
|
|
6
|
+
import { cn } from '../lib/utils';
|
|
7
|
+
|
|
8
|
+
const Switch = React.forwardRef<
|
|
9
|
+
React.ElementRef<typeof SwitchPrimitives.Root>,
|
|
10
|
+
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
|
11
|
+
>(({ className, ...props }, ref) => (
|
|
12
|
+
<SwitchPrimitives.Root
|
|
13
|
+
className={cn(
|
|
14
|
+
'peer focus-visible:ring-ring focus-visible:ring-offset-background data-[state=checked]:bg-primary data-[state=unchecked]:bg-input inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
|
15
|
+
className
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
ref={ref}
|
|
19
|
+
>
|
|
20
|
+
<SwitchPrimitives.Thumb
|
|
21
|
+
className={cn(
|
|
22
|
+
'bg-background pointer-events-none block h-4 w-4 rounded-full shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0'
|
|
23
|
+
)}
|
|
24
|
+
/>
|
|
25
|
+
</SwitchPrimitives.Root>
|
|
26
|
+
));
|
|
27
|
+
Switch.displayName = SwitchPrimitives.Root.displayName;
|
|
28
|
+
|
|
29
|
+
export { Switch };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '../story-types';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Table,
|
|
5
|
+
TableHeader,
|
|
6
|
+
TableBody,
|
|
7
|
+
TableRow,
|
|
8
|
+
TableHead,
|
|
9
|
+
TableCell,
|
|
10
|
+
TableCaption,
|
|
11
|
+
TableFooter,
|
|
12
|
+
} from './table';
|
|
13
|
+
|
|
14
|
+
const meta = {
|
|
15
|
+
// Leaf normalised to the exported component name and to the 'Components/*'
|
|
16
|
+
// namespace the rest of the package uses (was 'UI/Table').
|
|
17
|
+
title: 'Components/Table',
|
|
18
|
+
component: Table,
|
|
19
|
+
tags: ['autodocs'],
|
|
20
|
+
// Table is a plain <table> (`React.HTMLAttributes<HTMLTableElement>`) — it has
|
|
21
|
+
// no design-system prop of its own. Nothing was invented: the curated keys are
|
|
22
|
+
// real, forwarded HTML attributes. The rows/headers are `children` → slot (§4).
|
|
23
|
+
argTypes: {
|
|
24
|
+
'aria-label': {
|
|
25
|
+
control: 'text',
|
|
26
|
+
description: 'Accessible name for the table',
|
|
27
|
+
},
|
|
28
|
+
id: { control: 'text', description: 'DOM id on the <table> element' },
|
|
29
|
+
},
|
|
30
|
+
parameters: { puck: { slots: ['children'] } },
|
|
31
|
+
} satisfies Meta<typeof Table>;
|
|
32
|
+
|
|
33
|
+
export default meta;
|
|
34
|
+
type Story = StoryObj<typeof meta>;
|
|
35
|
+
|
|
36
|
+
export const Default: Story = {
|
|
37
|
+
args: { 'aria-label': 'Recent invoices' },
|
|
38
|
+
render: (args) => (
|
|
39
|
+
<div className='p-6'>
|
|
40
|
+
<Table {...args}>
|
|
41
|
+
<TableCaption>A list of recent invoices.</TableCaption>
|
|
42
|
+
<TableHeader>
|
|
43
|
+
<TableRow>
|
|
44
|
+
<TableHead>Invoice</TableHead>
|
|
45
|
+
<TableHead>Status</TableHead>
|
|
46
|
+
<TableHead>Method</TableHead>
|
|
47
|
+
<TableHead className='text-right'>Amount</TableHead>
|
|
48
|
+
</TableRow>
|
|
49
|
+
</TableHeader>
|
|
50
|
+
<TableBody>
|
|
51
|
+
{[
|
|
52
|
+
{
|
|
53
|
+
id: 'INV001',
|
|
54
|
+
status: 'Paid',
|
|
55
|
+
method: 'Credit Card',
|
|
56
|
+
amount: '$250.00',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 'INV002',
|
|
60
|
+
status: 'Pending',
|
|
61
|
+
method: 'PayPal',
|
|
62
|
+
amount: '$150.00',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'INV003',
|
|
66
|
+
status: 'Unpaid',
|
|
67
|
+
method: 'Bank Transfer',
|
|
68
|
+
amount: '$350.00',
|
|
69
|
+
},
|
|
70
|
+
].map((invoice) => (
|
|
71
|
+
<TableRow key={invoice.id}>
|
|
72
|
+
<TableCell className='font-medium'>{invoice.id}</TableCell>
|
|
73
|
+
<TableCell>{invoice.status}</TableCell>
|
|
74
|
+
<TableCell>{invoice.method}</TableCell>
|
|
75
|
+
<TableCell className='text-right'>{invoice.amount}</TableCell>
|
|
76
|
+
</TableRow>
|
|
77
|
+
))}
|
|
78
|
+
</TableBody>
|
|
79
|
+
<TableFooter>
|
|
80
|
+
<TableRow>
|
|
81
|
+
<TableCell colSpan={3}>Total</TableCell>
|
|
82
|
+
<TableCell className='text-right'>$750.00</TableCell>
|
|
83
|
+
</TableRow>
|
|
84
|
+
</TableFooter>
|
|
85
|
+
</Table>
|
|
86
|
+
</div>
|
|
87
|
+
),
|
|
88
|
+
};
|
package/src/ui/table.tsx
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../lib/utils';
|
|
4
|
+
|
|
5
|
+
const Table = React.forwardRef<
|
|
6
|
+
HTMLTableElement,
|
|
7
|
+
React.HTMLAttributes<HTMLTableElement>
|
|
8
|
+
>(({ className, ...props }, ref) => (
|
|
9
|
+
<div data-slot='table-wrapper' className='relative w-full overflow-auto'>
|
|
10
|
+
<table
|
|
11
|
+
ref={ref}
|
|
12
|
+
data-slot='table'
|
|
13
|
+
className={cn('w-full caption-bottom text-sm', className)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
</div>
|
|
17
|
+
));
|
|
18
|
+
Table.displayName = 'Table';
|
|
19
|
+
|
|
20
|
+
const TableHeader = React.forwardRef<
|
|
21
|
+
HTMLTableSectionElement,
|
|
22
|
+
React.HTMLAttributes<HTMLTableSectionElement>
|
|
23
|
+
>(({ className, ...props }, ref) => (
|
|
24
|
+
<thead
|
|
25
|
+
ref={ref}
|
|
26
|
+
data-slot='table-header'
|
|
27
|
+
className={cn('[&_tr]:border-b', className)}
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
));
|
|
31
|
+
TableHeader.displayName = 'TableHeader';
|
|
32
|
+
|
|
33
|
+
const TableBody = React.forwardRef<
|
|
34
|
+
HTMLTableSectionElement,
|
|
35
|
+
React.HTMLAttributes<HTMLTableSectionElement>
|
|
36
|
+
>(({ className, ...props }, ref) => (
|
|
37
|
+
<tbody
|
|
38
|
+
ref={ref}
|
|
39
|
+
data-slot='table-body'
|
|
40
|
+
className={cn('[&_tr:last-child]:border-0', className)}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
));
|
|
44
|
+
TableBody.displayName = 'TableBody';
|
|
45
|
+
|
|
46
|
+
const TableFooter = React.forwardRef<
|
|
47
|
+
HTMLTableSectionElement,
|
|
48
|
+
React.HTMLAttributes<HTMLTableSectionElement>
|
|
49
|
+
>(({ className, ...props }, ref) => (
|
|
50
|
+
<tfoot
|
|
51
|
+
ref={ref}
|
|
52
|
+
data-slot='table-footer'
|
|
53
|
+
className={cn(
|
|
54
|
+
'bg-muted/50 border-t font-medium [&>tr]:last:border-b-0',
|
|
55
|
+
className
|
|
56
|
+
)}
|
|
57
|
+
{...props}
|
|
58
|
+
/>
|
|
59
|
+
));
|
|
60
|
+
TableFooter.displayName = 'TableFooter';
|
|
61
|
+
|
|
62
|
+
const TableRow = React.forwardRef<
|
|
63
|
+
HTMLTableRowElement,
|
|
64
|
+
React.HTMLAttributes<HTMLTableRowElement>
|
|
65
|
+
>(({ className, ...props }, ref) => (
|
|
66
|
+
<tr
|
|
67
|
+
ref={ref}
|
|
68
|
+
data-slot='table-row'
|
|
69
|
+
className={cn(
|
|
70
|
+
'hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors',
|
|
71
|
+
className
|
|
72
|
+
)}
|
|
73
|
+
{...props}
|
|
74
|
+
/>
|
|
75
|
+
));
|
|
76
|
+
TableRow.displayName = 'TableRow';
|
|
77
|
+
|
|
78
|
+
const TableHead = React.forwardRef<
|
|
79
|
+
HTMLTableCellElement,
|
|
80
|
+
React.ThHTMLAttributes<HTMLTableCellElement>
|
|
81
|
+
>(({ className, ...props }, ref) => (
|
|
82
|
+
<th
|
|
83
|
+
ref={ref}
|
|
84
|
+
data-slot='table-head'
|
|
85
|
+
className={cn(
|
|
86
|
+
'text-muted-foreground h-10 px-2 text-left align-middle font-medium [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
|
|
87
|
+
className
|
|
88
|
+
)}
|
|
89
|
+
{...props}
|
|
90
|
+
/>
|
|
91
|
+
));
|
|
92
|
+
TableHead.displayName = 'TableHead';
|
|
93
|
+
|
|
94
|
+
const TableCell = React.forwardRef<
|
|
95
|
+
HTMLTableCellElement,
|
|
96
|
+
React.TdHTMLAttributes<HTMLTableCellElement>
|
|
97
|
+
>(({ className, ...props }, ref) => (
|
|
98
|
+
<td
|
|
99
|
+
ref={ref}
|
|
100
|
+
data-slot='table-cell'
|
|
101
|
+
className={cn(
|
|
102
|
+
'p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
|
|
103
|
+
className
|
|
104
|
+
)}
|
|
105
|
+
{...props}
|
|
106
|
+
/>
|
|
107
|
+
));
|
|
108
|
+
TableCell.displayName = 'TableCell';
|
|
109
|
+
|
|
110
|
+
const TableCaption = React.forwardRef<
|
|
111
|
+
HTMLTableCaptionElement,
|
|
112
|
+
React.HTMLAttributes<HTMLTableCaptionElement>
|
|
113
|
+
>(({ className, ...props }, ref) => (
|
|
114
|
+
<caption
|
|
115
|
+
ref={ref}
|
|
116
|
+
data-slot='table-caption'
|
|
117
|
+
className={cn('text-muted-foreground mt-4 text-sm', className)}
|
|
118
|
+
{...props}
|
|
119
|
+
/>
|
|
120
|
+
));
|
|
121
|
+
TableCaption.displayName = 'TableCaption';
|
|
122
|
+
|
|
123
|
+
export {
|
|
124
|
+
Table,
|
|
125
|
+
TableHeader,
|
|
126
|
+
TableBody,
|
|
127
|
+
TableFooter,
|
|
128
|
+
TableRow,
|
|
129
|
+
TableHead,
|
|
130
|
+
TableCell,
|
|
131
|
+
TableCaption,
|
|
132
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '../story-types';
|
|
2
|
+
|
|
3
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from './tabs';
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Components/Tabs',
|
|
7
|
+
component: Tabs,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
// Radix Tabs.Root scalars (`value` / `onValueChange` are controlled-mode
|
|
10
|
+
// only). `children` (TabsList + TabsContent) is composed content → a slot.
|
|
11
|
+
argTypes: {
|
|
12
|
+
defaultValue: {
|
|
13
|
+
control: 'text',
|
|
14
|
+
description: 'Value of the tab selected on first render',
|
|
15
|
+
},
|
|
16
|
+
orientation: {
|
|
17
|
+
control: 'inline-radio',
|
|
18
|
+
options: ['horizontal', 'vertical'],
|
|
19
|
+
},
|
|
20
|
+
dir: { control: 'inline-radio', options: ['ltr', 'rtl'] },
|
|
21
|
+
activationMode: {
|
|
22
|
+
control: 'inline-radio',
|
|
23
|
+
options: ['automatic', 'manual'],
|
|
24
|
+
description: 'Select a tab on focus, or only on click/Enter',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
args: {
|
|
28
|
+
defaultValue: 'upcoming',
|
|
29
|
+
orientation: 'horizontal',
|
|
30
|
+
activationMode: 'automatic',
|
|
31
|
+
},
|
|
32
|
+
parameters: { puck: { slots: ['children'] } },
|
|
33
|
+
} satisfies Meta<typeof Tabs>;
|
|
34
|
+
|
|
35
|
+
export default meta;
|
|
36
|
+
type Story = StoryObj<typeof meta>;
|
|
37
|
+
|
|
38
|
+
export const Default: Story = {
|
|
39
|
+
render: (args) => (
|
|
40
|
+
<Tabs {...args} className='w-96'>
|
|
41
|
+
<TabsList className='grid w-full grid-cols-3'>
|
|
42
|
+
<TabsTrigger value='upcoming'>Upcoming</TabsTrigger>
|
|
43
|
+
<TabsTrigger value='past'>Past</TabsTrigger>
|
|
44
|
+
<TabsTrigger value='canceled'>Canceled</TabsTrigger>
|
|
45
|
+
</TabsList>
|
|
46
|
+
<TabsContent
|
|
47
|
+
value='upcoming'
|
|
48
|
+
className='text-muted-foreground pt-3 text-sm'
|
|
49
|
+
>
|
|
50
|
+
3 upcoming appointments this week.
|
|
51
|
+
</TabsContent>
|
|
52
|
+
<TabsContent value='past' className='text-muted-foreground pt-3 text-sm'>
|
|
53
|
+
12 appointments in the last 30 days.
|
|
54
|
+
</TabsContent>
|
|
55
|
+
<TabsContent
|
|
56
|
+
value='canceled'
|
|
57
|
+
className='text-muted-foreground pt-3 text-sm'
|
|
58
|
+
>
|
|
59
|
+
No cancellations. Nice.
|
|
60
|
+
</TabsContent>
|
|
61
|
+
</Tabs>
|
|
62
|
+
),
|
|
63
|
+
};
|