@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.
- package/package.json +72 -0
- package/src/accordion.tsx +68 -0
- package/src/alert.tsx +107 -0
- package/src/avatar-group.tsx +96 -0
- package/src/avatar-tag.tsx +136 -0
- package/src/avatar.tsx +39 -0
- package/src/badge.tsx +98 -0
- package/src/blanket.tsx +61 -0
- package/src/breadcrumb.tsx +119 -0
- package/src/button-group.tsx +218 -0
- package/src/button.tsx +83 -0
- package/src/calendar.tsx +227 -0
- package/src/callout.tsx +68 -0
- package/src/card.tsx +103 -0
- package/src/chart-templates.tsx +587 -0
- package/src/chart.tsx +379 -0
- package/src/checkbox-select.tsx +239 -0
- package/src/checkbox.tsx +54 -0
- package/src/code.tsx +154 -0
- package/src/command.tsx +77 -0
- package/src/country-select.tsx +242 -0
- package/src/currency-amount-input.tsx +72 -0
- package/src/data-table.tsx +378 -0
- package/src/date-picker.tsx +317 -0
- package/src/dialog.tsx +81 -0
- package/src/drawer.tsx +91 -0
- package/src/dropdown-menu.tsx +174 -0
- package/src/empty-state.tsx +32 -0
- package/src/field.tsx +243 -0
- package/src/flag.tsx +265 -0
- package/src/form.tsx +168 -0
- package/src/grid-flex.tsx +241 -0
- package/src/heading.tsx +202 -0
- package/src/icon-button.tsx +93 -0
- package/src/index.ts +332 -0
- package/src/inline-dialog.tsx +153 -0
- package/src/inline-edit.tsx +212 -0
- package/src/input-group.tsx +151 -0
- package/src/input.tsx +28 -0
- package/src/label.tsx +21 -0
- package/src/layout.tsx +119 -0
- package/src/link.tsx +80 -0
- package/src/lozenge.tsx +61 -0
- package/src/menu.tsx +146 -0
- package/src/otp-input.tsx +117 -0
- package/src/page-header.tsx +28 -0
- package/src/pagination.tsx +185 -0
- package/src/password-input.tsx +34 -0
- package/src/popover.tsx +31 -0
- package/src/progress-indicator.tsx +94 -0
- package/src/progress.tsx +95 -0
- package/src/radio-group.tsx +46 -0
- package/src/responsive.tsx +276 -0
- package/src/scroll-area.tsx +39 -0
- package/src/section-message.tsx +119 -0
- package/src/select.tsx +144 -0
- package/src/separator.tsx +26 -0
- package/src/side-nav.tsx +264 -0
- package/src/skeleton.tsx +74 -0
- package/src/slider.tsx +25 -0
- package/src/sonner.tsx +32 -0
- package/src/spinner.tsx +54 -0
- package/src/spotlight.tsx +141 -0
- package/src/status-badge.tsx +86 -0
- package/src/switch.tsx +70 -0
- package/src/tabs.tsx +57 -0
- package/src/tag.tsx +52 -0
- package/src/textarea.tsx +25 -0
- package/src/time-picker.tsx +443 -0
- package/src/tooltip.tsx +29 -0
- package/src/utils.ts +6 -0
- 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
|
+
};
|