@majapisoftwares/ui 6.1.0 → 6.1.2

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 (73) hide show
  1. package/AGENTS.md +27 -27
  2. package/README.md +79 -79
  3. package/THIRD_PARTY_NOTICES.md +39 -39
  4. package/components.json +25 -25
  5. package/docs/MIGRATION-0.2.md +20 -20
  6. package/docs/MIGRATION-6.0.md +33 -33
  7. package/docs/NEXT-STARTER-VALIDATION.md +14 -14
  8. package/docs/STANDARDS.md +41 -41
  9. package/docs/VALIDATION.md +11 -11
  10. package/docs/VERSIONING.md +28 -28
  11. package/docs/validation/preference-titles.md +22 -0
  12. package/package.json +88 -84
  13. package/scripts/check-standards.mjs +41 -41
  14. package/src/components/ui/accordion.tsx +82 -82
  15. package/src/components/ui/alert-dialog.tsx +187 -187
  16. package/src/components/ui/alert.tsx +77 -77
  17. package/src/components/ui/avatar.tsx +108 -108
  18. package/src/components/ui/badge.tsx +55 -55
  19. package/src/components/ui/breadcrumb.tsx +125 -125
  20. package/src/components/ui/button-group.tsx +89 -89
  21. package/src/components/ui/button.tsx +59 -59
  22. package/src/components/ui/card.tsx +101 -101
  23. package/src/components/ui/checkbox.tsx +27 -27
  24. package/src/components/ui/collapsible.tsx +21 -21
  25. package/src/components/ui/context-menu.tsx +271 -271
  26. package/src/components/ui/dialog.tsx +161 -161
  27. package/src/components/ui/dropdown-menu.tsx +271 -271
  28. package/src/components/ui/empty.tsx +105 -105
  29. package/src/components/ui/field.tsx +238 -238
  30. package/src/components/ui/hover-card.tsx +50 -50
  31. package/src/components/ui/input-group.tsx +155 -155
  32. package/src/components/ui/input.tsx +21 -21
  33. package/src/components/ui/kbd.tsx +28 -28
  34. package/src/components/ui/label.tsx +19 -19
  35. package/src/components/ui/orb.css +1002 -1002
  36. package/src/components/ui/orb.tsx +752 -752
  37. package/src/components/ui/popover.tsx +89 -89
  38. package/src/components/ui/progress.tsx +82 -82
  39. package/src/components/ui/radio-group.tsx +37 -37
  40. package/src/components/ui/scroll-area.tsx +54 -54
  41. package/src/components/ui/select.tsx +204 -204
  42. package/src/components/ui/separator.tsx +24 -24
  43. package/src/components/ui/sheet.tsx +139 -139
  44. package/src/components/ui/sidebar.tsx +1039 -1035
  45. package/src/components/ui/skeleton.tsx +15 -15
  46. package/src/components/ui/slider.tsx +53 -53
  47. package/src/components/ui/sonner.tsx +44 -44
  48. package/src/components/ui/spinner.tsx +32 -32
  49. package/src/components/ui/switch.tsx +31 -31
  50. package/src/components/ui/table.tsx +121 -121
  51. package/src/components/ui/tabs.tsx +81 -81
  52. package/src/components/ui/textarea.tsx +19 -19
  53. package/src/components/ui/toggle-group.tsx +89 -89
  54. package/src/components/ui/toggle.tsx +44 -44
  55. package/src/components/ui/tooltip.tsx +65 -65
  56. package/src/hooks/use-mobile.ts +20 -20
  57. package/src/lib/i18n.tsx +109 -109
  58. package/src/lib/utils.ts +1 -1
  59. package/src/patterns/app-shell.tsx +177 -179
  60. package/src/patterns/data-table.tsx +2486 -2486
  61. package/src/patterns/details-sheet.tsx +54 -54
  62. package/src/patterns/filter-tabs.tsx +53 -53
  63. package/src/patterns/majapi-logo.tsx +32 -32
  64. package/src/patterns/page-header.tsx +62 -62
  65. package/src/patterns/preferences.tsx +15 -7
  66. package/src/patterns/status-badge.tsx +24 -24
  67. package/src/patterns/user-menu.tsx +218 -218
  68. package/src/patterns/workspace-switcher.tsx +176 -173
  69. package/src/styles.css +165 -165
  70. package/src/templates/admin-dashboard.tsx +44 -44
  71. package/src/templates/auth-layout.tsx +30 -30
  72. package/src/templates/login-form.tsx +129 -129
  73. package/src/templates/signup-form.tsx +177 -177
