@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.
@@ -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
+ };