@spree/dashboard-ui 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 (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -0
  3. package/package.json +86 -0
  4. package/src/hooks/use-copy-to-clipboard.ts +44 -0
  5. package/src/hooks/use-mobile.ts +19 -0
  6. package/src/hooks/use-scrolled.ts +30 -0
  7. package/src/index.ts +99 -0
  8. package/src/lib/date-locale.ts +29 -0
  9. package/src/lib/utils.ts +6 -0
  10. package/src/lib/validation-messages.ts +19 -0
  11. package/src/spree/address-block.tsx +74 -0
  12. package/src/spree/back-button.tsx +48 -0
  13. package/src/spree/bulk-dialog.tsx +71 -0
  14. package/src/spree/bulk-price-table.tsx +254 -0
  15. package/src/spree/calculator-summary.tsx +120 -0
  16. package/src/spree/color-picker.tsx +202 -0
  17. package/src/spree/confirm-dialog.tsx +82 -0
  18. package/src/spree/copy-to-clipboard-button.tsx +90 -0
  19. package/src/spree/country-flag.tsx +30 -0
  20. package/src/spree/data-grid/cells.tsx +415 -0
  21. package/src/spree/data-grid/context.ts +59 -0
  22. package/src/spree/data-grid/data-grid.tsx +214 -0
  23. package/src/spree/data-grid/fill-handle.tsx +153 -0
  24. package/src/spree/data-grid/helpers.ts +18 -0
  25. package/src/spree/data-grid/index.ts +5 -0
  26. package/src/spree/data-grid/types.ts +36 -0
  27. package/src/spree/data-grid/use-data-grid-keyboard.ts +254 -0
  28. package/src/spree/data-grid/use-stable-cell-registration.ts +45 -0
  29. package/src/spree/drag-handle.tsx +51 -0
  30. package/src/spree/form-actions.tsx +140 -0
  31. package/src/spree/json-preview-drawer.tsx +254 -0
  32. package/src/spree/json-value-view.tsx +149 -0
  33. package/src/spree/language-menu-items.tsx +54 -0
  34. package/src/spree/metadata/metadata-card.tsx +62 -0
  35. package/src/spree/relative-time.tsx +49 -0
  36. package/src/spree/resource-combobox.tsx +108 -0
  37. package/src/spree/resource-layout.tsx +31 -0
  38. package/src/spree/resource-multi-autocomplete.tsx +180 -0
  39. package/src/spree/resource-name-cell.tsx +44 -0
  40. package/src/spree/route-error-boundary.tsx +102 -0
  41. package/src/spree/row-actions.tsx +85 -0
  42. package/src/spree/row-click-bridge.tsx +17 -0
  43. package/src/spree/secret-input.tsx +137 -0
  44. package/src/spree/storefront-visible-switch.tsx +58 -0
  45. package/src/spree/tag-list.tsx +26 -0
  46. package/src/spree/theme-provider.tsx +83 -0
  47. package/src/spree/theme-toggle.tsx +35 -0
  48. package/src/styles.css +338 -0
  49. package/src/ui/attachment.tsx +187 -0
  50. package/src/ui/avatar.tsx +94 -0
  51. package/src/ui/badge.tsx +161 -0
  52. package/src/ui/breadcrumb.tsx +109 -0
  53. package/src/ui/button.tsx +60 -0
  54. package/src/ui/calendar.tsx +66 -0
  55. package/src/ui/card.tsx +72 -0
  56. package/src/ui/chart.tsx +94 -0
  57. package/src/ui/checkbox.tsx +50 -0
  58. package/src/ui/collapsible.tsx +18 -0
  59. package/src/ui/combobox.tsx +270 -0
  60. package/src/ui/command.tsx +172 -0
  61. package/src/ui/data-table.tsx +60 -0
  62. package/src/ui/date-picker.tsx +253 -0
  63. package/src/ui/date-range-picker.tsx +150 -0
  64. package/src/ui/dialog.tsx +214 -0
  65. package/src/ui/dropdown-menu.tsx +345 -0
  66. package/src/ui/empty.tsx +94 -0
  67. package/src/ui/field.tsx +221 -0
  68. package/src/ui/input-group.tsx +141 -0
  69. package/src/ui/input.tsx +19 -0
  70. package/src/ui/label.tsx +29 -0
  71. package/src/ui/pagination.tsx +123 -0
  72. package/src/ui/popover.tsx +76 -0
  73. package/src/ui/progress.tsx +73 -0
  74. package/src/ui/radio-group.tsx +42 -0
  75. package/src/ui/rich-text-editor.tsx +263 -0
  76. package/src/ui/select.tsx +213 -0
  77. package/src/ui/separator.tsx +31 -0
  78. package/src/ui/sheet.tsx +208 -0
  79. package/src/ui/sidebar.tsx +679 -0
  80. package/src/ui/skeleton.tsx +13 -0
  81. package/src/ui/slot.tsx +35 -0
  82. package/src/ui/sonner.tsx +47 -0
  83. package/src/ui/switch.tsx +39 -0
  84. package/src/ui/textarea.tsx +18 -0
  85. package/src/ui/tooltip.tsx +100 -0
@@ -0,0 +1,345 @@
1
+ 'use client'
2
+
3
+ import { Menu as MenuPrimitive } from '@base-ui/react/menu'
4
+ import { CheckIcon, ChevronRightIcon } from 'lucide-react'
5
+ import * as React from 'react'
6
+ import { cn } from '../lib/utils'
7
+
8
+ function DropdownMenu({
9
+ children,
10
+ open,
11
+ defaultOpen,
12
+ onOpenChange,
13
+ modal,
14
+ ...props
15
+ }: {
16
+ children?: React.ReactNode
17
+ open?: boolean
18
+ defaultOpen?: boolean
19
+ onOpenChange?: (open: boolean, eventDetails: MenuPrimitive.Root.ChangeEventDetails) => void
20
+ modal?: boolean
21
+ }) {
22
+ return (
23
+ <MenuPrimitive.Root
24
+ data-slot="dropdown-menu"
25
+ open={open}
26
+ defaultOpen={defaultOpen}
27
+ onOpenChange={onOpenChange}
28
+ modal={modal}
29
+ {...props}
30
+ >
31
+ {children}
32
+ </MenuPrimitive.Root>
33
+ )
34
+ }
35
+
36
+ function DropdownMenuPortal({
37
+ children,
38
+ ...props
39
+ }: React.ComponentProps<typeof MenuPrimitive.Portal>) {
40
+ return (
41
+ <MenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props}>
42
+ {children}
43
+ </MenuPrimitive.Portal>
44
+ )
45
+ }
46
+
47
+ function DropdownMenuTrigger({
48
+ asChild,
49
+ children,
50
+ ...props
51
+ }: React.ComponentProps<typeof MenuPrimitive.Trigger> & {
52
+ asChild?: boolean
53
+ }) {
54
+ if (asChild && React.isValidElement(children)) {
55
+ return <MenuPrimitive.Trigger data-slot="dropdown-menu-trigger" render={children} {...props} />
56
+ }
57
+ return (
58
+ <MenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props}>
59
+ {children}
60
+ </MenuPrimitive.Trigger>
61
+ )
62
+ }
63
+
64
+ function DropdownMenuContent({
65
+ className,
66
+ align = 'start',
67
+ sideOffset = 4,
68
+ side = 'bottom',
69
+ children,
70
+ ...props
71
+ }: {
72
+ className?: string
73
+ align?: 'start' | 'center' | 'end'
74
+ sideOffset?: number
75
+ side?: 'top' | 'bottom' | 'left' | 'right'
76
+ children?: React.ReactNode
77
+ } & Omit<React.ComponentProps<typeof MenuPrimitive.Popup>, 'className'>) {
78
+ return (
79
+ <MenuPrimitive.Portal>
80
+ <MenuPrimitive.Positioner side={side} sideOffset={sideOffset} align={align} className="z-50">
81
+ <MenuPrimitive.Popup
82
+ data-slot="dropdown-menu-content"
83
+ className={cn(
84
+ 'z-50 max-h-[var(--available-height)] min-w-40 max-w-80 overflow-x-hidden overflow-y-auto rounded-2xl border border-border bg-popover p-1 text-popover-foreground shadow-lg duration-100 data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95 transition-[opacity,transform]',
85
+ className,
86
+ )}
87
+ {...props}
88
+ >
89
+ {children}
90
+ </MenuPrimitive.Popup>
91
+ </MenuPrimitive.Positioner>
92
+ </MenuPrimitive.Portal>
93
+ )
94
+ }
95
+
96
+ function DropdownMenuGroup({ ...props }: React.ComponentProps<typeof MenuPrimitive.Group>) {
97
+ return <MenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
98
+ }
99
+
100
+ function DropdownMenuItem({
101
+ className,
102
+ inset,
103
+ variant = 'default',
104
+ asChild,
105
+ children,
106
+ ...props
107
+ }: React.ComponentProps<typeof MenuPrimitive.Item> & {
108
+ inset?: boolean
109
+ variant?: 'default' | 'destructive'
110
+ asChild?: boolean
111
+ }) {
112
+ if (asChild && React.isValidElement(children)) {
113
+ return (
114
+ <MenuPrimitive.Item
115
+ data-slot="dropdown-menu-item"
116
+ data-inset={inset}
117
+ data-variant={variant}
118
+ render={children}
119
+ className={cn(
120
+ "group/dropdown-menu-item relative flex cursor-default items-center gap-3 rounded-xl px-2.5 py-2 text-sm outline-hidden select-none transition-colors duration-75 focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
121
+ className,
122
+ )}
123
+ {...props}
124
+ />
125
+ )
126
+ }
127
+
128
+ return (
129
+ <MenuPrimitive.Item
130
+ data-slot="dropdown-menu-item"
131
+ data-inset={inset}
132
+ data-variant={variant}
133
+ className={cn(
134
+ "group/dropdown-menu-item relative flex cursor-default items-center gap-3 rounded-xl px-2.5 py-2 text-sm outline-hidden select-none transition-colors duration-75 focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
135
+ className,
136
+ )}
137
+ {...props}
138
+ >
139
+ {children}
140
+ </MenuPrimitive.Item>
141
+ )
142
+ }
143
+
144
+ function DropdownMenuCheckboxItem({
145
+ className,
146
+ children,
147
+ checked,
148
+ inset,
149
+ onCheckedChange,
150
+ ...props
151
+ }: Omit<React.ComponentProps<typeof MenuPrimitive.CheckboxItem>, 'onCheckedChange'> & {
152
+ inset?: boolean
153
+ checked?: boolean
154
+ onCheckedChange?: (checked: boolean) => void
155
+ }) {
156
+ return (
157
+ <MenuPrimitive.CheckboxItem
158
+ data-slot="dropdown-menu-checkbox-item"
159
+ data-inset={inset}
160
+ className={cn(
161
+ "relative flex cursor-default items-center gap-3 rounded-xl py-2 pr-8 pl-2.5 text-sm outline-hidden select-none transition-colors duration-75 focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
162
+ className,
163
+ )}
164
+ checked={checked}
165
+ onCheckedChange={onCheckedChange ? (checked) => onCheckedChange(checked) : undefined}
166
+ {...props}
167
+ >
168
+ <span
169
+ className="pointer-events-none absolute right-2 flex items-center justify-center"
170
+ data-slot="dropdown-menu-checkbox-item-indicator"
171
+ >
172
+ <MenuPrimitive.CheckboxItemIndicator keepMounted={false}>
173
+ <CheckIcon />
174
+ </MenuPrimitive.CheckboxItemIndicator>
175
+ </span>
176
+ {children}
177
+ </MenuPrimitive.CheckboxItem>
178
+ )
179
+ }
180
+
181
+ function DropdownMenuRadioGroup({
182
+ ...props
183
+ }: React.ComponentProps<typeof MenuPrimitive.RadioGroup>) {
184
+ return <MenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />
185
+ }
186
+
187
+ function DropdownMenuRadioItem({
188
+ className,
189
+ children,
190
+ inset,
191
+ ...props
192
+ }: React.ComponentProps<typeof MenuPrimitive.RadioItem> & {
193
+ inset?: boolean
194
+ }) {
195
+ return (
196
+ <MenuPrimitive.RadioItem
197
+ data-slot="dropdown-menu-radio-item"
198
+ data-inset={inset}
199
+ className={cn(
200
+ "relative flex cursor-default items-center gap-3 rounded-xl py-2 pr-8 pl-2.5 text-sm outline-hidden select-none transition-colors duration-75 focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
201
+ className,
202
+ )}
203
+ {...props}
204
+ >
205
+ <span
206
+ className="pointer-events-none absolute right-2 flex items-center justify-center"
207
+ data-slot="dropdown-menu-radio-item-indicator"
208
+ >
209
+ <MenuPrimitive.RadioItemIndicator keepMounted={false}>
210
+ <CheckIcon />
211
+ </MenuPrimitive.RadioItemIndicator>
212
+ </span>
213
+ {children}
214
+ </MenuPrimitive.RadioItem>
215
+ )
216
+ }
217
+
218
+ function DropdownMenuLabel({
219
+ className,
220
+ inset,
221
+ ...props
222
+ }: React.ComponentProps<typeof MenuPrimitive.GroupLabel> & {
223
+ inset?: boolean
224
+ }) {
225
+ return (
226
+ <MenuPrimitive.Group>
227
+ <MenuPrimitive.GroupLabel
228
+ data-slot="dropdown-menu-label"
229
+ data-inset={inset}
230
+ className={cn(
231
+ 'px-2.5 py-2 text-sm font-normal text-muted-foreground data-inset:pl-7',
232
+ className,
233
+ )}
234
+ {...props}
235
+ />
236
+ </MenuPrimitive.Group>
237
+ )
238
+ }
239
+
240
+ function DropdownMenuSeparator({
241
+ className,
242
+ ...props
243
+ }: React.ComponentProps<typeof MenuPrimitive.Separator>) {
244
+ return (
245
+ <MenuPrimitive.Separator
246
+ data-slot="dropdown-menu-separator"
247
+ className={cn('-mx-1 my-1 h-px bg-border', className)}
248
+ {...props}
249
+ />
250
+ )
251
+ }
252
+
253
+ function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<'span'>) {
254
+ return (
255
+ <span
256
+ data-slot="dropdown-menu-shortcut"
257
+ className={cn('ml-auto text-xs tracking-widest text-muted-foreground', className)}
258
+ {...props}
259
+ />
260
+ )
261
+ }
262
+
263
+ function DropdownMenuSub({
264
+ children,
265
+ ...props
266
+ }: {
267
+ children?: React.ReactNode
268
+ open?: boolean
269
+ defaultOpen?: boolean
270
+ onOpenChange?: (open: boolean, eventDetails: MenuPrimitive.SubmenuRoot.ChangeEventDetails) => void
271
+ }) {
272
+ return (
273
+ <MenuPrimitive.SubmenuRoot data-slot="dropdown-menu-sub" {...props}>
274
+ {children}
275
+ </MenuPrimitive.SubmenuRoot>
276
+ )
277
+ }
278
+
279
+ function DropdownMenuSubTrigger({
280
+ className,
281
+ inset,
282
+ children,
283
+ ...props
284
+ }: React.ComponentProps<typeof MenuPrimitive.SubmenuTrigger> & {
285
+ inset?: boolean
286
+ }) {
287
+ return (
288
+ <MenuPrimitive.SubmenuTrigger
289
+ data-slot="dropdown-menu-sub-trigger"
290
+ data-inset={inset}
291
+ className={cn(
292
+ "flex cursor-default items-center gap-3 rounded-xl px-2.5 py-2 text-sm outline-hidden select-none transition-colors duration-75 focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-[popup-open]:bg-accent data-[popup-open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
293
+ className,
294
+ )}
295
+ {...props}
296
+ >
297
+ {children}
298
+ <ChevronRightIcon className="ml-auto" />
299
+ </MenuPrimitive.SubmenuTrigger>
300
+ )
301
+ }
302
+
303
+ function DropdownMenuSubContent({
304
+ className,
305
+ children,
306
+ ...props
307
+ }: {
308
+ className?: string
309
+ children?: React.ReactNode
310
+ } & Omit<React.ComponentProps<typeof MenuPrimitive.Popup>, 'className'>) {
311
+ return (
312
+ <MenuPrimitive.Portal>
313
+ <MenuPrimitive.Positioner>
314
+ <MenuPrimitive.Popup
315
+ data-slot="dropdown-menu-sub-content"
316
+ className={cn(
317
+ 'z-50 min-w-[96px] overflow-hidden rounded-2xl border border-border bg-popover p-1 text-popover-foreground shadow-lg duration-100 data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95 transition-[opacity,transform]',
318
+ className,
319
+ )}
320
+ {...props}
321
+ >
322
+ {children}
323
+ </MenuPrimitive.Popup>
324
+ </MenuPrimitive.Positioner>
325
+ </MenuPrimitive.Portal>
326
+ )
327
+ }
328
+
329
+ export {
330
+ DropdownMenu,
331
+ DropdownMenuCheckboxItem,
332
+ DropdownMenuContent,
333
+ DropdownMenuGroup,
334
+ DropdownMenuItem,
335
+ DropdownMenuLabel,
336
+ DropdownMenuPortal,
337
+ DropdownMenuRadioGroup,
338
+ DropdownMenuRadioItem,
339
+ DropdownMenuSeparator,
340
+ DropdownMenuShortcut,
341
+ DropdownMenuSub,
342
+ DropdownMenuSubContent,
343
+ DropdownMenuSubTrigger,
344
+ DropdownMenuTrigger,
345
+ }
@@ -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
+ 'flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-xl border-dashed p-6 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('flex max-w-sm flex-col items-center gap-2', 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: "flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground opacity-50 [&_svg:not([class*='size-'])]:size-4",
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('cn-font-heading text-sm 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]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary',
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
+ 'flex w-full max-w-sm min-w-0 flex-col items-center gap-2.5 text-sm text-balance',
87
+ className,
88
+ )}
89
+ {...props}
90
+ />
91
+ )
92
+ }
93
+
94
+ export { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle }
@@ -0,0 +1,221 @@
1
+ import { cva, type VariantProps } from 'class-variance-authority'
2
+ import { useMemo } from 'react'
3
+ import { cn } from '../lib/utils'
4
+ import { Label } from './label'
5
+ import { Separator } from './separator'
6
+
7
+ function FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {
8
+ return (
9
+ <fieldset
10
+ data-slot="field-set"
11
+ className={cn(
12
+ 'flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3',
13
+ className,
14
+ )}
15
+ {...props}
16
+ />
17
+ )
18
+ }
19
+
20
+ function FieldLegend({
21
+ className,
22
+ variant = 'legend',
23
+ ...props
24
+ }: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {
25
+ return (
26
+ <legend
27
+ data-slot="field-legend"
28
+ data-variant={variant}
29
+ className={cn(
30
+ 'mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base',
31
+ className,
32
+ )}
33
+ {...props}
34
+ />
35
+ )
36
+ }
37
+
38
+ function FieldGroup({ className, ...props }: React.ComponentProps<'div'>) {
39
+ return (
40
+ <div
41
+ data-slot="field-group"
42
+ className={cn(
43
+ 'group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4',
44
+ className,
45
+ )}
46
+ {...props}
47
+ />
48
+ )
49
+ }
50
+
51
+ const fieldVariants = cva('group/field flex w-full gap-2 data-[invalid=true]:text-destructive', {
52
+ variants: {
53
+ orientation: {
54
+ vertical: 'flex-col *:w-full [&>.sr-only]:w-auto',
55
+ horizontal:
56
+ 'flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',
57
+ responsive:
58
+ 'flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',
59
+ },
60
+ },
61
+ defaultVariants: {
62
+ orientation: 'vertical',
63
+ },
64
+ })
65
+
66
+ function Field({
67
+ className,
68
+ orientation = 'vertical',
69
+ ...props
70
+ }: React.ComponentProps<'div'> & VariantProps<typeof fieldVariants>) {
71
+ return (
72
+ <div
73
+ role="group"
74
+ data-slot="field"
75
+ data-orientation={orientation}
76
+ className={cn(fieldVariants({ orientation }), className)}
77
+ {...props}
78
+ />
79
+ )
80
+ }
81
+
82
+ function FieldContent({ className, ...props }: React.ComponentProps<'div'>) {
83
+ return (
84
+ <div
85
+ data-slot="field-content"
86
+ className={cn('group/field-content flex flex-1 flex-col gap-0.5 leading-snug', className)}
87
+ {...props}
88
+ />
89
+ )
90
+ }
91
+
92
+ function FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>) {
93
+ return (
94
+ <Label
95
+ data-slot="field-label"
96
+ className={cn(
97
+ 'group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-blue-500/30 has-data-checked:bg-blue-500/5 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border *:data-[slot=field]:p-2.5 dark:has-data-checked:border-blue-500/20 dark:has-data-checked:bg-blue-500/10',
98
+ 'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col',
99
+ className,
100
+ )}
101
+ {...props}
102
+ />
103
+ )
104
+ }
105
+
106
+ function FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {
107
+ return (
108
+ <div
109
+ data-slot="field-label"
110
+ className={cn(
111
+ 'flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50',
112
+ className,
113
+ )}
114
+ {...props}
115
+ />
116
+ )
117
+ }
118
+
119
+ function FieldDescription({ className, ...props }: React.ComponentProps<'p'>) {
120
+ return (
121
+ <p
122
+ data-slot="field-description"
123
+ className={cn(
124
+ 'text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5',
125
+ 'last:mt-0 nth-last-2:-mt-1',
126
+ '[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary',
127
+ className,
128
+ )}
129
+ {...props}
130
+ />
131
+ )
132
+ }
133
+
134
+ function FieldSeparator({
135
+ children,
136
+ className,
137
+ ...props
138
+ }: React.ComponentProps<'div'> & {
139
+ children?: React.ReactNode
140
+ }) {
141
+ return (
142
+ <div
143
+ data-slot="field-separator"
144
+ data-content={!!children}
145
+ className={cn(
146
+ 'relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2',
147
+ className,
148
+ )}
149
+ {...props}
150
+ >
151
+ <Separator className="absolute inset-0 top-1/2" />
152
+ {children && (
153
+ <span
154
+ className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
155
+ data-slot="field-separator-content"
156
+ >
157
+ {children}
158
+ </span>
159
+ )}
160
+ </div>
161
+ )
162
+ }
163
+
164
+ function FieldError({
165
+ className,
166
+ children,
167
+ errors,
168
+ ...props
169
+ }: React.ComponentProps<'div'> & {
170
+ errors?: Array<{ message?: string } | undefined>
171
+ }) {
172
+ const content = useMemo(() => {
173
+ if (children) {
174
+ return children
175
+ }
176
+
177
+ if (!errors?.length) {
178
+ return null
179
+ }
180
+
181
+ const uniqueErrors = [...new Map(errors.map((error) => [error?.message, error])).values()]
182
+
183
+ if (uniqueErrors?.length == 1) {
184
+ return uniqueErrors[0]?.message
185
+ }
186
+
187
+ return (
188
+ <ul className="ml-4 flex list-disc flex-col gap-1">
189
+ {uniqueErrors.map((error, index) => error?.message && <li key={index}>{error.message}</li>)}
190
+ </ul>
191
+ )
192
+ }, [children, errors])
193
+
194
+ if (!content) {
195
+ return null
196
+ }
197
+
198
+ return (
199
+ <div
200
+ role="alert"
201
+ data-slot="field-error"
202
+ className={cn('text-sm font-normal text-destructive', className)}
203
+ {...props}
204
+ >
205
+ {content}
206
+ </div>
207
+ )
208
+ }
209
+
210
+ export {
211
+ Field,
212
+ FieldContent,
213
+ FieldDescription,
214
+ FieldError,
215
+ FieldGroup,
216
+ FieldLabel,
217
+ FieldLegend,
218
+ FieldSeparator,
219
+ FieldSet,
220
+ FieldTitle,
221
+ }