@trycompai/design-system 1.0.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/README.md +110 -0
- package/components.json +21 -0
- package/hooks/use-mobile.tsx +19 -0
- package/lib/utils.ts +6 -0
- package/package.json +103 -0
- package/postcss.config.mjs +8 -0
- package/src/components/ui/accordion.tsx +60 -0
- package/src/components/ui/alert-dialog.tsx +161 -0
- package/src/components/ui/alert.tsx +109 -0
- package/src/components/ui/aspect-ratio.tsx +21 -0
- package/src/components/ui/avatar.tsx +74 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/breadcrumb.tsx +254 -0
- package/src/components/ui/button-group.tsx +89 -0
- package/src/components/ui/button.tsx +122 -0
- package/src/components/ui/calendar.tsx +190 -0
- package/src/components/ui/card.tsx +155 -0
- package/src/components/ui/carousel.tsx +216 -0
- package/src/components/ui/chart.tsx +325 -0
- package/src/components/ui/checkbox.tsx +22 -0
- package/src/components/ui/collapsible.tsx +17 -0
- package/src/components/ui/combobox.tsx +248 -0
- package/src/components/ui/command.tsx +189 -0
- package/src/components/ui/container.tsx +34 -0
- package/src/components/ui/context-menu.tsx +235 -0
- package/src/components/ui/dialog.tsx +122 -0
- package/src/components/ui/drawer.tsx +102 -0
- package/src/components/ui/dropdown-menu.tsx +242 -0
- package/src/components/ui/empty.tsx +94 -0
- package/src/components/ui/field.tsx +215 -0
- package/src/components/ui/grid.tsx +135 -0
- package/src/components/ui/heading.tsx +56 -0
- package/src/components/ui/hover-card.tsx +46 -0
- package/src/components/ui/index.ts +61 -0
- package/src/components/ui/input-group.tsx +128 -0
- package/src/components/ui/input-otp.tsx +84 -0
- package/src/components/ui/input.tsx +15 -0
- package/src/components/ui/item.tsx +188 -0
- package/src/components/ui/kbd.tsx +26 -0
- package/src/components/ui/label.tsx +15 -0
- package/src/components/ui/menubar.tsx +163 -0
- package/src/components/ui/navigation-menu.tsx +147 -0
- package/src/components/ui/page-header.tsx +51 -0
- package/src/components/ui/page-layout.tsx +65 -0
- package/src/components/ui/pagination.tsx +104 -0
- package/src/components/ui/popover.tsx +57 -0
- package/src/components/ui/progress.tsx +61 -0
- package/src/components/ui/radio-group.tsx +37 -0
- package/src/components/ui/resizable.tsx +41 -0
- package/src/components/ui/scroll-area.tsx +48 -0
- package/src/components/ui/section.tsx +64 -0
- package/src/components/ui/select.tsx +166 -0
- package/src/components/ui/separator.tsx +17 -0
- package/src/components/ui/sheet.tsx +104 -0
- package/src/components/ui/sidebar.tsx +707 -0
- package/src/components/ui/skeleton.tsx +5 -0
- package/src/components/ui/slider.tsx +51 -0
- package/src/components/ui/sonner.tsx +43 -0
- package/src/components/ui/spinner.tsx +14 -0
- package/src/components/ui/stack.tsx +72 -0
- package/src/components/ui/switch.tsx +26 -0
- package/src/components/ui/table.tsx +65 -0
- package/src/components/ui/tabs.tsx +69 -0
- package/src/components/ui/text.tsx +59 -0
- package/src/components/ui/textarea.tsx +13 -0
- package/src/components/ui/toggle-group.tsx +87 -0
- package/src/components/ui/toggle.tsx +42 -0
- package/src/components/ui/tooltip.tsx +52 -0
- package/src/index.ts +3 -0
- package/src/styles/globals.css +122 -0
- package/tailwind.config.ts +59 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { Menu as MenuPrimitive } from '@base-ui/react/menu';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
import { CheckIcon, ChevronRightIcon } from 'lucide-react';
|
|
6
|
+
|
|
7
|
+
function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {
|
|
8
|
+
return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {
|
|
12
|
+
return <MenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const dropdownMenuTriggerVariants = cva('', {
|
|
16
|
+
variants: {
|
|
17
|
+
variant: {
|
|
18
|
+
default: '', // Uses render prop for custom trigger styling
|
|
19
|
+
menubar:
|
|
20
|
+
'hover:bg-muted aria-expanded:bg-muted rounded-sm px-2 py-1 text-sm font-medium flex items-center outline-hidden select-none',
|
|
21
|
+
ellipsis:
|
|
22
|
+
'size-5 [&>svg]:size-4 flex items-center justify-center cursor-pointer hover:text-foreground transition-colors rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
variant: 'default',
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
function DropdownMenuTrigger({
|
|
31
|
+
variant = 'default',
|
|
32
|
+
...props
|
|
33
|
+
}: Omit<MenuPrimitive.Trigger.Props, 'className'> &
|
|
34
|
+
VariantProps<typeof dropdownMenuTriggerVariants>) {
|
|
35
|
+
return (
|
|
36
|
+
<MenuPrimitive.Trigger
|
|
37
|
+
data-slot="dropdown-menu-trigger"
|
|
38
|
+
className={dropdownMenuTriggerVariants({ variant })}
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function DropdownMenuContent({
|
|
45
|
+
align = 'start',
|
|
46
|
+
alignOffset = 0,
|
|
47
|
+
side = 'bottom',
|
|
48
|
+
sideOffset = 4,
|
|
49
|
+
...props
|
|
50
|
+
}: Omit<MenuPrimitive.Popup.Props, 'className'> &
|
|
51
|
+
Pick<MenuPrimitive.Positioner.Props, 'align' | 'alignOffset' | 'side' | 'sideOffset'>) {
|
|
52
|
+
return (
|
|
53
|
+
<MenuPrimitive.Portal>
|
|
54
|
+
<MenuPrimitive.Positioner
|
|
55
|
+
className="isolate z-50 outline-none"
|
|
56
|
+
align={align}
|
|
57
|
+
alignOffset={alignOffset}
|
|
58
|
+
side={side}
|
|
59
|
+
sideOffset={sideOffset}
|
|
60
|
+
>
|
|
61
|
+
<MenuPrimitive.Popup
|
|
62
|
+
data-slot="dropdown-menu-content"
|
|
63
|
+
className="data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 bg-popover text-popover-foreground min-w-32 rounded-lg p-1 shadow-md ring-1 duration-100 z-50 max-h-(--available-height) w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto outline-none data-closed:overflow-hidden"
|
|
64
|
+
{...props}
|
|
65
|
+
/>
|
|
66
|
+
</MenuPrimitive.Positioner>
|
|
67
|
+
</MenuPrimitive.Portal>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {
|
|
72
|
+
return <MenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function DropdownMenuLabel({
|
|
76
|
+
inset,
|
|
77
|
+
...props
|
|
78
|
+
}: Omit<MenuPrimitive.GroupLabel.Props, 'className'> & {
|
|
79
|
+
inset?: boolean;
|
|
80
|
+
}) {
|
|
81
|
+
return (
|
|
82
|
+
<MenuPrimitive.GroupLabel
|
|
83
|
+
data-slot="dropdown-menu-label"
|
|
84
|
+
data-inset={inset}
|
|
85
|
+
className="text-muted-foreground px-1.5 py-1 text-xs font-medium data-[inset]:pl-8"
|
|
86
|
+
{...props}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function DropdownMenuItem({
|
|
92
|
+
inset,
|
|
93
|
+
variant = 'default',
|
|
94
|
+
...props
|
|
95
|
+
}: Omit<MenuPrimitive.Item.Props, 'className'> & {
|
|
96
|
+
inset?: boolean;
|
|
97
|
+
variant?: 'default' | 'destructive';
|
|
98
|
+
}) {
|
|
99
|
+
return (
|
|
100
|
+
<MenuPrimitive.Item
|
|
101
|
+
data-slot="dropdown-menu-item"
|
|
102
|
+
data-inset={inset}
|
|
103
|
+
data-variant={variant}
|
|
104
|
+
className="focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground gap-1.5 rounded-md px-1.5 py-1 text-sm [&_svg:not([class*='size-'])]:size-4 group/dropdown-menu-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0"
|
|
105
|
+
{...props}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {
|
|
111
|
+
return <MenuPrimitive.SubmenuRoot data-slot="dropdown-menu-sub" {...props} />;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function DropdownMenuSubTrigger({
|
|
115
|
+
inset,
|
|
116
|
+
children,
|
|
117
|
+
...props
|
|
118
|
+
}: Omit<MenuPrimitive.SubmenuTrigger.Props, 'className'> & {
|
|
119
|
+
inset?: boolean;
|
|
120
|
+
}) {
|
|
121
|
+
return (
|
|
122
|
+
<MenuPrimitive.SubmenuTrigger
|
|
123
|
+
data-slot="dropdown-menu-sub-trigger"
|
|
124
|
+
data-inset={inset}
|
|
125
|
+
className="focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-1.5 rounded-md px-1.5 py-1 text-sm [&_svg:not([class*='size-'])]:size-4 flex cursor-default items-center outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0"
|
|
126
|
+
{...props}
|
|
127
|
+
>
|
|
128
|
+
{children}
|
|
129
|
+
<ChevronRightIcon className="ml-auto" />
|
|
130
|
+
</MenuPrimitive.SubmenuTrigger>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function DropdownMenuSubContent({
|
|
135
|
+
align = 'start',
|
|
136
|
+
alignOffset = -3,
|
|
137
|
+
side = 'right',
|
|
138
|
+
sideOffset = 0,
|
|
139
|
+
...props
|
|
140
|
+
}: React.ComponentProps<typeof DropdownMenuContent>) {
|
|
141
|
+
return (
|
|
142
|
+
<DropdownMenuContent
|
|
143
|
+
data-slot="dropdown-menu-sub-content"
|
|
144
|
+
align={align}
|
|
145
|
+
alignOffset={alignOffset}
|
|
146
|
+
side={side}
|
|
147
|
+
sideOffset={sideOffset}
|
|
148
|
+
{...props}
|
|
149
|
+
/>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function DropdownMenuCheckboxItem({
|
|
154
|
+
children,
|
|
155
|
+
checked,
|
|
156
|
+
...props
|
|
157
|
+
}: Omit<MenuPrimitive.CheckboxItem.Props, 'className'>) {
|
|
158
|
+
return (
|
|
159
|
+
<MenuPrimitive.CheckboxItem
|
|
160
|
+
data-slot="dropdown-menu-checkbox-item"
|
|
161
|
+
className="focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0"
|
|
162
|
+
checked={checked}
|
|
163
|
+
{...props}
|
|
164
|
+
>
|
|
165
|
+
<span
|
|
166
|
+
className="pointer-events-none absolute right-2 flex size-4 items-center justify-center"
|
|
167
|
+
data-slot="dropdown-menu-checkbox-item-indicator"
|
|
168
|
+
>
|
|
169
|
+
<MenuPrimitive.CheckboxItemIndicator>
|
|
170
|
+
<CheckIcon className="size-4" />
|
|
171
|
+
</MenuPrimitive.CheckboxItemIndicator>
|
|
172
|
+
</span>
|
|
173
|
+
{children}
|
|
174
|
+
</MenuPrimitive.CheckboxItem>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {
|
|
179
|
+
return <MenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function DropdownMenuRadioItem({
|
|
183
|
+
children,
|
|
184
|
+
...props
|
|
185
|
+
}: Omit<MenuPrimitive.RadioItem.Props, 'className'>) {
|
|
186
|
+
return (
|
|
187
|
+
<MenuPrimitive.RadioItem
|
|
188
|
+
data-slot="dropdown-menu-radio-item"
|
|
189
|
+
className="focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0"
|
|
190
|
+
{...props}
|
|
191
|
+
>
|
|
192
|
+
<span
|
|
193
|
+
className="pointer-events-none absolute right-2 flex size-4 items-center justify-center"
|
|
194
|
+
data-slot="dropdown-menu-radio-item-indicator"
|
|
195
|
+
>
|
|
196
|
+
<MenuPrimitive.RadioItemIndicator>
|
|
197
|
+
<CheckIcon className="size-4" />
|
|
198
|
+
</MenuPrimitive.RadioItemIndicator>
|
|
199
|
+
</span>
|
|
200
|
+
{children}
|
|
201
|
+
</MenuPrimitive.RadioItem>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function DropdownMenuSeparator({ ...props }: Omit<MenuPrimitive.Separator.Props, 'className'>) {
|
|
206
|
+
return (
|
|
207
|
+
<MenuPrimitive.Separator
|
|
208
|
+
data-slot="dropdown-menu-separator"
|
|
209
|
+
className="bg-border -mx-1 my-1 h-px"
|
|
210
|
+
{...props}
|
|
211
|
+
/>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function DropdownMenuShortcut({ ...props }: Omit<React.ComponentProps<'span'>, 'className'>) {
|
|
216
|
+
return (
|
|
217
|
+
<span
|
|
218
|
+
data-slot="dropdown-menu-shortcut"
|
|
219
|
+
className="text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground ml-auto text-xs tracking-widest"
|
|
220
|
+
{...props}
|
|
221
|
+
/>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export {
|
|
226
|
+
DropdownMenu,
|
|
227
|
+
DropdownMenuCheckboxItem,
|
|
228
|
+
DropdownMenuContent,
|
|
229
|
+
DropdownMenuGroup,
|
|
230
|
+
DropdownMenuItem,
|
|
231
|
+
DropdownMenuLabel,
|
|
232
|
+
DropdownMenuPortal,
|
|
233
|
+
DropdownMenuRadioGroup,
|
|
234
|
+
DropdownMenuRadioItem,
|
|
235
|
+
DropdownMenuSeparator,
|
|
236
|
+
DropdownMenuShortcut,
|
|
237
|
+
DropdownMenuSub,
|
|
238
|
+
DropdownMenuSubContent,
|
|
239
|
+
DropdownMenuSubTrigger,
|
|
240
|
+
DropdownMenuTrigger,
|
|
241
|
+
dropdownMenuTriggerVariants,
|
|
242
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../../lib/utils';
|
|
4
|
+
|
|
5
|
+
function Empty({ className, ...props }: React.ComponentProps<'div'>) {
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
data-slot="empty"
|
|
9
|
+
className={cn(
|
|
10
|
+
'gap-4 rounded-lg border-dashed p-12 flex w-full min-w-0 flex-1 flex-col items-center justify-center text-center text-balance',
|
|
11
|
+
className,
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function EmptyHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
data-slot="empty-header"
|
|
22
|
+
className={cn('gap-2 flex max-w-sm flex-col items-center', className)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const emptyMediaVariants = cva(
|
|
29
|
+
'mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0',
|
|
30
|
+
{
|
|
31
|
+
variants: {
|
|
32
|
+
variant: {
|
|
33
|
+
default: 'bg-transparent',
|
|
34
|
+
icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
defaultVariants: {
|
|
38
|
+
variant: 'default',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
function EmptyMedia({
|
|
44
|
+
className,
|
|
45
|
+
variant = 'default',
|
|
46
|
+
...props
|
|
47
|
+
}: React.ComponentProps<'div'> & VariantProps<typeof emptyMediaVariants>) {
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
data-slot="empty-icon"
|
|
51
|
+
data-variant={variant}
|
|
52
|
+
className={cn(emptyMediaVariants({ variant, className }))}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function EmptyTitle({ className, ...props }: React.ComponentProps<'div'>) {
|
|
59
|
+
return (
|
|
60
|
+
<div
|
|
61
|
+
data-slot="empty-title"
|
|
62
|
+
className={cn('text-lg font-medium tracking-tight', className)}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function EmptyDescription({ className, ...props }: React.ComponentProps<'p'>) {
|
|
69
|
+
return (
|
|
70
|
+
<div
|
|
71
|
+
data-slot="empty-description"
|
|
72
|
+
className={cn(
|
|
73
|
+
'text-sm/relaxed text-muted-foreground [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',
|
|
74
|
+
className,
|
|
75
|
+
)}
|
|
76
|
+
{...props}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function EmptyContent({ className, ...props }: React.ComponentProps<'div'>) {
|
|
82
|
+
return (
|
|
83
|
+
<div
|
|
84
|
+
data-slot="empty-content"
|
|
85
|
+
className={cn(
|
|
86
|
+
'gap-4 text-sm flex w-full max-w-sm min-w-0 flex-col items-center text-balance',
|
|
87
|
+
className,
|
|
88
|
+
)}
|
|
89
|
+
{...props}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle };
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
4
|
+
import { useMemo } from 'react';
|
|
5
|
+
|
|
6
|
+
import { Separator as SeparatorPrimitive } from '@base-ui/react/separator';
|
|
7
|
+
import { cn } from '../../../lib/utils';
|
|
8
|
+
|
|
9
|
+
function FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {
|
|
10
|
+
return (
|
|
11
|
+
<fieldset
|
|
12
|
+
data-slot="field-set"
|
|
13
|
+
className={cn(
|
|
14
|
+
'gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3 flex flex-col',
|
|
15
|
+
className,
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function FieldLegend({
|
|
23
|
+
className,
|
|
24
|
+
variant = 'legend',
|
|
25
|
+
...props
|
|
26
|
+
}: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {
|
|
27
|
+
return (
|
|
28
|
+
<legend
|
|
29
|
+
data-slot="field-legend"
|
|
30
|
+
data-variant={variant}
|
|
31
|
+
className={cn(
|
|
32
|
+
'mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base',
|
|
33
|
+
className,
|
|
34
|
+
)}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function FieldGroup({ className, ...props }: React.ComponentProps<'div'>) {
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
data-slot="field-group"
|
|
44
|
+
className={cn(
|
|
45
|
+
'gap-5 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4 group/field-group @container/field-group flex w-full flex-col',
|
|
46
|
+
className,
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const fieldVariants = cva('data-[invalid=true]:text-destructive gap-2 group/field flex w-full', {
|
|
54
|
+
variants: {
|
|
55
|
+
orientation: {
|
|
56
|
+
vertical: 'flex-col [&>*]:w-full [&>.sr-only]:w-auto',
|
|
57
|
+
horizontal:
|
|
58
|
+
'flex-row items-center [&>[data-slot=field-label]]:flex-auto has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',
|
|
59
|
+
responsive:
|
|
60
|
+
'flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto @md/field-group:[&>[data-slot=field-label]]:flex-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
defaultVariants: {
|
|
64
|
+
orientation: 'vertical',
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
function Field({
|
|
69
|
+
className,
|
|
70
|
+
orientation = 'vertical',
|
|
71
|
+
...props
|
|
72
|
+
}: React.ComponentProps<'div'> & VariantProps<typeof fieldVariants>) {
|
|
73
|
+
return (
|
|
74
|
+
<div
|
|
75
|
+
role="group"
|
|
76
|
+
data-slot="field"
|
|
77
|
+
data-orientation={orientation}
|
|
78
|
+
className={cn(fieldVariants({ orientation }), className)}
|
|
79
|
+
{...props}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function FieldContent({ className, ...props }: React.ComponentProps<'div'>) {
|
|
85
|
+
return (
|
|
86
|
+
<div
|
|
87
|
+
data-slot="field-content"
|
|
88
|
+
className={cn('gap-0.5 group/field-content flex flex-1 flex-col leading-snug', className)}
|
|
89
|
+
{...props}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function FieldLabel({ ...props }: Omit<React.ComponentProps<'label'>, 'className'>) {
|
|
95
|
+
return (
|
|
96
|
+
<label
|
|
97
|
+
data-slot="field-label"
|
|
98
|
+
className="gap-2 text-sm leading-none font-medium group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed has-data-checked:bg-primary/5 has-data-checked:border-primary dark:has-data-checked:bg-primary/10 group-data-[disabled=true]/field:opacity-50 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-2.5 group/field-label peer/field-label w-fit leading-snug has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col"
|
|
99
|
+
{...props}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {
|
|
105
|
+
return (
|
|
106
|
+
<div
|
|
107
|
+
data-slot="field-label"
|
|
108
|
+
className={cn(
|
|
109
|
+
'gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50 flex w-fit items-center leading-snug',
|
|
110
|
+
className,
|
|
111
|
+
)}
|
|
112
|
+
{...props}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function FieldDescription({ className, ...props }: React.ComponentProps<'p'>) {
|
|
118
|
+
return (
|
|
119
|
+
<p
|
|
120
|
+
data-slot="field-description"
|
|
121
|
+
className={cn(
|
|
122
|
+
'text-muted-foreground text-left text-sm [[data-variant=legend]+&]:-mt-1.5 leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance',
|
|
123
|
+
'last:mt-0 nth-last-2:-mt-1',
|
|
124
|
+
'[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',
|
|
125
|
+
className,
|
|
126
|
+
)}
|
|
127
|
+
{...props}
|
|
128
|
+
/>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function FieldSeparator({
|
|
133
|
+
children,
|
|
134
|
+
...props
|
|
135
|
+
}: Omit<React.ComponentProps<'div'>, 'className'> & {
|
|
136
|
+
children?: React.ReactNode;
|
|
137
|
+
}) {
|
|
138
|
+
return (
|
|
139
|
+
<div
|
|
140
|
+
data-slot="field-separator"
|
|
141
|
+
data-content={!!children}
|
|
142
|
+
className="-my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2 relative"
|
|
143
|
+
{...props}
|
|
144
|
+
>
|
|
145
|
+
<SeparatorPrimitive className="bg-border shrink-0 h-px w-full absolute inset-0 top-1/2" />
|
|
146
|
+
{children && (
|
|
147
|
+
<span
|
|
148
|
+
className="text-muted-foreground px-2 bg-background relative mx-auto block w-fit"
|
|
149
|
+
data-slot="field-separator-content"
|
|
150
|
+
>
|
|
151
|
+
{children}
|
|
152
|
+
</span>
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function FieldError({
|
|
159
|
+
className,
|
|
160
|
+
children,
|
|
161
|
+
errors,
|
|
162
|
+
...props
|
|
163
|
+
}: React.ComponentProps<'div'> & {
|
|
164
|
+
errors?: Array<{ message?: string } | undefined>;
|
|
165
|
+
}) {
|
|
166
|
+
const content = useMemo(() => {
|
|
167
|
+
if (children) {
|
|
168
|
+
return children;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (!errors?.length) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const uniqueErrors = [...new Map(errors.map((error) => [error?.message, error])).values()];
|
|
176
|
+
|
|
177
|
+
if (uniqueErrors?.length == 1) {
|
|
178
|
+
return uniqueErrors[0]?.message;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<ul className="ml-4 flex list-disc flex-col gap-1">
|
|
183
|
+
{uniqueErrors.map((error, index) => error?.message && <li key={index}>{error.message}</li>)}
|
|
184
|
+
</ul>
|
|
185
|
+
);
|
|
186
|
+
}, [children, errors]);
|
|
187
|
+
|
|
188
|
+
if (!content) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<div
|
|
194
|
+
role="alert"
|
|
195
|
+
data-slot="field-error"
|
|
196
|
+
className={cn('text-destructive text-sm font-normal', className)}
|
|
197
|
+
{...props}
|
|
198
|
+
>
|
|
199
|
+
{content}
|
|
200
|
+
</div>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export {
|
|
205
|
+
Field,
|
|
206
|
+
FieldContent,
|
|
207
|
+
FieldDescription,
|
|
208
|
+
FieldError,
|
|
209
|
+
FieldGroup,
|
|
210
|
+
FieldLabel,
|
|
211
|
+
FieldLegend,
|
|
212
|
+
FieldSeparator,
|
|
213
|
+
FieldSet,
|
|
214
|
+
FieldTitle,
|
|
215
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { cn } from '../../../lib/utils';
|
|
5
|
+
|
|
6
|
+
const gridVariants = cva('grid', {
|
|
7
|
+
variants: {
|
|
8
|
+
gap: {
|
|
9
|
+
'0': 'gap-0',
|
|
10
|
+
'1': 'gap-1',
|
|
11
|
+
'2': 'gap-2',
|
|
12
|
+
'3': 'gap-3',
|
|
13
|
+
'4': 'gap-4',
|
|
14
|
+
'5': 'gap-5',
|
|
15
|
+
'6': 'gap-6',
|
|
16
|
+
'8': 'gap-8',
|
|
17
|
+
'10': 'gap-10',
|
|
18
|
+
'12': 'gap-12',
|
|
19
|
+
},
|
|
20
|
+
align: {
|
|
21
|
+
start: 'items-start',
|
|
22
|
+
center: 'items-center',
|
|
23
|
+
end: 'items-end',
|
|
24
|
+
stretch: 'items-stretch',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
defaultVariants: {
|
|
28
|
+
gap: '4',
|
|
29
|
+
align: 'stretch',
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const gridColsVariants = cva('', {
|
|
34
|
+
variants: {
|
|
35
|
+
cols: {
|
|
36
|
+
'1': 'grid-cols-1',
|
|
37
|
+
'2': 'grid-cols-2',
|
|
38
|
+
'3': 'grid-cols-3',
|
|
39
|
+
'4': 'grid-cols-4',
|
|
40
|
+
'5': 'grid-cols-5',
|
|
41
|
+
'6': 'grid-cols-6',
|
|
42
|
+
'12': 'grid-cols-12',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
defaultVariants: {
|
|
46
|
+
cols: '1',
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
type ResponsiveCols = {
|
|
51
|
+
base?: '1' | '2' | '3' | '4' | '5' | '6' | '12';
|
|
52
|
+
sm?: '1' | '2' | '3' | '4' | '5' | '6' | '12';
|
|
53
|
+
md?: '1' | '2' | '3' | '4' | '5' | '6' | '12';
|
|
54
|
+
lg?: '1' | '2' | '3' | '4' | '5' | '6' | '12';
|
|
55
|
+
xl?: '1' | '2' | '3' | '4' | '5' | '6' | '12';
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const responsiveColsMap: Record<string, Record<string, string>> = {
|
|
59
|
+
base: {
|
|
60
|
+
'1': 'grid-cols-1',
|
|
61
|
+
'2': 'grid-cols-2',
|
|
62
|
+
'3': 'grid-cols-3',
|
|
63
|
+
'4': 'grid-cols-4',
|
|
64
|
+
'5': 'grid-cols-5',
|
|
65
|
+
'6': 'grid-cols-6',
|
|
66
|
+
'12': 'grid-cols-12',
|
|
67
|
+
},
|
|
68
|
+
sm: {
|
|
69
|
+
'1': 'sm:grid-cols-1',
|
|
70
|
+
'2': 'sm:grid-cols-2',
|
|
71
|
+
'3': 'sm:grid-cols-3',
|
|
72
|
+
'4': 'sm:grid-cols-4',
|
|
73
|
+
'5': 'sm:grid-cols-5',
|
|
74
|
+
'6': 'sm:grid-cols-6',
|
|
75
|
+
'12': 'sm:grid-cols-12',
|
|
76
|
+
},
|
|
77
|
+
md: {
|
|
78
|
+
'1': 'md:grid-cols-1',
|
|
79
|
+
'2': 'md:grid-cols-2',
|
|
80
|
+
'3': 'md:grid-cols-3',
|
|
81
|
+
'4': 'md:grid-cols-4',
|
|
82
|
+
'5': 'md:grid-cols-5',
|
|
83
|
+
'6': 'md:grid-cols-6',
|
|
84
|
+
'12': 'md:grid-cols-12',
|
|
85
|
+
},
|
|
86
|
+
lg: {
|
|
87
|
+
'1': 'lg:grid-cols-1',
|
|
88
|
+
'2': 'lg:grid-cols-2',
|
|
89
|
+
'3': 'lg:grid-cols-3',
|
|
90
|
+
'4': 'lg:grid-cols-4',
|
|
91
|
+
'5': 'lg:grid-cols-5',
|
|
92
|
+
'6': 'lg:grid-cols-6',
|
|
93
|
+
'12': 'lg:grid-cols-12',
|
|
94
|
+
},
|
|
95
|
+
xl: {
|
|
96
|
+
'1': 'xl:grid-cols-1',
|
|
97
|
+
'2': 'xl:grid-cols-2',
|
|
98
|
+
'3': 'xl:grid-cols-3',
|
|
99
|
+
'4': 'xl:grid-cols-4',
|
|
100
|
+
'5': 'xl:grid-cols-5',
|
|
101
|
+
'6': 'xl:grid-cols-6',
|
|
102
|
+
'12': 'xl:grid-cols-12',
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const getResponsiveClasses = (cols: ResponsiveCols): string => {
|
|
107
|
+
const classes: string[] = [];
|
|
108
|
+
const withBase: ResponsiveCols = cols.base ? cols : { base: '1', ...cols };
|
|
109
|
+
|
|
110
|
+
for (const [breakpoint, value] of Object.entries(withBase)) {
|
|
111
|
+
if (value && responsiveColsMap[breakpoint]?.[value]) {
|
|
112
|
+
classes.push(responsiveColsMap[breakpoint][value]);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return classes.join(' ');
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
interface GridProps
|
|
120
|
+
extends
|
|
121
|
+
Omit<React.ComponentProps<'div'>, 'cols' | 'className'>,
|
|
122
|
+
VariantProps<typeof gridVariants> {
|
|
123
|
+
cols?: '1' | '2' | '3' | '4' | '5' | '6' | '12' | ResponsiveCols;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function Grid({ cols = '1', gap, align, ...props }: GridProps) {
|
|
127
|
+
const colsClasses =
|
|
128
|
+
typeof cols === 'object' ? getResponsiveClasses(cols) : gridColsVariants({ cols });
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<div data-slot="grid" className={cn(gridVariants({ gap, align }), colsClasses)} {...props} />
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export { Grid, gridVariants };
|