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,35 @@
1
+ import type { Meta, StoryObj } from '../story-types';
2
+
3
+ import { Label } from './label';
4
+ import { Input } from './input';
5
+
6
+ const meta = {
7
+ title: 'Components/Label',
8
+ component: Label,
9
+ tags: ['autodocs'],
10
+ // `children` is the label text — an editable string here, NOT a slot, so
11
+ // there is no SLOT_ARGTYPE_COLLISION (§5). `htmlFor` is the real
12
+ // `LabelPrimitive.Root` prop that wires the label to its control.
13
+ argTypes: {
14
+ children: { control: 'text', description: 'Label text' },
15
+ htmlFor: {
16
+ control: 'text',
17
+ description: 'id of the form control this label describes',
18
+ },
19
+ },
20
+ args: { children: 'Patient name' },
21
+ } satisfies Meta<typeof Label>;
22
+
23
+ export default meta;
24
+ type Story = StoryObj<typeof meta>;
25
+
26
+ export const Default: Story = {};
27
+
28
+ export const WithInput: Story = {
29
+ render: () => (
30
+ <div className='grid w-72 gap-2'>
31
+ <Label htmlFor='name'>Patient name</Label>
32
+ <Input id='name' placeholder='Jane Doe' />
33
+ </div>
34
+ ),
35
+ };
@@ -0,0 +1,26 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import * as LabelPrimitive from '@radix-ui/react-label';
5
+ import { cva, type VariantProps } from 'class-variance-authority';
6
+
7
+ import { cn } from '../lib/utils';
8
+
9
+ const labelVariants = cva(
10
+ 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
11
+ );
12
+
13
+ const Label = React.forwardRef<
14
+ React.ElementRef<typeof LabelPrimitive.Root>,
15
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
16
+ VariantProps<typeof labelVariants>
17
+ >(({ className, ...props }, ref) => (
18
+ <LabelPrimitive.Root
19
+ ref={ref}
20
+ className={cn(labelVariants(), className)}
21
+ {...props}
22
+ />
23
+ ));
24
+ Label.displayName = LabelPrimitive.Root.displayName;
25
+
26
+ export { Label };
@@ -0,0 +1,79 @@
1
+ import type { Meta, StoryObj } from '../story-types';
2
+
3
+ import {
4
+ NavigationMenu,
5
+ NavigationMenuContent,
6
+ NavigationMenuItem,
7
+ NavigationMenuLink,
8
+ NavigationMenuList,
9
+ NavigationMenuTrigger,
10
+ } from './navigation-menu';
11
+
12
+ const meta = {
13
+ title: 'Components/NavigationMenu',
14
+ component: NavigationMenu,
15
+ tags: ['autodocs'],
16
+ // Radix NavigationMenu.Root scalars (`value` / `onValueChange` are
17
+ // controlled-mode only). `children` (the NavigationMenuList) is composed
18
+ // content → a slot; the root also renders its own viewport after it.
19
+ argTypes: {
20
+ orientation: {
21
+ control: 'inline-radio',
22
+ options: ['horizontal', 'vertical'],
23
+ },
24
+ dir: { control: 'inline-radio', options: ['ltr', 'rtl'] },
25
+ defaultValue: {
26
+ control: 'text',
27
+ description: 'Value of the item open on first render',
28
+ },
29
+ delayDuration: {
30
+ control: 'number',
31
+ description: 'Ms from pointer-enter until the menu opens',
32
+ },
33
+ skipDelayDuration: {
34
+ control: 'number',
35
+ description: 'Ms the user has to move between items without a delay',
36
+ },
37
+ },
38
+ args: { orientation: 'horizontal', delayDuration: 200 },
39
+ parameters: { puck: { slots: ['children'] } },
40
+ } satisfies Meta<typeof NavigationMenu>;
41
+
42
+ export default meta;
43
+ type Story = StoryObj<typeof meta>;
44
+
45
+ export const Default: Story = {
46
+ render: (args) => (
47
+ <NavigationMenu {...args}>
48
+ <NavigationMenuList>
49
+ <NavigationMenuItem>
50
+ <NavigationMenuTrigger>Product</NavigationMenuTrigger>
51
+ <NavigationMenuContent>
52
+ <ul className='grid w-64 gap-1 p-3'>
53
+ <li>
54
+ <NavigationMenuLink className='hover:bg-accent block rounded-md p-2 text-sm'>
55
+ Calendar sync
56
+ </NavigationMenuLink>
57
+ </li>
58
+ <li>
59
+ <NavigationMenuLink className='hover:bg-accent block rounded-md p-2 text-sm'>
60
+ Booking pages
61
+ </NavigationMenuLink>
62
+ </li>
63
+ <li>
64
+ <NavigationMenuLink className='hover:bg-accent block rounded-md p-2 text-sm'>
65
+ Availability
66
+ </NavigationMenuLink>
67
+ </li>
68
+ </ul>
69
+ </NavigationMenuContent>
70
+ </NavigationMenuItem>
71
+ <NavigationMenuItem>
72
+ <NavigationMenuLink className='px-3 py-2 text-sm font-medium'>
73
+ Pricing
74
+ </NavigationMenuLink>
75
+ </NavigationMenuItem>
76
+ </NavigationMenuList>
77
+ </NavigationMenu>
78
+ ),
79
+ };
@@ -0,0 +1,128 @@
1
+ import * as React from 'react';
2
+ import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu';
3
+ import { cva } from 'class-variance-authority';
4
+ import { ChevronDown } from 'lucide-react';
5
+
6
+ import { cn } from '../lib/utils';
7
+
8
+ const NavigationMenu = React.forwardRef<
9
+ React.ElementRef<typeof NavigationMenuPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
11
+ >(({ className, children, ...props }, ref) => (
12
+ <NavigationMenuPrimitive.Root
13
+ ref={ref}
14
+ className={cn(
15
+ 'relative z-10 flex max-w-max flex-1 items-center justify-center',
16
+ className
17
+ )}
18
+ {...props}
19
+ >
20
+ {children}
21
+ <NavigationMenuViewport />
22
+ </NavigationMenuPrimitive.Root>
23
+ ));
24
+ NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;
25
+
26
+ const NavigationMenuList = React.forwardRef<
27
+ React.ElementRef<typeof NavigationMenuPrimitive.List>,
28
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
29
+ >(({ className, ...props }, ref) => (
30
+ <NavigationMenuPrimitive.List
31
+ ref={ref}
32
+ className={cn(
33
+ 'group flex flex-1 list-none items-center justify-center space-x-1',
34
+ className
35
+ )}
36
+ {...props}
37
+ />
38
+ ));
39
+ NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
40
+
41
+ const NavigationMenuItem = NavigationMenuPrimitive.Item;
42
+
43
+ const navigationMenuTriggerStyle = cva(
44
+ 'group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=open]:text-accent-foreground data-[state=open]:bg-accent/50 data-[state=open]:hover:bg-accent data-[state=open]:focus:bg-accent'
45
+ );
46
+
47
+ const NavigationMenuTrigger = React.forwardRef<
48
+ React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
49
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
50
+ >(({ className, children, ...props }, ref) => (
51
+ <NavigationMenuPrimitive.Trigger
52
+ ref={ref}
53
+ className={cn(navigationMenuTriggerStyle(), 'group', className)}
54
+ {...props}
55
+ >
56
+ {children}{' '}
57
+ <ChevronDown
58
+ className='relative top-[1px] ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180'
59
+ aria-hidden='true'
60
+ />
61
+ </NavigationMenuPrimitive.Trigger>
62
+ ));
63
+ NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;
64
+
65
+ const NavigationMenuContent = React.forwardRef<
66
+ React.ElementRef<typeof NavigationMenuPrimitive.Content>,
67
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
68
+ >(({ className, ...props }, ref) => (
69
+ <NavigationMenuPrimitive.Content
70
+ ref={ref}
71
+ className={cn(
72
+ 'data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full md:absolute md:w-auto',
73
+ className
74
+ )}
75
+ {...props}
76
+ />
77
+ ));
78
+ NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;
79
+
80
+ const NavigationMenuLink = NavigationMenuPrimitive.Link;
81
+
82
+ const NavigationMenuViewport = React.forwardRef<
83
+ React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
84
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
85
+ >(({ className, ...props }, ref) => (
86
+ <div className={cn('absolute top-full left-0 flex justify-center')}>
87
+ <NavigationMenuPrimitive.Viewport
88
+ className={cn(
89
+ 'origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]',
90
+ className
91
+ )}
92
+ ref={ref}
93
+ {...props}
94
+ />
95
+ </div>
96
+ ));
97
+ NavigationMenuViewport.displayName =
98
+ NavigationMenuPrimitive.Viewport.displayName;
99
+
100
+ const NavigationMenuIndicator = React.forwardRef<
101
+ React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
102
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
103
+ >(({ className, ...props }, ref) => (
104
+ <NavigationMenuPrimitive.Indicator
105
+ ref={ref}
106
+ className={cn(
107
+ 'data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden',
108
+ className
109
+ )}
110
+ {...props}
111
+ >
112
+ <div className='bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md' />
113
+ </NavigationMenuPrimitive.Indicator>
114
+ ));
115
+ NavigationMenuIndicator.displayName =
116
+ NavigationMenuPrimitive.Indicator.displayName;
117
+
118
+ export {
119
+ navigationMenuTriggerStyle,
120
+ NavigationMenu,
121
+ NavigationMenuList,
122
+ NavigationMenuItem,
123
+ NavigationMenuContent,
124
+ NavigationMenuTrigger,
125
+ NavigationMenuLink,
126
+ NavigationMenuIndicator,
127
+ NavigationMenuViewport,
128
+ };
@@ -0,0 +1,62 @@
1
+ import type { Meta, StoryObj } from '../story-types';
2
+
3
+ import {
4
+ Pagination,
5
+ PaginationContent,
6
+ PaginationEllipsis,
7
+ PaginationItem,
8
+ PaginationLink,
9
+ PaginationNext,
10
+ PaginationPrevious,
11
+ } from './pagination';
12
+
13
+ const meta = {
14
+ title: 'Components/Pagination',
15
+ component: Pagination,
16
+ tags: ['autodocs'],
17
+ // The Pagination root is a bare <nav> and had no scalar prop of its own, so a
18
+ // real `align` prop was added (pagination.tsx) rather than faking one — it
19
+ // drives the nav's justify-* and was previously hard-coded to center.
20
+ // `children` (the page list) is composed content → a slot.
21
+ argTypes: {
22
+ align: {
23
+ control: 'inline-radio',
24
+ options: ['start', 'center', 'end'],
25
+ description: 'Horizontal alignment of the page list',
26
+ },
27
+ },
28
+ args: { align: 'center' },
29
+ parameters: { puck: { slots: ['children'] } },
30
+ } satisfies Meta<typeof Pagination>;
31
+
32
+ export default meta;
33
+ type Story = StoryObj<typeof meta>;
34
+
35
+ export const Default: Story = {
36
+ render: (args) => (
37
+ <Pagination {...args}>
38
+ <PaginationContent>
39
+ <PaginationItem>
40
+ <PaginationPrevious href='#' />
41
+ </PaginationItem>
42
+ <PaginationItem>
43
+ <PaginationLink href='#'>1</PaginationLink>
44
+ </PaginationItem>
45
+ <PaginationItem>
46
+ <PaginationLink href='#' isActive>
47
+ 2
48
+ </PaginationLink>
49
+ </PaginationItem>
50
+ <PaginationItem>
51
+ <PaginationLink href='#'>3</PaginationLink>
52
+ </PaginationItem>
53
+ <PaginationItem>
54
+ <PaginationEllipsis />
55
+ </PaginationItem>
56
+ <PaginationItem>
57
+ <PaginationNext href='#' />
58
+ </PaginationItem>
59
+ </PaginationContent>
60
+ </Pagination>
61
+ ),
62
+ };
@@ -0,0 +1,137 @@
1
+ import * as React from 'react';
2
+ import { ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react';
3
+
4
+ import { cn } from '../lib/utils';
5
+ import { ButtonProps, buttonVariants } from './button';
6
+
7
+ export interface PaginationProps extends React.ComponentProps<'nav'> {
8
+ /**
9
+ * Horizontal alignment of the page list inside the nav. Defaults to centered.
10
+ */
11
+ align?: 'start' | 'center' | 'end';
12
+ }
13
+
14
+ const PAGINATION_ALIGN: Record<
15
+ NonNullable<PaginationProps['align']>,
16
+ string
17
+ > = {
18
+ start: 'justify-start',
19
+ center: 'justify-center',
20
+ end: 'justify-end',
21
+ };
22
+
23
+ const Pagination = ({
24
+ className,
25
+ align = 'center',
26
+ ...props
27
+ }: PaginationProps) => (
28
+ <nav
29
+ role='navigation'
30
+ aria-label='pagination'
31
+ className={cn('mx-auto flex w-full', PAGINATION_ALIGN[align], className)}
32
+ {...props}
33
+ />
34
+ );
35
+ Pagination.displayName = 'Pagination';
36
+
37
+ const PaginationContent = React.forwardRef<
38
+ HTMLUListElement,
39
+ React.ComponentProps<'ul'>
40
+ >(({ className, ...props }, ref) => (
41
+ <ul
42
+ ref={ref}
43
+ className={cn('flex flex-row items-center gap-1', className)}
44
+ {...props}
45
+ />
46
+ ));
47
+ PaginationContent.displayName = 'PaginationContent';
48
+
49
+ const PaginationItem = React.forwardRef<
50
+ HTMLLIElement,
51
+ React.ComponentProps<'li'>
52
+ >(({ className, ...props }, ref) => (
53
+ <li ref={ref} className={cn('', className)} {...props} />
54
+ ));
55
+ PaginationItem.displayName = 'PaginationItem';
56
+
57
+ type PaginationLinkProps = {
58
+ isActive?: boolean;
59
+ } & Pick<ButtonProps, 'size'> &
60
+ React.ComponentProps<'a'>;
61
+
62
+ const PaginationLink = ({
63
+ className,
64
+ isActive,
65
+ size = 'icon',
66
+ ...props
67
+ }: PaginationLinkProps) => (
68
+ <a
69
+ aria-current={isActive ? 'page' : undefined}
70
+ className={cn(
71
+ buttonVariants({
72
+ variant: isActive ? 'outline' : 'ghost',
73
+ size,
74
+ }),
75
+ className
76
+ )}
77
+ {...props}
78
+ />
79
+ );
80
+ PaginationLink.displayName = 'PaginationLink';
81
+
82
+ const PaginationPrevious = ({
83
+ className,
84
+ ...props
85
+ }: React.ComponentProps<typeof PaginationLink>) => (
86
+ <PaginationLink
87
+ aria-label='Go to previous page'
88
+ size='default'
89
+ className={cn('gap-1 pl-2.5', className)}
90
+ {...props}
91
+ >
92
+ <ChevronLeft className='h-4 w-4' />
93
+ <span>Previous</span>
94
+ </PaginationLink>
95
+ );
96
+ PaginationPrevious.displayName = 'PaginationPrevious';
97
+
98
+ const PaginationNext = ({
99
+ className,
100
+ ...props
101
+ }: React.ComponentProps<typeof PaginationLink>) => (
102
+ <PaginationLink
103
+ aria-label='Go to next page'
104
+ size='default'
105
+ className={cn('gap-1 pr-2.5', className)}
106
+ {...props}
107
+ >
108
+ <span>Next</span>
109
+ <ChevronRight className='h-4 w-4' />
110
+ </PaginationLink>
111
+ );
112
+ PaginationNext.displayName = 'PaginationNext';
113
+
114
+ const PaginationEllipsis = ({
115
+ className,
116
+ ...props
117
+ }: React.ComponentProps<'span'>) => (
118
+ <span
119
+ aria-hidden
120
+ className={cn('flex h-9 w-9 items-center justify-center', className)}
121
+ {...props}
122
+ >
123
+ <MoreHorizontal className='h-4 w-4' />
124
+ <span className='sr-only'>More pages</span>
125
+ </span>
126
+ );
127
+ PaginationEllipsis.displayName = 'PaginationEllipsis';
128
+
129
+ export {
130
+ Pagination,
131
+ PaginationContent,
132
+ PaginationLink,
133
+ PaginationItem,
134
+ PaginationPrevious,
135
+ PaginationNext,
136
+ PaginationEllipsis,
137
+ };
@@ -0,0 +1,89 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { Popover as PopoverPrimitive } from 'radix-ui';
5
+
6
+ import { cn } from '../lib/utils';
7
+
8
+ function Popover({
9
+ ...props
10
+ }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
11
+ return <PopoverPrimitive.Root data-slot='popover' {...props} />;
12
+ }
13
+
14
+ function PopoverTrigger({
15
+ ...props
16
+ }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
17
+ return <PopoverPrimitive.Trigger data-slot='popover-trigger' {...props} />;
18
+ }
19
+
20
+ function PopoverContent({
21
+ className,
22
+ align = 'center',
23
+ sideOffset = 4,
24
+ ...props
25
+ }: React.ComponentProps<typeof PopoverPrimitive.Content>) {
26
+ return (
27
+ <PopoverPrimitive.Portal>
28
+ <PopoverPrimitive.Content
29
+ data-slot='popover-content'
30
+ align={align}
31
+ sideOffset={sideOffset}
32
+ className={cn(
33
+ 'bg-popover text-popover-foreground 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden',
34
+ className
35
+ )}
36
+ {...props}
37
+ />
38
+ </PopoverPrimitive.Portal>
39
+ );
40
+ }
41
+
42
+ function PopoverAnchor({
43
+ ...props
44
+ }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
45
+ return <PopoverPrimitive.Anchor data-slot='popover-anchor' {...props} />;
46
+ }
47
+
48
+ function PopoverHeader({ className, ...props }: React.ComponentProps<'div'>) {
49
+ return (
50
+ <div
51
+ data-slot='popover-header'
52
+ className={cn('flex flex-col gap-1 text-sm', className)}
53
+ {...props}
54
+ />
55
+ );
56
+ }
57
+
58
+ function PopoverTitle({ className, ...props }: React.ComponentProps<'h2'>) {
59
+ return (
60
+ <div
61
+ data-slot='popover-title'
62
+ className={cn('font-medium', className)}
63
+ {...props}
64
+ />
65
+ );
66
+ }
67
+
68
+ function PopoverDescription({
69
+ className,
70
+ ...props
71
+ }: React.ComponentProps<'p'>) {
72
+ return (
73
+ <p
74
+ data-slot='popover-description'
75
+ className={cn('text-muted-foreground', className)}
76
+ {...props}
77
+ />
78
+ );
79
+ }
80
+
81
+ export {
82
+ Popover,
83
+ PopoverTrigger,
84
+ PopoverContent,
85
+ PopoverAnchor,
86
+ PopoverHeader,
87
+ PopoverTitle,
88
+ PopoverDescription,
89
+ };
@@ -0,0 +1,33 @@
1
+ import type { Meta, StoryObj } from '../story-types';
2
+
3
+ import { Progress } from './progress';
4
+
5
+ const meta = {
6
+ title: 'Components/Progress',
7
+ component: Progress,
8
+ tags: ['autodocs'],
9
+ // Leaf: Radix ProgressProps exposes `value` and `max` (numbers). It renders no
10
+ // composed children, so NO slot is declared (§4).
11
+ argTypes: {
12
+ value: { control: 'number', description: 'Current progress value' },
13
+ max: { control: 'number', description: 'Maximum value (defaults to 100)' },
14
+ },
15
+ args: { value: 60, max: 100 },
16
+ } satisfies Meta<typeof Progress>;
17
+
18
+ export default meta;
19
+ type Story = StoryObj<typeof meta>;
20
+
21
+ export const Default: Story = {
22
+ render: (args) => <Progress className='w-80' {...args} />,
23
+ };
24
+
25
+ export const Steps: Story = {
26
+ render: () => (
27
+ <div className='flex w-80 flex-col gap-4'>
28
+ <Progress value={25} />
29
+ <Progress value={50} />
30
+ <Progress value={100} />
31
+ </div>
32
+ ),
33
+ };
@@ -0,0 +1,28 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import * as ProgressPrimitive from '@radix-ui/react-progress';
5
+
6
+ import { cn } from '../lib/utils';
7
+
8
+ const Progress = React.forwardRef<
9
+ React.ElementRef<typeof ProgressPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
11
+ >(({ className, value, ...props }, ref) => (
12
+ <ProgressPrimitive.Root
13
+ ref={ref}
14
+ className={cn(
15
+ 'bg-primary/20 relative h-2 w-full overflow-hidden rounded-full',
16
+ className
17
+ )}
18
+ {...props}
19
+ >
20
+ <ProgressPrimitive.Indicator
21
+ className='bg-primary h-full w-full flex-1 transition-all'
22
+ style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
23
+ />
24
+ </ProgressPrimitive.Root>
25
+ ));
26
+ Progress.displayName = ProgressPrimitive.Root.displayName;
27
+
28
+ export { Progress };
@@ -0,0 +1,52 @@
1
+ import type { Meta, StoryObj } from '../story-types';
2
+
3
+ import { RadioGroup, RadioGroupItem } from './radio-group';
4
+ import { Label } from './label';
5
+
6
+ const meta = {
7
+ title: 'Components/RadioGroup',
8
+ component: RadioGroup,
9
+ tags: ['autodocs'],
10
+ // Container: RadioGroup renders composed `children` (its RadioGroupItems), so
11
+ // `children` is a slot — and therefore must NOT also appear in argTypes (§5).
12
+ // The argTypes below are real `RadioGroup.Root` props (§6: no className).
13
+ argTypes: {
14
+ defaultValue: {
15
+ control: 'text',
16
+ description: 'Value of the item checked by default',
17
+ },
18
+ orientation: {
19
+ control: 'inline-radio',
20
+ options: ['horizontal', 'vertical'],
21
+ },
22
+ disabled: { control: 'boolean' },
23
+ required: { control: 'boolean' },
24
+ loop: {
25
+ control: 'boolean',
26
+ description: 'Wrap keyboard focus from last item back to first',
27
+ },
28
+ name: { control: 'text', description: 'Name submitted with the form' },
29
+ },
30
+ args: { defaultValue: '30' },
31
+ parameters: { puck: { slots: ['children'] } },
32
+ } satisfies Meta<typeof RadioGroup>;
33
+
34
+ export default meta;
35
+ type Story = StoryObj<typeof meta>;
36
+
37
+ export const Default: Story = {
38
+ render: (args) => (
39
+ <RadioGroup {...args} className='gap-3'>
40
+ {[
41
+ { v: '15', l: '15 minutes' },
42
+ { v: '30', l: '30 minutes' },
43
+ { v: '60', l: '1 hour' },
44
+ ].map(({ v, l }) => (
45
+ <div key={v} className='flex items-center gap-2'>
46
+ <RadioGroupItem value={v} id={`dur-${v}`} />
47
+ <Label htmlFor={`dur-${v}`}>{l}</Label>
48
+ </div>
49
+ ))}
50
+ </RadioGroup>
51
+ ),
52
+ };