@payglocal_ui/flux-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 (72) hide show
  1. package/package.json +72 -0
  2. package/src/accordion.tsx +68 -0
  3. package/src/alert.tsx +107 -0
  4. package/src/avatar-group.tsx +96 -0
  5. package/src/avatar-tag.tsx +136 -0
  6. package/src/avatar.tsx +39 -0
  7. package/src/badge.tsx +98 -0
  8. package/src/blanket.tsx +61 -0
  9. package/src/breadcrumb.tsx +119 -0
  10. package/src/button-group.tsx +218 -0
  11. package/src/button.tsx +83 -0
  12. package/src/calendar.tsx +227 -0
  13. package/src/callout.tsx +68 -0
  14. package/src/card.tsx +103 -0
  15. package/src/chart-templates.tsx +587 -0
  16. package/src/chart.tsx +379 -0
  17. package/src/checkbox-select.tsx +239 -0
  18. package/src/checkbox.tsx +54 -0
  19. package/src/code.tsx +154 -0
  20. package/src/command.tsx +77 -0
  21. package/src/country-select.tsx +242 -0
  22. package/src/currency-amount-input.tsx +72 -0
  23. package/src/data-table.tsx +378 -0
  24. package/src/date-picker.tsx +317 -0
  25. package/src/dialog.tsx +81 -0
  26. package/src/drawer.tsx +91 -0
  27. package/src/dropdown-menu.tsx +174 -0
  28. package/src/empty-state.tsx +32 -0
  29. package/src/field.tsx +243 -0
  30. package/src/flag.tsx +265 -0
  31. package/src/form.tsx +168 -0
  32. package/src/grid-flex.tsx +241 -0
  33. package/src/heading.tsx +202 -0
  34. package/src/icon-button.tsx +93 -0
  35. package/src/index.ts +332 -0
  36. package/src/inline-dialog.tsx +153 -0
  37. package/src/inline-edit.tsx +212 -0
  38. package/src/input-group.tsx +151 -0
  39. package/src/input.tsx +28 -0
  40. package/src/label.tsx +21 -0
  41. package/src/layout.tsx +119 -0
  42. package/src/link.tsx +80 -0
  43. package/src/lozenge.tsx +61 -0
  44. package/src/menu.tsx +146 -0
  45. package/src/otp-input.tsx +117 -0
  46. package/src/page-header.tsx +28 -0
  47. package/src/pagination.tsx +185 -0
  48. package/src/password-input.tsx +34 -0
  49. package/src/popover.tsx +31 -0
  50. package/src/progress-indicator.tsx +94 -0
  51. package/src/progress.tsx +95 -0
  52. package/src/radio-group.tsx +46 -0
  53. package/src/responsive.tsx +276 -0
  54. package/src/scroll-area.tsx +39 -0
  55. package/src/section-message.tsx +119 -0
  56. package/src/select.tsx +144 -0
  57. package/src/separator.tsx +26 -0
  58. package/src/side-nav.tsx +264 -0
  59. package/src/skeleton.tsx +74 -0
  60. package/src/slider.tsx +25 -0
  61. package/src/sonner.tsx +32 -0
  62. package/src/spinner.tsx +54 -0
  63. package/src/spotlight.tsx +141 -0
  64. package/src/status-badge.tsx +86 -0
  65. package/src/switch.tsx +70 -0
  66. package/src/tabs.tsx +57 -0
  67. package/src/tag.tsx +52 -0
  68. package/src/textarea.tsx +25 -0
  69. package/src/time-picker.tsx +443 -0
  70. package/src/tooltip.tsx +29 -0
  71. package/src/utils.ts +6 -0
  72. package/src/visually-hidden.tsx +25 -0
