@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/select.tsx
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
5
|
+
import { Check, ChevronDown, ChevronUp } from "lucide-react";
|
|
6
|
+
import { cn } from "./utils";
|
|
7
|
+
|
|
8
|
+
const Select = SelectPrimitive.Root;
|
|
9
|
+
const SelectGroup = SelectPrimitive.Group;
|
|
10
|
+
const SelectValue = SelectPrimitive.Value;
|
|
11
|
+
|
|
12
|
+
const SelectTrigger = React.forwardRef<
|
|
13
|
+
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
|
14
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
|
15
|
+
>(({ className, children, ...props }, ref) => (
|
|
16
|
+
<SelectPrimitive.Trigger
|
|
17
|
+
ref={ref}
|
|
18
|
+
className={cn(
|
|
19
|
+
"flex h-11 min-h-11 w-full items-center justify-between gap-2.5 rounded-lg border border-border bg-card px-4 py-2 text-[15px] text-foreground shadow-sm outline-none",
|
|
20
|
+
"ring-ring/50 focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-50",
|
|
21
|
+
"[&>span]:line-clamp-1",
|
|
22
|
+
className
|
|
23
|
+
)}
|
|
24
|
+
{...props}
|
|
25
|
+
>
|
|
26
|
+
{children}
|
|
27
|
+
<SelectPrimitive.Icon asChild>
|
|
28
|
+
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground opacity-70" />
|
|
29
|
+
</SelectPrimitive.Icon>
|
|
30
|
+
</SelectPrimitive.Trigger>
|
|
31
|
+
));
|
|
32
|
+
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
|
33
|
+
|
|
34
|
+
const SelectScrollUpButton = React.forwardRef<
|
|
35
|
+
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
|
36
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
|
37
|
+
>(({ className, ...props }, ref) => (
|
|
38
|
+
<SelectPrimitive.ScrollUpButton
|
|
39
|
+
ref={ref}
|
|
40
|
+
className={cn("flex cursor-default items-center justify-center py-1 text-muted-foreground", className)}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
<ChevronUp className="h-4 w-4" />
|
|
44
|
+
</SelectPrimitive.ScrollUpButton>
|
|
45
|
+
));
|
|
46
|
+
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
|
|
47
|
+
|
|
48
|
+
const SelectScrollDownButton = React.forwardRef<
|
|
49
|
+
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
|
50
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
|
51
|
+
>(({ className, ...props }, ref) => (
|
|
52
|
+
<SelectPrimitive.ScrollDownButton
|
|
53
|
+
ref={ref}
|
|
54
|
+
className={cn("flex cursor-default items-center justify-center py-1 text-muted-foreground", className)}
|
|
55
|
+
{...props}
|
|
56
|
+
>
|
|
57
|
+
<ChevronDown className="h-4 w-4" />
|
|
58
|
+
</SelectPrimitive.ScrollDownButton>
|
|
59
|
+
));
|
|
60
|
+
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
|
|
61
|
+
|
|
62
|
+
const SelectContent = React.forwardRef<
|
|
63
|
+
React.ElementRef<typeof SelectPrimitive.Content>,
|
|
64
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
|
65
|
+
>(({ className, children, position = "popper", ...props }, ref) => (
|
|
66
|
+
<SelectPrimitive.Portal>
|
|
67
|
+
<SelectPrimitive.Content
|
|
68
|
+
ref={ref}
|
|
69
|
+
className={cn(
|
|
70
|
+
"relative z-[120] max-h-[min(24rem,var(--radix-select-content-available-height))] min-w-[var(--radix-select-trigger-width)] overflow-hidden rounded-xl border border-border bg-popover text-popover-foreground shadow-lg",
|
|
71
|
+
"data-[state=open]:opacity-100 data-[state=closed]:opacity-0 transition-opacity duration-150",
|
|
72
|
+
position === "popper" &&
|
|
73
|
+
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
|
74
|
+
className
|
|
75
|
+
)}
|
|
76
|
+
position={position}
|
|
77
|
+
{...props}
|
|
78
|
+
>
|
|
79
|
+
<SelectScrollUpButton />
|
|
80
|
+
<SelectPrimitive.Viewport
|
|
81
|
+
className={cn(
|
|
82
|
+
"p-1 max-h-72 overflow-y-auto",
|
|
83
|
+
position === "popper" && "w-full min-w-[var(--radix-select-trigger-width)]"
|
|
84
|
+
)}
|
|
85
|
+
>
|
|
86
|
+
{children}
|
|
87
|
+
</SelectPrimitive.Viewport>
|
|
88
|
+
<SelectScrollDownButton />
|
|
89
|
+
</SelectPrimitive.Content>
|
|
90
|
+
</SelectPrimitive.Portal>
|
|
91
|
+
));
|
|
92
|
+
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
|
93
|
+
|
|
94
|
+
const SelectLabel = React.forwardRef<
|
|
95
|
+
React.ElementRef<typeof SelectPrimitive.Label>,
|
|
96
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
|
97
|
+
>(({ className, ...props }, ref) => (
|
|
98
|
+
<SelectPrimitive.Label ref={ref} className={cn("px-2 py-1.5 text-xs font-medium text-muted-foreground", className)} {...props} />
|
|
99
|
+
));
|
|
100
|
+
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
|
101
|
+
|
|
102
|
+
const SelectItem = React.forwardRef<
|
|
103
|
+
React.ElementRef<typeof SelectPrimitive.Item>,
|
|
104
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
|
105
|
+
>(({ className, children, ...props }, ref) => (
|
|
106
|
+
<SelectPrimitive.Item
|
|
107
|
+
ref={ref}
|
|
108
|
+
className={cn(
|
|
109
|
+
"relative flex w-full cursor-default select-none items-center rounded-lg py-2 pl-2 pr-8 text-sm outline-none",
|
|
110
|
+
"focus:bg-muted focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
111
|
+
className
|
|
112
|
+
)}
|
|
113
|
+
{...props}
|
|
114
|
+
>
|
|
115
|
+
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
116
|
+
<SelectPrimitive.ItemIndicator>
|
|
117
|
+
<Check className="h-4 w-4 text-primary" />
|
|
118
|
+
</SelectPrimitive.ItemIndicator>
|
|
119
|
+
</span>
|
|
120
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
121
|
+
</SelectPrimitive.Item>
|
|
122
|
+
));
|
|
123
|
+
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
|
124
|
+
|
|
125
|
+
const SelectSeparator = React.forwardRef<
|
|
126
|
+
React.ElementRef<typeof SelectPrimitive.Separator>,
|
|
127
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
|
128
|
+
>(({ className, ...props }, ref) => (
|
|
129
|
+
<SelectPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-border", className)} {...props} />
|
|
130
|
+
));
|
|
131
|
+
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
|
132
|
+
|
|
133
|
+
export {
|
|
134
|
+
Select,
|
|
135
|
+
SelectGroup,
|
|
136
|
+
SelectValue,
|
|
137
|
+
SelectTrigger,
|
|
138
|
+
SelectContent,
|
|
139
|
+
SelectLabel,
|
|
140
|
+
SelectItem,
|
|
141
|
+
SelectSeparator,
|
|
142
|
+
SelectScrollUpButton,
|
|
143
|
+
SelectScrollDownButton,
|
|
144
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
|
5
|
+
|
|
6
|
+
import { cn } from "./utils";
|
|
7
|
+
|
|
8
|
+
const Separator = React.forwardRef<
|
|
9
|
+
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
|
10
|
+
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
|
11
|
+
>(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
|
|
12
|
+
<SeparatorPrimitive.Root
|
|
13
|
+
ref={ref}
|
|
14
|
+
decorative={decorative}
|
|
15
|
+
orientation={orientation}
|
|
16
|
+
className={cn(
|
|
17
|
+
"shrink-0 bg-border",
|
|
18
|
+
orientation === "horizontal" ? "h-px w-full" : "h-full w-px",
|
|
19
|
+
className
|
|
20
|
+
)}
|
|
21
|
+
{...props}
|
|
22
|
+
/>
|
|
23
|
+
));
|
|
24
|
+
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
|
25
|
+
|
|
26
|
+
export { Separator };
|
package/src/side-nav.tsx
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "./utils";
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Types
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
export interface SideNavProps extends React.ComponentPropsWithoutRef<"aside"> {
|
|
11
|
+
/** "collapsed" renders icon-only at 60px; "expanded" renders full at 240px (default) */
|
|
12
|
+
width?: "collapsed" | "expanded";
|
|
13
|
+
/** Controlled collapsed state */
|
|
14
|
+
isCollapsed?: boolean;
|
|
15
|
+
/** Called when the sidebar requests a collapse/expand */
|
|
16
|
+
onCollapse?: (collapsed: boolean) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SideNavItemProps extends React.ComponentPropsWithoutRef<"a"> {
|
|
20
|
+
/** Icon element — required, rendered at size-4 shrink-0 */
|
|
21
|
+
icon: React.ReactNode;
|
|
22
|
+
/** Visible label — hidden when sidebar is collapsed */
|
|
23
|
+
label: string;
|
|
24
|
+
/** Highlights the item as the current route */
|
|
25
|
+
isActive?: boolean;
|
|
26
|
+
/** Navigable href — renders an <a> when provided */
|
|
27
|
+
href?: string;
|
|
28
|
+
/** Click handler */
|
|
29
|
+
onClick?: React.MouseEventHandler<HTMLElement>;
|
|
30
|
+
/** Optional badge slot rendered after the label */
|
|
31
|
+
badge?: React.ReactNode;
|
|
32
|
+
/** Injected by SideNavSection — consumers should not pass this */
|
|
33
|
+
_collapsed?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Context
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
interface SideNavContextValue {
|
|
41
|
+
isCollapsed: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const SideNavContext = React.createContext<SideNavContextValue>({
|
|
45
|
+
isCollapsed: false,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
function useSideNavContext() {
|
|
49
|
+
return React.useContext(SideNavContext);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// SideNav
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
const SideNav = React.forwardRef<HTMLElement, SideNavProps>(
|
|
57
|
+
(
|
|
58
|
+
{
|
|
59
|
+
className,
|
|
60
|
+
width = "expanded",
|
|
61
|
+
isCollapsed: isCollapsedProp,
|
|
62
|
+
onCollapse,
|
|
63
|
+
children,
|
|
64
|
+
...props
|
|
65
|
+
},
|
|
66
|
+
ref
|
|
67
|
+
) => {
|
|
68
|
+
const collapsed =
|
|
69
|
+
isCollapsedProp !== undefined ? isCollapsedProp : width === "collapsed";
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<SideNavContext.Provider value={{ isCollapsed: collapsed }}>
|
|
73
|
+
<aside
|
|
74
|
+
ref={ref}
|
|
75
|
+
data-slot="side-nav"
|
|
76
|
+
data-collapsed={collapsed}
|
|
77
|
+
className={cn(
|
|
78
|
+
"flex flex-col bg-sidebar border-r border-sidebar-border",
|
|
79
|
+
"transition-all duration-pg-normal ease-pg-standard",
|
|
80
|
+
collapsed ? "w-[60px]" : "w-[240px]",
|
|
81
|
+
className
|
|
82
|
+
)}
|
|
83
|
+
{...props}
|
|
84
|
+
>
|
|
85
|
+
{children}
|
|
86
|
+
</aside>
|
|
87
|
+
</SideNavContext.Provider>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
SideNav.displayName = "SideNav";
|
|
92
|
+
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// SideNavHeader
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
const SideNavHeader = React.forwardRef<
|
|
98
|
+
HTMLDivElement,
|
|
99
|
+
React.ComponentPropsWithoutRef<"div">
|
|
100
|
+
>(({ className, ...props }, ref) => (
|
|
101
|
+
<div
|
|
102
|
+
ref={ref}
|
|
103
|
+
data-slot="side-nav-header"
|
|
104
|
+
className={cn(
|
|
105
|
+
"px-4 py-4 border-b border-sidebar-border shrink-0",
|
|
106
|
+
className
|
|
107
|
+
)}
|
|
108
|
+
{...props}
|
|
109
|
+
/>
|
|
110
|
+
));
|
|
111
|
+
SideNavHeader.displayName = "SideNavHeader";
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// SideNavSection
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
interface SideNavSectionProps extends React.ComponentPropsWithoutRef<"div"> {
|
|
118
|
+
/** Optional section label — hidden when sidebar is collapsed */
|
|
119
|
+
label?: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const SideNavSection = React.forwardRef<HTMLDivElement, SideNavSectionProps>(
|
|
123
|
+
({ className, label, children, ...props }, ref) => {
|
|
124
|
+
const { isCollapsed } = useSideNavContext();
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<div
|
|
128
|
+
ref={ref}
|
|
129
|
+
data-slot="side-nav-section"
|
|
130
|
+
className={cn("px-3 py-2", className)}
|
|
131
|
+
{...props}
|
|
132
|
+
>
|
|
133
|
+
{label && !isCollapsed && (
|
|
134
|
+
<p className="mb-1 px-3 text-xs font-medium text-muted-foreground uppercase tracking-wider select-none">
|
|
135
|
+
{label}
|
|
136
|
+
</p>
|
|
137
|
+
)}
|
|
138
|
+
{children}
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
SideNavSection.displayName = "SideNavSection";
|
|
144
|
+
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// SideNavItem
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
const SideNavItem = React.forwardRef<HTMLAnchorElement, SideNavItemProps>(
|
|
150
|
+
(
|
|
151
|
+
{
|
|
152
|
+
className,
|
|
153
|
+
icon,
|
|
154
|
+
label,
|
|
155
|
+
isActive = false,
|
|
156
|
+
href,
|
|
157
|
+
onClick,
|
|
158
|
+
badge,
|
|
159
|
+
_collapsed,
|
|
160
|
+
children,
|
|
161
|
+
...props
|
|
162
|
+
},
|
|
163
|
+
ref
|
|
164
|
+
) => {
|
|
165
|
+
const { isCollapsed } = useSideNavContext();
|
|
166
|
+
const collapsed = _collapsed !== undefined ? _collapsed : isCollapsed;
|
|
167
|
+
|
|
168
|
+
const baseClassName = cn(
|
|
169
|
+
"flex items-center gap-3 px-3 py-2 rounded-lg text-sm cursor-pointer",
|
|
170
|
+
"transition-colors duration-pg-fast ease-pg-standard",
|
|
171
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35",
|
|
172
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
173
|
+
isActive
|
|
174
|
+
? "bg-primary/10 text-primary font-medium"
|
|
175
|
+
: "text-sidebar-foreground hover:bg-muted/50",
|
|
176
|
+
collapsed && "justify-center px-0",
|
|
177
|
+
className
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const content = (
|
|
181
|
+
<>
|
|
182
|
+
<span className="size-4 shrink-0 flex items-center justify-center">
|
|
183
|
+
{icon}
|
|
184
|
+
</span>
|
|
185
|
+
{!collapsed && (
|
|
186
|
+
<>
|
|
187
|
+
<span className="flex-1 truncate">{label}</span>
|
|
188
|
+
{badge && <span className="shrink-0">{badge}</span>}
|
|
189
|
+
</>
|
|
190
|
+
)}
|
|
191
|
+
</>
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
if (href) {
|
|
195
|
+
return (
|
|
196
|
+
<a
|
|
197
|
+
ref={ref}
|
|
198
|
+
href={href}
|
|
199
|
+
title={collapsed ? label : undefined}
|
|
200
|
+
aria-current={isActive ? "page" : undefined}
|
|
201
|
+
className={baseClassName}
|
|
202
|
+
onClick={onClick as React.MouseEventHandler<HTMLAnchorElement>}
|
|
203
|
+
{...props}
|
|
204
|
+
>
|
|
205
|
+
{content}
|
|
206
|
+
</a>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<a
|
|
212
|
+
ref={ref}
|
|
213
|
+
role="button"
|
|
214
|
+
tabIndex={0}
|
|
215
|
+
title={collapsed ? label : undefined}
|
|
216
|
+
aria-current={isActive ? "page" : undefined}
|
|
217
|
+
className={baseClassName}
|
|
218
|
+
onClick={onClick as React.MouseEventHandler<HTMLAnchorElement>}
|
|
219
|
+
onKeyDown={(e) => {
|
|
220
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
221
|
+
e.preventDefault();
|
|
222
|
+
onClick?.(e as unknown as React.MouseEvent<HTMLElement>);
|
|
223
|
+
}
|
|
224
|
+
}}
|
|
225
|
+
{...props}
|
|
226
|
+
>
|
|
227
|
+
{content}
|
|
228
|
+
</a>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
SideNavItem.displayName = "SideNavItem";
|
|
233
|
+
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
// SideNavFooter
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
|
|
238
|
+
const SideNavFooter = React.forwardRef<
|
|
239
|
+
HTMLDivElement,
|
|
240
|
+
React.ComponentPropsWithoutRef<"div">
|
|
241
|
+
>(({ className, ...props }, ref) => (
|
|
242
|
+
<div
|
|
243
|
+
ref={ref}
|
|
244
|
+
data-slot="side-nav-footer"
|
|
245
|
+
className={cn(
|
|
246
|
+
"mt-auto border-t border-sidebar-border px-3 py-3 shrink-0",
|
|
247
|
+
className
|
|
248
|
+
)}
|
|
249
|
+
{...props}
|
|
250
|
+
/>
|
|
251
|
+
));
|
|
252
|
+
SideNavFooter.displayName = "SideNavFooter";
|
|
253
|
+
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
// Exports
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
|
|
258
|
+
export {
|
|
259
|
+
SideNav,
|
|
260
|
+
SideNavHeader,
|
|
261
|
+
SideNavSection,
|
|
262
|
+
SideNavItem,
|
|
263
|
+
SideNavFooter,
|
|
264
|
+
};
|
package/src/skeleton.tsx
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { cn } from "./utils";
|
|
2
|
+
|
|
3
|
+
interface ShimmerProps {
|
|
4
|
+
className?: string;
|
|
5
|
+
rounded?: "sm" | "md" | "lg" | "full";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function Shimmer({ className, rounded = "md" }: ShimmerProps) {
|
|
9
|
+
const r = { sm: "rounded", md: "rounded-lg", lg: "rounded-xl", full: "rounded-full" }[rounded];
|
|
10
|
+
return <div className={cn("shimmer", r, className)} />;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function StatCardSkeleton() {
|
|
14
|
+
return (
|
|
15
|
+
<div className="bg-card text-card-foreground rounded-xl p-5 flex flex-col gap-3 border border-border shadow-sm">
|
|
16
|
+
<div className="flex items-center justify-between">
|
|
17
|
+
<Shimmer className="h-3 w-28" />
|
|
18
|
+
<Shimmer className="h-10 w-10" rounded="full" />
|
|
19
|
+
</div>
|
|
20
|
+
<Shimmer className="h-9 w-44 mt-1" />
|
|
21
|
+
<Shimmer className="h-3 w-32" />
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function TableRowSkeleton({
|
|
27
|
+
cols = 6,
|
|
28
|
+
comfortable = false,
|
|
29
|
+
density: densityProp,
|
|
30
|
+
snug = false,
|
|
31
|
+
}: {
|
|
32
|
+
cols?: number;
|
|
33
|
+
/** @deprecated prefer `density` */
|
|
34
|
+
comfortable?: boolean;
|
|
35
|
+
density?: "default" | "comfortable" | "compact";
|
|
36
|
+
snug?: boolean;
|
|
37
|
+
}) {
|
|
38
|
+
const density =
|
|
39
|
+
densityProp ??
|
|
40
|
+
(comfortable ? "comfortable" : "default");
|
|
41
|
+
const cellPad =
|
|
42
|
+
density === "comfortable"
|
|
43
|
+
? "px-5 py-4"
|
|
44
|
+
: density === "compact"
|
|
45
|
+
? snug
|
|
46
|
+
? "pl-1.5 pr-2.5 py-2.5"
|
|
47
|
+
: "px-3 py-2.5"
|
|
48
|
+
: "px-4 py-3.5";
|
|
49
|
+
return (
|
|
50
|
+
<tr
|
|
51
|
+
className={cn(
|
|
52
|
+
"border-b border-border/60",
|
|
53
|
+
density === "comfortable" && "min-h-[56px]",
|
|
54
|
+
density === "compact" && "min-h-[44px]"
|
|
55
|
+
)}
|
|
56
|
+
>
|
|
57
|
+
{Array.from({ length: cols }).map((_, i) => (
|
|
58
|
+
<td key={i} className={cellPad}>
|
|
59
|
+
<Shimmer className={cn("h-3.5", i === 0 ? "w-20" : i === cols - 1 ? "w-14" : "w-28")} />
|
|
60
|
+
</td>
|
|
61
|
+
))}
|
|
62
|
+
</tr>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function ChartSkeleton({ height = "h-48" }: { height?: string }) {
|
|
67
|
+
return (
|
|
68
|
+
<div className={cn("flex items-end gap-2 px-2", height)}>
|
|
69
|
+
{[55, 72, 40, 88, 65, 82, 48, 95, 60, 70].map((h, i) => (
|
|
70
|
+
<div key={i} className="flex-1 shimmer rounded-t-md" style={{ height: `${h}%` }} />
|
|
71
|
+
))}
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
package/src/slider.tsx
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as SliderPrimitive from "@radix-ui/react-slider";
|
|
5
|
+
import { cn } from "./utils";
|
|
6
|
+
|
|
7
|
+
export interface SliderProps extends React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> {}
|
|
8
|
+
|
|
9
|
+
const Slider = React.forwardRef<React.ElementRef<typeof SliderPrimitive.Root>, SliderProps>(
|
|
10
|
+
({ className, ...props }, ref) => (
|
|
11
|
+
<SliderPrimitive.Root
|
|
12
|
+
ref={ref}
|
|
13
|
+
className={cn("relative flex w-full touch-none select-none items-center", className)}
|
|
14
|
+
{...props}
|
|
15
|
+
>
|
|
16
|
+
<SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-muted">
|
|
17
|
+
<SliderPrimitive.Range className="absolute h-full bg-primary" />
|
|
18
|
+
</SliderPrimitive.Track>
|
|
19
|
+
<SliderPrimitive.Thumb className="block h-[18px] w-[18px] rounded-full border-2 border-primary bg-card shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35 disabled:pointer-events-none disabled:opacity-50" />
|
|
20
|
+
</SliderPrimitive.Root>
|
|
21
|
+
)
|
|
22
|
+
);
|
|
23
|
+
Slider.displayName = SliderPrimitive.Root.displayName;
|
|
24
|
+
|
|
25
|
+
export { Slider };
|
package/src/sonner.tsx
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useTheme } from "next-themes";
|
|
4
|
+
import { Toaster as Sonner, type ToasterProps } from "sonner";
|
|
5
|
+
import { useEffect, useState } from "react";
|
|
6
|
+
|
|
7
|
+
/** Drop-in toast host; pair with `toast` from `sonner`. Resolves theme via next-themes when mounted. */
|
|
8
|
+
export function Toaster({ theme, ...props }: ToasterProps) {
|
|
9
|
+
const { resolvedTheme } = useTheme();
|
|
10
|
+
const [mounted, setMounted] = useState(false);
|
|
11
|
+
useEffect(() => setMounted(true), []);
|
|
12
|
+
|
|
13
|
+
const resolved =
|
|
14
|
+
theme ?? (mounted && resolvedTheme === "dark" ? "dark" : "light");
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<Sonner
|
|
18
|
+
theme={resolved}
|
|
19
|
+
position="bottom-right"
|
|
20
|
+
toastOptions={{
|
|
21
|
+
classNames: {
|
|
22
|
+
toast:
|
|
23
|
+
"bg-[var(--popover)] text-[var(--popover-foreground)] border-[var(--border)] shadow-lg rounded-[10px] text-[13px]",
|
|
24
|
+
description: "text-[var(--muted-foreground)]",
|
|
25
|
+
},
|
|
26
|
+
}}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { toast } from "sonner";
|
package/src/spinner.tsx
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Loader2 } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "./utils";
|
|
7
|
+
|
|
8
|
+
const sizeMap = {
|
|
9
|
+
xs: 12,
|
|
10
|
+
sm: 16,
|
|
11
|
+
md: 20,
|
|
12
|
+
lg: 28,
|
|
13
|
+
xl: 36,
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
const colorMap = {
|
|
17
|
+
primary: "text-primary",
|
|
18
|
+
muted: "text-muted-foreground",
|
|
19
|
+
white: "text-white",
|
|
20
|
+
inherit: "",
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
export interface SpinnerProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|
24
|
+
size?: keyof typeof sizeMap;
|
|
25
|
+
color?: keyof typeof colorMap;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const Spinner = React.forwardRef<HTMLSpanElement, SpinnerProps>(
|
|
29
|
+
({ className, size = "md", color = "primary", ...props }, ref) => {
|
|
30
|
+
const px = sizeMap[size];
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<span
|
|
34
|
+
ref={ref}
|
|
35
|
+
data-slot="spinner"
|
|
36
|
+
role="status"
|
|
37
|
+
aria-label="Loading"
|
|
38
|
+
className={cn("inline-flex items-center justify-center", colorMap[color], className)}
|
|
39
|
+
{...props}
|
|
40
|
+
>
|
|
41
|
+
<Loader2
|
|
42
|
+
width={px}
|
|
43
|
+
height={px}
|
|
44
|
+
className="animate-spin"
|
|
45
|
+
aria-hidden="true"
|
|
46
|
+
/>
|
|
47
|
+
<span className="sr-only">Loading...</span>
|
|
48
|
+
</span>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
Spinner.displayName = "Spinner";
|
|
53
|
+
|
|
54
|
+
export { Spinner };
|