@kinetixui/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/LICENSE +21 -0
- package/dist/index.d.ts +1176 -0
- package/dist/index.js +3939 -0
- package/package.json +106 -0
- package/src/components/accordion.tsx +52 -0
- package/src/components/alert-dialog.tsx +115 -0
- package/src/components/alert.tsx +47 -0
- package/src/components/aspect-ratio.tsx +7 -0
- package/src/components/audio-player.tsx +150 -0
- package/src/components/avatar.tsx +76 -0
- package/src/components/badge.tsx +40 -0
- package/src/components/breadcrumb.tsx +77 -0
- package/src/components/button.tsx +119 -0
- package/src/components/calendar.tsx +60 -0
- package/src/components/card.tsx +50 -0
- package/src/components/carousel.tsx +181 -0
- package/src/components/chart.tsx +177 -0
- package/src/components/checkbox.tsx +38 -0
- package/src/components/circular-progress.tsx +67 -0
- package/src/components/code-block.tsx +96 -0
- package/src/components/collapsible.tsx +9 -0
- package/src/components/command.tsx +123 -0
- package/src/components/context-menu.tsx +132 -0
- package/src/components/data-table.tsx +92 -0
- package/src/components/date-picker.tsx +68 -0
- package/src/components/dialog.tsx +101 -0
- package/src/components/drawer.tsx +82 -0
- package/src/components/dropdown-menu.tsx +132 -0
- package/src/components/fab.tsx +58 -0
- package/src/components/field.tsx +128 -0
- package/src/components/file-upload.tsx +183 -0
- package/src/components/footer.tsx +62 -0
- package/src/components/form.tsx +130 -0
- package/src/components/hover-card.tsx +28 -0
- package/src/components/image.tsx +87 -0
- package/src/components/inform.tsx +82 -0
- package/src/components/input-group.tsx +96 -0
- package/src/components/input-otp.tsx +63 -0
- package/src/components/input.tsx +72 -0
- package/src/components/label.tsx +20 -0
- package/src/components/list.tsx +69 -0
- package/src/components/menubar.tsx +168 -0
- package/src/components/metric.tsx +51 -0
- package/src/components/modal.tsx +126 -0
- package/src/components/navigation-bar.tsx +49 -0
- package/src/components/navigation-menu.tsx +117 -0
- package/src/components/number-input.tsx +86 -0
- package/src/components/pagination.tsx +77 -0
- package/src/components/password-input.tsx +36 -0
- package/src/components/popover.tsx +32 -0
- package/src/components/progress.tsx +24 -0
- package/src/components/quote.tsx +34 -0
- package/src/components/radio-group.tsx +44 -0
- package/src/components/rating.tsx +88 -0
- package/src/components/resizable.tsx +40 -0
- package/src/components/scroll-area.tsx +39 -0
- package/src/components/select.tsx +124 -0
- package/src/components/separator.tsx +25 -0
- package/src/components/sheet.tsx +104 -0
- package/src/components/sidebar.tsx +390 -0
- package/src/components/skeleton.tsx +9 -0
- package/src/components/slider.tsx +24 -0
- package/src/components/sonner.tsx +30 -0
- package/src/components/spinner.tsx +43 -0
- package/src/components/stepper.tsx +75 -0
- package/src/components/switch.tsx +37 -0
- package/src/components/tab-bar.tsx +58 -0
- package/src/components/table-of-contents.tsx +46 -0
- package/src/components/table.tsx +70 -0
- package/src/components/tabs.tsx +54 -0
- package/src/components/tag.tsx +56 -0
- package/src/components/textarea.tsx +56 -0
- package/src/components/toggle-group.tsx +41 -0
- package/src/components/toggle.tsx +34 -0
- package/src/components/tooltip.tsx +31 -0
- package/src/index.ts +305 -0
- package/src/lib/utils.ts +7 -0
- package/src/stories/Button.stories.tsx +155 -0
- package/src/stories/Input.stories.tsx +80 -0
- package/src/stories/Textarea.stories.tsx +69 -0
- package/tailwind.config.ts +107 -0
|
@@ -0,0 +1,101 @@
|
|
|
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 "../lib/utils";
|
|
7
|
+
|
|
8
|
+
const Dialog = DialogPrimitive.Root;
|
|
9
|
+
const DialogTrigger = DialogPrimitive.Trigger;
|
|
10
|
+
const DialogPortal = DialogPrimitive.Portal;
|
|
11
|
+
const DialogClose = DialogPrimitive.Close;
|
|
12
|
+
|
|
13
|
+
const DialogOverlay = React.forwardRef<
|
|
14
|
+
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
|
15
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
|
16
|
+
>(({ className, ...props }, ref) => (
|
|
17
|
+
<DialogPrimitive.Overlay
|
|
18
|
+
ref={ref}
|
|
19
|
+
className={cn(
|
|
20
|
+
"fixed inset-0 z-50 bg-foreground/40 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",
|
|
21
|
+
className,
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
));
|
|
26
|
+
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
|
27
|
+
|
|
28
|
+
const DialogContent = React.forwardRef<
|
|
29
|
+
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
30
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
|
31
|
+
>(({ className, children, ...props }, ref) => (
|
|
32
|
+
<DialogPortal>
|
|
33
|
+
<DialogOverlay />
|
|
34
|
+
<DialogPrimitive.Content
|
|
35
|
+
ref={ref}
|
|
36
|
+
className={cn(
|
|
37
|
+
"fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border bg-background p-6 shadow-lg sm:rounded-xl font-sans",
|
|
38
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
39
|
+
className,
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring disabled:pointer-events-none">
|
|
45
|
+
<X className="size-4" />
|
|
46
|
+
<span className="sr-only">Close</span>
|
|
47
|
+
</DialogPrimitive.Close>
|
|
48
|
+
</DialogPrimitive.Content>
|
|
49
|
+
</DialogPortal>
|
|
50
|
+
));
|
|
51
|
+
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
|
52
|
+
|
|
53
|
+
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
54
|
+
<div className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...props} />
|
|
55
|
+
);
|
|
56
|
+
DialogHeader.displayName = "DialogHeader";
|
|
57
|
+
|
|
58
|
+
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
59
|
+
<div
|
|
60
|
+
className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
|
|
61
|
+
{...props}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
DialogFooter.displayName = "DialogFooter";
|
|
65
|
+
|
|
66
|
+
const DialogTitle = React.forwardRef<
|
|
67
|
+
React.ElementRef<typeof DialogPrimitive.Title>,
|
|
68
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
|
69
|
+
>(({ className, ...props }, ref) => (
|
|
70
|
+
<DialogPrimitive.Title
|
|
71
|
+
ref={ref}
|
|
72
|
+
className={cn("text-lg font-semibold leading-none tracking-tight", className)}
|
|
73
|
+
{...props}
|
|
74
|
+
/>
|
|
75
|
+
));
|
|
76
|
+
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
|
77
|
+
|
|
78
|
+
const DialogDescription = React.forwardRef<
|
|
79
|
+
React.ElementRef<typeof DialogPrimitive.Description>,
|
|
80
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
|
81
|
+
>(({ className, ...props }, ref) => (
|
|
82
|
+
<DialogPrimitive.Description
|
|
83
|
+
ref={ref}
|
|
84
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
85
|
+
{...props}
|
|
86
|
+
/>
|
|
87
|
+
));
|
|
88
|
+
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
|
89
|
+
|
|
90
|
+
export {
|
|
91
|
+
Dialog,
|
|
92
|
+
DialogPortal,
|
|
93
|
+
DialogOverlay,
|
|
94
|
+
DialogClose,
|
|
95
|
+
DialogTrigger,
|
|
96
|
+
DialogContent,
|
|
97
|
+
DialogHeader,
|
|
98
|
+
DialogFooter,
|
|
99
|
+
DialogTitle,
|
|
100
|
+
DialogDescription,
|
|
101
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Drawer as DrawerPrimitive } from "vaul";
|
|
5
|
+
import { cn } from "../lib/utils";
|
|
6
|
+
|
|
7
|
+
const Drawer = ({ shouldScaleBackground = true, ...props }: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
|
|
8
|
+
<DrawerPrimitive.Root shouldScaleBackground={shouldScaleBackground} {...props} />
|
|
9
|
+
);
|
|
10
|
+
Drawer.displayName = "Drawer";
|
|
11
|
+
|
|
12
|
+
const DrawerTrigger = DrawerPrimitive.Trigger;
|
|
13
|
+
const DrawerPortal = DrawerPrimitive.Portal;
|
|
14
|
+
const DrawerClose = DrawerPrimitive.Close;
|
|
15
|
+
|
|
16
|
+
const DrawerOverlay = React.forwardRef<
|
|
17
|
+
React.ElementRef<typeof DrawerPrimitive.Overlay>,
|
|
18
|
+
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
|
|
19
|
+
>(({ className, ...props }, ref) => (
|
|
20
|
+
<DrawerPrimitive.Overlay ref={ref} className={cn("fixed inset-0 z-50 bg-foreground/40", className)} {...props} />
|
|
21
|
+
));
|
|
22
|
+
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
|
|
23
|
+
|
|
24
|
+
const DrawerContent = React.forwardRef<
|
|
25
|
+
React.ElementRef<typeof DrawerPrimitive.Content>,
|
|
26
|
+
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
|
|
27
|
+
>(({ className, children, ...props }, ref) => (
|
|
28
|
+
<DrawerPortal>
|
|
29
|
+
<DrawerOverlay />
|
|
30
|
+
<DrawerPrimitive.Content
|
|
31
|
+
ref={ref}
|
|
32
|
+
className={cn(
|
|
33
|
+
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background font-sans",
|
|
34
|
+
className,
|
|
35
|
+
)}
|
|
36
|
+
{...props}
|
|
37
|
+
>
|
|
38
|
+
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
|
|
39
|
+
{children}
|
|
40
|
+
</DrawerPrimitive.Content>
|
|
41
|
+
</DrawerPortal>
|
|
42
|
+
));
|
|
43
|
+
DrawerContent.displayName = "DrawerContent";
|
|
44
|
+
|
|
45
|
+
const DrawerHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
46
|
+
<div className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)} {...props} />
|
|
47
|
+
);
|
|
48
|
+
DrawerHeader.displayName = "DrawerHeader";
|
|
49
|
+
|
|
50
|
+
const DrawerFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
51
|
+
<div className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />
|
|
52
|
+
);
|
|
53
|
+
DrawerFooter.displayName = "DrawerFooter";
|
|
54
|
+
|
|
55
|
+
const DrawerTitle = React.forwardRef<
|
|
56
|
+
React.ElementRef<typeof DrawerPrimitive.Title>,
|
|
57
|
+
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
|
|
58
|
+
>(({ className, ...props }, ref) => (
|
|
59
|
+
<DrawerPrimitive.Title ref={ref} className={cn("text-lg font-semibold leading-none tracking-tight", className)} {...props} />
|
|
60
|
+
));
|
|
61
|
+
DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
|
|
62
|
+
|
|
63
|
+
const DrawerDescription = React.forwardRef<
|
|
64
|
+
React.ElementRef<typeof DrawerPrimitive.Description>,
|
|
65
|
+
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
|
|
66
|
+
>(({ className, ...props }, ref) => (
|
|
67
|
+
<DrawerPrimitive.Description ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
|
|
68
|
+
));
|
|
69
|
+
DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
|
|
70
|
+
|
|
71
|
+
export {
|
|
72
|
+
Drawer,
|
|
73
|
+
DrawerPortal,
|
|
74
|
+
DrawerOverlay,
|
|
75
|
+
DrawerTrigger,
|
|
76
|
+
DrawerClose,
|
|
77
|
+
DrawerContent,
|
|
78
|
+
DrawerHeader,
|
|
79
|
+
DrawerFooter,
|
|
80
|
+
DrawerTitle,
|
|
81
|
+
DrawerDescription,
|
|
82
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
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 "../lib/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 contentCls =
|
|
16
|
+
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md font-sans data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95";
|
|
17
|
+
const itemCls =
|
|
18
|
+
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50";
|
|
19
|
+
|
|
20
|
+
const DropdownMenuContent = React.forwardRef<
|
|
21
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
|
22
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
|
23
|
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
24
|
+
<DropdownMenuPrimitive.Portal>
|
|
25
|
+
<DropdownMenuPrimitive.Content ref={ref} sideOffset={sideOffset} className={cn(contentCls, className)} {...props} />
|
|
26
|
+
</DropdownMenuPrimitive.Portal>
|
|
27
|
+
));
|
|
28
|
+
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
|
29
|
+
|
|
30
|
+
const DropdownMenuItem = React.forwardRef<
|
|
31
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
|
32
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & { inset?: boolean }
|
|
33
|
+
>(({ className, inset, ...props }, ref) => (
|
|
34
|
+
<DropdownMenuPrimitive.Item ref={ref} className={cn(itemCls, inset && "pl-8", className)} {...props} />
|
|
35
|
+
));
|
|
36
|
+
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
|
|
37
|
+
|
|
38
|
+
const DropdownMenuCheckboxItem = React.forwardRef<
|
|
39
|
+
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
|
40
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
|
41
|
+
>(({ className, children, checked, ...props }, ref) => (
|
|
42
|
+
<DropdownMenuPrimitive.CheckboxItem ref={ref} className={cn(itemCls, "pl-8", className)} checked={checked} {...props}>
|
|
43
|
+
<span className="absolute left-2 flex size-3.5 items-center justify-center">
|
|
44
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
45
|
+
<Check className="size-4" />
|
|
46
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
47
|
+
</span>
|
|
48
|
+
{children}
|
|
49
|
+
</DropdownMenuPrimitive.CheckboxItem>
|
|
50
|
+
));
|
|
51
|
+
DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
|
|
52
|
+
|
|
53
|
+
const DropdownMenuRadioItem = React.forwardRef<
|
|
54
|
+
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
|
55
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
|
56
|
+
>(({ className, children, ...props }, ref) => (
|
|
57
|
+
<DropdownMenuPrimitive.RadioItem ref={ref} className={cn(itemCls, "pl-8", className)} {...props}>
|
|
58
|
+
<span className="absolute left-2 flex size-3.5 items-center justify-center">
|
|
59
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
60
|
+
<Circle className="size-2 fill-current" />
|
|
61
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
62
|
+
</span>
|
|
63
|
+
{children}
|
|
64
|
+
</DropdownMenuPrimitive.RadioItem>
|
|
65
|
+
));
|
|
66
|
+
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
|
|
67
|
+
|
|
68
|
+
const DropdownMenuLabel = React.forwardRef<
|
|
69
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
|
70
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & { inset?: boolean }
|
|
71
|
+
>(({ className, inset, ...props }, ref) => (
|
|
72
|
+
<DropdownMenuPrimitive.Label
|
|
73
|
+
ref={ref}
|
|
74
|
+
className={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
));
|
|
78
|
+
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
|
|
79
|
+
|
|
80
|
+
const DropdownMenuSeparator = React.forwardRef<
|
|
81
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
|
82
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
|
83
|
+
>(({ className, ...props }, ref) => (
|
|
84
|
+
<DropdownMenuPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} />
|
|
85
|
+
));
|
|
86
|
+
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
|
|
87
|
+
|
|
88
|
+
const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => (
|
|
89
|
+
<span className={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...props} />
|
|
90
|
+
);
|
|
91
|
+
DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
|
|
92
|
+
|
|
93
|
+
const DropdownMenuSubTrigger = React.forwardRef<
|
|
94
|
+
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
|
95
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & { inset?: boolean }
|
|
96
|
+
>(({ className, inset, children, ...props }, ref) => (
|
|
97
|
+
<DropdownMenuPrimitive.SubTrigger
|
|
98
|
+
ref={ref}
|
|
99
|
+
className={cn(itemCls, "data-[state=open]:bg-accent", inset && "pl-8", className)}
|
|
100
|
+
{...props}
|
|
101
|
+
>
|
|
102
|
+
{children}
|
|
103
|
+
<ChevronRight className="ml-auto size-4" />
|
|
104
|
+
</DropdownMenuPrimitive.SubTrigger>
|
|
105
|
+
));
|
|
106
|
+
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
|
|
107
|
+
|
|
108
|
+
const DropdownMenuSubContent = React.forwardRef<
|
|
109
|
+
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
|
110
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
|
111
|
+
>(({ className, ...props }, ref) => (
|
|
112
|
+
<DropdownMenuPrimitive.SubContent ref={ref} className={cn(contentCls, className)} {...props} />
|
|
113
|
+
));
|
|
114
|
+
DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
|
|
115
|
+
|
|
116
|
+
export {
|
|
117
|
+
DropdownMenu,
|
|
118
|
+
DropdownMenuTrigger,
|
|
119
|
+
DropdownMenuContent,
|
|
120
|
+
DropdownMenuItem,
|
|
121
|
+
DropdownMenuCheckboxItem,
|
|
122
|
+
DropdownMenuRadioItem,
|
|
123
|
+
DropdownMenuLabel,
|
|
124
|
+
DropdownMenuSeparator,
|
|
125
|
+
DropdownMenuShortcut,
|
|
126
|
+
DropdownMenuGroup,
|
|
127
|
+
DropdownMenuPortal,
|
|
128
|
+
DropdownMenuSub,
|
|
129
|
+
DropdownMenuSubContent,
|
|
130
|
+
DropdownMenuSubTrigger,
|
|
131
|
+
DropdownMenuRadioGroup,
|
|
132
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
5
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
6
|
+
import { cn } from "../lib/utils";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Fab — a floating action button. Circular by default; pass a text child
|
|
10
|
+
* alongside the icon to render the extended pill form (`extended`).
|
|
11
|
+
*/
|
|
12
|
+
const fabVariants = cva(
|
|
13
|
+
[
|
|
14
|
+
"inline-flex shrink-0 items-center justify-center gap-2 rounded-full font-sans font-medium",
|
|
15
|
+
"shadow-lg outline-none transition-colors",
|
|
16
|
+
"focus-visible:shadow-focus disabled:pointer-events-none disabled:opacity-50",
|
|
17
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
18
|
+
],
|
|
19
|
+
{
|
|
20
|
+
variants: {
|
|
21
|
+
variant: {
|
|
22
|
+
Primary: "bg-primary text-primary-foreground hover:bg-[--color-blue-600] active:bg-[--color-blue-700]",
|
|
23
|
+
Secondary:
|
|
24
|
+
"bg-secondary text-secondary-foreground hover:bg-[--color-green-500] hover:text-[--color-green-50] active:bg-[--color-green-600] active:text-[--color-green-50]",
|
|
25
|
+
},
|
|
26
|
+
size: {
|
|
27
|
+
default: "size-14 [&_svg]:size-6",
|
|
28
|
+
sm: "size-11 [&_svg]:size-5",
|
|
29
|
+
},
|
|
30
|
+
extended: {
|
|
31
|
+
true: "w-auto px-5",
|
|
32
|
+
false: "",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
defaultVariants: { variant: "Primary", size: "default", extended: false },
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
export interface FabProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof fabVariants> {
|
|
40
|
+
asChild?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const Fab = React.forwardRef<HTMLButtonElement, FabProps>(
|
|
44
|
+
({ className, variant, size, extended, asChild = false, ...props }, ref) => {
|
|
45
|
+
const Comp: React.ElementType = asChild ? Slot : "button";
|
|
46
|
+
return (
|
|
47
|
+
<Comp
|
|
48
|
+
ref={ref}
|
|
49
|
+
data-slot="fab"
|
|
50
|
+
className={cn(fabVariants({ variant, size, extended }), className)}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
Fab.displayName = "Fab";
|
|
57
|
+
|
|
58
|
+
export { Fab, fabVariants };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
5
|
+
import { CircleAlert, CircleCheck, Info, TriangleAlert } from "lucide-react";
|
|
6
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
7
|
+
import { cn } from "../lib/utils";
|
|
8
|
+
import { Label } from "./label";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Field — the label + control + description + feedback composition, matching the
|
|
12
|
+
* KinetixUI design source's input anatomy (label above, helper / feedback below,
|
|
13
|
+
* label & message colour tracking the invalid state). Framework-agnostic: for
|
|
14
|
+
* React Hook Form, use `Form` / `FormField` instead.
|
|
15
|
+
*
|
|
16
|
+
* <Field invalid={!!error}>
|
|
17
|
+
* <FieldLabel>Email</FieldLabel>
|
|
18
|
+
* <FieldControl><Input type="email" /></FieldControl>
|
|
19
|
+
* <FieldDescription>We'll never share it.</FieldDescription>
|
|
20
|
+
* {error && <FieldMessage intent="error">{error}</FieldMessage>}
|
|
21
|
+
* </Field>
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
type FieldContextValue = { id: string; descriptionId: string; messageId: string; invalid: boolean };
|
|
25
|
+
const FieldContext = React.createContext<FieldContextValue | null>(null);
|
|
26
|
+
|
|
27
|
+
function useField() {
|
|
28
|
+
const ctx = React.useContext(FieldContext);
|
|
29
|
+
if (!ctx) throw new Error("Field parts must be used within <Field>");
|
|
30
|
+
return ctx;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface FieldProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
34
|
+
invalid?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const Field = React.forwardRef<HTMLDivElement, FieldProps>(
|
|
38
|
+
({ className, invalid = false, ...props }, ref) => {
|
|
39
|
+
const id = React.useId();
|
|
40
|
+
const value = React.useMemo<FieldContextValue>(
|
|
41
|
+
() => ({ id: `${id}-control`, descriptionId: `${id}-description`, messageId: `${id}-message`, invalid }),
|
|
42
|
+
[id, invalid],
|
|
43
|
+
);
|
|
44
|
+
return (
|
|
45
|
+
<FieldContext.Provider value={value}>
|
|
46
|
+
<div ref={ref} data-invalid={invalid || undefined} className={cn("grid gap-1.5", className)} {...props} />
|
|
47
|
+
</FieldContext.Provider>
|
|
48
|
+
);
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
Field.displayName = "Field";
|
|
52
|
+
|
|
53
|
+
const FieldLabel = React.forwardRef<
|
|
54
|
+
React.ElementRef<typeof Label>,
|
|
55
|
+
React.ComponentPropsWithoutRef<typeof Label>
|
|
56
|
+
>(({ className, ...props }, ref) => {
|
|
57
|
+
const { id, invalid } = useField();
|
|
58
|
+
return <Label ref={ref} htmlFor={id} className={cn(invalid && "text-destructive", className)} {...props} />;
|
|
59
|
+
});
|
|
60
|
+
FieldLabel.displayName = "FieldLabel";
|
|
61
|
+
|
|
62
|
+
/** wraps a single form control (Input, Textarea, Select…) and wires a11y attrs */
|
|
63
|
+
const FieldControl = React.forwardRef<HTMLElement, React.ComponentPropsWithoutRef<typeof Slot>>(
|
|
64
|
+
({ ...props }, ref) => {
|
|
65
|
+
const { id, descriptionId, messageId, invalid } = useField();
|
|
66
|
+
return (
|
|
67
|
+
<Slot
|
|
68
|
+
ref={ref}
|
|
69
|
+
id={id}
|
|
70
|
+
aria-invalid={invalid || undefined}
|
|
71
|
+
aria-describedby={`${descriptionId} ${messageId}`}
|
|
72
|
+
{...props}
|
|
73
|
+
/>
|
|
74
|
+
);
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
FieldControl.displayName = "FieldControl";
|
|
78
|
+
|
|
79
|
+
const FieldDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
|
80
|
+
({ className, ...props }, ref) => {
|
|
81
|
+
const { descriptionId } = useField();
|
|
82
|
+
return <p ref={ref} id={descriptionId} className={cn("text-body-sm text-muted-foreground", className)} {...props} />;
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
FieldDescription.displayName = "FieldDescription";
|
|
86
|
+
|
|
87
|
+
const messageVariants = cva("flex items-center gap-1.5 text-body-sm [&>svg]:size-3.5 [&>svg]:shrink-0", {
|
|
88
|
+
variants: {
|
|
89
|
+
intent: {
|
|
90
|
+
error: "text-destructive",
|
|
91
|
+
warning: "text-warning",
|
|
92
|
+
success: "text-success",
|
|
93
|
+
info: "text-info",
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
defaultVariants: { intent: "error" },
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const INTENT_ICON = {
|
|
100
|
+
error: CircleAlert,
|
|
101
|
+
warning: TriangleAlert,
|
|
102
|
+
success: CircleCheck,
|
|
103
|
+
info: Info,
|
|
104
|
+
} as const;
|
|
105
|
+
|
|
106
|
+
interface FieldMessageProps
|
|
107
|
+
extends React.HTMLAttributes<HTMLParagraphElement>,
|
|
108
|
+
VariantProps<typeof messageVariants> {
|
|
109
|
+
/** hide the leading intent icon */
|
|
110
|
+
hideIcon?: boolean;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const FieldMessage = React.forwardRef<HTMLParagraphElement, FieldMessageProps>(
|
|
114
|
+
({ className, intent = "error", hideIcon, children, ...props }, ref) => {
|
|
115
|
+
const { messageId } = useField();
|
|
116
|
+
const Icon = INTENT_ICON[intent ?? "error"];
|
|
117
|
+
if (!children) return null;
|
|
118
|
+
return (
|
|
119
|
+
<p ref={ref} id={messageId} className={cn(messageVariants({ intent }), className)} {...props}>
|
|
120
|
+
{!hideIcon && <Icon />}
|
|
121
|
+
<span>{children}</span>
|
|
122
|
+
</p>
|
|
123
|
+
);
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
FieldMessage.displayName = "FieldMessage";
|
|
127
|
+
|
|
128
|
+
export { Field, FieldLabel, FieldControl, FieldDescription, FieldMessage, messageVariants };
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { File, Loader2, TriangleAlert, Upload, X } from "lucide-react";
|
|
5
|
+
import { cn } from "../lib/utils";
|
|
6
|
+
import { Button } from "./button";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* FileUpload — a drag-and-drop zone plus file list, single or multiple.
|
|
10
|
+
* Upload logic (network, storage) is the consumer's — this component is
|
|
11
|
+
* presentational: it reports picked files via `onFilesSelected` and renders
|
|
12
|
+
* whatever `files` list you hand back, each carrying its own status.
|
|
13
|
+
*/
|
|
14
|
+
export interface UploadFile {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
size?: number;
|
|
18
|
+
status?: "loading" | "uploaded" | "error";
|
|
19
|
+
progress?: number;
|
|
20
|
+
error?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function formatBytes(bytes?: number) {
|
|
24
|
+
if (!bytes) return "";
|
|
25
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
26
|
+
let v = bytes;
|
|
27
|
+
let i = 0;
|
|
28
|
+
while (v >= 1024 && i < units.length - 1) {
|
|
29
|
+
v /= 1024;
|
|
30
|
+
i++;
|
|
31
|
+
}
|
|
32
|
+
return `${v.toFixed(v < 10 && i > 0 ? 1 : 0)} ${units[i]}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface FileUploadProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onDrop"> {
|
|
36
|
+
multiple?: boolean;
|
|
37
|
+
accept?: string;
|
|
38
|
+
disabled?: boolean;
|
|
39
|
+
helperText?: React.ReactNode;
|
|
40
|
+
buttonLabel?: React.ReactNode;
|
|
41
|
+
files?: UploadFile[];
|
|
42
|
+
onFilesSelected?: (files: File[]) => void;
|
|
43
|
+
onRemove?: (id: string) => void;
|
|
44
|
+
onRetry?: (id: string) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const FileUpload = React.forwardRef<HTMLDivElement, FileUploadProps>(
|
|
48
|
+
(
|
|
49
|
+
{ className, multiple, accept, disabled, helperText, buttonLabel = "Browse files", files = [], onFilesSelected, onRemove, onRetry, ...props },
|
|
50
|
+
ref,
|
|
51
|
+
) => {
|
|
52
|
+
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
53
|
+
const [dragging, setDragging] = React.useState(false);
|
|
54
|
+
|
|
55
|
+
const pick = (list: FileList | null) => {
|
|
56
|
+
if (!list || !list.length) return;
|
|
57
|
+
onFilesSelected?.(Array.from(list));
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div ref={ref} className={cn("flex w-full flex-col gap-3 font-sans", className)} {...props}>
|
|
62
|
+
<div
|
|
63
|
+
role="button"
|
|
64
|
+
tabIndex={disabled ? -1 : 0}
|
|
65
|
+
aria-disabled={disabled}
|
|
66
|
+
onClick={() => !disabled && inputRef.current?.click()}
|
|
67
|
+
onKeyDown={(e) => {
|
|
68
|
+
if (!disabled && (e.key === "Enter" || e.key === " ")) {
|
|
69
|
+
e.preventDefault();
|
|
70
|
+
inputRef.current?.click();
|
|
71
|
+
}
|
|
72
|
+
}}
|
|
73
|
+
onDragOver={(e) => {
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
if (!disabled) setDragging(true);
|
|
76
|
+
}}
|
|
77
|
+
onDragLeave={() => setDragging(false)}
|
|
78
|
+
onDrop={(e) => {
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
setDragging(false);
|
|
81
|
+
if (!disabled) pick(e.dataTransfer.files);
|
|
82
|
+
}}
|
|
83
|
+
className={cn(
|
|
84
|
+
"flex flex-col items-center gap-2 rounded-md border border-dashed border-input p-6 text-center outline-none transition-colors",
|
|
85
|
+
!disabled && "cursor-pointer hover:bg-accent/50 focus-visible:shadow-focus",
|
|
86
|
+
dragging && "border-primary bg-accent",
|
|
87
|
+
disabled && "pointer-events-none opacity-50",
|
|
88
|
+
)}
|
|
89
|
+
>
|
|
90
|
+
<Upload className="size-6 text-muted-foreground" aria-hidden />
|
|
91
|
+
<p className="text-body-sm text-muted-foreground">Drag & drop {multiple ? "files" : "a file"} here, or</p>
|
|
92
|
+
<Button
|
|
93
|
+
type="button"
|
|
94
|
+
variant="Outline"
|
|
95
|
+
size="sm"
|
|
96
|
+
disabled={disabled}
|
|
97
|
+
onClick={(e) => {
|
|
98
|
+
e.stopPropagation();
|
|
99
|
+
inputRef.current?.click();
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
{buttonLabel}
|
|
103
|
+
</Button>
|
|
104
|
+
{helperText && <p className="text-body-sm text-muted-foreground">{helperText}</p>}
|
|
105
|
+
<input
|
|
106
|
+
ref={inputRef}
|
|
107
|
+
type="file"
|
|
108
|
+
multiple={multiple}
|
|
109
|
+
accept={accept}
|
|
110
|
+
disabled={disabled}
|
|
111
|
+
className="sr-only"
|
|
112
|
+
onChange={(e) => {
|
|
113
|
+
pick(e.target.files);
|
|
114
|
+
e.target.value = "";
|
|
115
|
+
}}
|
|
116
|
+
/>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
{files.length > 0 && (
|
|
120
|
+
<ul className="flex flex-col gap-2">
|
|
121
|
+
{files.map((f) => (
|
|
122
|
+
<FileUploadItem key={f.id} file={f} onRemove={onRemove} onRetry={onRetry} />
|
|
123
|
+
))}
|
|
124
|
+
</ul>
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
FileUpload.displayName = "FileUpload";
|
|
131
|
+
|
|
132
|
+
export interface FileUploadItemProps {
|
|
133
|
+
file: UploadFile;
|
|
134
|
+
onRemove?: (id: string) => void;
|
|
135
|
+
onRetry?: (id: string) => void;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function FileUploadItem({ file, onRemove, onRetry }: FileUploadItemProps) {
|
|
139
|
+
const isError = file.status === "error";
|
|
140
|
+
const isLoading = file.status === "loading";
|
|
141
|
+
return (
|
|
142
|
+
<li className={cn("flex items-center gap-2.5 rounded-md border p-2.5 text-body-sm", isError ? "border-destructive bg-destructive/5" : "border-input")}>
|
|
143
|
+
{isLoading ? (
|
|
144
|
+
<Loader2 className="size-4 shrink-0 animate-spin text-muted-foreground" aria-hidden />
|
|
145
|
+
) : isError ? (
|
|
146
|
+
<TriangleAlert className="size-4 shrink-0 text-destructive" aria-hidden />
|
|
147
|
+
) : (
|
|
148
|
+
<File className="size-4 shrink-0 text-muted-foreground" aria-hidden />
|
|
149
|
+
)}
|
|
150
|
+
<div className="min-w-0 flex-1">
|
|
151
|
+
<p className="truncate text-foreground">{file.name}</p>
|
|
152
|
+
{isError && file.error ? <p className="text-destructive">{file.error}</p> : <p className="text-muted-foreground">{formatBytes(file.size)}</p>}
|
|
153
|
+
{isLoading && typeof file.progress === "number" && (
|
|
154
|
+
<div className="mt-1 h-1 w-full overflow-hidden rounded-full bg-muted">
|
|
155
|
+
<div className="h-full bg-primary transition-[width]" style={{ width: `${Math.max(0, Math.min(100, file.progress))}%` }} />
|
|
156
|
+
</div>
|
|
157
|
+
)}
|
|
158
|
+
</div>
|
|
159
|
+
{isError && onRetry && (
|
|
160
|
+
<button
|
|
161
|
+
type="button"
|
|
162
|
+
onClick={() => onRetry(file.id)}
|
|
163
|
+
className="shrink-0 text-label-md font-medium text-primary underline-offset-2 outline-none hover:underline focus-visible:underline"
|
|
164
|
+
>
|
|
165
|
+
Retry
|
|
166
|
+
</button>
|
|
167
|
+
)}
|
|
168
|
+
{onRemove && (
|
|
169
|
+
<button
|
|
170
|
+
type="button"
|
|
171
|
+
onClick={() => onRemove(file.id)}
|
|
172
|
+
aria-label={`Remove ${file.name}`}
|
|
173
|
+
className="shrink-0 rounded-[2px] p-0.5 text-muted-foreground outline-none transition-colors hover:text-foreground focus-visible:ring-1 focus-visible:ring-current"
|
|
174
|
+
>
|
|
175
|
+
<X className="size-3.5" />
|
|
176
|
+
</button>
|
|
177
|
+
)}
|
|
178
|
+
</li>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
FileUploadItem.displayName = "FileUploadItem";
|
|
182
|
+
|
|
183
|
+
export { FileUpload, FileUploadItem, formatBytes };
|