@hobenakicoffee/libraries 1.11.0 → 1.13.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 (76) hide show
  1. package/README.md +25 -4
  2. package/package.json +84 -9
  3. package/src/App.tsx +28 -0
  4. package/src/components/turnstile-captcha.tsx +47 -0
  5. package/src/components/ui/alert-dialog.tsx +196 -0
  6. package/src/components/ui/alert.tsx +76 -0
  7. package/src/components/ui/avatar.tsx +110 -0
  8. package/src/components/ui/badge.tsx +49 -0
  9. package/src/components/ui/breadcrumb.tsx +122 -0
  10. package/src/components/ui/button-group.tsx +82 -0
  11. package/src/components/ui/button.tsx +77 -0
  12. package/src/components/ui/calendar.tsx +235 -0
  13. package/src/components/ui/card.tsx +100 -0
  14. package/src/components/ui/chart.tsx +364 -0
  15. package/src/components/ui/checkbox.tsx +30 -0
  16. package/src/components/ui/dialog.tsx +162 -0
  17. package/src/components/ui/drawer.tsx +126 -0
  18. package/src/components/ui/dropdown-menu.tsx +267 -0
  19. package/src/components/ui/empty-minimal.tsx +20 -0
  20. package/src/components/ui/empty.tsx +101 -0
  21. package/src/components/ui/field.tsx +235 -0
  22. package/src/components/ui/input-group.tsx +170 -0
  23. package/src/components/ui/input-otp.tsx +84 -0
  24. package/src/components/ui/input.tsx +37 -0
  25. package/src/components/ui/item.tsx +196 -0
  26. package/src/components/ui/label.tsx +19 -0
  27. package/src/components/ui/popover.tsx +87 -0
  28. package/src/components/ui/radio-group.tsx +47 -0
  29. package/src/components/ui/select.tsx +205 -0
  30. package/src/components/ui/separator.tsx +26 -0
  31. package/src/components/ui/sheet.tsx +141 -0
  32. package/src/components/ui/sidebar.tsx +699 -0
  33. package/src/components/ui/skeleton.tsx +13 -0
  34. package/src/components/ui/sonner.tsx +74 -0
  35. package/src/components/ui/spinner.tsx +18 -0
  36. package/src/components/ui/table.tsx +114 -0
  37. package/src/components/ui/tabs.tsx +88 -0
  38. package/src/components/ui/textarea.tsx +35 -0
  39. package/src/components/ui/toggle-group.tsx +91 -0
  40. package/src/components/ui/toggle.tsx +44 -0
  41. package/src/components/ui/tooltip.tsx +59 -0
  42. package/src/constants/common.test.ts +1 -1
  43. package/src/constants/legal.test.ts +1 -1
  44. package/src/constants/payment.test.ts +9 -9
  45. package/src/constants/platforms.test.ts +1 -1
  46. package/src/constants/services.test.ts +1 -1
  47. package/src/hooks/use-mobile.ts +19 -0
  48. package/src/index.css +135 -0
  49. package/src/lib/utils.ts +6 -0
  50. package/src/main.tsx +16 -0
  51. package/src/moderation/datasets/bn.ts +708 -708
  52. package/src/moderation/normalizer.test.ts +1 -1
  53. package/src/moderation/normalizer.ts +16 -16
  54. package/src/moderation/profanity-service.test.ts +3 -3
  55. package/src/providers/theme-provider.tsx +73 -0
  56. package/src/types/supabase.ts +751 -647
  57. package/src/utils/check-moderation.test.ts +12 -12
  58. package/src/utils/format-number.test.ts +1 -1
  59. package/src/utils/format-plain-text.test.ts +1 -1
  60. package/src/utils/get-social-handle.test.ts +3 -3
  61. package/src/utils/get-social-link.test.ts +9 -9
  62. package/src/utils/get-social-link.ts +5 -3
  63. package/src/utils/get-user-name-initials.test.ts +1 -1
  64. package/src/utils/get-user-name-initials.ts +4 -4
  65. package/src/utils/get-user-page-link.ts +1 -1
  66. package/src/utils/index.ts +5 -5
  67. package/src/utils/open-to-new-window.ts +3 -1
  68. package/src/utils/post-to-facebook.test.ts +1 -1
  69. package/src/utils/post-to-facebook.ts +9 -3
  70. package/src/utils/post-to-instagram.test.ts +1 -1
  71. package/src/utils/post-to-linkedin.test.ts +1 -1
  72. package/src/utils/post-to-linkedin.ts +9 -3
  73. package/src/utils/post-to-x.test.ts +1 -1
  74. package/src/utils/post-to-x.ts +12 -4
  75. package/src/utils/to-human-readable.ts +6 -2
  76. package/src/utils/validate-phone-number.test.ts +1 -1
