@hilum/ui 0.1.1
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/README.md +42 -0
- package/dist/icons.d.ts +1 -0
- package/dist/icons.js +3 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +992 -0
- package/dist/index.js +4433 -0
- package/dist/index.js.map +1 -0
- package/dist/tokens.css +265 -0
- package/dist/tokens.d.ts +189 -0
- package/dist/tokens.js +193 -0
- package/dist/tokens.js.map +1 -0
- package/package.json +80 -0
- package/src/components/__tests__/data.test.tsx +180 -0
- package/src/components/__tests__/disclosure.test.tsx +198 -0
- package/src/components/__tests__/display.test.tsx +262 -0
- package/src/components/__tests__/feedback.test.tsx +138 -0
- package/src/components/__tests__/form.test.tsx +297 -0
- package/src/components/__tests__/layout.test.tsx +388 -0
- package/src/components/__tests__/overlay.test.tsx +90 -0
- package/src/components/__tests__/primitives.test.tsx +248 -0
- package/src/components/accordion.tsx +68 -0
- package/src/components/action-panel.tsx +112 -0
- package/src/components/activity-feed.tsx +69 -0
- package/src/components/alert-dialog.tsx +132 -0
- package/src/components/alert.tsx +44 -0
- package/src/components/aspect-ratio.tsx +19 -0
- package/src/components/avatar-stack.tsx +70 -0
- package/src/components/avatar.tsx +103 -0
- package/src/components/badge.tsx +33 -0
- package/src/components/breadcrumb.tsx +76 -0
- package/src/components/button-group.tsx +45 -0
- package/src/components/button.tsx +60 -0
- package/src/components/calendar.tsx +61 -0
- package/src/components/card-heading.tsx +60 -0
- package/src/components/card.tsx +120 -0
- package/src/components/carousel.tsx +222 -0
- package/src/components/chart.tsx +77 -0
- package/src/components/checkbox.tsx +35 -0
- package/src/components/collapsible.tsx +9 -0
- package/src/components/color-input.tsx +90 -0
- package/src/components/color-picker.tsx +111 -0
- package/src/components/combobox.tsx +207 -0
- package/src/components/command-palette.tsx +137 -0
- package/src/components/command.tsx +248 -0
- package/src/components/context-menu.tsx +201 -0
- package/src/components/data-table.tsx +151 -0
- package/src/components/date-picker.tsx +56 -0
- package/src/components/description-list.tsx +70 -0
- package/src/components/dialog.tsx +97 -0
- package/src/components/drawer.tsx +90 -0
- package/src/components/dropdown-menu.tsx +202 -0
- package/src/components/empty-state.tsx +43 -0
- package/src/components/field.tsx +45 -0
- package/src/components/grid-list.tsx +159 -0
- package/src/components/hover-card.tsx +34 -0
- package/src/components/input-group.tsx +134 -0
- package/src/components/input-number.tsx +142 -0
- package/src/components/input-otp.tsx +68 -0
- package/src/components/input.tsx +23 -0
- package/src/components/kbd.tsx +18 -0
- package/src/components/label.tsx +23 -0
- package/src/components/media-object.tsx +49 -0
- package/src/components/menubar.tsx +235 -0
- package/src/components/native-select.tsx +44 -0
- package/src/components/navigation-menu.tsx +158 -0
- package/src/components/notification.tsx +109 -0
- package/src/components/page-heading.tsx +125 -0
- package/src/components/pagination.tsx +89 -0
- package/src/components/popover.tsx +35 -0
- package/src/components/progress.tsx +23 -0
- package/src/components/property-row.tsx +56 -0
- package/src/components/radio-card.tsx +105 -0
- package/src/components/radio-group.tsx +38 -0
- package/src/components/resizable.tsx +74 -0
- package/src/components/scroll-area.tsx +42 -0
- package/src/components/section-heading.tsx +64 -0
- package/src/components/select.tsx +130 -0
- package/src/components/separator.tsx +25 -0
- package/src/components/sheet.tsx +117 -0
- package/src/components/sidebar.tsx +636 -0
- package/src/components/skeleton.tsx +14 -0
- package/src/components/slider.tsx +24 -0
- package/src/components/sonner.tsx +34 -0
- package/src/components/spinner.tsx +37 -0
- package/src/components/stacked-list.tsx +64 -0
- package/src/components/stat-card.tsx +54 -0
- package/src/components/steps.tsx +179 -0
- package/src/components/switch.tsx +33 -0
- package/src/components/table.tsx +101 -0
- package/src/components/tabs.tsx +55 -0
- package/src/components/textarea.tsx +21 -0
- package/src/components/toggle-group.tsx +49 -0
- package/src/components/toggle.tsx +66 -0
- package/src/components/tooltip.tsx +43 -0
- package/src/icons.ts +158 -0
- package/src/index.ts +84 -0
- package/src/lib/utils.ts +6 -0
- package/src/tokens/tokens.ts +194 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { NavigationMenu } from "radix-ui";
|
|
5
|
+
import { ChevronDown } from "lucide-react";
|
|
6
|
+
import { cn } from "../lib/utils";
|
|
7
|
+
|
|
8
|
+
const NavigationMenuRoot = React.forwardRef<
|
|
9
|
+
React.ComponentRef<typeof NavigationMenu.Root>,
|
|
10
|
+
React.ComponentPropsWithoutRef<typeof NavigationMenu.Root>
|
|
11
|
+
>(({ className, children, ...props }, ref) => (
|
|
12
|
+
<NavigationMenu.Root
|
|
13
|
+
ref={ref}
|
|
14
|
+
className={cn("relative z-10 flex max-w-max flex-1 items-center justify-center", className)}
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
{children}
|
|
18
|
+
</NavigationMenu.Root>
|
|
19
|
+
));
|
|
20
|
+
NavigationMenuRoot.displayName = "NavigationMenu";
|
|
21
|
+
|
|
22
|
+
const NavigationMenuList = React.forwardRef<
|
|
23
|
+
React.ComponentRef<typeof NavigationMenu.List>,
|
|
24
|
+
React.ComponentPropsWithoutRef<typeof NavigationMenu.List>
|
|
25
|
+
>(({ className, ...props }, ref) => (
|
|
26
|
+
<NavigationMenu.List
|
|
27
|
+
ref={ref}
|
|
28
|
+
className={cn("group flex flex-1 list-none items-center justify-center gap-1", className)}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
));
|
|
32
|
+
NavigationMenuList.displayName = "NavigationMenuList";
|
|
33
|
+
|
|
34
|
+
const NavigationMenuItem = NavigationMenu.Item;
|
|
35
|
+
|
|
36
|
+
const NavigationMenuTrigger = React.forwardRef<
|
|
37
|
+
React.ComponentRef<typeof NavigationMenu.Trigger>,
|
|
38
|
+
React.ComponentPropsWithoutRef<typeof NavigationMenu.Trigger>
|
|
39
|
+
>(({ className, children, ...props }, ref) => (
|
|
40
|
+
<NavigationMenu.Trigger
|
|
41
|
+
ref={ref}
|
|
42
|
+
className={cn(
|
|
43
|
+
"group inline-flex h-9 w-max items-center justify-center gap-1 rounded-md px-3 py-2",
|
|
44
|
+
"body font-medium text-taupe-700",
|
|
45
|
+
"hover:bg-taupe-100 hover:text-taupe-900",
|
|
46
|
+
"data-[active]:bg-taupe-100 data-[state=open]:bg-taupe-100",
|
|
47
|
+
"outline-none transition-colors",
|
|
48
|
+
className,
|
|
49
|
+
)}
|
|
50
|
+
{...props}
|
|
51
|
+
>
|
|
52
|
+
{children}
|
|
53
|
+
<ChevronDown
|
|
54
|
+
size={12}
|
|
55
|
+
className="transition-transform duration-200 group-data-[state=open]:rotate-180"
|
|
56
|
+
aria-hidden="true"
|
|
57
|
+
/>
|
|
58
|
+
</NavigationMenu.Trigger>
|
|
59
|
+
));
|
|
60
|
+
NavigationMenuTrigger.displayName = "NavigationMenuTrigger";
|
|
61
|
+
|
|
62
|
+
const NavigationMenuContent = React.forwardRef<
|
|
63
|
+
React.ComponentRef<typeof NavigationMenu.Content>,
|
|
64
|
+
React.ComponentPropsWithoutRef<typeof NavigationMenu.Content>
|
|
65
|
+
>(({ className, ...props }, ref) => (
|
|
66
|
+
<NavigationMenu.Content
|
|
67
|
+
ref={ref}
|
|
68
|
+
className={cn(
|
|
69
|
+
"left-0 top-0 w-full",
|
|
70
|
+
"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out",
|
|
71
|
+
"data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out",
|
|
72
|
+
"data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52",
|
|
73
|
+
"data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52",
|
|
74
|
+
"md:absolute md:w-auto",
|
|
75
|
+
className,
|
|
76
|
+
)}
|
|
77
|
+
{...props}
|
|
78
|
+
/>
|
|
79
|
+
));
|
|
80
|
+
NavigationMenuContent.displayName = "NavigationMenuContent";
|
|
81
|
+
|
|
82
|
+
const NavigationMenuLink = React.forwardRef<
|
|
83
|
+
React.ComponentRef<typeof NavigationMenu.Link>,
|
|
84
|
+
React.ComponentPropsWithoutRef<typeof NavigationMenu.Link>
|
|
85
|
+
>(({ className, ...props }, ref) => (
|
|
86
|
+
<NavigationMenu.Link
|
|
87
|
+
ref={ref}
|
|
88
|
+
className={cn(
|
|
89
|
+
"block select-none rounded-md px-3 py-2 body text-taupe-700",
|
|
90
|
+
"hover:bg-taupe-50 hover:text-taupe-900",
|
|
91
|
+
"outline-none transition-colors",
|
|
92
|
+
className,
|
|
93
|
+
)}
|
|
94
|
+
{...props}
|
|
95
|
+
/>
|
|
96
|
+
));
|
|
97
|
+
NavigationMenuLink.displayName = "NavigationMenuLink";
|
|
98
|
+
|
|
99
|
+
const NavigationMenuViewport = React.forwardRef<
|
|
100
|
+
React.ComponentRef<typeof NavigationMenu.Viewport>,
|
|
101
|
+
React.ComponentPropsWithoutRef<typeof NavigationMenu.Viewport>
|
|
102
|
+
>(({ className, ...props }, ref) => (
|
|
103
|
+
<div className="absolute left-0 top-full flex justify-center">
|
|
104
|
+
<NavigationMenu.Viewport
|
|
105
|
+
ref={ref}
|
|
106
|
+
className={cn(
|
|
107
|
+
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-xl border border-taupe-100 bg-white shadow-elevated",
|
|
108
|
+
"md:w-[var(--radix-navigation-menu-viewport-width)]",
|
|
109
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
|
110
|
+
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90",
|
|
111
|
+
className,
|
|
112
|
+
)}
|
|
113
|
+
{...props}
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
));
|
|
117
|
+
NavigationMenuViewport.displayName = "NavigationMenuViewport";
|
|
118
|
+
|
|
119
|
+
const NavigationMenuIndicator = React.forwardRef<
|
|
120
|
+
React.ComponentRef<typeof NavigationMenu.Indicator>,
|
|
121
|
+
React.ComponentPropsWithoutRef<typeof NavigationMenu.Indicator>
|
|
122
|
+
>(({ className, ...props }, ref) => (
|
|
123
|
+
<NavigationMenu.Indicator
|
|
124
|
+
ref={ref}
|
|
125
|
+
className={cn(
|
|
126
|
+
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden",
|
|
127
|
+
"data-[state=visible]:animate-in data-[state=hidden]:animate-out",
|
|
128
|
+
"data-[state=hidden]:fade-out data-[state=visible]:fade-in",
|
|
129
|
+
className,
|
|
130
|
+
)}
|
|
131
|
+
{...props}
|
|
132
|
+
>
|
|
133
|
+
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-taupe-100 shadow-elevated" />
|
|
134
|
+
</NavigationMenu.Indicator>
|
|
135
|
+
));
|
|
136
|
+
NavigationMenuIndicator.displayName = "NavigationMenuIndicator";
|
|
137
|
+
|
|
138
|
+
function navigationMenuTriggerStyle() {
|
|
139
|
+
return cn(
|
|
140
|
+
"group inline-flex h-9 w-max items-center justify-center gap-1 rounded-md px-3 py-2",
|
|
141
|
+
"body font-medium text-taupe-700",
|
|
142
|
+
"hover:bg-taupe-100 hover:text-taupe-900",
|
|
143
|
+
"data-[active]:bg-taupe-100 data-[state=open]:bg-taupe-100",
|
|
144
|
+
"outline-none transition-colors",
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export {
|
|
149
|
+
NavigationMenuRoot as NavigationMenu,
|
|
150
|
+
NavigationMenuList,
|
|
151
|
+
NavigationMenuItem,
|
|
152
|
+
NavigationMenuTrigger,
|
|
153
|
+
NavigationMenuContent,
|
|
154
|
+
NavigationMenuLink,
|
|
155
|
+
NavigationMenuViewport,
|
|
156
|
+
NavigationMenuIndicator,
|
|
157
|
+
navigationMenuTriggerStyle,
|
|
158
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { X, CheckCircle, AlertCircle, Info, AlertTriangle } from "lucide-react";
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
|
|
5
|
+
interface NotificationAction {
|
|
6
|
+
label: string;
|
|
7
|
+
onClick?: () => void;
|
|
8
|
+
variant?: "default" | "ghost";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface NotificationProps {
|
|
12
|
+
title: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
icon?: React.ReactNode;
|
|
15
|
+
variant?: "default" | "success" | "error" | "warning" | "info";
|
|
16
|
+
actions?: NotificationAction[];
|
|
17
|
+
onClose?: () => void;
|
|
18
|
+
className?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const variantConfig = {
|
|
22
|
+
default: { icon: null, iconClass: "text-taupe-400" },
|
|
23
|
+
success: {
|
|
24
|
+
icon: CheckCircle,
|
|
25
|
+
iconClass: "bg-brand-secondary/25 text-taupe-700 rounded-full p-1",
|
|
26
|
+
},
|
|
27
|
+
error: { icon: AlertCircle, iconClass: "text-red-500" },
|
|
28
|
+
warning: {
|
|
29
|
+
icon: AlertTriangle,
|
|
30
|
+
iconClass: "bg-brand-secondary/80 text-taupe-700 rounded-full p-1",
|
|
31
|
+
},
|
|
32
|
+
info: { icon: Info, iconClass: "text-taupe-400" },
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function Notification({
|
|
36
|
+
title,
|
|
37
|
+
description,
|
|
38
|
+
icon,
|
|
39
|
+
variant = "default",
|
|
40
|
+
actions,
|
|
41
|
+
onClose,
|
|
42
|
+
className,
|
|
43
|
+
}: NotificationProps) {
|
|
44
|
+
const config = variantConfig[variant];
|
|
45
|
+
const DefaultIcon = config.icon;
|
|
46
|
+
|
|
47
|
+
const resolvedIcon = icon ?? (DefaultIcon ? <DefaultIcon size={20} /> : null);
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div
|
|
51
|
+
className={cn(
|
|
52
|
+
"pointer-events-auto w-full max-w-sm overflow-hidden rounded-xl border border-taupe-100 bg-white shadow-elevated",
|
|
53
|
+
className,
|
|
54
|
+
)}
|
|
55
|
+
>
|
|
56
|
+
<div className="p-4">
|
|
57
|
+
<div className="flex items-start gap-3">
|
|
58
|
+
{/* Icon */}
|
|
59
|
+
{resolvedIcon && (
|
|
60
|
+
<div className={cn("shrink-0 mt-0.5", config.iconClass)}>{resolvedIcon}</div>
|
|
61
|
+
)}
|
|
62
|
+
|
|
63
|
+
{/* Body */}
|
|
64
|
+
<div className="min-w-0 flex-1">
|
|
65
|
+
<p className="body font-semibold text-taupe-900">{title}</p>
|
|
66
|
+
{description && <p className="mt-0.5 body text-taupe-500">{description}</p>}
|
|
67
|
+
|
|
68
|
+
{/* Actions */}
|
|
69
|
+
{actions && actions.length > 0 && (
|
|
70
|
+
<div className="mt-3 flex gap-3">
|
|
71
|
+
{actions.map((action, i) => (
|
|
72
|
+
<button
|
|
73
|
+
key={i}
|
|
74
|
+
type="button"
|
|
75
|
+
onClick={action.onClick}
|
|
76
|
+
className={cn(
|
|
77
|
+
"body font-semibold transition-colors",
|
|
78
|
+
action.variant === "ghost"
|
|
79
|
+
? "text-taupe-400 hover:text-taupe-700"
|
|
80
|
+
: "text-taupe-900 hover:text-taupe-600",
|
|
81
|
+
)}
|
|
82
|
+
>
|
|
83
|
+
{action.label}
|
|
84
|
+
</button>
|
|
85
|
+
))}
|
|
86
|
+
</div>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
{/* Close */}
|
|
91
|
+
{onClose && (
|
|
92
|
+
<div className="shrink-0">
|
|
93
|
+
<button
|
|
94
|
+
type="button"
|
|
95
|
+
onClick={onClose}
|
|
96
|
+
className="rounded-md text-taupe-400 hover:text-taupe-600 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-taupe-400/40"
|
|
97
|
+
>
|
|
98
|
+
<span className="sr-only">Close</span>
|
|
99
|
+
<X size={16} />
|
|
100
|
+
</button>
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export { Notification };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { ChevronRight } from "lucide-react";
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
import { Button } from "./button";
|
|
5
|
+
import { Badge } from "./badge";
|
|
6
|
+
|
|
7
|
+
interface BreadcrumbItem {
|
|
8
|
+
label: string;
|
|
9
|
+
href?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface PageHeadingAction {
|
|
13
|
+
label: string;
|
|
14
|
+
href?: string;
|
|
15
|
+
onClick?: () => void;
|
|
16
|
+
variant?: "default" | "outline" | "ghost";
|
|
17
|
+
primary?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface PageHeadingMeta {
|
|
21
|
+
icon?: React.ReactNode;
|
|
22
|
+
text: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface PageHeadingProps {
|
|
26
|
+
title: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
breadcrumbs?: BreadcrumbItem[];
|
|
29
|
+
actions?: PageHeadingAction[];
|
|
30
|
+
meta?: PageHeadingMeta[];
|
|
31
|
+
badge?: string | { label: string; variant?: "default" | "secondary" | "outline" | "destructive" };
|
|
32
|
+
className?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function PageHeading({
|
|
36
|
+
title,
|
|
37
|
+
description,
|
|
38
|
+
breadcrumbs,
|
|
39
|
+
actions,
|
|
40
|
+
meta,
|
|
41
|
+
badge,
|
|
42
|
+
className,
|
|
43
|
+
}: PageHeadingProps) {
|
|
44
|
+
return (
|
|
45
|
+
<div className={cn("border-b border-taupe-100 pb-6", className)}>
|
|
46
|
+
{/* Breadcrumbs */}
|
|
47
|
+
{breadcrumbs && breadcrumbs.length > 0 && (
|
|
48
|
+
<nav className="mb-3 flex items-center gap-1.5" aria-label="Breadcrumb">
|
|
49
|
+
{breadcrumbs.map((crumb, i) => (
|
|
50
|
+
<React.Fragment key={i}>
|
|
51
|
+
{i > 0 && <ChevronRight size={12} className="text-taupe-300 shrink-0" />}
|
|
52
|
+
{crumb.href ? (
|
|
53
|
+
<a
|
|
54
|
+
href={crumb.href}
|
|
55
|
+
className="caption text-taupe-400 hover:text-taupe-700 transition-colors"
|
|
56
|
+
>
|
|
57
|
+
{crumb.label}
|
|
58
|
+
</a>
|
|
59
|
+
) : (
|
|
60
|
+
<span className="caption font-semibold text-taupe-700">{crumb.label}</span>
|
|
61
|
+
)}
|
|
62
|
+
</React.Fragment>
|
|
63
|
+
))}
|
|
64
|
+
</nav>
|
|
65
|
+
)}
|
|
66
|
+
|
|
67
|
+
{/* Title row */}
|
|
68
|
+
<div className="flex flex-wrap items-start justify-between gap-4">
|
|
69
|
+
<div className="min-w-0 flex-1">
|
|
70
|
+
<div className="flex items-center gap-3">
|
|
71
|
+
<h1 className="display text-taupe-900 truncate">{title}</h1>
|
|
72
|
+
{badge &&
|
|
73
|
+
(typeof badge === "string" ? (
|
|
74
|
+
<Badge variant="secondary">{badge}</Badge>
|
|
75
|
+
) : (
|
|
76
|
+
<Badge variant={badge.variant ?? "secondary"}>{badge.label}</Badge>
|
|
77
|
+
))}
|
|
78
|
+
</div>
|
|
79
|
+
{description && <p className="mt-1.5 body text-taupe-400 max-w-2xl">{description}</p>}
|
|
80
|
+
|
|
81
|
+
{/* Meta */}
|
|
82
|
+
{meta && meta.length > 0 && (
|
|
83
|
+
<div className="mt-3 flex flex-wrap items-center gap-4">
|
|
84
|
+
{meta.map((m, i) => (
|
|
85
|
+
<div key={i} className="flex items-center gap-1.5 caption text-taupe-400">
|
|
86
|
+
{m.icon && <span className="text-taupe-400">{m.icon}</span>}
|
|
87
|
+
<span>{m.text}</span>
|
|
88
|
+
</div>
|
|
89
|
+
))}
|
|
90
|
+
</div>
|
|
91
|
+
)}
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
{/* Actions */}
|
|
95
|
+
{actions && actions.length > 0 && (
|
|
96
|
+
<div className="flex shrink-0 items-center gap-2">
|
|
97
|
+
{actions.map((action, i) =>
|
|
98
|
+
action.href ? (
|
|
99
|
+
<Button
|
|
100
|
+
key={i}
|
|
101
|
+
size="sm"
|
|
102
|
+
variant={action.primary ? "default" : (action.variant ?? "outline")}
|
|
103
|
+
asChild
|
|
104
|
+
>
|
|
105
|
+
<a href={action.href}>{action.label}</a>
|
|
106
|
+
</Button>
|
|
107
|
+
) : (
|
|
108
|
+
<Button
|
|
109
|
+
key={i}
|
|
110
|
+
size="sm"
|
|
111
|
+
variant={action.primary ? "default" : (action.variant ?? "outline")}
|
|
112
|
+
onClick={action.onClick}
|
|
113
|
+
>
|
|
114
|
+
{action.label}
|
|
115
|
+
</Button>
|
|
116
|
+
),
|
|
117
|
+
)}
|
|
118
|
+
</div>
|
|
119
|
+
)}
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export { PageHeading };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
import { buttonVariants } from "./button";
|
|
5
|
+
|
|
6
|
+
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
|
7
|
+
<nav
|
|
8
|
+
role="navigation"
|
|
9
|
+
aria-label="pagination"
|
|
10
|
+
className={cn("mx-auto flex w-full justify-center", className)}
|
|
11
|
+
{...props}
|
|
12
|
+
/>
|
|
13
|
+
);
|
|
14
|
+
Pagination.displayName = "Pagination";
|
|
15
|
+
|
|
16
|
+
const PaginationContent = React.forwardRef<HTMLUListElement, React.ComponentProps<"ul">>(
|
|
17
|
+
({ className, ...props }, ref) => (
|
|
18
|
+
<ul ref={ref} className={cn("flex flex-row items-center gap-1", className)} {...props} />
|
|
19
|
+
),
|
|
20
|
+
);
|
|
21
|
+
PaginationContent.displayName = "PaginationContent";
|
|
22
|
+
|
|
23
|
+
const PaginationItem = React.forwardRef<HTMLLIElement, React.ComponentProps<"li">>(
|
|
24
|
+
({ className, ...props }, ref) => <li ref={ref} className={cn("", className)} {...props} />,
|
|
25
|
+
);
|
|
26
|
+
PaginationItem.displayName = "PaginationItem";
|
|
27
|
+
|
|
28
|
+
type PaginationLinkProps = React.ComponentProps<"a"> & { isActive?: boolean };
|
|
29
|
+
|
|
30
|
+
const PaginationLink = ({ className, isActive, ...props }: PaginationLinkProps) => (
|
|
31
|
+
<a
|
|
32
|
+
aria-current={isActive ? "page" : undefined}
|
|
33
|
+
className={cn(
|
|
34
|
+
buttonVariants({ variant: isActive ? "default" : "ghost", size: "icon" }),
|
|
35
|
+
"size-8 rounded-md body",
|
|
36
|
+
className,
|
|
37
|
+
)}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
PaginationLink.displayName = "PaginationLink";
|
|
42
|
+
|
|
43
|
+
const PaginationPrevious = ({
|
|
44
|
+
className,
|
|
45
|
+
...props
|
|
46
|
+
}: React.ComponentProps<typeof PaginationLink>) => (
|
|
47
|
+
<PaginationLink
|
|
48
|
+
aria-label="Go to previous page"
|
|
49
|
+
className={cn("gap-1 px-3 w-auto", className)}
|
|
50
|
+
{...props}
|
|
51
|
+
>
|
|
52
|
+
<ChevronLeft size={14} />
|
|
53
|
+
<span className="body">Prev</span>
|
|
54
|
+
</PaginationLink>
|
|
55
|
+
);
|
|
56
|
+
PaginationPrevious.displayName = "PaginationPrevious";
|
|
57
|
+
|
|
58
|
+
const PaginationNext = ({ className, ...props }: React.ComponentProps<typeof PaginationLink>) => (
|
|
59
|
+
<PaginationLink
|
|
60
|
+
aria-label="Go to next page"
|
|
61
|
+
className={cn("gap-1 px-3 w-auto", className)}
|
|
62
|
+
{...props}
|
|
63
|
+
>
|
|
64
|
+
<span className="body">Next</span>
|
|
65
|
+
<ChevronRight size={14} />
|
|
66
|
+
</PaginationLink>
|
|
67
|
+
);
|
|
68
|
+
PaginationNext.displayName = "PaginationNext";
|
|
69
|
+
|
|
70
|
+
const PaginationEllipsis = ({ className, ...props }: React.ComponentProps<"span">) => (
|
|
71
|
+
<span
|
|
72
|
+
aria-hidden
|
|
73
|
+
className={cn("flex size-8 items-center justify-center text-taupe-400", className)}
|
|
74
|
+
{...props}
|
|
75
|
+
>
|
|
76
|
+
<MoreHorizontal size={14} />
|
|
77
|
+
</span>
|
|
78
|
+
);
|
|
79
|
+
PaginationEllipsis.displayName = "PaginationEllipsis";
|
|
80
|
+
|
|
81
|
+
export {
|
|
82
|
+
Pagination,
|
|
83
|
+
PaginationContent,
|
|
84
|
+
PaginationItem,
|
|
85
|
+
PaginationLink,
|
|
86
|
+
PaginationPrevious,
|
|
87
|
+
PaginationNext,
|
|
88
|
+
PaginationEllipsis,
|
|
89
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Popover } from "radix-ui";
|
|
5
|
+
import { cn } from "../lib/utils";
|
|
6
|
+
|
|
7
|
+
const PopoverRoot = Popover.Root;
|
|
8
|
+
const PopoverTrigger = Popover.Trigger;
|
|
9
|
+
const PopoverClose = Popover.Close;
|
|
10
|
+
|
|
11
|
+
const PopoverContent = React.forwardRef<
|
|
12
|
+
React.ComponentRef<typeof Popover.Content>,
|
|
13
|
+
React.ComponentPropsWithoutRef<typeof Popover.Content>
|
|
14
|
+
>(({ className, align = "center", sideOffset = 6, ...props }, ref) => (
|
|
15
|
+
<Popover.Portal>
|
|
16
|
+
<Popover.Content
|
|
17
|
+
ref={ref}
|
|
18
|
+
align={align}
|
|
19
|
+
sideOffset={sideOffset}
|
|
20
|
+
className={cn(
|
|
21
|
+
"z-50 w-72 rounded-xl bg-white p-4 shadow-natural outline-none",
|
|
22
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
|
23
|
+
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
24
|
+
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
25
|
+
"data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
|
|
26
|
+
"data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
|
|
27
|
+
className,
|
|
28
|
+
)}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
</Popover.Portal>
|
|
32
|
+
));
|
|
33
|
+
PopoverContent.displayName = "PopoverContent";
|
|
34
|
+
|
|
35
|
+
export { PopoverRoot as Popover, PopoverTrigger, PopoverContent, PopoverClose };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Progress } from "radix-ui";
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
|
|
5
|
+
const ProgressRoot = React.forwardRef<
|
|
6
|
+
React.ComponentRef<typeof Progress.Root>,
|
|
7
|
+
React.ComponentPropsWithoutRef<typeof Progress.Root>
|
|
8
|
+
>(({ className, value, ...props }, ref) => (
|
|
9
|
+
<Progress.Root
|
|
10
|
+
ref={ref}
|
|
11
|
+
className={cn("relative h-2 w-full overflow-hidden rounded-full bg-taupe-100", className)}
|
|
12
|
+
value={value}
|
|
13
|
+
{...props}
|
|
14
|
+
>
|
|
15
|
+
<Progress.Indicator
|
|
16
|
+
className="h-full w-full flex-1 bg-brand-primary transition-all"
|
|
17
|
+
style={{ transform: `translateX(-${100 - (value ?? 0)}%)` }}
|
|
18
|
+
/>
|
|
19
|
+
</Progress.Root>
|
|
20
|
+
));
|
|
21
|
+
ProgressRoot.displayName = "Progress";
|
|
22
|
+
|
|
23
|
+
export { ProgressRoot as Progress };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
|
|
6
|
+
interface PropertyRowProps extends React.ComponentProps<"div"> {
|
|
7
|
+
/** Optional label rendered on the left. Pass a string or any React node (icon + label, etc.). */
|
|
8
|
+
label?: React.ReactNode;
|
|
9
|
+
/** Width of the label column. Default: 96px. */
|
|
10
|
+
labelWidth?: number | string;
|
|
11
|
+
/** Aligns label vertically with controls. Default: "center". */
|
|
12
|
+
labelAlign?: "start" | "center";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Horizontal property row used inside designer inspector panels:
|
|
17
|
+
* left-aligned label, right-aligned controls, single visual row.
|
|
18
|
+
* Different from <Field>, which is a vertical form field with hint/error.
|
|
19
|
+
*
|
|
20
|
+
* <PropertyRow label="Opacity">
|
|
21
|
+
* <Slider />
|
|
22
|
+
* <InputNumber />
|
|
23
|
+
* </PropertyRow>
|
|
24
|
+
*/
|
|
25
|
+
function PropertyRow({
|
|
26
|
+
label,
|
|
27
|
+
labelWidth = 96,
|
|
28
|
+
labelAlign = "center",
|
|
29
|
+
className,
|
|
30
|
+
children,
|
|
31
|
+
...rest
|
|
32
|
+
}: PropertyRowProps) {
|
|
33
|
+
return (
|
|
34
|
+
<div
|
|
35
|
+
className={cn(
|
|
36
|
+
"flex w-full gap-3 py-1",
|
|
37
|
+
labelAlign === "center" ? "items-center" : "items-start",
|
|
38
|
+
className,
|
|
39
|
+
)}
|
|
40
|
+
{...rest}
|
|
41
|
+
>
|
|
42
|
+
{label !== undefined && (
|
|
43
|
+
<div
|
|
44
|
+
className="caption text-taupe-500 shrink-0 select-none"
|
|
45
|
+
style={{ width: typeof labelWidth === "number" ? `${labelWidth}px` : labelWidth }}
|
|
46
|
+
>
|
|
47
|
+
{label}
|
|
48
|
+
</div>
|
|
49
|
+
)}
|
|
50
|
+
<div className="flex flex-1 items-center gap-2 min-w-0">{children}</div>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { PropertyRow };
|
|
56
|
+
export type { PropertyRowProps };
|