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,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
|
+
};
|
package/src/ui/label.tsx
ADDED
|
@@ -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
|
+
};
|