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,44 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
|
|
5
|
+
import { Circle } from 'lucide-react';
|
|
6
|
+
|
|
7
|
+
import { cn } from '../lib/utils';
|
|
8
|
+
|
|
9
|
+
const RadioGroup = React.forwardRef<
|
|
10
|
+
React.ElementRef<typeof RadioGroupPrimitive.Root>,
|
|
11
|
+
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
|
|
12
|
+
>(({ className, ...props }, ref) => {
|
|
13
|
+
return (
|
|
14
|
+
<RadioGroupPrimitive.Root
|
|
15
|
+
className={cn('grid gap-2', className)}
|
|
16
|
+
{...props}
|
|
17
|
+
ref={ref}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
|
|
22
|
+
|
|
23
|
+
const RadioGroupItem = React.forwardRef<
|
|
24
|
+
React.ElementRef<typeof RadioGroupPrimitive.Item>,
|
|
25
|
+
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
|
|
26
|
+
>(({ className, ...props }, ref) => {
|
|
27
|
+
return (
|
|
28
|
+
<RadioGroupPrimitive.Item
|
|
29
|
+
ref={ref}
|
|
30
|
+
className={cn(
|
|
31
|
+
'border-primary text-primary focus-visible:ring-ring aspect-square h-4 w-4 rounded-full border shadow focus:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50',
|
|
32
|
+
className
|
|
33
|
+
)}
|
|
34
|
+
{...props}
|
|
35
|
+
>
|
|
36
|
+
<RadioGroupPrimitive.Indicator className='flex items-center justify-center'>
|
|
37
|
+
<Circle className='fill-primary h-3.5 w-3.5' />
|
|
38
|
+
</RadioGroupPrimitive.Indicator>
|
|
39
|
+
</RadioGroupPrimitive.Item>
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
|
|
43
|
+
|
|
44
|
+
export { RadioGroup, RadioGroupItem };
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '../story-types';
|
|
2
|
+
|
|
3
|
+
import { SchedulingChip } from './scheduling-chip';
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Components/SchedulingChip',
|
|
7
|
+
component: SchedulingChip,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
// Leaf label component: `status` options come from schedulingChipVariants' cva
|
|
10
|
+
// config; `title`/`meta` are ReactNode props but read as text here (§5) — so
|
|
11
|
+
// they are argTypes, not slots.
|
|
12
|
+
argTypes: {
|
|
13
|
+
status: {
|
|
14
|
+
control: 'select',
|
|
15
|
+
options: ['booked', 'available', 'tentative', 'conflict'],
|
|
16
|
+
},
|
|
17
|
+
title: { control: 'text', description: 'Primary chip label' },
|
|
18
|
+
meta: { control: 'text', description: 'Secondary line under the title' },
|
|
19
|
+
},
|
|
20
|
+
args: {
|
|
21
|
+
status: 'booked',
|
|
22
|
+
title: 'Prenatal intake',
|
|
23
|
+
meta: 'Dr. Pires · Room 2',
|
|
24
|
+
},
|
|
25
|
+
} satisfies Meta<typeof SchedulingChip>;
|
|
26
|
+
|
|
27
|
+
export default meta;
|
|
28
|
+
type Story = StoryObj<typeof meta>;
|
|
29
|
+
|
|
30
|
+
export const Default: Story = {
|
|
31
|
+
render: (args) => <SchedulingChip className='w-56' {...args} />,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const States: Story = {
|
|
35
|
+
render: () => (
|
|
36
|
+
<div className='flex w-56 flex-col gap-2'>
|
|
37
|
+
<SchedulingChip
|
|
38
|
+
status='booked'
|
|
39
|
+
title='Prenatal intake'
|
|
40
|
+
meta='Dr. Pires · Room 2'
|
|
41
|
+
/>
|
|
42
|
+
<SchedulingChip
|
|
43
|
+
status='available'
|
|
44
|
+
title='Available'
|
|
45
|
+
meta='Open for booking'
|
|
46
|
+
/>
|
|
47
|
+
<SchedulingChip
|
|
48
|
+
status='tentative'
|
|
49
|
+
title='Hold — follow-up'
|
|
50
|
+
meta='Pending confirmation'
|
|
51
|
+
/>
|
|
52
|
+
<SchedulingChip
|
|
53
|
+
status='conflict'
|
|
54
|
+
title='Double-booked'
|
|
55
|
+
meta='2 calendars overlap'
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const HOURS = [
|
|
62
|
+
{
|
|
63
|
+
t: '9 AM',
|
|
64
|
+
node: (
|
|
65
|
+
<SchedulingChip
|
|
66
|
+
status='booked'
|
|
67
|
+
title='Prenatal intake'
|
|
68
|
+
meta='Dr. Pires · Room 2'
|
|
69
|
+
/>
|
|
70
|
+
),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
t: '10 AM',
|
|
74
|
+
node: (
|
|
75
|
+
<SchedulingChip
|
|
76
|
+
status='available'
|
|
77
|
+
title='Available'
|
|
78
|
+
meta='Open for booking'
|
|
79
|
+
/>
|
|
80
|
+
),
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
t: '11 AM',
|
|
84
|
+
node: (
|
|
85
|
+
<SchedulingChip
|
|
86
|
+
status='tentative'
|
|
87
|
+
title='Hold — follow-up'
|
|
88
|
+
meta='Pending confirmation'
|
|
89
|
+
/>
|
|
90
|
+
),
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
t: '12 PM',
|
|
94
|
+
node: (
|
|
95
|
+
<SchedulingChip
|
|
96
|
+
status='conflict'
|
|
97
|
+
title='Double-booked'
|
|
98
|
+
meta='2 calendars overlap'
|
|
99
|
+
/>
|
|
100
|
+
),
|
|
101
|
+
},
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
export const InCalendar: Story = {
|
|
105
|
+
render: () => (
|
|
106
|
+
<div className='grid w-80 grid-cols-[44px_1fr] gap-x-2.5'>
|
|
107
|
+
{HOURS.map(({ t, node }) => (
|
|
108
|
+
<div key={t} className='contents'>
|
|
109
|
+
<div className='text-muted-foreground pt-1 text-right font-mono text-[11px]'>
|
|
110
|
+
{t}
|
|
111
|
+
</div>
|
|
112
|
+
<div className='border-border border-t py-1'>{node}</div>
|
|
113
|
+
</div>
|
|
114
|
+
))}
|
|
115
|
+
</div>
|
|
116
|
+
),
|
|
117
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
|
|
4
|
+
import { cn } from '../lib/utils';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Scheduling chip — a calendar event block whose color carries its state:
|
|
8
|
+
* booked / available / tentative / conflict. Mirrors the Vinta Schedule
|
|
9
|
+
* design system's "scheduling chips" (left accent bar + soft tint).
|
|
10
|
+
*/
|
|
11
|
+
const schedulingChipVariants = cva(
|
|
12
|
+
'flex flex-col justify-center gap-0.5 rounded-md border-l-[3px] px-2.5 py-1.5 text-xs font-medium',
|
|
13
|
+
{
|
|
14
|
+
variants: {
|
|
15
|
+
status: {
|
|
16
|
+
booked:
|
|
17
|
+
'border-vinta-600 bg-vinta-50 text-vinta-800 dark:border-vinta-400 dark:bg-vinta-950 dark:text-vinta-200',
|
|
18
|
+
available:
|
|
19
|
+
'border-teal-600 bg-teal-100 text-teal-700 dark:border-teal-400 dark:bg-teal-950 dark:text-teal-300',
|
|
20
|
+
tentative:
|
|
21
|
+
'border-warning bg-amber-100 text-warning dark:border-amber-400 dark:bg-amber-950 dark:text-amber-300',
|
|
22
|
+
conflict:
|
|
23
|
+
'border-destructive bg-red-100 text-destructive dark:border-red-400 dark:bg-red-950 dark:text-red-300',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
defaultVariants: {
|
|
27
|
+
status: 'booked',
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
export interface SchedulingChipProps
|
|
33
|
+
extends
|
|
34
|
+
Omit<React.HTMLAttributes<HTMLDivElement>, 'title'>,
|
|
35
|
+
VariantProps<typeof schedulingChipVariants> {
|
|
36
|
+
title: React.ReactNode;
|
|
37
|
+
meta?: React.ReactNode;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function SchedulingChip({
|
|
41
|
+
className,
|
|
42
|
+
status,
|
|
43
|
+
title,
|
|
44
|
+
meta,
|
|
45
|
+
...props
|
|
46
|
+
}: SchedulingChipProps) {
|
|
47
|
+
return (
|
|
48
|
+
<div
|
|
49
|
+
className={cn(schedulingChipVariants({ status }), className)}
|
|
50
|
+
{...props}
|
|
51
|
+
>
|
|
52
|
+
<span className='truncate leading-tight'>{title}</span>
|
|
53
|
+
{meta ? (
|
|
54
|
+
<span className='truncate text-[11px] font-normal opacity-80'>
|
|
55
|
+
{meta}
|
|
56
|
+
</span>
|
|
57
|
+
) : null}
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { SchedulingChip, schedulingChipVariants };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '../story-types';
|
|
2
|
+
|
|
3
|
+
import { ScrollArea } from './scroll-area';
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Components/ScrollArea',
|
|
7
|
+
component: ScrollArea,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
// Radix ScrollAreaProps: `type`, `dir`, `scrollHideDelay`. Container — the
|
|
10
|
+
// scrollable content is `children` → slot (§4), so it is not an argType (§5).
|
|
11
|
+
argTypes: {
|
|
12
|
+
type: {
|
|
13
|
+
control: 'select',
|
|
14
|
+
options: ['auto', 'always', 'scroll', 'hover'],
|
|
15
|
+
description: 'When the scrollbars are visible',
|
|
16
|
+
},
|
|
17
|
+
dir: { control: 'inline-radio', options: ['ltr', 'rtl'] },
|
|
18
|
+
scrollHideDelay: {
|
|
19
|
+
control: 'number',
|
|
20
|
+
description: 'ms before scrollbars hide (type="scroll"/"hover")',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
args: { type: 'hover' },
|
|
24
|
+
parameters: { puck: { slots: ['children'] } },
|
|
25
|
+
} satisfies Meta<typeof ScrollArea>;
|
|
26
|
+
|
|
27
|
+
export default meta;
|
|
28
|
+
type Story = StoryObj<typeof meta>;
|
|
29
|
+
|
|
30
|
+
const timeSlots = Array.from({ length: 24 }, (_, i) => {
|
|
31
|
+
const h = 8 + Math.floor(i / 2);
|
|
32
|
+
const m = i % 2 === 0 ? '00' : '30';
|
|
33
|
+
return `${String(h).padStart(2, '0')}:${m}`;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export const Default: Story = {
|
|
37
|
+
render: (args) => (
|
|
38
|
+
<ScrollArea className='h-64 w-56 rounded-md border' {...args}>
|
|
39
|
+
<div className='p-3'>
|
|
40
|
+
<p className='mb-2 text-sm font-medium'>Available slots</p>
|
|
41
|
+
{timeSlots.map((s) => (
|
|
42
|
+
<div
|
|
43
|
+
key={s}
|
|
44
|
+
className='hover:bg-accent rounded px-2 py-1.5 font-mono text-sm'
|
|
45
|
+
>
|
|
46
|
+
{s}
|
|
47
|
+
</div>
|
|
48
|
+
))}
|
|
49
|
+
</div>
|
|
50
|
+
</ScrollArea>
|
|
51
|
+
),
|
|
52
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
|
|
5
|
+
|
|
6
|
+
import { cn } from '../lib/utils';
|
|
7
|
+
|
|
8
|
+
const ScrollArea = React.forwardRef<
|
|
9
|
+
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
|
10
|
+
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
|
11
|
+
>(({ className, children, ...props }, ref) => (
|
|
12
|
+
<ScrollAreaPrimitive.Root
|
|
13
|
+
ref={ref}
|
|
14
|
+
className={cn('relative overflow-hidden', className)}
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<ScrollAreaPrimitive.Viewport className='h-full w-full rounded-[inherit]'>
|
|
18
|
+
{children}
|
|
19
|
+
</ScrollAreaPrimitive.Viewport>
|
|
20
|
+
<ScrollBar />
|
|
21
|
+
<ScrollAreaPrimitive.Corner />
|
|
22
|
+
</ScrollAreaPrimitive.Root>
|
|
23
|
+
));
|
|
24
|
+
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
|
|
25
|
+
|
|
26
|
+
const ScrollBar = React.forwardRef<
|
|
27
|
+
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
|
28
|
+
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
|
29
|
+
>(({ className, orientation = 'vertical', ...props }, ref) => (
|
|
30
|
+
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
|
31
|
+
ref={ref}
|
|
32
|
+
orientation={orientation}
|
|
33
|
+
className={cn(
|
|
34
|
+
'flex touch-none transition-colors select-none',
|
|
35
|
+
orientation === 'vertical' &&
|
|
36
|
+
'h-full w-2.5 border-l border-l-transparent p-[1px]',
|
|
37
|
+
orientation === 'horizontal' &&
|
|
38
|
+
'h-2.5 flex-col border-t border-t-transparent p-[1px]',
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
<ScrollAreaPrimitive.ScrollAreaThumb className='bg-border relative flex-1 rounded-full' />
|
|
44
|
+
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
|
45
|
+
));
|
|
46
|
+
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
|
|
47
|
+
|
|
48
|
+
export { ScrollArea, ScrollBar };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '../story-types';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Select,
|
|
5
|
+
SelectContent,
|
|
6
|
+
SelectGroup,
|
|
7
|
+
SelectItem,
|
|
8
|
+
SelectLabel,
|
|
9
|
+
SelectTrigger,
|
|
10
|
+
SelectValue,
|
|
11
|
+
} from './select';
|
|
12
|
+
|
|
13
|
+
const meta = {
|
|
14
|
+
title: 'Components/Select',
|
|
15
|
+
component: Select,
|
|
16
|
+
tags: ['autodocs'],
|
|
17
|
+
// Container: `Select` (Radix Select.Root) renders composed children — the
|
|
18
|
+
// trigger and the content list — so `children` is a slot and must not also be
|
|
19
|
+
// an argType (§5). argTypes below are real Select.Root props (§6).
|
|
20
|
+
argTypes: {
|
|
21
|
+
defaultValue: {
|
|
22
|
+
control: 'text',
|
|
23
|
+
description: 'Value of the item selected by default',
|
|
24
|
+
},
|
|
25
|
+
defaultOpen: {
|
|
26
|
+
control: 'boolean',
|
|
27
|
+
description: 'Open the menu on mount (uncontrolled)',
|
|
28
|
+
},
|
|
29
|
+
disabled: { control: 'boolean' },
|
|
30
|
+
required: { control: 'boolean' },
|
|
31
|
+
name: { control: 'text', description: 'Name submitted with the form' },
|
|
32
|
+
dir: { control: 'inline-radio', options: ['ltr', 'rtl'] },
|
|
33
|
+
},
|
|
34
|
+
parameters: { puck: { slots: ['children'] } },
|
|
35
|
+
} satisfies Meta<typeof Select>;
|
|
36
|
+
|
|
37
|
+
export default meta;
|
|
38
|
+
type Story = StoryObj<typeof meta>;
|
|
39
|
+
|
|
40
|
+
export const Default: Story = {
|
|
41
|
+
render: (args) => (
|
|
42
|
+
<Select {...args}>
|
|
43
|
+
<SelectTrigger className='w-64'>
|
|
44
|
+
<SelectValue placeholder='Pick a calendar' />
|
|
45
|
+
</SelectTrigger>
|
|
46
|
+
<SelectContent>
|
|
47
|
+
<SelectGroup>
|
|
48
|
+
<SelectLabel>Connected calendars</SelectLabel>
|
|
49
|
+
<SelectItem value='google'>Google Calendar</SelectItem>
|
|
50
|
+
<SelectItem value='outlook'>Outlook</SelectItem>
|
|
51
|
+
<SelectItem value='ical'>Apple iCloud</SelectItem>
|
|
52
|
+
</SelectGroup>
|
|
53
|
+
</SelectContent>
|
|
54
|
+
</Select>
|
|
55
|
+
),
|
|
56
|
+
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import * as SelectPrimitive from '@radix-ui/react-select';
|
|
5
|
+
import { Check, ChevronDown, ChevronUp } from 'lucide-react';
|
|
6
|
+
|
|
7
|
+
import { cn } from '../lib/utils';
|
|
8
|
+
|
|
9
|
+
const Select = SelectPrimitive.Root;
|
|
10
|
+
|
|
11
|
+
const SelectGroup = SelectPrimitive.Group;
|
|
12
|
+
|
|
13
|
+
const SelectValue = SelectPrimitive.Value;
|
|
14
|
+
|
|
15
|
+
const SelectTrigger = React.forwardRef<
|
|
16
|
+
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
|
17
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
|
18
|
+
>(({ className, children, ...props }, ref) => (
|
|
19
|
+
<SelectPrimitive.Trigger
|
|
20
|
+
ref={ref}
|
|
21
|
+
className={cn(
|
|
22
|
+
'border-input ring-offset-background data-[placeholder]:text-muted-foreground focus:ring-ring flex h-9 w-full items-center justify-between rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-sm focus:ring-1 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
|
|
23
|
+
className
|
|
24
|
+
)}
|
|
25
|
+
{...props}
|
|
26
|
+
>
|
|
27
|
+
{children}
|
|
28
|
+
<SelectPrimitive.Icon asChild>
|
|
29
|
+
<ChevronDown className='h-4 w-4 opacity-50' />
|
|
30
|
+
</SelectPrimitive.Icon>
|
|
31
|
+
</SelectPrimitive.Trigger>
|
|
32
|
+
));
|
|
33
|
+
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
|
34
|
+
|
|
35
|
+
const SelectScrollUpButton = React.forwardRef<
|
|
36
|
+
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
|
37
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
|
38
|
+
>(({ className, ...props }, ref) => (
|
|
39
|
+
<SelectPrimitive.ScrollUpButton
|
|
40
|
+
ref={ref}
|
|
41
|
+
className={cn(
|
|
42
|
+
'flex cursor-default items-center justify-center py-1',
|
|
43
|
+
className
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
>
|
|
47
|
+
<ChevronUp className='h-4 w-4' />
|
|
48
|
+
</SelectPrimitive.ScrollUpButton>
|
|
49
|
+
));
|
|
50
|
+
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
|
|
51
|
+
|
|
52
|
+
const SelectScrollDownButton = React.forwardRef<
|
|
53
|
+
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
|
54
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
|
55
|
+
>(({ className, ...props }, ref) => (
|
|
56
|
+
<SelectPrimitive.ScrollDownButton
|
|
57
|
+
ref={ref}
|
|
58
|
+
className={cn(
|
|
59
|
+
'flex cursor-default items-center justify-center py-1',
|
|
60
|
+
className
|
|
61
|
+
)}
|
|
62
|
+
{...props}
|
|
63
|
+
>
|
|
64
|
+
<ChevronDown className='h-4 w-4' />
|
|
65
|
+
</SelectPrimitive.ScrollDownButton>
|
|
66
|
+
));
|
|
67
|
+
SelectScrollDownButton.displayName =
|
|
68
|
+
SelectPrimitive.ScrollDownButton.displayName;
|
|
69
|
+
|
|
70
|
+
const SelectContent = React.forwardRef<
|
|
71
|
+
React.ElementRef<typeof SelectPrimitive.Content>,
|
|
72
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
|
73
|
+
>(({ className, children, position = 'popper', ...props }, ref) => (
|
|
74
|
+
<SelectPrimitive.Portal>
|
|
75
|
+
<SelectPrimitive.Content
|
|
76
|
+
ref={ref}
|
|
77
|
+
className={cn(
|
|
78
|
+
'bg-popover text-popover-foreground 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] origin-[--radix-select-content-transform-origin] overflow-x-hidden overflow-y-auto rounded-md border shadow-md',
|
|
79
|
+
position === 'popper' &&
|
|
80
|
+
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
|
81
|
+
className
|
|
82
|
+
)}
|
|
83
|
+
position={position}
|
|
84
|
+
{...props}
|
|
85
|
+
>
|
|
86
|
+
<SelectScrollUpButton />
|
|
87
|
+
<SelectPrimitive.Viewport
|
|
88
|
+
className={cn(
|
|
89
|
+
'p-1',
|
|
90
|
+
position === 'popper' &&
|
|
91
|
+
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]'
|
|
92
|
+
)}
|
|
93
|
+
>
|
|
94
|
+
{children}
|
|
95
|
+
</SelectPrimitive.Viewport>
|
|
96
|
+
<SelectScrollDownButton />
|
|
97
|
+
</SelectPrimitive.Content>
|
|
98
|
+
</SelectPrimitive.Portal>
|
|
99
|
+
));
|
|
100
|
+
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
|
101
|
+
|
|
102
|
+
const SelectLabel = React.forwardRef<
|
|
103
|
+
React.ElementRef<typeof SelectPrimitive.Label>,
|
|
104
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
|
105
|
+
>(({ className, ...props }, ref) => (
|
|
106
|
+
<SelectPrimitive.Label
|
|
107
|
+
ref={ref}
|
|
108
|
+
className={cn('px-2 py-1.5 text-sm font-semibold', className)}
|
|
109
|
+
{...props}
|
|
110
|
+
/>
|
|
111
|
+
));
|
|
112
|
+
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
|
113
|
+
|
|
114
|
+
const SelectItem = React.forwardRef<
|
|
115
|
+
React.ElementRef<typeof SelectPrimitive.Item>,
|
|
116
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
|
117
|
+
>(({ className, children, ...props }, ref) => (
|
|
118
|
+
<SelectPrimitive.Item
|
|
119
|
+
ref={ref}
|
|
120
|
+
className={cn(
|
|
121
|
+
'focus:bg-accent focus:text-accent-foreground relative flex w-full cursor-default items-center rounded-sm py-1.5 pr-8 pl-2 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
122
|
+
className
|
|
123
|
+
)}
|
|
124
|
+
{...props}
|
|
125
|
+
>
|
|
126
|
+
<span className='absolute right-2 flex h-3.5 w-3.5 items-center justify-center'>
|
|
127
|
+
<SelectPrimitive.ItemIndicator>
|
|
128
|
+
<Check className='h-4 w-4' />
|
|
129
|
+
</SelectPrimitive.ItemIndicator>
|
|
130
|
+
</span>
|
|
131
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
132
|
+
</SelectPrimitive.Item>
|
|
133
|
+
));
|
|
134
|
+
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
|
135
|
+
|
|
136
|
+
const SelectSeparator = React.forwardRef<
|
|
137
|
+
React.ElementRef<typeof SelectPrimitive.Separator>,
|
|
138
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
|
139
|
+
>(({ className, ...props }, ref) => (
|
|
140
|
+
<SelectPrimitive.Separator
|
|
141
|
+
ref={ref}
|
|
142
|
+
className={cn('bg-muted -mx-1 my-1 h-px', className)}
|
|
143
|
+
{...props}
|
|
144
|
+
/>
|
|
145
|
+
));
|
|
146
|
+
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
|
147
|
+
|
|
148
|
+
export {
|
|
149
|
+
Select,
|
|
150
|
+
SelectGroup,
|
|
151
|
+
SelectValue,
|
|
152
|
+
SelectTrigger,
|
|
153
|
+
SelectContent,
|
|
154
|
+
SelectLabel,
|
|
155
|
+
SelectItem,
|
|
156
|
+
SelectSeparator,
|
|
157
|
+
SelectScrollUpButton,
|
|
158
|
+
SelectScrollDownButton,
|
|
159
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '../story-types';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Sheet,
|
|
5
|
+
SheetClose,
|
|
6
|
+
SheetContent,
|
|
7
|
+
SheetDescription,
|
|
8
|
+
SheetFooter,
|
|
9
|
+
SheetHeader,
|
|
10
|
+
SheetTitle,
|
|
11
|
+
SheetTrigger,
|
|
12
|
+
} from './sheet';
|
|
13
|
+
import { Button } from './button';
|
|
14
|
+
|
|
15
|
+
const meta = {
|
|
16
|
+
title: 'Components/Sheet',
|
|
17
|
+
// `component:` is deliberately omitted: Sheet re-exports Radix's Dialog Root,
|
|
18
|
+
// whose displayName is 'Dialog' and would not match the title leaf 'Sheet'
|
|
19
|
+
// (§7). The leaf still resolves through the named `Sheet` import.
|
|
20
|
+
tags: ['autodocs'],
|
|
21
|
+
// Radix DialogProps: `open`/`defaultOpen`/`modal`/`onOpenChange`. `side` is a
|
|
22
|
+
// SheetContent prop, not a root prop, so it is not curated here. `children`
|
|
23
|
+
// (trigger + content) is the composed slot (§4).
|
|
24
|
+
argTypes: {
|
|
25
|
+
defaultOpen: {
|
|
26
|
+
control: 'boolean',
|
|
27
|
+
description: 'Open the sheet on mount (uncontrolled)',
|
|
28
|
+
},
|
|
29
|
+
modal: {
|
|
30
|
+
control: 'boolean',
|
|
31
|
+
description: 'Trap focus and block outside interaction',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
args: { defaultOpen: false, modal: true },
|
|
35
|
+
parameters: { puck: { slots: ['children'] } },
|
|
36
|
+
} satisfies Meta<typeof Sheet>;
|
|
37
|
+
|
|
38
|
+
export default meta;
|
|
39
|
+
type Story = StoryObj<typeof meta>;
|
|
40
|
+
|
|
41
|
+
export const Default: Story = {
|
|
42
|
+
render: (args) => (
|
|
43
|
+
<Sheet {...args}>
|
|
44
|
+
<SheetTrigger asChild>
|
|
45
|
+
<Button variant='outline'>Open details</Button>
|
|
46
|
+
</SheetTrigger>
|
|
47
|
+
<SheetContent>
|
|
48
|
+
<SheetHeader>
|
|
49
|
+
<SheetTitle>Appointment details</SheetTitle>
|
|
50
|
+
<SheetDescription>Dr. Lopez · Mon 9:00 AM</SheetDescription>
|
|
51
|
+
</SheetHeader>
|
|
52
|
+
<div className='text-muted-foreground px-4 py-2 text-sm'>
|
|
53
|
+
Edit the visit, add notes, or reassign the provider.
|
|
54
|
+
</div>
|
|
55
|
+
<SheetFooter>
|
|
56
|
+
<SheetClose asChild>
|
|
57
|
+
<Button>Done</Button>
|
|
58
|
+
</SheetClose>
|
|
59
|
+
</SheetFooter>
|
|
60
|
+
</SheetContent>
|
|
61
|
+
</Sheet>
|
|
62
|
+
),
|
|
63
|
+
};
|