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
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
|
|
5
|
+
|
|
6
|
+
import { cn } from '../lib/utils';
|
|
7
|
+
import { buttonVariants } from './button';
|
|
8
|
+
|
|
9
|
+
const AlertDialog = AlertDialogPrimitive.Root;
|
|
10
|
+
|
|
11
|
+
const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
|
|
12
|
+
|
|
13
|
+
const AlertDialogPortal = AlertDialogPrimitive.Portal;
|
|
14
|
+
|
|
15
|
+
const AlertDialogOverlay = React.forwardRef<
|
|
16
|
+
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
|
17
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
|
18
|
+
>(({ className, ...props }, ref) => (
|
|
19
|
+
<AlertDialogPrimitive.Overlay
|
|
20
|
+
className={cn(
|
|
21
|
+
'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',
|
|
22
|
+
className
|
|
23
|
+
)}
|
|
24
|
+
{...props}
|
|
25
|
+
ref={ref}
|
|
26
|
+
/>
|
|
27
|
+
));
|
|
28
|
+
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
|
|
29
|
+
|
|
30
|
+
const AlertDialogContent = React.forwardRef<
|
|
31
|
+
React.ElementRef<typeof AlertDialogPrimitive.Content>,
|
|
32
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
|
|
33
|
+
>(({ className, ...props }, ref) => (
|
|
34
|
+
<AlertDialogPortal>
|
|
35
|
+
<AlertDialogOverlay />
|
|
36
|
+
<AlertDialogPrimitive.Content
|
|
37
|
+
ref={ref}
|
|
38
|
+
className={cn(
|
|
39
|
+
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg',
|
|
40
|
+
className
|
|
41
|
+
)}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
</AlertDialogPortal>
|
|
45
|
+
));
|
|
46
|
+
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
|
|
47
|
+
|
|
48
|
+
const AlertDialogHeader = ({
|
|
49
|
+
className,
|
|
50
|
+
...props
|
|
51
|
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
52
|
+
<div
|
|
53
|
+
className={cn(
|
|
54
|
+
'flex flex-col space-y-2 text-center sm:text-left',
|
|
55
|
+
className
|
|
56
|
+
)}
|
|
57
|
+
{...props}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
AlertDialogHeader.displayName = 'AlertDialogHeader';
|
|
61
|
+
|
|
62
|
+
const AlertDialogFooter = ({
|
|
63
|
+
className,
|
|
64
|
+
...props
|
|
65
|
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
66
|
+
<div
|
|
67
|
+
className={cn(
|
|
68
|
+
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
|
|
69
|
+
className
|
|
70
|
+
)}
|
|
71
|
+
{...props}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
AlertDialogFooter.displayName = 'AlertDialogFooter';
|
|
75
|
+
|
|
76
|
+
const AlertDialogTitle = React.forwardRef<
|
|
77
|
+
React.ElementRef<typeof AlertDialogPrimitive.Title>,
|
|
78
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
|
79
|
+
>(({ className, ...props }, ref) => (
|
|
80
|
+
<AlertDialogPrimitive.Title
|
|
81
|
+
ref={ref}
|
|
82
|
+
className={cn('text-lg font-semibold', className)}
|
|
83
|
+
{...props}
|
|
84
|
+
/>
|
|
85
|
+
));
|
|
86
|
+
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
|
|
87
|
+
|
|
88
|
+
const AlertDialogDescription = React.forwardRef<
|
|
89
|
+
React.ElementRef<typeof AlertDialogPrimitive.Description>,
|
|
90
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
|
91
|
+
>(({ className, ...props }, ref) => (
|
|
92
|
+
<AlertDialogPrimitive.Description
|
|
93
|
+
ref={ref}
|
|
94
|
+
className={cn('text-muted-foreground text-sm', className)}
|
|
95
|
+
{...props}
|
|
96
|
+
/>
|
|
97
|
+
));
|
|
98
|
+
AlertDialogDescription.displayName =
|
|
99
|
+
AlertDialogPrimitive.Description.displayName;
|
|
100
|
+
|
|
101
|
+
const AlertDialogAction = React.forwardRef<
|
|
102
|
+
React.ElementRef<typeof AlertDialogPrimitive.Action>,
|
|
103
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
|
|
104
|
+
>(({ className, ...props }, ref) => (
|
|
105
|
+
<AlertDialogPrimitive.Action
|
|
106
|
+
ref={ref}
|
|
107
|
+
className={cn(buttonVariants(), className)}
|
|
108
|
+
{...props}
|
|
109
|
+
/>
|
|
110
|
+
));
|
|
111
|
+
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
|
|
112
|
+
|
|
113
|
+
const AlertDialogCancel = React.forwardRef<
|
|
114
|
+
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
|
|
115
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
|
|
116
|
+
>(({ className, ...props }, ref) => (
|
|
117
|
+
<AlertDialogPrimitive.Cancel
|
|
118
|
+
ref={ref}
|
|
119
|
+
className={cn(
|
|
120
|
+
buttonVariants({ variant: 'outline' }),
|
|
121
|
+
'mt-2 sm:mt-0',
|
|
122
|
+
className
|
|
123
|
+
)}
|
|
124
|
+
{...props}
|
|
125
|
+
/>
|
|
126
|
+
));
|
|
127
|
+
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
|
|
128
|
+
|
|
129
|
+
export {
|
|
130
|
+
AlertDialog,
|
|
131
|
+
AlertDialogPortal,
|
|
132
|
+
AlertDialogOverlay,
|
|
133
|
+
AlertDialogTrigger,
|
|
134
|
+
AlertDialogContent,
|
|
135
|
+
AlertDialogHeader,
|
|
136
|
+
AlertDialogFooter,
|
|
137
|
+
AlertDialogTitle,
|
|
138
|
+
AlertDialogDescription,
|
|
139
|
+
AlertDialogAction,
|
|
140
|
+
AlertDialogCancel,
|
|
141
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '../story-types';
|
|
2
|
+
import { CalendarClock, TriangleAlert } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
import { Alert, AlertDescription, AlertTitle } from './alert';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Components/Alert',
|
|
8
|
+
component: Alert,
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
// `variant` (cva, alert.tsx) is the only real scalar prop. Alert composes an
|
|
11
|
+
// icon + AlertTitle + AlertDescription through `children`, so `children` is a
|
|
12
|
+
// slot and must never also be an argTypes key (SLOT_ARGTYPE_COLLISION).
|
|
13
|
+
argTypes: {
|
|
14
|
+
variant: { control: 'inline-radio', options: ['default', 'destructive'] },
|
|
15
|
+
},
|
|
16
|
+
args: { variant: 'default' },
|
|
17
|
+
parameters: { puck: { slots: ['children'] } },
|
|
18
|
+
} satisfies Meta<typeof Alert>;
|
|
19
|
+
|
|
20
|
+
export default meta;
|
|
21
|
+
type Story = StoryObj<typeof meta>;
|
|
22
|
+
|
|
23
|
+
export const Default: Story = {
|
|
24
|
+
render: (args) => (
|
|
25
|
+
<Alert {...args} className='w-96'>
|
|
26
|
+
<CalendarClock className='h-4 w-4' />
|
|
27
|
+
<AlertTitle>Sync complete</AlertTitle>
|
|
28
|
+
<AlertDescription>
|
|
29
|
+
Your Google Calendar is up to date as of a moment ago.
|
|
30
|
+
</AlertDescription>
|
|
31
|
+
</Alert>
|
|
32
|
+
),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const Destructive: Story = {
|
|
36
|
+
args: { variant: 'destructive' },
|
|
37
|
+
render: (args) => (
|
|
38
|
+
<Alert {...args} className='w-96'>
|
|
39
|
+
<TriangleAlert className='h-4 w-4' />
|
|
40
|
+
<AlertTitle>Booking conflict</AlertTitle>
|
|
41
|
+
<AlertDescription>
|
|
42
|
+
This slot overlaps an existing appointment. Pick another time.
|
|
43
|
+
</AlertDescription>
|
|
44
|
+
</Alert>
|
|
45
|
+
),
|
|
46
|
+
};
|
package/src/ui/alert.tsx
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
|
|
4
|
+
import { cn } from '../lib/utils';
|
|
5
|
+
|
|
6
|
+
const alertVariants = cva(
|
|
7
|
+
'relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7',
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: 'bg-background text-foreground',
|
|
12
|
+
destructive:
|
|
13
|
+
'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
variant: 'default',
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const Alert = React.forwardRef<
|
|
23
|
+
HTMLDivElement,
|
|
24
|
+
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
|
25
|
+
>(({ className, variant, ...props }, ref) => (
|
|
26
|
+
<div
|
|
27
|
+
ref={ref}
|
|
28
|
+
role='alert'
|
|
29
|
+
className={cn(alertVariants({ variant }), className)}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
));
|
|
33
|
+
Alert.displayName = 'Alert';
|
|
34
|
+
|
|
35
|
+
const AlertTitle = React.forwardRef<
|
|
36
|
+
HTMLParagraphElement,
|
|
37
|
+
React.HTMLAttributes<HTMLHeadingElement>
|
|
38
|
+
>(({ className, ...props }, ref) => (
|
|
39
|
+
<h5
|
|
40
|
+
ref={ref}
|
|
41
|
+
className={cn('mb-1 leading-none font-medium tracking-tight', className)}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
));
|
|
45
|
+
AlertTitle.displayName = 'AlertTitle';
|
|
46
|
+
|
|
47
|
+
const AlertDescription = React.forwardRef<
|
|
48
|
+
HTMLParagraphElement,
|
|
49
|
+
React.HTMLAttributes<HTMLParagraphElement>
|
|
50
|
+
>(({ className, ...props }, ref) => (
|
|
51
|
+
<div
|
|
52
|
+
ref={ref}
|
|
53
|
+
className={cn('text-sm [&_p]:leading-relaxed', className)}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
));
|
|
57
|
+
AlertDescription.displayName = 'AlertDescription';
|
|
58
|
+
|
|
59
|
+
export { Alert, AlertTitle, AlertDescription };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '../story-types';
|
|
2
|
+
|
|
3
|
+
import { Avatar, AvatarFallback, AvatarImage } from './avatar';
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Components/Avatar',
|
|
7
|
+
component: Avatar,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
// Radix's Avatar root (`AvatarProps extends PrimitiveSpanProps`) declares no
|
|
10
|
+
// props of its own: `src`/`alt` belong to AvatarImage and the initials belong
|
|
11
|
+
// to AvatarFallback — both composed through the `children` slot (§3). The one
|
|
12
|
+
// real, forwarded scalar left on the root is its accessible name.
|
|
13
|
+
// `className`/`style` stay unexposed (§6).
|
|
14
|
+
argTypes: {
|
|
15
|
+
'aria-label': {
|
|
16
|
+
control: 'text',
|
|
17
|
+
description: 'Accessible name for the avatar',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
parameters: { puck: { slots: ['children'] } },
|
|
21
|
+
} satisfies Meta<typeof Avatar>;
|
|
22
|
+
|
|
23
|
+
export default meta;
|
|
24
|
+
type Story = StoryObj<typeof meta>;
|
|
25
|
+
|
|
26
|
+
export const Default: Story = {
|
|
27
|
+
args: { 'aria-label': 'Dr. Lopez' },
|
|
28
|
+
render: (args) => (
|
|
29
|
+
<Avatar {...args}>
|
|
30
|
+
<AvatarImage src='https://i.pravatar.cc/80?img=12' alt='Dr. Lopez' />
|
|
31
|
+
<AvatarFallback>DL</AvatarFallback>
|
|
32
|
+
</Avatar>
|
|
33
|
+
),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const Fallback: Story = {
|
|
37
|
+
args: { 'aria-label': 'Jane Doe' },
|
|
38
|
+
render: (args) => (
|
|
39
|
+
<Avatar {...args}>
|
|
40
|
+
<AvatarFallback>JD</AvatarFallback>
|
|
41
|
+
</Avatar>
|
|
42
|
+
),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const Group: Story = {
|
|
46
|
+
render: () => (
|
|
47
|
+
<div className='flex -space-x-2'>
|
|
48
|
+
{['1', '5', '9', '12'].map((i) => (
|
|
49
|
+
<Avatar key={i} className='ring-background ring-2'>
|
|
50
|
+
<AvatarImage src={`https://i.pravatar.cc/80?img=${i}`} alt='' />
|
|
51
|
+
<AvatarFallback>U{i}</AvatarFallback>
|
|
52
|
+
</Avatar>
|
|
53
|
+
))}
|
|
54
|
+
</div>
|
|
55
|
+
),
|
|
56
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
|
5
|
+
|
|
6
|
+
import { cn } from '../lib/utils';
|
|
7
|
+
|
|
8
|
+
const Avatar = React.forwardRef<
|
|
9
|
+
React.ElementRef<typeof AvatarPrimitive.Root>,
|
|
10
|
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
|
11
|
+
>(({ className, ...props }, ref) => (
|
|
12
|
+
<AvatarPrimitive.Root
|
|
13
|
+
ref={ref}
|
|
14
|
+
className={cn(
|
|
15
|
+
'relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full',
|
|
16
|
+
className
|
|
17
|
+
)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
));
|
|
21
|
+
Avatar.displayName = AvatarPrimitive.Root.displayName;
|
|
22
|
+
|
|
23
|
+
const AvatarImage = React.forwardRef<
|
|
24
|
+
React.ElementRef<typeof AvatarPrimitive.Image>,
|
|
25
|
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
|
26
|
+
>(({ className, ...props }, ref) => (
|
|
27
|
+
<AvatarPrimitive.Image
|
|
28
|
+
ref={ref}
|
|
29
|
+
className={cn('aspect-square h-full w-full', className)}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
));
|
|
33
|
+
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
|
|
34
|
+
|
|
35
|
+
const AvatarFallback = React.forwardRef<
|
|
36
|
+
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
|
37
|
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
|
38
|
+
>(({ className, ...props }, ref) => (
|
|
39
|
+
<AvatarPrimitive.Fallback
|
|
40
|
+
ref={ref}
|
|
41
|
+
className={cn(
|
|
42
|
+
'bg-muted flex h-full w-full items-center justify-center rounded-full',
|
|
43
|
+
className
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
));
|
|
48
|
+
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
|
|
49
|
+
|
|
50
|
+
export { Avatar, AvatarImage, AvatarFallback };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '../story-types';
|
|
2
|
+
import { Check, Clock, TriangleAlert, Video } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
import { Badge, BadgeDot } from './badge';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Components/Badge',
|
|
8
|
+
component: Badge,
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
// Leaf label component. `variant` options are read straight off badgeVariants'
|
|
11
|
+
// cva config; `children` is the label — an editable string here, NOT a slot
|
|
12
|
+
// (§5: a name may never be both an argType and a slot).
|
|
13
|
+
argTypes: {
|
|
14
|
+
children: { control: 'text', description: 'Badge label' },
|
|
15
|
+
variant: {
|
|
16
|
+
control: 'select',
|
|
17
|
+
options: [
|
|
18
|
+
'default',
|
|
19
|
+
'secondary',
|
|
20
|
+
'destructive',
|
|
21
|
+
'outline',
|
|
22
|
+
'info',
|
|
23
|
+
'success',
|
|
24
|
+
'warning',
|
|
25
|
+
'danger',
|
|
26
|
+
'teal',
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
args: { children: 'Confirmed', variant: 'default' },
|
|
31
|
+
} satisfies Meta<typeof Badge>;
|
|
32
|
+
|
|
33
|
+
export default meta;
|
|
34
|
+
type Story = StoryObj<typeof meta>;
|
|
35
|
+
|
|
36
|
+
export const Default: Story = {};
|
|
37
|
+
|
|
38
|
+
export const Solid: Story = {
|
|
39
|
+
render: () => (
|
|
40
|
+
<div className='flex flex-wrap items-center gap-2'>
|
|
41
|
+
<Badge>Pro</Badge>
|
|
42
|
+
<Badge variant='secondary'>Secondary</Badge>
|
|
43
|
+
<Badge variant='destructive'>Conflict</Badge>
|
|
44
|
+
<Badge variant='outline'>Virtual</Badge>
|
|
45
|
+
</div>
|
|
46
|
+
),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const SoftColors: Story = {
|
|
50
|
+
render: () => (
|
|
51
|
+
<div className='flex flex-wrap items-center gap-2'>
|
|
52
|
+
<Badge variant='info'>Calendar group</Badge>
|
|
53
|
+
<Badge variant='success'>Synced</Badge>
|
|
54
|
+
<Badge variant='warning'>Tentative</Badge>
|
|
55
|
+
<Badge variant='danger'>Conflict</Badge>
|
|
56
|
+
<Badge variant='teal'>42 available</Badge>
|
|
57
|
+
</div>
|
|
58
|
+
),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const WithIcons: Story = {
|
|
62
|
+
render: () => (
|
|
63
|
+
<div className='flex flex-wrap items-center gap-2'>
|
|
64
|
+
<Badge variant='success'>
|
|
65
|
+
<Check />
|
|
66
|
+
Confirmed
|
|
67
|
+
</Badge>
|
|
68
|
+
<Badge variant='warning'>
|
|
69
|
+
<Clock />
|
|
70
|
+
Pending
|
|
71
|
+
</Badge>
|
|
72
|
+
<Badge variant='danger'>
|
|
73
|
+
<TriangleAlert />
|
|
74
|
+
Conflict
|
|
75
|
+
</Badge>
|
|
76
|
+
<Badge variant='outline'>
|
|
77
|
+
<Video />
|
|
78
|
+
Telehealth
|
|
79
|
+
</Badge>
|
|
80
|
+
</div>
|
|
81
|
+
),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const WithStatusDot: Story = {
|
|
85
|
+
render: () => (
|
|
86
|
+
<div className='flex flex-wrap items-center gap-2'>
|
|
87
|
+
<Badge variant='success'>
|
|
88
|
+
<BadgeDot />
|
|
89
|
+
Synced
|
|
90
|
+
</Badge>
|
|
91
|
+
<Badge variant='warning'>
|
|
92
|
+
<BadgeDot />
|
|
93
|
+
Tentative
|
|
94
|
+
</Badge>
|
|
95
|
+
<Badge variant='danger'>
|
|
96
|
+
<BadgeDot />
|
|
97
|
+
Conflict
|
|
98
|
+
</Badge>
|
|
99
|
+
<Badge variant='info'>
|
|
100
|
+
<BadgeDot />
|
|
101
|
+
Booked
|
|
102
|
+
</Badge>
|
|
103
|
+
</div>
|
|
104
|
+
),
|
|
105
|
+
};
|
package/src/ui/badge.tsx
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
|
|
4
|
+
import { cn } from '../lib/utils';
|
|
5
|
+
|
|
6
|
+
const badgeVariants = cva(
|
|
7
|
+
'inline-flex items-center gap-1 rounded-full border px-2.5 py-0.5 text-xs font-medium leading-normal transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 [&>svg]:size-3 [&>svg]:shrink-0',
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
// Solid
|
|
12
|
+
default:
|
|
13
|
+
'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80',
|
|
14
|
+
secondary:
|
|
15
|
+
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
16
|
+
destructive:
|
|
17
|
+
'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
|
|
18
|
+
outline: 'text-foreground',
|
|
19
|
+
// Soft tints — scheduling / status palette
|
|
20
|
+
info: 'border-transparent bg-vinta-50 text-vinta-700',
|
|
21
|
+
success: 'border-transparent bg-green-100 text-success',
|
|
22
|
+
warning: 'border-transparent bg-amber-100 text-warning',
|
|
23
|
+
danger: 'border-transparent bg-red-100 text-destructive',
|
|
24
|
+
teal: 'border-transparent bg-teal-100 text-teal-700',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
defaultVariants: {
|
|
28
|
+
variant: 'default',
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export interface BadgeProps
|
|
34
|
+
extends
|
|
35
|
+
React.HTMLAttributes<HTMLDivElement>,
|
|
36
|
+
VariantProps<typeof badgeVariants> {}
|
|
37
|
+
|
|
38
|
+
function Badge({ className, variant, ...props }: BadgeProps) {
|
|
39
|
+
return (
|
|
40
|
+
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Small leading status dot — pair with a soft Badge variant. */
|
|
45
|
+
function BadgeDot({
|
|
46
|
+
className,
|
|
47
|
+
...props
|
|
48
|
+
}: React.HTMLAttributes<HTMLSpanElement>) {
|
|
49
|
+
return (
|
|
50
|
+
<span
|
|
51
|
+
className={cn('size-1.5 shrink-0 rounded-full bg-current', className)}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { Badge, BadgeDot, badgeVariants };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '../story-types';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Breadcrumb,
|
|
5
|
+
BreadcrumbItem,
|
|
6
|
+
BreadcrumbLink,
|
|
7
|
+
BreadcrumbList,
|
|
8
|
+
BreadcrumbPage,
|
|
9
|
+
BreadcrumbSeparator,
|
|
10
|
+
} from './breadcrumb';
|
|
11
|
+
|
|
12
|
+
const meta = {
|
|
13
|
+
title: 'Components/Breadcrumb',
|
|
14
|
+
component: Breadcrumb,
|
|
15
|
+
tags: ['autodocs'],
|
|
16
|
+
// The Breadcrumb root had no scalar prop of its own (its declared
|
|
17
|
+
// `separator?: ReactNode` was never rendered — it leaked onto the <nav>). It
|
|
18
|
+
// is now a real, rendered `separator` variant shared with every childless
|
|
19
|
+
// BreadcrumbSeparator through context. `children` (the trail) is a slot.
|
|
20
|
+
argTypes: {
|
|
21
|
+
separator: {
|
|
22
|
+
control: 'inline-radio',
|
|
23
|
+
options: ['chevron', 'slash', 'dot'],
|
|
24
|
+
description: 'Glyph rendered between crumbs',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
args: { separator: 'chevron' },
|
|
28
|
+
parameters: { puck: { slots: ['children'] } },
|
|
29
|
+
} satisfies Meta<typeof Breadcrumb>;
|
|
30
|
+
|
|
31
|
+
export default meta;
|
|
32
|
+
type Story = StoryObj<typeof meta>;
|
|
33
|
+
|
|
34
|
+
export const Default: Story = {
|
|
35
|
+
render: (args) => (
|
|
36
|
+
<Breadcrumb {...args}>
|
|
37
|
+
<BreadcrumbList>
|
|
38
|
+
<BreadcrumbItem>
|
|
39
|
+
<BreadcrumbLink href='#'>Calendars</BreadcrumbLink>
|
|
40
|
+
</BreadcrumbItem>
|
|
41
|
+
<BreadcrumbSeparator />
|
|
42
|
+
<BreadcrumbItem>
|
|
43
|
+
<BreadcrumbLink href='#'>Cardiology</BreadcrumbLink>
|
|
44
|
+
</BreadcrumbItem>
|
|
45
|
+
<BreadcrumbSeparator />
|
|
46
|
+
<BreadcrumbItem>
|
|
47
|
+
<BreadcrumbPage>Dr. Lopez</BreadcrumbPage>
|
|
48
|
+
</BreadcrumbItem>
|
|
49
|
+
</BreadcrumbList>
|
|
50
|
+
</Breadcrumb>
|
|
51
|
+
),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const SlashSeparator: Story = {
|
|
55
|
+
args: { separator: 'slash' },
|
|
56
|
+
render: (args) => (
|
|
57
|
+
<Breadcrumb {...args}>
|
|
58
|
+
<BreadcrumbList>
|
|
59
|
+
<BreadcrumbItem>
|
|
60
|
+
<BreadcrumbLink href='#'>Calendars</BreadcrumbLink>
|
|
61
|
+
</BreadcrumbItem>
|
|
62
|
+
<BreadcrumbSeparator />
|
|
63
|
+
<BreadcrumbItem>
|
|
64
|
+
<BreadcrumbPage>Cardiology</BreadcrumbPage>
|
|
65
|
+
</BreadcrumbItem>
|
|
66
|
+
</BreadcrumbList>
|
|
67
|
+
</Breadcrumb>
|
|
68
|
+
),
|
|
69
|
+
};
|