package/src/dialog.tsx ADDED
@@ -0,0 +1,81 @@
1
+ "use client";
2
+
3
+ import type { ComponentProps } from "react";
4
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
5
+ import { X } from "lucide-react";
6
+ import { cn } from "./utils";
7
+
8
+ const Dialog = DialogPrimitive.Root;
9
+ const DialogTrigger = DialogPrimitive.Trigger;
10
+ const DialogPortal = DialogPrimitive.Portal;
11
+ const DialogClose = DialogPrimitive.Close;
12
+
13
+ function DialogOverlay({ className, ...props }: ComponentProps<typeof DialogPrimitive.Overlay>) {
14
+ return (
15
+ <DialogPrimitive.Overlay
16
+ className={cn(
17
+ "fixed inset-0 z-[100] min-h-[100dvh] w-full bg-black/50 backdrop-blur-[2px]",
18
+ "data-[state=open]:opacity-100 data-[state=closed]:opacity-0 transition-opacity duration-200",
19
+ className
20
+ )}
21
+ {...props}
22
+ />
23
+ );
24
+ }
25
+
26
+ function DialogContent({
27
+ className,
28
+ children,
29
+ showClose = true,
30
+ overlayClassName,
31
+ ...props
32
+ }: ComponentProps<typeof DialogPrimitive.Content> & {
33
+ showClose?: boolean;
34
+ overlayClassName?: string;
35
+ }) {
36
+ return (
37
+ <DialogPortal>
38
+ <DialogOverlay className={overlayClassName} />
39
+ <DialogPrimitive.Content
40
+ className={cn(
41
+ "fixed left-1/2 top-1/2 z-[101] w-[calc(100%-1.5rem)] max-w-[min(100%,26rem)] -translate-x-1/2 -translate-y-1/2",
42
+ // Default padding reserves space for the close button.
43
+ // Pass p-0 in className to opt out (manage padding per section).
44
+ "rounded-2xl border border-border bg-card p-6 pt-10 text-card-foreground shadow-2xl outline-none",
45
+ "max-h-[min(90vh,720px)] overflow-y-auto",
46
+ "data-[state=open]:opacity-100 data-[state=closed]:opacity-0 data-[state=open]:scale-100 data-[state=closed]:scale-[0.98]",
47
+ "transition-[opacity,transform] duration-200 ease-out",
48
+ className
49
+ )}
50
+ {...props}
51
+ >
52
+ {children}
53
+ {showClose && (
54
+ <DialogPrimitive.Close
55
+ className="absolute right-3 top-3 inline-flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
56
+ aria-label="Close"
57
+ >
58
+ <X className="h-4 w-4" />
59
+ </DialogPrimitive.Close>
60
+ )}
61
+ </DialogPrimitive.Content>
62
+ </DialogPortal>
63
+ );
64
+ }
65
+
66
+ function DialogTitle({ className, ...props }: ComponentProps<typeof DialogPrimitive.Title>) {
67
+ return (
68
+ <DialogPrimitive.Title
69
+ className={cn("text-lg font-semibold text-foreground tracking-tight pr-10", className)}
70
+ {...props}
71
+ />
72
+ );
73
+ }
74
+
75
+ function DialogDescription({ className, ...props }: ComponentProps<typeof DialogPrimitive.Description>) {
76
+ return (
77
+ <DialogPrimitive.Description className={cn("text-[13px] text-muted-foreground mt-1", className)} {...props} />
78
+ );
79
+ }
80
+
81
+ export { Dialog, DialogTrigger, DialogPortal, DialogClose, DialogContent, DialogTitle, DialogDescription };
package/src/drawer.tsx ADDED
@@ -0,0 +1,91 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
5
+ import { X } from "lucide-react";
6
+ import { cn } from "./utils";
7
+
8
+ type DrawerSide = "right" | "left" | "top" | "bottom";
9
+ const DrawerContext = React.createContext<{ side: DrawerSide }>({ side: "right" });
10
+
11
+ interface DrawerProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Root> { side?: DrawerSide; }
12
+
13
+ const Drawer = ({ side = "right", children, ...props }: DrawerProps) => (
14
+ <DrawerContext.Provider value={{ side }}>
15
+ <DialogPrimitive.Root {...props}>{children}</DialogPrimitive.Root>
16
+ </DrawerContext.Provider>
17
+ );
18
+ Drawer.displayName = "Drawer";
19
+
20
+ const DrawerTrigger = DialogPrimitive.Trigger;
21
+ const DrawerClose = DialogPrimitive.Close;
22
+
23
+ const DrawerOverlay = React.forwardRef<
24
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
25
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
26
+ >(({ className, ...props }, ref) => (
27
+ <DialogPrimitive.Overlay
28
+ ref={ref}
29
+ className={cn("fixed inset-0 z-50 bg-black/50 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", className)}
30
+ {...props}
31
+ />
32
+ ));
33
+ DrawerOverlay.displayName = "DrawerOverlay";
34
+
35
+ const sideClasses: Record<DrawerSide, string> = {
36
+ right: "inset-y-0 right-0 h-full w-80 sm:w-96 border-l data-[state=open]:slide-in-from-right data-[state=closed]:slide-out-to-right",
37
+ left: "inset-y-0 left-0 h-full w-80 sm:w-96 border-r data-[state=open]:slide-in-from-left data-[state=closed]:slide-out-to-left",
38
+ top: "inset-x-0 top-0 w-full border-b rounded-b-2xl data-[state=open]:slide-in-from-top data-[state=closed]:slide-out-to-top",
39
+ bottom: "inset-x-0 bottom-0 w-full border-t rounded-t-2xl max-h-[85vh] data-[state=open]:slide-in-from-bottom data-[state=closed]:slide-out-to-bottom",
40
+ };
41
+
42
+ const DrawerContent = React.forwardRef<
43
+ React.ElementRef<typeof DialogPrimitive.Content>,
44
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
45
+ >(({ className, children, ...props }, ref) => {
46
+ const { side } = React.useContext(DrawerContext);
47
+ return (
48
+ <DialogPrimitive.Portal>
49
+ <DrawerOverlay />
50
+ <DialogPrimitive.Content
51
+ ref={ref}
52
+ className={cn("fixed z-50 flex flex-col bg-card border-border shadow-xl transition-all duration-pg-normal ease-pg-standard data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-200", sideClasses[side], className)}
53
+ {...props}
54
+ >
55
+ {children}
56
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-md p-1 text-muted-foreground opacity-70 hover:opacity-100 transition-opacity focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35">
57
+ <X className="size-4" /><span className="sr-only">Close</span>
58
+ </DialogPrimitive.Close>
59
+ </DialogPrimitive.Content>
60
+ </DialogPrimitive.Portal>
61
+ );
62
+ });
63
+ DrawerContent.displayName = "DrawerContent";
64
+
65
+ const DrawerHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
66
+ ({ className, ...props }, ref) => <div ref={ref} className={cn("px-6 pt-6 pb-4 border-b border-border", className)} {...props} />
67
+ );
68
+ DrawerHeader.displayName = "DrawerHeader";
69
+
70
+ const DrawerFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
71
+ ({ className, ...props }, ref) => <div ref={ref} className={cn("px-6 py-4 border-t border-border flex gap-2 justify-end mt-auto", className)} {...props} />
72
+ );
73
+ DrawerFooter.displayName = "DrawerFooter";
74
+
75
+ const DrawerTitle = React.forwardRef<
76
+ React.ElementRef<typeof DialogPrimitive.Title>,
77
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
78
+ >(({ className, ...props }, ref) => (
79
+ <DialogPrimitive.Title ref={ref} className={cn("text-base font-semibold text-foreground pr-6", className)} {...props} />
80
+ ));
81
+ DrawerTitle.displayName = "DrawerTitle";
82
+
83
+ const DrawerDescription = React.forwardRef<
84
+ React.ElementRef<typeof DialogPrimitive.Description>,
85
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
86
+ >(({ className, ...props }, ref) => (
87
+ <DialogPrimitive.Description ref={ref} className={cn("mt-1 text-sm text-muted-foreground", className)} {...props} />
88
+ ));
89
+ DrawerDescription.displayName = "DrawerDescription";
90
+
91
+ export { Drawer, DrawerTrigger, DrawerClose, DrawerContent, DrawerHeader, DrawerFooter, DrawerTitle, DrawerDescription };
@@ -0,0 +1,174 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
5
+ import { Check, ChevronRight, Circle } from "lucide-react";
6
+ import { cn } from "./utils";
7
+
8
+ const DropdownMenu = DropdownMenuPrimitive.Root;
9
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
10
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group;
11
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
12
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub;
13
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
14
+
15
+ const DropdownMenuSubTrigger = React.forwardRef<
16
+ React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
17
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & { inset?: boolean }
18
+ >(({ className, inset, children, ...props }, ref) => (
19
+ <DropdownMenuPrimitive.SubTrigger
20
+ ref={ref}
21
+ className={cn(
22
+ "flex cursor-default select-none items-center gap-2 rounded-lg px-3 py-2.5 text-[15px] outline-none",
23
+ "focus:bg-muted data-[state=open]:bg-muted",
24
+ inset && "pl-8",
25
+ className
26
+ )}
27
+ {...props}
28
+ >
29
+ {children}
30
+ <ChevronRight className="ml-auto h-4 w-4 text-muted-foreground" />
31
+ </DropdownMenuPrimitive.SubTrigger>
32
+ ));
33
+ DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
34
+
35
+ const DropdownMenuSubContent = React.forwardRef<
36
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
37
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
38
+ >(({ className, ...props }, ref) => (
39
+ <DropdownMenuPrimitive.SubContent
40
+ ref={ref}
41
+ className={cn(
42
+ "z-[130] min-w-[8rem] overflow-hidden rounded-xl border border-border bg-popover p-1.5 text-popover-foreground shadow-lg",
43
+ "data-[state=open]:opacity-100 data-[state=closed]:opacity-0 transition-opacity duration-150",
44
+ className
45
+ )}
46
+ {...props}
47
+ />
48
+ ));
49
+ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
50
+
51
+ const DropdownMenuContent = React.forwardRef<
52
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
53
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
54
+ >(({ className, sideOffset = 8, ...props }, ref) => (
55
+ <DropdownMenuPrimitive.Portal>
56
+ <DropdownMenuPrimitive.Content
57
+ ref={ref}
58
+ sideOffset={sideOffset}
59
+ className={cn(
60
+ "z-[130] min-w-[11rem] overflow-hidden rounded-xl border border-border bg-popover p-1.5 text-popover-foreground shadow-lg",
61
+ "data-[state=open]:opacity-100 data-[state=closed]:opacity-0 transition-opacity duration-150",
62
+ className
63
+ )}
64
+ {...props}
65
+ />
66
+ </DropdownMenuPrimitive.Portal>
67
+ ));
68
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
69
+
70
+ const DropdownMenuItem = React.forwardRef<
71
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
72
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & { inset?: boolean }
73
+ >(({ className, inset, ...props }, ref) => (
74
+ <DropdownMenuPrimitive.Item
75
+ ref={ref}
76
+ className={cn(
77
+ "relative flex cursor-default select-none items-center gap-2 rounded-lg px-3 py-2.5 text-[15px] outline-none transition-colors",
78
+ "focus:bg-muted focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
79
+ inset && "pl-8",
80
+ className
81
+ )}
82
+ {...props}
83
+ />
84
+ ));
85
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
86
+
87
+ const DropdownMenuCheckboxItem = React.forwardRef<
88
+ React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
89
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
90
+ >(({ className, children, checked, ...props }, ref) => (
91
+ <DropdownMenuPrimitive.CheckboxItem
92
+ ref={ref}
93
+ className={cn(
94
+ "relative flex cursor-default select-none items-center rounded-lg py-2 pl-8 pr-2 text-sm outline-none transition-colors",
95
+ "focus:bg-muted focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
96
+ className
97
+ )}
98
+ checked={checked}
99
+ {...props}
100
+ >
101
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
102
+ <DropdownMenuPrimitive.ItemIndicator>
103
+ <Check className="h-4 w-4 text-primary" />
104
+ </DropdownMenuPrimitive.ItemIndicator>
105
+ </span>
106
+ {children}
107
+ </DropdownMenuPrimitive.CheckboxItem>
108
+ ));
109
+ DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
110
+
111
+ const DropdownMenuRadioItem = React.forwardRef<
112
+ React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
113
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
114
+ >(({ className, children, ...props }, ref) => (
115
+ <DropdownMenuPrimitive.RadioItem
116
+ ref={ref}
117
+ className={cn(
118
+ "relative flex cursor-default select-none items-center rounded-lg py-2 pl-8 pr-2 text-sm outline-none transition-colors",
119
+ "focus:bg-muted focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
120
+ className
121
+ )}
122
+ {...props}
123
+ >
124
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
125
+ <DropdownMenuPrimitive.ItemIndicator>
126
+ <Circle className="h-2 w-2 fill-current text-primary" />
127
+ </DropdownMenuPrimitive.ItemIndicator>
128
+ </span>
129
+ {children}
130
+ </DropdownMenuPrimitive.RadioItem>
131
+ ));
132
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
133
+
134
+ const DropdownMenuLabel = React.forwardRef<
135
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
136
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & { inset?: boolean }
137
+ >(({ className, inset, ...props }, ref) => (
138
+ <DropdownMenuPrimitive.Label
139
+ ref={ref}
140
+ className={cn("px-2 py-1.5 text-xs font-semibold text-muted-foreground", inset && "pl-8", className)}
141
+ {...props}
142
+ />
143
+ ));
144
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
145
+
146
+ const DropdownMenuSeparator = React.forwardRef<
147
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
148
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
149
+ >(({ className, ...props }, ref) => (
150
+ <DropdownMenuPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-border", className)} {...props} />
151
+ ));
152
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
153
+
154
+ function DropdownMenuShortcut({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) {
155
+ return <span className={cn("ml-auto text-[10px] tracking-wide text-muted-foreground", className)} {...props} />;
156
+ }
157
+
158
+ export {
159
+ DropdownMenu,
160
+ DropdownMenuTrigger,
161
+ DropdownMenuContent,
162
+ DropdownMenuItem,
163
+ DropdownMenuCheckboxItem,
164
+ DropdownMenuRadioItem,
165
+ DropdownMenuLabel,
166
+ DropdownMenuSeparator,
167
+ DropdownMenuShortcut,
168
+ DropdownMenuGroup,
169
+ DropdownMenuPortal,
170
+ DropdownMenuSub,
171
+ DropdownMenuSubContent,
172
+ DropdownMenuSubTrigger,
173
+ DropdownMenuRadioGroup,
174
+ };
@@ -0,0 +1,32 @@
1
+ import type { ReactNode } from "react";
2
+ import { cn } from "./utils";
3
+ import { type LucideIcon, Inbox } from "lucide-react";
4
+
5
+ interface EmptyStateProps {
6
+ icon?: LucideIcon;
7
+ title: string;
8
+ description?: string;
9
+ action?: ReactNode;
10
+ className?: string;
11
+ }
12
+
13
+ export function EmptyState({
14
+ icon: Icon = Inbox,
15
+ title,
16
+ description,
17
+ action,
18
+ className,
19
+ }: EmptyStateProps) {
20
+ return (
21
+ <div className={cn("flex flex-col items-center justify-center py-16 px-4 text-center", className)}>
22
+ <div className="w-14 h-14 rounded-2xl bg-slate-100 flex items-center justify-center mb-4">
23
+ <Icon className="w-6 h-6 text-slate-400" />
24
+ </div>
25
+ <h3 className="text-sm font-semibold text-slate-700 mb-1">{title}</h3>
26
+ {description && (
27
+ <p className="text-xs text-slate-500 max-w-xs leading-relaxed">{description}</p>
28
+ )}
29
+ {action && <div className="mt-4">{action}</div>}
30
+ </div>
31
+ );
32
+ }
package/src/field.tsx ADDED
@@ -0,0 +1,243 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { useMemo } from "react";
5
+ import { cva, type VariantProps } from "class-variance-authority";
6
+
7
+ import { cn } from "./utils";
8
+ import { Label } from "./label";
9
+ import { Separator } from "./separator";
10
+
11
+ function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
12
+ return (
13
+ <fieldset
14
+ data-slot="field-set"
15
+ className={cn(
16
+ "flex flex-col gap-6",
17
+ "has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
18
+ className
19
+ )}
20
+ {...props}
21
+ />
22
+ );
23
+ }
24
+
25
+ function FieldLegend({
26
+ className,
27
+ variant = "legend",
28
+ ...props
29
+ }: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
30
+ return (
31
+ <legend
32
+ data-slot="field-legend"
33
+ data-variant={variant}
34
+ className={cn(
35
+ "mb-3 font-medium",
36
+ "data-[variant=legend]:text-base",
37
+ "data-[variant=label]:text-sm",
38
+ className
39
+ )}
40
+ {...props}
41
+ />
42
+ );
43
+ }
44
+
45
+ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
46
+ return (
47
+ <div
48
+ data-slot="field-group"
49
+ className={cn(
50
+ "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",
51
+ className
52
+ )}
53
+ {...props}
54
+ />
55
+ );
56
+ }
57
+
58
+ const fieldVariants = cva("group/field flex w-full gap-3 data-[invalid=true]:text-destructive", {
59
+ variants: {
60
+ orientation: {
61
+ vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"],
62
+ horizontal: [
63
+ "flex-row items-center",
64
+ "[&>[data-slot=field-label]]:flex-auto",
65
+ "has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px has-[>[data-slot=field-content]]:items-start",
66
+ ],
67
+ responsive: [
68
+ "@md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto flex-col [&>*]:w-full [&>.sr-only]:w-auto",
69
+ "@md/field-group:[&>[data-slot=field-label]]:flex-auto",
70
+ "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
71
+ ],
72
+ },
73
+ },
74
+ defaultVariants: {
75
+ orientation: "vertical",
76
+ },
77
+ });
78
+
79
+ function Field({
80
+ className,
81
+ orientation = "vertical",
82
+ invalid,
83
+ disabled,
84
+ ...props
85
+ }: React.ComponentProps<"div"> &
86
+ VariantProps<typeof fieldVariants> & {
87
+ invalid?: boolean;
88
+ disabled?: boolean;
89
+ }) {
90
+ return (
91
+ <div
92
+ role="group"
93
+ data-slot="field"
94
+ data-orientation={orientation}
95
+ data-invalid={invalid ? true : undefined}
96
+ data-disabled={disabled ? true : undefined}
97
+ className={cn(fieldVariants({ orientation }), className)}
98
+ {...props}
99
+ />
100
+ );
101
+ }
102
+
103
+ function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
104
+ return (
105
+ <div
106
+ data-slot="field-content"
107
+ className={cn("group/field-content flex flex-1 flex-col gap-1.5 leading-snug", className)}
108
+ {...props}
109
+ />
110
+ );
111
+ }
112
+
113
+ function FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>) {
114
+ return (
115
+ <Label
116
+ data-slot="field-label"
117
+ className={cn(
118
+ "group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50",
119
+ "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border has-[>[data-slot=field]]:border-border [&>[data-slot=field]]:p-4",
120
+ "has-data-[state=checked]:border-primary has-data-[state=checked]:bg-primary/5 dark:has-data-[state=checked]:bg-primary/10",
121
+ className
122
+ )}
123
+ {...props}
124
+ />
125
+ );
126
+ }
127
+
128
+ function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
129
+ return (
130
+ <div
131
+ data-slot="field-label"
132
+ className={cn(
133
+ "flex w-fit items-center gap-2 text-sm font-medium leading-snug group-data-[disabled=true]/field:opacity-50",
134
+ className
135
+ )}
136
+ {...props}
137
+ />
138
+ );
139
+ }
140
+
141
+ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
142
+ return (
143
+ <p
144
+ data-slot="field-description"
145
+ className={cn(
146
+ "text-sm font-normal leading-normal text-muted-foreground group-has-[[data-orientation=horizontal]]/field:text-balance",
147
+ "nth-last-2:-mt-1 last:mt-0 [[data-variant=legend]+&]:-mt-1.5",
148
+ "[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
149
+ className
150
+ )}
151
+ {...props}
152
+ />
153
+ );
154
+ }
155
+
156
+ function FieldSeparator({
157
+ children,
158
+ className,
159
+ ...props
160
+ }: React.ComponentProps<"div"> & {
161
+ children?: React.ReactNode;
162
+ }) {
163
+ return (
164
+ <div
165
+ data-slot="field-separator"
166
+ data-content={!!children}
167
+ className={cn(
168
+ "relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
169
+ className
170
+ )}
171
+ {...props}
172
+ >
173
+ <Separator className="absolute inset-0 top-1/2" />
174
+ {children ? (
175
+ <span
176
+ className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
177
+ data-slot="field-separator-content"
178
+ >
179
+ {children}
180
+ </span>
181
+ ) : null}
182
+ </div>
183
+ );
184
+ }
185
+
186
+ function FieldError({
187
+ className,
188
+ children,
189
+ errors,
190
+ ...props
191
+ }: React.ComponentProps<"div"> & {
192
+ errors?: Array<{ message?: string } | undefined>;
193
+ }) {
194
+ const content = useMemo(() => {
195
+ if (children) {
196
+ return children;
197
+ }
198
+
199
+ if (!errors) {
200
+ return null;
201
+ }
202
+
203
+ if (errors?.length === 1 && errors[0]?.message) {
204
+ return errors[0].message;
205
+ }
206
+
207
+ return (
208
+ <ul className="ml-4 flex list-disc flex-col gap-1">
209
+ {errors.map(
210
+ (error, index) => error?.message && <li key={index}>{error.message}</li>
211
+ )}
212
+ </ul>
213
+ );
214
+ }, [children, errors]);
215
+
216
+ if (!content) {
217
+ return null;
218
+ }
219
+
220
+ return (
221
+ <div
222
+ role="alert"
223
+ data-slot="field-error"
224
+ className={cn("text-sm font-normal text-destructive", className)}
225
+ {...props}
226
+ >
227
+ {content}
228
+ </div>
229
+ );
230
+ }
231
+
232
+ export {
233
+ Field,
234
+ FieldLabel,
235
+ FieldDescription,
236
+ FieldError,
237
+ FieldGroup,
238
+ FieldLegend,
239
+ FieldSeparator,
240
+ FieldSet,
241
+ FieldContent,
242
+ FieldTitle,
243
+ };