@@ -1,105 +1,105 @@
1
- "use client";
2
-
3
- import { cva, type VariantProps } from "class-variance-authority";
4
- import { cn } from "cn";
5
-
6
- function Empty({ className, ...props }: React.ComponentProps<"div">) {
7
- return (
8
- <div
9
- data-slot="empty"
10
- className={cn(
11
- "flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-2xl border-dashed p-12 text-center text-balance",
12
- className,
13
- )}
14
- {...props}
15
- />
16
- );
17
- }
18
-
19
- function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
20
- return (
21
- <div
22
- data-slot="empty-header"
23
- className={cn("flex max-w-sm flex-col items-center gap-2", className)}
24
- {...props}
25
- />
26
- );
27
- }
28
-
29
- const emptyMediaVariants = cva(
30
- "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
31
- {
32
- variants: {
33
- variant: {
34
- default: "bg-transparent",
35
- icon: "flex size-10 shrink-0 items-center justify-center rounded-xl bg-muted text-foreground [&_svg:not([class*='size-'])]:size-5",
36
- },
37
- },
38
- defaultVariants: {
39
- variant: "default",
40
- },
41
- },
42
- );
43
-
44
- function EmptyMedia({
45
- className,
46
- variant = "default",
47
- ...props
48
- }: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) {
49
- return (
50
- <div
51
- data-slot="empty-icon"
52
- data-variant={variant}
53
- className={cn(emptyMediaVariants({ variant, className }))}
54
- {...props}
55
- />
56
- );
57
- }
58
-
59
- function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
60
- return (
61
- <div
62
- data-slot="empty-title"
63
- className={cn(
64
- "font-heading text-lg font-medium tracking-tight",
65
- className,
66
- )}
67
- {...props}
68
- />
69
- );
70
- }
71
-
72
- function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
73
- return (
74
- <div
75
- data-slot="empty-description"
76
- className={cn(
77
- "text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
78
- className,
79
- )}
80
- {...props}
81
- />
82
- );
83
- }
84
-
85
- function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
86
- return (
87
- <div
88
- data-slot="empty-content"
89
- className={cn(
90
- "flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance",
91
- className,
92
- )}
93
- {...props}
94
- />
95
- );
96
- }
97
-
98
- export {
99
- Empty,
100
- EmptyHeader,
101
- EmptyTitle,
102
- EmptyDescription,
103
- EmptyContent,
104
- EmptyMedia,
105
- };
1
+ "use client";
2
+
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+ import { cn } from "cn";
5
+
6
+ function Empty({ className, ...props }: React.ComponentProps<"div">) {
7
+ return (
8
+ <div
9
+ data-slot="empty"
10
+ className={cn(
11
+ "flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-2xl border-dashed p-12 text-center text-balance",
12
+ className,
13
+ )}
14
+ {...props}
15
+ />
16
+ );
17
+ }
18
+
19
+ function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
20
+ return (
21
+ <div
22
+ data-slot="empty-header"
23
+ className={cn("flex max-w-sm flex-col items-center gap-2", className)}
24
+ {...props}
25
+ />
26
+ );
27
+ }
28
+
29
+ const emptyMediaVariants = cva(
30
+ "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
31
+ {
32
+ variants: {
33
+ variant: {
34
+ default: "bg-transparent",
35
+ icon: "flex size-10 shrink-0 items-center justify-center rounded-xl bg-muted text-foreground [&_svg:not([class*='size-'])]:size-5",
36
+ },
37
+ },
38
+ defaultVariants: {
39
+ variant: "default",
40
+ },
41
+ },
42
+ );
43
+
44
+ function EmptyMedia({
45
+ className,
46
+ variant = "default",
47
+ ...props
48
+ }: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) {
49
+ return (
50
+ <div
51
+ data-slot="empty-icon"
52
+ data-variant={variant}
53
+ className={cn(emptyMediaVariants({ variant, className }))}
54
+ {...props}
55
+ />
56
+ );
57
+ }
58
+
59
+ function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
60
+ return (
61
+ <div
62
+ data-slot="empty-title"
63
+ className={cn(
64
+ "font-heading text-lg font-medium tracking-tight",
65
+ className,
66
+ )}
67
+ {...props}
68
+ />
69
+ );
70
+ }
71
+
72
+ function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
73
+ return (
74
+ <div
75
+ data-slot="empty-description"
76
+ className={cn(
77
+ "text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
78
+ className,
79
+ )}
80
+ {...props}
81
+ />
82
+ );
83
+ }
84
+
85
+ function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
86
+ return (
87
+ <div
88
+ data-slot="empty-content"
89
+ className={cn(
90
+ "flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance",
91
+ className,
92
+ )}
93
+ {...props}
94
+ />
95
+ );
96
+ }
97
+
98
+ export {
99
+ Empty,
100
+ EmptyHeader,
101
+ EmptyTitle,
102
+ EmptyDescription,
103
+ EmptyContent,
104
+ EmptyMedia,
105
+ };
@@ -1,238 +1,238 @@
1
- "use client";
2
-
3
- import { useMemo } from "react";
4
- import { cva, type VariantProps } from "class-variance-authority";
5
- import { cn } from "cn";
6
-
7
- import { Label } from "./label";
8
- import { Separator } from "./separator";
9
-
10
- function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
11
- return (
12
- <fieldset
13
- data-slot="field-set"
14
- className={cn(
15
- "flex flex-col gap-6 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
16
- className,
17
- )}
18
- {...props}
19
- />
20
- );
21
- }
22
-
23
- function FieldLegend({
24
- className,
25
- variant = "legend",
26
- ...props
27
- }: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
28
- return (
29
- <legend
30
- data-slot="field-legend"
31
- data-variant={variant}
32
- className={cn(
33
- "mb-3 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base",
34
- className,
35
- )}
36
- {...props}
37
- />
38
- );
39
- }
40
-
41
- function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
42
- return (
43
- <div
44
- data-slot="field-group"
45
- className={cn(
46
- "group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4",
47
- className,
48
- )}
49
- {...props}
50
- />
51
- );
52
- }
53
-
54
- const fieldVariants = cva(
55
- "group/field flex w-full gap-3 data-[invalid=true]:text-destructive",
56
- {
57
- variants: {
58
- orientation: {
59
- vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
60
- horizontal:
61
- "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",
62
- responsive:
63
- "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",
64
- },
65
- },
66
- defaultVariants: {
67
- orientation: "vertical",
68
- },
69
- },
70
- );
71
-
72
- function Field({
73
- className,
74
- orientation = "vertical",
75
- ...props
76
- }: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
77
- return (
78
- <div
79
- role="group"
80
- data-slot="field"
81
- data-orientation={orientation}
82
- className={cn(fieldVariants({ orientation }), className)}
83
- {...props}
84
- />
85
- );
86
- }
87
-
88
- function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
89
- return (
90
- <div
91
- data-slot="field-content"
92
- className={cn(
93
- "group/field-content flex flex-1 flex-col gap-1 leading-snug",
94
- className,
95
- )}
96
- {...props}
97
- />
98
- );
99
- }
100
-
101
- function FieldLabel({
102
- className,
103
- ...props
104
- }: React.ComponentProps<typeof Label>) {
105
- return (
106
- <Label
107
- data-slot="field-label"
108
- className={cn(
109
- "group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:bg-input/30 has-[>[data-slot=field]]:rounded-2xl has-[>[data-slot=field]]:border has-[>[data-slot=field]]:not-has-[:disabled,[data-disabled]]:hover:bg-input/40 has-[>[data-slot=field]]:has-[:focus-visible]:border-ring has-[>[data-slot=field]]:has-[:focus-visible]:ring-3 has-[>[data-slot=field]]:has-[:focus-visible]:ring-ring/50 *:data-[slot=field]:p-4",
110
- "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
111
- className,
112
- )}
113
- {...props}
114
- />
115
- );
116
- }
117
-
118
- function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
119
- return (
120
- <div
121
- data-slot="field-label"
122
- className={cn(
123
- "flex w-fit items-center gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50",
124
- className,
125
- )}
126
- {...props}
127
- />
128
- );
129
- }
130
-
131
- function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
132
- return (
133
- <p
134
- data-slot="field-description"
135
- className={cn(
136
- "text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
137
- "last:mt-0 nth-last-2:-mt-1",
138
- "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
139
- className,
140
- )}
141
- {...props}
142
- />
143
- );
144
- }
145
-
146
- function FieldSeparator({
147
- children,
148
- className,
149
- ...props
150
- }: React.ComponentProps<"div"> & {
151
- children?: React.ReactNode;
152
- }) {
153
- return (
154
- <div
155
- data-slot="field-separator"
156
- data-content={!!children}
157
- className={cn(
158
- "relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
159
- className,
160
- )}
161
- {...props}
162
- >
163
- <Separator className="absolute inset-0 top-1/2" />
164
- {children && (
165
- <span
166
- className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
167
- data-slot="field-separator-content"
168
- >
169
- {children}
170
- </span>
171
- )}
172
- </div>
173
- );
174
- }
175
-
176
- function FieldError({
177
- className,
178
- children,
179
- errors,
180
- ...props
181
- }: React.ComponentProps<"div"> & {
182
- errors?: Array<{ message?: string } | undefined>;
183
- }) {
184
- const content = useMemo(() => {
185
- if (children) {
186
- return children;
187
- }
188
-
189
- if (!errors?.length) {
190
- return null;
191
- }
192
-
193
- const uniqueErrors = [
194
- ...new Map(errors.map((error) => [error?.message, error])).values(),
195
- ];
196
-
197
- if (uniqueErrors?.length == 1) {
198
- return uniqueErrors[0]?.message;
199
- }
200
-
201
- return (
202
- <ul className="ml-4 flex list-disc flex-col gap-1">
203
- {uniqueErrors.map(
204
- (error, index) =>
205
- error?.message && <li key={index}>{error.message}</li>,
206
- )}
207
- </ul>
208
- );
209
- }, [children, errors]);
210
-
211
- if (!content) {
212
- return null;
213
- }
214
-
215
- return (
216
- <div
217
- role="alert"
218
- data-slot="field-error"
219
- className={cn("text-sm font-normal text-destructive", className)}
220
- {...props}
221
- >
222
- {content}
223
- </div>
224
- );
225
- }
226
-
227
- export {
228
- Field,
229
- FieldLabel,
230
- FieldDescription,
231
- FieldError,
232
- FieldGroup,
233
- FieldLegend,
234
- FieldSeparator,
235
- FieldSet,
236
- FieldContent,
237
- FieldTitle,
238
- };
1
+ "use client";
2
+
3
+ import { useMemo } from "react";
4
+ import { cva, type VariantProps } from "class-variance-authority";
5
+ import { cn } from "cn";
6
+
7
+ import { Label } from "./label";
8
+ import { Separator } from "./separator";
9
+
10
+ function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
11
+ return (
12
+ <fieldset
13
+ data-slot="field-set"
14
+ className={cn(
15
+ "flex flex-col gap-6 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
16
+ className,
17
+ )}
18
+ {...props}
19
+ />
20
+ );
21
+ }
22
+
23
+ function FieldLegend({
24
+ className,
25
+ variant = "legend",
26
+ ...props
27
+ }: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
28
+ return (
29
+ <legend
30
+ data-slot="field-legend"
31
+ data-variant={variant}
32
+ className={cn(
33
+ "mb-3 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base",
34
+ className,
35
+ )}
36
+ {...props}
37
+ />
38
+ );
39
+ }
40
+
41
+ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
42
+ return (
43
+ <div
44
+ data-slot="field-group"
45
+ className={cn(
46
+ "group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4",
47
+ className,
48
+ )}
49
+ {...props}
50
+ />
51
+ );
52
+ }
53
+
54
+ const fieldVariants = cva(
55
+ "group/field flex w-full gap-3 data-[invalid=true]:text-destructive",
56
+ {
57
+ variants: {
58
+ orientation: {
59
+ vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
60
+ horizontal:
61
+ "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",
62
+ responsive:
63
+ "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",
64
+ },
65
+ },
66
+ defaultVariants: {
67
+ orientation: "vertical",
68
+ },
69
+ },
70
+ );
71
+
72
+ function Field({
73
+ className,
74
+ orientation = "vertical",
75
+ ...props
76
+ }: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
77
+ return (
78
+ <div
79
+ role="group"
80
+ data-slot="field"
81
+ data-orientation={orientation}
82
+ className={cn(fieldVariants({ orientation }), className)}
83
+ {...props}
84
+ />
85
+ );
86
+ }
87
+
88
+ function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
89
+ return (
90
+ <div
91
+ data-slot="field-content"
92
+ className={cn(
93
+ "group/field-content flex flex-1 flex-col gap-1 leading-snug",
94
+ className,
95
+ )}
96
+ {...props}
97
+ />
98
+ );
99
+ }
100
+
101
+ function FieldLabel({
102
+ className,
103
+ ...props
104
+ }: React.ComponentProps<typeof Label>) {
105
+ return (
106
+ <Label
107
+ data-slot="field-label"
108
+ className={cn(
109
+ "group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:bg-input/30 has-[>[data-slot=field]]:rounded-2xl has-[>[data-slot=field]]:border has-[>[data-slot=field]]:not-has-[:disabled,[data-disabled]]:hover:bg-input/40 has-[>[data-slot=field]]:has-[:focus-visible]:border-ring has-[>[data-slot=field]]:has-[:focus-visible]:ring-3 has-[>[data-slot=field]]:has-[:focus-visible]:ring-ring/50 *:data-[slot=field]:p-4",
110
+ "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
111
+ className,
112
+ )}
113
+ {...props}
114
+ />
115
+ );
116
+ }
117
+
118
+ function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
119
+ return (
120
+ <div
121
+ data-slot="field-label"
122
+ className={cn(
123
+ "flex w-fit items-center gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50",
124
+ className,
125
+ )}
126
+ {...props}
127
+ />
128
+ );
129
+ }
130
+
131
+ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
132
+ return (
133
+ <p
134
+ data-slot="field-description"
135
+ className={cn(
136
+ "text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
137
+ "last:mt-0 nth-last-2:-mt-1",
138
+ "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
139
+ className,
140
+ )}
141
+ {...props}
142
+ />
143
+ );
144
+ }
145
+
146
+ function FieldSeparator({
147
+ children,
148
+ className,
149
+ ...props
150
+ }: React.ComponentProps<"div"> & {
151
+ children?: React.ReactNode;
152
+ }) {
153
+ return (
154
+ <div
155
+ data-slot="field-separator"
156
+ data-content={!!children}
157
+ className={cn(
158
+ "relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
159
+ className,
160
+ )}
161
+ {...props}
162
+ >
163
+ <Separator className="absolute inset-0 top-1/2" />
164
+ {children && (
165
+ <span
166
+ className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
167
+ data-slot="field-separator-content"
168
+ >
169
+ {children}
170
+ </span>
171
+ )}
172
+ </div>
173
+ );
174
+ }
175
+
176
+ function FieldError({
177
+ className,
178
+ children,
179
+ errors,
180
+ ...props
181
+ }: React.ComponentProps<"div"> & {
182
+ errors?: Array<{ message?: string } | undefined>;
183
+ }) {
184
+ const content = useMemo(() => {
185
+ if (children) {
186
+ return children;
187
+ }
188
+
189
+ if (!errors?.length) {
190
+ return null;
191
+ }
192
+
193
+ const uniqueErrors = [
194
+ ...new Map(errors.map((error) => [error?.message, error])).values(),
195
+ ];
196
+
197
+ if (uniqueErrors?.length == 1) {
198
+ return uniqueErrors[0]?.message;
199
+ }
200
+
201
+ return (
202
+ <ul className="ml-4 flex list-disc flex-col gap-1">
203
+ {uniqueErrors.map(
204
+ (error, index) =>
205
+ error?.message && <li key={index}>{error.message}</li>,
206
+ )}
207
+ </ul>
208
+ );
209
+ }, [children, errors]);
210
+
211
+ if (!content) {
212
+ return null;
213
+ }
214
+
215
+ return (
216
+ <div
217
+ role="alert"
218
+ data-slot="field-error"
219
+ className={cn("text-sm font-normal text-destructive", className)}
220
+ {...props}
221
+ >
222
+ {content}
223
+ </div>
224
+ );
225
+ }
226
+
227
+ export {
228
+ Field,
229
+ FieldLabel,
230
+ FieldDescription,
231
+ FieldError,
232
+ FieldGroup,
233
+ FieldLegend,
234
+ FieldSeparator,
235
+ FieldSet,
236
+ FieldContent,
237
+ FieldTitle,
238
+ };