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,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
+ };