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.
Files changed (107) hide show
  1. package/components.json +21 -0
  2. package/dist/styles.css +3452 -0
  3. package/package.json +103 -0
  4. package/src/layout/app-shell.tsx +115 -0
  5. package/src/layout/app-topbar.stories.tsx +74 -0
  6. package/src/layout/app-topbar.tsx +145 -0
  7. package/src/layout/auth-layout.tsx +45 -0
  8. package/src/layout/box.stories.tsx +86 -0
  9. package/src/layout/box.tsx +34 -0
  10. package/src/layout/center.tsx +24 -0
  11. package/src/layout/container.stories.tsx +44 -0
  12. package/src/layout/container.tsx +39 -0
  13. package/src/layout/divider.tsx +64 -0
  14. package/src/layout/flex.stories.tsx +109 -0
  15. package/src/layout/flex.test.tsx +71 -0
  16. package/src/layout/flex.tsx +112 -0
  17. package/src/layout/grid.stories.tsx +91 -0
  18. package/src/layout/grid.tsx +132 -0
  19. package/src/layout/heading.tsx +53 -0
  20. package/src/layout/index.ts +47 -0
  21. package/src/layout/layout-style.ts +291 -0
  22. package/src/layout/link.stories.tsx +54 -0
  23. package/src/layout/link.test.tsx +75 -0
  24. package/src/layout/link.tsx +100 -0
  25. package/src/layout/navbar.stories.tsx +70 -0
  26. package/src/layout/navbar.tsx +128 -0
  27. package/src/layout/page-header.stories.tsx +80 -0
  28. package/src/layout/page-header.tsx +59 -0
  29. package/src/layout/section.tsx +22 -0
  30. package/src/layout/spacer.tsx +21 -0
  31. package/src/layout/stack.stories.tsx +58 -0
  32. package/src/layout/stack.tsx +19 -0
  33. package/src/layout/text.tsx +139 -0
  34. package/src/layout/typography.stories.tsx +112 -0
  35. package/src/lib/utils.ts +6 -0
  36. package/src/story-types.ts +80 -0
  37. package/src/styles/build.css +33 -0
  38. package/src/styles/globals.css +9 -0
  39. package/src/styles/tokens.css +281 -0
  40. package/src/ui/accordion.stories.tsx +84 -0
  41. package/src/ui/accordion.tsx +57 -0
  42. package/src/ui/alert-dialog.stories.tsx +57 -0
  43. package/src/ui/alert-dialog.tsx +141 -0
  44. package/src/ui/alert.stories.tsx +46 -0
  45. package/src/ui/alert.tsx +59 -0
  46. package/src/ui/avatar.stories.tsx +56 -0
  47. package/src/ui/avatar.tsx +50 -0
  48. package/src/ui/badge.stories.tsx +105 -0
  49. package/src/ui/badge.tsx +57 -0
  50. package/src/ui/breadcrumb.stories.tsx +69 -0
  51. package/src/ui/breadcrumb.tsx +140 -0
  52. package/src/ui/button.stories.tsx +85 -0
  53. package/src/ui/button.tsx +60 -0
  54. package/src/ui/card.stories.tsx +62 -0
  55. package/src/ui/card.tsx +96 -0
  56. package/src/ui/carousel.stories.tsx +55 -0
  57. package/src/ui/carousel.tsx +262 -0
  58. package/src/ui/checkbox.stories.tsx +52 -0
  59. package/src/ui/checkbox.tsx +30 -0
  60. package/src/ui/combobox.stories.tsx +138 -0
  61. package/src/ui/combobox.tsx +217 -0
  62. package/src/ui/command.tsx +184 -0
  63. package/src/ui/dialog.stories.tsx +66 -0
  64. package/src/ui/dialog.tsx +163 -0
  65. package/src/ui/drawer.stories.tsx +80 -0
  66. package/src/ui/drawer.tsx +118 -0
  67. package/src/ui/dropdown-menu.stories.tsx +57 -0
  68. package/src/ui/dropdown-menu.tsx +201 -0
  69. package/src/ui/form.stories.tsx +70 -0
  70. package/src/ui/form.tsx +181 -0
  71. package/src/ui/input-otp.stories.tsx +52 -0
  72. package/src/ui/input-otp.tsx +71 -0
  73. package/src/ui/input.stories.tsx +68 -0
  74. package/src/ui/input.tsx +22 -0
  75. package/src/ui/label.stories.tsx +35 -0
  76. package/src/ui/label.tsx +26 -0
  77. package/src/ui/navigation-menu.stories.tsx +79 -0
  78. package/src/ui/navigation-menu.tsx +128 -0
  79. package/src/ui/pagination.stories.tsx +62 -0
  80. package/src/ui/pagination.tsx +137 -0
  81. package/src/ui/popover.tsx +89 -0
  82. package/src/ui/progress.stories.tsx +33 -0
  83. package/src/ui/progress.tsx +28 -0
  84. package/src/ui/radio-group.stories.tsx +52 -0
  85. package/src/ui/radio-group.tsx +44 -0
  86. package/src/ui/scheduling-chip.stories.tsx +117 -0
  87. package/src/ui/scheduling-chip.tsx +62 -0
  88. package/src/ui/scroll-area.stories.tsx +52 -0
  89. package/src/ui/scroll-area.tsx +48 -0
  90. package/src/ui/select.stories.tsx +56 -0
  91. package/src/ui/select.tsx +159 -0
  92. package/src/ui/sheet.stories.tsx +63 -0
  93. package/src/ui/sheet.tsx +141 -0
  94. package/src/ui/skeleton.stories.tsx +40 -0
  95. package/src/ui/skeleton.tsx +15 -0
  96. package/src/ui/sonner.stories.tsx +62 -0
  97. package/src/ui/sonner.tsx +34 -0
  98. package/src/ui/switch.stories.tsx +41 -0
  99. package/src/ui/switch.tsx +29 -0
  100. package/src/ui/table.stories.tsx +88 -0
  101. package/src/ui/table.tsx +132 -0
  102. package/src/ui/tabs.stories.tsx +63 -0
  103. package/src/ui/tabs.tsx +55 -0
  104. package/src/ui/textarea.stories.tsx +45 -0
  105. package/src/ui/textarea.tsx +22 -0
  106. package/src/ui/tooltip.stories.tsx +48 -0
  107. 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
+ };
@@ -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
+ };
@@ -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
+ };