@orbitlabsui/ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1299 @@
1
+ import { createContext, forwardRef, useState, useEffect, useCallback, useMemo, useContext, useId, useRef, Children, isValidElement, cloneElement } from 'react';
2
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
+ import { EyeOffIcon, EyeIcon, BanIcon, ScanSearchIcon, Clock4Icon, ArchiveIcon, CircleDotIcon, CheckCircle2Icon, CircleDashedIcon, PlayCircleIcon, SunIcon, MoonIcon, MonitorIcon, GithubIcon, ExternalLinkIcon, CopyIcon, PlusIcon, XIcon, Loader2Icon, CheckIcon, AlertTriangleIcon, ChevronDownIcon, SearchIcon, InfoIcon, XCircleIcon } from 'lucide-react';
4
+ import { FloatingOverlay, useFloating, autoUpdate, useDismiss, useRole, useInteractions, FloatingPortal, FloatingFocusManager, offset, flip, shift, useHover, useFocus, size } from '@floating-ui/react';
5
+ import { motion, AnimatePresence } from 'framer-motion';
6
+ import toast, { Toaster } from 'react-hot-toast';
7
+ import ReactMarkdown from 'react-markdown';
8
+ import remarkGfm from 'remark-gfm';
9
+
10
+ // src/theme/theme.ts
11
+ var THEME_STORAGE_KEY = "orbit-theme";
12
+ var CHOICES = ["light", "dark", "system"];
13
+ function resolveTheme(choice, prefersDark) {
14
+ if (choice === "system") return prefersDark ? "dark" : "light";
15
+ return choice;
16
+ }
17
+ function getStoredTheme() {
18
+ try {
19
+ const stored = localStorage.getItem(THEME_STORAGE_KEY);
20
+ if (stored && CHOICES.includes(stored)) return stored;
21
+ } catch {
22
+ }
23
+ return "system";
24
+ }
25
+ function storeTheme(choice) {
26
+ try {
27
+ localStorage.setItem(THEME_STORAGE_KEY, choice);
28
+ } catch {
29
+ }
30
+ }
31
+ function systemPrefersDark() {
32
+ return typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches;
33
+ }
34
+ function applyThemeClass(resolved) {
35
+ const root = document.documentElement;
36
+ root.classList.toggle("dark", resolved === "dark");
37
+ root.style.colorScheme = resolved;
38
+ const bg = getComputedStyle(root).getPropertyValue("--color-bg").trim();
39
+ if (bg) root.style.backgroundColor = bg;
40
+ }
41
+ var ThemeContext = createContext(null);
42
+ function ThemeProvider({ children }) {
43
+ const [theme, setThemeState] = useState(() => getStoredTheme());
44
+ const [prefersDark, setPrefersDark] = useState(() => systemPrefersDark());
45
+ useEffect(() => {
46
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
47
+ const onChange = (e) => setPrefersDark(e.matches);
48
+ mq.addEventListener("change", onChange);
49
+ return () => mq.removeEventListener("change", onChange);
50
+ }, []);
51
+ const resolvedTheme = resolveTheme(theme, prefersDark);
52
+ useEffect(() => {
53
+ applyThemeClass(resolvedTheme);
54
+ }, [resolvedTheme]);
55
+ const setTheme = useCallback((choice) => {
56
+ storeTheme(choice);
57
+ setThemeState(choice);
58
+ }, []);
59
+ const value = useMemo(
60
+ () => ({ theme, resolvedTheme, setTheme }),
61
+ [theme, resolvedTheme, setTheme]
62
+ );
63
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children });
64
+ }
65
+ function useTheme() {
66
+ const ctx = useContext(ThemeContext);
67
+ if (!ctx) throw new Error("useTheme must be used within a ThemeProvider");
68
+ return ctx;
69
+ }
70
+ var OPTIONS = [
71
+ { value: "light", label: "Light", Icon: SunIcon },
72
+ { value: "dark", label: "Dark", Icon: MoonIcon },
73
+ { value: "system", label: "System", Icon: MonitorIcon }
74
+ ];
75
+ function ThemeToggle() {
76
+ const { theme, setTheme } = useTheme();
77
+ return /* @__PURE__ */ jsx(
78
+ "div",
79
+ {
80
+ role: "radiogroup",
81
+ "aria-label": "Theme",
82
+ className: "flex items-center gap-0.5 rounded-md border border-border-strong bg-surface-raised p-0.5",
83
+ children: OPTIONS.map(({ value, label, Icon }) => {
84
+ const active = theme === value;
85
+ return /* @__PURE__ */ jsx(
86
+ "button",
87
+ {
88
+ type: "button",
89
+ role: "radio",
90
+ "aria-checked": active,
91
+ "aria-label": label,
92
+ title: label,
93
+ onClick: () => setTheme(value),
94
+ className: `flex h-6 w-6 items-center justify-center rounded transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent ${active ? "bg-surface text-fg shadow-sm" : "text-muted hover:text-fg"}`,
95
+ children: /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5", strokeWidth: 1.75, "aria-hidden": "true" })
96
+ },
97
+ value
98
+ );
99
+ })
100
+ }
101
+ );
102
+ }
103
+ var ICONS = {
104
+ plus: PlusIcon,
105
+ copy: CopyIcon,
106
+ "external-link": ExternalLinkIcon,
107
+ github: GithubIcon
108
+ };
109
+ function Button({
110
+ children,
111
+ variant = "primary",
112
+ icon,
113
+ iconPosition = "none",
114
+ onClick,
115
+ disabled = false,
116
+ fullWidth = false
117
+ }) {
118
+ const Icon = icon ? ICONS[icon] : null;
119
+ const showLeading = Icon && iconPosition === "leading";
120
+ const showTrailing = Icon && iconPosition === "trailing";
121
+ const base = `${fullWidth ? "flex w-full" : "inline-flex"} h-[38px] items-center justify-center gap-2.5 rounded px-3.5 font-condensed text-sm font-medium leading-[17.5px] transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-60`;
122
+ const variants = {
123
+ primary: "bg-accent text-on-accent hover:bg-accent-hover focus-visible:ring-accent shadow-[inset_0_1px_0_0_rgba(255,255,255,0.25),inset_0_0_0_1px_rgba(255,255,255,0.12)]",
124
+ secondary: "bg-surface text-fg border border-border-strong hover:bg-surface-raised focus-visible:ring-accent",
125
+ // destructive stays hardcoded — themed in a later pass (see plan Global Constraints).
126
+ destructive: "bg-[#DC2626] text-white hover:bg-[#B91C1C] focus-visible:ring-[#DC2626] shadow-[inset_0_1px_0_0_rgba(255,255,255,0.25),inset_0_0_0_1px_rgba(255,255,255,0.12)]"
127
+ };
128
+ return /* @__PURE__ */ jsxs(
129
+ "button",
130
+ {
131
+ type: "button",
132
+ onClick,
133
+ disabled,
134
+ className: `${base} ${variants[variant]}`,
135
+ children: [
136
+ showLeading && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4", strokeWidth: 1.33, "aria-hidden": "true" }),
137
+ /* @__PURE__ */ jsx("span", { children }),
138
+ showTrailing && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4", strokeWidth: 1.33, "aria-hidden": "true" })
139
+ ]
140
+ }
141
+ );
142
+ }
143
+ var MotionFloatingOverlay = motion(FloatingOverlay);
144
+ var CONTAINER = {
145
+ center: "items-center justify-center p-4",
146
+ right: "justify-end",
147
+ left: "justify-start"
148
+ };
149
+ var PANEL = {
150
+ center: "max-h-[85vh] w-full rounded-lg border-[0.5px] border-border-strong shadow-lg",
151
+ right: "h-full w-full border-l-[0.5px] border-border-strong shadow-xl",
152
+ left: "h-full w-full border-r-[0.5px] border-border-strong shadow-xl"
153
+ };
154
+ var PANEL_MOTION = {
155
+ center: { from: { scale: 0.96, y: 8 }, to: { scale: 1, y: 0 } },
156
+ right: { from: { x: "100%" }, to: { x: 0 } },
157
+ left: { from: { x: "-100%" }, to: { x: 0 } }
158
+ };
159
+ function Overlay({
160
+ open,
161
+ onClose,
162
+ position,
163
+ widthClassName,
164
+ title,
165
+ description,
166
+ footer,
167
+ hideCloseButton = false,
168
+ closeOnBackdrop = true,
169
+ closeOnEsc = true,
170
+ children
171
+ }) {
172
+ const titleId = useId();
173
+ const { refs, context } = useFloating({
174
+ open,
175
+ onOpenChange: (next) => {
176
+ if (!next) onClose();
177
+ },
178
+ whileElementsMounted: autoUpdate
179
+ });
180
+ const dismiss = useDismiss(context, {
181
+ outsidePress: closeOnBackdrop,
182
+ escapeKey: closeOnEsc
183
+ });
184
+ const role = useRole(context, { role: "dialog" });
185
+ const { getFloatingProps } = useInteractions([dismiss, role]);
186
+ const panelMotion = PANEL_MOTION[position];
187
+ const hasHeader = Boolean(title) || !hideCloseButton;
188
+ return /* @__PURE__ */ jsx(FloatingPortal, { children: /* @__PURE__ */ jsx(AnimatePresence, { children: open && /* @__PURE__ */ jsx(
189
+ MotionFloatingOverlay,
190
+ {
191
+ lockScroll: true,
192
+ className: `fixed inset-0 z-50 flex bg-black/40 ${CONTAINER[position]}`,
193
+ initial: { opacity: 0 },
194
+ animate: { opacity: 1 },
195
+ exit: { opacity: 0 },
196
+ transition: { duration: 0.2, ease: "easeOut" },
197
+ children: /* @__PURE__ */ jsx(FloatingFocusManager, { context, modal: true, children: /* @__PURE__ */ jsxs(
198
+ motion.div,
199
+ {
200
+ ref: refs.setFloating,
201
+ ...getFloatingProps(),
202
+ role: "dialog",
203
+ "aria-modal": "true",
204
+ "aria-labelledby": title ? titleId : void 0,
205
+ className: `relative flex flex-col overflow-hidden bg-surface font-sans ${PANEL[position]} ${widthClassName}`,
206
+ initial: panelMotion.from,
207
+ animate: panelMotion.to,
208
+ exit: panelMotion.from,
209
+ transition: { duration: 0.2, ease: "easeOut" },
210
+ children: [
211
+ hasHeader && /* @__PURE__ */ jsxs("div", { className: "flex flex-shrink-0 items-start justify-between gap-4 border-b-[0.5px] border-border-strong px-6 py-4", children: [
212
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
213
+ title && /* @__PURE__ */ jsx(
214
+ "h2",
215
+ {
216
+ id: titleId,
217
+ className: "font-sans text-lg font-semibold text-fg",
218
+ children: title
219
+ }
220
+ ),
221
+ description && /* @__PURE__ */ jsx("p", { className: "mt-1 font-sans text-sm text-muted", children: description })
222
+ ] }),
223
+ !hideCloseButton && /* @__PURE__ */ jsx(
224
+ "button",
225
+ {
226
+ type: "button",
227
+ onClick: onClose,
228
+ "aria-label": "Close",
229
+ className: "-mr-1.5 flex h-8 w-8 flex-shrink-0 items-center justify-center rounded text-muted transition-colors hover:bg-surface-raised hover:text-fg focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
230
+ children: /* @__PURE__ */ jsx(XIcon, { className: "h-4 w-4", strokeWidth: 2 })
231
+ }
232
+ )
233
+ ] }),
234
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto px-6 py-5", children }),
235
+ footer && /* @__PURE__ */ jsx("div", { className: "flex flex-shrink-0 items-center justify-end gap-3 border-t-[0.5px] border-border-strong px-6 py-4", children: footer })
236
+ ]
237
+ }
238
+ ) })
239
+ }
240
+ ) }) });
241
+ }
242
+ var SIZES = {
243
+ sm: "h-6 w-6 text-[10px]",
244
+ md: "h-7 w-7 text-[11px]",
245
+ lg: "h-9 w-9 text-sm"
246
+ };
247
+ var GRAY = {
248
+ bg: "var(--color-surface-avatar)",
249
+ color: "var(--color-text-secondary)"
250
+ };
251
+ function getInitials(name) {
252
+ const parts = name.trim().split(/\s+/);
253
+ if (parts.length === 1) return (parts[0] ?? "").slice(0, 2).toUpperCase();
254
+ return ((parts[0]?.[0] ?? "") + (parts[parts.length - 1]?.[0] ?? "")).toUpperCase();
255
+ }
256
+ function Avatar({
257
+ name,
258
+ initials,
259
+ size: size2 = "md",
260
+ className = ""
261
+ }) {
262
+ const label = initials ?? getInitials(name);
263
+ return /* @__PURE__ */ jsx(
264
+ "div",
265
+ {
266
+ className: `flex flex-shrink-0 items-center justify-center rounded-full font-medium ${SIZES[size2]} ${className}`,
267
+ style: {
268
+ backgroundColor: GRAY.bg,
269
+ color: GRAY.color
270
+ },
271
+ "aria-hidden": "true",
272
+ title: name,
273
+ children: label
274
+ }
275
+ );
276
+ }
277
+ var Input = forwardRef(
278
+ ({
279
+ label,
280
+ helperText,
281
+ error,
282
+ icon: Icon,
283
+ type = "text",
284
+ className = "",
285
+ disabled,
286
+ ...props
287
+ }, ref) => {
288
+ const [showPassword, setShowPassword] = useState(false);
289
+ const isPassword = type === "password";
290
+ const inputType = isPassword ? showPassword ? "text" : "password" : type;
291
+ return /* @__PURE__ */ jsxs("div", { className: `flex w-full flex-col gap-1.5 ${className}`, children: [
292
+ label && /* @__PURE__ */ jsx("label", { className: "font-sans text-sm font-medium text-fg", children: label }),
293
+ /* @__PURE__ */ jsxs("div", { className: "relative flex items-center", children: [
294
+ Icon && /* @__PURE__ */ jsx("div", { className: "absolute left-3 text-muted", children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4", strokeWidth: 1.5 }) }),
295
+ /* @__PURE__ */ jsx(
296
+ "input",
297
+ {
298
+ ref,
299
+ type: inputType,
300
+ disabled,
301
+ className: `w-full rounded border bg-surface px-3 py-2 font-sans text-sm text-fg placeholder:text-muted transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:bg-surface-raised disabled:text-muted ${Icon ? "pl-9" : ""} ${isPassword ? "pr-9" : ""} ${error ? "border-red-500 focus-visible:border-red-500 focus-visible:ring-red-500" : "border-border hover:border-border-strong focus-visible:border-accent focus-visible:ring-accent"}`,
302
+ ...props
303
+ }
304
+ ),
305
+ isPassword && /* @__PURE__ */ jsx(
306
+ "button",
307
+ {
308
+ type: "button",
309
+ onClick: () => setShowPassword(!showPassword),
310
+ disabled,
311
+ className: "absolute right-3 text-muted hover:text-fg focus:outline-none disabled:cursor-not-allowed disabled:opacity-50",
312
+ "aria-label": showPassword ? "Hide password" : "Show password",
313
+ children: showPassword ? /* @__PURE__ */ jsx(EyeOffIcon, { className: "h-4 w-4", strokeWidth: 1.5 }) : /* @__PURE__ */ jsx(EyeIcon, { className: "h-4 w-4", strokeWidth: 1.5 })
314
+ }
315
+ )
316
+ ] }),
317
+ helperText && /* @__PURE__ */ jsx(
318
+ "span",
319
+ {
320
+ className: `font-sans text-xs ${error ? "text-red-500" : "text-muted"}`,
321
+ children: helperText
322
+ }
323
+ )
324
+ ] });
325
+ }
326
+ );
327
+ Input.displayName = "Input";
328
+ var Textarea = forwardRef(
329
+ ({ label, helperText, error, className = "", disabled, ...props }, ref) => {
330
+ return /* @__PURE__ */ jsxs("div", { className: `flex w-full flex-col gap-1.5 ${className}`, children: [
331
+ label && /* @__PURE__ */ jsx("label", { className: "font-sans text-sm font-medium text-fg", children: label }),
332
+ /* @__PURE__ */ jsx(
333
+ "textarea",
334
+ {
335
+ ref,
336
+ disabled,
337
+ className: `min-h-[80px] w-full rounded border bg-surface px-3 py-2 font-sans text-sm text-fg placeholder:text-muted transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:bg-surface-raised disabled:text-muted ${error ? "border-red-500 focus-visible:border-red-500 focus-visible:ring-red-500" : "border-border hover:border-border-strong focus-visible:border-accent focus-visible:ring-accent"}`,
338
+ ...props
339
+ }
340
+ ),
341
+ helperText && /* @__PURE__ */ jsx(
342
+ "span",
343
+ {
344
+ className: `font-sans text-xs ${error ? "text-red-500" : "text-muted"}`,
345
+ children: helperText
346
+ }
347
+ )
348
+ ] });
349
+ }
350
+ );
351
+ Textarea.displayName = "Textarea";
352
+ function Code({
353
+ children,
354
+ className = ""
355
+ }) {
356
+ return /* @__PURE__ */ jsx("span", { className: `font-condensed text-muted ${className}`, children });
357
+ }
358
+ function Tooltip({ label, children, placement = "top", block }) {
359
+ const [open, setOpen] = useState(false);
360
+ const { refs, floatingStyles, context } = useFloating({
361
+ open,
362
+ onOpenChange: setOpen,
363
+ placement,
364
+ middleware: [offset(6), flip({ padding: 8 }), shift({ padding: 8 })],
365
+ whileElementsMounted: autoUpdate
366
+ });
367
+ const hover = useHover(context, { delay: { open: 250, close: 0 }, move: false });
368
+ const focus = useFocus(context);
369
+ const dismiss = useDismiss(context);
370
+ const role = useRole(context, { role: "tooltip" });
371
+ const { getReferenceProps, getFloatingProps } = useInteractions(
372
+ [
373
+ hover,
374
+ focus,
375
+ dismiss,
376
+ role
377
+ ]
378
+ );
379
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
380
+ /* @__PURE__ */ jsx(
381
+ "span",
382
+ {
383
+ ref: refs.setReference,
384
+ ...getReferenceProps(),
385
+ className: block ? "block min-w-0 truncate" : "inline-flex",
386
+ children
387
+ }
388
+ ),
389
+ open && /* @__PURE__ */ jsx(FloatingPortal, { children: /* @__PURE__ */ jsx(
390
+ "div",
391
+ {
392
+ ref: refs.setFloating,
393
+ style: floatingStyles,
394
+ ...getFloatingProps(),
395
+ className: "z-[60] max-w-xs rounded-md bg-black px-2.5 py-1.5 font-sans text-xs font-medium leading-snug text-white shadow-lg",
396
+ children: label
397
+ }
398
+ ) })
399
+ ] });
400
+ }
401
+ function Tabs({ items, value, onChange, className = "" }) {
402
+ return /* @__PURE__ */ jsx(
403
+ "div",
404
+ {
405
+ role: "tablist",
406
+ className: `flex items-center gap-1 border-b border-border-strong ${className}`,
407
+ children: items.map((item) => {
408
+ const Icon = item.icon;
409
+ const active = item.id === value;
410
+ return /* @__PURE__ */ jsxs(
411
+ "button",
412
+ {
413
+ type: "button",
414
+ role: "tab",
415
+ "aria-selected": active,
416
+ onClick: () => onChange(item.id),
417
+ className: `-mb-px flex items-center gap-2 border-b-2 px-3 py-2.5 font-sans text-sm font-medium transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 ${active ? "border-accent text-accent" : "border-transparent text-muted hover:text-fg"}`,
418
+ children: [
419
+ Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4", strokeWidth: 1.75 }),
420
+ item.label
421
+ ]
422
+ },
423
+ item.id
424
+ );
425
+ })
426
+ }
427
+ );
428
+ }
429
+ var VARIANT = {
430
+ success: {
431
+ Icon: CheckCircle2Icon,
432
+ fill: "var(--color-success-bg)",
433
+ border: "var(--color-success-border)",
434
+ color: "var(--color-success)"
435
+ },
436
+ error: {
437
+ Icon: XCircleIcon,
438
+ fill: "var(--color-danger-bg)",
439
+ border: "var(--color-danger-border)",
440
+ color: "var(--color-danger)"
441
+ },
442
+ info: {
443
+ Icon: InfoIcon,
444
+ fill: "var(--color-info-bg)",
445
+ border: "var(--color-info-border)",
446
+ color: "var(--color-info)"
447
+ },
448
+ warning: {
449
+ Icon: AlertTriangleIcon,
450
+ fill: "var(--color-warning-bg)",
451
+ border: "var(--color-warning-border)",
452
+ color: "var(--color-warning)"
453
+ }
454
+ };
455
+ function ToastCard({
456
+ t,
457
+ variant,
458
+ title,
459
+ description
460
+ }) {
461
+ const { Icon, fill, border, color } = VARIANT[variant];
462
+ return /* @__PURE__ */ jsxs(
463
+ "div",
464
+ {
465
+ className: `pointer-events-auto flex w-full max-w-sm items-start gap-3 rounded-lg border border-border-strong bg-surface px-4 py-3 font-sans shadow-lg transition-all duration-200 ${t.visible ? "translate-y-0 opacity-100" : "-translate-y-1 opacity-0"}`,
466
+ role: "status",
467
+ children: [
468
+ /* @__PURE__ */ jsx(
469
+ "span",
470
+ {
471
+ className: "mt-0.5 flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full border",
472
+ style: { backgroundColor: fill, borderColor: border, color },
473
+ "aria-hidden": "true",
474
+ children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4", strokeWidth: 1.5 })
475
+ }
476
+ ),
477
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 pt-0.5", children: [
478
+ /* @__PURE__ */ jsx("p", { className: "font-sans text-sm font-semibold text-fg", children: title }),
479
+ description && /* @__PURE__ */ jsx("p", { className: "mt-0.5 font-sans text-sm leading-snug text-fg-secondary", children: description })
480
+ ] }),
481
+ /* @__PURE__ */ jsx(
482
+ "button",
483
+ {
484
+ type: "button",
485
+ onClick: () => toast.dismiss(t.id),
486
+ "aria-label": "Dismiss",
487
+ className: "-mr-1 flex h-6 w-6 flex-shrink-0 items-center justify-center rounded text-muted transition-colors hover:bg-surface-raised hover:text-fg focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
488
+ children: /* @__PURE__ */ jsx(XIcon, { className: "h-3.5 w-3.5", strokeWidth: 2 })
489
+ }
490
+ )
491
+ ]
492
+ }
493
+ );
494
+ }
495
+ function show(variant, title, options = {}) {
496
+ return toast.custom(
497
+ (t) => /* @__PURE__ */ jsx(
498
+ ToastCard,
499
+ {
500
+ t,
501
+ variant,
502
+ title,
503
+ description: options.description
504
+ }
505
+ ),
506
+ { duration: options.duration }
507
+ );
508
+ }
509
+ var notify = {
510
+ success: (title, options) => show("success", title, options),
511
+ error: (title, options) => show("error", title, options),
512
+ info: (title, options) => show("info", title, options),
513
+ warning: (title, options) => show("warning", title, options),
514
+ dismiss: (id) => toast.dismiss(id)
515
+ };
516
+ function AppToaster() {
517
+ return /* @__PURE__ */ jsx(
518
+ Toaster,
519
+ {
520
+ position: "bottom-right",
521
+ gutter: 12,
522
+ toastOptions: { duration: 4e3 }
523
+ }
524
+ );
525
+ }
526
+ var STATUS_CONFIG = {
527
+ backlog: {
528
+ label: "Backlog",
529
+ Icon: Clock4Icon,
530
+ fill: "var(--color-neutral-bg)",
531
+ border: "var(--color-neutral-border)",
532
+ color: "var(--color-neutral)"
533
+ },
534
+ ready: {
535
+ label: "Ready",
536
+ Icon: PlayCircleIcon,
537
+ fill: "var(--color-info-bg)",
538
+ border: "var(--color-info-border)",
539
+ color: "var(--color-info)"
540
+ },
541
+ "in-progress": {
542
+ label: "In progress",
543
+ Icon: CircleDashedIcon,
544
+ fill: "var(--color-warning-bg)",
545
+ border: "var(--color-warning-border)",
546
+ color: "var(--color-warning)"
547
+ },
548
+ "in-review": {
549
+ label: "In Review",
550
+ Icon: ScanSearchIcon,
551
+ fill: "var(--color-review-bg)",
552
+ border: "var(--color-review-border)",
553
+ color: "var(--color-review)"
554
+ },
555
+ blocked: {
556
+ label: "Blocked",
557
+ Icon: BanIcon,
558
+ fill: "var(--color-danger-bg)",
559
+ border: "var(--color-danger-border)",
560
+ color: "var(--color-danger)"
561
+ },
562
+ completed: {
563
+ label: "Completed",
564
+ Icon: CheckCircle2Icon,
565
+ fill: "var(--color-success-bg)",
566
+ border: "var(--color-success-border)",
567
+ color: "var(--color-success)"
568
+ }
569
+ };
570
+ var PROJECT_STATUS_CONFIG = {
571
+ active: {
572
+ label: "Active",
573
+ Icon: CircleDotIcon,
574
+ fill: "var(--color-success-bg)",
575
+ border: "var(--color-success-border)",
576
+ color: "var(--color-success)"
577
+ },
578
+ archived: {
579
+ label: "Archived",
580
+ Icon: ArchiveIcon,
581
+ fill: "var(--color-neutral-bg)",
582
+ border: "var(--color-neutral-border)",
583
+ color: "var(--color-neutral)"
584
+ }
585
+ };
586
+ var PLAN_STATUS_CONFIG = {
587
+ backlog: {
588
+ label: "Backlog",
589
+ Icon: Clock4Icon,
590
+ fill: "var(--color-neutral-bg)",
591
+ border: "var(--color-neutral-border)",
592
+ color: "var(--color-neutral)"
593
+ },
594
+ scoped: {
595
+ label: "Scoped",
596
+ Icon: ScanSearchIcon,
597
+ fill: "var(--color-info-bg)",
598
+ border: "var(--color-info-border)",
599
+ color: "var(--color-info)"
600
+ },
601
+ cancelled: {
602
+ label: "Cancelled",
603
+ Icon: BanIcon,
604
+ fill: "var(--color-danger-bg)",
605
+ border: "var(--color-danger-border)",
606
+ color: "var(--color-danger)"
607
+ }
608
+ };
609
+ var PLAN_STATUS_ORDER = ["backlog", "scoped", "cancelled"];
610
+ var ALL_STATUS_CONFIG = { ...STATUS_CONFIG, ...PROJECT_STATUS_CONFIG, ...PLAN_STATUS_CONFIG };
611
+ function StatusBadge({ status }) {
612
+ const { label, Icon, fill, border, color } = ALL_STATUS_CONFIG[status];
613
+ return /* @__PURE__ */ jsxs(
614
+ "span",
615
+ {
616
+ className: "inline-flex items-center gap-1.5 rounded border px-2 py-1 font-condensed text-xs font-medium leading-[15px]",
617
+ style: {
618
+ backgroundColor: fill,
619
+ borderColor: border,
620
+ color
621
+ },
622
+ children: [
623
+ /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5", strokeWidth: 1.5, "aria-hidden": "true" }),
624
+ label
625
+ ]
626
+ }
627
+ );
628
+ }
629
+ var NEUTRAL_TONE = {
630
+ bg: "var(--color-surface-strong)",
631
+ border: "var(--color-border-strong)",
632
+ color: "var(--color-text-muted)"
633
+ };
634
+ function EmptyState({
635
+ icon: Icon,
636
+ title,
637
+ description,
638
+ action,
639
+ tone = NEUTRAL_TONE,
640
+ className = ""
641
+ }) {
642
+ return /* @__PURE__ */ jsxs(
643
+ "div",
644
+ {
645
+ className: `flex h-full min-h-[360px] w-full flex-col items-center justify-center px-6 py-12 text-center ${className}`,
646
+ children: [
647
+ /* @__PURE__ */ jsxs("div", { className: "relative mb-5 flex items-center justify-center", children: [
648
+ /* @__PURE__ */ jsx(
649
+ "span",
650
+ {
651
+ className: "absolute h-20 w-20 rounded-3xl opacity-60",
652
+ style: { backgroundColor: tone.bg },
653
+ "aria-hidden": "true"
654
+ }
655
+ ),
656
+ /* @__PURE__ */ jsx(
657
+ "span",
658
+ {
659
+ className: "relative flex h-14 w-14 items-center justify-center rounded-2xl border shadow-[0px_1px_2px_rgba(0,0,0,0.06)]",
660
+ style: {
661
+ backgroundColor: "var(--color-surface)",
662
+ borderColor: tone.border,
663
+ color: tone.color
664
+ },
665
+ "aria-hidden": "true",
666
+ children: /* @__PURE__ */ jsx(Icon, { className: "h-6 w-6", strokeWidth: 1.5 })
667
+ }
668
+ )
669
+ ] }),
670
+ /* @__PURE__ */ jsx("h2", { className: "font-sans text-lg font-semibold tracking-tight text-fg", children: title }),
671
+ description && /* @__PURE__ */ jsx("p", { className: "mt-2 max-w-sm font-sans text-sm leading-relaxed text-muted", children: description }),
672
+ action && /* @__PURE__ */ jsx("div", { className: "mt-6", children: action })
673
+ ]
674
+ }
675
+ );
676
+ }
677
+ function FormSection({
678
+ title,
679
+ description,
680
+ children,
681
+ className = ""
682
+ }) {
683
+ return /* @__PURE__ */ jsxs("section", { className: `flex flex-col gap-4 ${className}`, children: [
684
+ (title || description) && /* @__PURE__ */ jsxs("div", { children: [
685
+ title && /* @__PURE__ */ jsx("h3", { className: "font-sans text-sm font-semibold text-fg", children: title }),
686
+ description && /* @__PURE__ */ jsx("p", { className: "mt-0.5 font-sans text-sm text-muted", children: description })
687
+ ] }),
688
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-4", children })
689
+ ] });
690
+ }
691
+ function Field({ label, htmlFor, help, error, children }) {
692
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
693
+ /* @__PURE__ */ jsx(
694
+ "label",
695
+ {
696
+ htmlFor,
697
+ className: "font-sans text-sm font-medium text-fg",
698
+ children: label
699
+ }
700
+ ),
701
+ children,
702
+ error ? /* @__PURE__ */ jsx("span", { className: "font-sans text-xs text-red-500", children: error }) : help ? /* @__PURE__ */ jsx("span", { className: "font-sans text-xs text-muted", children: help }) : null
703
+ ] });
704
+ }
705
+ var CONFIG = {
706
+ clean: null,
707
+ dirty: { label: "Unsaved changes", className: "text-warning" },
708
+ saving: { label: "Saving\u2026", className: "text-muted" },
709
+ saved: { label: "Saved", className: "text-success" }
710
+ };
711
+ function DirtyStateIndicator({ state }) {
712
+ const config = CONFIG[state];
713
+ if (!config) return null;
714
+ return /* @__PURE__ */ jsxs(
715
+ "span",
716
+ {
717
+ className: `flex items-center gap-1.5 font-sans text-xs font-medium ${config.className}`,
718
+ "aria-live": "polite",
719
+ children: [
720
+ state === "saving" && /* @__PURE__ */ jsx(Loader2Icon, { className: "h-3.5 w-3.5 animate-spin", strokeWidth: 2 }),
721
+ state === "saved" && /* @__PURE__ */ jsx(CheckIcon, { className: "h-3.5 w-3.5", strokeWidth: 2.5 }),
722
+ state === "dirty" && /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-warning", "aria-hidden": "true" }),
723
+ config.label
724
+ ]
725
+ }
726
+ );
727
+ }
728
+ function RowActions({ actions }) {
729
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end gap-0.5", children: actions.map((a) => /* @__PURE__ */ jsx(
730
+ "button",
731
+ {
732
+ type: "button",
733
+ "aria-label": a.label,
734
+ title: a.label,
735
+ onClick: (e) => {
736
+ e.stopPropagation();
737
+ a.onClick();
738
+ },
739
+ className: `rounded p-1.5 text-muted transition-colors ${a.danger ? "hover:bg-danger-bg hover:text-danger" : "hover:bg-surface-raised hover:text-fg"}`,
740
+ children: /* @__PURE__ */ jsx(a.icon, { className: "h-4 w-4", strokeWidth: 1.75 })
741
+ },
742
+ a.label
743
+ )) });
744
+ }
745
+ function Markdown({ children }) {
746
+ return /* @__PURE__ */ jsx(
747
+ "div",
748
+ {
749
+ className: [
750
+ "font-sans text-[15px] leading-7 text-fg-secondary",
751
+ "[&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
752
+ "[&_p]:my-3",
753
+ "[&_h1]:mb-3 [&_h1]:mt-8 [&_h1]:text-2xl [&_h1]:font-bold [&_h1]:leading-snug [&_h1]:text-fg",
754
+ "[&_h2]:mb-2.5 [&_h2]:mt-8 [&_h2]:text-xl [&_h2]:font-semibold [&_h2]:leading-snug [&_h2]:text-fg",
755
+ "[&_h3]:mb-2 [&_h3]:mt-6 [&_h3]:text-base [&_h3]:font-semibold [&_h3]:text-fg",
756
+ "[&_ul]:my-3.5 [&_ul]:list-disc [&_ul]:pl-6 [&_ol]:my-3.5 [&_ol]:list-decimal [&_ol]:pl-6",
757
+ "[&_li]:my-1.5 [&_li]:pl-1.5 [&_li>p]:my-0",
758
+ "[&_a]:text-accent [&_a]:underline [&_a]:underline-offset-2 [&_strong]:font-semibold [&_strong]:text-fg",
759
+ "[&_blockquote]:my-4 [&_blockquote]:border-l-2 [&_blockquote]:border-accent [&_blockquote]:pl-4 [&_blockquote]:text-fg-secondary",
760
+ "[&_code]:rounded [&_code]:bg-surface-strong [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-[13px] [&_code]:text-fg-secondary",
761
+ "[&_pre]:my-4 [&_pre]:overflow-x-auto [&_pre]:rounded-lg [&_pre]:bg-surface-raised [&_pre]:p-4 [&_pre_code]:bg-transparent [&_pre_code]:p-0",
762
+ "[&_hr]:my-7 [&_hr]:border-border-strong",
763
+ "[&_table]:my-4 [&_table]:w-full [&_table]:border-collapse [&_td]:border [&_td]:border-border-strong [&_td]:px-3 [&_td]:py-1.5 [&_th]:border [&_th]:border-border-strong [&_th]:bg-surface-raised [&_th]:px-3 [&_th]:py-1.5 [&_th]:text-left [&_th]:font-semibold"
764
+ ].join(" "),
765
+ children: /* @__PURE__ */ jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], children })
766
+ }
767
+ );
768
+ }
769
+ var SIZES2 = {
770
+ sm: "max-w-sm",
771
+ md: "max-w-lg",
772
+ lg: "max-w-2xl"
773
+ };
774
+ function Modal({
775
+ open,
776
+ onClose,
777
+ title,
778
+ description,
779
+ footer,
780
+ size: size2 = "md",
781
+ hideCloseButton,
782
+ closeOnBackdrop,
783
+ closeOnEsc,
784
+ children
785
+ }) {
786
+ return /* @__PURE__ */ jsx(
787
+ Overlay,
788
+ {
789
+ open,
790
+ onClose,
791
+ position: "center",
792
+ widthClassName: SIZES2[size2],
793
+ title,
794
+ description,
795
+ footer,
796
+ hideCloseButton,
797
+ closeOnBackdrop,
798
+ closeOnEsc,
799
+ children
800
+ }
801
+ );
802
+ }
803
+ var SIZES3 = {
804
+ sm: "max-w-sm",
805
+ md: "max-w-md",
806
+ lg: "max-w-lg"
807
+ };
808
+ function Drawer({
809
+ open,
810
+ onClose,
811
+ side = "right",
812
+ title,
813
+ description,
814
+ footer,
815
+ size: size2 = "md",
816
+ hideCloseButton,
817
+ closeOnBackdrop,
818
+ closeOnEsc,
819
+ children
820
+ }) {
821
+ return /* @__PURE__ */ jsx(
822
+ Overlay,
823
+ {
824
+ open,
825
+ onClose,
826
+ position: side,
827
+ widthClassName: SIZES3[size2],
828
+ title,
829
+ description,
830
+ footer,
831
+ hideCloseButton,
832
+ closeOnBackdrop,
833
+ closeOnEsc,
834
+ children
835
+ }
836
+ );
837
+ }
838
+ function ConfirmDialog({
839
+ open,
840
+ onClose,
841
+ title,
842
+ message,
843
+ confirmLabel = "Confirm",
844
+ cancelLabel = "Cancel",
845
+ onConfirm,
846
+ variant = "default",
847
+ loading = false
848
+ }) {
849
+ const destructive = variant === "destructive";
850
+ return /* @__PURE__ */ jsx(
851
+ Modal,
852
+ {
853
+ open,
854
+ onClose,
855
+ size: "sm",
856
+ title,
857
+ footer: /* @__PURE__ */ jsxs(Fragment, { children: [
858
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onClose, disabled: loading, children: cancelLabel }),
859
+ /* @__PURE__ */ jsx(
860
+ Button,
861
+ {
862
+ variant: destructive ? "destructive" : "primary",
863
+ onClick: onConfirm,
864
+ disabled: loading,
865
+ children: loading ? "Working\u2026" : confirmLabel
866
+ }
867
+ )
868
+ ] }),
869
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
870
+ destructive && /* @__PURE__ */ jsx("span", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full bg-danger-bg text-danger", children: /* @__PURE__ */ jsx(
871
+ AlertTriangleIcon,
872
+ {
873
+ className: "h-5 w-5",
874
+ strokeWidth: 2,
875
+ "aria-hidden": "true"
876
+ }
877
+ ) }),
878
+ /* @__PURE__ */ jsx("p", { className: "font-sans text-sm leading-relaxed text-fg-secondary", children: message })
879
+ ] })
880
+ }
881
+ );
882
+ }
883
+ function SaveBar({
884
+ dirty,
885
+ onSave,
886
+ onDiscard,
887
+ state,
888
+ saveLabel = "Save",
889
+ discardLabel = "Discard"
890
+ }) {
891
+ const resolvedState = state ?? (dirty ? "dirty" : "clean");
892
+ return /* @__PURE__ */ jsxs("div", { className: "sticky bottom-0 z-10 flex items-center justify-between gap-4 border-t border-border-strong bg-surface px-1 py-3 backdrop-blur", children: [
893
+ /* @__PURE__ */ jsx(DirtyStateIndicator, { state: resolvedState }),
894
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
895
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onDiscard, disabled: !dirty, children: discardLabel }),
896
+ /* @__PURE__ */ jsx(Button, { onClick: onSave, disabled: !dirty, children: saveLabel })
897
+ ] })
898
+ ] });
899
+ }
900
+ var activeClose = null;
901
+ function usePopover({
902
+ open,
903
+ onOpenChange,
904
+ placement = "bottom-start",
905
+ gap = 6,
906
+ matchWidth = false,
907
+ role = "menu"
908
+ }) {
909
+ const { refs, floatingStyles, context } = useFloating({
910
+ open,
911
+ onOpenChange,
912
+ placement,
913
+ whileElementsMounted: autoUpdate,
914
+ middleware: [
915
+ offset(gap),
916
+ flip({ padding: 8 }),
917
+ shift({ padding: 8 }),
918
+ size({
919
+ padding: 8,
920
+ apply({ availableHeight, rects, elements }) {
921
+ Object.assign(elements.floating.style, {
922
+ maxHeight: `${Math.max(160, availableHeight)}px`,
923
+ ...matchWidth ? { width: `${rects.reference.width}px` } : {}
924
+ });
925
+ }
926
+ })
927
+ ]
928
+ });
929
+ const onOpenChangeRef = useRef(onOpenChange);
930
+ onOpenChangeRef.current = onOpenChange;
931
+ const closeSelfRef = useRef();
932
+ if (!closeSelfRef.current) {
933
+ closeSelfRef.current = () => onOpenChangeRef.current(false);
934
+ }
935
+ useEffect(() => {
936
+ if (!open) return;
937
+ if (activeClose && activeClose !== closeSelfRef.current) {
938
+ activeClose();
939
+ }
940
+ activeClose = closeSelfRef.current;
941
+ return () => {
942
+ if (activeClose === closeSelfRef.current) activeClose = null;
943
+ };
944
+ }, [open]);
945
+ const dismiss = useDismiss(context, { outsidePress: true });
946
+ const roleInteraction = useRole(context, { role });
947
+ const { getReferenceProps, getFloatingProps } = useInteractions(
948
+ [
949
+ dismiss,
950
+ roleInteraction
951
+ ]
952
+ );
953
+ return {
954
+ refs,
955
+ floatingStyles,
956
+ context,
957
+ getReferenceProps,
958
+ getFloatingProps
959
+ };
960
+ }
961
+ function Dropdown({
962
+ trigger,
963
+ children,
964
+ align = "left",
965
+ className = ""
966
+ }) {
967
+ const [isOpen, setIsOpen] = useState(false);
968
+ const { refs, floatingStyles, context, getReferenceProps, getFloatingProps } = usePopover({
969
+ open: isOpen,
970
+ onOpenChange: setIsOpen,
971
+ placement: align === "right" ? "bottom-end" : "bottom-start",
972
+ role: "menu"
973
+ });
974
+ return /* @__PURE__ */ jsxs("div", { className: `relative inline-block text-left ${className}`, children: [
975
+ /* @__PURE__ */ jsx(
976
+ "div",
977
+ {
978
+ ref: refs.setReference,
979
+ ...getReferenceProps({
980
+ onClick: () => setIsOpen((o) => !o)
981
+ }),
982
+ className: "inline-block",
983
+ children: trigger
984
+ }
985
+ ),
986
+ /* @__PURE__ */ jsx(FloatingPortal, { children: /* @__PURE__ */ jsx(AnimatePresence, { children: isOpen && /* @__PURE__ */ jsx(
987
+ "div",
988
+ {
989
+ ref: refs.setFloating,
990
+ style: floatingStyles,
991
+ ...getFloatingProps(),
992
+ className: "z-50",
993
+ children: /* @__PURE__ */ jsx(
994
+ motion.div,
995
+ {
996
+ initial: {
997
+ opacity: 0,
998
+ y: -4,
999
+ scale: 0.98
1000
+ },
1001
+ animate: {
1002
+ opacity: 1,
1003
+ y: 0,
1004
+ scale: 1
1005
+ },
1006
+ exit: {
1007
+ opacity: 0,
1008
+ y: -4,
1009
+ scale: 0.98
1010
+ },
1011
+ transition: {
1012
+ duration: 0.15,
1013
+ ease: "easeOut"
1014
+ },
1015
+ className: "min-w-[200px] overflow-auto rounded-md border border-border bg-surface py-1 shadow-lg",
1016
+ children: Children.map(children, (child) => {
1017
+ if (isValidElement(child)) {
1018
+ return cloneElement(child, {
1019
+ onClick: (e) => {
1020
+ if (child.props.onClick) child.props.onClick(e);
1021
+ setIsOpen(false);
1022
+ }
1023
+ });
1024
+ }
1025
+ return child;
1026
+ })
1027
+ }
1028
+ )
1029
+ }
1030
+ ) }) })
1031
+ ] });
1032
+ }
1033
+ function DropdownItem({
1034
+ children,
1035
+ icon: Icon,
1036
+ danger,
1037
+ className = "",
1038
+ ...props
1039
+ }) {
1040
+ return /* @__PURE__ */ jsxs(
1041
+ "button",
1042
+ {
1043
+ type: "button",
1044
+ role: "menuitem",
1045
+ className: `flex w-full items-center gap-2 px-4 py-2 text-left font-sans text-sm transition-colors hover:bg-surface-raised focus:bg-surface-raised focus:outline-none ${danger ? "text-red-600" : "text-fg"} ${className}`,
1046
+ ...props,
1047
+ children: [
1048
+ Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4", strokeWidth: 1.5 }),
1049
+ children
1050
+ ]
1051
+ }
1052
+ );
1053
+ }
1054
+ function Select({
1055
+ label,
1056
+ placeholder = "Select an option",
1057
+ options,
1058
+ value,
1059
+ onChange,
1060
+ error,
1061
+ helperText,
1062
+ disabled,
1063
+ className = ""
1064
+ }) {
1065
+ const [isOpen, setIsOpen] = useState(false);
1066
+ const { refs, floatingStyles, getReferenceProps, getFloatingProps } = usePopover({
1067
+ open: isOpen,
1068
+ onOpenChange: setIsOpen,
1069
+ placement: "bottom-start",
1070
+ matchWidth: true,
1071
+ role: "listbox"
1072
+ });
1073
+ const selectedOption = options.find((opt) => opt.value === value);
1074
+ const handleKeyDown = (e) => {
1075
+ if (disabled) return;
1076
+ if (e.key === "Enter" || e.key === " ") {
1077
+ e.preventDefault();
1078
+ setIsOpen(!isOpen);
1079
+ } else if (e.key === "Escape") {
1080
+ setIsOpen(false);
1081
+ }
1082
+ };
1083
+ const handleSelect = (val) => {
1084
+ onChange?.(val);
1085
+ setIsOpen(false);
1086
+ };
1087
+ return /* @__PURE__ */ jsxs("div", { className: `flex w-full flex-col gap-1.5 ${className}`, children: [
1088
+ label && /* @__PURE__ */ jsx("label", { className: "font-sans text-sm font-medium text-fg", children: label }),
1089
+ /* @__PURE__ */ jsxs(
1090
+ "button",
1091
+ {
1092
+ ref: refs.setReference,
1093
+ type: "button",
1094
+ disabled,
1095
+ "aria-haspopup": "listbox",
1096
+ "aria-expanded": isOpen,
1097
+ ...getReferenceProps({
1098
+ onClick: () => !disabled && setIsOpen((o) => !o),
1099
+ onKeyDown: handleKeyDown
1100
+ }),
1101
+ className: `flex w-full items-center justify-between rounded border bg-surface px-3 py-2 text-left font-sans text-sm transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:bg-surface-raised disabled:text-muted ${error ? "border-red-500 focus-visible:border-red-500 focus-visible:ring-red-500" : "border-border hover:border-border-strong focus-visible:border-accent focus-visible:ring-accent"}`,
1102
+ children: [
1103
+ /* @__PURE__ */ jsx("span", { className: selectedOption ? "text-fg" : "text-muted", children: selectedOption ? selectedOption.label : placeholder }),
1104
+ /* @__PURE__ */ jsx(
1105
+ ChevronDownIcon,
1106
+ {
1107
+ className: `h-4 w-4 text-muted transition-transform duration-200 ${isOpen ? "rotate-180" : ""}`,
1108
+ strokeWidth: 1.5
1109
+ }
1110
+ )
1111
+ ]
1112
+ }
1113
+ ),
1114
+ /* @__PURE__ */ jsx(FloatingPortal, { children: /* @__PURE__ */ jsx(AnimatePresence, { children: isOpen && /* @__PURE__ */ jsx(
1115
+ "div",
1116
+ {
1117
+ ref: refs.setFloating,
1118
+ style: floatingStyles,
1119
+ ...getFloatingProps(),
1120
+ className: "z-50",
1121
+ children: /* @__PURE__ */ jsx(
1122
+ motion.ul,
1123
+ {
1124
+ role: "listbox",
1125
+ initial: {
1126
+ opacity: 0,
1127
+ y: -4,
1128
+ scale: 0.98
1129
+ },
1130
+ animate: {
1131
+ opacity: 1,
1132
+ y: 0,
1133
+ scale: 1
1134
+ },
1135
+ exit: {
1136
+ opacity: 0,
1137
+ y: -4,
1138
+ scale: 0.98
1139
+ },
1140
+ transition: {
1141
+ duration: 0.15,
1142
+ ease: "easeOut"
1143
+ },
1144
+ className: "overflow-auto rounded-md border border-border bg-surface py-1 shadow-lg focus:outline-none",
1145
+ children: options.map(
1146
+ (option) => /* @__PURE__ */ jsxs(
1147
+ "li",
1148
+ {
1149
+ role: "option",
1150
+ "aria-selected": value === option.value,
1151
+ onClick: () => handleSelect(option.value),
1152
+ className: `relative flex cursor-pointer select-none items-center py-2 pl-3 pr-9 font-sans text-sm transition-colors hover:bg-surface-raised ${value === option.value ? "bg-surface-raised font-medium text-accent" : "text-fg"}`,
1153
+ children: [
1154
+ /* @__PURE__ */ jsx("span", { className: "block truncate", children: option.label }),
1155
+ value === option.value && /* @__PURE__ */ jsx("span", { className: "absolute inset-y-0 right-0 flex items-center pr-3 text-accent", children: /* @__PURE__ */ jsx(CheckIcon, { className: "h-4 w-4", strokeWidth: 2 }) })
1156
+ ]
1157
+ },
1158
+ option.value
1159
+ )
1160
+ )
1161
+ }
1162
+ )
1163
+ }
1164
+ ) }) }),
1165
+ helperText && /* @__PURE__ */ jsx(
1166
+ "span",
1167
+ {
1168
+ className: `font-sans text-xs ${error ? "text-red-500" : "text-muted"}`,
1169
+ children: helperText
1170
+ }
1171
+ )
1172
+ ] });
1173
+ }
1174
+ function Combobox({
1175
+ label,
1176
+ placeholder = "Search...",
1177
+ options,
1178
+ value,
1179
+ onChange,
1180
+ error,
1181
+ helperText,
1182
+ disabled,
1183
+ className = ""
1184
+ }) {
1185
+ const [isOpen, setIsOpen] = useState(false);
1186
+ const [query, setQuery] = useState("");
1187
+ const inputRef = useRef(null);
1188
+ const { refs, floatingStyles, getReferenceProps, getFloatingProps } = usePopover({
1189
+ open: isOpen,
1190
+ onOpenChange: (next) => {
1191
+ setIsOpen(next);
1192
+ if (!next) setQuery("");
1193
+ },
1194
+ placement: "bottom-start",
1195
+ matchWidth: true,
1196
+ role: "listbox"
1197
+ });
1198
+ useEffect(() => {
1199
+ if (isOpen) inputRef.current?.focus();
1200
+ }, [isOpen]);
1201
+ const selectedOption = options.find((opt) => opt.value === value);
1202
+ const filteredOptions = query === "" ? options : options.filter(
1203
+ (option) => option.label.toLowerCase().includes(query.toLowerCase())
1204
+ );
1205
+ const handleSelect = (val) => {
1206
+ onChange?.(val);
1207
+ setIsOpen(false);
1208
+ setQuery("");
1209
+ };
1210
+ return /* @__PURE__ */ jsxs("div", { className: `flex w-full flex-col gap-1.5 ${className}`, children: [
1211
+ label && /* @__PURE__ */ jsx("label", { className: "font-sans text-sm font-medium text-fg", children: label }),
1212
+ /* @__PURE__ */ jsxs(
1213
+ "button",
1214
+ {
1215
+ ref: refs.setReference,
1216
+ type: "button",
1217
+ disabled,
1218
+ "aria-haspopup": "listbox",
1219
+ "aria-expanded": isOpen,
1220
+ ...getReferenceProps({
1221
+ onClick: () => !disabled && setIsOpen((o) => !o)
1222
+ }),
1223
+ className: `flex w-full items-center justify-between rounded border bg-surface px-3 py-2 text-left font-sans text-sm transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:bg-surface-raised disabled:text-muted ${error ? "border-red-500 focus-visible:ring-red-500" : "border-border hover:border-border-strong focus-visible:border-accent focus-visible:ring-accent"}`,
1224
+ children: [
1225
+ /* @__PURE__ */ jsx("span", { className: selectedOption ? "text-fg" : "text-muted", children: selectedOption ? selectedOption.label : placeholder }),
1226
+ /* @__PURE__ */ jsx(
1227
+ ChevronDownIcon,
1228
+ {
1229
+ className: `h-4 w-4 text-muted transition-transform duration-200 ${isOpen ? "rotate-180" : ""}`,
1230
+ strokeWidth: 1.5
1231
+ }
1232
+ )
1233
+ ]
1234
+ }
1235
+ ),
1236
+ /* @__PURE__ */ jsx(FloatingPortal, { children: /* @__PURE__ */ jsx(AnimatePresence, { children: isOpen && /* @__PURE__ */ jsx(
1237
+ "div",
1238
+ {
1239
+ ref: refs.setFloating,
1240
+ style: floatingStyles,
1241
+ ...getFloatingProps(),
1242
+ className: "z-50",
1243
+ children: /* @__PURE__ */ jsxs(
1244
+ motion.div,
1245
+ {
1246
+ initial: { opacity: 0, y: -4, scale: 0.98 },
1247
+ animate: { opacity: 1, y: 0, scale: 1 },
1248
+ exit: { opacity: 0, y: -4, scale: 0.98 },
1249
+ transition: { duration: 0.15, ease: "easeOut" },
1250
+ className: "overflow-hidden rounded-md border border-border bg-surface shadow-lg",
1251
+ children: [
1252
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b border-border px-3 py-2", children: [
1253
+ /* @__PURE__ */ jsx(SearchIcon, { className: "h-4 w-4 flex-shrink-0 text-muted", strokeWidth: 1.5 }),
1254
+ /* @__PURE__ */ jsx(
1255
+ "input",
1256
+ {
1257
+ ref: inputRef,
1258
+ type: "text",
1259
+ className: "w-full bg-transparent p-0 font-sans text-sm text-fg placeholder:text-muted focus:outline-none",
1260
+ placeholder,
1261
+ value: query,
1262
+ onChange: (e) => setQuery(e.target.value)
1263
+ }
1264
+ )
1265
+ ] }),
1266
+ /* @__PURE__ */ jsx("ul", { role: "listbox", className: "max-h-60 overflow-auto py-1", children: filteredOptions.length === 0 ? /* @__PURE__ */ jsx("li", { className: "px-3 py-2 font-sans text-sm text-muted", children: "No results found." }) : filteredOptions.map(
1267
+ (option) => /* @__PURE__ */ jsxs(
1268
+ "li",
1269
+ {
1270
+ role: "option",
1271
+ "aria-selected": value === option.value,
1272
+ onClick: () => handleSelect(option.value),
1273
+ className: `relative flex cursor-pointer select-none items-center py-2 pl-3 pr-9 font-sans text-sm transition-colors hover:bg-surface-raised ${value === option.value ? "bg-surface-raised font-medium text-accent" : "text-fg"}`,
1274
+ children: [
1275
+ /* @__PURE__ */ jsx("span", { className: "block truncate", children: option.label }),
1276
+ value === option.value && /* @__PURE__ */ jsx("span", { className: "absolute inset-y-0 right-0 flex items-center pr-3 text-accent", children: /* @__PURE__ */ jsx(CheckIcon, { className: "h-4 w-4", strokeWidth: 2 }) })
1277
+ ]
1278
+ },
1279
+ option.value
1280
+ )
1281
+ ) })
1282
+ ]
1283
+ }
1284
+ )
1285
+ }
1286
+ ) }) }),
1287
+ helperText && /* @__PURE__ */ jsx(
1288
+ "span",
1289
+ {
1290
+ className: `font-sans text-xs ${error ? "text-red-500" : "text-muted"}`,
1291
+ children: helperText
1292
+ }
1293
+ )
1294
+ ] });
1295
+ }
1296
+
1297
+ export { AppToaster, Avatar, Button, Code, Combobox, ConfirmDialog, DirtyStateIndicator, Drawer, Dropdown, DropdownItem, EmptyState, Field, FormSection, Input, Markdown, Modal, Overlay, PLAN_STATUS_CONFIG, PLAN_STATUS_ORDER, PROJECT_STATUS_CONFIG, RowActions, STATUS_CONFIG, SaveBar, Select, StatusBadge, THEME_STORAGE_KEY, Tabs, Textarea, ThemeProvider, ThemeToggle, Tooltip, applyThemeClass, getStoredTheme, notify, resolveTheme, storeTheme, systemPrefersDark, usePopover, useTheme };
1298
+ //# sourceMappingURL=index.js.map
1299
+ //# sourceMappingURL=index.js.map