@@ -0,0 +1,267 @@
1
+ import { ArrowRight01Icon, Tick02Icon } from "@hugeicons/core-free-icons";
2
+ import { HugeiconsIcon } from "@hugeicons/react";
3
+ import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
4
+ import type * as React from "react";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ function DropdownMenu({
8
+ ...props
9
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
10
+ return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
11
+ }
12
+
13
+ function DropdownMenuPortal({
14
+ ...props
15
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
16
+ return (
17
+ <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
18
+ );
19
+ }
20
+
21
+ function DropdownMenuTrigger({
22
+ ...props
23
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
24
+ return (
25
+ <DropdownMenuPrimitive.Trigger
26
+ data-slot="dropdown-menu-trigger"
27
+ {...props}
28
+ />
29
+ );
30
+ }
31
+
32
+ function DropdownMenuContent({
33
+ className,
34
+ align = "start",
35
+ sideOffset = 4,
36
+ ...props
37
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
38
+ return (
39
+ <DropdownMenuPrimitive.Portal>
40
+ <DropdownMenuPrimitive.Content
41
+ align={align}
42
+ className={cn(
43
+ "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 z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) min-w-48 origin-(--radix-dropdown-menu-content-transform-origin) overflow-y-auto overflow-x-hidden rounded-xl bg-popover p-1 text-popover-foreground shadow-2xl ring-1 ring-foreground/5 duration-100 data-closed:animate-out data-open:animate-in data-[state=closed]:overflow-hidden",
44
+ className
45
+ )}
46
+ data-slot="dropdown-menu-content"
47
+ sideOffset={sideOffset}
48
+ {...props}
49
+ />
50
+ </DropdownMenuPrimitive.Portal>
51
+ );
52
+ }
53
+
54
+ function DropdownMenuGroup({
55
+ ...props
56
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
57
+ return (
58
+ <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
59
+ );
60
+ }
61
+
62
+ function DropdownMenuItem({
63
+ className,
64
+ inset,
65
+ variant = "default",
66
+ ...props
67
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
68
+ inset?: boolean;
69
+ variant?: "default" | "destructive";
70
+ }) {
71
+ return (
72
+ <DropdownMenuPrimitive.Item
73
+ className={cn(
74
+ "group/dropdown-menu-item relative flex cursor-default select-none items-center gap-2.5 rounded-lg px-4 py-3 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-8 data-[variant=destructive]:text-destructive data-disabled:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=destructive]:*:[svg]:text-destructive",
75
+ className
76
+ )}
77
+ data-inset={inset}
78
+ data-slot="dropdown-menu-item"
79
+ data-variant={variant}
80
+ {...props}
81
+ />
82
+ );
83
+ }
84
+
85
+ function DropdownMenuCheckboxItem({
86
+ className,
87
+ children,
88
+ checked,
89
+ ...props
90
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
91
+ return (
92
+ <DropdownMenuPrimitive.CheckboxItem
93
+ checked={checked}
94
+ className={cn(
95
+ "relative flex cursor-default select-none items-center gap-2.5 rounded-xl py-2 pr-8 pl-3 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
96
+ className
97
+ )}
98
+ data-slot="dropdown-menu-checkbox-item"
99
+ {...props}
100
+ >
101
+ <span
102
+ className="pointer-events-none absolute right-2 flex items-center justify-center"
103
+ data-slot="dropdown-menu-checkbox-item-indicator"
104
+ >
105
+ <DropdownMenuPrimitive.ItemIndicator>
106
+ <HugeiconsIcon icon={Tick02Icon} strokeWidth={2} />
107
+ </DropdownMenuPrimitive.ItemIndicator>
108
+ </span>
109
+ {children}
110
+ </DropdownMenuPrimitive.CheckboxItem>
111
+ );
112
+ }
113
+
114
+ function DropdownMenuRadioGroup({
115
+ ...props
116
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
117
+ return (
118
+ <DropdownMenuPrimitive.RadioGroup
119
+ data-slot="dropdown-menu-radio-group"
120
+ {...props}
121
+ />
122
+ );
123
+ }
124
+
125
+ function DropdownMenuRadioItem({
126
+ className,
127
+ children,
128
+ ...props
129
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
130
+ return (
131
+ <DropdownMenuPrimitive.RadioItem
132
+ className={cn(
133
+ "relative flex cursor-default select-none items-center gap-2.5 rounded-xl py-2 pr-8 pl-3 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
134
+ className
135
+ )}
136
+ data-slot="dropdown-menu-radio-item"
137
+ {...props}
138
+ >
139
+ <span
140
+ className="pointer-events-none absolute right-2 flex items-center justify-center"
141
+ data-slot="dropdown-menu-radio-item-indicator"
142
+ >
143
+ <DropdownMenuPrimitive.ItemIndicator>
144
+ <HugeiconsIcon icon={Tick02Icon} strokeWidth={2} />
145
+ </DropdownMenuPrimitive.ItemIndicator>
146
+ </span>
147
+ {children}
148
+ </DropdownMenuPrimitive.RadioItem>
149
+ );
150
+ }
151
+
152
+ function DropdownMenuLabel({
153
+ className,
154
+ inset,
155
+ ...props
156
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
157
+ inset?: boolean;
158
+ }) {
159
+ return (
160
+ <DropdownMenuPrimitive.Label
161
+ className={cn(
162
+ "px-3 py-2.5 text-muted-foreground text-xs data-inset:pl-8",
163
+ className
164
+ )}
165
+ data-inset={inset}
166
+ data-slot="dropdown-menu-label"
167
+ {...props}
168
+ />
169
+ );
170
+ }
171
+
172
+ function DropdownMenuSeparator({
173
+ className,
174
+ ...props
175
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
176
+ return (
177
+ <DropdownMenuPrimitive.Separator
178
+ className={cn("-mx-1 my-1 h-px bg-border/50", className)}
179
+ data-slot="dropdown-menu-separator"
180
+ {...props}
181
+ />
182
+ );
183
+ }
184
+
185
+ function DropdownMenuShortcut({
186
+ className,
187
+ ...props
188
+ }: React.ComponentProps<"span">) {
189
+ return (
190
+ <span
191
+ className={cn(
192
+ "ml-auto text-muted-foreground text-xs tracking-widest group-focus/dropdown-menu-item:text-accent-foreground",
193
+ className
194
+ )}
195
+ data-slot="dropdown-menu-shortcut"
196
+ {...props}
197
+ />
198
+ );
199
+ }
200
+
201
+ function DropdownMenuSub({
202
+ ...props
203
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
204
+ return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
205
+ }
206
+
207
+ function DropdownMenuSubTrigger({
208
+ className,
209
+ inset,
210
+ children,
211
+ ...props
212
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
213
+ inset?: boolean;
214
+ }) {
215
+ return (
216
+ <DropdownMenuPrimitive.SubTrigger
217
+ className={cn(
218
+ "flex cursor-default select-none items-center gap-2 rounded-xl px-3 py-2 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-inset:pl-8 data-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
219
+ className
220
+ )}
221
+ data-inset={inset}
222
+ data-slot="dropdown-menu-sub-trigger"
223
+ {...props}
224
+ >
225
+ {children}
226
+ <HugeiconsIcon
227
+ className="ml-auto"
228
+ icon={ArrowRight01Icon}
229
+ strokeWidth={2}
230
+ />
231
+ </DropdownMenuPrimitive.SubTrigger>
232
+ );
233
+ }
234
+
235
+ function DropdownMenuSubContent({
236
+ className,
237
+ ...props
238
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
239
+ return (
240
+ <DropdownMenuPrimitive.SubContent
241
+ className={cn(
242
+ "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 z-50 min-w-36 origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-xl bg-popover p-1 text-popover-foreground shadow-2xl ring-1 ring-foreground/5 duration-100 data-closed:animate-out data-open:animate-in",
243
+ className
244
+ )}
245
+ data-slot="dropdown-menu-sub-content"
246
+ {...props}
247
+ />
248
+ );
249
+ }
250
+
251
+ export {
252
+ DropdownMenu,
253
+ DropdownMenuPortal,
254
+ DropdownMenuTrigger,
255
+ DropdownMenuContent,
256
+ DropdownMenuGroup,
257
+ DropdownMenuLabel,
258
+ DropdownMenuItem,
259
+ DropdownMenuCheckboxItem,
260
+ DropdownMenuRadioGroup,
261
+ DropdownMenuRadioItem,
262
+ DropdownMenuSeparator,
263
+ DropdownMenuShortcut,
264
+ DropdownMenuSub,
265
+ DropdownMenuSubTrigger,
266
+ DropdownMenuSubContent,
267
+ };
@@ -0,0 +1,20 @@
1
+ import { cn } from "@/lib/utils";
2
+
3
+ export function EmptyMinimal({
4
+ className,
5
+ children,
6
+ ...props
7
+ }: React.ComponentProps<"div">) {
8
+ return (
9
+ <div
10
+ className={cn(
11
+ "h-fit rounded-lg border border-dashed py-8 text-center text-muted-foreground text-sm",
12
+ className
13
+ )}
14
+ data-slot="empty"
15
+ {...props}
16
+ >
17
+ {children}
18
+ </div>
19
+ );
20
+ }
@@ -0,0 +1,101 @@
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
+ className={cn(
9
+ "flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 text-balance rounded-lg border-dashed p-12 text-center",
10
+ className
11
+ )}
12
+ data-slot="empty"
13
+ {...props}
14
+ />
15
+ );
16
+ }
17
+
18
+ function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
19
+ return (
20
+ <div
21
+ className={cn("flex max-w-sm flex-col items-center gap-2", className)}
22
+ data-slot="empty-header"
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-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_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
+ className={cn(emptyMediaVariants({ variant, className }))}
51
+ data-slot="empty-icon"
52
+ data-variant={variant}
53
+ {...props}
54
+ />
55
+ );
56
+ }
57
+
58
+ function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
59
+ return (
60
+ <div
61
+ className={cn("font-medium text-lg tracking-tight", className)}
62
+ data-slot="empty-title"
63
+ {...props}
64
+ />
65
+ );
66
+ }
67
+
68
+ function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
69
+ return (
70
+ <div
71
+ className={cn(
72
+ "text-muted-foreground text-sm/relaxed [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
73
+ className
74
+ )}
75
+ data-slot="empty-description"
76
+ {...props}
77
+ />
78
+ );
79
+ }
80
+
81
+ function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
82
+ return (
83
+ <div
84
+ className={cn(
85
+ "flex w-full min-w-0 max-w-sm flex-col items-center gap-4 text-balance text-sm",
86
+ className
87
+ )}
88
+ data-slot="empty-content"
89
+ {...props}
90
+ />
91
+ );
92
+ }
93
+
94
+ export {
95
+ Empty,
96
+ EmptyHeader,
97
+ EmptyTitle,
98
+ EmptyDescription,
99
+ EmptyContent,
100
+ EmptyMedia,
101
+ };
@@ -0,0 +1,235 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import { useMemo } from "react";
3
+ import { Label } from "@/components/ui/label";
4
+ import { Separator } from "@/components/ui/separator";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
8
+ return (
9
+ <fieldset
10
+ className={cn(
11
+ "flex flex-col gap-6 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
12
+ className
13
+ )}
14
+ data-slot="field-set"
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
+ className={cn(
28
+ "mb-3 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base",
29
+ className
30
+ )}
31
+ data-slot="field-legend"
32
+ data-variant={variant}
33
+ {...props}
34
+ />
35
+ );
36
+ }
37
+
38
+ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
39
+ return (
40
+ <div
41
+ className={cn(
42
+ "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",
43
+ className
44
+ )}
45
+ data-slot="field-group"
46
+ {...props}
47
+ />
48
+ );
49
+ }
50
+
51
+ const fieldVariants = cva(
52
+ "group/field flex w-full gap-3 data-[invalid=true]:text-destructive",
53
+ {
54
+ variants: {
55
+ orientation: {
56
+ vertical: "flex-col [&>*]:w-full [&>.sr-only]:w-auto",
57
+ horizontal:
58
+ "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",
59
+ responsive:
60
+ "@md/field-group:flex-row flex-col @md/field-group:items-center @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:[&>*]:w-auto [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:[&>[data-slot=field-label]]:flex-auto @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
+
69
+ function Field({
70
+ className,
71
+ orientation = "vertical",
72
+ ...props
73
+ }: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
74
+ return (
75
+ <div
76
+ className={cn(fieldVariants({ orientation }), className)}
77
+ data-orientation={orientation}
78
+ data-slot="field"
79
+ role="group"
80
+ {...props}
81
+ />
82
+ );
83
+ }
84
+
85
+ function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
86
+ return (
87
+ <div
88
+ className={cn(
89
+ "group/field-content flex flex-1 flex-col gap-1 leading-snug",
90
+ className
91
+ )}
92
+ data-slot="field-content"
93
+ {...props}
94
+ />
95
+ );
96
+ }
97
+
98
+ function FieldLabel({
99
+ className,
100
+ ...props
101
+ }: React.ComponentProps<typeof Label>) {
102
+ return (
103
+ <Label
104
+ className={cn(
105
+ "group/field-label peer/field-label flex w-fit gap-2 leading-snug has-[>[data-slot=field]]:rounded-xl has-[>[data-slot=field]]:border-2 has-data-checked:border-primary has-data-checked:bg-primary/5 *:data-[slot=field]:p-4 group-data-[disabled=true]/field:opacity-50 dark:has-data-checked:bg-primary/10",
106
+ "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
107
+ className
108
+ )}
109
+ data-slot="field-label"
110
+ {...props}
111
+ />
112
+ );
113
+ }
114
+
115
+ function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
116
+ return (
117
+ <div
118
+ className={cn(
119
+ "flex w-fit items-center gap-2 font-medium text-sm leading-snug group-data-[disabled=true]/field:opacity-50",
120
+ className
121
+ )}
122
+ data-slot="field-label"
123
+ {...props}
124
+ />
125
+ );
126
+ }
127
+
128
+ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
129
+ return (
130
+ <p
131
+ className={cn(
132
+ "text-left font-normal text-muted-foreground text-sm leading-normal group-has-data-[orientation=horizontal]/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
133
+ "nth-last-2:-mt-1 last:mt-0",
134
+ "[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
135
+ className
136
+ )}
137
+ data-slot="field-description"
138
+ {...props}
139
+ />
140
+ );
141
+ }
142
+
143
+ function FieldSeparator({
144
+ children,
145
+ className,
146
+ ...props
147
+ }: React.ComponentProps<"div"> & {
148
+ children?: React.ReactNode;
149
+ }) {
150
+ return (
151
+ <div
152
+ className={cn(
153
+ "relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
154
+ className
155
+ )}
156
+ data-content={!!children}
157
+ data-slot="field-separator"
158
+ {...props}
159
+ >
160
+ <Separator className="absolute inset-0 top-1/2" />
161
+ {children && (
162
+ <span
163
+ className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
164
+ data-slot="field-separator-content"
165
+ >
166
+ {children}
167
+ </span>
168
+ )}
169
+ </div>
170
+ );
171
+ }
172
+
173
+ function FieldError({
174
+ className,
175
+ children,
176
+ errors,
177
+ ...props
178
+ }: React.ComponentProps<"div"> & {
179
+ errors?: Array<{ message?: string } | undefined>;
180
+ }) {
181
+ const content = useMemo(() => {
182
+ if (children) {
183
+ return children;
184
+ }
185
+
186
+ if (!errors?.length) {
187
+ return null;
188
+ }
189
+
190
+ const uniqueErrors = [
191
+ ...new Map(errors.map((error) => [error?.message, error])).values(),
192
+ ];
193
+
194
+ if (uniqueErrors?.length === 1) {
195
+ return uniqueErrors[0]?.message;
196
+ }
197
+
198
+ return (
199
+ <ul className="ml-4 flex list-disc flex-col gap-1">
200
+ {uniqueErrors.map(
201
+ (error, index) =>
202
+ error?.message && <li key={index}>{error.message}</li>
203
+ )}
204
+ </ul>
205
+ );
206
+ }, [children, errors]);
207
+
208
+ if (!content) {
209
+ return null;
210
+ }
211
+
212
+ return (
213
+ <div
214
+ className={cn("font-normal text-destructive text-sm", className)}
215
+ data-slot="field-error"
216
+ role="alert"
217
+ {...props}
218
+ >
219
+ {content}
220
+ </div>
221
+ );
222
+ }
223
+
224
+ export {
225
+ Field,
226
+ FieldLabel,
227
+ FieldDescription,
228
+ FieldError,
229
+ FieldGroup,
230
+ FieldLegend,
231
+ FieldSeparator,
232
+ FieldSet,
233
+ FieldContent,
234
+ FieldTitle,
235
+ };