@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,67 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* CircularProgress — a ring progress indicator. Track = `--muted`, indicator
|
|
8
|
+
* = `--primary`. Optionally shows the value in the centre.
|
|
9
|
+
*/
|
|
10
|
+
export interface CircularProgressProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
11
|
+
value?: number;
|
|
12
|
+
/** diameter in px */
|
|
13
|
+
size?: number;
|
|
14
|
+
strokeWidth?: number;
|
|
15
|
+
showValue?: boolean;
|
|
16
|
+
/** override the visible label (defaults to `${value}%`) */
|
|
17
|
+
label?: React.ReactNode;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const CircularProgress = React.forwardRef<HTMLDivElement, CircularProgressProps>(
|
|
21
|
+
({ value = 0, size = 48, strokeWidth = 4, showValue = false, label, className, ...props }, ref) => {
|
|
22
|
+
const v = Math.max(0, Math.min(100, value));
|
|
23
|
+
const r = (size - strokeWidth) / 2;
|
|
24
|
+
const c = 2 * Math.PI * r;
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
ref={ref}
|
|
29
|
+
role="progressbar"
|
|
30
|
+
aria-valuemin={0}
|
|
31
|
+
aria-valuemax={100}
|
|
32
|
+
aria-valuenow={v}
|
|
33
|
+
className={cn("relative inline-grid place-items-center font-sans", className)}
|
|
34
|
+
style={{ width: size, height: size }}
|
|
35
|
+
{...props}
|
|
36
|
+
>
|
|
37
|
+
<svg width={size} height={size} className="-rotate-90" aria-hidden>
|
|
38
|
+
<circle
|
|
39
|
+
cx={size / 2}
|
|
40
|
+
cy={size / 2}
|
|
41
|
+
r={r}
|
|
42
|
+
fill="none"
|
|
43
|
+
strokeWidth={strokeWidth}
|
|
44
|
+
className="stroke-muted"
|
|
45
|
+
/>
|
|
46
|
+
<circle
|
|
47
|
+
cx={size / 2}
|
|
48
|
+
cy={size / 2}
|
|
49
|
+
r={r}
|
|
50
|
+
fill="none"
|
|
51
|
+
strokeWidth={strokeWidth}
|
|
52
|
+
strokeLinecap="round"
|
|
53
|
+
strokeDasharray={c}
|
|
54
|
+
strokeDashoffset={c - (v / 100) * c}
|
|
55
|
+
className="stroke-primary transition-[stroke-dashoffset] duration-300"
|
|
56
|
+
/>
|
|
57
|
+
</svg>
|
|
58
|
+
{(showValue || label != null) && (
|
|
59
|
+
<span className="absolute text-label-md text-foreground">{label ?? `${Math.round(v)}%`}</span>
|
|
60
|
+
)}
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
CircularProgress.displayName = "CircularProgress";
|
|
66
|
+
|
|
67
|
+
export { CircularProgress };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Check, Copy } from "lucide-react";
|
|
5
|
+
import { cn } from "../lib/utils";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* CodeBlock — a code display with a copy button and, for more than one
|
|
9
|
+
* file, a tab strip. Presentational only: bring your own syntax
|
|
10
|
+
* highlighting by rendering highlighted markup as `code`/`files[].code`.
|
|
11
|
+
*/
|
|
12
|
+
export interface CodeBlockFile {
|
|
13
|
+
name: string;
|
|
14
|
+
code: string;
|
|
15
|
+
language?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface CodeBlockProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
|
|
19
|
+
code?: string;
|
|
20
|
+
language?: string;
|
|
21
|
+
filename?: string;
|
|
22
|
+
files?: CodeBlockFile[];
|
|
23
|
+
hideCopy?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const CodeBlock = React.forwardRef<HTMLDivElement, CodeBlockProps>(
|
|
27
|
+
({ className, code, language, filename, files, hideCopy, ...props }, ref) => {
|
|
28
|
+
const tabs = files && files.length > 0 ? files : [{ name: filename ?? language ?? "", code: code ?? "" }];
|
|
29
|
+
const [active, setActive] = React.useState(0);
|
|
30
|
+
const [copied, setCopied] = React.useState(false);
|
|
31
|
+
const current = tabs[active] ?? tabs[0]!;
|
|
32
|
+
const hasHeader = tabs.length > 1 || !!current.name;
|
|
33
|
+
|
|
34
|
+
const copy = async () => {
|
|
35
|
+
try {
|
|
36
|
+
await navigator.clipboard.writeText(current.code);
|
|
37
|
+
setCopied(true);
|
|
38
|
+
window.setTimeout(() => setCopied(false), 1500);
|
|
39
|
+
} catch {
|
|
40
|
+
// clipboard unavailable — no-op
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div ref={ref} className={cn("overflow-hidden rounded-md border border-input bg-muted font-sans", className)} {...props}>
|
|
46
|
+
{hasHeader && (
|
|
47
|
+
<div className="flex items-center justify-between border-b border-input bg-background px-2">
|
|
48
|
+
<div className="flex">
|
|
49
|
+
{tabs.map((t, i) => (
|
|
50
|
+
<button
|
|
51
|
+
key={t.name || i}
|
|
52
|
+
type="button"
|
|
53
|
+
onClick={() => setActive(i)}
|
|
54
|
+
className={cn(
|
|
55
|
+
"border-b-2 px-3 py-2 text-body-sm transition-colors outline-none",
|
|
56
|
+
i === active ? "border-primary text-foreground" : "border-transparent text-muted-foreground hover:text-foreground",
|
|
57
|
+
)}
|
|
58
|
+
>
|
|
59
|
+
{t.name || "code"}
|
|
60
|
+
</button>
|
|
61
|
+
))}
|
|
62
|
+
</div>
|
|
63
|
+
{!hideCopy && (
|
|
64
|
+
<button
|
|
65
|
+
type="button"
|
|
66
|
+
onClick={copy}
|
|
67
|
+
aria-label="Copy code"
|
|
68
|
+
className="rounded-[2px] p-1.5 text-muted-foreground outline-none transition-colors hover:text-foreground focus-visible:ring-1 focus-visible:ring-current"
|
|
69
|
+
>
|
|
70
|
+
{copied ? <Check className="size-4" /> : <Copy className="size-4" />}
|
|
71
|
+
</button>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
75
|
+
<div className="relative">
|
|
76
|
+
{!hasHeader && !hideCopy && (
|
|
77
|
+
<button
|
|
78
|
+
type="button"
|
|
79
|
+
onClick={copy}
|
|
80
|
+
aria-label="Copy code"
|
|
81
|
+
className="absolute right-2 top-2 rounded-[2px] p-1.5 text-muted-foreground outline-none transition-colors hover:text-foreground focus-visible:ring-1 focus-visible:ring-current"
|
|
82
|
+
>
|
|
83
|
+
{copied ? <Check className="size-4" /> : <Copy className="size-4" />}
|
|
84
|
+
</button>
|
|
85
|
+
)}
|
|
86
|
+
<pre className="overflow-x-auto p-4 text-body-sm text-foreground">
|
|
87
|
+
<code>{current.code}</code>
|
|
88
|
+
</pre>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
CodeBlock.displayName = "CodeBlock";
|
|
95
|
+
|
|
96
|
+
export { CodeBlock };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
|
|
4
|
+
|
|
5
|
+
const Collapsible = CollapsiblePrimitive.Root;
|
|
6
|
+
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
|
|
7
|
+
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
|
|
8
|
+
|
|
9
|
+
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Command as CommandPrimitive } from "cmdk";
|
|
5
|
+
import { Search } from "lucide-react";
|
|
6
|
+
import { cn } from "../lib/utils";
|
|
7
|
+
import { Dialog, DialogContent } from "./dialog";
|
|
8
|
+
|
|
9
|
+
const Command = React.forwardRef<
|
|
10
|
+
React.ElementRef<typeof CommandPrimitive>,
|
|
11
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
|
12
|
+
>(({ className, ...props }, ref) => (
|
|
13
|
+
<CommandPrimitive
|
|
14
|
+
ref={ref}
|
|
15
|
+
className={cn(
|
|
16
|
+
"flex size-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground font-sans",
|
|
17
|
+
className,
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
));
|
|
22
|
+
Command.displayName = CommandPrimitive.displayName;
|
|
23
|
+
|
|
24
|
+
const CommandDialog = ({ children, ...props }: React.ComponentProps<typeof Dialog>) => (
|
|
25
|
+
<Dialog {...props}>
|
|
26
|
+
<DialogContent className="overflow-hidden p-0">
|
|
27
|
+
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-input-wrapper]_svg]:size-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:size-5">
|
|
28
|
+
{children}
|
|
29
|
+
</Command>
|
|
30
|
+
</DialogContent>
|
|
31
|
+
</Dialog>
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const CommandInput = React.forwardRef<
|
|
35
|
+
React.ElementRef<typeof CommandPrimitive.Input>,
|
|
36
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
|
37
|
+
>(({ className, ...props }, ref) => (
|
|
38
|
+
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
|
39
|
+
<Search className="mr-2 size-4 shrink-0 opacity-50" />
|
|
40
|
+
<CommandPrimitive.Input
|
|
41
|
+
ref={ref}
|
|
42
|
+
className={cn(
|
|
43
|
+
"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
</div>
|
|
49
|
+
));
|
|
50
|
+
CommandInput.displayName = CommandPrimitive.Input.displayName;
|
|
51
|
+
|
|
52
|
+
const CommandList = React.forwardRef<
|
|
53
|
+
React.ElementRef<typeof CommandPrimitive.List>,
|
|
54
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
|
55
|
+
>(({ className, ...props }, ref) => (
|
|
56
|
+
<CommandPrimitive.List
|
|
57
|
+
ref={ref}
|
|
58
|
+
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
));
|
|
62
|
+
CommandList.displayName = CommandPrimitive.List.displayName;
|
|
63
|
+
|
|
64
|
+
const CommandEmpty = React.forwardRef<
|
|
65
|
+
React.ElementRef<typeof CommandPrimitive.Empty>,
|
|
66
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
|
67
|
+
>((props, ref) => <CommandPrimitive.Empty ref={ref} className="py-6 text-center text-sm" {...props} />);
|
|
68
|
+
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
|
|
69
|
+
|
|
70
|
+
const CommandGroup = React.forwardRef<
|
|
71
|
+
React.ElementRef<typeof CommandPrimitive.Group>,
|
|
72
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
|
73
|
+
>(({ className, ...props }, ref) => (
|
|
74
|
+
<CommandPrimitive.Group
|
|
75
|
+
ref={ref}
|
|
76
|
+
className={cn(
|
|
77
|
+
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
|
|
78
|
+
className,
|
|
79
|
+
)}
|
|
80
|
+
{...props}
|
|
81
|
+
/>
|
|
82
|
+
));
|
|
83
|
+
CommandGroup.displayName = CommandPrimitive.Group.displayName;
|
|
84
|
+
|
|
85
|
+
const CommandSeparator = React.forwardRef<
|
|
86
|
+
React.ElementRef<typeof CommandPrimitive.Separator>,
|
|
87
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
|
88
|
+
>(({ className, ...props }, ref) => (
|
|
89
|
+
<CommandPrimitive.Separator ref={ref} className={cn("-mx-1 h-px bg-border", className)} {...props} />
|
|
90
|
+
));
|
|
91
|
+
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
|
|
92
|
+
|
|
93
|
+
const CommandItem = React.forwardRef<
|
|
94
|
+
React.ElementRef<typeof CommandPrimitive.Item>,
|
|
95
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
|
96
|
+
>(({ className, ...props }, ref) => (
|
|
97
|
+
<CommandPrimitive.Item
|
|
98
|
+
ref={ref}
|
|
99
|
+
className={cn(
|
|
100
|
+
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0",
|
|
101
|
+
className,
|
|
102
|
+
)}
|
|
103
|
+
{...props}
|
|
104
|
+
/>
|
|
105
|
+
));
|
|
106
|
+
CommandItem.displayName = CommandPrimitive.Item.displayName;
|
|
107
|
+
|
|
108
|
+
const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => (
|
|
109
|
+
<span className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)} {...props} />
|
|
110
|
+
);
|
|
111
|
+
CommandShortcut.displayName = "CommandShortcut";
|
|
112
|
+
|
|
113
|
+
export {
|
|
114
|
+
Command,
|
|
115
|
+
CommandDialog,
|
|
116
|
+
CommandInput,
|
|
117
|
+
CommandList,
|
|
118
|
+
CommandEmpty,
|
|
119
|
+
CommandGroup,
|
|
120
|
+
CommandItem,
|
|
121
|
+
CommandShortcut,
|
|
122
|
+
CommandSeparator,
|
|
123
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
|
|
5
|
+
import { Check, ChevronRight, Circle } from "lucide-react";
|
|
6
|
+
import { cn } from "../lib/utils";
|
|
7
|
+
|
|
8
|
+
const ContextMenu = ContextMenuPrimitive.Root;
|
|
9
|
+
const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
|
|
10
|
+
const ContextMenuGroup = ContextMenuPrimitive.Group;
|
|
11
|
+
const ContextMenuPortal = ContextMenuPrimitive.Portal;
|
|
12
|
+
const ContextMenuSub = ContextMenuPrimitive.Sub;
|
|
13
|
+
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
|
|
14
|
+
|
|
15
|
+
const itemCls =
|
|
16
|
+
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50";
|
|
17
|
+
const contentCls =
|
|
18
|
+
"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";
|
|
19
|
+
|
|
20
|
+
const ContextMenuSubTrigger = React.forwardRef<
|
|
21
|
+
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
|
|
22
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & { inset?: boolean }
|
|
23
|
+
>(({ className, inset, children, ...props }, ref) => (
|
|
24
|
+
<ContextMenuPrimitive.SubTrigger
|
|
25
|
+
ref={ref}
|
|
26
|
+
className={cn(itemCls, "data-[state=open]:bg-accent", inset && "pl-8", className)}
|
|
27
|
+
{...props}
|
|
28
|
+
>
|
|
29
|
+
{children}
|
|
30
|
+
<ChevronRight className="ml-auto size-4" />
|
|
31
|
+
</ContextMenuPrimitive.SubTrigger>
|
|
32
|
+
));
|
|
33
|
+
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
|
|
34
|
+
|
|
35
|
+
const ContextMenuSubContent = React.forwardRef<
|
|
36
|
+
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
|
|
37
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
|
|
38
|
+
>(({ className, ...props }, ref) => (
|
|
39
|
+
<ContextMenuPrimitive.SubContent ref={ref} className={cn(contentCls, className)} {...props} />
|
|
40
|
+
));
|
|
41
|
+
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
|
|
42
|
+
|
|
43
|
+
const ContextMenuContent = React.forwardRef<
|
|
44
|
+
React.ElementRef<typeof ContextMenuPrimitive.Content>,
|
|
45
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
|
|
46
|
+
>(({ className, ...props }, ref) => (
|
|
47
|
+
<ContextMenuPrimitive.Portal>
|
|
48
|
+
<ContextMenuPrimitive.Content ref={ref} className={cn(contentCls, className)} {...props} />
|
|
49
|
+
</ContextMenuPrimitive.Portal>
|
|
50
|
+
));
|
|
51
|
+
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
|
|
52
|
+
|
|
53
|
+
const ContextMenuItem = React.forwardRef<
|
|
54
|
+
React.ElementRef<typeof ContextMenuPrimitive.Item>,
|
|
55
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & { inset?: boolean }
|
|
56
|
+
>(({ className, inset, ...props }, ref) => (
|
|
57
|
+
<ContextMenuPrimitive.Item ref={ref} className={cn(itemCls, inset && "pl-8", className)} {...props} />
|
|
58
|
+
));
|
|
59
|
+
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
|
|
60
|
+
|
|
61
|
+
const ContextMenuCheckboxItem = React.forwardRef<
|
|
62
|
+
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
|
|
63
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
|
|
64
|
+
>(({ className, children, checked, ...props }, ref) => (
|
|
65
|
+
<ContextMenuPrimitive.CheckboxItem ref={ref} className={cn(itemCls, "pl-8", className)} checked={checked} {...props}>
|
|
66
|
+
<span className="absolute left-2 flex size-3.5 items-center justify-center">
|
|
67
|
+
<ContextMenuPrimitive.ItemIndicator>
|
|
68
|
+
<Check className="size-4" />
|
|
69
|
+
</ContextMenuPrimitive.ItemIndicator>
|
|
70
|
+
</span>
|
|
71
|
+
{children}
|
|
72
|
+
</ContextMenuPrimitive.CheckboxItem>
|
|
73
|
+
));
|
|
74
|
+
ContextMenuCheckboxItem.displayName = ContextMenuPrimitive.CheckboxItem.displayName;
|
|
75
|
+
|
|
76
|
+
const ContextMenuRadioItem = React.forwardRef<
|
|
77
|
+
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
|
|
78
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
|
|
79
|
+
>(({ className, children, ...props }, ref) => (
|
|
80
|
+
<ContextMenuPrimitive.RadioItem ref={ref} className={cn(itemCls, "pl-8", className)} {...props}>
|
|
81
|
+
<span className="absolute left-2 flex size-3.5 items-center justify-center">
|
|
82
|
+
<ContextMenuPrimitive.ItemIndicator>
|
|
83
|
+
<Circle className="size-2 fill-current" />
|
|
84
|
+
</ContextMenuPrimitive.ItemIndicator>
|
|
85
|
+
</span>
|
|
86
|
+
{children}
|
|
87
|
+
</ContextMenuPrimitive.RadioItem>
|
|
88
|
+
));
|
|
89
|
+
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
|
|
90
|
+
|
|
91
|
+
const ContextMenuLabel = React.forwardRef<
|
|
92
|
+
React.ElementRef<typeof ContextMenuPrimitive.Label>,
|
|
93
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & { inset?: boolean }
|
|
94
|
+
>(({ className, inset, ...props }, ref) => (
|
|
95
|
+
<ContextMenuPrimitive.Label
|
|
96
|
+
ref={ref}
|
|
97
|
+
className={cn("px-2 py-1.5 text-sm font-semibold text-foreground", inset && "pl-8", className)}
|
|
98
|
+
{...props}
|
|
99
|
+
/>
|
|
100
|
+
));
|
|
101
|
+
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
|
|
102
|
+
|
|
103
|
+
const ContextMenuSeparator = React.forwardRef<
|
|
104
|
+
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
|
|
105
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
|
|
106
|
+
>(({ className, ...props }, ref) => (
|
|
107
|
+
<ContextMenuPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-border", className)} {...props} />
|
|
108
|
+
));
|
|
109
|
+
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
|
|
110
|
+
|
|
111
|
+
const ContextMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => (
|
|
112
|
+
<span className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)} {...props} />
|
|
113
|
+
);
|
|
114
|
+
ContextMenuShortcut.displayName = "ContextMenuShortcut";
|
|
115
|
+
|
|
116
|
+
export {
|
|
117
|
+
ContextMenu,
|
|
118
|
+
ContextMenuTrigger,
|
|
119
|
+
ContextMenuContent,
|
|
120
|
+
ContextMenuItem,
|
|
121
|
+
ContextMenuCheckboxItem,
|
|
122
|
+
ContextMenuRadioItem,
|
|
123
|
+
ContextMenuLabel,
|
|
124
|
+
ContextMenuSeparator,
|
|
125
|
+
ContextMenuShortcut,
|
|
126
|
+
ContextMenuGroup,
|
|
127
|
+
ContextMenuPortal,
|
|
128
|
+
ContextMenuSub,
|
|
129
|
+
ContextMenuSubContent,
|
|
130
|
+
ContextMenuSubTrigger,
|
|
131
|
+
ContextMenuRadioGroup,
|
|
132
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import {
|
|
5
|
+
flexRender,
|
|
6
|
+
getCoreRowModel,
|
|
7
|
+
getPaginationRowModel,
|
|
8
|
+
getSortedRowModel,
|
|
9
|
+
useReactTable,
|
|
10
|
+
type ColumnDef,
|
|
11
|
+
type SortingState,
|
|
12
|
+
} from "@tanstack/react-table";
|
|
13
|
+
import { cn } from "../lib/utils";
|
|
14
|
+
import { Button } from "./button";
|
|
15
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./table";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* DataTable — a thin wrapper over @tanstack/react-table + the KinetixUI Table.
|
|
19
|
+
* Pass `columns` and `data`; sorting + pagination are on by default. For full
|
|
20
|
+
* control, drop down to `useReactTable` directly.
|
|
21
|
+
*/
|
|
22
|
+
export function DataTable<TData, TValue>({
|
|
23
|
+
columns,
|
|
24
|
+
data,
|
|
25
|
+
className,
|
|
26
|
+
pageSize = 10,
|
|
27
|
+
}: {
|
|
28
|
+
columns: ColumnDef<TData, TValue>[];
|
|
29
|
+
data: TData[];
|
|
30
|
+
className?: string;
|
|
31
|
+
pageSize?: number;
|
|
32
|
+
}) {
|
|
33
|
+
const [sorting, setSorting] = React.useState<SortingState>([]);
|
|
34
|
+
|
|
35
|
+
const table = useReactTable({
|
|
36
|
+
data,
|
|
37
|
+
columns,
|
|
38
|
+
state: { sorting },
|
|
39
|
+
onSortingChange: setSorting,
|
|
40
|
+
getCoreRowModel: getCoreRowModel(),
|
|
41
|
+
getSortedRowModel: getSortedRowModel(),
|
|
42
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
43
|
+
initialState: { pagination: { pageSize } },
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className={cn("space-y-3", className)}>
|
|
48
|
+
<div className="rounded-md border">
|
|
49
|
+
<Table>
|
|
50
|
+
<TableHeader>
|
|
51
|
+
{table.getHeaderGroups().map((hg) => (
|
|
52
|
+
<TableRow key={hg.id}>
|
|
53
|
+
{hg.headers.map((header) => (
|
|
54
|
+
<TableHead key={header.id}>
|
|
55
|
+
{header.isPlaceholder
|
|
56
|
+
? null
|
|
57
|
+
: flexRender(header.column.columnDef.header, header.getContext())}
|
|
58
|
+
</TableHead>
|
|
59
|
+
))}
|
|
60
|
+
</TableRow>
|
|
61
|
+
))}
|
|
62
|
+
</TableHeader>
|
|
63
|
+
<TableBody>
|
|
64
|
+
{table.getRowModel().rows.length ? (
|
|
65
|
+
table.getRowModel().rows.map((row) => (
|
|
66
|
+
<TableRow key={row.id} data-state={row.getIsSelected() && "selected"}>
|
|
67
|
+
{row.getVisibleCells().map((cell) => (
|
|
68
|
+
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
|
|
69
|
+
))}
|
|
70
|
+
</TableRow>
|
|
71
|
+
))
|
|
72
|
+
) : (
|
|
73
|
+
<TableRow>
|
|
74
|
+
<TableCell colSpan={columns.length} className="h-24 text-center">
|
|
75
|
+
No results.
|
|
76
|
+
</TableCell>
|
|
77
|
+
</TableRow>
|
|
78
|
+
)}
|
|
79
|
+
</TableBody>
|
|
80
|
+
</Table>
|
|
81
|
+
</div>
|
|
82
|
+
<div className="flex items-center justify-end gap-2">
|
|
83
|
+
<Button variant="Outline" size="sm" onClick={() => table.previousPage()} disabled={!table.getCanPreviousPage()}>
|
|
84
|
+
Previous
|
|
85
|
+
</Button>
|
|
86
|
+
<Button variant="Outline" size="sm" onClick={() => table.nextPage()} disabled={!table.getCanNextPage()}>
|
|
87
|
+
Next
|
|
88
|
+
</Button>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { CalendarIcon } from "lucide-react";
|
|
5
|
+
import { format } from "date-fns";
|
|
6
|
+
import { cn } from "../lib/utils";
|
|
7
|
+
import { Button } from "./button";
|
|
8
|
+
import { Calendar, type CalendarProps } from "./calendar";
|
|
9
|
+
import { Popover, PopoverContent, PopoverTrigger } from "./popover";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* DatePicker — a text-field trigger + calendar popover. Composes the
|
|
13
|
+
* existing `Popover` + `Calendar`; single-date only — for a range, compose
|
|
14
|
+
* `Calendar mode="range"` directly.
|
|
15
|
+
*/
|
|
16
|
+
export interface DatePickerProps {
|
|
17
|
+
value?: Date;
|
|
18
|
+
onChange?: (date: Date | undefined) => void;
|
|
19
|
+
placeholder?: string;
|
|
20
|
+
label?: React.ReactNode;
|
|
21
|
+
helperText?: React.ReactNode;
|
|
22
|
+
error?: boolean;
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
className?: string;
|
|
25
|
+
formatStr?: string;
|
|
26
|
+
calendarProps?: Omit<CalendarProps, "mode" | "selected" | "onSelect">;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
|
|
30
|
+
({ value, onChange, placeholder = "Select date", label, helperText, error, disabled, className, formatStr = "PP", calendarProps }, ref) => {
|
|
31
|
+
const [open, setOpen] = React.useState(false);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className={cn("flex flex-col gap-1.5 font-sans", className)}>
|
|
35
|
+
{label && <label className="text-label-md font-medium text-foreground">{label}</label>}
|
|
36
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
37
|
+
<PopoverTrigger asChild>
|
|
38
|
+
<Button
|
|
39
|
+
ref={ref}
|
|
40
|
+
type="button"
|
|
41
|
+
variant="Outline"
|
|
42
|
+
disabled={disabled}
|
|
43
|
+
className={cn("w-full justify-start px-3 font-normal", !value && "text-muted-foreground", error && "border-destructive text-destructive")}
|
|
44
|
+
>
|
|
45
|
+
<CalendarIcon className="size-4" />
|
|
46
|
+
{value ? format(value, formatStr) : placeholder}
|
|
47
|
+
</Button>
|
|
48
|
+
</PopoverTrigger>
|
|
49
|
+
<PopoverContent className="w-auto p-0" align="start">
|
|
50
|
+
<Calendar
|
|
51
|
+
mode="single"
|
|
52
|
+
selected={value}
|
|
53
|
+
onSelect={(d) => {
|
|
54
|
+
onChange?.(d);
|
|
55
|
+
setOpen(false);
|
|
56
|
+
}}
|
|
57
|
+
{...calendarProps}
|
|
58
|
+
/>
|
|
59
|
+
</PopoverContent>
|
|
60
|
+
</Popover>
|
|
61
|
+
{helperText && <p className={cn("text-body-sm", error ? "text-destructive" : "text-muted-foreground")}>{helperText}</p>}
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
DatePicker.displayName = "DatePicker";
|
|
67
|
+
|
|
68
|
+
export { DatePicker };
|