@nebutra/ui 0.1.0 → 0.2.1

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.
Files changed (39) hide show
  1. package/dist/components/index.d.ts +380 -0
  2. package/dist/components/index.js +2398 -0
  3. package/dist/components/index.js.map +1 -0
  4. package/dist/hooks/index.d.ts +106 -0
  5. package/dist/hooks/index.js +182 -0
  6. package/dist/hooks/index.js.map +1 -0
  7. package/dist/icons/index.d.ts +3 -0
  8. package/dist/icons/index.js +5 -0
  9. package/dist/icons/index.js.map +1 -0
  10. package/dist/index.d.ts +5 -0
  11. package/dist/index.js +31 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/layout/index.d.ts +296 -0
  14. package/dist/layout/index.js +1303 -0
  15. package/dist/layout/index.js.map +1 -0
  16. package/dist/layouts/index.d.ts +161 -0
  17. package/dist/layouts/index.js +611 -0
  18. package/dist/layouts/index.js.map +1 -0
  19. package/dist/patterns/index.d.ts +363 -0
  20. package/dist/patterns/index.js +3004 -0
  21. package/dist/patterns/index.js.map +1 -0
  22. package/dist/primitives/index.d.ts +9588 -0
  23. package/dist/primitives/index.js +26779 -0
  24. package/dist/primitives/index.js.map +1 -0
  25. package/dist/styles/brand-override.css +61 -0
  26. package/dist/tailwind.preset.d.ts +251 -0
  27. package/dist/tailwind.preset.js +273 -0
  28. package/dist/tailwind.preset.js.map +1 -0
  29. package/dist/theme/index.d.ts +150 -0
  30. package/dist/theme/index.js +28 -0
  31. package/dist/theme/index.js.map +1 -0
  32. package/dist/typography/fonts.css +110 -0
  33. package/dist/typography/index.d.ts +425 -0
  34. package/dist/typography/index.js +374 -0
  35. package/dist/typography/index.js.map +1 -0
  36. package/dist/utils/index.d.ts +61 -0
  37. package/dist/utils/index.js +55 -0
  38. package/dist/utils/index.js.map +1 -0
  39. package/package.json +18 -8
@@ -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("![alt text](", ")"), 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