@etherealui/react 0.0.0-stage → 0.0.2
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 +62 -2
- package/dist/index.d.mts +583 -0
- package/dist/index.d.ts +583 -0
- package/dist/index.js +5189 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +5189 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +83 -3
- package/src/blocks/app-shell.tsx +441 -0
- package/src/blocks/billing-card.tsx +2 -0
- package/src/blocks/data-table.tsx +479 -0
- package/src/blocks/filter-bar.tsx +134 -0
- package/src/blocks/header-navbar.tsx +7 -0
- package/src/blocks/metric-grid.tsx +5 -0
- package/src/blocks/profile-form.tsx +2 -0
- package/src/blocks/security-settings.tsx +2 -0
- package/src/blocks/settings-blocks.tsx +406 -0
- package/src/blocks/sidebar.tsx +6 -0
- package/src/blocks/stat-card.tsx +7 -0
- package/src/blocks/stat-cards.tsx +212 -0
- package/src/blocks/team-members.tsx +5 -0
- package/src/index.ts +114 -0
- package/src/primitives/accordion.tsx +61 -0
- package/src/primitives/announcement.tsx +33 -0
- package/src/primitives/avatar.tsx +156 -0
- package/src/primitives/badge.tsx +108 -0
- package/src/primitives/breadcrumb.tsx +45 -0
- package/src/primitives/button-group.tsx +79 -0
- package/src/primitives/button.tsx +165 -0
- package/src/primitives/carousel.tsx +84 -0
- package/src/primitives/chart.tsx +831 -0
- package/src/primitives/checkbox.tsx +26 -0
- package/src/primitives/chip.tsx +37 -0
- package/src/primitives/close-button.tsx +29 -0
- package/src/primitives/color-picker.tsx +62 -0
- package/src/primitives/combobox.tsx +123 -0
- package/src/primitives/command-palette.tsx +216 -0
- package/src/primitives/context-menu.tsx +84 -0
- package/src/primitives/date-calendar.tsx +76 -0
- package/src/primitives/date-picker.tsx +400 -0
- package/src/primitives/dialog.tsx +279 -0
- package/src/primitives/divider.tsx +31 -0
- package/src/primitives/drawer.tsx +95 -0
- package/src/primitives/dropdown-menu.tsx +164 -0
- package/src/primitives/file-upload.tsx +123 -0
- package/src/primitives/icon-button.tsx +23 -0
- package/src/primitives/input.tsx +124 -0
- package/src/primitives/modal.tsx +26 -0
- package/src/primitives/pagination.tsx +58 -0
- package/src/primitives/popover.tsx +28 -0
- package/src/primitives/progress.tsx +145 -0
- package/src/primitives/radio-group.tsx +63 -0
- package/src/primitives/select.tsx +207 -0
- package/src/primitives/skeleton.tsx +61 -0
- package/src/primitives/slider.tsx +44 -0
- package/src/primitives/switch.tsx +146 -0
- package/src/primitives/table.tsx +47 -0
- package/src/primitives/tabs.tsx +86 -0
- package/src/primitives/textarea.tsx +40 -0
- package/src/primitives/toast.tsx +71 -0
- package/src/primitives/tooltip.tsx +58 -0
- package/src/styles/globals.css +49 -0
- package/src/templates/control-tower.tsx +41 -0
- package/src/templates/executive-analytics.tsx +272 -0
- package/src/templates/workspace-settings.tsx +179 -0
- package/src/utils/cn.ts +7 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "../utils/cn"
|
|
3
|
+
|
|
4
|
+
export interface ProgressProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
value: number
|
|
6
|
+
max?: number
|
|
7
|
+
variant?: "blue" | "indigo" | "emerald" | "amber" | "rose" | "white"
|
|
8
|
+
size?: "sm" | "default" | "lg"
|
|
9
|
+
showValue?: boolean
|
|
10
|
+
label?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const barColorMap: Record<NonNullable<ProgressProps["variant"]>, string> = {
|
|
14
|
+
blue: "bg-blue-600 dark:bg-blue-500",
|
|
15
|
+
indigo: "bg-blue-600 dark:bg-blue-500",
|
|
16
|
+
emerald: "bg-emerald-600 dark:bg-emerald-500",
|
|
17
|
+
amber: "bg-amber-500",
|
|
18
|
+
rose: "bg-rose-600 dark:bg-rose-500",
|
|
19
|
+
white: "bg-zinc-900 dark:bg-zinc-100",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
|
|
23
|
+
(
|
|
24
|
+
{
|
|
25
|
+
className,
|
|
26
|
+
value,
|
|
27
|
+
max = 100,
|
|
28
|
+
variant = "blue",
|
|
29
|
+
size = "default",
|
|
30
|
+
showValue = false,
|
|
31
|
+
label,
|
|
32
|
+
...props
|
|
33
|
+
},
|
|
34
|
+
ref
|
|
35
|
+
) => {
|
|
36
|
+
const percentage = Math.min(100, Math.max(0, (value / max) * 100))
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className={cn("w-full space-y-1.5", className)} ref={ref} {...props}>
|
|
40
|
+
{(label || showValue) && (
|
|
41
|
+
<div className="flex items-center justify-between text-xs">
|
|
42
|
+
{label && (
|
|
43
|
+
<span className="text-zinc-700 dark:text-zinc-300 font-medium">
|
|
44
|
+
{label}
|
|
45
|
+
</span>
|
|
46
|
+
)}
|
|
47
|
+
{showValue && (
|
|
48
|
+
<span className="font-mono text-zinc-500 dark:text-zinc-400">
|
|
49
|
+
{Math.round(percentage)}%
|
|
50
|
+
</span>
|
|
51
|
+
)}
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
<div
|
|
55
|
+
role="progressbar"
|
|
56
|
+
aria-valuenow={value}
|
|
57
|
+
aria-valuemin={0}
|
|
58
|
+
aria-valuemax={max}
|
|
59
|
+
className={cn(
|
|
60
|
+
"w-full overflow-hidden rounded-full bg-zinc-200/80 dark:bg-zinc-800",
|
|
61
|
+
size === "sm" && "h-1.5",
|
|
62
|
+
size === "default" && "h-2",
|
|
63
|
+
size === "lg" && "h-3"
|
|
64
|
+
)}
|
|
65
|
+
>
|
|
66
|
+
<div
|
|
67
|
+
className={cn(
|
|
68
|
+
"h-full rounded-full transition-all duration-300",
|
|
69
|
+
barColorMap[variant]
|
|
70
|
+
)}
|
|
71
|
+
style={{ width: `${percentage}%` }}
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
Progress.displayName = "Progress"
|
|
79
|
+
|
|
80
|
+
export interface CircularProgressProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
81
|
+
value: number
|
|
82
|
+
size?: number
|
|
83
|
+
strokeWidth?: number
|
|
84
|
+
variant?: "blue" | "indigo" | "emerald" | "amber" | "rose"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const strokeColorMap: Record<NonNullable<CircularProgressProps["variant"]>, string> = {
|
|
88
|
+
blue: "text-blue-600 dark:text-blue-500",
|
|
89
|
+
indigo: "text-blue-600 dark:text-blue-500",
|
|
90
|
+
emerald: "text-emerald-600 dark:text-emerald-500",
|
|
91
|
+
amber: "text-amber-500",
|
|
92
|
+
rose: "text-rose-600 dark:text-rose-500",
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const CircularProgress: React.FC<CircularProgressProps> = ({
|
|
96
|
+
value,
|
|
97
|
+
size = 48,
|
|
98
|
+
strokeWidth = 4,
|
|
99
|
+
variant = "blue",
|
|
100
|
+
className,
|
|
101
|
+
...props
|
|
102
|
+
}) => {
|
|
103
|
+
const clamped = Math.min(100, Math.max(0, value))
|
|
104
|
+
const radius = (size - strokeWidth) / 2
|
|
105
|
+
const circumference = 2 * Math.PI * radius
|
|
106
|
+
const offset = circumference - (clamped / 100) * circumference
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<div
|
|
110
|
+
className={cn("relative inline-flex items-center justify-center", className)}
|
|
111
|
+
style={{ width: size, height: size }}
|
|
112
|
+
{...props}
|
|
113
|
+
>
|
|
114
|
+
<svg width={size} height={size} className="-rotate-90 transform">
|
|
115
|
+
<circle
|
|
116
|
+
cx={size / 2}
|
|
117
|
+
cy={size / 2}
|
|
118
|
+
r={radius}
|
|
119
|
+
fill="transparent"
|
|
120
|
+
stroke="currentColor"
|
|
121
|
+
strokeWidth={strokeWidth}
|
|
122
|
+
className="text-zinc-200 dark:text-zinc-800"
|
|
123
|
+
/>
|
|
124
|
+
<circle
|
|
125
|
+
cx={size / 2}
|
|
126
|
+
cy={size / 2}
|
|
127
|
+
r={radius}
|
|
128
|
+
fill="transparent"
|
|
129
|
+
stroke="currentColor"
|
|
130
|
+
strokeWidth={strokeWidth}
|
|
131
|
+
strokeDasharray={circumference}
|
|
132
|
+
strokeDashoffset={offset}
|
|
133
|
+
strokeLinecap="round"
|
|
134
|
+
className={cn("transition-all duration-300", strokeColorMap[variant])}
|
|
135
|
+
/>
|
|
136
|
+
</svg>
|
|
137
|
+
<span className="absolute font-mono text-[11px] font-medium text-zinc-800 dark:text-zinc-200">
|
|
138
|
+
{Math.round(clamped)}%
|
|
139
|
+
</span>
|
|
140
|
+
</div>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
CircularProgress.displayName = "CircularProgress"
|
|
144
|
+
|
|
145
|
+
export { Progress, CircularProgress }
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "../utils/cn"
|
|
3
|
+
|
|
4
|
+
export interface RadioGroupOption {
|
|
5
|
+
value: string
|
|
6
|
+
label: string
|
|
7
|
+
description?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface RadioGroupProps {
|
|
11
|
+
options: RadioGroupOption[]
|
|
12
|
+
value: string
|
|
13
|
+
onValueChange: (val: string) => void
|
|
14
|
+
className?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const RadioGroup: React.FC<RadioGroupProps> = ({
|
|
18
|
+
options,
|
|
19
|
+
value,
|
|
20
|
+
onValueChange,
|
|
21
|
+
className,
|
|
22
|
+
}) => (
|
|
23
|
+
<div role="radiogroup" className={cn("space-y-2", className)}>
|
|
24
|
+
{options.map((opt) => {
|
|
25
|
+
const checked = opt.value === value
|
|
26
|
+
return (
|
|
27
|
+
<label
|
|
28
|
+
key={opt.value}
|
|
29
|
+
onClick={() => onValueChange(opt.value)}
|
|
30
|
+
className={cn(
|
|
31
|
+
"flex items-start gap-3 rounded-xl border p-3.5 transition-colors cursor-pointer",
|
|
32
|
+
checked
|
|
33
|
+
? "border-blue-500/50 bg-blue-50/40 dark:border-blue-500/40 dark:bg-blue-500/10"
|
|
34
|
+
: "border-zinc-200 bg-white hover:bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-900/60 dark:hover:bg-zinc-800/50"
|
|
35
|
+
)}
|
|
36
|
+
>
|
|
37
|
+
<span
|
|
38
|
+
className={cn(
|
|
39
|
+
"mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full border",
|
|
40
|
+
checked
|
|
41
|
+
? "border-blue-600 bg-blue-600 text-white"
|
|
42
|
+
: "border-zinc-300 dark:border-zinc-700"
|
|
43
|
+
)}
|
|
44
|
+
>
|
|
45
|
+
{checked && <span className="h-1.5 w-1.5 rounded-full bg-white" />}
|
|
46
|
+
</span>
|
|
47
|
+
<div className="space-y-0.5 text-xs">
|
|
48
|
+
<div className="font-medium text-zinc-900 dark:text-zinc-100">
|
|
49
|
+
{opt.label}
|
|
50
|
+
</div>
|
|
51
|
+
{opt.description && (
|
|
52
|
+
<div className="text-zinc-500 dark:text-zinc-400">
|
|
53
|
+
{opt.description}
|
|
54
|
+
</div>
|
|
55
|
+
)}
|
|
56
|
+
</div>
|
|
57
|
+
</label>
|
|
58
|
+
)
|
|
59
|
+
})}
|
|
60
|
+
</div>
|
|
61
|
+
)
|
|
62
|
+
RadioGroup.displayName = "RadioGroup"
|
|
63
|
+
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import * as SelectPrimitive from "@radix-ui/react-select"
|
|
3
|
+
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
|
4
|
+
import { Check, ChevronDown, Search, ChevronsUpDown } from "lucide-react"
|
|
5
|
+
import { cn } from "../utils/cn"
|
|
6
|
+
|
|
7
|
+
const Select = SelectPrimitive.Root
|
|
8
|
+
const SelectGroup = SelectPrimitive.Group
|
|
9
|
+
const SelectValue = SelectPrimitive.Value
|
|
10
|
+
|
|
11
|
+
const SelectTrigger = React.forwardRef<
|
|
12
|
+
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
|
13
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
|
14
|
+
>(({ className, children, ...props }, ref) => (
|
|
15
|
+
<SelectPrimitive.Trigger
|
|
16
|
+
ref={ref}
|
|
17
|
+
className={cn(
|
|
18
|
+
"flex h-9 w-full items-center justify-between gap-2 rounded-lg border border-zinc-200 bg-white px-3 py-2 text-xs text-zinc-900 placeholder:text-zinc-400 transition-colors hover:bg-zinc-50 focus:outline-none focus:ring-2 focus:ring-blue-500/20 disabled:cursor-not-allowed disabled:opacity-50 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100 dark:hover:bg-zinc-800/60 [&>span]:line-clamp-1 cursor-pointer",
|
|
19
|
+
className
|
|
20
|
+
)}
|
|
21
|
+
{...props}
|
|
22
|
+
>
|
|
23
|
+
{children}
|
|
24
|
+
<SelectPrimitive.Icon asChild>
|
|
25
|
+
<ChevronDown className="h-3.5 w-3.5 text-zinc-400 shrink-0" />
|
|
26
|
+
</SelectPrimitive.Icon>
|
|
27
|
+
</SelectPrimitive.Trigger>
|
|
28
|
+
))
|
|
29
|
+
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
|
30
|
+
|
|
31
|
+
const SelectContent = React.forwardRef<
|
|
32
|
+
React.ElementRef<typeof SelectPrimitive.Content>,
|
|
33
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
|
34
|
+
>(({ className, children, position = "popper", ...props }, ref) => (
|
|
35
|
+
<SelectPrimitive.Portal>
|
|
36
|
+
<SelectPrimitive.Content
|
|
37
|
+
ref={ref}
|
|
38
|
+
className={cn(
|
|
39
|
+
"relative z-50 max-h-72 min-w-[8rem] overflow-hidden rounded-xl border border-zinc-200 bg-white text-zinc-900 shadow-lg dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100",
|
|
40
|
+
position === "popper" &&
|
|
41
|
+
"data-[side=bottom]:translate-y-1 data-[side=top]:-translate-y-1",
|
|
42
|
+
className
|
|
43
|
+
)}
|
|
44
|
+
position={position}
|
|
45
|
+
{...props}
|
|
46
|
+
>
|
|
47
|
+
<SelectPrimitive.Viewport
|
|
48
|
+
className={cn(
|
|
49
|
+
"p-1",
|
|
50
|
+
position === "popper" &&
|
|
51
|
+
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
|
52
|
+
)}
|
|
53
|
+
>
|
|
54
|
+
{children}
|
|
55
|
+
</SelectPrimitive.Viewport>
|
|
56
|
+
</SelectPrimitive.Content>
|
|
57
|
+
</SelectPrimitive.Portal>
|
|
58
|
+
))
|
|
59
|
+
SelectContent.displayName = SelectPrimitive.Content.displayName
|
|
60
|
+
|
|
61
|
+
const SelectItem = React.forwardRef<
|
|
62
|
+
React.ElementRef<typeof SelectPrimitive.Item>,
|
|
63
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
|
64
|
+
>(({ className, children, ...props }, ref) => (
|
|
65
|
+
<SelectPrimitive.Item
|
|
66
|
+
ref={ref}
|
|
67
|
+
className={cn(
|
|
68
|
+
"relative flex w-full cursor-pointer select-none items-center rounded-lg py-1.5 pl-8 pr-2.5 text-xs text-zinc-700 outline-none transition-colors focus:bg-zinc-100 focus:text-zinc-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:text-zinc-200 dark:focus:bg-zinc-800 dark:focus:text-white",
|
|
69
|
+
className
|
|
70
|
+
)}
|
|
71
|
+
{...props}
|
|
72
|
+
>
|
|
73
|
+
<span className="absolute left-2.5 flex h-3.5 w-3.5 items-center justify-center">
|
|
74
|
+
<SelectPrimitive.ItemIndicator>
|
|
75
|
+
<Check className="h-3.5 w-3.5 text-blue-600 dark:text-blue-400" />
|
|
76
|
+
</SelectPrimitive.ItemIndicator>
|
|
77
|
+
</span>
|
|
78
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
79
|
+
</SelectPrimitive.Item>
|
|
80
|
+
))
|
|
81
|
+
SelectItem.displayName = SelectPrimitive.Item.displayName
|
|
82
|
+
|
|
83
|
+
export interface ComboboxOption {
|
|
84
|
+
value: string
|
|
85
|
+
label: string
|
|
86
|
+
badge?: string
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface ComboboxProps {
|
|
90
|
+
options: ComboboxOption[]
|
|
91
|
+
value: string
|
|
92
|
+
onValueChange: (value: string) => void
|
|
93
|
+
placeholder?: string
|
|
94
|
+
searchPlaceholder?: string
|
|
95
|
+
className?: string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const Combobox: React.FC<ComboboxProps> = ({
|
|
99
|
+
options,
|
|
100
|
+
value,
|
|
101
|
+
onValueChange,
|
|
102
|
+
placeholder = "Select option...",
|
|
103
|
+
searchPlaceholder = "Search...",
|
|
104
|
+
className,
|
|
105
|
+
}) => {
|
|
106
|
+
const [open, setOpen] = React.useState(false)
|
|
107
|
+
const [query, setQuery] = React.useState("")
|
|
108
|
+
|
|
109
|
+
const selected = options.find((o) => o.value === value)
|
|
110
|
+
const filtered = React.useMemo(() => {
|
|
111
|
+
if (!query.trim()) return options
|
|
112
|
+
return options.filter((o) =>
|
|
113
|
+
o.label.toLowerCase().includes(query.toLowerCase())
|
|
114
|
+
)
|
|
115
|
+
}, [options, query])
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<PopoverPrimitive.Root open={open} onOpenChange={setOpen}>
|
|
119
|
+
<PopoverPrimitive.Trigger asChild>
|
|
120
|
+
<button
|
|
121
|
+
type="button"
|
|
122
|
+
className={cn(
|
|
123
|
+
"flex h-9 w-full items-center justify-between gap-2 rounded-lg border border-zinc-200 bg-white px-3 py-2 text-xs text-zinc-900 transition-colors hover:bg-zinc-50 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100 dark:hover:bg-zinc-800/60 cursor-pointer",
|
|
124
|
+
className
|
|
125
|
+
)}
|
|
126
|
+
>
|
|
127
|
+
<span className={cn("truncate", !selected && "text-zinc-400")}>
|
|
128
|
+
{selected ? selected.label : placeholder}
|
|
129
|
+
</span>
|
|
130
|
+
<ChevronsUpDown className="h-3.5 w-3.5 text-zinc-400 shrink-0" />
|
|
131
|
+
</button>
|
|
132
|
+
</PopoverPrimitive.Trigger>
|
|
133
|
+
<PopoverPrimitive.Portal>
|
|
134
|
+
<PopoverPrimitive.Content
|
|
135
|
+
align="start"
|
|
136
|
+
sideOffset={6}
|
|
137
|
+
className="z-50 w-64 rounded-xl border border-zinc-200 bg-white p-1.5 text-zinc-900 shadow-lg focus:outline-none dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100"
|
|
138
|
+
>
|
|
139
|
+
<div className="flex items-center gap-2 border-b border-zinc-200 px-2.5 py-1.5 dark:border-zinc-800">
|
|
140
|
+
<Search className="h-3.5 w-3.5 text-zinc-400" />
|
|
141
|
+
<input
|
|
142
|
+
type="text"
|
|
143
|
+
value={query}
|
|
144
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
145
|
+
placeholder={searchPlaceholder}
|
|
146
|
+
className="w-full bg-transparent text-xs text-zinc-900 placeholder:text-zinc-400 focus:outline-none dark:text-zinc-100"
|
|
147
|
+
/>
|
|
148
|
+
</div>
|
|
149
|
+
<div className="mt-1 max-h-52 overflow-y-auto space-y-0.5">
|
|
150
|
+
{filtered.length === 0 ? (
|
|
151
|
+
<div className="py-4 text-center text-xs text-zinc-400">
|
|
152
|
+
No matching items found.
|
|
153
|
+
</div>
|
|
154
|
+
) : (
|
|
155
|
+
filtered.map((opt) => {
|
|
156
|
+
const isSelected = opt.value === value
|
|
157
|
+
return (
|
|
158
|
+
<button
|
|
159
|
+
key={opt.value}
|
|
160
|
+
type="button"
|
|
161
|
+
onClick={() => {
|
|
162
|
+
onValueChange(opt.value)
|
|
163
|
+
setOpen(false)
|
|
164
|
+
setQuery("")
|
|
165
|
+
}}
|
|
166
|
+
className={cn(
|
|
167
|
+
"flex w-full items-center justify-between rounded-lg px-2.5 py-1.5 text-xs transition-colors cursor-pointer",
|
|
168
|
+
isSelected
|
|
169
|
+
? "bg-blue-50 text-blue-700 dark:bg-blue-500/15 dark:text-blue-300"
|
|
170
|
+
: "text-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
|
171
|
+
)}
|
|
172
|
+
>
|
|
173
|
+
<span className="flex items-center gap-2">
|
|
174
|
+
<Check
|
|
175
|
+
className={cn(
|
|
176
|
+
"h-3.5 w-3.5",
|
|
177
|
+
isSelected ? "opacity-100 text-blue-600 dark:text-blue-400" : "opacity-0"
|
|
178
|
+
)}
|
|
179
|
+
/>
|
|
180
|
+
{opt.label}
|
|
181
|
+
</span>
|
|
182
|
+
{opt.badge && (
|
|
183
|
+
<span className="rounded bg-zinc-100 px-1.5 py-0.5 font-mono text-[10px] text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400">
|
|
184
|
+
{opt.badge}
|
|
185
|
+
</span>
|
|
186
|
+
)}
|
|
187
|
+
</button>
|
|
188
|
+
)
|
|
189
|
+
})
|
|
190
|
+
)}
|
|
191
|
+
</div>
|
|
192
|
+
</PopoverPrimitive.Content>
|
|
193
|
+
</PopoverPrimitive.Portal>
|
|
194
|
+
</PopoverPrimitive.Root>
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
Combobox.displayName = "Combobox"
|
|
198
|
+
|
|
199
|
+
export {
|
|
200
|
+
Select,
|
|
201
|
+
SelectGroup,
|
|
202
|
+
SelectValue,
|
|
203
|
+
SelectTrigger,
|
|
204
|
+
SelectContent,
|
|
205
|
+
SelectItem,
|
|
206
|
+
Combobox,
|
|
207
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "../utils/cn"
|
|
3
|
+
|
|
4
|
+
function Skeleton({
|
|
5
|
+
className,
|
|
6
|
+
...props
|
|
7
|
+
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
className={cn(
|
|
11
|
+
"animate-pulse rounded-md bg-zinc-200/80 dark:bg-zinc-800",
|
|
12
|
+
className
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function CardSkeleton({ className }: { className?: string }) {
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
className={cn(
|
|
23
|
+
"rounded-xl border border-zinc-200 bg-white p-5 space-y-4 dark:border-zinc-800 dark:bg-zinc-900",
|
|
24
|
+
className
|
|
25
|
+
)}
|
|
26
|
+
>
|
|
27
|
+
<div className="flex items-center justify-between">
|
|
28
|
+
<Skeleton className="h-4 w-28" />
|
|
29
|
+
<Skeleton className="h-5 w-14 rounded-full" />
|
|
30
|
+
</div>
|
|
31
|
+
<Skeleton className="h-8 w-36" />
|
|
32
|
+
<div className="space-y-2 pt-2">
|
|
33
|
+
<Skeleton className="h-2.5 w-full" />
|
|
34
|
+
<Skeleton className="h-2.5 w-4/5" />
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function TableRowSkeleton({ rows = 4 }: { rows?: number }) {
|
|
41
|
+
return (
|
|
42
|
+
<div className="w-full rounded-xl border border-zinc-200 bg-white divide-y divide-zinc-200 dark:border-zinc-800 dark:bg-zinc-900 dark:divide-zinc-800">
|
|
43
|
+
{Array.from({ length: rows }).map((_, idx) => (
|
|
44
|
+
<div key={idx} className="flex items-center justify-between px-4 py-3 gap-4">
|
|
45
|
+
<div className="flex items-center gap-3">
|
|
46
|
+
<Skeleton className="h-8 w-8 rounded-full" />
|
|
47
|
+
<div className="space-y-1.5">
|
|
48
|
+
<Skeleton className="h-3.5 w-32" />
|
|
49
|
+
<Skeleton className="h-2.5 w-24" />
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
<Skeleton className="h-5 w-16 rounded-md" />
|
|
53
|
+
<Skeleton className="h-3.5 w-20" />
|
|
54
|
+
<Skeleton className="h-7 w-7 rounded-md" />
|
|
55
|
+
</div>
|
|
56
|
+
))}
|
|
57
|
+
</div>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { Skeleton, CardSkeleton, TableRowSkeleton }
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "../utils/cn"
|
|
3
|
+
|
|
4
|
+
export interface SliderProps {
|
|
5
|
+
value: number
|
|
6
|
+
min?: number
|
|
7
|
+
max?: number
|
|
8
|
+
step?: number
|
|
9
|
+
onValueChange?: (val: number) => void
|
|
10
|
+
label?: string
|
|
11
|
+
className?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const Slider: React.FC<SliderProps> = ({
|
|
15
|
+
value,
|
|
16
|
+
min = 0,
|
|
17
|
+
max = 100,
|
|
18
|
+
step = 1,
|
|
19
|
+
onValueChange,
|
|
20
|
+
label,
|
|
21
|
+
className,
|
|
22
|
+
}) => (
|
|
23
|
+
<div className={cn("space-y-2", className)}>
|
|
24
|
+
{label && (
|
|
25
|
+
<div className="flex items-center justify-between text-xs">
|
|
26
|
+
<span className="font-medium text-zinc-700 dark:text-zinc-300">
|
|
27
|
+
{label}
|
|
28
|
+
</span>
|
|
29
|
+
<span className="font-mono text-zinc-500">{value}%</span>
|
|
30
|
+
</div>
|
|
31
|
+
)}
|
|
32
|
+
<input
|
|
33
|
+
type="range"
|
|
34
|
+
min={min}
|
|
35
|
+
max={max}
|
|
36
|
+
step={step}
|
|
37
|
+
value={value}
|
|
38
|
+
onChange={(e) => onValueChange?.(Number(e.target.value))}
|
|
39
|
+
className="h-1.5 w-full cursor-pointer appearance-none rounded-lg bg-zinc-200 accent-blue-600 dark:bg-zinc-800"
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
)
|
|
43
|
+
Slider.displayName = "Slider"
|
|
44
|
+
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import * as SwitchPrimitives from "@radix-ui/react-switch"
|
|
3
|
+
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
|
4
|
+
import { Check } from "lucide-react"
|
|
5
|
+
import { cn } from "../utils/cn"
|
|
6
|
+
|
|
7
|
+
const Switch = React.forwardRef<
|
|
8
|
+
React.ElementRef<typeof SwitchPrimitives.Root>,
|
|
9
|
+
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
|
10
|
+
>(({ className, ...props }, ref) => (
|
|
11
|
+
<SwitchPrimitives.Root
|
|
12
|
+
className={cn(
|
|
13
|
+
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/30 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-blue-600 data-[state=unchecked]:bg-zinc-200 dark:data-[state=unchecked]:bg-zinc-800",
|
|
14
|
+
className
|
|
15
|
+
)}
|
|
16
|
+
{...props}
|
|
17
|
+
ref={ref}
|
|
18
|
+
>
|
|
19
|
+
<SwitchPrimitives.Thumb
|
|
20
|
+
className={cn(
|
|
21
|
+
"pointer-events-none block h-4 w-4 rounded-full bg-white shadow-xs ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0.5"
|
|
22
|
+
)}
|
|
23
|
+
/>
|
|
24
|
+
</SwitchPrimitives.Root>
|
|
25
|
+
))
|
|
26
|
+
Switch.displayName = SwitchPrimitives.Root.displayName
|
|
27
|
+
|
|
28
|
+
const Checkbox = React.forwardRef<
|
|
29
|
+
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
|
30
|
+
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
|
31
|
+
>(({ className, ...props }, ref) => (
|
|
32
|
+
<CheckboxPrimitive.Root
|
|
33
|
+
ref={ref}
|
|
34
|
+
className={cn(
|
|
35
|
+
"peer h-4 w-4 shrink-0 rounded border border-zinc-300 bg-white transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/30 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-blue-600 data-[state=checked]:border-blue-600 data-[state=checked]:text-white dark:border-zinc-700 dark:bg-zinc-900 dark:data-[state=checked]:bg-blue-600 cursor-pointer",
|
|
36
|
+
className
|
|
37
|
+
)}
|
|
38
|
+
{...props}
|
|
39
|
+
>
|
|
40
|
+
<CheckboxPrimitive.Indicator
|
|
41
|
+
className={cn("flex items-center justify-center text-current")}
|
|
42
|
+
>
|
|
43
|
+
<Check className="h-3 w-3 stroke-[2.5]" />
|
|
44
|
+
</CheckboxPrimitive.Indicator>
|
|
45
|
+
</CheckboxPrimitive.Root>
|
|
46
|
+
))
|
|
47
|
+
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
|
48
|
+
|
|
49
|
+
export interface RadioGroupOption {
|
|
50
|
+
value: string
|
|
51
|
+
label: string
|
|
52
|
+
description?: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface RadioGroupProps {
|
|
56
|
+
options: RadioGroupOption[]
|
|
57
|
+
value: string
|
|
58
|
+
onValueChange: (val: string) => void
|
|
59
|
+
className?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const RadioGroup: React.FC<RadioGroupProps> = ({
|
|
63
|
+
options,
|
|
64
|
+
value,
|
|
65
|
+
onValueChange,
|
|
66
|
+
className,
|
|
67
|
+
}) => (
|
|
68
|
+
<div role="radiogroup" className={cn("space-y-2", className)}>
|
|
69
|
+
{options.map((opt) => {
|
|
70
|
+
const checked = opt.value === value
|
|
71
|
+
return (
|
|
72
|
+
<label
|
|
73
|
+
key={opt.value}
|
|
74
|
+
onClick={() => onValueChange(opt.value)}
|
|
75
|
+
className={cn(
|
|
76
|
+
"flex items-start gap-3 rounded-xl border p-3.5 transition-colors cursor-pointer",
|
|
77
|
+
checked
|
|
78
|
+
? "border-blue-500/50 bg-blue-50/40 dark:border-blue-500/40 dark:bg-blue-500/10"
|
|
79
|
+
: "border-zinc-200 bg-white hover:bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-900/60 dark:hover:bg-zinc-800/50"
|
|
80
|
+
)}
|
|
81
|
+
>
|
|
82
|
+
<span
|
|
83
|
+
className={cn(
|
|
84
|
+
"mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full border",
|
|
85
|
+
checked
|
|
86
|
+
? "border-blue-600 bg-blue-600 text-white"
|
|
87
|
+
: "border-zinc-300 dark:border-zinc-700"
|
|
88
|
+
)}
|
|
89
|
+
>
|
|
90
|
+
{checked && <span className="h-1.5 w-1.5 rounded-full bg-white" />}
|
|
91
|
+
</span>
|
|
92
|
+
<div className="space-y-0.5 text-xs">
|
|
93
|
+
<div className="font-medium text-zinc-900 dark:text-zinc-100">
|
|
94
|
+
{opt.label}
|
|
95
|
+
</div>
|
|
96
|
+
{opt.description && (
|
|
97
|
+
<div className="text-zinc-500 dark:text-zinc-400">
|
|
98
|
+
{opt.description}
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
</label>
|
|
103
|
+
)
|
|
104
|
+
})}
|
|
105
|
+
</div>
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
export interface SliderProps {
|
|
109
|
+
value: number
|
|
110
|
+
min?: number
|
|
111
|
+
max?: number
|
|
112
|
+
step?: number
|
|
113
|
+
onValueChange?: (val: number) => void
|
|
114
|
+
label?: string
|
|
115
|
+
className?: string
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const Slider: React.FC<SliderProps> = ({
|
|
119
|
+
value,
|
|
120
|
+
min = 0,
|
|
121
|
+
max = 100,
|
|
122
|
+
step = 1,
|
|
123
|
+
onValueChange,
|
|
124
|
+
label,
|
|
125
|
+
className,
|
|
126
|
+
}) => (
|
|
127
|
+
<div className={cn("space-y-2", className)}>
|
|
128
|
+
{label && (
|
|
129
|
+
<div className="flex items-center justify-between text-xs">
|
|
130
|
+
<span className="font-medium text-zinc-700 dark:text-zinc-300">{label}</span>
|
|
131
|
+
<span className="font-mono text-zinc-500">{value}%</span>
|
|
132
|
+
</div>
|
|
133
|
+
)}
|
|
134
|
+
<input
|
|
135
|
+
type="range"
|
|
136
|
+
min={min}
|
|
137
|
+
max={max}
|
|
138
|
+
step={step}
|
|
139
|
+
value={value}
|
|
140
|
+
onChange={(e) => onValueChange?.(Number(e.target.value))}
|
|
141
|
+
className="h-1.5 w-full cursor-pointer appearance-none rounded-lg bg-zinc-200 accent-blue-600 dark:bg-zinc-800"
|
|
142
|
+
/>
|
|
143
|
+
</div>
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
export { Switch, Checkbox, RadioGroup, Slider }
|