@holaboss/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/README.md +116 -0
- package/dist/index.cjs +973 -0
- package/dist/index.d.cts +643 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +643 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +904 -0
- package/dist/index.js.map +1 -0
- package/dist/themes/holaos.css +121 -0
- package/dist/tokens.css +446 -0
- package/package.json +72 -0
- package/src/index.ts +108 -0
- package/src/layouts/dashboard-shell.tsx +43 -0
- package/src/layouts/data-table.tsx +110 -0
- package/src/layouts/error-state.tsx +52 -0
- package/src/layouts/filter-bar.tsx +58 -0
- package/src/layouts/loading-state.tsx +67 -0
- package/src/layouts/page-header.tsx +44 -0
- package/src/layouts/section.tsx +49 -0
- package/src/layouts/stat-pill.tsx +57 -0
- package/src/lib/utils.ts +6 -0
- package/src/primitives/alert.tsx +76 -0
- package/src/primitives/badge.tsx +52 -0
- package/src/primitives/button.tsx +61 -0
- package/src/primitives/card.tsx +107 -0
- package/src/primitives/dropdown-menu.tsx +270 -0
- package/src/primitives/empty-state.tsx +112 -0
- package/src/primitives/input.tsx +20 -0
- package/src/primitives/kbd.tsx +38 -0
- package/src/primitives/label.tsx +20 -0
- package/src/primitives/popover.tsx +87 -0
- package/src/primitives/select.tsx +202 -0
- package/src/primitives/status-dot.tsx +85 -0
- package/src/primitives/switch.tsx +19 -0
- package/src/primitives/tabs.tsx +80 -0
- package/src/primitives/tooltip.tsx +64 -0
- package/src/tokens/themes/holaos.css +121 -0
- package/src/tokens/tokens.css +446 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "../lib/utils"
|
|
4
|
+
|
|
5
|
+
function Card({
|
|
6
|
+
className,
|
|
7
|
+
size = "default",
|
|
8
|
+
...props
|
|
9
|
+
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
data-slot="card"
|
|
13
|
+
data-size={size}
|
|
14
|
+
className={cn(
|
|
15
|
+
// Hairline edge driven by shadow-sm (the new unified token —
|
|
16
|
+
// 1px oklch-from-foreground ring with --hairline-alpha tuned per
|
|
17
|
+
// mode). Replaces the explicit ring-1 ring-border so cards inherit
|
|
18
|
+
// the same edge treatment as inputs/popovers/menus.
|
|
19
|
+
"group/card flex flex-col gap-4 overflow-hidden rounded-xl bg-card py-4 text-sm text-card-foreground shadow-sm has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
|
|
20
|
+
className
|
|
21
|
+
)}
|
|
22
|
+
{...props}
|
|
23
|
+
/>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
28
|
+
return (
|
|
29
|
+
<div
|
|
30
|
+
data-slot="card-header"
|
|
31
|
+
className={cn(
|
|
32
|
+
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3",
|
|
33
|
+
className
|
|
34
|
+
)}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
data-slot="card-title"
|
|
44
|
+
className={cn(
|
|
45
|
+
"font-heading text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",
|
|
46
|
+
className
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
54
|
+
return (
|
|
55
|
+
<div
|
|
56
|
+
data-slot="card-description"
|
|
57
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
64
|
+
return (
|
|
65
|
+
<div
|
|
66
|
+
data-slot="card-action"
|
|
67
|
+
className={cn(
|
|
68
|
+
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
69
|
+
className
|
|
70
|
+
)}
|
|
71
|
+
{...props}
|
|
72
|
+
/>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
77
|
+
return (
|
|
78
|
+
<div
|
|
79
|
+
data-slot="card-content"
|
|
80
|
+
className={cn("px-4 group-data-[size=sm]/card:px-3", className)}
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
87
|
+
return (
|
|
88
|
+
<div
|
|
89
|
+
data-slot="card-footer"
|
|
90
|
+
className={cn(
|
|
91
|
+
"flex items-center rounded-b-xl border-t bg-muted p-4 group-data-[size=sm]/card:p-3",
|
|
92
|
+
className
|
|
93
|
+
)}
|
|
94
|
+
{...props}
|
|
95
|
+
/>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export {
|
|
100
|
+
Card,
|
|
101
|
+
CardHeader,
|
|
102
|
+
CardFooter,
|
|
103
|
+
CardTitle,
|
|
104
|
+
CardAction,
|
|
105
|
+
CardDescription,
|
|
106
|
+
CardContent,
|
|
107
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Menu as MenuPrimitive } from "@base-ui/react/menu";
|
|
3
|
+
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
import { ChevronRightIcon, CheckIcon } from "lucide-react";
|
|
6
|
+
|
|
7
|
+
function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {
|
|
8
|
+
return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {
|
|
12
|
+
return <MenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {
|
|
16
|
+
return <MenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function DropdownMenuContent({
|
|
20
|
+
align = "start",
|
|
21
|
+
alignOffset = 0,
|
|
22
|
+
side = "bottom",
|
|
23
|
+
sideOffset = 4,
|
|
24
|
+
className,
|
|
25
|
+
...props
|
|
26
|
+
}: MenuPrimitive.Popup.Props &
|
|
27
|
+
Pick<
|
|
28
|
+
MenuPrimitive.Positioner.Props,
|
|
29
|
+
"align" | "alignOffset" | "side" | "sideOffset"
|
|
30
|
+
>) {
|
|
31
|
+
return (
|
|
32
|
+
<MenuPrimitive.Portal>
|
|
33
|
+
<MenuPrimitive.Positioner
|
|
34
|
+
className="isolate z-50 outline-none"
|
|
35
|
+
align={align}
|
|
36
|
+
alignOffset={alignOffset}
|
|
37
|
+
side={side}
|
|
38
|
+
sideOffset={sideOffset}
|
|
39
|
+
>
|
|
40
|
+
<MenuPrimitive.Popup
|
|
41
|
+
data-slot="dropdown-menu-content"
|
|
42
|
+
className={cn(
|
|
43
|
+
"dark z-50 max-h-(--available-height) w-(--anchor-width) min-w-44 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg p-1 text-popover-foreground shadow-md ring-1 ring-border duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95 animate-none! relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:not-data-[variant=destructive]:focus:bg-foreground/10 **:data-[slot$=-item]:not-data-[variant=destructive]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-fg-5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10!",
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
</MenuPrimitive.Positioner>
|
|
49
|
+
</MenuPrimitive.Portal>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {
|
|
54
|
+
return <MenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function DropdownMenuLabel({
|
|
58
|
+
className,
|
|
59
|
+
inset,
|
|
60
|
+
...props
|
|
61
|
+
}: MenuPrimitive.GroupLabel.Props & {
|
|
62
|
+
inset?: boolean;
|
|
63
|
+
}) {
|
|
64
|
+
return (
|
|
65
|
+
<MenuPrimitive.GroupLabel
|
|
66
|
+
data-slot="dropdown-menu-label"
|
|
67
|
+
data-inset={inset}
|
|
68
|
+
className={cn(
|
|
69
|
+
"px-1.5 py-1 text-xs font-medium text-muted-foreground data-inset:pl-7",
|
|
70
|
+
className,
|
|
71
|
+
)}
|
|
72
|
+
{...props}
|
|
73
|
+
/>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function DropdownMenuItem({
|
|
78
|
+
className,
|
|
79
|
+
inset,
|
|
80
|
+
variant = "default",
|
|
81
|
+
...props
|
|
82
|
+
}: MenuPrimitive.Item.Props & {
|
|
83
|
+
inset?: boolean;
|
|
84
|
+
variant?: "default" | "destructive";
|
|
85
|
+
}) {
|
|
86
|
+
return (
|
|
87
|
+
<MenuPrimitive.Item
|
|
88
|
+
data-slot="dropdown-menu-item"
|
|
89
|
+
data-inset={inset}
|
|
90
|
+
data-variant={variant}
|
|
91
|
+
className={cn(
|
|
92
|
+
"group/dropdown-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-2.5 py-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",
|
|
93
|
+
className,
|
|
94
|
+
)}
|
|
95
|
+
{...props}
|
|
96
|
+
/>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {
|
|
101
|
+
return <MenuPrimitive.SubmenuRoot data-slot="dropdown-menu-sub" {...props} />;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function DropdownMenuSubTrigger({
|
|
105
|
+
className,
|
|
106
|
+
inset,
|
|
107
|
+
children,
|
|
108
|
+
...props
|
|
109
|
+
}: MenuPrimitive.SubmenuTrigger.Props & {
|
|
110
|
+
inset?: boolean;
|
|
111
|
+
}) {
|
|
112
|
+
return (
|
|
113
|
+
<MenuPrimitive.SubmenuTrigger
|
|
114
|
+
data-slot="dropdown-menu-sub-trigger"
|
|
115
|
+
data-inset={inset}
|
|
116
|
+
className={cn(
|
|
117
|
+
"flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-popup-open:bg-accent data-popup-open:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
118
|
+
className,
|
|
119
|
+
)}
|
|
120
|
+
{...props}
|
|
121
|
+
>
|
|
122
|
+
{children}
|
|
123
|
+
<ChevronRightIcon className="ml-auto" />
|
|
124
|
+
</MenuPrimitive.SubmenuTrigger>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function DropdownMenuSubContent({
|
|
129
|
+
align = "start",
|
|
130
|
+
alignOffset = -3,
|
|
131
|
+
side = "right",
|
|
132
|
+
sideOffset = 0,
|
|
133
|
+
className,
|
|
134
|
+
...props
|
|
135
|
+
}: React.ComponentProps<typeof DropdownMenuContent>) {
|
|
136
|
+
return (
|
|
137
|
+
<DropdownMenuContent
|
|
138
|
+
data-slot="dropdown-menu-sub-content"
|
|
139
|
+
className={cn(
|
|
140
|
+
"dark w-auto min-w-[96px] rounded-lg p-1 text-popover-foreground shadow-lg ring-1 ring-border duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 animate-none! relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:not-data-[variant=destructive]:focus:bg-foreground/10 **:data-[slot$=-item]:not-data-[variant=destructive]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-fg-5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10!",
|
|
141
|
+
className,
|
|
142
|
+
)}
|
|
143
|
+
align={align}
|
|
144
|
+
alignOffset={alignOffset}
|
|
145
|
+
side={side}
|
|
146
|
+
sideOffset={sideOffset}
|
|
147
|
+
{...props}
|
|
148
|
+
/>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function DropdownMenuCheckboxItem({
|
|
153
|
+
className,
|
|
154
|
+
children,
|
|
155
|
+
checked,
|
|
156
|
+
inset,
|
|
157
|
+
...props
|
|
158
|
+
}: MenuPrimitive.CheckboxItem.Props & {
|
|
159
|
+
inset?: boolean;
|
|
160
|
+
}) {
|
|
161
|
+
return (
|
|
162
|
+
<MenuPrimitive.CheckboxItem
|
|
163
|
+
data-slot="dropdown-menu-checkbox-item"
|
|
164
|
+
data-inset={inset}
|
|
165
|
+
className={cn(
|
|
166
|
+
"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
167
|
+
className,
|
|
168
|
+
)}
|
|
169
|
+
checked={checked}
|
|
170
|
+
{...props}
|
|
171
|
+
>
|
|
172
|
+
<span
|
|
173
|
+
className="pointer-events-none absolute right-2 flex items-center justify-center"
|
|
174
|
+
data-slot="dropdown-menu-checkbox-item-indicator"
|
|
175
|
+
>
|
|
176
|
+
<MenuPrimitive.CheckboxItemIndicator>
|
|
177
|
+
<CheckIcon />
|
|
178
|
+
</MenuPrimitive.CheckboxItemIndicator>
|
|
179
|
+
</span>
|
|
180
|
+
{children}
|
|
181
|
+
</MenuPrimitive.CheckboxItem>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {
|
|
186
|
+
return (
|
|
187
|
+
<MenuPrimitive.RadioGroup
|
|
188
|
+
data-slot="dropdown-menu-radio-group"
|
|
189
|
+
{...props}
|
|
190
|
+
/>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function DropdownMenuRadioItem({
|
|
195
|
+
className,
|
|
196
|
+
children,
|
|
197
|
+
inset,
|
|
198
|
+
...props
|
|
199
|
+
}: MenuPrimitive.RadioItem.Props & {
|
|
200
|
+
inset?: boolean;
|
|
201
|
+
}) {
|
|
202
|
+
return (
|
|
203
|
+
<MenuPrimitive.RadioItem
|
|
204
|
+
data-slot="dropdown-menu-radio-item"
|
|
205
|
+
data-inset={inset}
|
|
206
|
+
className={cn(
|
|
207
|
+
"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
208
|
+
className,
|
|
209
|
+
)}
|
|
210
|
+
{...props}
|
|
211
|
+
>
|
|
212
|
+
<span
|
|
213
|
+
className="pointer-events-none absolute right-2 flex items-center justify-center"
|
|
214
|
+
data-slot="dropdown-menu-radio-item-indicator"
|
|
215
|
+
>
|
|
216
|
+
<MenuPrimitive.RadioItemIndicator>
|
|
217
|
+
<CheckIcon />
|
|
218
|
+
</MenuPrimitive.RadioItemIndicator>
|
|
219
|
+
</span>
|
|
220
|
+
{children}
|
|
221
|
+
</MenuPrimitive.RadioItem>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function DropdownMenuSeparator({
|
|
226
|
+
className,
|
|
227
|
+
...props
|
|
228
|
+
}: MenuPrimitive.Separator.Props) {
|
|
229
|
+
return (
|
|
230
|
+
<MenuPrimitive.Separator
|
|
231
|
+
data-slot="dropdown-menu-separator"
|
|
232
|
+
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
|
233
|
+
{...props}
|
|
234
|
+
/>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function DropdownMenuShortcut({
|
|
239
|
+
className,
|
|
240
|
+
...props
|
|
241
|
+
}: React.ComponentProps<"span">) {
|
|
242
|
+
return (
|
|
243
|
+
<span
|
|
244
|
+
data-slot="dropdown-menu-shortcut"
|
|
245
|
+
className={cn(
|
|
246
|
+
"ml-auto text-xs tracking-widest text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground",
|
|
247
|
+
className,
|
|
248
|
+
)}
|
|
249
|
+
{...props}
|
|
250
|
+
/>
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export {
|
|
255
|
+
DropdownMenu,
|
|
256
|
+
DropdownMenuPortal,
|
|
257
|
+
DropdownMenuTrigger,
|
|
258
|
+
DropdownMenuContent,
|
|
259
|
+
DropdownMenuGroup,
|
|
260
|
+
DropdownMenuLabel,
|
|
261
|
+
DropdownMenuItem,
|
|
262
|
+
DropdownMenuCheckboxItem,
|
|
263
|
+
DropdownMenuRadioGroup,
|
|
264
|
+
DropdownMenuRadioItem,
|
|
265
|
+
DropdownMenuSeparator,
|
|
266
|
+
DropdownMenuShortcut,
|
|
267
|
+
DropdownMenuSub,
|
|
268
|
+
DropdownMenuSubTrigger,
|
|
269
|
+
DropdownMenuSubContent,
|
|
270
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
|
|
2
|
+
import type { LucideIcon } from "lucide-react"
|
|
3
|
+
import type { ReactNode } from "react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../lib/utils"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* EmptyState — centered icon + title + optional description + optional
|
|
9
|
+
* action. Replaces the multiple hand-rolled placeholder variants
|
|
10
|
+
* scattered across panes (sidebar empties, dashboard "no rows",
|
|
11
|
+
* automations "no schedules", etc.).
|
|
12
|
+
*
|
|
13
|
+
* Two visual presentations driven by `size`:
|
|
14
|
+
*
|
|
15
|
+
* - `sm` (compact, default in dashboard panels) — small unframed icon
|
|
16
|
+
* at low opacity, `text-xs` copy. Good when the empty state shares
|
|
17
|
+
* a tight pane with chrome; was the original dashboard EmptyState.
|
|
18
|
+
*
|
|
19
|
+
* - `md` (roomier, default for sidebar / list empties) — icon wrapped
|
|
20
|
+
* in a chip background, `text-sm` title + `text-xs` description.
|
|
21
|
+
* More presence for full-pane empties.
|
|
22
|
+
*
|
|
23
|
+
* Pass `action` to surface a CTA below the description.
|
|
24
|
+
*
|
|
25
|
+
* `minHeight` forces a min-height (used by chart panels so the panel
|
|
26
|
+
* doesn't collapse when there's no data).
|
|
27
|
+
*/
|
|
28
|
+
export interface EmptyStateProps {
|
|
29
|
+
icon?: LucideIcon
|
|
30
|
+
title: string
|
|
31
|
+
description?: ReactNode
|
|
32
|
+
action?: ReactNode
|
|
33
|
+
size?: "sm" | "md"
|
|
34
|
+
/** Force min-height (px). Useful for chart cells that shouldn't collapse. */
|
|
35
|
+
minHeight?: number
|
|
36
|
+
/**
|
|
37
|
+
* Wrap the icon in a card-on-card chip framed by an Attio-style wide
|
|
38
|
+
* hairline grid backdrop that fades to transparent at the outer
|
|
39
|
+
* edges. Use for full-pane empties that need real presence
|
|
40
|
+
* (Automations, primary list views). Default off so compact in-card
|
|
41
|
+
* empties stay flat. Only applies when `size="md"`.
|
|
42
|
+
*/
|
|
43
|
+
decorated?: boolean
|
|
44
|
+
/** Extra classes on the outer wrapper. */
|
|
45
|
+
className?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function EmptyState({
|
|
49
|
+
icon: Icon,
|
|
50
|
+
title,
|
|
51
|
+
description,
|
|
52
|
+
action,
|
|
53
|
+
size = "sm",
|
|
54
|
+
minHeight,
|
|
55
|
+
decorated = false,
|
|
56
|
+
className,
|
|
57
|
+
}: EmptyStateProps) {
|
|
58
|
+
const isMd = size === "md"
|
|
59
|
+
const wrapperClass = cn(
|
|
60
|
+
"flex flex-col items-center justify-center text-center",
|
|
61
|
+
isMd ? "gap-3 px-4 py-14" : "gap-2 py-10 text-muted-foreground",
|
|
62
|
+
className,
|
|
63
|
+
)
|
|
64
|
+
const titleClass = isMd
|
|
65
|
+
? "text-sm font-medium text-foreground"
|
|
66
|
+
: "text-xs"
|
|
67
|
+
const descriptionClass = isMd
|
|
68
|
+
? "max-w-xs text-xs leading-5 text-muted-foreground"
|
|
69
|
+
: "text-[11px] opacity-70"
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div
|
|
73
|
+
className={wrapperClass}
|
|
74
|
+
style={minHeight ? { minHeight } : undefined}
|
|
75
|
+
>
|
|
76
|
+
{Icon ? (
|
|
77
|
+
isMd ? (
|
|
78
|
+
decorated ? (
|
|
79
|
+
<div className="relative flex h-24 w-72 items-center justify-center overflow-hidden">
|
|
80
|
+
<div
|
|
81
|
+
aria-hidden="true"
|
|
82
|
+
className="pointer-events-none absolute inset-0"
|
|
83
|
+
style={{
|
|
84
|
+
backgroundImage:
|
|
85
|
+
"linear-gradient(to right, var(--color-fg-8) 1px, transparent 1px), linear-gradient(to bottom, var(--color-fg-8) 1px, transparent 1px)",
|
|
86
|
+
backgroundSize: "32px 32px",
|
|
87
|
+
backgroundPosition: "center center",
|
|
88
|
+
maskImage:
|
|
89
|
+
"radial-gradient(ellipse at center, black 0%, black 38%, transparent 80%)",
|
|
90
|
+
WebkitMaskImage:
|
|
91
|
+
"radial-gradient(ellipse at center, black 0%, black 38%, transparent 80%)",
|
|
92
|
+
}}
|
|
93
|
+
/>
|
|
94
|
+
<div className="relative grid size-12 place-items-center rounded-xl border border-border bg-card text-muted-foreground shadow-xs">
|
|
95
|
+
<Icon className="size-[18px]" strokeWidth={1.6} />
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
) : (
|
|
99
|
+
<div className="grid size-10 place-items-center rounded-xl bg-muted text-muted-foreground">
|
|
100
|
+
<Icon className="size-4" strokeWidth={1.6} />
|
|
101
|
+
</div>
|
|
102
|
+
)
|
|
103
|
+
) : (
|
|
104
|
+
<Icon size={22} strokeWidth={1.5} className="opacity-45" />
|
|
105
|
+
)
|
|
106
|
+
) : null}
|
|
107
|
+
<p className={titleClass}>{title}</p>
|
|
108
|
+
{description ? <p className={descriptionClass}>{description}</p> : null}
|
|
109
|
+
{action ? <div className={isMd ? "mt-2" : "mt-1.5"}>{action}</div> : null}
|
|
110
|
+
</div>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Input as InputPrimitive } from "@base-ui/react/input"
|
|
3
|
+
|
|
4
|
+
import { cn } from "../lib/utils"
|
|
5
|
+
|
|
6
|
+
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|
7
|
+
return (
|
|
8
|
+
<InputPrimitive
|
|
9
|
+
type={type}
|
|
10
|
+
data-slot="input"
|
|
11
|
+
className={cn(
|
|
12
|
+
"h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
|
13
|
+
className
|
|
14
|
+
)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { Input }
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
import type { ComponentProps } from "react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Kbd — keyboard shortcut hint. Inline `<kbd>` styled as a tiny pill.
|
|
8
|
+
* Use in tooltip footers, menu trailing slots, and help text to teach
|
|
9
|
+
* keyboard grammar continuously.
|
|
10
|
+
*
|
|
11
|
+
* Single-key glyphs (⌘, ⇧, ↑, K) auto-center via the square sizing.
|
|
12
|
+
* For multi-key sequences, render multiple <Kbd> with a separator:
|
|
13
|
+
* <Kbd>⌘</Kbd><Kbd>K</Kbd>
|
|
14
|
+
*/
|
|
15
|
+
const kbdVariants = cva(
|
|
16
|
+
"inline-flex items-center justify-center rounded border border-border bg-fg-2 font-mono text-[10px] font-medium text-muted-foreground tabular-nums",
|
|
17
|
+
{
|
|
18
|
+
variants: {
|
|
19
|
+
size: {
|
|
20
|
+
sm: "h-4 min-w-4 px-1",
|
|
21
|
+
md: "h-5 min-w-5 px-1.5 text-[11px]",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
defaultVariants: { size: "sm" },
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
export type KbdProps = ComponentProps<"kbd"> & VariantProps<typeof kbdVariants>;
|
|
29
|
+
|
|
30
|
+
export function Kbd({ className, size, ...props }: KbdProps) {
|
|
31
|
+
return (
|
|
32
|
+
<kbd
|
|
33
|
+
data-slot="kbd"
|
|
34
|
+
className={cn(kbdVariants({ size }), className)}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../lib/utils"
|
|
6
|
+
|
|
7
|
+
function Label({ className, ...props }: React.ComponentProps<"label">) {
|
|
8
|
+
return (
|
|
9
|
+
<label
|
|
10
|
+
data-slot="label"
|
|
11
|
+
className={cn(
|
|
12
|
+
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
13
|
+
className
|
|
14
|
+
)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { Label }
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Popover as PopoverPrimitive } from "@base-ui/react/popover";
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
|
|
5
|
+
function Popover({ ...props }: PopoverPrimitive.Root.Props) {
|
|
6
|
+
return <PopoverPrimitive.Root data-slot="popover" {...props} />;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function PopoverTrigger({ ...props }: PopoverPrimitive.Trigger.Props) {
|
|
10
|
+
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function PopoverContent({
|
|
14
|
+
className,
|
|
15
|
+
positionerClassName,
|
|
16
|
+
align = "center",
|
|
17
|
+
alignOffset = 0,
|
|
18
|
+
side = "bottom",
|
|
19
|
+
sideOffset = 6,
|
|
20
|
+
...props
|
|
21
|
+
}: PopoverPrimitive.Popup.Props &
|
|
22
|
+
Pick<
|
|
23
|
+
PopoverPrimitive.Positioner.Props,
|
|
24
|
+
"align" | "alignOffset" | "side" | "sideOffset"
|
|
25
|
+
> & {
|
|
26
|
+
/** Override classes on the Positioner — needed when stacking above
|
|
27
|
+
portals that already sit at high z-index (e.g. the workspace
|
|
28
|
+
switcher pop-out at z-[80]). */
|
|
29
|
+
positionerClassName?: string;
|
|
30
|
+
}) {
|
|
31
|
+
return (
|
|
32
|
+
<PopoverPrimitive.Portal>
|
|
33
|
+
<PopoverPrimitive.Positioner
|
|
34
|
+
align={align}
|
|
35
|
+
alignOffset={alignOffset}
|
|
36
|
+
side={side}
|
|
37
|
+
sideOffset={sideOffset}
|
|
38
|
+
className={cn("isolate z-50", positionerClassName)}
|
|
39
|
+
>
|
|
40
|
+
<PopoverPrimitive.Popup
|
|
41
|
+
data-slot="popover-content"
|
|
42
|
+
className={cn(
|
|
43
|
+
"z-50 flex w-72 origin-(--transform-origin) flex-col gap-3 rounded-xl border border-border bg-popover p-4 text-sm text-popover-foreground shadow-2xl ring-0 ring-border outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
</PopoverPrimitive.Positioner>
|
|
49
|
+
</PopoverPrimitive.Portal>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function PopoverHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
54
|
+
return <div className={cn("flex flex-col gap-1", className)} {...props} />;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) {
|
|
58
|
+
return (
|
|
59
|
+
<PopoverPrimitive.Title
|
|
60
|
+
data-slot="popover-title"
|
|
61
|
+
className={cn("text-base font-semibold", className)}
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function PopoverDescription({
|
|
68
|
+
className,
|
|
69
|
+
...props
|
|
70
|
+
}: PopoverPrimitive.Description.Props) {
|
|
71
|
+
return (
|
|
72
|
+
<PopoverPrimitive.Description
|
|
73
|
+
data-slot="popover-description"
|
|
74
|
+
className={cn("text-muted-foreground", className)}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export {
|
|
81
|
+
Popover,
|
|
82
|
+
PopoverContent,
|
|
83
|
+
PopoverDescription,
|
|
84
|
+
PopoverHeader,
|
|
85
|
+
PopoverTitle,
|
|
86
|
+
PopoverTrigger,
|
|
87
|
+
};
|