@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.
Files changed (71) hide show
  1. package/README.md +110 -0
  2. package/components.json +21 -0
  3. package/hooks/use-mobile.tsx +19 -0
  4. package/lib/utils.ts +6 -0
  5. package/package.json +103 -0
  6. package/postcss.config.mjs +8 -0
  7. package/src/components/ui/accordion.tsx +60 -0
  8. package/src/components/ui/alert-dialog.tsx +161 -0
  9. package/src/components/ui/alert.tsx +109 -0
  10. package/src/components/ui/aspect-ratio.tsx +21 -0
  11. package/src/components/ui/avatar.tsx +74 -0
  12. package/src/components/ui/badge.tsx +48 -0
  13. package/src/components/ui/breadcrumb.tsx +254 -0
  14. package/src/components/ui/button-group.tsx +89 -0
  15. package/src/components/ui/button.tsx +122 -0
  16. package/src/components/ui/calendar.tsx +190 -0
  17. package/src/components/ui/card.tsx +155 -0
  18. package/src/components/ui/carousel.tsx +216 -0
  19. package/src/components/ui/chart.tsx +325 -0
  20. package/src/components/ui/checkbox.tsx +22 -0
  21. package/src/components/ui/collapsible.tsx +17 -0
  22. package/src/components/ui/combobox.tsx +248 -0
  23. package/src/components/ui/command.tsx +189 -0
  24. package/src/components/ui/container.tsx +34 -0
  25. package/src/components/ui/context-menu.tsx +235 -0
  26. package/src/components/ui/dialog.tsx +122 -0
  27. package/src/components/ui/drawer.tsx +102 -0
  28. package/src/components/ui/dropdown-menu.tsx +242 -0
  29. package/src/components/ui/empty.tsx +94 -0
  30. package/src/components/ui/field.tsx +215 -0
  31. package/src/components/ui/grid.tsx +135 -0
  32. package/src/components/ui/heading.tsx +56 -0
  33. package/src/components/ui/hover-card.tsx +46 -0
  34. package/src/components/ui/index.ts +61 -0
  35. package/src/components/ui/input-group.tsx +128 -0
  36. package/src/components/ui/input-otp.tsx +84 -0
  37. package/src/components/ui/input.tsx +15 -0
  38. package/src/components/ui/item.tsx +188 -0
  39. package/src/components/ui/kbd.tsx +26 -0
  40. package/src/components/ui/label.tsx +15 -0
  41. package/src/components/ui/menubar.tsx +163 -0
  42. package/src/components/ui/navigation-menu.tsx +147 -0
  43. package/src/components/ui/page-header.tsx +51 -0
  44. package/src/components/ui/page-layout.tsx +65 -0
  45. package/src/components/ui/pagination.tsx +104 -0
  46. package/src/components/ui/popover.tsx +57 -0
  47. package/src/components/ui/progress.tsx +61 -0
  48. package/src/components/ui/radio-group.tsx +37 -0
  49. package/src/components/ui/resizable.tsx +41 -0
  50. package/src/components/ui/scroll-area.tsx +48 -0
  51. package/src/components/ui/section.tsx +64 -0
  52. package/src/components/ui/select.tsx +166 -0
  53. package/src/components/ui/separator.tsx +17 -0
  54. package/src/components/ui/sheet.tsx +104 -0
  55. package/src/components/ui/sidebar.tsx +707 -0
  56. package/src/components/ui/skeleton.tsx +5 -0
  57. package/src/components/ui/slider.tsx +51 -0
  58. package/src/components/ui/sonner.tsx +43 -0
  59. package/src/components/ui/spinner.tsx +14 -0
  60. package/src/components/ui/stack.tsx +72 -0
  61. package/src/components/ui/switch.tsx +26 -0
  62. package/src/components/ui/table.tsx +65 -0
  63. package/src/components/ui/tabs.tsx +69 -0
  64. package/src/components/ui/text.tsx +59 -0
  65. package/src/components/ui/textarea.tsx +13 -0
  66. package/src/components/ui/toggle-group.tsx +87 -0
  67. package/src/components/ui/toggle.tsx +42 -0
  68. package/src/components/ui/tooltip.tsx +52 -0
  69. package/src/index.ts +3 -0
  70. package/src/styles/globals.css +122 -0
  71. 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 };