@nebutra/ui 0.1.0 → 0.2.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/dist/components/index.d.ts +380 -0
- package/dist/components/index.js +2398 -0
- package/dist/components/index.js.map +1 -0
- package/dist/hooks/index.d.ts +106 -0
- package/dist/hooks/index.js +182 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/icons/index.d.ts +3 -0
- package/dist/icons/index.js +5 -0
- package/dist/icons/index.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/layout/index.d.ts +296 -0
- package/dist/layout/index.js +1303 -0
- package/dist/layout/index.js.map +1 -0
- package/dist/layouts/index.d.ts +161 -0
- package/dist/layouts/index.js +611 -0
- package/dist/layouts/index.js.map +1 -0
- package/dist/patterns/index.d.ts +363 -0
- package/dist/patterns/index.js +3004 -0
- package/dist/patterns/index.js.map +1 -0
- package/dist/primitives/index.d.ts +9588 -0
- package/dist/primitives/index.js +26779 -0
- package/dist/primitives/index.js.map +1 -0
- package/dist/styles/brand-override.css +61 -0
- package/dist/tailwind.preset.d.ts +251 -0
- package/dist/tailwind.preset.js +273 -0
- package/dist/tailwind.preset.js.map +1 -0
- package/dist/theme/index.d.ts +150 -0
- package/dist/theme/index.js +28 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/typography/fonts.css +110 -0
- package/dist/typography/index.d.ts +425 -0
- package/dist/typography/index.js +374 -0
- package/dist/typography/index.js.map +1 -0
- package/dist/utils/index.d.ts +61 -0
- package/dist/utils/index.js +55 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +16 -6
|
@@ -0,0 +1,3004 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React14 from 'react';
|
|
3
|
+
import { useState, useRef, useCallback, useMemo, Fragment as Fragment$1 } from 'react';
|
|
4
|
+
import { clsx } from 'clsx';
|
|
5
|
+
import { twMerge } from 'tailwind-merge';
|
|
6
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
7
|
+
import { ChevronRight, Check, Status, ChevronUpDown, Pencil, FileText, TextBold, TextItalic, Code, Hash, ListUnordered, Link, Image, ChevronUp, ChevronDown, Calendar, Eye, Bookmark, StarFill, Share, Flag, Pin, MoreHorizontal } from '@nebutra/icons';
|
|
8
|
+
import { cva } from 'class-variance-authority';
|
|
9
|
+
import { Menu } from '@base-ui/react/menu';
|
|
10
|
+
import { Tabs as Tabs$1 } from '@base-ui/react/tabs';
|
|
11
|
+
import { colors } from '@nebutra/brand';
|
|
12
|
+
import { Tooltip as Tooltip$1 } from '@base-ui/react/tooltip';
|
|
13
|
+
import ReactMarkdown from 'react-markdown';
|
|
14
|
+
import remarkGfm from 'remark-gfm';
|
|
15
|
+
import { Separator as Separator$1 } from '@base-ui/react/separator';
|
|
16
|
+
import { Avatar as Avatar$1 } from '@base-ui/react/avatar';
|
|
17
|
+
import { AnimatePresence, motion } from 'framer-motion';
|
|
18
|
+
|
|
19
|
+
// src/patterns/Card/Card.tsx
|
|
20
|
+
function cn(...inputs) {
|
|
21
|
+
return twMerge(clsx(inputs));
|
|
22
|
+
}
|
|
23
|
+
var CardContext = React14.createContext(null);
|
|
24
|
+
var paddingMap = {
|
|
25
|
+
none: "",
|
|
26
|
+
sm: "p-4",
|
|
27
|
+
md: "p-6",
|
|
28
|
+
lg: "p-8"
|
|
29
|
+
};
|
|
30
|
+
var variantStyles = {
|
|
31
|
+
default: "bg-card border border-border",
|
|
32
|
+
elevated: "bg-card border border-border shadow-lg",
|
|
33
|
+
bordered: "bg-transparent border-2 border-border",
|
|
34
|
+
ghost: "bg-transparent",
|
|
35
|
+
gradient: "bg-gradient-to-br from-card to-muted border border-border"
|
|
36
|
+
};
|
|
37
|
+
var CardRoot = React14.forwardRef(
|
|
38
|
+
({ className, variant = "default", padding = "md", children, ...props }, ref) => {
|
|
39
|
+
return /* @__PURE__ */ jsx(CardContext.Provider, { value: { variant }, children: /* @__PURE__ */ jsx(
|
|
40
|
+
"div",
|
|
41
|
+
{
|
|
42
|
+
ref,
|
|
43
|
+
className: cn(
|
|
44
|
+
"rounded-[var(--radius-xl)] transition-all duration-200",
|
|
45
|
+
variantStyles[variant],
|
|
46
|
+
paddingMap[padding],
|
|
47
|
+
className
|
|
48
|
+
),
|
|
49
|
+
...props,
|
|
50
|
+
children
|
|
51
|
+
}
|
|
52
|
+
) });
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
CardRoot.displayName = "Card";
|
|
56
|
+
var CardHeader = React14.forwardRef(
|
|
57
|
+
({ className, spacing: gap = "md", children, ...props }, ref) => {
|
|
58
|
+
const gapMap = { sm: "gap-2", md: "gap-3", lg: "gap-4" };
|
|
59
|
+
return /* @__PURE__ */ jsx("div", { ref, className: cn("flex items-start", gapMap[gap], className), ...props, children });
|
|
60
|
+
}
|
|
61
|
+
);
|
|
62
|
+
CardHeader.displayName = "Card.Header";
|
|
63
|
+
var CardBody = React14.forwardRef(
|
|
64
|
+
({ className, children, ...props }, ref) => {
|
|
65
|
+
return /* @__PURE__ */ jsx("div", { ref, className: cn("mt-4", className), ...props, children });
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
CardBody.displayName = "Card.Body";
|
|
69
|
+
var CardFooter = React14.forwardRef(
|
|
70
|
+
({ className, children, ...props }, ref) => {
|
|
71
|
+
return /* @__PURE__ */ jsx("div", { ref, className: cn("mt-6 pt-4 border-t border-border/50", className), ...props, children });
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
CardFooter.displayName = "Card.Footer";
|
|
75
|
+
var CardIcon = React14.forwardRef(
|
|
76
|
+
({ className, size = "md", children, ...props }, ref) => {
|
|
77
|
+
const sizeMap = {
|
|
78
|
+
sm: "w-8 h-8",
|
|
79
|
+
md: "w-10 h-10",
|
|
80
|
+
lg: "w-12 h-12"
|
|
81
|
+
};
|
|
82
|
+
return /* @__PURE__ */ jsx(
|
|
83
|
+
"div",
|
|
84
|
+
{
|
|
85
|
+
ref,
|
|
86
|
+
className: cn(
|
|
87
|
+
"flex items-center justify-center rounded-[var(--radius-lg)] bg-muted/50 shrink-0",
|
|
88
|
+
sizeMap[size],
|
|
89
|
+
className
|
|
90
|
+
),
|
|
91
|
+
...props,
|
|
92
|
+
children
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
CardIcon.displayName = "Card.Icon";
|
|
98
|
+
var CardTitle = React14.forwardRef(
|
|
99
|
+
({ className, as: Tag = "h3", children, ...props }, ref) => {
|
|
100
|
+
return /* @__PURE__ */ jsx(Tag, { ref, className: cn("text-lg font-semibold text-foreground", className), ...props, children });
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
CardTitle.displayName = "Card.Title";
|
|
104
|
+
var CardDescription = React14.forwardRef(({ className, as: Tag = "p", children, ...props }, ref) => {
|
|
105
|
+
return React14.createElement(
|
|
106
|
+
Tag,
|
|
107
|
+
{
|
|
108
|
+
ref,
|
|
109
|
+
className: cn("text-sm text-muted-foreground leading-relaxed", className),
|
|
110
|
+
...props
|
|
111
|
+
},
|
|
112
|
+
children
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
CardDescription.displayName = "Card.Description";
|
|
116
|
+
var Card = Object.assign(CardRoot, {
|
|
117
|
+
Header: CardHeader,
|
|
118
|
+
Body: CardBody,
|
|
119
|
+
Footer: CardFooter,
|
|
120
|
+
Icon: CardIcon,
|
|
121
|
+
Title: CardTitle,
|
|
122
|
+
Description: CardDescription
|
|
123
|
+
});
|
|
124
|
+
var CommandBox = React14.forwardRef(
|
|
125
|
+
({
|
|
126
|
+
className,
|
|
127
|
+
command,
|
|
128
|
+
prefix = "$",
|
|
129
|
+
showCopyButton = true,
|
|
130
|
+
variant = "default",
|
|
131
|
+
onCopy,
|
|
132
|
+
...props
|
|
133
|
+
}, ref) => {
|
|
134
|
+
const [copied, setCopied] = React14.useState(false);
|
|
135
|
+
const handleCopy = React14.useCallback(async () => {
|
|
136
|
+
try {
|
|
137
|
+
await navigator.clipboard.writeText(command);
|
|
138
|
+
setCopied(true);
|
|
139
|
+
onCopy?.();
|
|
140
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
141
|
+
} catch (_err) {
|
|
142
|
+
}
|
|
143
|
+
}, [command, onCopy]);
|
|
144
|
+
const variantStyles3 = {
|
|
145
|
+
default: "bg-[var(--neutral-2)] border border-[var(--neutral-7)] rounded-[var(--radius-lg)]",
|
|
146
|
+
minimal: "bg-[var(--neutral-1)] border border-[var(--neutral-7)] rounded-[var(--radius-md)]",
|
|
147
|
+
pill: "bg-[var(--neutral-2)] border border-[var(--neutral-7)] rounded-full"
|
|
148
|
+
};
|
|
149
|
+
return (
|
|
150
|
+
// biome-ignore lint/a11y/useSemanticElements: ARIA pattern
|
|
151
|
+
/* @__PURE__ */ jsxs(
|
|
152
|
+
"div",
|
|
153
|
+
{
|
|
154
|
+
ref,
|
|
155
|
+
className: cn(
|
|
156
|
+
"group relative flex items-center gap-3 px-4 py-2.5 font-mono text-sm cursor-pointer transition-all hover:bg-[var(--neutral-3)]",
|
|
157
|
+
variantStyles3[variant],
|
|
158
|
+
className
|
|
159
|
+
),
|
|
160
|
+
onClick: handleCopy,
|
|
161
|
+
role: "button",
|
|
162
|
+
tabIndex: 0,
|
|
163
|
+
onKeyDown: (e) => {
|
|
164
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
165
|
+
e.preventDefault();
|
|
166
|
+
handleCopy();
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
"aria-label": `Copy command: ${command}`,
|
|
170
|
+
...props,
|
|
171
|
+
children: [
|
|
172
|
+
/* @__PURE__ */ jsx("span", { className: "text-emerald-400 shrink-0 select-none", children: prefix }),
|
|
173
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 text-[var(--neutral-11)] truncate", children: command }),
|
|
174
|
+
showCopyButton && /* @__PURE__ */ jsx(
|
|
175
|
+
"span",
|
|
176
|
+
{
|
|
177
|
+
className: cn(
|
|
178
|
+
"shrink-0 text-xs transition-colors",
|
|
179
|
+
copied ? "text-emerald-400" : "text-[var(--neutral-9)] group-hover:text-[var(--neutral-11)]"
|
|
180
|
+
),
|
|
181
|
+
children: copied ? /* @__PURE__ */ jsx(CopiedIcon, { className: "w-4 h-4" }) : /* @__PURE__ */ jsx(CopyIcon, { className: "w-4 h-4" })
|
|
182
|
+
}
|
|
183
|
+
),
|
|
184
|
+
/* @__PURE__ */ jsx(
|
|
185
|
+
"span",
|
|
186
|
+
{
|
|
187
|
+
className: cn(
|
|
188
|
+
"absolute -top-8 left-1/2 -translate-x-1/2 px-2 py-1 text-xs rounded bg-zinc-700 text-[var(--neutral-11)] opacity-0 transition-opacity pointer-events-none",
|
|
189
|
+
"group-hover:opacity-100",
|
|
190
|
+
copied && "opacity-100"
|
|
191
|
+
),
|
|
192
|
+
children: copied ? "Copied!" : "Click to copy"
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
)
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
CommandBox.displayName = "CommandBox";
|
|
202
|
+
function CopyIcon({ className }) {
|
|
203
|
+
return /* @__PURE__ */ jsxs(
|
|
204
|
+
"svg",
|
|
205
|
+
{
|
|
206
|
+
"aria-hidden": "true",
|
|
207
|
+
className,
|
|
208
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
209
|
+
viewBox: "0 0 24 24",
|
|
210
|
+
fill: "none",
|
|
211
|
+
stroke: "currentColor",
|
|
212
|
+
strokeWidth: "2",
|
|
213
|
+
strokeLinecap: "round",
|
|
214
|
+
strokeLinejoin: "round",
|
|
215
|
+
children: [
|
|
216
|
+
/* @__PURE__ */ jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
|
|
217
|
+
/* @__PURE__ */ jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
|
|
218
|
+
]
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
function CopiedIcon({ className }) {
|
|
223
|
+
return /* @__PURE__ */ jsx(
|
|
224
|
+
"svg",
|
|
225
|
+
{
|
|
226
|
+
"aria-hidden": "true",
|
|
227
|
+
className,
|
|
228
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
229
|
+
viewBox: "0 0 24 24",
|
|
230
|
+
fill: "none",
|
|
231
|
+
stroke: "currentColor",
|
|
232
|
+
strokeWidth: "2",
|
|
233
|
+
strokeLinecap: "round",
|
|
234
|
+
strokeLinejoin: "round",
|
|
235
|
+
children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" })
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
var badgeVariants = cva(
|
|
240
|
+
"inline-flex justify-center items-center align-middle shrink-0 rounded-full font-sans font-medium whitespace-nowrap tabular-nums transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 border border-transparent",
|
|
241
|
+
{
|
|
242
|
+
variants: {
|
|
243
|
+
variant: {
|
|
244
|
+
// ─── Semantic / brand variants ────────────────────────────────────────
|
|
245
|
+
default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
|
246
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
247
|
+
destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
|
248
|
+
outline: "border-border text-foreground",
|
|
249
|
+
success: "border-transparent bg-success text-success-foreground hover:bg-success/80",
|
|
250
|
+
warning: "border-transparent bg-warning text-warning-foreground hover:bg-warning/80",
|
|
251
|
+
info: "border-transparent bg-info text-info-foreground hover:bg-info/80",
|
|
252
|
+
error: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
|
253
|
+
// ─── Geist-style color palette ────────────────────────────────────────
|
|
254
|
+
gray: "bg-geist-gray-700 text-white fill-white",
|
|
255
|
+
"gray-subtle": "bg-geist-gray-200 text-geist-gray-1000 fill-geist-gray-1000 border-transparent",
|
|
256
|
+
blue: "bg-blue-700 text-white fill-white",
|
|
257
|
+
"blue-subtle": "bg-blue-200 text-blue-900 fill-blue-900 border-transparent",
|
|
258
|
+
purple: "bg-purple-700 text-white fill-white",
|
|
259
|
+
"purple-subtle": "bg-purple-200 text-purple-900 fill-purple-900 border-transparent",
|
|
260
|
+
amber: "bg-amber-700 text-black fill-black",
|
|
261
|
+
"amber-subtle": "bg-amber-200 text-amber-900 fill-amber-900 border-transparent",
|
|
262
|
+
red: "bg-red-700 text-white fill-white",
|
|
263
|
+
"red-subtle": "bg-red-200 text-red-900 fill-red-900 border-transparent",
|
|
264
|
+
pink: "bg-pink-700 text-white fill-white",
|
|
265
|
+
"pink-subtle": "bg-pink-300 text-pink-900 fill-pink-900 border-transparent",
|
|
266
|
+
green: "bg-green-700 text-white fill-white",
|
|
267
|
+
"green-subtle": "bg-green-200 text-green-900 fill-green-900 border-transparent",
|
|
268
|
+
teal: "bg-teal-700 text-white fill-white",
|
|
269
|
+
"teal-subtle": "bg-teal-300 text-teal-900 fill-teal-900 border-transparent",
|
|
270
|
+
inverted: "bg-geist-gray-1000 text-geist-gray-100 fill-geist-gray-100",
|
|
271
|
+
trial: "bg-gradient-to-br from-trial-start to-trial-end text-white fill-white",
|
|
272
|
+
turbo: "bg-gradient-to-br from-turbo-start to-turbo-end text-white fill-white",
|
|
273
|
+
pill: "bg-background text-foreground fill-foreground !border-gray-alpha-400 hover:bg-muted/50 focus-visible:bg-muted/50 dark:!border-border",
|
|
274
|
+
// ─── Semantic tone variants (nav badges, ownership, featured) ─────────
|
|
275
|
+
beta: "border-transparent bg-muted text-muted-foreground",
|
|
276
|
+
new: "border-transparent bg-blue-100 text-blue-700 dark:bg-blue-950/50 dark:text-blue-300",
|
|
277
|
+
owner: "border-border bg-transparent text-foreground",
|
|
278
|
+
featured: "border-transparent text-white bg-[image:var(--brand-gradient)] [text-shadow:0_0_1px_rgba(0,0,0,0.2)]",
|
|
279
|
+
"coming-soon": "border-transparent bg-muted/60 text-muted-foreground italic"
|
|
280
|
+
},
|
|
281
|
+
size: {
|
|
282
|
+
sm: "text-[11px] h-5 px-1.5 tracking-[0.2px] gap-[3px] [&_svg]:h-[11px] [&_svg]:w-[11px]",
|
|
283
|
+
md: "text-[12px] h-6 px-2.5 tracking-normal gap-1 [&_svg]:h-[14px] [&_svg]:w-[14px]",
|
|
284
|
+
lg: "text-[14px] h-8 px-3 tracking-normal gap-1.5 [&_svg]:h-[16px] [&_svg]:w-[16px]"
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
defaultVariants: {
|
|
288
|
+
variant: "default",
|
|
289
|
+
size: "md"
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
);
|
|
293
|
+
var dotColorMap = {
|
|
294
|
+
success: "bg-success-foreground",
|
|
295
|
+
warning: "bg-warning-foreground",
|
|
296
|
+
info: "bg-info-foreground",
|
|
297
|
+
error: "bg-destructive-foreground",
|
|
298
|
+
destructive: "bg-destructive-foreground",
|
|
299
|
+
"gray-subtle": "bg-geist-gray-500",
|
|
300
|
+
"green-subtle": "bg-green-500",
|
|
301
|
+
"amber-subtle": "bg-amber-500",
|
|
302
|
+
"red-subtle": "bg-red-500",
|
|
303
|
+
"blue-subtle": "bg-blue-500",
|
|
304
|
+
"purple-subtle": "bg-purple-500",
|
|
305
|
+
"pink-subtle": "bg-pink-500",
|
|
306
|
+
"teal-subtle": "bg-teal-500",
|
|
307
|
+
default: "bg-primary-foreground",
|
|
308
|
+
secondary: "bg-secondary-foreground",
|
|
309
|
+
outline: "bg-muted-foreground"
|
|
310
|
+
};
|
|
311
|
+
function Badge({
|
|
312
|
+
className,
|
|
313
|
+
variant = "default",
|
|
314
|
+
size = "md",
|
|
315
|
+
dot,
|
|
316
|
+
icon,
|
|
317
|
+
asChild,
|
|
318
|
+
children,
|
|
319
|
+
...props
|
|
320
|
+
}) {
|
|
321
|
+
const badgeClassName = cn(badgeVariants({ variant, size }), className);
|
|
322
|
+
const renderContent = (label) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
323
|
+
dot && /* @__PURE__ */ jsx(
|
|
324
|
+
"span",
|
|
325
|
+
{
|
|
326
|
+
"aria-hidden": "true",
|
|
327
|
+
className: cn(
|
|
328
|
+
"h-1.5 w-1.5 shrink-0 rounded-full",
|
|
329
|
+
dotColorMap[variant ?? "default"] ?? "bg-current"
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
),
|
|
333
|
+
icon && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "shrink-0 flex items-center justify-center", children: icon }),
|
|
334
|
+
label
|
|
335
|
+
] });
|
|
336
|
+
if (asChild) {
|
|
337
|
+
const childArray = React14.Children.toArray(children);
|
|
338
|
+
const child = childArray.length === 1 ? childArray[0] : null;
|
|
339
|
+
if (React14.isValidElement(child)) {
|
|
340
|
+
const childProps = child.props;
|
|
341
|
+
return React14.cloneElement(child, {
|
|
342
|
+
...props,
|
|
343
|
+
...childProps,
|
|
344
|
+
"data-slot": "badge",
|
|
345
|
+
className: cn(badgeClassName, childProps.className),
|
|
346
|
+
style: { ...props.style, ...childProps.style },
|
|
347
|
+
children: renderContent(childProps.children)
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return /* @__PURE__ */ jsx("span", { "data-slot": "badge", className: badgeClassName, ...props, children: renderContent(children) });
|
|
352
|
+
}
|
|
353
|
+
var DropdownMenu = Menu.Root;
|
|
354
|
+
var DropdownMenuTrigger = React14.forwardRef(
|
|
355
|
+
({ asChild, children, render, ...props }, ref) => {
|
|
356
|
+
const renderElement = asChild && React14.isValidElement(children) ? children : render;
|
|
357
|
+
return /* @__PURE__ */ jsx(
|
|
358
|
+
Menu.Trigger,
|
|
359
|
+
{
|
|
360
|
+
ref,
|
|
361
|
+
render: renderElement,
|
|
362
|
+
...renderElement ? props : { ...props, children }
|
|
363
|
+
}
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
);
|
|
367
|
+
DropdownMenuTrigger.displayName = "DropdownMenuTrigger";
|
|
368
|
+
Menu.Group;
|
|
369
|
+
Menu.Portal;
|
|
370
|
+
Menu.SubmenuRoot;
|
|
371
|
+
Menu.RadioGroup;
|
|
372
|
+
var DropdownMenuSubTrigger = React14.forwardRef(({ className, inset, children, ...props }, ref) => /* @__PURE__ */ jsxs(
|
|
373
|
+
Menu.SubmenuTrigger,
|
|
374
|
+
{
|
|
375
|
+
ref,
|
|
376
|
+
className: cn(
|
|
377
|
+
"flex cursor-default select-none items-center rounded-[var(--radius-md)] px-2 py-1.5 text-sm outline-none focus:bg-accent data-[popup-open]:bg-accent",
|
|
378
|
+
inset && "pl-8",
|
|
379
|
+
className
|
|
380
|
+
),
|
|
381
|
+
...props,
|
|
382
|
+
children: [
|
|
383
|
+
children,
|
|
384
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: "ml-auto size-4" })
|
|
385
|
+
]
|
|
386
|
+
}
|
|
387
|
+
));
|
|
388
|
+
DropdownMenuSubTrigger.displayName = "DropdownMenuSubTrigger";
|
|
389
|
+
var DropdownMenuSubContent = React14.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(Menu.Portal, { children: /* @__PURE__ */ jsx(Menu.Positioner, { children: /* @__PURE__ */ jsx(
|
|
390
|
+
Menu.Popup,
|
|
391
|
+
{
|
|
392
|
+
ref,
|
|
393
|
+
className: cn(
|
|
394
|
+
"z-50 min-w-32 overflow-hidden rounded-xl border bg-background/90 backdrop-blur-md p-1 text-popover-foreground shadow-xl transition-[opacity,transform,display] duration-flow ease-out data-[starting-style]:zoom-out-95 data-[ending-style]:zoom-out-95 data-[starting-style]:fade-out-0 data-[ending-style]:fade-out-0",
|
|
395
|
+
className
|
|
396
|
+
),
|
|
397
|
+
...props
|
|
398
|
+
}
|
|
399
|
+
) }) }));
|
|
400
|
+
DropdownMenuSubContent.displayName = "DropdownMenuSubContent";
|
|
401
|
+
var DropdownMenuContent = React14.forwardRef(
|
|
402
|
+
({ className, sideOffset = 4, align = "center", alignOffset = 0, side = "bottom", ...props }, ref) => /* @__PURE__ */ jsx(Menu.Portal, { children: /* @__PURE__ */ jsx(
|
|
403
|
+
Menu.Positioner,
|
|
404
|
+
{
|
|
405
|
+
sideOffset,
|
|
406
|
+
align,
|
|
407
|
+
alignOffset,
|
|
408
|
+
side,
|
|
409
|
+
children: /* @__PURE__ */ jsx(
|
|
410
|
+
Menu.Popup,
|
|
411
|
+
{
|
|
412
|
+
ref,
|
|
413
|
+
className: cn(
|
|
414
|
+
"z-50 min-w-32 overflow-hidden rounded-xl border bg-background/90 backdrop-blur-md p-1 text-popover-foreground shadow-xl transition-[opacity,transform,display] duration-flow ease-out outline-none data-[starting-style]:zoom-out-95 data-[ending-style]:zoom-out-95 data-[starting-style]:fade-out-0 data-[ending-style]:fade-out-0",
|
|
415
|
+
className
|
|
416
|
+
),
|
|
417
|
+
...props
|
|
418
|
+
}
|
|
419
|
+
)
|
|
420
|
+
}
|
|
421
|
+
) })
|
|
422
|
+
);
|
|
423
|
+
DropdownMenuContent.displayName = "DropdownMenuContent";
|
|
424
|
+
var DropdownMenuItem = React14.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
425
|
+
Menu.Item,
|
|
426
|
+
{
|
|
427
|
+
ref,
|
|
428
|
+
className: cn(
|
|
429
|
+
"relative flex cursor-default select-none items-center rounded-[var(--radius-md)] px-2 py-1.5 text-sm outline-none transition-colors data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
430
|
+
inset && "pl-8",
|
|
431
|
+
className
|
|
432
|
+
),
|
|
433
|
+
...props
|
|
434
|
+
}
|
|
435
|
+
));
|
|
436
|
+
DropdownMenuItem.displayName = "DropdownMenuItem";
|
|
437
|
+
var DropdownMenuCheckboxItem = React14.forwardRef(({ className, children, checked, ...props }, ref) => /* @__PURE__ */ jsxs(
|
|
438
|
+
Menu.CheckboxItem,
|
|
439
|
+
{
|
|
440
|
+
ref,
|
|
441
|
+
className: cn(
|
|
442
|
+
"relative flex cursor-default select-none items-center rounded-[var(--radius-md)] py-1.5 pl-8 pr-2 text-sm outline-none transition-colors data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
443
|
+
className
|
|
444
|
+
),
|
|
445
|
+
...checked !== void 0 && { checked },
|
|
446
|
+
...props,
|
|
447
|
+
children: [
|
|
448
|
+
/* @__PURE__ */ jsx("span", { className: "absolute left-2 flex size-3.5 items-center justify-center", children: /* @__PURE__ */ jsx(Menu.CheckboxItemIndicator, { children: /* @__PURE__ */ jsx(Check, { className: "size-4" }) }) }),
|
|
449
|
+
children
|
|
450
|
+
]
|
|
451
|
+
}
|
|
452
|
+
));
|
|
453
|
+
DropdownMenuCheckboxItem.displayName = "DropdownMenuCheckboxItem";
|
|
454
|
+
var DropdownMenuRadioItem = React14.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs(
|
|
455
|
+
Menu.RadioItem,
|
|
456
|
+
{
|
|
457
|
+
ref,
|
|
458
|
+
className: cn(
|
|
459
|
+
"relative flex cursor-default select-none items-center rounded-[var(--radius-md)] py-1.5 pl-8 pr-2 text-sm outline-none transition-colors data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
460
|
+
className
|
|
461
|
+
),
|
|
462
|
+
...props,
|
|
463
|
+
children: [
|
|
464
|
+
/* @__PURE__ */ jsx("span", { className: "absolute left-2 flex size-3.5 items-center justify-center", children: /* @__PURE__ */ jsx(Menu.RadioItemIndicator, { children: /* @__PURE__ */ jsx(Status, { className: "size-2 fill-current" }) }) }),
|
|
465
|
+
children
|
|
466
|
+
]
|
|
467
|
+
}
|
|
468
|
+
));
|
|
469
|
+
DropdownMenuRadioItem.displayName = "DropdownMenuRadioItem";
|
|
470
|
+
var DropdownMenuLabel = React14.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
471
|
+
Menu.GroupLabel,
|
|
472
|
+
{
|
|
473
|
+
ref,
|
|
474
|
+
className: cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className),
|
|
475
|
+
...props
|
|
476
|
+
}
|
|
477
|
+
));
|
|
478
|
+
DropdownMenuLabel.displayName = "DropdownMenuLabel";
|
|
479
|
+
var DropdownMenuSeparator = React14.forwardRef(({ className, orientation = "horizontal", ...props }, ref) => /* @__PURE__ */ jsx(
|
|
480
|
+
Menu.Separator,
|
|
481
|
+
{
|
|
482
|
+
ref,
|
|
483
|
+
orientation,
|
|
484
|
+
className: cn("-mx-1 my-1 h-px bg-muted", className),
|
|
485
|
+
...props
|
|
486
|
+
}
|
|
487
|
+
));
|
|
488
|
+
DropdownMenuSeparator.displayName = "DropdownMenuSeparator";
|
|
489
|
+
var iconToneStyles = {
|
|
490
|
+
neutral: "bg-muted text-muted-foreground",
|
|
491
|
+
blue: "bg-blue-50 text-blue-700 dark:bg-blue-950/40 dark:text-blue-300",
|
|
492
|
+
cyan: "bg-cyan-50 text-cyan-700 dark:bg-cyan-950/40 dark:text-cyan-300",
|
|
493
|
+
purple: "bg-purple-50 text-purple-700 dark:bg-purple-950/40 dark:text-purple-300",
|
|
494
|
+
amber: "bg-amber-50 text-amber-700 dark:bg-amber-950/40 dark:text-amber-300",
|
|
495
|
+
green: "bg-green-50 text-green-700 dark:bg-green-950/40 dark:text-green-300",
|
|
496
|
+
pink: "bg-pink-50 text-pink-700 dark:bg-pink-950/40 dark:text-pink-300"
|
|
497
|
+
};
|
|
498
|
+
var badgeToneToVariant = {
|
|
499
|
+
beta: "beta",
|
|
500
|
+
new: "new",
|
|
501
|
+
owner: "owner",
|
|
502
|
+
featured: "featured",
|
|
503
|
+
"coming-soon": "coming-soon"
|
|
504
|
+
};
|
|
505
|
+
var cardClassName = cn(
|
|
506
|
+
"group relative block rounded-xl border border-border bg-card text-card-foreground p-5 text-left",
|
|
507
|
+
"transition-all duration-150",
|
|
508
|
+
"hover:border-neutral-7 hover:shadow-sm hover:-translate-y-px",
|
|
509
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
510
|
+
);
|
|
511
|
+
function GalleryCardActionsMenu({ actions, title }) {
|
|
512
|
+
const stop = React14.useCallback((event) => {
|
|
513
|
+
event.stopPropagation();
|
|
514
|
+
}, []);
|
|
515
|
+
return /* @__PURE__ */ jsx("div", { onClickCapture: stop, onPointerDownCapture: stop, children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
516
|
+
/* @__PURE__ */ jsx(
|
|
517
|
+
DropdownMenuTrigger,
|
|
518
|
+
{
|
|
519
|
+
render: /* @__PURE__ */ jsx(
|
|
520
|
+
"button",
|
|
521
|
+
{
|
|
522
|
+
type: "button",
|
|
523
|
+
"aria-label": `Actions for ${title}`,
|
|
524
|
+
className: cn(
|
|
525
|
+
"inline-flex h-7 w-7 items-center justify-center rounded-md",
|
|
526
|
+
"text-muted-foreground transition-colors",
|
|
527
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
528
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
|
|
529
|
+
),
|
|
530
|
+
children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "h-4 w-4" })
|
|
531
|
+
}
|
|
532
|
+
)
|
|
533
|
+
}
|
|
534
|
+
),
|
|
535
|
+
/* @__PURE__ */ jsx(DropdownMenuContent, { align: "end", sideOffset: 6, children: actions.map((action) => /* @__PURE__ */ jsx(
|
|
536
|
+
DropdownMenuItem,
|
|
537
|
+
{
|
|
538
|
+
onClick: (event) => {
|
|
539
|
+
event.stopPropagation();
|
|
540
|
+
action.onSelect();
|
|
541
|
+
},
|
|
542
|
+
className: cn(
|
|
543
|
+
action.destructive && "text-destructive data-[highlighted]:bg-destructive/10 data-[highlighted]:text-destructive"
|
|
544
|
+
),
|
|
545
|
+
children: action.label
|
|
546
|
+
},
|
|
547
|
+
action.id
|
|
548
|
+
)) })
|
|
549
|
+
] }) });
|
|
550
|
+
}
|
|
551
|
+
function GalleryCardBody({
|
|
552
|
+
title,
|
|
553
|
+
description,
|
|
554
|
+
icon,
|
|
555
|
+
iconTone,
|
|
556
|
+
badge,
|
|
557
|
+
pinned,
|
|
558
|
+
metadata,
|
|
559
|
+
actions
|
|
560
|
+
}) {
|
|
561
|
+
const hasActions = Boolean(actions && actions.length > 0);
|
|
562
|
+
const hasMetadataStrip = Boolean(metadata?.author || metadata?.metric || metadata?.trailing);
|
|
563
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
564
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-start justify-between gap-2", children: [
|
|
565
|
+
icon ? /* @__PURE__ */ jsx(
|
|
566
|
+
"div",
|
|
567
|
+
{
|
|
568
|
+
className: cn(
|
|
569
|
+
"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg text-base",
|
|
570
|
+
iconToneStyles[iconTone]
|
|
571
|
+
),
|
|
572
|
+
"aria-hidden": "true",
|
|
573
|
+
children: icon
|
|
574
|
+
}
|
|
575
|
+
) : /* @__PURE__ */ jsx("span", { className: "h-10 w-10 shrink-0", "aria-hidden": "true" }),
|
|
576
|
+
(pinned || hasActions) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
577
|
+
pinned && /* @__PURE__ */ jsx(Pin, { className: "h-3.5 w-3.5 fill-amber-400 text-amber-500", "aria-label": "Pinned" }),
|
|
578
|
+
hasActions && actions ? /* @__PURE__ */ jsx(GalleryCardActionsMenu, { actions, title }) : null
|
|
579
|
+
] })
|
|
580
|
+
] }),
|
|
581
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-1.5 flex items-center gap-2", children: [
|
|
582
|
+
/* @__PURE__ */ jsx("h3", { className: "line-clamp-1 text-[15px] font-semibold leading-tight", children: title }),
|
|
583
|
+
badge && /* @__PURE__ */ jsx(Badge, { variant: badgeToneToVariant[badge.tone], size: "sm", children: badge.label })
|
|
584
|
+
] }),
|
|
585
|
+
/* @__PURE__ */ jsx("p", { className: "mb-4 line-clamp-2 min-h-[2.5rem] text-sm text-muted-foreground", children: description ?? "\xA0" }),
|
|
586
|
+
hasMetadataStrip && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-t border-border pt-3 text-xs text-muted-foreground", children: [
|
|
587
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: metadata?.author ?? "" }),
|
|
588
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
589
|
+
metadata?.metric && /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: metadata.metric }),
|
|
590
|
+
metadata?.trailing
|
|
591
|
+
] })
|
|
592
|
+
] })
|
|
593
|
+
] });
|
|
594
|
+
}
|
|
595
|
+
function GalleryCard(props) {
|
|
596
|
+
const {
|
|
597
|
+
title,
|
|
598
|
+
description,
|
|
599
|
+
icon,
|
|
600
|
+
iconTone = "neutral",
|
|
601
|
+
badge,
|
|
602
|
+
pinned,
|
|
603
|
+
metadata,
|
|
604
|
+
actions,
|
|
605
|
+
onClick,
|
|
606
|
+
href,
|
|
607
|
+
renderLink,
|
|
608
|
+
className
|
|
609
|
+
} = props;
|
|
610
|
+
const body = /* @__PURE__ */ jsx(
|
|
611
|
+
GalleryCardBody,
|
|
612
|
+
{
|
|
613
|
+
title,
|
|
614
|
+
description,
|
|
615
|
+
icon,
|
|
616
|
+
iconTone,
|
|
617
|
+
badge,
|
|
618
|
+
pinned,
|
|
619
|
+
metadata,
|
|
620
|
+
actions
|
|
621
|
+
}
|
|
622
|
+
);
|
|
623
|
+
const composedClassName = cn(cardClassName, "cursor-pointer", className);
|
|
624
|
+
if (href) {
|
|
625
|
+
if (renderLink) {
|
|
626
|
+
return renderLink({
|
|
627
|
+
href,
|
|
628
|
+
className: composedClassName,
|
|
629
|
+
"aria-label": title,
|
|
630
|
+
children: body
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
return /* @__PURE__ */ jsx("a", { href, "aria-label": title, className: composedClassName, onClick, children: body });
|
|
634
|
+
}
|
|
635
|
+
if (onClick) {
|
|
636
|
+
return /* @__PURE__ */ jsx("button", { type: "button", "aria-label": title, className: composedClassName, onClick, children: body });
|
|
637
|
+
}
|
|
638
|
+
return /* @__PURE__ */ jsx("div", { className: cn(cardClassName, "cursor-default", className), children: body });
|
|
639
|
+
}
|
|
640
|
+
GalleryCard.displayName = "GalleryCard";
|
|
641
|
+
var Slot = React14.forwardRef(
|
|
642
|
+
({ children, className, style, ...props }, ref) => {
|
|
643
|
+
const childArray = React14.Children.toArray(children);
|
|
644
|
+
const child = childArray.length === 1 ? childArray[0] : null;
|
|
645
|
+
if (child == null || !React14.isValidElement(child)) {
|
|
646
|
+
return /* @__PURE__ */ jsx("span", { ref, className, style, ...props, children });
|
|
647
|
+
}
|
|
648
|
+
const childProps = child.props;
|
|
649
|
+
const mergedRef = (node) => {
|
|
650
|
+
if (typeof ref === "function") ref(node);
|
|
651
|
+
else if (ref) ref.current = node;
|
|
652
|
+
const childRef = child.ref;
|
|
653
|
+
if (typeof childRef === "function") childRef(node);
|
|
654
|
+
else if (childRef && "current" in childRef) {
|
|
655
|
+
childRef.current = node;
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
const mergedClass = [className, childProps.className].filter(Boolean).join(" ");
|
|
659
|
+
const merged = {
|
|
660
|
+
...childProps,
|
|
661
|
+
...props,
|
|
662
|
+
ref: mergedRef,
|
|
663
|
+
className: mergedClass,
|
|
664
|
+
style: { ...childProps.style, ...style }
|
|
665
|
+
};
|
|
666
|
+
return React14.cloneElement(
|
|
667
|
+
child,
|
|
668
|
+
merged
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
);
|
|
672
|
+
Slot.displayName = "Slot";
|
|
673
|
+
var buttonVariants = cva(
|
|
674
|
+
[
|
|
675
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap",
|
|
676
|
+
"rounded-[var(--radius-md)] text-sm font-medium",
|
|
677
|
+
"transition-colors duration-150 ease-out",
|
|
678
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
679
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
680
|
+
"aria-busy:cursor-wait"
|
|
681
|
+
].join(" "),
|
|
682
|
+
{
|
|
683
|
+
variants: {
|
|
684
|
+
variant: {
|
|
685
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
686
|
+
ink: "bg-[var(--neutral-12)] text-[var(--neutral-1)] ring-1 ring-inset ring-white/[0.04] hover:bg-[var(--neutral-11)] hover:-translate-y-px active:translate-y-0 transition-[transform,background-color] duration-150",
|
|
687
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
688
|
+
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
689
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
690
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
691
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
692
|
+
tertiary: "border border-transparent bg-transparent text-muted-foreground hover:bg-accent hover:text-accent-foreground hover:border-input",
|
|
693
|
+
warning: "bg-warning text-warning-foreground hover:bg-warning/90"
|
|
694
|
+
},
|
|
695
|
+
size: {
|
|
696
|
+
tiny: "h-6 rounded-[var(--radius-sm)] px-2 text-[11px]",
|
|
697
|
+
// 24px
|
|
698
|
+
sm: "h-8 rounded-[var(--radius-sm)] px-3 text-xs",
|
|
699
|
+
// 32px
|
|
700
|
+
default: "h-10 px-4 py-2",
|
|
701
|
+
// 40px
|
|
702
|
+
lg: "h-12 rounded-[var(--radius-lg)] px-5 text-base",
|
|
703
|
+
// 48px
|
|
704
|
+
icon: "h-10 w-10"
|
|
705
|
+
// 40×40px (kept for backward compat)
|
|
706
|
+
},
|
|
707
|
+
shape: {
|
|
708
|
+
default: "",
|
|
709
|
+
square: "",
|
|
710
|
+
circle: ""
|
|
711
|
+
}
|
|
712
|
+
},
|
|
713
|
+
compoundVariants: [
|
|
714
|
+
// shape="square" — width = height, no horizontal padding
|
|
715
|
+
{ shape: "square", size: "tiny", className: "w-6 px-0" },
|
|
716
|
+
{ shape: "square", size: "sm", className: "w-8 px-0" },
|
|
717
|
+
{ shape: "square", size: "default", className: "w-10 px-0" },
|
|
718
|
+
{ shape: "square", size: "lg", className: "w-12 px-0" },
|
|
719
|
+
// shape="circle" — width = height + rounded-full
|
|
720
|
+
{ shape: "circle", size: "tiny", className: "w-6 px-0 rounded-full" },
|
|
721
|
+
{ shape: "circle", size: "sm", className: "w-8 px-0 rounded-full" },
|
|
722
|
+
{ shape: "circle", size: "default", className: "w-10 px-0 rounded-full" },
|
|
723
|
+
{ shape: "circle", size: "lg", className: "w-12 px-0 rounded-full" }
|
|
724
|
+
],
|
|
725
|
+
defaultVariants: {
|
|
726
|
+
variant: "default",
|
|
727
|
+
size: "default",
|
|
728
|
+
shape: "default"
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
);
|
|
732
|
+
function Spinner({ className }) {
|
|
733
|
+
return /* @__PURE__ */ jsx(
|
|
734
|
+
"span",
|
|
735
|
+
{
|
|
736
|
+
"aria-hidden": "true",
|
|
737
|
+
className: cn(
|
|
738
|
+
"block rounded-full border-2 border-current border-t-transparent animate-[spinner_0.6s_linear_infinite]",
|
|
739
|
+
className
|
|
740
|
+
)
|
|
741
|
+
}
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
function getIconSizeClass(size) {
|
|
745
|
+
switch (size) {
|
|
746
|
+
case "tiny":
|
|
747
|
+
return "[&>svg]:size-3";
|
|
748
|
+
// 12px
|
|
749
|
+
case "sm":
|
|
750
|
+
return "[&>svg]:size-3.5";
|
|
751
|
+
// 14px
|
|
752
|
+
case "lg":
|
|
753
|
+
return "[&>svg]:size-4.5";
|
|
754
|
+
// 18px
|
|
755
|
+
default:
|
|
756
|
+
return "[&>svg]:size-4";
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
function getSpinnerSizeClass(size) {
|
|
760
|
+
switch (size) {
|
|
761
|
+
case "tiny":
|
|
762
|
+
return "size-3";
|
|
763
|
+
// 12px
|
|
764
|
+
case "sm":
|
|
765
|
+
return "size-3.5";
|
|
766
|
+
// 14px
|
|
767
|
+
case "lg":
|
|
768
|
+
return "size-4.5";
|
|
769
|
+
// 18px
|
|
770
|
+
default:
|
|
771
|
+
return "size-4";
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
var SHADOW_CLASSES = {
|
|
775
|
+
sm: "shadow-sm",
|
|
776
|
+
md: "shadow-md",
|
|
777
|
+
lg: "shadow-lg"
|
|
778
|
+
};
|
|
779
|
+
function resolveShadowClass(shadow) {
|
|
780
|
+
if (!shadow) return void 0;
|
|
781
|
+
const level = shadow === true ? "md" : shadow;
|
|
782
|
+
return SHADOW_CLASSES[level];
|
|
783
|
+
}
|
|
784
|
+
function ButtonContent({ loading, prefix, suffix, size, children }) {
|
|
785
|
+
const iconSizeClass = getIconSizeClass(size);
|
|
786
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
787
|
+
loading && /* @__PURE__ */ jsx(Spinner, { className: getSpinnerSizeClass(size) }),
|
|
788
|
+
prefix != null && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn("shrink-0", iconSizeClass), children: prefix }),
|
|
789
|
+
children,
|
|
790
|
+
suffix != null && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn("shrink-0", iconSizeClass), children: suffix })
|
|
791
|
+
] });
|
|
792
|
+
}
|
|
793
|
+
var Button = React14.forwardRef(
|
|
794
|
+
({
|
|
795
|
+
className,
|
|
796
|
+
variant,
|
|
797
|
+
size,
|
|
798
|
+
shape,
|
|
799
|
+
asChild = false,
|
|
800
|
+
loading = false,
|
|
801
|
+
disabled,
|
|
802
|
+
prefix,
|
|
803
|
+
suffix,
|
|
804
|
+
shadow,
|
|
805
|
+
children,
|
|
806
|
+
...props
|
|
807
|
+
}, ref) => {
|
|
808
|
+
const Comp = asChild ? Slot : "button";
|
|
809
|
+
const isDisabled = disabled ?? loading;
|
|
810
|
+
const shadowClass = resolveShadowClass(shadow);
|
|
811
|
+
return /* @__PURE__ */ jsx(
|
|
812
|
+
Comp,
|
|
813
|
+
{
|
|
814
|
+
ref,
|
|
815
|
+
className: cn(buttonVariants({ variant, size, shape }), shadowClass, className),
|
|
816
|
+
disabled: isDisabled,
|
|
817
|
+
"aria-busy": loading || void 0,
|
|
818
|
+
...props,
|
|
819
|
+
children: asChild ? children : /* @__PURE__ */ jsx(ButtonContent, { loading, prefix, suffix, size, children })
|
|
820
|
+
}
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
);
|
|
824
|
+
Button.displayName = "Button";
|
|
825
|
+
var ButtonLink = React14.forwardRef(
|
|
826
|
+
({
|
|
827
|
+
className,
|
|
828
|
+
variant,
|
|
829
|
+
size,
|
|
830
|
+
shape,
|
|
831
|
+
prefix,
|
|
832
|
+
suffix,
|
|
833
|
+
shadow,
|
|
834
|
+
loading = false,
|
|
835
|
+
children,
|
|
836
|
+
...props
|
|
837
|
+
}, ref) => {
|
|
838
|
+
const shadowClass = resolveShadowClass(shadow);
|
|
839
|
+
const loadingProps = loading ? {
|
|
840
|
+
"aria-busy": "true",
|
|
841
|
+
"aria-disabled": "true",
|
|
842
|
+
tabIndex: -1
|
|
843
|
+
} : {};
|
|
844
|
+
return /* @__PURE__ */ jsx(
|
|
845
|
+
"a",
|
|
846
|
+
{
|
|
847
|
+
ref,
|
|
848
|
+
className: cn(
|
|
849
|
+
buttonVariants({ variant, size, shape }),
|
|
850
|
+
shadowClass,
|
|
851
|
+
loading && "pointer-events-none opacity-50",
|
|
852
|
+
className
|
|
853
|
+
),
|
|
854
|
+
...loadingProps,
|
|
855
|
+
...props,
|
|
856
|
+
children: /* @__PURE__ */ jsx(ButtonContent, { loading, prefix, suffix, size, children })
|
|
857
|
+
}
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
);
|
|
861
|
+
ButtonLink.displayName = "ButtonLink";
|
|
862
|
+
({
|
|
863
|
+
// Nebutra Blue (云毓蓝) — technology & trust — VI: #0033FE
|
|
864
|
+
blue50: colors.primary[50],
|
|
865
|
+
blue100: colors.primary[100],
|
|
866
|
+
blue200: colors.primary[200],
|
|
867
|
+
blue300: colors.primary[300],
|
|
868
|
+
blue400: colors.primary[400],
|
|
869
|
+
blue500: colors.primary[500],
|
|
870
|
+
// brand primary
|
|
871
|
+
blue600: colors.primary[600],
|
|
872
|
+
blue700: colors.primary[700],
|
|
873
|
+
blue800: colors.primary[800],
|
|
874
|
+
blue900: colors.primary[900],
|
|
875
|
+
blue950: colors.primary[950],
|
|
876
|
+
// Nebutra Cyan (云毓青) — data flow & intelligence — VI: #0BF1C3
|
|
877
|
+
cyan50: colors.accent[50],
|
|
878
|
+
cyan100: colors.accent[100],
|
|
879
|
+
cyan200: colors.accent[200],
|
|
880
|
+
cyan300: colors.accent[300],
|
|
881
|
+
cyan400: colors.accent[400],
|
|
882
|
+
cyan500: colors.accent[500],
|
|
883
|
+
// brand secondary
|
|
884
|
+
cyan600: colors.accent[600],
|
|
885
|
+
cyan700: colors.accent[700],
|
|
886
|
+
cyan800: colors.accent[800],
|
|
887
|
+
cyan900: colors.accent[900],
|
|
888
|
+
cyan950: colors.accent[950],
|
|
889
|
+
// Neutral (blue-undertone gray scale)
|
|
890
|
+
neutral50: colors.neutral[50],
|
|
891
|
+
neutral100: colors.neutral[100],
|
|
892
|
+
neutral200: colors.neutral[200],
|
|
893
|
+
neutral300: colors.neutral[300],
|
|
894
|
+
neutral400: colors.neutral[400],
|
|
895
|
+
neutral500: colors.neutral[500],
|
|
896
|
+
neutral600: colors.neutral[600],
|
|
897
|
+
neutral700: colors.neutral[700],
|
|
898
|
+
neutral800: colors.neutral[800],
|
|
899
|
+
neutral900: colors.neutral[900],
|
|
900
|
+
neutral950: colors.neutral[950],
|
|
901
|
+
white: colors.white,
|
|
902
|
+
black: colors.black
|
|
903
|
+
});
|
|
904
|
+
({
|
|
905
|
+
/** Hero sections, primary CTA buttons, logo fills — VI signature gradient */
|
|
906
|
+
primary: colors.gradient.primary,
|
|
907
|
+
/** Hover states, secondary gradient elements */
|
|
908
|
+
reverse: colors.gradient.primaryReverse,
|
|
909
|
+
/** Vertical layout dividers, page section separators */
|
|
910
|
+
vertical: colors.gradient.primaryVertical,
|
|
911
|
+
/** Background halos, focus glow effects, radial emphasis */
|
|
912
|
+
radial: colors.gradient.primaryRadial});
|
|
913
|
+
var primitiveSpacing = {
|
|
914
|
+
1: 4,
|
|
915
|
+
2: 8,
|
|
916
|
+
3: 12,
|
|
917
|
+
4: 16,
|
|
918
|
+
5: 20,
|
|
919
|
+
20: 80,
|
|
920
|
+
24: 96
|
|
921
|
+
};
|
|
922
|
+
var primitiveSizing = {
|
|
923
|
+
/** Geist button-specific size (24px). Larger than xs by design. */
|
|
924
|
+
tiny: 24,
|
|
925
|
+
sm: 32,
|
|
926
|
+
md: 40,
|
|
927
|
+
lg: 48};
|
|
928
|
+
var primitiveRadius = {
|
|
929
|
+
md: 6,
|
|
930
|
+
// Geist default — slightly more rounded than 4px
|
|
931
|
+
lg: 8,
|
|
932
|
+
full: 9999
|
|
933
|
+
};
|
|
934
|
+
var primitiveFontSize = {
|
|
935
|
+
xs: 12,
|
|
936
|
+
sm: 14,
|
|
937
|
+
base: 16,
|
|
938
|
+
lg: 18};
|
|
939
|
+
var primitiveTransition = {
|
|
940
|
+
duration: {
|
|
941
|
+
// hover, focus, small state changes
|
|
942
|
+
flow: 200},
|
|
943
|
+
easing: {
|
|
944
|
+
inOut: "cubic-bezier(0.4, 0, 0.2, 1)"
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
var primitiveFocusRing = {
|
|
948
|
+
width: 2,
|
|
949
|
+
// px
|
|
950
|
+
offset: 2
|
|
951
|
+
// px — gap between element and ring
|
|
952
|
+
};
|
|
953
|
+
|
|
954
|
+
// src/tokens/components/tabs.ts
|
|
955
|
+
var tabsSizes = {
|
|
956
|
+
xs: {
|
|
957
|
+
height: primitiveSizing.tiny,
|
|
958
|
+
padding: primitiveSpacing[1],
|
|
959
|
+
paddingX: primitiveSpacing[2],
|
|
960
|
+
gap: primitiveSpacing[1],
|
|
961
|
+
triggerGap: primitiveSpacing[1],
|
|
962
|
+
iconSize: primitiveFontSize.sm,
|
|
963
|
+
fontSize: primitiveFontSize.xs,
|
|
964
|
+
minWidth: 56
|
|
965
|
+
},
|
|
966
|
+
sm: {
|
|
967
|
+
height: primitiveSizing.sm,
|
|
968
|
+
padding: primitiveSpacing[1],
|
|
969
|
+
paddingX: primitiveSpacing[3],
|
|
970
|
+
gap: primitiveSpacing[1],
|
|
971
|
+
triggerGap: primitiveSpacing[1] + primitiveSpacing[1] / 2,
|
|
972
|
+
iconSize: primitiveFontSize.sm,
|
|
973
|
+
fontSize: primitiveFontSize.xs,
|
|
974
|
+
minWidth: 64
|
|
975
|
+
},
|
|
976
|
+
md: {
|
|
977
|
+
height: primitiveSizing.md,
|
|
978
|
+
padding: primitiveSpacing[1],
|
|
979
|
+
paddingX: primitiveSpacing[3],
|
|
980
|
+
gap: primitiveSpacing[2],
|
|
981
|
+
triggerGap: primitiveSpacing[1] + primitiveSpacing[1] / 2,
|
|
982
|
+
iconSize: primitiveFontSize.base,
|
|
983
|
+
fontSize: primitiveFontSize.sm,
|
|
984
|
+
minWidth: 72
|
|
985
|
+
},
|
|
986
|
+
lg: {
|
|
987
|
+
height: primitiveSizing.lg,
|
|
988
|
+
padding: primitiveSpacing[1],
|
|
989
|
+
paddingX: primitiveSpacing[4],
|
|
990
|
+
gap: primitiveSpacing[2] + primitiveSpacing[1] / 2,
|
|
991
|
+
triggerGap: primitiveSpacing[2],
|
|
992
|
+
iconSize: primitiveFontSize.lg,
|
|
993
|
+
fontSize: primitiveFontSize.sm,
|
|
994
|
+
minWidth: 88
|
|
995
|
+
}
|
|
996
|
+
};
|
|
997
|
+
var tabsTokens = {
|
|
998
|
+
radius: primitiveRadius.lg,
|
|
999
|
+
triggerRadius: primitiveRadius.md,
|
|
1000
|
+
pillRadius: primitiveRadius.full,
|
|
1001
|
+
lineThickness: 2,
|
|
1002
|
+
panelGap: primitiveSpacing[3],
|
|
1003
|
+
badgeHeight: primitiveSpacing[5],
|
|
1004
|
+
badgePaddingX: primitiveSpacing[1] + primitiveSpacing[1] / 2,
|
|
1005
|
+
badgeFontSize: primitiveFontSize.xs,
|
|
1006
|
+
focusRingWidth: primitiveFocusRing.width,
|
|
1007
|
+
focusRingOffset: primitiveFocusRing.offset,
|
|
1008
|
+
motion: {
|
|
1009
|
+
duration: primitiveTransition.duration.flow,
|
|
1010
|
+
easing: primitiveTransition.easing.inOut
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
1013
|
+
|
|
1014
|
+
// src/utils/primitive-props.ts
|
|
1015
|
+
function withHtmlProps(Component) {
|
|
1016
|
+
return Component;
|
|
1017
|
+
}
|
|
1018
|
+
var DEFAULT_TOOLTIP_DELAY_MS = 150;
|
|
1019
|
+
var TooltipProvider = ({
|
|
1020
|
+
children,
|
|
1021
|
+
delayDuration = DEFAULT_TOOLTIP_DELAY_MS
|
|
1022
|
+
}) => {
|
|
1023
|
+
return /* @__PURE__ */ jsx(Tooltip$1.Provider, { delay: delayDuration, children });
|
|
1024
|
+
};
|
|
1025
|
+
var Tooltip = ({
|
|
1026
|
+
delayDuration,
|
|
1027
|
+
children,
|
|
1028
|
+
...props
|
|
1029
|
+
}) => {
|
|
1030
|
+
const delay = delayDuration ?? DEFAULT_TOOLTIP_DELAY_MS;
|
|
1031
|
+
return /* @__PURE__ */ jsx(Tooltip$1.Provider, { delay, children: /* @__PURE__ */ jsx(Tooltip$1.Root, { ...props, children }) });
|
|
1032
|
+
};
|
|
1033
|
+
var TooltipTrigger = React14.forwardRef(({ asChild, children, ...props }, ref) => {
|
|
1034
|
+
if (asChild && React14.isValidElement(children)) {
|
|
1035
|
+
return /* @__PURE__ */ jsx(
|
|
1036
|
+
Tooltip$1.Trigger,
|
|
1037
|
+
{
|
|
1038
|
+
ref,
|
|
1039
|
+
...props,
|
|
1040
|
+
render: children
|
|
1041
|
+
}
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
return /* @__PURE__ */ jsx(Tooltip$1.Trigger, { ref, ...props, children });
|
|
1045
|
+
});
|
|
1046
|
+
TooltipTrigger.displayName = "TooltipTrigger";
|
|
1047
|
+
var TooltipContent = React14.forwardRef(
|
|
1048
|
+
({ className, side = "top", align = "center", sideOffset = 4, alignOffset = 0, ...props }, ref) => /* @__PURE__ */ jsx(Tooltip$1.Portal, { children: /* @__PURE__ */ jsx(
|
|
1049
|
+
Tooltip$1.Positioner,
|
|
1050
|
+
{
|
|
1051
|
+
side,
|
|
1052
|
+
align,
|
|
1053
|
+
sideOffset,
|
|
1054
|
+
alignOffset,
|
|
1055
|
+
className: "z-50",
|
|
1056
|
+
children: /* @__PURE__ */ jsx(
|
|
1057
|
+
Tooltip$1.Popup,
|
|
1058
|
+
{
|
|
1059
|
+
ref,
|
|
1060
|
+
className: cn(
|
|
1061
|
+
"z-50 overflow-hidden rounded-xl border bg-background/90 backdrop-blur-md px-3 py-1.5 text-sm text-popover-foreground shadow-xl transition-[opacity,transform,display] duration-200 data-starting-style:animate-in data-starting-style:fade-in-0 data-starting-style:zoom-in-95 data-ending-style:animate-out data-ending-style:fade-out-0 data-ending-style:zoom-out-95 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",
|
|
1062
|
+
className
|
|
1063
|
+
),
|
|
1064
|
+
...props
|
|
1065
|
+
}
|
|
1066
|
+
)
|
|
1067
|
+
}
|
|
1068
|
+
) })
|
|
1069
|
+
);
|
|
1070
|
+
TooltipContent.displayName = "TooltipContent";
|
|
1071
|
+
var BaseRoot = withHtmlProps(Tabs$1.Root);
|
|
1072
|
+
var BaseList = withHtmlProps(Tabs$1.List);
|
|
1073
|
+
var BaseTrigger = withHtmlProps(Tabs$1.Tab);
|
|
1074
|
+
var BaseContent = withHtmlProps(Tabs$1.Panel);
|
|
1075
|
+
var BaseIndicator = withHtmlProps(Tabs$1.Indicator);
|
|
1076
|
+
var tabsListVariants = cva(
|
|
1077
|
+
"relative z-0 inline-flex max-w-full shrink-0 items-center overflow-x-auto overscroll-x-contain scrollbar-none",
|
|
1078
|
+
{
|
|
1079
|
+
variants: {
|
|
1080
|
+
variant: {
|
|
1081
|
+
default: "gap-[var(--tabs-gap)] rounded-[var(--tabs-radius)] border border-border bg-muted p-[var(--tabs-padding)] text-muted-foreground",
|
|
1082
|
+
button: "gap-[var(--tabs-gap)] text-muted-foreground",
|
|
1083
|
+
line: "gap-[calc(var(--tabs-gap)*3)] border-b border-border text-muted-foreground",
|
|
1084
|
+
secondary: "gap-[var(--tabs-gap)] rounded-[var(--tabs-radius)] bg-transparent text-muted-foreground"
|
|
1085
|
+
},
|
|
1086
|
+
shape: {
|
|
1087
|
+
default: "",
|
|
1088
|
+
pill: "[&_[role=tab]]:rounded-[var(--tabs-pill-radius)]"
|
|
1089
|
+
}
|
|
1090
|
+
},
|
|
1091
|
+
compoundVariants: [
|
|
1092
|
+
{
|
|
1093
|
+
variant: "default",
|
|
1094
|
+
shape: "pill",
|
|
1095
|
+
className: "rounded-[var(--tabs-pill-radius)]"
|
|
1096
|
+
},
|
|
1097
|
+
{
|
|
1098
|
+
variant: "button",
|
|
1099
|
+
shape: "pill",
|
|
1100
|
+
className: "[&_[role=tab]]:rounded-[var(--tabs-pill-radius)]"
|
|
1101
|
+
},
|
|
1102
|
+
{
|
|
1103
|
+
variant: "secondary",
|
|
1104
|
+
shape: "pill",
|
|
1105
|
+
className: "rounded-[var(--tabs-pill-radius)]"
|
|
1106
|
+
}
|
|
1107
|
+
],
|
|
1108
|
+
defaultVariants: {
|
|
1109
|
+
shape: "default",
|
|
1110
|
+
variant: "default"
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
);
|
|
1114
|
+
var tabsTriggerVariants = cva(
|
|
1115
|
+
"relative inline-flex h-[calc(var(--tabs-height)-var(--tabs-padding)*2)] min-w-[var(--tabs-trigger-min-width)] shrink-0 cursor-pointer items-center justify-center gap-[var(--tabs-trigger-gap)] whitespace-nowrap rounded-[var(--tabs-trigger-radius)] px-[var(--tabs-trigger-padding-x)] font-medium text-[length:var(--tabs-font-size)] outline-none transition-[background-color,border-color,box-shadow,color,opacity] duration-[var(--tabs-duration)] ease-[var(--tabs-easing)] focus-visible:ring-[length:var(--tabs-focus-ring-width)] focus-visible:ring-ring focus-visible:ring-offset-[length:var(--tabs-focus-ring-offset)] focus-visible:ring-offset-background disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 data-[disabled]:pointer-events-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 [&_svg]:size-[var(--tabs-icon-size)] [&_svg]:shrink-0",
|
|
1116
|
+
{
|
|
1117
|
+
variants: {
|
|
1118
|
+
variant: {
|
|
1119
|
+
default: "hover:text-foreground data-[active]:text-foreground",
|
|
1120
|
+
button: "border border-border bg-background text-muted-foreground shadow-[var(--shadow-xs)] hover:border-ring/40 hover:text-foreground data-[active]:border-ring/40 data-[active]:text-foreground",
|
|
1121
|
+
line: "min-w-0 rounded-none px-0 hover:text-foreground data-[active]:text-foreground",
|
|
1122
|
+
secondary: "hover:bg-muted hover:text-foreground data-[active]:bg-muted data-[active]:text-foreground"
|
|
1123
|
+
}
|
|
1124
|
+
},
|
|
1125
|
+
defaultVariants: {
|
|
1126
|
+
variant: "default"
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
);
|
|
1130
|
+
var tabsIndicatorVariants = cva(
|
|
1131
|
+
"pointer-events-none absolute transition-[left,top,width,height,bottom,background-color] duration-[var(--tabs-duration)] ease-[var(--tabs-easing)] motion-reduce:transition-none",
|
|
1132
|
+
{
|
|
1133
|
+
variants: {
|
|
1134
|
+
variant: {
|
|
1135
|
+
default: "z-[-1] rounded-[var(--tabs-trigger-radius)] bg-background shadow-[var(--shadow-xs)]",
|
|
1136
|
+
button: "z-[-1] rounded-[var(--tabs-trigger-radius)] bg-muted",
|
|
1137
|
+
line: "bottom-0 h-[var(--tabs-line-thickness)] rounded-[var(--tabs-pill-radius)] bg-foreground",
|
|
1138
|
+
secondary: "z-[-1] rounded-[var(--tabs-trigger-radius)] bg-muted"
|
|
1139
|
+
},
|
|
1140
|
+
shape: {
|
|
1141
|
+
default: "",
|
|
1142
|
+
pill: "rounded-[var(--tabs-pill-radius)]"
|
|
1143
|
+
}
|
|
1144
|
+
},
|
|
1145
|
+
defaultVariants: {
|
|
1146
|
+
shape: "default",
|
|
1147
|
+
variant: "default"
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
);
|
|
1151
|
+
var tabsContentVariants = cva(
|
|
1152
|
+
"mt-[var(--tabs-panel-gap)] text-[length:var(--tabs-font-size)] text-muted-foreground focus-visible:outline-none focus-visible:ring-[length:var(--tabs-focus-ring-width)] focus-visible:ring-ring focus-visible:ring-offset-[length:var(--tabs-focus-ring-offset)] focus-visible:ring-offset-background",
|
|
1153
|
+
{
|
|
1154
|
+
variants: {
|
|
1155
|
+
variant: {
|
|
1156
|
+
default: ""
|
|
1157
|
+
}
|
|
1158
|
+
},
|
|
1159
|
+
defaultVariants: {
|
|
1160
|
+
variant: "default"
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
);
|
|
1164
|
+
var TabsContext = React14.createContext({
|
|
1165
|
+
activationMode: "manual",
|
|
1166
|
+
disabled: false,
|
|
1167
|
+
shape: "default",
|
|
1168
|
+
size: "md",
|
|
1169
|
+
variant: "default"
|
|
1170
|
+
});
|
|
1171
|
+
function getTabsStyle(size, style) {
|
|
1172
|
+
const token = tabsSizes[size];
|
|
1173
|
+
return {
|
|
1174
|
+
"--tabs-height": `${token.height}px`,
|
|
1175
|
+
"--tabs-padding": `${token.padding}px`,
|
|
1176
|
+
"--tabs-gap": `${token.gap}px`,
|
|
1177
|
+
"--tabs-trigger-gap": `${token.triggerGap}px`,
|
|
1178
|
+
"--tabs-trigger-padding-x": `${token.paddingX}px`,
|
|
1179
|
+
"--tabs-trigger-min-width": `${token.minWidth}px`,
|
|
1180
|
+
"--tabs-icon-size": `${token.iconSize}px`,
|
|
1181
|
+
"--tabs-font-size": `${token.fontSize}px`,
|
|
1182
|
+
"--tabs-radius": `${tabsTokens.radius}px`,
|
|
1183
|
+
"--tabs-trigger-radius": `${tabsTokens.triggerRadius}px`,
|
|
1184
|
+
"--tabs-pill-radius": `${tabsTokens.pillRadius}px`,
|
|
1185
|
+
"--tabs-line-thickness": `${tabsTokens.lineThickness}px`,
|
|
1186
|
+
"--tabs-focus-ring-width": `${tabsTokens.focusRingWidth}px`,
|
|
1187
|
+
"--tabs-focus-ring-offset": `${tabsTokens.focusRingOffset}px`,
|
|
1188
|
+
"--tabs-panel-gap": `${tabsTokens.panelGap}px`,
|
|
1189
|
+
"--tabs-badge-height": `${tabsTokens.badgeHeight}px`,
|
|
1190
|
+
"--tabs-badge-padding-x": `${tabsTokens.badgePaddingX}px`,
|
|
1191
|
+
"--tabs-badge-font-size": `${tabsTokens.badgeFontSize}px`,
|
|
1192
|
+
"--tabs-duration": `${tabsTokens.motion.duration}ms`,
|
|
1193
|
+
"--tabs-easing": tabsTokens.motion.easing,
|
|
1194
|
+
...style
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
function getFirstEnabledValue(tabs) {
|
|
1198
|
+
return tabs?.find((tab) => !tab.disabled)?.value;
|
|
1199
|
+
}
|
|
1200
|
+
function Tabs({
|
|
1201
|
+
"aria-label": ariaLabel,
|
|
1202
|
+
activationMode = "manual",
|
|
1203
|
+
className,
|
|
1204
|
+
defaultValue,
|
|
1205
|
+
disabled = false,
|
|
1206
|
+
listClassName,
|
|
1207
|
+
onValueChange,
|
|
1208
|
+
orientation = "horizontal",
|
|
1209
|
+
selected,
|
|
1210
|
+
setSelected,
|
|
1211
|
+
shape = "default",
|
|
1212
|
+
size = "md",
|
|
1213
|
+
style,
|
|
1214
|
+
tabs,
|
|
1215
|
+
value,
|
|
1216
|
+
variant = "default",
|
|
1217
|
+
children,
|
|
1218
|
+
...props
|
|
1219
|
+
}) {
|
|
1220
|
+
const rootValue = selected ?? value;
|
|
1221
|
+
const rootDefaultValue = defaultValue ?? getFirstEnabledValue(tabs);
|
|
1222
|
+
const context = React14.useMemo(
|
|
1223
|
+
() => ({ activationMode, disabled, shape, size, variant }),
|
|
1224
|
+
[activationMode, disabled, shape, size, variant]
|
|
1225
|
+
);
|
|
1226
|
+
function handleValueChange(nextValue) {
|
|
1227
|
+
onValueChange?.(nextValue);
|
|
1228
|
+
setSelected?.(nextValue);
|
|
1229
|
+
}
|
|
1230
|
+
return /* @__PURE__ */ jsx(TabsContext.Provider, { value: context, children: /* @__PURE__ */ jsx(
|
|
1231
|
+
BaseRoot,
|
|
1232
|
+
{
|
|
1233
|
+
"data-slot": "tabs",
|
|
1234
|
+
"data-disabled": disabled ? "" : void 0,
|
|
1235
|
+
value: rootValue,
|
|
1236
|
+
defaultValue: rootValue === void 0 ? rootDefaultValue : void 0,
|
|
1237
|
+
onValueChange: handleValueChange,
|
|
1238
|
+
orientation,
|
|
1239
|
+
className: cn("w-full", className),
|
|
1240
|
+
style: getTabsStyle(size, style),
|
|
1241
|
+
...props,
|
|
1242
|
+
children: tabs ? /* @__PURE__ */ jsx(
|
|
1243
|
+
TabsCollection,
|
|
1244
|
+
{
|
|
1245
|
+
tabs,
|
|
1246
|
+
ariaLabel,
|
|
1247
|
+
className: listClassName,
|
|
1248
|
+
variant,
|
|
1249
|
+
shape,
|
|
1250
|
+
disabled
|
|
1251
|
+
}
|
|
1252
|
+
) : children
|
|
1253
|
+
}
|
|
1254
|
+
) });
|
|
1255
|
+
}
|
|
1256
|
+
function TabsCollection({
|
|
1257
|
+
ariaLabel,
|
|
1258
|
+
tabs,
|
|
1259
|
+
className,
|
|
1260
|
+
variant,
|
|
1261
|
+
shape,
|
|
1262
|
+
disabled
|
|
1263
|
+
}) {
|
|
1264
|
+
const hasTooltip = tabs.some((tab) => tab.tooltip);
|
|
1265
|
+
const list = /* @__PURE__ */ jsx(
|
|
1266
|
+
TabsList,
|
|
1267
|
+
{
|
|
1268
|
+
"aria-label": ariaLabel ?? "Sections",
|
|
1269
|
+
className,
|
|
1270
|
+
variant,
|
|
1271
|
+
shape,
|
|
1272
|
+
children: tabs.map((tab) => /* @__PURE__ */ jsx(TabsCollectionTrigger, { disabled, tab }, tab.value))
|
|
1273
|
+
}
|
|
1274
|
+
);
|
|
1275
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1276
|
+
hasTooltip ? /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 150, children: list }) : list,
|
|
1277
|
+
tabs.map(
|
|
1278
|
+
(tab) => tab.content === void 0 ? null : /* @__PURE__ */ jsx(TabsContent, { value: tab.value, children: tab.content }, tab.value)
|
|
1279
|
+
)
|
|
1280
|
+
] });
|
|
1281
|
+
}
|
|
1282
|
+
function TabsCollectionTrigger({ tab, disabled }) {
|
|
1283
|
+
const trigger = /* @__PURE__ */ jsx(
|
|
1284
|
+
TabsTrigger,
|
|
1285
|
+
{
|
|
1286
|
+
value: tab.value,
|
|
1287
|
+
disabled: disabled || tab.disabled,
|
|
1288
|
+
icon: tab.icon,
|
|
1289
|
+
badge: tab.badge,
|
|
1290
|
+
children: tab.title
|
|
1291
|
+
}
|
|
1292
|
+
);
|
|
1293
|
+
if (!tab.tooltip) return trigger;
|
|
1294
|
+
return /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
1295
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx("span", { className: "inline-flex", children: trigger }) }),
|
|
1296
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: tab.tooltip })
|
|
1297
|
+
] });
|
|
1298
|
+
}
|
|
1299
|
+
function TabsList({
|
|
1300
|
+
activateOnFocus,
|
|
1301
|
+
className,
|
|
1302
|
+
loop = true,
|
|
1303
|
+
shape,
|
|
1304
|
+
variant,
|
|
1305
|
+
children,
|
|
1306
|
+
...props
|
|
1307
|
+
}) {
|
|
1308
|
+
const context = React14.useContext(TabsContext);
|
|
1309
|
+
const resolvedVariant = variant ?? context.variant;
|
|
1310
|
+
const resolvedShape = shape ?? context.shape;
|
|
1311
|
+
const resolvedActivateOnFocus = activateOnFocus ?? context.activationMode === "automatic";
|
|
1312
|
+
const listContext = React14.useMemo(
|
|
1313
|
+
() => ({ ...context, shape: resolvedShape, variant: resolvedVariant }),
|
|
1314
|
+
[context, resolvedShape, resolvedVariant]
|
|
1315
|
+
);
|
|
1316
|
+
return /* @__PURE__ */ jsx(TabsContext.Provider, { value: listContext, children: /* @__PURE__ */ jsxs(
|
|
1317
|
+
BaseList,
|
|
1318
|
+
{
|
|
1319
|
+
"data-slot": "tabs-list",
|
|
1320
|
+
activateOnFocus: resolvedActivateOnFocus,
|
|
1321
|
+
loopFocus: loop,
|
|
1322
|
+
className: cn(
|
|
1323
|
+
tabsListVariants({ variant: resolvedVariant, shape: resolvedShape }),
|
|
1324
|
+
className
|
|
1325
|
+
),
|
|
1326
|
+
...props,
|
|
1327
|
+
children: [
|
|
1328
|
+
/* @__PURE__ */ jsx(
|
|
1329
|
+
BaseIndicator,
|
|
1330
|
+
{
|
|
1331
|
+
"aria-hidden": "true",
|
|
1332
|
+
"data-slot": "tabs-indicator",
|
|
1333
|
+
className: tabsIndicatorVariants({ variant: resolvedVariant, shape: resolvedShape }),
|
|
1334
|
+
style: {
|
|
1335
|
+
left: "var(--active-tab-left)",
|
|
1336
|
+
top: resolvedVariant === "line" ? "auto" : "var(--active-tab-top)",
|
|
1337
|
+
bottom: resolvedVariant === "line" ? "0" : "auto",
|
|
1338
|
+
width: "var(--active-tab-width)",
|
|
1339
|
+
height: resolvedVariant === "line" ? "var(--tabs-line-thickness)" : "var(--active-tab-height)"
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
),
|
|
1343
|
+
children
|
|
1344
|
+
]
|
|
1345
|
+
}
|
|
1346
|
+
) });
|
|
1347
|
+
}
|
|
1348
|
+
function TabsTrigger({ badge, children, className, disabled, icon, ...props }) {
|
|
1349
|
+
const context = React14.useContext(TabsContext);
|
|
1350
|
+
const isDisabled = disabled ?? context.disabled;
|
|
1351
|
+
return /* @__PURE__ */ jsxs(
|
|
1352
|
+
BaseTrigger,
|
|
1353
|
+
{
|
|
1354
|
+
"data-slot": "tabs-trigger",
|
|
1355
|
+
disabled: isDisabled,
|
|
1356
|
+
className: cn(tabsTriggerVariants({ variant: context.variant }), className),
|
|
1357
|
+
...props,
|
|
1358
|
+
children: [
|
|
1359
|
+
icon,
|
|
1360
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children }),
|
|
1361
|
+
badge ? /* @__PURE__ */ jsx("span", { className: "inline-flex h-[var(--tabs-badge-height)] items-center rounded-[var(--tabs-pill-radius)] bg-muted px-[var(--tabs-badge-padding-x)] text-[length:var(--tabs-badge-font-size)] text-muted-foreground", children: badge }) : null
|
|
1362
|
+
]
|
|
1363
|
+
}
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
function TabsContent({ className, forceMount, keepMounted, variant, ...props }) {
|
|
1367
|
+
return /* @__PURE__ */ jsx(
|
|
1368
|
+
BaseContent,
|
|
1369
|
+
{
|
|
1370
|
+
"data-slot": "tabs-content",
|
|
1371
|
+
keepMounted: keepMounted ?? forceMount,
|
|
1372
|
+
className: cn(tabsContentVariants({ variant }), className),
|
|
1373
|
+
...props
|
|
1374
|
+
}
|
|
1375
|
+
);
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// src/tokens/components/textarea.ts
|
|
1379
|
+
var textareaTokens = {
|
|
1380
|
+
sizes: {
|
|
1381
|
+
sm: {
|
|
1382
|
+
minHeight: primitiveSpacing[20],
|
|
1383
|
+
paddingX: primitiveSpacing[2],
|
|
1384
|
+
paddingY: primitiveSpacing[2],
|
|
1385
|
+
fontSize: primitiveFontSize.xs,
|
|
1386
|
+
radius: primitiveRadius.md
|
|
1387
|
+
},
|
|
1388
|
+
md: {
|
|
1389
|
+
minHeight: primitiveSpacing[24],
|
|
1390
|
+
paddingX: primitiveSpacing[3],
|
|
1391
|
+
paddingY: primitiveSpacing[2],
|
|
1392
|
+
fontSize: primitiveFontSize.sm,
|
|
1393
|
+
radius: primitiveRadius.md
|
|
1394
|
+
},
|
|
1395
|
+
lg: {
|
|
1396
|
+
minHeight: primitiveSpacing[24],
|
|
1397
|
+
paddingX: primitiveSpacing[4],
|
|
1398
|
+
paddingY: primitiveSpacing[3],
|
|
1399
|
+
fontSize: primitiveFontSize.base,
|
|
1400
|
+
radius: primitiveRadius.lg
|
|
1401
|
+
}
|
|
1402
|
+
},
|
|
1403
|
+
labelSize: primitiveFontSize.sm,
|
|
1404
|
+
focusRingWidth: 3,
|
|
1405
|
+
fieldGap: primitiveSpacing[2] - primitiveSpacing[1] / 2};
|
|
1406
|
+
var fieldMessageClassName = "text-xs text-muted-foreground";
|
|
1407
|
+
var fieldErrorClassName = "text-xs font-medium text-destructive";
|
|
1408
|
+
function getTextareaStyle(size, style) {
|
|
1409
|
+
const token = textareaTokens.sizes[size];
|
|
1410
|
+
return {
|
|
1411
|
+
"--textarea-min-height": `${token.minHeight}px`,
|
|
1412
|
+
"--textarea-padding-x": `${token.paddingX}px`,
|
|
1413
|
+
"--textarea-padding-y": `${token.paddingY}px`,
|
|
1414
|
+
"--textarea-font-size": `${token.fontSize}px`,
|
|
1415
|
+
"--textarea-radius": `${token.radius}px`,
|
|
1416
|
+
"--textarea-focus-ring-width": `${textareaTokens.focusRingWidth}px`,
|
|
1417
|
+
borderRadius: "var(--textarea-radius)",
|
|
1418
|
+
outline: "none",
|
|
1419
|
+
...style
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
function getTextareaFieldStyle() {
|
|
1423
|
+
return {
|
|
1424
|
+
"--textarea-field-gap": `${textareaTokens.fieldGap}px`,
|
|
1425
|
+
"--textarea-label-size": `${textareaTokens.labelSize}px`
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
function joinDescriptionIds(...ids) {
|
|
1429
|
+
return ids.filter(Boolean).join(" ") || void 0;
|
|
1430
|
+
}
|
|
1431
|
+
function isInvalid(error, ariaInvalid) {
|
|
1432
|
+
return error === true || typeof error === "string" || ariaInvalid === true || ariaInvalid === "true";
|
|
1433
|
+
}
|
|
1434
|
+
var Textarea = React14.forwardRef(
|
|
1435
|
+
({
|
|
1436
|
+
className,
|
|
1437
|
+
style,
|
|
1438
|
+
size = "md",
|
|
1439
|
+
label,
|
|
1440
|
+
description,
|
|
1441
|
+
error,
|
|
1442
|
+
fieldClassName,
|
|
1443
|
+
id,
|
|
1444
|
+
onChange,
|
|
1445
|
+
onValueChange,
|
|
1446
|
+
"aria-describedby": ariaDescribedBy,
|
|
1447
|
+
"aria-invalid": ariaInvalid,
|
|
1448
|
+
...props
|
|
1449
|
+
}, ref) => {
|
|
1450
|
+
const baseId = React14.useId();
|
|
1451
|
+
const textareaId = id ?? (label || description || typeof error === "string" ? baseId : void 0);
|
|
1452
|
+
const resolvedInvalid = isInvalid(error, ariaInvalid);
|
|
1453
|
+
const descriptionId = description && textareaId ? `${textareaId}-description` : void 0;
|
|
1454
|
+
const errorId = typeof error === "string" && textareaId ? `${textareaId}-error` : void 0;
|
|
1455
|
+
const describedBy = joinDescriptionIds(ariaDescribedBy, descriptionId, errorId);
|
|
1456
|
+
const handleChange = React14.useCallback(
|
|
1457
|
+
(event) => {
|
|
1458
|
+
onValueChange?.(event.currentTarget.value);
|
|
1459
|
+
onChange?.(event);
|
|
1460
|
+
},
|
|
1461
|
+
[onChange, onValueChange]
|
|
1462
|
+
);
|
|
1463
|
+
const control = /* @__PURE__ */ jsx(
|
|
1464
|
+
"textarea",
|
|
1465
|
+
{
|
|
1466
|
+
className: cn(
|
|
1467
|
+
"flex min-h-[var(--textarea-min-height)] w-full resize-y rounded-[var(--textarea-radius)] border border-input bg-background",
|
|
1468
|
+
"px-[var(--textarea-padding-x)] py-[var(--textarea-padding-y)] text-[length:var(--textarea-font-size)] text-foreground shadow-[var(--shadow-xs)]",
|
|
1469
|
+
"transition-[background-color,border-color,box-shadow,color] duration-micro ease-out placeholder:text-muted-foreground",
|
|
1470
|
+
"outline-none focus:border-ring focus:ring-[length:var(--textarea-focus-ring-width)] focus:ring-ring/30",
|
|
1471
|
+
"disabled:cursor-not-allowed disabled:opacity-50 read-only:cursor-default read-only:bg-muted/70",
|
|
1472
|
+
"aria-invalid:border-destructive/60 aria-invalid:focus:border-destructive aria-invalid:focus:ring-destructive/20",
|
|
1473
|
+
className
|
|
1474
|
+
),
|
|
1475
|
+
ref,
|
|
1476
|
+
id: textareaId,
|
|
1477
|
+
"aria-invalid": resolvedInvalid || void 0,
|
|
1478
|
+
"aria-describedby": describedBy,
|
|
1479
|
+
style: getTextareaStyle(size, style),
|
|
1480
|
+
onChange: handleChange,
|
|
1481
|
+
...props
|
|
1482
|
+
}
|
|
1483
|
+
);
|
|
1484
|
+
if (!label && !description && typeof error !== "string" && !fieldClassName) {
|
|
1485
|
+
return control;
|
|
1486
|
+
}
|
|
1487
|
+
return /* @__PURE__ */ jsxs(
|
|
1488
|
+
"div",
|
|
1489
|
+
{
|
|
1490
|
+
"data-slot": "textarea-field",
|
|
1491
|
+
className: cn("grid gap-[var(--textarea-field-gap)]", fieldClassName),
|
|
1492
|
+
style: getTextareaFieldStyle(),
|
|
1493
|
+
children: [
|
|
1494
|
+
label && /* @__PURE__ */ jsx(
|
|
1495
|
+
"label",
|
|
1496
|
+
{
|
|
1497
|
+
htmlFor: textareaId,
|
|
1498
|
+
className: "text-[length:var(--textarea-label-size)] font-medium text-foreground",
|
|
1499
|
+
children: label
|
|
1500
|
+
}
|
|
1501
|
+
),
|
|
1502
|
+
control,
|
|
1503
|
+
description && !error && /* @__PURE__ */ jsx("p", { id: descriptionId, className: fieldMessageClassName, children: description }),
|
|
1504
|
+
typeof error === "string" && /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", className: fieldErrorClassName, children: error })
|
|
1505
|
+
]
|
|
1506
|
+
}
|
|
1507
|
+
);
|
|
1508
|
+
}
|
|
1509
|
+
);
|
|
1510
|
+
Textarea.displayName = "Textarea";
|
|
1511
|
+
function MarkdownRenderer({ content, className }) {
|
|
1512
|
+
return /* @__PURE__ */ jsx("div", { className: cn("prose prose-sm dark:prose-invert max-w-none", className), children: /* @__PURE__ */ jsx(
|
|
1513
|
+
ReactMarkdown,
|
|
1514
|
+
{
|
|
1515
|
+
remarkPlugins: [remarkGfm],
|
|
1516
|
+
components: {
|
|
1517
|
+
// Inline + block code use semantic tokens so they read in light + dark
|
|
1518
|
+
// themes without hardcoded slate/blue.
|
|
1519
|
+
code(props) {
|
|
1520
|
+
const { className: codeClassName, children, ...rest } = props;
|
|
1521
|
+
const isBlock = /language-/.test(codeClassName ?? "");
|
|
1522
|
+
if (isBlock) {
|
|
1523
|
+
return /* @__PURE__ */ jsx(
|
|
1524
|
+
"code",
|
|
1525
|
+
{
|
|
1526
|
+
...rest,
|
|
1527
|
+
className: cn(
|
|
1528
|
+
"block overflow-x-auto rounded-[var(--radius-md)] border border-border bg-muted p-4 font-mono text-foreground text-sm",
|
|
1529
|
+
codeClassName
|
|
1530
|
+
),
|
|
1531
|
+
children
|
|
1532
|
+
}
|
|
1533
|
+
);
|
|
1534
|
+
}
|
|
1535
|
+
return /* @__PURE__ */ jsx(
|
|
1536
|
+
"code",
|
|
1537
|
+
{
|
|
1538
|
+
...rest,
|
|
1539
|
+
className: cn(
|
|
1540
|
+
"rounded bg-muted px-1.5 py-0.5 font-mono text-foreground text-sm",
|
|
1541
|
+
codeClassName
|
|
1542
|
+
),
|
|
1543
|
+
children
|
|
1544
|
+
}
|
|
1545
|
+
);
|
|
1546
|
+
},
|
|
1547
|
+
// Anchor: open external links in a new tab; protect against
|
|
1548
|
+
// tabnabbing with rel="noopener noreferrer".
|
|
1549
|
+
a({ href, children, ...rest }) {
|
|
1550
|
+
const external = typeof href === "string" && /^https?:/.test(href);
|
|
1551
|
+
return /* @__PURE__ */ jsx(
|
|
1552
|
+
"a",
|
|
1553
|
+
{
|
|
1554
|
+
href,
|
|
1555
|
+
className: "text-primary underline-offset-4 hover:underline",
|
|
1556
|
+
...external ? { target: "_blank", rel: "noopener noreferrer" } : {},
|
|
1557
|
+
...rest,
|
|
1558
|
+
children
|
|
1559
|
+
}
|
|
1560
|
+
);
|
|
1561
|
+
},
|
|
1562
|
+
blockquote({ children, ...rest }) {
|
|
1563
|
+
return /* @__PURE__ */ jsx(
|
|
1564
|
+
"blockquote",
|
|
1565
|
+
{
|
|
1566
|
+
...rest,
|
|
1567
|
+
className: "my-4 border-primary border-l-4 bg-muted py-2 pl-4 text-foreground italic",
|
|
1568
|
+
children
|
|
1569
|
+
}
|
|
1570
|
+
);
|
|
1571
|
+
}
|
|
1572
|
+
},
|
|
1573
|
+
children: content
|
|
1574
|
+
}
|
|
1575
|
+
) });
|
|
1576
|
+
}
|
|
1577
|
+
function QuoteGlyph(props) {
|
|
1578
|
+
return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", fill: "currentColor", "aria-hidden": "true", className: props.className, children: [
|
|
1579
|
+
/* @__PURE__ */ jsx("title", { children: "Block quote" }),
|
|
1580
|
+
/* @__PURE__ */ jsx("path", { d: "M4 4h2l-1.5 4H6v4H2V8l2-4Zm6 0h2l-1.5 4H12v4H8V8l2-4Z" })
|
|
1581
|
+
] });
|
|
1582
|
+
}
|
|
1583
|
+
function MarkdownEditor({
|
|
1584
|
+
value,
|
|
1585
|
+
onChange,
|
|
1586
|
+
placeholder = "Write your answer here\u2026",
|
|
1587
|
+
minHeight = 250,
|
|
1588
|
+
className
|
|
1589
|
+
}) {
|
|
1590
|
+
const [tab, setTab] = useState("write");
|
|
1591
|
+
const textareaRef = useRef(null);
|
|
1592
|
+
const insertMarkdown = useCallback(
|
|
1593
|
+
(before, after = "", forceNewLine = false) => {
|
|
1594
|
+
const el = textareaRef.current;
|
|
1595
|
+
if (!el) return;
|
|
1596
|
+
const start = el.selectionStart;
|
|
1597
|
+
const end = el.selectionEnd;
|
|
1598
|
+
const selected = value.slice(start, end);
|
|
1599
|
+
const needsLeadingNl = forceNewLine && start > 0 && value[start - 1] !== "\n";
|
|
1600
|
+
const insert = (needsLeadingNl ? "\n" : "") + before + selected + after;
|
|
1601
|
+
const next = value.slice(0, start) + insert + value.slice(end);
|
|
1602
|
+
onChange(next);
|
|
1603
|
+
requestAnimationFrame(() => {
|
|
1604
|
+
el.focus();
|
|
1605
|
+
const caretStart = start + (needsLeadingNl ? 1 : 0) + before.length;
|
|
1606
|
+
el.setSelectionRange(caretStart, caretStart + selected.length);
|
|
1607
|
+
});
|
|
1608
|
+
},
|
|
1609
|
+
[value, onChange]
|
|
1610
|
+
);
|
|
1611
|
+
return /* @__PURE__ */ jsxs(
|
|
1612
|
+
"div",
|
|
1613
|
+
{
|
|
1614
|
+
className: cn("overflow-hidden rounded-[var(--radius-md)] border border-border", className),
|
|
1615
|
+
children: [
|
|
1616
|
+
/* @__PURE__ */ jsxs(Tabs, { value: tab, onValueChange: (v) => setTab(v), className: "w-full", children: [
|
|
1617
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-border border-b bg-muted/30 px-3 py-2", children: [
|
|
1618
|
+
/* @__PURE__ */ jsxs(TabsList, { className: "grid w-fit grid-cols-2", children: [
|
|
1619
|
+
/* @__PURE__ */ jsxs(TabsTrigger, { value: "write", className: "text-xs", children: [
|
|
1620
|
+
/* @__PURE__ */ jsx(Pencil, { className: "mr-1 h-3 w-3", "aria-hidden": "true" }),
|
|
1621
|
+
"Write"
|
|
1622
|
+
] }),
|
|
1623
|
+
/* @__PURE__ */ jsxs(TabsTrigger, { value: "preview", className: "text-xs", children: [
|
|
1624
|
+
/* @__PURE__ */ jsx(FileText, { className: "mr-1 h-3 w-3", "aria-hidden": "true" }),
|
|
1625
|
+
"Preview"
|
|
1626
|
+
] })
|
|
1627
|
+
] }),
|
|
1628
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1", children: [
|
|
1629
|
+
/* @__PURE__ */ jsx(ToolbarButton, { "aria-label": "Bold (Ctrl+B)", onClick: () => insertMarkdown("**", "**"), children: /* @__PURE__ */ jsx(TextBold, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) }),
|
|
1630
|
+
/* @__PURE__ */ jsx(ToolbarButton, { "aria-label": "Italic (Ctrl+I)", onClick: () => insertMarkdown("*", "*"), children: /* @__PURE__ */ jsx(TextItalic, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) }),
|
|
1631
|
+
/* @__PURE__ */ jsx(ToolbarButton, { "aria-label": "Inline code", onClick: () => insertMarkdown("`", "`"), children: /* @__PURE__ */ jsx(Code, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) }),
|
|
1632
|
+
/* @__PURE__ */ jsx(ToolbarButton, { "aria-label": "Heading", onClick: () => insertMarkdown("\n## ", "", true), children: /* @__PURE__ */ jsx(Hash, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) }),
|
|
1633
|
+
/* @__PURE__ */ jsx(
|
|
1634
|
+
ToolbarButton,
|
|
1635
|
+
{
|
|
1636
|
+
"aria-label": "Block quote",
|
|
1637
|
+
onClick: () => insertMarkdown("\n> ", "", true),
|
|
1638
|
+
children: /* @__PURE__ */ jsx(QuoteGlyph, { className: "h-3.5 w-3.5" })
|
|
1639
|
+
}
|
|
1640
|
+
),
|
|
1641
|
+
/* @__PURE__ */ jsx(
|
|
1642
|
+
ToolbarButton,
|
|
1643
|
+
{
|
|
1644
|
+
"aria-label": "Bullet list",
|
|
1645
|
+
onClick: () => insertMarkdown("\n- ", "", true),
|
|
1646
|
+
children: /* @__PURE__ */ jsx(ListUnordered, { className: "h-3.5 w-3.5", "aria-hidden": "true" })
|
|
1647
|
+
}
|
|
1648
|
+
),
|
|
1649
|
+
/* @__PURE__ */ jsx(ToolbarButton, { "aria-label": "Link", onClick: () => insertMarkdown("[", "](url)"), children: /* @__PURE__ */ jsx(Link, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) }),
|
|
1650
|
+
/* @__PURE__ */ jsx(ToolbarButton, { "aria-label": "Image", onClick: () => insertMarkdown(""), children: /* @__PURE__ */ jsx(Image, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) }),
|
|
1651
|
+
/* @__PURE__ */ jsx(
|
|
1652
|
+
ToolbarButton,
|
|
1653
|
+
{
|
|
1654
|
+
"aria-label": "Fenced code block",
|
|
1655
|
+
onClick: () => insertMarkdown("\n```javascript\n", "\n```\n", true),
|
|
1656
|
+
children: /* @__PURE__ */ jsx(FileText, { className: "h-3.5 w-3.5", "aria-hidden": "true" })
|
|
1657
|
+
}
|
|
1658
|
+
)
|
|
1659
|
+
] })
|
|
1660
|
+
] }),
|
|
1661
|
+
/* @__PURE__ */ jsx(TabsContent, { value: "write", className: "mt-0", children: /* @__PURE__ */ jsx(
|
|
1662
|
+
Textarea,
|
|
1663
|
+
{
|
|
1664
|
+
ref: textareaRef,
|
|
1665
|
+
value,
|
|
1666
|
+
onChange: (e) => onChange(e.target.value),
|
|
1667
|
+
placeholder,
|
|
1668
|
+
className: "resize-none rounded-none border-0 font-mono text-sm shadow-none focus-visible:ring-0",
|
|
1669
|
+
style: { minHeight }
|
|
1670
|
+
}
|
|
1671
|
+
) }),
|
|
1672
|
+
/* @__PURE__ */ jsx(TabsContent, { value: "preview", className: "mt-0", children: /* @__PURE__ */ jsx("div", { className: "bg-background p-4", style: { minHeight }, children: value ? /* @__PURE__ */ jsx(MarkdownRenderer, { content: value }) : /* @__PURE__ */ jsx("div", { className: "flex h-32 items-center justify-center text-muted-foreground text-sm italic", children: "Nothing to preview." }) }) })
|
|
1673
|
+
] }),
|
|
1674
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-border border-t bg-muted/30 px-3 py-2 text-muted-foreground text-xs", children: [
|
|
1675
|
+
/* @__PURE__ */ jsx("span", { children: "Markdown supported" }),
|
|
1676
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
1677
|
+
value.length,
|
|
1678
|
+
" characters"
|
|
1679
|
+
] })
|
|
1680
|
+
] })
|
|
1681
|
+
]
|
|
1682
|
+
}
|
|
1683
|
+
);
|
|
1684
|
+
}
|
|
1685
|
+
function ToolbarButton({
|
|
1686
|
+
children,
|
|
1687
|
+
onClick,
|
|
1688
|
+
"aria-label": ariaLabel
|
|
1689
|
+
}) {
|
|
1690
|
+
return /* @__PURE__ */ jsx(
|
|
1691
|
+
Button,
|
|
1692
|
+
{
|
|
1693
|
+
type: "button",
|
|
1694
|
+
variant: "ghost",
|
|
1695
|
+
size: "sm",
|
|
1696
|
+
className: "h-7 w-7 p-0",
|
|
1697
|
+
onClick,
|
|
1698
|
+
"aria-label": ariaLabel,
|
|
1699
|
+
children
|
|
1700
|
+
}
|
|
1701
|
+
);
|
|
1702
|
+
}
|
|
1703
|
+
var Card2 = React14.forwardRef(
|
|
1704
|
+
({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
1705
|
+
"div",
|
|
1706
|
+
{
|
|
1707
|
+
ref,
|
|
1708
|
+
className: cn(
|
|
1709
|
+
"rounded-[var(--radius-lg)] border border-border bg-card text-card-foreground shadow-sm",
|
|
1710
|
+
className
|
|
1711
|
+
),
|
|
1712
|
+
...props
|
|
1713
|
+
}
|
|
1714
|
+
)
|
|
1715
|
+
);
|
|
1716
|
+
Card2.displayName = "Card";
|
|
1717
|
+
var CardHeader2 = React14.forwardRef(
|
|
1718
|
+
({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("flex flex-col space-y-1.5 p-6", className), ...props })
|
|
1719
|
+
);
|
|
1720
|
+
CardHeader2.displayName = "CardHeader";
|
|
1721
|
+
var CardTitle2 = React14.forwardRef(
|
|
1722
|
+
({ className, children, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
1723
|
+
"h3",
|
|
1724
|
+
{
|
|
1725
|
+
ref,
|
|
1726
|
+
className: cn("text-2xl font-semibold leading-none tracking-tight", className),
|
|
1727
|
+
...props,
|
|
1728
|
+
children
|
|
1729
|
+
}
|
|
1730
|
+
)
|
|
1731
|
+
);
|
|
1732
|
+
CardTitle2.displayName = "CardTitle";
|
|
1733
|
+
var CardDescription2 = React14.forwardRef(({ className, as: Tag = "p", ...props }, ref) => {
|
|
1734
|
+
return React14.createElement(Tag, {
|
|
1735
|
+
ref,
|
|
1736
|
+
className: cn("text-sm text-muted-foreground", className),
|
|
1737
|
+
...props
|
|
1738
|
+
});
|
|
1739
|
+
});
|
|
1740
|
+
CardDescription2.displayName = "CardDescription";
|
|
1741
|
+
var CardContent = React14.forwardRef(
|
|
1742
|
+
({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("p-6 pt-0", className), ...props })
|
|
1743
|
+
);
|
|
1744
|
+
CardContent.displayName = "CardContent";
|
|
1745
|
+
var CardFooter2 = React14.forwardRef(
|
|
1746
|
+
({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("flex items-center p-6 pt-0", className), ...props })
|
|
1747
|
+
);
|
|
1748
|
+
CardFooter2.displayName = "CardFooter";
|
|
1749
|
+
var Separator = React14.forwardRef(({ className, orientation = "horizontal", decorative: _decorative = true, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
1750
|
+
Separator$1,
|
|
1751
|
+
{
|
|
1752
|
+
ref,
|
|
1753
|
+
orientation,
|
|
1754
|
+
className: cn(
|
|
1755
|
+
"shrink-0 bg-border",
|
|
1756
|
+
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
|
1757
|
+
className
|
|
1758
|
+
),
|
|
1759
|
+
...props
|
|
1760
|
+
}
|
|
1761
|
+
));
|
|
1762
|
+
Separator.displayName = "Separator";
|
|
1763
|
+
var sizeClasses = {
|
|
1764
|
+
xs: { root: "h-5 w-5", fallback: "text-[8px]" },
|
|
1765
|
+
sm: { root: "h-8 w-8", fallback: "text-xs" },
|
|
1766
|
+
md: { root: "h-10 w-10", fallback: "text-sm" },
|
|
1767
|
+
lg: { root: "h-14 w-14", fallback: "text-xl" },
|
|
1768
|
+
xl: { root: "h-20 w-20", fallback: "text-2xl" }
|
|
1769
|
+
};
|
|
1770
|
+
var numericAvatarClass = "h-[var(--avatar-size)] w-[var(--avatar-size)]";
|
|
1771
|
+
var AvatarContext = React14.createContext(null);
|
|
1772
|
+
function isPresetSize(size) {
|
|
1773
|
+
return typeof size === "string";
|
|
1774
|
+
}
|
|
1775
|
+
function getAvatarRootClass(size) {
|
|
1776
|
+
return isPresetSize(size) ? sizeClasses[size].root : numericAvatarClass;
|
|
1777
|
+
}
|
|
1778
|
+
function getAvatarFallbackClass(size) {
|
|
1779
|
+
return isPresetSize(size) ? sizeClasses[size].fallback : "text-[var(--avatar-font-size)]";
|
|
1780
|
+
}
|
|
1781
|
+
function getAvatarStyle(size, style) {
|
|
1782
|
+
if (typeof size !== "number") return style;
|
|
1783
|
+
return {
|
|
1784
|
+
"--avatar-size": `${size}px`,
|
|
1785
|
+
"--avatar-font-size": `${Math.max(10, Math.round(size * 0.375))}px`,
|
|
1786
|
+
...style
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
function getAvatarInitials(value) {
|
|
1790
|
+
const cleaned = value.trim();
|
|
1791
|
+
if (!cleaned) return "";
|
|
1792
|
+
const parts = cleaned.split(/[\s._-]+/).filter(Boolean);
|
|
1793
|
+
if (parts.length === 1) return (parts[0] ?? "").slice(0, 2).toUpperCase();
|
|
1794
|
+
return parts.slice(0, 2).map((part) => part[0] ?? "").join("").toUpperCase();
|
|
1795
|
+
}
|
|
1796
|
+
function normalizeAvatarLabel({
|
|
1797
|
+
alt,
|
|
1798
|
+
title,
|
|
1799
|
+
username
|
|
1800
|
+
}) {
|
|
1801
|
+
return title ?? alt ?? username ?? "Avatar";
|
|
1802
|
+
}
|
|
1803
|
+
var Avatar = React14.forwardRef(
|
|
1804
|
+
({
|
|
1805
|
+
className,
|
|
1806
|
+
size = "md",
|
|
1807
|
+
src,
|
|
1808
|
+
alt,
|
|
1809
|
+
title,
|
|
1810
|
+
letter,
|
|
1811
|
+
username,
|
|
1812
|
+
placeholder,
|
|
1813
|
+
fallbackDelayMs,
|
|
1814
|
+
children,
|
|
1815
|
+
style,
|
|
1816
|
+
...props
|
|
1817
|
+
}, ref) => {
|
|
1818
|
+
const label = normalizeAvatarLabel({ alt, title, username });
|
|
1819
|
+
const fallback = letter ?? getAvatarInitials(title ?? username ?? alt ?? "");
|
|
1820
|
+
const contextTitle = title ?? alt ?? username;
|
|
1821
|
+
const contextValue = contextTitle === void 0 ? { size } : { size, title: contextTitle };
|
|
1822
|
+
const shouldRenderConvenienceContent = children === void 0 && (src !== void 0 || placeholder === true || fallback.length > 0);
|
|
1823
|
+
return /* @__PURE__ */ jsx(AvatarContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
|
|
1824
|
+
Avatar$1.Root,
|
|
1825
|
+
{
|
|
1826
|
+
ref,
|
|
1827
|
+
className: cn(
|
|
1828
|
+
"relative flex shrink-0 overflow-hidden rounded-full bg-muted",
|
|
1829
|
+
getAvatarRootClass(size),
|
|
1830
|
+
className
|
|
1831
|
+
),
|
|
1832
|
+
style: getAvatarStyle(size, style),
|
|
1833
|
+
...props,
|
|
1834
|
+
children: shouldRenderConvenienceContent ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1835
|
+
src && /* @__PURE__ */ jsx(AvatarImage, { src, alt: label }),
|
|
1836
|
+
/* @__PURE__ */ jsx(AvatarFallback, { size, delay: fallbackDelayMs, children: placeholder ? /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Loading avatar" }) : fallback })
|
|
1837
|
+
] }) : children
|
|
1838
|
+
}
|
|
1839
|
+
) });
|
|
1840
|
+
}
|
|
1841
|
+
);
|
|
1842
|
+
Avatar.displayName = "Avatar";
|
|
1843
|
+
var AvatarImage = React14.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
1844
|
+
Avatar$1.Image,
|
|
1845
|
+
{
|
|
1846
|
+
ref,
|
|
1847
|
+
className: cn("aspect-square h-full w-full object-cover", className),
|
|
1848
|
+
...props
|
|
1849
|
+
}
|
|
1850
|
+
));
|
|
1851
|
+
AvatarImage.displayName = "AvatarImage";
|
|
1852
|
+
var AvatarFallback = React14.forwardRef(
|
|
1853
|
+
({ className, size, children, "aria-label": ariaLabel, style, ...props }, ref) => {
|
|
1854
|
+
const context = React14.useContext(AvatarContext);
|
|
1855
|
+
const resolvedSize = size ?? context?.size ?? "md";
|
|
1856
|
+
const fallbackLabel = ariaLabel ?? (typeof children === "string" && children.trim().length > 0 ? `Avatar with initials: ${children}` : context?.title);
|
|
1857
|
+
return /* @__PURE__ */ jsx(
|
|
1858
|
+
Avatar$1.Fallback,
|
|
1859
|
+
{
|
|
1860
|
+
ref,
|
|
1861
|
+
"aria-label": fallbackLabel,
|
|
1862
|
+
className: cn(
|
|
1863
|
+
"flex h-full w-full items-center justify-center rounded-full bg-muted font-medium text-muted-foreground",
|
|
1864
|
+
getAvatarFallbackClass(resolvedSize),
|
|
1865
|
+
className
|
|
1866
|
+
),
|
|
1867
|
+
style: getAvatarStyle(resolvedSize, style),
|
|
1868
|
+
...props,
|
|
1869
|
+
children
|
|
1870
|
+
}
|
|
1871
|
+
);
|
|
1872
|
+
}
|
|
1873
|
+
);
|
|
1874
|
+
AvatarFallback.displayName = "AvatarFallback";
|
|
1875
|
+
function initials(name) {
|
|
1876
|
+
return name.split(/\s+/).map((part) => part.charAt(0).toUpperCase()).join("").slice(0, 2);
|
|
1877
|
+
}
|
|
1878
|
+
function UserInfo({ author, timestamp, verb = "asked", className }) {
|
|
1879
|
+
return /* @__PURE__ */ jsxs(
|
|
1880
|
+
"div",
|
|
1881
|
+
{
|
|
1882
|
+
className: cn("flex items-start gap-3 rounded-[var(--radius-md)] bg-muted/40 p-3", className),
|
|
1883
|
+
children: [
|
|
1884
|
+
/* @__PURE__ */ jsxs(Avatar, { className: "h-8 w-8 shrink-0", children: [
|
|
1885
|
+
author.avatar && /* @__PURE__ */ jsx(AvatarImage, { src: author.avatar, alt: author.name }),
|
|
1886
|
+
/* @__PURE__ */ jsx(AvatarFallback, { children: initials(author.name) })
|
|
1887
|
+
] }),
|
|
1888
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
|
|
1889
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-xs", children: [
|
|
1890
|
+
verb,
|
|
1891
|
+
" ",
|
|
1892
|
+
timestamp
|
|
1893
|
+
] }),
|
|
1894
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
1895
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground text-sm", children: author.name }),
|
|
1896
|
+
/* @__PURE__ */ jsx("span", { className: "font-bold text-muted-foreground text-xs tabular-nums", children: author.reputation.toLocaleString() }),
|
|
1897
|
+
author.badges.gold > 0 && /* @__PURE__ */ jsx(Badge2, { color: "bg-yellow-500", count: author.badges.gold, label: "gold" }),
|
|
1898
|
+
author.badges.silver > 0 && /* @__PURE__ */ jsx(Badge2, { color: "bg-gray-400", count: author.badges.silver, label: "silver" }),
|
|
1899
|
+
author.badges.bronze > 0 && /* @__PURE__ */ jsx(Badge2, { color: "bg-amber-600", count: author.badges.bronze, label: "bronze" })
|
|
1900
|
+
] })
|
|
1901
|
+
] })
|
|
1902
|
+
]
|
|
1903
|
+
}
|
|
1904
|
+
);
|
|
1905
|
+
}
|
|
1906
|
+
function Badge2({ color, count, label }) {
|
|
1907
|
+
return /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
|
|
1908
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn("h-2 w-2 rounded-full", color) }),
|
|
1909
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-xs", children: [
|
|
1910
|
+
/* @__PURE__ */ jsxs("span", { className: "sr-only", children: [
|
|
1911
|
+
label,
|
|
1912
|
+
" badges: "
|
|
1913
|
+
] }),
|
|
1914
|
+
count
|
|
1915
|
+
] })
|
|
1916
|
+
] });
|
|
1917
|
+
}
|
|
1918
|
+
function voteDelta(userVote) {
|
|
1919
|
+
if (userVote === "up") return 1;
|
|
1920
|
+
if (userVote === "down") return -1;
|
|
1921
|
+
return 0;
|
|
1922
|
+
}
|
|
1923
|
+
function VoteButtons({
|
|
1924
|
+
votes,
|
|
1925
|
+
userVote,
|
|
1926
|
+
onVote,
|
|
1927
|
+
size = "default",
|
|
1928
|
+
label = "post"
|
|
1929
|
+
}) {
|
|
1930
|
+
const iconSize = size === "large" ? "h-5 w-5" : "h-4 w-4";
|
|
1931
|
+
const scoreSize = size === "large" ? "text-lg" : "text-base";
|
|
1932
|
+
const score = votes + voteDelta(userVote);
|
|
1933
|
+
return /* @__PURE__ */ jsxs("fieldset", { className: "flex flex-col items-center gap-2", children: [
|
|
1934
|
+
/* @__PURE__ */ jsxs("legend", { className: "sr-only", children: [
|
|
1935
|
+
"Vote on ",
|
|
1936
|
+
label
|
|
1937
|
+
] }),
|
|
1938
|
+
/* @__PURE__ */ jsx(
|
|
1939
|
+
Button,
|
|
1940
|
+
{
|
|
1941
|
+
type: "button",
|
|
1942
|
+
variant: "ghost",
|
|
1943
|
+
size: size === "large" ? "default" : "sm",
|
|
1944
|
+
"aria-label": `Upvote ${label}`,
|
|
1945
|
+
"aria-pressed": userVote === "up",
|
|
1946
|
+
onClick: () => onVote("up"),
|
|
1947
|
+
className: cn("p-2", userVote === "up" && "bg-primary/10 text-primary hover:bg-primary/15"),
|
|
1948
|
+
children: /* @__PURE__ */ jsx(ChevronUp, { className: iconSize, "aria-hidden": "true" })
|
|
1949
|
+
}
|
|
1950
|
+
),
|
|
1951
|
+
/* @__PURE__ */ jsx(
|
|
1952
|
+
"span",
|
|
1953
|
+
{
|
|
1954
|
+
className: cn(
|
|
1955
|
+
"font-bold tabular-nums",
|
|
1956
|
+
scoreSize,
|
|
1957
|
+
userVote === "up" && "text-primary",
|
|
1958
|
+
userVote === "down" && "text-muted-foreground"
|
|
1959
|
+
),
|
|
1960
|
+
"aria-live": "polite",
|
|
1961
|
+
"aria-atomic": "true",
|
|
1962
|
+
children: score
|
|
1963
|
+
}
|
|
1964
|
+
),
|
|
1965
|
+
/* @__PURE__ */ jsx(
|
|
1966
|
+
Button,
|
|
1967
|
+
{
|
|
1968
|
+
type: "button",
|
|
1969
|
+
variant: "ghost",
|
|
1970
|
+
size: size === "large" ? "default" : "sm",
|
|
1971
|
+
"aria-label": `Downvote ${label}`,
|
|
1972
|
+
"aria-pressed": userVote === "down",
|
|
1973
|
+
onClick: () => onVote("down"),
|
|
1974
|
+
className: cn(
|
|
1975
|
+
"p-2",
|
|
1976
|
+
userVote === "down" && "bg-muted text-muted-foreground hover:bg-muted"
|
|
1977
|
+
),
|
|
1978
|
+
children: /* @__PURE__ */ jsx(ChevronDown, { className: iconSize, "aria-hidden": "true" })
|
|
1979
|
+
}
|
|
1980
|
+
)
|
|
1981
|
+
] });
|
|
1982
|
+
}
|
|
1983
|
+
function QAPage({
|
|
1984
|
+
initialQuestion,
|
|
1985
|
+
initialAnswers,
|
|
1986
|
+
currentAuthor = {
|
|
1987
|
+
name: "you",
|
|
1988
|
+
reputation: 0,
|
|
1989
|
+
badges: { gold: 0, silver: 0, bronze: 0 }
|
|
1990
|
+
},
|
|
1991
|
+
onSubmitAnswer,
|
|
1992
|
+
onVoteAnswer,
|
|
1993
|
+
onVoteQuestion,
|
|
1994
|
+
onAcceptAnswer,
|
|
1995
|
+
onBookmark,
|
|
1996
|
+
className
|
|
1997
|
+
}) {
|
|
1998
|
+
const [question, setQuestion] = useState(initialQuestion);
|
|
1999
|
+
const [answers, setAnswers] = useState(initialAnswers);
|
|
2000
|
+
const [draft, setDraft] = useState("");
|
|
2001
|
+
const handleQuestionVote = (next) => {
|
|
2002
|
+
const userVote = question.userVote === next ? null : next;
|
|
2003
|
+
setQuestion((q) => ({ ...q, userVote }));
|
|
2004
|
+
onVoteQuestion?.(userVote);
|
|
2005
|
+
};
|
|
2006
|
+
const handleAnswerVote = (id, next) => {
|
|
2007
|
+
setAnswers(
|
|
2008
|
+
(list) => list.map((a) => a.id === id ? { ...a, userVote: a.userVote === next ? null : next } : a)
|
|
2009
|
+
);
|
|
2010
|
+
const fresh = answers.find((a) => a.id === id);
|
|
2011
|
+
const userVote = fresh && fresh.userVote === next ? null : next;
|
|
2012
|
+
onVoteAnswer?.(id, userVote);
|
|
2013
|
+
};
|
|
2014
|
+
const handleAccept = (id) => {
|
|
2015
|
+
setAnswers(
|
|
2016
|
+
(list) => list.map((a) => ({ ...a, isAccepted: a.id === id ? !a.isAccepted : false }))
|
|
2017
|
+
);
|
|
2018
|
+
onAcceptAnswer?.(id);
|
|
2019
|
+
};
|
|
2020
|
+
const handleBookmarkToggle = () => {
|
|
2021
|
+
setQuestion((q) => {
|
|
2022
|
+
const next = !q.bookmarked;
|
|
2023
|
+
onBookmark?.(next);
|
|
2024
|
+
return { ...q, bookmarked: next };
|
|
2025
|
+
});
|
|
2026
|
+
};
|
|
2027
|
+
const handleSubmit = () => {
|
|
2028
|
+
const trimmed = draft.trim();
|
|
2029
|
+
if (!trimmed) return;
|
|
2030
|
+
const newAnswer = {
|
|
2031
|
+
id: Date.now(),
|
|
2032
|
+
content: trimmed,
|
|
2033
|
+
author: currentAuthor,
|
|
2034
|
+
votes: 0,
|
|
2035
|
+
timestamp: "just now",
|
|
2036
|
+
isAccepted: false,
|
|
2037
|
+
userVote: null
|
|
2038
|
+
};
|
|
2039
|
+
setAnswers((list) => [...list, newAnswer]);
|
|
2040
|
+
setDraft("");
|
|
2041
|
+
onSubmitAnswer?.(trimmed);
|
|
2042
|
+
};
|
|
2043
|
+
const sortedAnswers = useMemo(() => {
|
|
2044
|
+
return [...answers].sort((a, b) => {
|
|
2045
|
+
if (a.isAccepted !== b.isAccepted) return a.isAccepted ? -1 : 1;
|
|
2046
|
+
const aScore = a.votes + (a.userVote === "up" ? 1 : a.userVote === "down" ? -1 : 0);
|
|
2047
|
+
const bScore = b.votes + (b.userVote === "up" ? 1 : b.userVote === "down" ? -1 : 0);
|
|
2048
|
+
return bScore - aScore;
|
|
2049
|
+
});
|
|
2050
|
+
}, [answers]);
|
|
2051
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("mx-auto w-full max-w-5xl p-4 md:p-6", className), children: [
|
|
2052
|
+
/* @__PURE__ */ jsxs("header", { className: "mb-6", children: [
|
|
2053
|
+
/* @__PURE__ */ jsx("h1", { className: "mb-4 font-bold text-2xl leading-tight text-foreground", children: question.title }),
|
|
2054
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-4 flex items-center gap-4 text-muted-foreground text-sm", children: [
|
|
2055
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
|
|
2056
|
+
/* @__PURE__ */ jsx(Calendar, { "aria-hidden": "true", className: "h-4 w-4" }),
|
|
2057
|
+
"Asked ",
|
|
2058
|
+
question.timestamp
|
|
2059
|
+
] }),
|
|
2060
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
|
|
2061
|
+
/* @__PURE__ */ jsx(Eye, { "aria-hidden": "true", className: "h-4 w-4" }),
|
|
2062
|
+
"Viewed ",
|
|
2063
|
+
question.views.toLocaleString(),
|
|
2064
|
+
" times"
|
|
2065
|
+
] })
|
|
2066
|
+
] }),
|
|
2067
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: question.tags.map((tag) => /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-xs", children: tag }, tag)) })
|
|
2068
|
+
] }),
|
|
2069
|
+
/* @__PURE__ */ jsx(Card2, { className: "mb-8", children: /* @__PURE__ */ jsxs(CardContent, { className: "flex gap-6 p-6", children: [
|
|
2070
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
|
|
2071
|
+
/* @__PURE__ */ jsx(
|
|
2072
|
+
VoteButtons,
|
|
2073
|
+
{
|
|
2074
|
+
votes: question.votes,
|
|
2075
|
+
userVote: question.userVote,
|
|
2076
|
+
onVote: handleQuestionVote,
|
|
2077
|
+
size: "large",
|
|
2078
|
+
label: "question"
|
|
2079
|
+
}
|
|
2080
|
+
),
|
|
2081
|
+
/* @__PURE__ */ jsx(
|
|
2082
|
+
Button,
|
|
2083
|
+
{
|
|
2084
|
+
type: "button",
|
|
2085
|
+
variant: "ghost",
|
|
2086
|
+
size: "sm",
|
|
2087
|
+
"aria-label": question.bookmarked ? "Remove bookmark" : "Bookmark question",
|
|
2088
|
+
"aria-pressed": question.bookmarked,
|
|
2089
|
+
onClick: handleBookmarkToggle,
|
|
2090
|
+
className: cn("p-2", question.bookmarked && "bg-warning/10 text-warning"),
|
|
2091
|
+
children: /* @__PURE__ */ jsx(Bookmark, { className: "h-4 w-4", "aria-hidden": "true" })
|
|
2092
|
+
}
|
|
2093
|
+
)
|
|
2094
|
+
] }),
|
|
2095
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
2096
|
+
/* @__PURE__ */ jsx(MarkdownRenderer, { content: question.content }),
|
|
2097
|
+
/* @__PURE__ */ jsx(
|
|
2098
|
+
PostActions,
|
|
2099
|
+
{
|
|
2100
|
+
className: "mt-6",
|
|
2101
|
+
author: question.author,
|
|
2102
|
+
timestamp: question.timestamp,
|
|
2103
|
+
verb: "asked",
|
|
2104
|
+
shareLabel: "Share Question",
|
|
2105
|
+
editLabel: "Edit Question",
|
|
2106
|
+
flagLabel: "Flag Question"
|
|
2107
|
+
}
|
|
2108
|
+
)
|
|
2109
|
+
] })
|
|
2110
|
+
] }) }),
|
|
2111
|
+
/* @__PURE__ */ jsx("div", { className: "mb-6 flex items-center justify-between", children: /* @__PURE__ */ jsxs("h2", { className: "font-semibold text-foreground text-xl", children: [
|
|
2112
|
+
answers.length,
|
|
2113
|
+
" ",
|
|
2114
|
+
answers.length === 1 ? "Answer" : "Answers"
|
|
2115
|
+
] }) }),
|
|
2116
|
+
/* @__PURE__ */ jsx("div", { className: "mb-8", children: sortedAnswers.map((answer, index) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
2117
|
+
/* @__PURE__ */ jsx(Card2, { className: cn("mb-6", answer.isAccepted && "ring-2 ring-success/40"), children: /* @__PURE__ */ jsxs(CardContent, { className: "flex gap-6 p-6", children: [
|
|
2118
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
|
|
2119
|
+
/* @__PURE__ */ jsx(
|
|
2120
|
+
VoteButtons,
|
|
2121
|
+
{
|
|
2122
|
+
votes: answer.votes,
|
|
2123
|
+
userVote: answer.userVote,
|
|
2124
|
+
onVote: (v) => handleAnswerVote(answer.id, v),
|
|
2125
|
+
size: "large",
|
|
2126
|
+
label: "answer"
|
|
2127
|
+
}
|
|
2128
|
+
),
|
|
2129
|
+
/* @__PURE__ */ jsx(
|
|
2130
|
+
Button,
|
|
2131
|
+
{
|
|
2132
|
+
type: "button",
|
|
2133
|
+
variant: "ghost",
|
|
2134
|
+
size: "sm",
|
|
2135
|
+
"aria-label": answer.isAccepted ? "Unaccept this answer" : "Accept this answer",
|
|
2136
|
+
"aria-pressed": answer.isAccepted,
|
|
2137
|
+
onClick: () => handleAccept(answer.id),
|
|
2138
|
+
className: cn("p-2", answer.isAccepted && "bg-success/10 text-success"),
|
|
2139
|
+
children: /* @__PURE__ */ jsx(Check, { className: "h-5 w-5", "aria-hidden": "true" })
|
|
2140
|
+
}
|
|
2141
|
+
),
|
|
2142
|
+
answer.isAccepted && /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
|
|
2143
|
+
/* @__PURE__ */ jsx(StarFill, { "aria-hidden": "true", className: "mx-auto mb-1 h-4 w-4 text-success" }),
|
|
2144
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-success text-xs", children: "Accepted" })
|
|
2145
|
+
] })
|
|
2146
|
+
] }),
|
|
2147
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
2148
|
+
/* @__PURE__ */ jsx(MarkdownRenderer, { content: answer.content }),
|
|
2149
|
+
/* @__PURE__ */ jsx(
|
|
2150
|
+
PostActions,
|
|
2151
|
+
{
|
|
2152
|
+
className: "mt-6",
|
|
2153
|
+
author: answer.author,
|
|
2154
|
+
timestamp: answer.timestamp,
|
|
2155
|
+
verb: "answered",
|
|
2156
|
+
shareLabel: "Share Answer",
|
|
2157
|
+
editLabel: "Edit Answer",
|
|
2158
|
+
flagLabel: "Flag Answer"
|
|
2159
|
+
}
|
|
2160
|
+
)
|
|
2161
|
+
] })
|
|
2162
|
+
] }) }),
|
|
2163
|
+
index < sortedAnswers.length - 1 && /* @__PURE__ */ jsx(Separator, { className: "mb-6" })
|
|
2164
|
+
] }, answer.id)) }),
|
|
2165
|
+
/* @__PURE__ */ jsxs(Card2, { children: [
|
|
2166
|
+
/* @__PURE__ */ jsx(CardHeader2, { children: /* @__PURE__ */ jsx("h3", { className: "font-semibold text-foreground text-lg", children: "Your Answer" }) }),
|
|
2167
|
+
/* @__PURE__ */ jsxs(CardContent, { className: "space-y-4", children: [
|
|
2168
|
+
/* @__PURE__ */ jsx(MarkdownEditor, { value: draft, onChange: setDraft, placeholder: "Write your answer here\u2026" }),
|
|
2169
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
2170
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: "Thanks for contributing an answer!" }),
|
|
2171
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
2172
|
+
/* @__PURE__ */ jsx(
|
|
2173
|
+
Button,
|
|
2174
|
+
{
|
|
2175
|
+
type: "button",
|
|
2176
|
+
variant: "outline",
|
|
2177
|
+
onClick: () => setDraft(""),
|
|
2178
|
+
disabled: !draft,
|
|
2179
|
+
children: "Discard Draft"
|
|
2180
|
+
}
|
|
2181
|
+
),
|
|
2182
|
+
/* @__PURE__ */ jsx(Button, { type: "button", onClick: handleSubmit, disabled: !draft.trim(), children: "Post Your Answer" })
|
|
2183
|
+
] })
|
|
2184
|
+
] })
|
|
2185
|
+
] })
|
|
2186
|
+
] })
|
|
2187
|
+
] });
|
|
2188
|
+
}
|
|
2189
|
+
function PostActions({
|
|
2190
|
+
author,
|
|
2191
|
+
timestamp,
|
|
2192
|
+
verb,
|
|
2193
|
+
shareLabel,
|
|
2194
|
+
editLabel,
|
|
2195
|
+
flagLabel,
|
|
2196
|
+
className
|
|
2197
|
+
}) {
|
|
2198
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-wrap items-center justify-between gap-3", className), children: [
|
|
2199
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
2200
|
+
/* @__PURE__ */ jsxs(Button, { type: "button", variant: "ghost", size: "sm", className: "text-xs", children: [
|
|
2201
|
+
/* @__PURE__ */ jsx(Share, { className: "mr-1 h-3 w-3", "aria-hidden": "true" }),
|
|
2202
|
+
shareLabel
|
|
2203
|
+
] }),
|
|
2204
|
+
/* @__PURE__ */ jsxs(Button, { type: "button", variant: "ghost", size: "sm", className: "text-xs", children: [
|
|
2205
|
+
/* @__PURE__ */ jsx(Pencil, { className: "mr-1 h-3 w-3", "aria-hidden": "true" }),
|
|
2206
|
+
editLabel
|
|
2207
|
+
] }),
|
|
2208
|
+
/* @__PURE__ */ jsxs(Button, { type: "button", variant: "ghost", size: "sm", className: "text-xs", children: [
|
|
2209
|
+
/* @__PURE__ */ jsx(Flag, { className: "mr-1 h-3 w-3", "aria-hidden": "true" }),
|
|
2210
|
+
flagLabel
|
|
2211
|
+
] })
|
|
2212
|
+
] }),
|
|
2213
|
+
/* @__PURE__ */ jsx(UserInfo, { author, timestamp, verb })
|
|
2214
|
+
] });
|
|
2215
|
+
}
|
|
2216
|
+
var CollapsibleContext = React14.createContext({});
|
|
2217
|
+
var Collapsible = React14.forwardRef(({ className, open: openProp, defaultOpen, onOpenChange, disabled, children, ...props }, ref) => {
|
|
2218
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React14.useState(defaultOpen || false);
|
|
2219
|
+
const isControlled = openProp !== void 0;
|
|
2220
|
+
const open = isControlled ? openProp : uncontrolledOpen;
|
|
2221
|
+
const handleOpenChange = React14.useCallback(
|
|
2222
|
+
(nextOpen) => {
|
|
2223
|
+
if (!isControlled) {
|
|
2224
|
+
setUncontrolledOpen(nextOpen);
|
|
2225
|
+
}
|
|
2226
|
+
onOpenChange?.(nextOpen);
|
|
2227
|
+
},
|
|
2228
|
+
[isControlled, onOpenChange]
|
|
2229
|
+
);
|
|
2230
|
+
return /* @__PURE__ */ jsx(CollapsibleContext.Provider, { value: { open, onOpenChange: handleOpenChange, disabled }, children: /* @__PURE__ */ jsx(
|
|
2231
|
+
"div",
|
|
2232
|
+
{
|
|
2233
|
+
ref,
|
|
2234
|
+
className,
|
|
2235
|
+
"data-state": open ? "open" : "closed",
|
|
2236
|
+
"data-disabled": disabled ? "" : void 0,
|
|
2237
|
+
...props,
|
|
2238
|
+
children
|
|
2239
|
+
}
|
|
2240
|
+
) });
|
|
2241
|
+
});
|
|
2242
|
+
Collapsible.displayName = "Collapsible";
|
|
2243
|
+
var CollapsibleTrigger = React14.forwardRef(({ className, asChild, children, ...props }, ref) => {
|
|
2244
|
+
const { open, onOpenChange, disabled } = React14.useContext(CollapsibleContext);
|
|
2245
|
+
const handleClick = React14.useCallback(
|
|
2246
|
+
(e) => {
|
|
2247
|
+
if (disabled) return;
|
|
2248
|
+
onOpenChange?.(!open);
|
|
2249
|
+
props.onClick?.(e);
|
|
2250
|
+
},
|
|
2251
|
+
[disabled, open, onOpenChange, props.onClick]
|
|
2252
|
+
);
|
|
2253
|
+
if (asChild && React14.isValidElement(children)) {
|
|
2254
|
+
return React14.cloneElement(children, {
|
|
2255
|
+
ref,
|
|
2256
|
+
onClick: handleClick,
|
|
2257
|
+
"data-state": open ? "open" : "closed",
|
|
2258
|
+
"data-disabled": disabled ? "" : void 0,
|
|
2259
|
+
"aria-expanded": open,
|
|
2260
|
+
"aria-controls": props["aria-controls"],
|
|
2261
|
+
disabled: disabled || props.disabled,
|
|
2262
|
+
...props
|
|
2263
|
+
});
|
|
2264
|
+
}
|
|
2265
|
+
return /* @__PURE__ */ jsx(
|
|
2266
|
+
"button",
|
|
2267
|
+
{
|
|
2268
|
+
ref,
|
|
2269
|
+
type: "button",
|
|
2270
|
+
onClick: handleClick,
|
|
2271
|
+
"data-state": open ? "open" : "closed",
|
|
2272
|
+
"data-disabled": disabled ? "" : void 0,
|
|
2273
|
+
"aria-expanded": open,
|
|
2274
|
+
disabled: disabled || props.disabled,
|
|
2275
|
+
className,
|
|
2276
|
+
...props,
|
|
2277
|
+
children
|
|
2278
|
+
}
|
|
2279
|
+
);
|
|
2280
|
+
});
|
|
2281
|
+
CollapsibleTrigger.displayName = "CollapsibleTrigger";
|
|
2282
|
+
var CollapsibleContent = React14.forwardRef(
|
|
2283
|
+
({ className, children, ...props }, ref) => {
|
|
2284
|
+
const { open } = React14.useContext(CollapsibleContext);
|
|
2285
|
+
return /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: open && /* @__PURE__ */ jsx(
|
|
2286
|
+
motion.div,
|
|
2287
|
+
{
|
|
2288
|
+
initial: "collapsed",
|
|
2289
|
+
animate: "open",
|
|
2290
|
+
exit: "collapsed",
|
|
2291
|
+
variants: {
|
|
2292
|
+
open: { opacity: 1, height: "auto" },
|
|
2293
|
+
collapsed: { opacity: 0, height: 0 }
|
|
2294
|
+
},
|
|
2295
|
+
transition: { duration: 0.3, ease: [0.04, 0.62, 0.23, 0.98] },
|
|
2296
|
+
className: "overflow-hidden",
|
|
2297
|
+
children: /* @__PURE__ */ jsx("div", { ref, "data-state": "open", className: cn("", className), ...props, children })
|
|
2298
|
+
},
|
|
2299
|
+
"content"
|
|
2300
|
+
) });
|
|
2301
|
+
}
|
|
2302
|
+
);
|
|
2303
|
+
CollapsibleContent.displayName = "CollapsibleContent";
|
|
2304
|
+
var ITEM_BASE_CLASSES = "flex items-center gap-2 rounded-md px-2.5 py-1.5 text-[13px] leading-5 transition-colors";
|
|
2305
|
+
var ITEM_DEFAULT_CLASSES = "text-foreground hover:bg-accent hover:text-accent-foreground";
|
|
2306
|
+
var ITEM_ACTIVE_CLASSES = "bg-accent text-accent-foreground font-medium";
|
|
2307
|
+
var ITEM_DISABLED_CLASSES = "opacity-50 pointer-events-none";
|
|
2308
|
+
var ITEM_COLLAPSED_CLASSES = "justify-center px-0 size-8 mx-auto";
|
|
2309
|
+
var ICON_CLASSES = "size-3.5 shrink-0";
|
|
2310
|
+
function defaultRenderLink({
|
|
2311
|
+
href,
|
|
2312
|
+
children,
|
|
2313
|
+
className,
|
|
2314
|
+
"aria-current": ariaCurrent,
|
|
2315
|
+
"aria-label": ariaLabel,
|
|
2316
|
+
onClick
|
|
2317
|
+
}) {
|
|
2318
|
+
return /* @__PURE__ */ jsx(
|
|
2319
|
+
"a",
|
|
2320
|
+
{
|
|
2321
|
+
href,
|
|
2322
|
+
className,
|
|
2323
|
+
"aria-current": ariaCurrent,
|
|
2324
|
+
"aria-label": ariaLabel,
|
|
2325
|
+
onClick,
|
|
2326
|
+
children
|
|
2327
|
+
}
|
|
2328
|
+
);
|
|
2329
|
+
}
|
|
2330
|
+
function ItemContent({
|
|
2331
|
+
item,
|
|
2332
|
+
collapsed,
|
|
2333
|
+
showChevron,
|
|
2334
|
+
chevronOpen
|
|
2335
|
+
}) {
|
|
2336
|
+
const Icon = item.icon;
|
|
2337
|
+
if (collapsed) {
|
|
2338
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2339
|
+
Icon ? /* @__PURE__ */ jsx(Icon, { className: ICON_CLASSES }) : null,
|
|
2340
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: item.label })
|
|
2341
|
+
] });
|
|
2342
|
+
}
|
|
2343
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2344
|
+
Icon ? /* @__PURE__ */ jsx(Icon, { className: ICON_CLASSES }) : null,
|
|
2345
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: item.label }),
|
|
2346
|
+
item.badge ? /* @__PURE__ */ jsx(Badge, { variant: item.badge.tone, size: "sm", children: item.badge.label }) : null,
|
|
2347
|
+
showChevron ? /* @__PURE__ */ jsx(
|
|
2348
|
+
ChevronRight,
|
|
2349
|
+
{
|
|
2350
|
+
className: cn(
|
|
2351
|
+
"h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform",
|
|
2352
|
+
chevronOpen && "rotate-90"
|
|
2353
|
+
),
|
|
2354
|
+
"aria-hidden": "true"
|
|
2355
|
+
}
|
|
2356
|
+
) : null
|
|
2357
|
+
] });
|
|
2358
|
+
}
|
|
2359
|
+
function InteractiveItem({
|
|
2360
|
+
item,
|
|
2361
|
+
collapsed,
|
|
2362
|
+
itemClassName,
|
|
2363
|
+
renderLink,
|
|
2364
|
+
nested = false
|
|
2365
|
+
}) {
|
|
2366
|
+
const isActive = item.isActive === true;
|
|
2367
|
+
const isDisabled = item.disabled === true;
|
|
2368
|
+
const classes = cn(
|
|
2369
|
+
ITEM_BASE_CLASSES,
|
|
2370
|
+
isActive ? ITEM_ACTIVE_CLASSES : ITEM_DEFAULT_CLASSES,
|
|
2371
|
+
isDisabled && ITEM_DISABLED_CLASSES,
|
|
2372
|
+
collapsed && ITEM_COLLAPSED_CLASSES,
|
|
2373
|
+
!collapsed && nested && "pl-9",
|
|
2374
|
+
itemClassName
|
|
2375
|
+
);
|
|
2376
|
+
const ariaCurrent = isActive ? "page" : void 0;
|
|
2377
|
+
const ariaDisabled = isDisabled ? true : void 0;
|
|
2378
|
+
const inner = /* @__PURE__ */ jsx(ItemContent, { item, collapsed });
|
|
2379
|
+
let element;
|
|
2380
|
+
if (item.href && !isDisabled) {
|
|
2381
|
+
if (item.external) {
|
|
2382
|
+
element = /* @__PURE__ */ jsx(
|
|
2383
|
+
"a",
|
|
2384
|
+
{
|
|
2385
|
+
href: item.href,
|
|
2386
|
+
target: "_blank",
|
|
2387
|
+
rel: "noopener noreferrer",
|
|
2388
|
+
className: classes,
|
|
2389
|
+
"aria-current": ariaCurrent,
|
|
2390
|
+
"aria-label": collapsed ? item.label : void 0,
|
|
2391
|
+
onClick: item.onClick,
|
|
2392
|
+
children: inner
|
|
2393
|
+
}
|
|
2394
|
+
);
|
|
2395
|
+
} else {
|
|
2396
|
+
element = renderLink({
|
|
2397
|
+
href: item.href,
|
|
2398
|
+
className: classes,
|
|
2399
|
+
children: inner,
|
|
2400
|
+
"aria-current": ariaCurrent,
|
|
2401
|
+
"aria-label": collapsed ? item.label : void 0,
|
|
2402
|
+
onClick: item.onClick
|
|
2403
|
+
});
|
|
2404
|
+
}
|
|
2405
|
+
} else {
|
|
2406
|
+
element = /* @__PURE__ */ jsx(
|
|
2407
|
+
"button",
|
|
2408
|
+
{
|
|
2409
|
+
type: "button",
|
|
2410
|
+
className: cn(classes, "w-full text-left"),
|
|
2411
|
+
"aria-current": ariaCurrent,
|
|
2412
|
+
"aria-disabled": ariaDisabled,
|
|
2413
|
+
"aria-label": collapsed ? item.label : void 0,
|
|
2414
|
+
disabled: isDisabled,
|
|
2415
|
+
onClick: item.onClick,
|
|
2416
|
+
children: inner
|
|
2417
|
+
}
|
|
2418
|
+
);
|
|
2419
|
+
}
|
|
2420
|
+
if (collapsed) {
|
|
2421
|
+
return /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
2422
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: element }),
|
|
2423
|
+
/* @__PURE__ */ jsx(TooltipContent, { side: "right", sideOffset: 8, children: item.label })
|
|
2424
|
+
] });
|
|
2425
|
+
}
|
|
2426
|
+
return element;
|
|
2427
|
+
}
|
|
2428
|
+
function ParentItem({
|
|
2429
|
+
item,
|
|
2430
|
+
collapsed,
|
|
2431
|
+
itemClassName,
|
|
2432
|
+
renderLink
|
|
2433
|
+
}) {
|
|
2434
|
+
const hasActiveChild = React14.useMemo(
|
|
2435
|
+
() => item.children?.some((c) => c.isActive === true) ?? false,
|
|
2436
|
+
[item.children]
|
|
2437
|
+
);
|
|
2438
|
+
const [open, setOpen] = React14.useState(item.isActive === true || hasActiveChild);
|
|
2439
|
+
if (collapsed) {
|
|
2440
|
+
return /* @__PURE__ */ jsx(
|
|
2441
|
+
InteractiveItem,
|
|
2442
|
+
{
|
|
2443
|
+
item,
|
|
2444
|
+
collapsed: true,
|
|
2445
|
+
itemClassName,
|
|
2446
|
+
renderLink
|
|
2447
|
+
}
|
|
2448
|
+
);
|
|
2449
|
+
}
|
|
2450
|
+
const isActive = item.isActive === true;
|
|
2451
|
+
const isDisabled = item.disabled === true;
|
|
2452
|
+
const triggerClasses = cn(
|
|
2453
|
+
ITEM_BASE_CLASSES,
|
|
2454
|
+
"w-full text-left",
|
|
2455
|
+
isActive ? ITEM_ACTIVE_CLASSES : ITEM_DEFAULT_CLASSES,
|
|
2456
|
+
isDisabled && ITEM_DISABLED_CLASSES,
|
|
2457
|
+
itemClassName
|
|
2458
|
+
);
|
|
2459
|
+
return /* @__PURE__ */ jsxs(Collapsible, { open, onOpenChange: setOpen, children: [
|
|
2460
|
+
/* @__PURE__ */ jsx(
|
|
2461
|
+
CollapsibleTrigger,
|
|
2462
|
+
{
|
|
2463
|
+
className: triggerClasses,
|
|
2464
|
+
"aria-current": isActive ? "page" : void 0,
|
|
2465
|
+
disabled: isDisabled,
|
|
2466
|
+
children: /* @__PURE__ */ jsx(ItemContent, { item, collapsed: false, showChevron: true, chevronOpen: open })
|
|
2467
|
+
}
|
|
2468
|
+
),
|
|
2469
|
+
/* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("ul", { className: "mt-1 flex flex-col gap-0.5", children: item.children?.map((child) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
2470
|
+
InteractiveItem,
|
|
2471
|
+
{
|
|
2472
|
+
item: child,
|
|
2473
|
+
collapsed: false,
|
|
2474
|
+
itemClassName,
|
|
2475
|
+
renderLink,
|
|
2476
|
+
nested: true
|
|
2477
|
+
}
|
|
2478
|
+
) }, child.id)) }) })
|
|
2479
|
+
] });
|
|
2480
|
+
}
|
|
2481
|
+
function SidebarNav({
|
|
2482
|
+
sections,
|
|
2483
|
+
collapsed = false,
|
|
2484
|
+
className,
|
|
2485
|
+
itemClassName,
|
|
2486
|
+
header,
|
|
2487
|
+
footer,
|
|
2488
|
+
renderLink = defaultRenderLink
|
|
2489
|
+
}) {
|
|
2490
|
+
return /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsxs(
|
|
2491
|
+
"nav",
|
|
2492
|
+
{
|
|
2493
|
+
"aria-label": "Sidebar",
|
|
2494
|
+
className: cn(
|
|
2495
|
+
"flex h-full flex-col gap-4",
|
|
2496
|
+
collapsed ? "px-2 py-2.5" : "px-2.5 py-3",
|
|
2497
|
+
className
|
|
2498
|
+
),
|
|
2499
|
+
children: [
|
|
2500
|
+
header ? /* @__PURE__ */ jsx("div", { className: "shrink-0", children: header }) : null,
|
|
2501
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 space-y-4 overflow-y-auto", children: sections.map((section) => /* @__PURE__ */ jsxs("div", { children: [
|
|
2502
|
+
section.label && !collapsed ? /* @__PURE__ */ jsx("div", { className: "mb-1.5 px-2.5 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground", children: section.label }) : null,
|
|
2503
|
+
/* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-0.5", children: section.items.map((item) => {
|
|
2504
|
+
const hasChildren = Array.isArray(item.children) && item.children.length > 0;
|
|
2505
|
+
return /* @__PURE__ */ jsx("li", { children: hasChildren ? /* @__PURE__ */ jsx(
|
|
2506
|
+
ParentItem,
|
|
2507
|
+
{
|
|
2508
|
+
item,
|
|
2509
|
+
collapsed,
|
|
2510
|
+
itemClassName,
|
|
2511
|
+
renderLink
|
|
2512
|
+
}
|
|
2513
|
+
) : /* @__PURE__ */ jsx(
|
|
2514
|
+
InteractiveItem,
|
|
2515
|
+
{
|
|
2516
|
+
item,
|
|
2517
|
+
collapsed,
|
|
2518
|
+
itemClassName,
|
|
2519
|
+
renderLink
|
|
2520
|
+
}
|
|
2521
|
+
) }, item.id);
|
|
2522
|
+
}) })
|
|
2523
|
+
] }, section.id)) }),
|
|
2524
|
+
footer ? /* @__PURE__ */ jsx("div", { className: "shrink-0 border-t border-border pt-3", children: footer }) : null
|
|
2525
|
+
]
|
|
2526
|
+
}
|
|
2527
|
+
) });
|
|
2528
|
+
}
|
|
2529
|
+
var TerminalContext = React14.createContext(null);
|
|
2530
|
+
var variantStyles2 = {
|
|
2531
|
+
default: "bg-[var(--neutral-2)] border border-[var(--neutral-7)]",
|
|
2532
|
+
minimal: "bg-[var(--neutral-1)] border border-[var(--neutral-7)]",
|
|
2533
|
+
glass: "bg-[var(--neutral-2)] backdrop-blur-md border border-[var(--neutral-7)]"
|
|
2534
|
+
};
|
|
2535
|
+
var TerminalRoot = React14.forwardRef(
|
|
2536
|
+
({ className, variant = "default", children, ...props }, ref) => {
|
|
2537
|
+
return /* @__PURE__ */ jsx(TerminalContext.Provider, { value: { variant }, children: /* @__PURE__ */ jsx(
|
|
2538
|
+
"div",
|
|
2539
|
+
{
|
|
2540
|
+
ref,
|
|
2541
|
+
className: cn(
|
|
2542
|
+
"rounded-[var(--radius-xl)] overflow-hidden font-mono text-sm",
|
|
2543
|
+
variantStyles2[variant],
|
|
2544
|
+
className
|
|
2545
|
+
),
|
|
2546
|
+
...props,
|
|
2547
|
+
children
|
|
2548
|
+
}
|
|
2549
|
+
) });
|
|
2550
|
+
}
|
|
2551
|
+
);
|
|
2552
|
+
TerminalRoot.displayName = "Terminal";
|
|
2553
|
+
var TerminalHeader = React14.forwardRef(
|
|
2554
|
+
({ className, title, showControls = true, children, ...props }, ref) => {
|
|
2555
|
+
return /* @__PURE__ */ jsxs(
|
|
2556
|
+
"div",
|
|
2557
|
+
{
|
|
2558
|
+
ref,
|
|
2559
|
+
className: cn(
|
|
2560
|
+
"flex items-center gap-2 px-4 py-3 border-b border-[var(--neutral-7)] bg-[var(--neutral-3)]",
|
|
2561
|
+
className
|
|
2562
|
+
),
|
|
2563
|
+
...props,
|
|
2564
|
+
children: [
|
|
2565
|
+
showControls && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
2566
|
+
/* @__PURE__ */ jsx("span", { className: "w-3 h-3 rounded-full bg-red-500/80", "aria-hidden": true }),
|
|
2567
|
+
/* @__PURE__ */ jsx("span", { className: "w-3 h-3 rounded-full bg-yellow-500/80", "aria-hidden": true }),
|
|
2568
|
+
/* @__PURE__ */ jsx("span", { className: "w-3 h-3 rounded-full bg-green-500/80", "aria-hidden": true })
|
|
2569
|
+
] }),
|
|
2570
|
+
title && /* @__PURE__ */ jsx("span", { className: "ml-2 text-xs text-[var(--neutral-9)]", children: title }),
|
|
2571
|
+
children
|
|
2572
|
+
]
|
|
2573
|
+
}
|
|
2574
|
+
);
|
|
2575
|
+
}
|
|
2576
|
+
);
|
|
2577
|
+
TerminalHeader.displayName = "Terminal.Header";
|
|
2578
|
+
var TerminalBody = React14.forwardRef(
|
|
2579
|
+
({ className, children, ...props }, ref) => {
|
|
2580
|
+
return /* @__PURE__ */ jsx("div", { ref, className: cn("p-4 space-y-1", className), ...props, children });
|
|
2581
|
+
}
|
|
2582
|
+
);
|
|
2583
|
+
TerminalBody.displayName = "Terminal.Body";
|
|
2584
|
+
var TerminalLine = React14.forwardRef(
|
|
2585
|
+
({ className, prompt = "$", output = false, highlight = false, children, ...props }, ref) => {
|
|
2586
|
+
return /* @__PURE__ */ jsxs(
|
|
2587
|
+
"div",
|
|
2588
|
+
{
|
|
2589
|
+
ref,
|
|
2590
|
+
className: cn(
|
|
2591
|
+
"flex items-start gap-2",
|
|
2592
|
+
highlight && "bg-[var(--neutral-3)] -mx-2 px-2 py-0.5 rounded",
|
|
2593
|
+
className
|
|
2594
|
+
),
|
|
2595
|
+
...props,
|
|
2596
|
+
children: [
|
|
2597
|
+
!output && /* @__PURE__ */ jsx("span", { className: "text-emerald-400 shrink-0 select-none", children: prompt }),
|
|
2598
|
+
/* @__PURE__ */ jsx(
|
|
2599
|
+
"span",
|
|
2600
|
+
{
|
|
2601
|
+
className: cn("flex-1", output ? "text-[var(--neutral-9)]" : "text-[var(--neutral-11)]"),
|
|
2602
|
+
children
|
|
2603
|
+
}
|
|
2604
|
+
)
|
|
2605
|
+
]
|
|
2606
|
+
}
|
|
2607
|
+
);
|
|
2608
|
+
}
|
|
2609
|
+
);
|
|
2610
|
+
TerminalLine.displayName = "Terminal.Line";
|
|
2611
|
+
var Terminal = Object.assign(TerminalRoot, {
|
|
2612
|
+
Header: TerminalHeader,
|
|
2613
|
+
Body: TerminalBody,
|
|
2614
|
+
Line: TerminalLine
|
|
2615
|
+
});
|
|
2616
|
+
var modifierOrder = ["meta", "shift", "alt", "ctrl"];
|
|
2617
|
+
var appleModifierSymbols = {
|
|
2618
|
+
meta: "\u2318",
|
|
2619
|
+
shift: "\u21E7",
|
|
2620
|
+
alt: "\u2325",
|
|
2621
|
+
ctrl: "\u2303"
|
|
2622
|
+
};
|
|
2623
|
+
var otherModifierSymbols = {
|
|
2624
|
+
meta: "Ctrl",
|
|
2625
|
+
shift: "Shift",
|
|
2626
|
+
alt: "Alt",
|
|
2627
|
+
ctrl: "Ctrl"
|
|
2628
|
+
};
|
|
2629
|
+
var modifierLabels = {
|
|
2630
|
+
meta: "Command or Control",
|
|
2631
|
+
shift: "Shift",
|
|
2632
|
+
alt: "Option or Alt",
|
|
2633
|
+
ctrl: "Control"
|
|
2634
|
+
};
|
|
2635
|
+
function getClientPlatform() {
|
|
2636
|
+
const platform = window.navigator.platform.toLowerCase();
|
|
2637
|
+
const userAgent = window.navigator.userAgent.toLowerCase();
|
|
2638
|
+
const isApple = /mac|iphone|ipad|ipod/.test(platform) || /mac|iphone|ipad|ipod/.test(userAgent);
|
|
2639
|
+
return isApple ? "apple" : "other";
|
|
2640
|
+
}
|
|
2641
|
+
function subscribePlatform() {
|
|
2642
|
+
return () => void 0;
|
|
2643
|
+
}
|
|
2644
|
+
function usePlatform() {
|
|
2645
|
+
return React14.useSyncExternalStore(subscribePlatform, getClientPlatform, () => "apple");
|
|
2646
|
+
}
|
|
2647
|
+
function normalizeKey(children) {
|
|
2648
|
+
if (typeof children !== "string") {
|
|
2649
|
+
return children;
|
|
2650
|
+
}
|
|
2651
|
+
const trimmed = children.trim();
|
|
2652
|
+
if (/^[a-z]$/.test(trimmed)) {
|
|
2653
|
+
return trimmed.toUpperCase();
|
|
2654
|
+
}
|
|
2655
|
+
return trimmed;
|
|
2656
|
+
}
|
|
2657
|
+
function getModifierSymbols(platform) {
|
|
2658
|
+
return platform === "apple" ? appleModifierSymbols : otherModifierSymbols;
|
|
2659
|
+
}
|
|
2660
|
+
var Kbd = React14.forwardRef(
|
|
2661
|
+
({ meta, shift, alt, ctrl, small, children, className, "aria-label": ariaLabel, ...props }, ref) => {
|
|
2662
|
+
const platform = usePlatform();
|
|
2663
|
+
const symbols = getModifierSymbols(platform);
|
|
2664
|
+
const activeModifiers = modifierOrder.filter((modifier) => {
|
|
2665
|
+
if (modifier === "meta") return meta;
|
|
2666
|
+
if (modifier === "shift") return shift;
|
|
2667
|
+
if (modifier === "alt") return alt;
|
|
2668
|
+
return ctrl;
|
|
2669
|
+
});
|
|
2670
|
+
const normalizedKey = normalizeKey(children);
|
|
2671
|
+
const visualParts = [
|
|
2672
|
+
...activeModifiers.map((modifier) => symbols[modifier]),
|
|
2673
|
+
normalizedKey
|
|
2674
|
+
].filter(Boolean);
|
|
2675
|
+
const accessibleName = ariaLabel ?? [
|
|
2676
|
+
...activeModifiers.map((modifier) => modifierLabels[modifier]),
|
|
2677
|
+
typeof normalizedKey === "string" ? normalizedKey : void 0
|
|
2678
|
+
].filter(Boolean).join(" ");
|
|
2679
|
+
return /* @__PURE__ */ jsx(
|
|
2680
|
+
"kbd",
|
|
2681
|
+
{
|
|
2682
|
+
ref,
|
|
2683
|
+
"aria-label": accessibleName || void 0,
|
|
2684
|
+
className: cn(
|
|
2685
|
+
"inline-flex select-none items-center justify-center gap-0.5 rounded-[var(--radius-sm)] border border-border bg-muted font-mono font-medium leading-none text-muted-foreground tabular-nums",
|
|
2686
|
+
small ? "min-h-4 min-w-4 px-1 py-0 text-xs" : "min-h-5 min-w-5 px-1.5 py-0.5 text-xs",
|
|
2687
|
+
className
|
|
2688
|
+
),
|
|
2689
|
+
...props,
|
|
2690
|
+
children: visualParts.join("")
|
|
2691
|
+
}
|
|
2692
|
+
);
|
|
2693
|
+
}
|
|
2694
|
+
);
|
|
2695
|
+
Kbd.displayName = "Kbd";
|
|
2696
|
+
function computeInitials(user) {
|
|
2697
|
+
if (user.initials && user.initials.trim().length > 0) {
|
|
2698
|
+
return user.initials.toUpperCase().slice(0, 2);
|
|
2699
|
+
}
|
|
2700
|
+
const source = (user.name ?? "").trim() || (user.email ?? "").trim();
|
|
2701
|
+
if (!source) return "?";
|
|
2702
|
+
const tokens = source.split(/\s+/).filter(Boolean);
|
|
2703
|
+
if (tokens.length >= 2) {
|
|
2704
|
+
const first = tokens[0]?.[0] ?? "";
|
|
2705
|
+
const second = tokens[1]?.[0] ?? "";
|
|
2706
|
+
return (first + second).toUpperCase();
|
|
2707
|
+
}
|
|
2708
|
+
return source.slice(0, 2).toUpperCase();
|
|
2709
|
+
}
|
|
2710
|
+
function DefaultTrigger({ user }) {
|
|
2711
|
+
const initials2 = computeInitials(user);
|
|
2712
|
+
return /* @__PURE__ */ jsx(
|
|
2713
|
+
"button",
|
|
2714
|
+
{
|
|
2715
|
+
type: "button",
|
|
2716
|
+
"aria-label": "Open user menu",
|
|
2717
|
+
className: cn(
|
|
2718
|
+
"inline-flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-full",
|
|
2719
|
+
"outline-none transition-all hover:opacity-90",
|
|
2720
|
+
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
2721
|
+
"hover:ring-1 hover:ring-border"
|
|
2722
|
+
),
|
|
2723
|
+
children: /* @__PURE__ */ jsxs(Avatar, { size: "sm", children: [
|
|
2724
|
+
user.avatarUrl ? /* @__PURE__ */ jsx(AvatarImage, { src: user.avatarUrl, alt: user.name }) : null,
|
|
2725
|
+
/* @__PURE__ */ jsx(AvatarFallback, { size: "sm", className: "bg-[image:var(--brand-gradient)] text-white", children: initials2 })
|
|
2726
|
+
] })
|
|
2727
|
+
}
|
|
2728
|
+
);
|
|
2729
|
+
}
|
|
2730
|
+
function UserMenuItemContent({ item }) {
|
|
2731
|
+
const Icon = item.icon;
|
|
2732
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2733
|
+
Icon ? /* @__PURE__ */ jsx(Icon, { className: "mr-2 h-4 w-4 shrink-0", "aria-hidden": true }) : null,
|
|
2734
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: item.label }),
|
|
2735
|
+
item.shortcut ? /* @__PURE__ */ jsx(Kbd, { className: "ml-2", small: true, children: item.shortcut }) : null
|
|
2736
|
+
] });
|
|
2737
|
+
}
|
|
2738
|
+
function UserMenu(props) {
|
|
2739
|
+
const {
|
|
2740
|
+
user,
|
|
2741
|
+
groups,
|
|
2742
|
+
workspaceSlot,
|
|
2743
|
+
renderTrigger,
|
|
2744
|
+
renderLink,
|
|
2745
|
+
className,
|
|
2746
|
+
align = "end"
|
|
2747
|
+
} = props;
|
|
2748
|
+
const initials2 = computeInitials(user);
|
|
2749
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
2750
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: renderTrigger ? renderTrigger(user) : /* @__PURE__ */ jsx(DefaultTrigger, { user }) }),
|
|
2751
|
+
/* @__PURE__ */ jsxs(DropdownMenuContent, { align, sideOffset: 8, className: cn("w-[280px] p-1", className), children: [
|
|
2752
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 p-3", children: [
|
|
2753
|
+
/* @__PURE__ */ jsxs(Avatar, { size: "md", children: [
|
|
2754
|
+
user.avatarUrl ? /* @__PURE__ */ jsx(AvatarImage, { src: user.avatarUrl, alt: user.name }) : null,
|
|
2755
|
+
/* @__PURE__ */ jsx(AvatarFallback, { size: "md", className: "bg-[image:var(--brand-gradient)] text-white", children: initials2 })
|
|
2756
|
+
] }),
|
|
2757
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
2758
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium text-foreground", children: user.name }),
|
|
2759
|
+
user.email ? /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: user.email }) : null
|
|
2760
|
+
] })
|
|
2761
|
+
] }),
|
|
2762
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
2763
|
+
workspaceSlot ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2764
|
+
/* @__PURE__ */ jsx("div", { className: "px-1 py-1", children: workspaceSlot }),
|
|
2765
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {})
|
|
2766
|
+
] }) : null,
|
|
2767
|
+
groups.map((group, groupIndex) => {
|
|
2768
|
+
const isLastGroup = groupIndex === groups.length - 1;
|
|
2769
|
+
return /* @__PURE__ */ jsxs(React14.Fragment, { children: [
|
|
2770
|
+
group.label ? /* @__PURE__ */ jsx("div", { className: "px-2 py-1 text-xs font-medium uppercase tracking-wider text-muted-foreground", children: group.label }) : null,
|
|
2771
|
+
group.items.map((item) => {
|
|
2772
|
+
const itemClassName = cn(
|
|
2773
|
+
"flex w-full cursor-pointer items-center",
|
|
2774
|
+
item.destructive && "text-destructive focus:bg-destructive focus:text-destructive-foreground data-[highlighted]:bg-destructive data-[highlighted]:text-destructive-foreground"
|
|
2775
|
+
);
|
|
2776
|
+
if (item.href && !item.disabled) {
|
|
2777
|
+
const linkContent = /* @__PURE__ */ jsx(UserMenuItemContent, { item });
|
|
2778
|
+
const linkElement = renderLink ? renderLink({ href: item.href, children: linkContent }) : /* @__PURE__ */ jsx("a", { href: item.href, children: linkContent });
|
|
2779
|
+
return /* @__PURE__ */ jsx(
|
|
2780
|
+
DropdownMenuItem,
|
|
2781
|
+
{
|
|
2782
|
+
className: itemClassName,
|
|
2783
|
+
onClick: item.onSelect,
|
|
2784
|
+
render: linkElement
|
|
2785
|
+
},
|
|
2786
|
+
item.id
|
|
2787
|
+
);
|
|
2788
|
+
}
|
|
2789
|
+
return /* @__PURE__ */ jsx(
|
|
2790
|
+
DropdownMenuItem,
|
|
2791
|
+
{
|
|
2792
|
+
className: itemClassName,
|
|
2793
|
+
disabled: item.disabled,
|
|
2794
|
+
onClick: () => {
|
|
2795
|
+
if (!item.disabled) item.onSelect?.();
|
|
2796
|
+
},
|
|
2797
|
+
children: /* @__PURE__ */ jsx(UserMenuItemContent, { item })
|
|
2798
|
+
},
|
|
2799
|
+
item.id
|
|
2800
|
+
);
|
|
2801
|
+
}),
|
|
2802
|
+
!isLastGroup ? /* @__PURE__ */ jsx(DropdownMenuSeparator, {}) : null
|
|
2803
|
+
] }, group.id);
|
|
2804
|
+
})
|
|
2805
|
+
] })
|
|
2806
|
+
] });
|
|
2807
|
+
}
|
|
2808
|
+
UserMenu.displayName = "UserMenu";
|
|
2809
|
+
function getInitials(workspace) {
|
|
2810
|
+
if (workspace.initials) return workspace.initials.slice(0, 2).toUpperCase();
|
|
2811
|
+
const trimmed = workspace.name.trim();
|
|
2812
|
+
if (!trimmed) return "??";
|
|
2813
|
+
const parts = trimmed.split(/\s+/).filter(Boolean);
|
|
2814
|
+
if (parts.length >= 2) {
|
|
2815
|
+
return `${parts[0][0] ?? ""}${parts[1][0] ?? ""}`.toUpperCase();
|
|
2816
|
+
}
|
|
2817
|
+
return trimmed.slice(0, 2).toUpperCase();
|
|
2818
|
+
}
|
|
2819
|
+
function roleLabel(role) {
|
|
2820
|
+
switch (role) {
|
|
2821
|
+
case "owner":
|
|
2822
|
+
return "Owner";
|
|
2823
|
+
case "admin":
|
|
2824
|
+
return "Admin";
|
|
2825
|
+
case "member":
|
|
2826
|
+
return "Member";
|
|
2827
|
+
case "guest":
|
|
2828
|
+
return "Guest";
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
function WorkspaceAvatar({ workspace, size = "sm", className }) {
|
|
2832
|
+
const initials2 = getInitials(workspace);
|
|
2833
|
+
return /* @__PURE__ */ jsxs(Avatar, { size, className: cn("rounded-md", className), children: [
|
|
2834
|
+
workspace.avatarUrl && /* @__PURE__ */ jsx(AvatarImage, { src: workspace.avatarUrl, alt: workspace.name, className: "rounded-md" }),
|
|
2835
|
+
/* @__PURE__ */ jsx(
|
|
2836
|
+
AvatarFallback,
|
|
2837
|
+
{
|
|
2838
|
+
size,
|
|
2839
|
+
className: "rounded-md bg-[image:var(--brand-gradient)] text-white font-semibold",
|
|
2840
|
+
children: initials2
|
|
2841
|
+
}
|
|
2842
|
+
)
|
|
2843
|
+
] });
|
|
2844
|
+
}
|
|
2845
|
+
var TriggerButton = React14.forwardRef(
|
|
2846
|
+
({ activeWorkspace, variant, showRoleBadge, disabled, className }, ref) => {
|
|
2847
|
+
const isOwner = activeWorkspace.role === "owner";
|
|
2848
|
+
if (variant === "compact") {
|
|
2849
|
+
return /* @__PURE__ */ jsxs(
|
|
2850
|
+
"button",
|
|
2851
|
+
{
|
|
2852
|
+
ref,
|
|
2853
|
+
type: "button",
|
|
2854
|
+
"aria-label": "Switch workspace",
|
|
2855
|
+
disabled,
|
|
2856
|
+
className: cn(
|
|
2857
|
+
"inline-flex items-center gap-1 rounded-md p-1 outline-none transition-colors",
|
|
2858
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
2859
|
+
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
|
|
2860
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
2861
|
+
className
|
|
2862
|
+
),
|
|
2863
|
+
children: [
|
|
2864
|
+
/* @__PURE__ */ jsx(WorkspaceAvatar, { workspace: activeWorkspace, size: "sm" }),
|
|
2865
|
+
/* @__PURE__ */ jsx(ChevronUpDown, { "aria-hidden": "true", className: "h-3.5 w-3.5 text-muted-foreground" })
|
|
2866
|
+
]
|
|
2867
|
+
}
|
|
2868
|
+
);
|
|
2869
|
+
}
|
|
2870
|
+
return /* @__PURE__ */ jsxs(
|
|
2871
|
+
"button",
|
|
2872
|
+
{
|
|
2873
|
+
ref,
|
|
2874
|
+
type: "button",
|
|
2875
|
+
"aria-label": "Switch workspace",
|
|
2876
|
+
disabled,
|
|
2877
|
+
className: cn(
|
|
2878
|
+
"inline-flex w-full items-center gap-2 rounded-md border border-border bg-background px-2 py-1 text-left outline-none transition-colors",
|
|
2879
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
2880
|
+
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
|
|
2881
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
2882
|
+
className
|
|
2883
|
+
),
|
|
2884
|
+
children: [
|
|
2885
|
+
/* @__PURE__ */ jsx(WorkspaceAvatar, { workspace: activeWorkspace, size: "sm" }),
|
|
2886
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
|
|
2887
|
+
/* @__PURE__ */ jsx("span", { className: "truncate font-medium text-[13px] text-foreground", children: activeWorkspace.name }),
|
|
2888
|
+
showRoleBadge && isOwner && /* @__PURE__ */ jsx(Badge, { variant: "owner", size: "sm", children: "Owner" })
|
|
2889
|
+
] }),
|
|
2890
|
+
/* @__PURE__ */ jsx(ChevronUpDown, { "aria-hidden": "true", className: "size-3.5 shrink-0 text-muted-foreground" })
|
|
2891
|
+
]
|
|
2892
|
+
}
|
|
2893
|
+
);
|
|
2894
|
+
}
|
|
2895
|
+
);
|
|
2896
|
+
TriggerButton.displayName = "WorkspaceSwitcherTrigger";
|
|
2897
|
+
function WorkspaceItem({ workspace, isActive, onSelect }) {
|
|
2898
|
+
return /* @__PURE__ */ jsxs(
|
|
2899
|
+
DropdownMenuItem,
|
|
2900
|
+
{
|
|
2901
|
+
onClick: onSelect,
|
|
2902
|
+
"aria-current": isActive ? "true" : void 0,
|
|
2903
|
+
className: "flex items-center gap-2 py-1.5",
|
|
2904
|
+
children: [
|
|
2905
|
+
/* @__PURE__ */ jsx(WorkspaceAvatar, { workspace, size: "sm" }),
|
|
2906
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
2907
|
+
/* @__PURE__ */ jsx(
|
|
2908
|
+
"span",
|
|
2909
|
+
{
|
|
2910
|
+
className: cn(
|
|
2911
|
+
"truncate text-sm text-foreground",
|
|
2912
|
+
isActive ? "font-semibold" : "font-medium"
|
|
2913
|
+
),
|
|
2914
|
+
children: workspace.name
|
|
2915
|
+
}
|
|
2916
|
+
),
|
|
2917
|
+
workspace.plan && /* @__PURE__ */ jsxs("span", { className: "truncate text-xs text-muted-foreground", children: [
|
|
2918
|
+
workspace.plan,
|
|
2919
|
+
workspace.role && workspace.role !== "owner" ? ` \xB7 ${roleLabel(workspace.role)}` : ""
|
|
2920
|
+
] }),
|
|
2921
|
+
!workspace.plan && workspace.role && /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: roleLabel(workspace.role) })
|
|
2922
|
+
] }),
|
|
2923
|
+
isActive && /* @__PURE__ */ jsx(Check, { "aria-hidden": "true", className: "ml-auto h-4 w-4 shrink-0 text-foreground" })
|
|
2924
|
+
]
|
|
2925
|
+
}
|
|
2926
|
+
);
|
|
2927
|
+
}
|
|
2928
|
+
function WorkspaceSwitcher({
|
|
2929
|
+
workspaces,
|
|
2930
|
+
activeWorkspaceId,
|
|
2931
|
+
onSwitch,
|
|
2932
|
+
variant = "expanded",
|
|
2933
|
+
footerSlot,
|
|
2934
|
+
headerSlot,
|
|
2935
|
+
className,
|
|
2936
|
+
showRoleBadge = true,
|
|
2937
|
+
disabled = false
|
|
2938
|
+
}) {
|
|
2939
|
+
const activeWorkspace = workspaces.find((ws) => ws.id === activeWorkspaceId) ?? workspaces[0];
|
|
2940
|
+
const handleSelect = (id) => {
|
|
2941
|
+
const result = onSwitch(id);
|
|
2942
|
+
if (result instanceof Promise) {
|
|
2943
|
+
result.catch((err) => {
|
|
2944
|
+
console.error("WorkspaceSwitcher.onSwitch failed:", err);
|
|
2945
|
+
});
|
|
2946
|
+
}
|
|
2947
|
+
};
|
|
2948
|
+
if (!activeWorkspace) {
|
|
2949
|
+
return /* @__PURE__ */ jsx(
|
|
2950
|
+
"div",
|
|
2951
|
+
{
|
|
2952
|
+
className: cn(
|
|
2953
|
+
"inline-flex items-center gap-2 rounded-md border border-dashed border-border px-2 py-1.5 text-sm text-muted-foreground",
|
|
2954
|
+
className
|
|
2955
|
+
),
|
|
2956
|
+
children: "No workspaces"
|
|
2957
|
+
}
|
|
2958
|
+
);
|
|
2959
|
+
}
|
|
2960
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
2961
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
2962
|
+
TriggerButton,
|
|
2963
|
+
{
|
|
2964
|
+
activeWorkspace,
|
|
2965
|
+
variant,
|
|
2966
|
+
showRoleBadge,
|
|
2967
|
+
disabled,
|
|
2968
|
+
className
|
|
2969
|
+
}
|
|
2970
|
+
) }),
|
|
2971
|
+
/* @__PURE__ */ jsxs(
|
|
2972
|
+
DropdownMenuContent,
|
|
2973
|
+
{
|
|
2974
|
+
align: "start",
|
|
2975
|
+
sideOffset: 6,
|
|
2976
|
+
className: "w-[min(20rem,calc(100vw-2rem))] p-1",
|
|
2977
|
+
children: [
|
|
2978
|
+
headerSlot && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2979
|
+
/* @__PURE__ */ jsx("div", { className: "px-2 py-1.5", children: headerSlot }),
|
|
2980
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {})
|
|
2981
|
+
] }),
|
|
2982
|
+
/* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-xs font-medium uppercase tracking-wider text-muted-foreground", children: "Workspaces" }),
|
|
2983
|
+
/* @__PURE__ */ jsx("div", { className: "max-h-72 overflow-y-auto", children: workspaces.map((ws) => /* @__PURE__ */ jsx(
|
|
2984
|
+
WorkspaceItem,
|
|
2985
|
+
{
|
|
2986
|
+
workspace: ws,
|
|
2987
|
+
isActive: ws.id === activeWorkspaceId,
|
|
2988
|
+
onSelect: () => handleSelect(ws.id)
|
|
2989
|
+
},
|
|
2990
|
+
ws.id
|
|
2991
|
+
)) }),
|
|
2992
|
+
footerSlot && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2993
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
2994
|
+
/* @__PURE__ */ jsx("div", { className: "px-1 py-0.5", children: footerSlot })
|
|
2995
|
+
] })
|
|
2996
|
+
]
|
|
2997
|
+
}
|
|
2998
|
+
)
|
|
2999
|
+
] });
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
export { Card, CardBody, CardDescription, CardFooter, CardHeader, CardIcon, CardRoot, CardTitle, CommandBox, GalleryCard, MarkdownEditor, MarkdownRenderer, QAPage, SidebarNav, Terminal, TerminalBody, TerminalHeader, TerminalLine, TerminalRoot, UserInfo, UserMenu, VoteButtons, WorkspaceSwitcher };
|
|
3003
|
+
//# sourceMappingURL=index.js.map
|
|
3004
|
+
//# sourceMappingURL=index.js.map
|