@kinetixui/ui 0.11.0 → 0.12.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 CHANGED
@@ -3712,14 +3712,14 @@ var NumberInput = React62.forwardRef(
3712
3712
  ({ className, value, defaultValue = 0, min, max, step = 1, onChange, disabled, ...props }, ref) => {
3713
3713
  const [internal, setInternal] = React62.useState(defaultValue);
3714
3714
  const current = value ?? internal;
3715
- const clamp = (n) => {
3715
+ const clamp2 = (n) => {
3716
3716
  let v = n;
3717
3717
  if (min != null) v = Math.max(min, v);
3718
3718
  if (max != null) v = Math.min(max, v);
3719
3719
  return v;
3720
3720
  };
3721
3721
  const set = (n) => {
3722
- const v = clamp(n);
3722
+ const v = clamp2(n);
3723
3723
  setInternal(v);
3724
3724
  onChange?.(v);
3725
3725
  };
@@ -4330,6 +4330,1692 @@ var PageHeader = React76.forwardRef(
4330
4330
  )
4331
4331
  );
4332
4332
  PageHeader.displayName = "PageHeader";
4333
+
4334
+ // src/components/notification-center.tsx
4335
+ import * as React77 from "react";
4336
+ import { Bell } from "lucide-react";
4337
+ import { jsx as jsx82, jsxs as jsxs48 } from "react/jsx-runtime";
4338
+ var NotificationCenter = Popover;
4339
+ var NotificationCenterTrigger = React77.forwardRef(
4340
+ ({ className, unreadCount = 0, children, ...props }, ref) => /* @__PURE__ */ jsx82(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs48(
4341
+ Button,
4342
+ {
4343
+ ref,
4344
+ variant: "Ghost",
4345
+ size: "icon",
4346
+ "aria-label": unreadCount > 0 ? `Notifications, ${unreadCount} unread` : "Notifications",
4347
+ className: cn("relative", className),
4348
+ ...props,
4349
+ children: [
4350
+ children ?? /* @__PURE__ */ jsx82(Bell, { className: "size-5" }),
4351
+ unreadCount > 0 && /* @__PURE__ */ jsx82("span", { className: "absolute right-1.5 top-1.5 flex size-2 rounded-full bg-destructive ring-2 ring-background" })
4352
+ ]
4353
+ }
4354
+ ) })
4355
+ );
4356
+ NotificationCenterTrigger.displayName = "NotificationCenterTrigger";
4357
+ var NotificationCenterContent = React77.forwardRef(
4358
+ ({ className, onMarkAllRead, children, align = "end", ...props }, ref) => /* @__PURE__ */ jsxs48(PopoverContent, { ref, align, className: cn("w-80 p-0", className), ...props, children: [
4359
+ /* @__PURE__ */ jsxs48("div", { className: "flex items-center justify-between border-b border-border px-4 py-3", children: [
4360
+ /* @__PURE__ */ jsx82("p", { className: "text-title-sm font-medium text-foreground", children: "Notifications" }),
4361
+ onMarkAllRead && /* @__PURE__ */ jsx82(
4362
+ "button",
4363
+ {
4364
+ type: "button",
4365
+ onClick: onMarkAllRead,
4366
+ className: "text-label-md font-medium text-primary outline-none hover:underline focus-visible:underline",
4367
+ children: "Mark all read"
4368
+ }
4369
+ )
4370
+ ] }),
4371
+ /* @__PURE__ */ jsx82("div", { className: "flex max-h-80 flex-col divide-y divide-border overflow-y-auto", children })
4372
+ ] })
4373
+ );
4374
+ NotificationCenterContent.displayName = "NotificationCenterContent";
4375
+ var NotificationItem = React77.forwardRef(
4376
+ ({ className, title, description, time, unread, onSelect, ...props }, ref) => {
4377
+ const interactive = !!onSelect;
4378
+ return /* @__PURE__ */ jsxs48(
4379
+ "div",
4380
+ {
4381
+ ref,
4382
+ role: interactive ? "button" : void 0,
4383
+ tabIndex: interactive ? 0 : void 0,
4384
+ onClick: onSelect,
4385
+ onKeyDown: interactive ? (e) => {
4386
+ if (e.key === "Enter" || e.key === " ") {
4387
+ e.preventDefault();
4388
+ onSelect?.();
4389
+ }
4390
+ } : void 0,
4391
+ className: cn(
4392
+ "flex items-start gap-3 px-4 py-3 font-sans outline-none",
4393
+ interactive && "cursor-pointer hover:bg-accent focus-visible:bg-accent",
4394
+ className
4395
+ ),
4396
+ ...props,
4397
+ children: [
4398
+ /* @__PURE__ */ jsx82("span", { className: cn("mt-1.5 size-2 shrink-0 rounded-full", unread ? "bg-primary" : "bg-transparent") }),
4399
+ /* @__PURE__ */ jsxs48("div", { className: "min-w-0 flex-1", children: [
4400
+ /* @__PURE__ */ jsx82("p", { className: cn("truncate text-body-sm", unread ? "font-medium text-foreground" : "text-muted-foreground"), children: title }),
4401
+ description && /* @__PURE__ */ jsx82("p", { className: "truncate text-body-sm text-muted-foreground", children: description }),
4402
+ time && /* @__PURE__ */ jsx82("p", { className: "mt-0.5 text-label-sm text-muted-foreground", children: time })
4403
+ ] })
4404
+ ]
4405
+ }
4406
+ );
4407
+ }
4408
+ );
4409
+ NotificationItem.displayName = "NotificationItem";
4410
+
4411
+ // src/components/tree-view.tsx
4412
+ import * as React78 from "react";
4413
+ import { ChevronRight as ChevronRight7 } from "lucide-react";
4414
+ import { jsx as jsx83, jsxs as jsxs49 } from "react/jsx-runtime";
4415
+ var TreeViewContext = React78.createContext(null);
4416
+ function useTreeViewContext() {
4417
+ const ctx = React78.useContext(TreeViewContext);
4418
+ if (!ctx) throw new Error("TreeItem must be rendered inside a TreeView");
4419
+ return ctx;
4420
+ }
4421
+ var TreeLevelContext = React78.createContext(0);
4422
+ var TreeView = React78.forwardRef(
4423
+ ({
4424
+ className,
4425
+ checkable = false,
4426
+ expanded,
4427
+ defaultExpanded = [],
4428
+ onExpandedChange,
4429
+ selected,
4430
+ onSelectedChange,
4431
+ checkedValues,
4432
+ defaultCheckedValues = [],
4433
+ onCheckedChange,
4434
+ children,
4435
+ ...props
4436
+ }, ref) => {
4437
+ const [internalExpanded, setInternalExpanded] = React78.useState(defaultExpanded);
4438
+ const currentExpanded = expanded ?? internalExpanded;
4439
+ const [internalChecked, setInternalChecked] = React78.useState(defaultCheckedValues);
4440
+ const currentChecked = checkedValues ?? internalChecked;
4441
+ const [tabbableValue, setTabbableValue] = React78.useState(null);
4442
+ const firstValueRef = React78.useRef(null);
4443
+ const toggleExpanded = React78.useCallback(
4444
+ (value) => {
4445
+ const next = currentExpanded.includes(value) ? currentExpanded.filter((v) => v !== value) : [...currentExpanded, value];
4446
+ setInternalExpanded(next);
4447
+ onExpandedChange?.(next);
4448
+ },
4449
+ [currentExpanded, onExpandedChange]
4450
+ );
4451
+ const toggleChecked = React78.useCallback(
4452
+ (value) => {
4453
+ const next = currentChecked.includes(value) ? currentChecked.filter((v) => v !== value) : [...currentChecked, value];
4454
+ setInternalChecked(next);
4455
+ onCheckedChange?.(next);
4456
+ },
4457
+ [currentChecked, onCheckedChange]
4458
+ );
4459
+ const contextValue = {
4460
+ isExpanded: (value) => currentExpanded.includes(value),
4461
+ toggleExpanded,
4462
+ selected,
4463
+ select: (value) => onSelectedChange?.(value),
4464
+ checkable,
4465
+ isChecked: (value) => currentChecked.includes(value),
4466
+ toggleChecked,
4467
+ tabbableValue,
4468
+ setTabbableValue,
4469
+ firstValueRef
4470
+ };
4471
+ return /* @__PURE__ */ jsx83(
4472
+ "div",
4473
+ {
4474
+ ref,
4475
+ role: "tree",
4476
+ "aria-multiselectable": checkable || void 0,
4477
+ className: cn("flex flex-col font-sans", className),
4478
+ ...props,
4479
+ children: /* @__PURE__ */ jsx83(TreeViewContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx83(TreeLevelContext.Provider, { value: 0, children }) })
4480
+ }
4481
+ );
4482
+ }
4483
+ );
4484
+ TreeView.displayName = "TreeView";
4485
+ var TreeItem = React78.forwardRef(
4486
+ ({ className, value, label, icon, disabled, children, onClick, onKeyDown, ...props }, ref) => {
4487
+ const ctx = useTreeViewContext();
4488
+ const level = React78.useContext(TreeLevelContext);
4489
+ const hasChildren = React78.Children.count(children) > 0;
4490
+ const expanded = ctx.isExpanded(value);
4491
+ const isChecked = ctx.isChecked(value);
4492
+ if (ctx.firstValueRef.current === null) ctx.firstValueRef.current = value;
4493
+ const isTabbable = (ctx.tabbableValue ?? ctx.firstValueRef.current) === value;
4494
+ const localRef = React78.useRef(null);
4495
+ const setRefs = (node) => {
4496
+ localRef.current = node;
4497
+ if (typeof ref === "function") ref(node);
4498
+ else if (ref) ref.current = node;
4499
+ };
4500
+ const activate = () => {
4501
+ ctx.select(value);
4502
+ if (ctx.checkable) ctx.toggleChecked(value);
4503
+ };
4504
+ const handleKeyDown = (e) => {
4505
+ onKeyDown?.(e);
4506
+ if (e.defaultPrevented || disabled) return;
4507
+ const root = localRef.current?.closest('[role="tree"]');
4508
+ const items = root ? Array.from(root.querySelectorAll('[role="treeitem"]')) : [];
4509
+ const idx = items.indexOf(e.currentTarget);
4510
+ switch (e.key) {
4511
+ case "ArrowDown":
4512
+ e.preventDefault();
4513
+ items[idx + 1]?.focus();
4514
+ break;
4515
+ case "ArrowUp":
4516
+ e.preventDefault();
4517
+ items[idx - 1]?.focus();
4518
+ break;
4519
+ case "Home":
4520
+ e.preventDefault();
4521
+ items[0]?.focus();
4522
+ break;
4523
+ case "End":
4524
+ e.preventDefault();
4525
+ items[items.length - 1]?.focus();
4526
+ break;
4527
+ case "ArrowRight":
4528
+ e.preventDefault();
4529
+ if (hasChildren && !expanded) ctx.toggleExpanded(value);
4530
+ else if (hasChildren) items[idx + 1]?.focus();
4531
+ break;
4532
+ case "ArrowLeft":
4533
+ e.preventDefault();
4534
+ if (hasChildren && expanded) ctx.toggleExpanded(value);
4535
+ else {
4536
+ const parent = localRef.current?.parentElement?.closest('[role="treeitem"]');
4537
+ parent?.focus();
4538
+ }
4539
+ break;
4540
+ case "Enter":
4541
+ case " ":
4542
+ e.preventDefault();
4543
+ activate();
4544
+ break;
4545
+ }
4546
+ };
4547
+ return /* @__PURE__ */ jsxs49(
4548
+ "div",
4549
+ {
4550
+ ref: setRefs,
4551
+ role: "treeitem",
4552
+ "aria-expanded": hasChildren ? expanded : void 0,
4553
+ "aria-selected": ctx.selected === value,
4554
+ "aria-disabled": disabled || void 0,
4555
+ tabIndex: disabled ? void 0 : isTabbable ? 0 : -1,
4556
+ onFocus: () => ctx.setTabbableValue(value),
4557
+ onKeyDown: handleKeyDown,
4558
+ onClick: (e) => {
4559
+ onClick?.(e);
4560
+ if (e.defaultPrevented || disabled) return;
4561
+ activate();
4562
+ },
4563
+ className: cn("outline-none", disabled && "pointer-events-none opacity-disabled", className),
4564
+ ...props,
4565
+ children: [
4566
+ /* @__PURE__ */ jsxs49(
4567
+ "div",
4568
+ {
4569
+ style: { paddingLeft: level * 20 + 8 },
4570
+ className: cn(
4571
+ "flex items-center gap-1.5 rounded-sm py-1.5 pr-2 text-body-sm text-foreground",
4572
+ "hover:bg-accent",
4573
+ ctx.selected === value && "bg-accent",
4574
+ "focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring"
4575
+ ),
4576
+ children: [
4577
+ hasChildren ? /* @__PURE__ */ jsx83(
4578
+ ChevronRight7,
4579
+ {
4580
+ className: cn("size-4 shrink-0 text-muted-foreground transition-transform", expanded && "rotate-90"),
4581
+ onClick: (e) => {
4582
+ e.stopPropagation();
4583
+ ctx.toggleExpanded(value);
4584
+ }
4585
+ }
4586
+ ) : /* @__PURE__ */ jsx83("span", { className: "size-4 shrink-0" }),
4587
+ ctx.checkable && /* @__PURE__ */ jsx83(
4588
+ Checkbox,
4589
+ {
4590
+ checked: isChecked,
4591
+ onCheckedChange: () => ctx.toggleChecked(value),
4592
+ onClick: (e) => e.stopPropagation(),
4593
+ tabIndex: -1
4594
+ }
4595
+ ),
4596
+ icon,
4597
+ /* @__PURE__ */ jsx83("span", { className: "truncate", children: label })
4598
+ ]
4599
+ }
4600
+ ),
4601
+ hasChildren && expanded && /* @__PURE__ */ jsx83("div", { role: "group", children: /* @__PURE__ */ jsx83(TreeLevelContext.Provider, { value: level + 1, children }) })
4602
+ ]
4603
+ }
4604
+ );
4605
+ }
4606
+ );
4607
+ TreeItem.displayName = "TreeItem";
4608
+
4609
+ // src/components/multi-select.tsx
4610
+ import * as React79 from "react";
4611
+ import { Check as Check8 } from "lucide-react";
4612
+ import { jsx as jsx84, jsxs as jsxs50 } from "react/jsx-runtime";
4613
+ var MultiSelect = React79.forwardRef(
4614
+ ({ className, options, value, defaultValue = [], onValueChange, placeholder = "Select\u2026", creatable = false, ...props }, ref) => {
4615
+ const [internal, setInternal] = React79.useState(defaultValue);
4616
+ const current = value ?? internal;
4617
+ const [open, setOpen] = React79.useState(false);
4618
+ const [query, setQuery] = React79.useState("");
4619
+ const set = (next) => {
4620
+ setInternal(next);
4621
+ onValueChange?.(next);
4622
+ };
4623
+ const toggle = (v) => set(current.includes(v) ? current.filter((x) => x !== v) : [...current, v]);
4624
+ const remove = (v) => set(current.filter((x) => x !== v));
4625
+ const labelFor = (v) => options.find((o) => o.value === v)?.label ?? v;
4626
+ const trimmed = query.trim();
4627
+ const canCreate = creatable && trimmed.length > 0 && !options.some((o) => o.label.toLowerCase() === trimmed.toLowerCase());
4628
+ const visibleOptions = creatable ? options.filter((o) => o.label.toLowerCase().includes(trimmed.toLowerCase())) : options;
4629
+ return /* @__PURE__ */ jsxs50(Popover, { open, onOpenChange: setOpen, children: [
4630
+ /* @__PURE__ */ jsx84(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxs50(
4631
+ "div",
4632
+ {
4633
+ ref,
4634
+ role: "combobox",
4635
+ "aria-expanded": open,
4636
+ "aria-haspopup": "listbox",
4637
+ tabIndex: 0,
4638
+ onClick: () => setOpen(true),
4639
+ onKeyDown: (e) => {
4640
+ if (e.key === "Enter" || e.key === " ") {
4641
+ e.preventDefault();
4642
+ setOpen(true);
4643
+ }
4644
+ },
4645
+ className: cn(
4646
+ "flex min-h-11 w-full flex-wrap items-center gap-1.5 border border-input bg-background px-3 py-2 font-sans",
4647
+ "outline-none focus-visible:border-primary focus-visible:shadow-focus",
4648
+ "aria-expanded:border-primary aria-expanded:shadow-focus",
4649
+ className
4650
+ ),
4651
+ ...props,
4652
+ children: [
4653
+ current.length === 0 && /* @__PURE__ */ jsx84("span", { className: "text-body-sm text-muted-foreground", children: placeholder }),
4654
+ current.map((v) => /* @__PURE__ */ jsx84(Tag, { variant: "secondary", onRemove: () => remove(v), onClick: (e) => e.stopPropagation(), children: labelFor(v) }, v))
4655
+ ]
4656
+ }
4657
+ ) }),
4658
+ /* @__PURE__ */ jsx84(
4659
+ PopoverContent,
4660
+ {
4661
+ align: "start",
4662
+ className: "w-[var(--radix-popover-trigger-width)] p-0",
4663
+ onOpenAutoFocus: (e) => e.preventDefault(),
4664
+ children: /* @__PURE__ */ jsxs50(Command, { shouldFilter: !creatable, children: [
4665
+ /* @__PURE__ */ jsx84(CommandInput, { placeholder: "Search\u2026", value: query, onValueChange: setQuery }),
4666
+ /* @__PURE__ */ jsxs50(CommandList, { children: [
4667
+ /* @__PURE__ */ jsx84(CommandEmpty, { children: canCreate ? /* @__PURE__ */ jsxs50(
4668
+ "button",
4669
+ {
4670
+ type: "button",
4671
+ className: "flex w-full items-center px-2 py-1.5 text-left text-body-sm text-foreground hover:bg-accent",
4672
+ onClick: () => {
4673
+ toggle(trimmed);
4674
+ setQuery("");
4675
+ },
4676
+ children: [
4677
+ "Create \u201C",
4678
+ trimmed,
4679
+ "\u201D"
4680
+ ]
4681
+ }
4682
+ ) : "No results." }),
4683
+ /* @__PURE__ */ jsx84(CommandGroup, { children: visibleOptions.map((option) => /* @__PURE__ */ jsxs50(CommandItem, { value: option.label, onSelect: () => toggle(option.value), children: [
4684
+ /* @__PURE__ */ jsx84(Check8, { className: cn("size-4", current.includes(option.value) ? "opacity-100" : "opacity-0") }),
4685
+ option.label
4686
+ ] }, option.value)) })
4687
+ ] })
4688
+ ] })
4689
+ }
4690
+ )
4691
+ ] });
4692
+ }
4693
+ );
4694
+ MultiSelect.displayName = "MultiSelect";
4695
+
4696
+ // src/components/message-bubble.tsx
4697
+ import * as React80 from "react";
4698
+ import { Check as Check9, CheckCheck } from "lucide-react";
4699
+ import { jsx as jsx85, jsxs as jsxs51 } from "react/jsx-runtime";
4700
+ var MessageBubble = React80.forwardRef(
4701
+ ({ className, variant = "received", timestamp, status, grouped = false, avatar, children, ...props }, ref) => {
4702
+ const sent = variant === "sent";
4703
+ return /* @__PURE__ */ jsxs51("div", { ref, className: cn("flex items-end gap-2 font-sans", sent && "flex-row-reverse", className), ...props, children: [
4704
+ avatar && !sent && /* @__PURE__ */ jsx85("div", { className: "shrink-0 self-end", children: avatar }),
4705
+ /* @__PURE__ */ jsxs51("div", { className: cn("flex max-w-[75%] flex-col gap-1", sent ? "items-end" : "items-start"), children: [
4706
+ /* @__PURE__ */ jsx85(
4707
+ "div",
4708
+ {
4709
+ className: cn(
4710
+ "min-w-0 rounded-2xl px-3.5 py-2 text-body-sm",
4711
+ sent ? "bg-primary text-primary-foreground" : "bg-muted text-foreground",
4712
+ sent && grouped && "rounded-tr-md",
4713
+ !sent && grouped && "rounded-tl-md"
4714
+ ),
4715
+ children
4716
+ }
4717
+ ),
4718
+ (timestamp || sent && status) && /* @__PURE__ */ jsxs51("div", { className: "flex items-center gap-1 px-1 text-label-sm text-muted-foreground", children: [
4719
+ timestamp,
4720
+ sent && status && /* @__PURE__ */ jsx85("span", { className: cn(status === "read" && "text-primary"), children: status === "sent" ? /* @__PURE__ */ jsx85(Check9, { className: "size-3.5" }) : /* @__PURE__ */ jsx85(CheckCheck, { className: "size-3.5" }) })
4721
+ ] })
4722
+ ] })
4723
+ ] });
4724
+ }
4725
+ );
4726
+ MessageBubble.displayName = "MessageBubble";
4727
+ var TypingIndicator = React80.forwardRef(
4728
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx85(
4729
+ "div",
4730
+ {
4731
+ ref,
4732
+ role: "status",
4733
+ "aria-label": "Typing",
4734
+ className: cn("flex w-fit items-center gap-1 rounded-2xl bg-muted px-3.5 py-2.5", className),
4735
+ ...props,
4736
+ children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx85(
4737
+ "span",
4738
+ {
4739
+ "aria-hidden": true,
4740
+ className: "size-1.5 animate-typing-dot rounded-full bg-muted-foreground",
4741
+ style: { animationDelay: `${i * 150}ms` }
4742
+ },
4743
+ i
4744
+ ))
4745
+ }
4746
+ )
4747
+ );
4748
+ TypingIndicator.displayName = "TypingIndicator";
4749
+
4750
+ // src/components/tour.tsx
4751
+ import * as React81 from "react";
4752
+ import { createPortal } from "react-dom";
4753
+ import { Fragment, jsx as jsx86, jsxs as jsxs52 } from "react/jsx-runtime";
4754
+ var SPOTLIGHT_PADDING = 8;
4755
+ var Tour = ({
4756
+ steps,
4757
+ open,
4758
+ stepIndex,
4759
+ onStepIndexChange,
4760
+ onOpenChange,
4761
+ nextLabel = "Next",
4762
+ backLabel = "Back",
4763
+ skipLabel = "Skip",
4764
+ doneLabel = "Done"
4765
+ }) => {
4766
+ const [mounted, setMounted] = React81.useState(false);
4767
+ const [rect, setRect] = React81.useState(null);
4768
+ const step = steps[stepIndex];
4769
+ React81.useEffect(() => setMounted(true), []);
4770
+ React81.useEffect(() => {
4771
+ if (!open || !step) return;
4772
+ const update = () => {
4773
+ const el = document.querySelector(step.target);
4774
+ if (el) {
4775
+ setRect(el.getBoundingClientRect());
4776
+ el.scrollIntoView({ behavior: "smooth", block: "center" });
4777
+ } else {
4778
+ setRect(null);
4779
+ }
4780
+ };
4781
+ update();
4782
+ window.addEventListener("resize", update);
4783
+ window.addEventListener("scroll", update, true);
4784
+ return () => {
4785
+ window.removeEventListener("resize", update);
4786
+ window.removeEventListener("scroll", update, true);
4787
+ };
4788
+ }, [open, step]);
4789
+ React81.useEffect(() => {
4790
+ if (!open) return;
4791
+ const onKeyDown = (e) => {
4792
+ if (e.key === "Escape") onOpenChange(false);
4793
+ };
4794
+ document.addEventListener("keydown", onKeyDown);
4795
+ return () => document.removeEventListener("keydown", onKeyDown);
4796
+ }, [open, onOpenChange]);
4797
+ if (!mounted || !open || !step) return null;
4798
+ const isFirst = stepIndex === 0;
4799
+ const isLast = stepIndex === steps.length - 1;
4800
+ const spotlightStyle = rect ? {
4801
+ top: rect.top - SPOTLIGHT_PADDING,
4802
+ left: rect.left - SPOTLIGHT_PADDING,
4803
+ width: rect.width + SPOTLIGHT_PADDING * 2,
4804
+ height: rect.height + SPOTLIGHT_PADDING * 2,
4805
+ boxShadow: "0 0 0 9999px rgb(0 0 0 / 0.6)"
4806
+ } : { inset: 0, background: "rgb(0 0 0 / 0.6)" };
4807
+ const cardStyle = rect ? {
4808
+ top: Math.min(rect.bottom + SPOTLIGHT_PADDING + 8, window.innerHeight - 220),
4809
+ left: Math.min(Math.max(rect.left, 16), window.innerWidth - 336)
4810
+ } : { top: "50%", left: "50%", transform: "translate(-50%, -50%)" };
4811
+ return createPortal(
4812
+ /* @__PURE__ */ jsxs52(Fragment, { children: [
4813
+ /* @__PURE__ */ jsx86(
4814
+ "div",
4815
+ {
4816
+ "aria-hidden": true,
4817
+ className: "pointer-events-none fixed z-overlay rounded-lg transition-all duration-fast",
4818
+ style: spotlightStyle
4819
+ }
4820
+ ),
4821
+ /* @__PURE__ */ jsxs52(
4822
+ "div",
4823
+ {
4824
+ role: "dialog",
4825
+ "aria-modal": "true",
4826
+ className: "fixed z-overlay w-80 rounded-lg border border-border bg-popover p-4 font-sans text-popover-foreground shadow-lg transition-all duration-fast",
4827
+ style: cardStyle,
4828
+ children: [
4829
+ /* @__PURE__ */ jsx86("p", { className: "text-title-sm font-medium text-foreground", children: step.title }),
4830
+ /* @__PURE__ */ jsx86("div", { className: "mt-1.5 text-body-sm text-muted-foreground", children: step.content }),
4831
+ /* @__PURE__ */ jsxs52("div", { className: "mt-4 flex items-center justify-between", children: [
4832
+ /* @__PURE__ */ jsxs52("span", { className: "text-label-sm text-muted-foreground", children: [
4833
+ stepIndex + 1,
4834
+ " / ",
4835
+ steps.length
4836
+ ] }),
4837
+ /* @__PURE__ */ jsxs52("div", { className: "flex items-center gap-2", children: [
4838
+ !isFirst && /* @__PURE__ */ jsx86(Button, { variant: "Ghost", size: "sm", onClick: () => onStepIndexChange(stepIndex - 1), children: backLabel }),
4839
+ !isLast ? /* @__PURE__ */ jsxs52(Fragment, { children: [
4840
+ /* @__PURE__ */ jsx86(Button, { variant: "Link", size: "sm", onClick: () => onOpenChange(false), children: skipLabel }),
4841
+ /* @__PURE__ */ jsx86(Button, { size: "sm", onClick: () => onStepIndexChange(stepIndex + 1), children: nextLabel })
4842
+ ] }) : /* @__PURE__ */ jsx86(Button, { size: "sm", onClick: () => onOpenChange(false), children: doneLabel })
4843
+ ] })
4844
+ ] })
4845
+ ]
4846
+ }
4847
+ )
4848
+ ] }),
4849
+ document.body
4850
+ );
4851
+ };
4852
+
4853
+ // src/components/virtual-list.tsx
4854
+ import * as React82 from "react";
4855
+ import { jsx as jsx87 } from "react/jsx-runtime";
4856
+ function VirtualList({
4857
+ items,
4858
+ itemHeight,
4859
+ height,
4860
+ renderItem,
4861
+ overscan = 4,
4862
+ getItemKey,
4863
+ className,
4864
+ style,
4865
+ onScroll,
4866
+ ...props
4867
+ }) {
4868
+ const containerRef = React82.useRef(null);
4869
+ const [scrollTop, setScrollTop] = React82.useState(0);
4870
+ const [viewportHeight, setViewportHeight] = React82.useState(typeof height === "number" ? height : 0);
4871
+ React82.useEffect(() => {
4872
+ const el = containerRef.current;
4873
+ if (!el) return;
4874
+ const observer = new ResizeObserver((entries) => {
4875
+ const entry = entries[0];
4876
+ if (entry) setViewportHeight(entry.contentRect.height);
4877
+ });
4878
+ observer.observe(el);
4879
+ return () => observer.disconnect();
4880
+ }, []);
4881
+ const totalHeight = items.length * itemHeight;
4882
+ const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);
4883
+ const visibleCount = Math.ceil(viewportHeight / itemHeight) + overscan * 2;
4884
+ const endIndex = Math.min(items.length, startIndex + Math.max(visibleCount, 0));
4885
+ const visibleItems = items.slice(startIndex, endIndex);
4886
+ return /* @__PURE__ */ jsx87(
4887
+ "div",
4888
+ {
4889
+ ref: containerRef,
4890
+ role: "list",
4891
+ className: cn("overflow-y-auto font-sans", className),
4892
+ style: { height, ...style },
4893
+ onScroll: (e) => {
4894
+ onScroll?.(e);
4895
+ setScrollTop(e.currentTarget.scrollTop);
4896
+ },
4897
+ ...props,
4898
+ children: /* @__PURE__ */ jsx87("div", { style: { height: totalHeight, position: "relative" }, children: visibleItems.map((item, i) => {
4899
+ const index = startIndex + i;
4900
+ return /* @__PURE__ */ jsx87(
4901
+ "div",
4902
+ {
4903
+ role: "listitem",
4904
+ style: { position: "absolute", top: index * itemHeight, left: 0, right: 0, height: itemHeight },
4905
+ children: renderItem(item, index)
4906
+ },
4907
+ getItemKey ? getItemKey(item, index) : index
4908
+ );
4909
+ }) })
4910
+ }
4911
+ );
4912
+ }
4913
+
4914
+ // src/components/data-grid.tsx
4915
+ import * as React83 from "react";
4916
+ import { jsx as jsx88, jsxs as jsxs53 } from "react/jsx-runtime";
4917
+ var DEFAULT_WIDTH = 160;
4918
+ var DEFAULT_MIN_WIDTH = 60;
4919
+ var DEFAULT_ROW_HEIGHT = 40;
4920
+ function DataGrid({
4921
+ columns,
4922
+ data,
4923
+ height,
4924
+ rowHeight = DEFAULT_ROW_HEIGHT,
4925
+ overscan = 6,
4926
+ getRowId,
4927
+ className
4928
+ }) {
4929
+ const columnById = React83.useMemo(() => new Map(columns.map((c) => [c.id, c])), [columns]);
4930
+ const [order, setOrder] = React83.useState(() => columns.map((c) => c.id));
4931
+ const [widths, setWidths] = React83.useState(
4932
+ () => Object.fromEntries(columns.map((c) => [c.id, c.width ?? DEFAULT_WIDTH]))
4933
+ );
4934
+ const [sort, setSort] = React83.useState(null);
4935
+ const [editing, setEditing] = React83.useState(null);
4936
+ const dragIdRef = React83.useRef(null);
4937
+ React83.useEffect(() => {
4938
+ const knownIds = columns.map((c) => c.id);
4939
+ const known = new Set(knownIds);
4940
+ setOrder((prev) => {
4941
+ const kept = prev.filter((id) => known.has(id));
4942
+ const added = knownIds.filter((id) => !kept.includes(id));
4943
+ return [...kept, ...added];
4944
+ });
4945
+ setWidths((prev) => {
4946
+ const next = { ...prev };
4947
+ for (const c of columns) if (!(c.id in next)) next[c.id] = c.width ?? DEFAULT_WIDTH;
4948
+ return next;
4949
+ });
4950
+ }, [columns]);
4951
+ const leftPinned = columns.filter((c) => c.pinned === "left");
4952
+ const rightPinned = columns.filter((c) => c.pinned === "right");
4953
+ const unpinnedOrdered = order.filter((id) => columnById.get(id) && !columnById.get(id).pinned).map((id) => columnById.get(id));
4954
+ const renderedColumns = [...leftPinned, ...unpinnedOrdered, ...rightPinned];
4955
+ const leftOffsets = React83.useMemo(() => {
4956
+ const offsets = {};
4957
+ let acc = 0;
4958
+ for (const c of leftPinned) {
4959
+ offsets[c.id] = acc;
4960
+ acc += widths[c.id] ?? c.width ?? DEFAULT_WIDTH;
4961
+ }
4962
+ return offsets;
4963
+ }, [columns, widths]);
4964
+ const rightOffsets = React83.useMemo(() => {
4965
+ const offsets = {};
4966
+ let acc = 0;
4967
+ for (const c of [...rightPinned].reverse()) {
4968
+ offsets[c.id] = acc;
4969
+ acc += widths[c.id] ?? c.width ?? DEFAULT_WIDTH;
4970
+ }
4971
+ return offsets;
4972
+ }, [columns, widths]);
4973
+ const totalWidth = renderedColumns.reduce((sum, c) => sum + (widths[c.id] ?? c.width ?? DEFAULT_WIDTH), 0);
4974
+ const sortedData = React83.useMemo(() => {
4975
+ if (!sort) return data;
4976
+ const col = columnById.get(sort.columnId);
4977
+ if (!col?.value) return data;
4978
+ const indexed = data.map((row, index) => ({ row, index }));
4979
+ indexed.sort((a, b) => {
4980
+ const av = col.value(a.row);
4981
+ const bv = col.value(b.row);
4982
+ if (av === bv) return a.index - b.index;
4983
+ const cmp = av < bv ? -1 : 1;
4984
+ return sort.direction === "asc" ? cmp : -cmp;
4985
+ });
4986
+ return indexed.map((entry) => entry.row);
4987
+ }, [data, sort, columnById]);
4988
+ const containerRef = React83.useRef(null);
4989
+ const [scrollTop, setScrollTop] = React83.useState(0);
4990
+ const [viewportHeight, setViewportHeight] = React83.useState(typeof height === "number" ? height : 0);
4991
+ React83.useEffect(() => {
4992
+ const el = containerRef.current;
4993
+ if (!el) return;
4994
+ const observer = new ResizeObserver((entries) => {
4995
+ const entry = entries[0];
4996
+ if (entry) setViewportHeight(entry.contentRect.height);
4997
+ });
4998
+ observer.observe(el);
4999
+ return () => observer.disconnect();
5000
+ }, []);
5001
+ const totalHeight = sortedData.length * rowHeight;
5002
+ const startIndex = Math.max(0, Math.floor(scrollTop / rowHeight) - overscan);
5003
+ const visibleCount = Math.ceil(viewportHeight / rowHeight) + overscan * 2;
5004
+ const endIndex = Math.min(sortedData.length, startIndex + Math.max(visibleCount, 0));
5005
+ const visibleRows = sortedData.slice(startIndex, endIndex);
5006
+ function toggleSort(columnId) {
5007
+ setSort((prev) => {
5008
+ if (!prev || prev.columnId !== columnId) return { columnId, direction: "asc" };
5009
+ if (prev.direction === "asc") return { columnId, direction: "desc" };
5010
+ return null;
5011
+ });
5012
+ }
5013
+ function beginResize(columnId, minWidth, startX, startWidth) {
5014
+ const handleMove = (ev) => {
5015
+ setWidths((prev) => ({ ...prev, [columnId]: Math.max(minWidth, startWidth + (ev.clientX - startX)) }));
5016
+ };
5017
+ const handleUp = () => {
5018
+ window.removeEventListener("pointermove", handleMove);
5019
+ window.removeEventListener("pointerup", handleUp);
5020
+ };
5021
+ window.addEventListener("pointermove", handleMove);
5022
+ window.addEventListener("pointerup", handleUp);
5023
+ }
5024
+ function commitEdit(column, row, rowIndex, value) {
5025
+ column.onCellEdit?.(row, rowIndex, value);
5026
+ setEditing(null);
5027
+ }
5028
+ return /* @__PURE__ */ jsx88(
5029
+ "div",
5030
+ {
5031
+ ref: containerRef,
5032
+ role: "grid",
5033
+ className: cn("relative overflow-auto rounded-md border font-sans text-sm", className),
5034
+ style: { height },
5035
+ onScroll: (e) => setScrollTop(e.currentTarget.scrollTop),
5036
+ children: /* @__PURE__ */ jsxs53("div", { style: { width: totalWidth, minWidth: "100%" }, children: [
5037
+ /* @__PURE__ */ jsx88("div", { role: "row", className: "sticky top-0 z-[2] flex border-b bg-background", children: renderedColumns.map((column) => {
5038
+ const width = widths[column.id] ?? column.width ?? DEFAULT_WIDTH;
5039
+ const pinnedStyle = column.pinned === "left" ? { position: "sticky", left: leftOffsets[column.id], zIndex: 3 } : column.pinned === "right" ? { position: "sticky", right: rightOffsets[column.id], zIndex: 3 } : {};
5040
+ const isSorted = sort?.columnId === column.id;
5041
+ return /* @__PURE__ */ jsxs53(
5042
+ "div",
5043
+ {
5044
+ role: "columnheader",
5045
+ ...column.sortable ? { "aria-sort": isSorted ? sort.direction === "asc" ? "ascending" : "descending" : "none" } : {},
5046
+ draggable: !column.pinned,
5047
+ onDragStart: () => {
5048
+ dragIdRef.current = column.id;
5049
+ },
5050
+ onDragOver: (e) => {
5051
+ if (!column.pinned) e.preventDefault();
5052
+ },
5053
+ onDrop: (e) => {
5054
+ e.preventDefault();
5055
+ const draggedId = dragIdRef.current;
5056
+ dragIdRef.current = null;
5057
+ if (!draggedId || draggedId === column.id) return;
5058
+ setOrder((prev) => {
5059
+ const next = prev.filter((id) => id !== draggedId);
5060
+ const targetIndex = next.indexOf(column.id);
5061
+ next.splice(targetIndex, 0, draggedId);
5062
+ return next;
5063
+ });
5064
+ },
5065
+ onClick: () => column.sortable && toggleSort(column.id),
5066
+ className: cn(
5067
+ "relative flex h-10 shrink-0 select-none items-center gap-1 px-2 font-medium text-muted-foreground",
5068
+ column.sortable && "cursor-pointer hover:text-foreground",
5069
+ column.pinned && "bg-background"
5070
+ ),
5071
+ style: { width, ...pinnedStyle },
5072
+ children: [
5073
+ /* @__PURE__ */ jsx88("span", { className: "truncate", children: column.header }),
5074
+ column.sortable && isSorted && /* @__PURE__ */ jsx88("span", { "aria-hidden": "true", className: "text-xs", children: sort.direction === "asc" ? "\u25B2" : "\u25BC" }),
5075
+ /* @__PURE__ */ jsx88(
5076
+ "div",
5077
+ {
5078
+ onPointerDown: (e) => {
5079
+ e.stopPropagation();
5080
+ beginResize(column.id, column.minWidth ?? DEFAULT_MIN_WIDTH, e.clientX, width);
5081
+ },
5082
+ className: "absolute right-0 top-0 h-full w-1 touch-none cursor-col-resize hover:bg-primary/50"
5083
+ }
5084
+ )
5085
+ ]
5086
+ },
5087
+ column.id
5088
+ );
5089
+ }) }),
5090
+ /* @__PURE__ */ jsx88("div", { style: { height: totalHeight, position: "relative" }, children: visibleRows.map((row, i) => {
5091
+ const rowIndex = startIndex + i;
5092
+ return /* @__PURE__ */ jsx88(
5093
+ "div",
5094
+ {
5095
+ role: "row",
5096
+ className: "absolute flex w-full border-b hover:bg-muted/50",
5097
+ style: { top: rowIndex * rowHeight, height: rowHeight },
5098
+ children: renderedColumns.map((column) => {
5099
+ const width = widths[column.id] ?? column.width ?? DEFAULT_WIDTH;
5100
+ const pinnedStyle = column.pinned === "left" ? { position: "sticky", left: leftOffsets[column.id], zIndex: 1 } : column.pinned === "right" ? { position: "sticky", right: rightOffsets[column.id], zIndex: 1 } : {};
5101
+ const isEditing = editing?.rowIndex === rowIndex && editing.columnId === column.id;
5102
+ return /* @__PURE__ */ jsx88(
5103
+ "div",
5104
+ {
5105
+ role: "gridcell",
5106
+ onDoubleClick: () => column.editable && setEditing({ rowIndex, columnId: column.id }),
5107
+ className: cn("flex shrink-0 items-center px-2", column.pinned && "bg-background"),
5108
+ style: { width, ...pinnedStyle },
5109
+ children: isEditing ? /* @__PURE__ */ jsx88(
5110
+ "input",
5111
+ {
5112
+ autoFocus: true,
5113
+ defaultValue: column.value ? String(column.value(row)) : "",
5114
+ className: "h-7 w-full rounded-sm border-0 bg-transparent px-1 text-sm outline-none ring-1 ring-inset ring-ring",
5115
+ onKeyDown: (e) => {
5116
+ if (e.key === "Enter") commitEdit(column, row, rowIndex, e.currentTarget.value);
5117
+ else if (e.key === "Escape") setEditing(null);
5118
+ },
5119
+ onBlur: (e) => commitEdit(column, row, rowIndex, e.currentTarget.value)
5120
+ }
5121
+ ) : /* @__PURE__ */ jsx88("span", { className: "truncate", children: column.cell(row, rowIndex) })
5122
+ },
5123
+ column.id
5124
+ );
5125
+ })
5126
+ },
5127
+ getRowId ? getRowId(row, rowIndex) : rowIndex
5128
+ );
5129
+ }) })
5130
+ ] })
5131
+ }
5132
+ );
5133
+ }
5134
+
5135
+ // src/components/json-viewer.tsx
5136
+ import * as React84 from "react";
5137
+ import { Check as Check10, ChevronRight as ChevronRight8, Copy as Copy2 } from "lucide-react";
5138
+ import { Fragment as Fragment2, jsx as jsx89, jsxs as jsxs54 } from "react/jsx-runtime";
5139
+ var JsonViewer = React84.forwardRef(
5140
+ ({ data, expandDepth = 1, hideCopy, className, ...props }, ref) => {
5141
+ const [copied, setCopied] = React84.useState(false);
5142
+ const copy = async () => {
5143
+ try {
5144
+ await navigator.clipboard.writeText(JSON.stringify(data, null, 2));
5145
+ setCopied(true);
5146
+ window.setTimeout(() => setCopied(false), 1500);
5147
+ } catch {
5148
+ }
5149
+ };
5150
+ return /* @__PURE__ */ jsxs54(
5151
+ "div",
5152
+ {
5153
+ ref,
5154
+ className: cn("relative overflow-auto rounded-md border bg-muted/30 p-3 font-mono text-xs", className),
5155
+ ...props,
5156
+ children: [
5157
+ !hideCopy && /* @__PURE__ */ jsx89(
5158
+ "button",
5159
+ {
5160
+ type: "button",
5161
+ onClick: copy,
5162
+ "aria-label": "Copy JSON",
5163
+ className: "absolute right-2 top-2 inline-flex size-6 items-center justify-center rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground",
5164
+ children: copied ? /* @__PURE__ */ jsx89(Check10, { className: "size-3.5" }) : /* @__PURE__ */ jsx89(Copy2, { className: "size-3.5" })
5165
+ }
5166
+ ),
5167
+ /* @__PURE__ */ jsx89("div", { role: "tree", children: /* @__PURE__ */ jsx89(JsonNode, { value: data, depth: 0, expandDepth, isLast: true }) })
5168
+ ]
5169
+ }
5170
+ );
5171
+ }
5172
+ );
5173
+ JsonViewer.displayName = "JsonViewer";
5174
+ function JsonNode({
5175
+ name,
5176
+ value,
5177
+ depth,
5178
+ expandDepth,
5179
+ isLast
5180
+ }) {
5181
+ const isContainer = value !== null && typeof value === "object";
5182
+ const [expanded, setExpanded] = React84.useState(depth < expandDepth);
5183
+ if (!isContainer) {
5184
+ return /* @__PURE__ */ jsxs54("div", { role: "treeitem", style: { paddingLeft: depth * 16 }, children: [
5185
+ name !== void 0 && /* @__PURE__ */ jsxs54("span", { className: "text-muted-foreground", children: [
5186
+ JSON.stringify(name),
5187
+ ": "
5188
+ ] }),
5189
+ /* @__PURE__ */ jsx89(JsonPrimitive, { value }),
5190
+ !isLast && /* @__PURE__ */ jsx89("span", { className: "text-muted-foreground", children: "," })
5191
+ ] });
5192
+ }
5193
+ const isArray = Array.isArray(value);
5194
+ const entries = isArray ? value.map((v, i) => [String(i), v]) : Object.entries(value);
5195
+ const openBracket = isArray ? "[" : "{";
5196
+ const closeBracket = isArray ? "]" : "}";
5197
+ if (entries.length === 0) {
5198
+ return /* @__PURE__ */ jsxs54("div", { role: "treeitem", style: { paddingLeft: depth * 16 + 20 }, children: [
5199
+ name !== void 0 && /* @__PURE__ */ jsxs54("span", { className: "text-muted-foreground", children: [
5200
+ JSON.stringify(name),
5201
+ ": "
5202
+ ] }),
5203
+ /* @__PURE__ */ jsxs54("span", { children: [
5204
+ openBracket,
5205
+ closeBracket
5206
+ ] }),
5207
+ !isLast && /* @__PURE__ */ jsx89("span", { className: "text-muted-foreground", children: "," })
5208
+ ] });
5209
+ }
5210
+ return /* @__PURE__ */ jsxs54("div", { role: "treeitem", "aria-expanded": expanded, children: [
5211
+ /* @__PURE__ */ jsxs54("div", { style: { paddingLeft: depth * 16 }, className: "flex items-start gap-1", children: [
5212
+ /* @__PURE__ */ jsx89(
5213
+ "button",
5214
+ {
5215
+ type: "button",
5216
+ onClick: () => setExpanded((e) => !e),
5217
+ "aria-label": expanded ? "Collapse" : "Expand",
5218
+ className: "mt-0.5 inline-flex size-4 shrink-0 items-center justify-center text-muted-foreground hover:text-foreground",
5219
+ children: /* @__PURE__ */ jsx89(ChevronRight8, { className: cn("size-3 transition-transform", expanded && "rotate-90") })
5220
+ }
5221
+ ),
5222
+ /* @__PURE__ */ jsxs54("span", { children: [
5223
+ name !== void 0 && /* @__PURE__ */ jsxs54("span", { className: "text-muted-foreground", children: [
5224
+ JSON.stringify(name),
5225
+ ": "
5226
+ ] }),
5227
+ /* @__PURE__ */ jsx89("span", { children: openBracket }),
5228
+ !expanded && /* @__PURE__ */ jsxs54("span", { className: "text-muted-foreground", children: [
5229
+ " ",
5230
+ entries.length,
5231
+ " ",
5232
+ isArray ? "item" : "key",
5233
+ entries.length === 1 ? "" : "s",
5234
+ " "
5235
+ ] }),
5236
+ !expanded && /* @__PURE__ */ jsx89("span", { children: closeBracket }),
5237
+ !expanded && !isLast && /* @__PURE__ */ jsx89("span", { className: "text-muted-foreground", children: "," })
5238
+ ] })
5239
+ ] }),
5240
+ expanded && /* @__PURE__ */ jsxs54(Fragment2, { children: [
5241
+ entries.map(([key, val], i) => /* @__PURE__ */ jsx89(
5242
+ JsonNode,
5243
+ {
5244
+ name: isArray ? void 0 : key,
5245
+ value: val,
5246
+ depth: depth + 1,
5247
+ expandDepth,
5248
+ isLast: i === entries.length - 1
5249
+ },
5250
+ key
5251
+ )),
5252
+ /* @__PURE__ */ jsxs54("div", { style: { paddingLeft: depth * 16 }, children: [
5253
+ /* @__PURE__ */ jsx89("span", { children: closeBracket }),
5254
+ !isLast && /* @__PURE__ */ jsx89("span", { className: "text-muted-foreground", children: "," })
5255
+ ] })
5256
+ ] })
5257
+ ] });
5258
+ }
5259
+ function JsonPrimitive({ value }) {
5260
+ if (value === null) return /* @__PURE__ */ jsx89("span", { className: "italic text-muted-foreground", children: "null" });
5261
+ if (value === void 0) return /* @__PURE__ */ jsx89("span", { className: "italic text-muted-foreground", children: "undefined" });
5262
+ if (typeof value === "string") return /* @__PURE__ */ jsx89("span", { className: "text-success", children: JSON.stringify(value) });
5263
+ if (typeof value === "number") return /* @__PURE__ */ jsx89("span", { className: "text-info", children: value });
5264
+ if (typeof value === "boolean") return /* @__PURE__ */ jsx89("span", { className: "text-warning", children: String(value) });
5265
+ return /* @__PURE__ */ jsx89("span", { children: String(value) });
5266
+ }
5267
+
5268
+ // src/components/diff-viewer.tsx
5269
+ import * as React85 from "react";
5270
+ import { jsx as jsx90, jsxs as jsxs55 } from "react/jsx-runtime";
5271
+ function computeLineDiff(oldText, newText) {
5272
+ const oldLines = oldText.split("\n");
5273
+ const newLines = newText.split("\n");
5274
+ const n = oldLines.length;
5275
+ const m = newLines.length;
5276
+ const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
5277
+ for (let i2 = n - 1; i2 >= 0; i2--) {
5278
+ for (let j2 = m - 1; j2 >= 0; j2--) {
5279
+ dp[i2][j2] = oldLines[i2] === newLines[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
5280
+ }
5281
+ }
5282
+ const ops = [];
5283
+ let i = 0;
5284
+ let j = 0;
5285
+ while (i < n && j < m) {
5286
+ if (oldLines[i] === newLines[j]) {
5287
+ ops.push({ type: "equal", oldLine: i + 1, newLine: j + 1, text: oldLines[i] });
5288
+ i++;
5289
+ j++;
5290
+ } else if (dp[i + 1][j] >= dp[i][j + 1]) {
5291
+ ops.push({ type: "remove", oldLine: i + 1, text: oldLines[i] });
5292
+ i++;
5293
+ } else {
5294
+ ops.push({ type: "add", newLine: j + 1, text: newLines[j] });
5295
+ j++;
5296
+ }
5297
+ }
5298
+ while (i < n) {
5299
+ ops.push({ type: "remove", oldLine: i + 1, text: oldLines[i] });
5300
+ i++;
5301
+ }
5302
+ while (j < m) {
5303
+ ops.push({ type: "add", newLine: j + 1, text: newLines[j] });
5304
+ j++;
5305
+ }
5306
+ return ops;
5307
+ }
5308
+ var DiffViewer = React85.forwardRef(
5309
+ ({ oldText, newText, mode = "unified", oldLabel = "Before", newLabel = "After", className, ...props }, ref) => {
5310
+ const ops = React85.useMemo(() => computeLineDiff(oldText, newText), [oldText, newText]);
5311
+ return /* @__PURE__ */ jsx90("div", { ref, className: cn("overflow-auto rounded-md border font-mono text-xs", className), ...props, children: mode === "split" ? /* @__PURE__ */ jsx90(SplitDiff, { ops, oldLabel, newLabel }) : /* @__PURE__ */ jsx90(UnifiedDiff, { ops }) });
5312
+ }
5313
+ );
5314
+ DiffViewer.displayName = "DiffViewer";
5315
+ function UnifiedDiff({ ops }) {
5316
+ return /* @__PURE__ */ jsx90("div", { role: "table", children: ops.map((op, i) => /* @__PURE__ */ jsxs55(
5317
+ "div",
5318
+ {
5319
+ role: "row",
5320
+ className: cn("flex", op.type === "add" && "bg-success/10", op.type === "remove" && "bg-destructive/10"),
5321
+ children: [
5322
+ /* @__PURE__ */ jsx90("span", { className: "w-9 shrink-0 select-none border-r px-1.5 text-right text-muted-foreground", children: op.type !== "add" ? op.oldLine : "" }),
5323
+ /* @__PURE__ */ jsx90("span", { className: "w-9 shrink-0 select-none border-r px-1.5 text-right text-muted-foreground", children: op.type !== "remove" ? op.newLine : "" }),
5324
+ /* @__PURE__ */ jsx90(
5325
+ "span",
5326
+ {
5327
+ className: cn(
5328
+ "w-4 shrink-0 select-none text-center",
5329
+ op.type === "add" && "text-success",
5330
+ op.type === "remove" && "text-destructive"
5331
+ ),
5332
+ children: op.type === "add" ? "+" : op.type === "remove" ? "\u2212" : ""
5333
+ }
5334
+ ),
5335
+ /* @__PURE__ */ jsx90("span", { className: "min-w-0 flex-1 whitespace-pre px-1", children: op.text })
5336
+ ]
5337
+ },
5338
+ i
5339
+ )) });
5340
+ }
5341
+ function SplitDiff({ ops, oldLabel, newLabel }) {
5342
+ return /* @__PURE__ */ jsxs55("div", { role: "table", children: [
5343
+ /* @__PURE__ */ jsxs55("div", { className: "flex border-b bg-muted/40 text-muted-foreground", children: [
5344
+ /* @__PURE__ */ jsx90("div", { className: "flex-1 px-2 py-1", children: oldLabel }),
5345
+ /* @__PURE__ */ jsx90("div", { className: "flex-1 border-l px-2 py-1", children: newLabel })
5346
+ ] }),
5347
+ ops.map((op, i) => /* @__PURE__ */ jsxs55("div", { role: "row", className: "flex", children: [
5348
+ /* @__PURE__ */ jsxs55("div", { className: cn("flex flex-1 items-start", op.type === "remove" && "bg-destructive/10"), children: [
5349
+ /* @__PURE__ */ jsx90("span", { className: "w-9 shrink-0 select-none px-1.5 text-right text-muted-foreground", children: op.type !== "add" ? op.oldLine : "" }),
5350
+ /* @__PURE__ */ jsx90("span", { className: "min-w-0 flex-1 whitespace-pre px-1", children: op.type !== "add" ? op.text : "" })
5351
+ ] }),
5352
+ /* @__PURE__ */ jsxs55("div", { className: cn("flex flex-1 items-start border-l", op.type === "add" && "bg-success/10"), children: [
5353
+ /* @__PURE__ */ jsx90("span", { className: "w-9 shrink-0 select-none px-1.5 text-right text-muted-foreground", children: op.type !== "remove" ? op.newLine : "" }),
5354
+ /* @__PURE__ */ jsx90("span", { className: "min-w-0 flex-1 whitespace-pre px-1", children: op.type !== "remove" ? op.text : "" })
5355
+ ] })
5356
+ ] }, i))
5357
+ ] });
5358
+ }
5359
+
5360
+ // src/components/color-picker.tsx
5361
+ import * as React86 from "react";
5362
+ import * as SliderPrimitive3 from "@radix-ui/react-slider";
5363
+ import { Pipette } from "lucide-react";
5364
+ import { jsx as jsx91, jsxs as jsxs56 } from "react/jsx-runtime";
5365
+ var clamp = (v, min, max) => Math.min(max, Math.max(min, v));
5366
+ function hexToRgba(hex) {
5367
+ const h = hex.replace("#", "");
5368
+ if (h.length === 8) {
5369
+ const n2 = parseInt(h, 16);
5370
+ return [n2 >>> 24 & 255, n2 >>> 16 & 255, n2 >>> 8 & 255, (n2 & 255) / 255];
5371
+ }
5372
+ const n = parseInt(h.length === 3 ? h.replace(/(.)/g, "$1$1") : h, 16);
5373
+ return [n >> 16 & 255, n >> 8 & 255, n & 255, 1];
5374
+ }
5375
+ function rgbaToHex(r, g, b, a, includeAlpha) {
5376
+ const toHex = (v) => clamp(Math.round(v), 0, 255).toString(16).padStart(2, "0");
5377
+ const base = `#${toHex(r)}${toHex(g)}${toHex(b)}`;
5378
+ return includeAlpha ? `${base}${toHex(a * 255)}` : base;
5379
+ }
5380
+ function hsvToRgb(h, s, v) {
5381
+ s /= 100;
5382
+ v /= 100;
5383
+ const c = v * s;
5384
+ const hh = h / 60;
5385
+ const x = c * (1 - Math.abs(hh % 2 - 1));
5386
+ let r = 0;
5387
+ let g = 0;
5388
+ let b = 0;
5389
+ if (hh < 1) [r, g, b] = [c, x, 0];
5390
+ else if (hh < 2) [r, g, b] = [x, c, 0];
5391
+ else if (hh < 3) [r, g, b] = [0, c, x];
5392
+ else if (hh < 4) [r, g, b] = [0, x, c];
5393
+ else if (hh < 5) [r, g, b] = [x, 0, c];
5394
+ else [r, g, b] = [c, 0, x];
5395
+ const m = v - c;
5396
+ return [(r + m) * 255, (g + m) * 255, (b + m) * 255];
5397
+ }
5398
+ function rgbToHsv(r, g, b) {
5399
+ r /= 255;
5400
+ g /= 255;
5401
+ b /= 255;
5402
+ const max = Math.max(r, g, b);
5403
+ const min = Math.min(r, g, b);
5404
+ const d = max - min;
5405
+ let h = 0;
5406
+ if (d !== 0) {
5407
+ switch (max) {
5408
+ case r:
5409
+ h = (g - b) / d % 6;
5410
+ break;
5411
+ case g:
5412
+ h = (b - r) / d + 2;
5413
+ break;
5414
+ default:
5415
+ h = (r - g) / d + 4;
5416
+ }
5417
+ h *= 60;
5418
+ if (h < 0) h += 360;
5419
+ }
5420
+ const s = max === 0 ? 0 : d / max;
5421
+ return [h, s * 100, max * 100];
5422
+ }
5423
+ var HEX_PATTERN_ALPHA = /^[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/;
5424
+ var HEX_PATTERN = /^[0-9a-fA-F]{6}$/;
5425
+ var ColorPicker = React86.forwardRef(
5426
+ ({ value, onChange, alpha = false, swatches, className, ...props }, ref) => {
5427
+ const [hsv, setHsv] = React86.useState(() => {
5428
+ const [r, g, b] = hexToRgba(value);
5429
+ return rgbToHsv(r, g, b);
5430
+ });
5431
+ const [a, setA] = React86.useState(() => hexToRgba(value)[3]);
5432
+ const lastEmitted = React86.useRef(value);
5433
+ const [h, s, v] = hsv;
5434
+ React86.useEffect(() => {
5435
+ if (value === lastEmitted.current) return;
5436
+ const [r, g, b, na] = hexToRgba(value);
5437
+ setHsv(rgbToHsv(r, g, b));
5438
+ setA(na);
5439
+ lastEmitted.current = value;
5440
+ }, [value]);
5441
+ const commit = React86.useCallback(
5442
+ (nh, ns, nv, na) => {
5443
+ setHsv([nh, ns, nv]);
5444
+ setA(na);
5445
+ const [r, g, b] = hsvToRgb(nh, ns, nv);
5446
+ const hex = rgbaToHex(r, g, b, na, alpha);
5447
+ lastEmitted.current = hex;
5448
+ onChange(hex);
5449
+ },
5450
+ [alpha, onChange]
5451
+ );
5452
+ const squareRef = React86.useRef(null);
5453
+ const draggingRef = React86.useRef(false);
5454
+ const updateFromPoint = React86.useCallback(
5455
+ (clientX, clientY) => {
5456
+ const el = squareRef.current;
5457
+ if (!el) return;
5458
+ const rect = el.getBoundingClientRect();
5459
+ const ns = clamp((clientX - rect.left) / rect.width * 100, 0, 100);
5460
+ const nv = clamp(100 - (clientY - rect.top) / rect.height * 100, 0, 100);
5461
+ commit(h, ns, nv, a);
5462
+ },
5463
+ [commit, h, a]
5464
+ );
5465
+ const handleSquarePointerDown = (e) => {
5466
+ draggingRef.current = true;
5467
+ e.currentTarget.setPointerCapture(e.pointerId);
5468
+ updateFromPoint(e.clientX, e.clientY);
5469
+ };
5470
+ const handleSquarePointerMove = (e) => {
5471
+ if (!draggingRef.current) return;
5472
+ updateFromPoint(e.clientX, e.clientY);
5473
+ };
5474
+ const handleSquarePointerUp = () => {
5475
+ draggingRef.current = false;
5476
+ };
5477
+ const handleSquareKeyDown = (e) => {
5478
+ const step = e.shiftKey ? 10 : 1;
5479
+ if (e.key === "ArrowLeft") commit(h, clamp(s - step, 0, 100), v, a);
5480
+ else if (e.key === "ArrowRight") commit(h, clamp(s + step, 0, 100), v, a);
5481
+ else if (e.key === "ArrowUp") commit(h, s, clamp(v + step, 0, 100), a);
5482
+ else if (e.key === "ArrowDown") commit(h, s, clamp(v - step, 0, 100), a);
5483
+ else return;
5484
+ e.preventDefault();
5485
+ };
5486
+ const [supportsEyeDropper, setSupportsEyeDropper] = React86.useState(false);
5487
+ React86.useEffect(() => setSupportsEyeDropper(typeof window !== "undefined" && !!window.EyeDropper), []);
5488
+ const pickFromScreen = async () => {
5489
+ if (!window.EyeDropper) return;
5490
+ try {
5491
+ const result = await new window.EyeDropper().open();
5492
+ const [r, g, b] = hexToRgba(result.sRGBHex);
5493
+ commit(...rgbToHsv(r, g, b), a);
5494
+ } catch {
5495
+ }
5496
+ };
5497
+ return /* @__PURE__ */ jsxs56("div", { ref, className: cn("flex w-64 flex-col gap-3 font-sans", className), ...props, children: [
5498
+ /* @__PURE__ */ jsx91(
5499
+ "div",
5500
+ {
5501
+ ref: squareRef,
5502
+ role: "slider",
5503
+ tabIndex: 0,
5504
+ "aria-label": "Saturation and value",
5505
+ "aria-valuetext": `saturation ${Math.round(s)}%, value ${Math.round(v)}%`,
5506
+ onPointerDown: handleSquarePointerDown,
5507
+ onPointerMove: handleSquarePointerMove,
5508
+ onPointerUp: handleSquarePointerUp,
5509
+ onKeyDown: handleSquareKeyDown,
5510
+ className: "relative h-40 w-full touch-none rounded-md outline-none focus-visible:ring-1 focus-visible:ring-ring",
5511
+ style: {
5512
+ backgroundColor: `hsl(${h} 100% 50%)`,
5513
+ backgroundImage: "linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, transparent)"
5514
+ },
5515
+ children: /* @__PURE__ */ jsx91(
5516
+ "div",
5517
+ {
5518
+ className: "absolute size-3.5 -translate-x-1/2 translate-y-1/2 rounded-full border-2 border-white shadow",
5519
+ style: { left: `${s}%`, bottom: `${v}%`, backgroundColor: `hsl(${h} ${s}% ${v}%)` }
5520
+ }
5521
+ )
5522
+ }
5523
+ ),
5524
+ /* @__PURE__ */ jsxs56(
5525
+ SliderPrimitive3.Root,
5526
+ {
5527
+ value: [h],
5528
+ min: 0,
5529
+ max: 360,
5530
+ step: 1,
5531
+ onValueChange: ([nh]) => commit(nh ?? h, s, v, a),
5532
+ "aria-label": "Hue",
5533
+ className: "relative flex h-4 w-full touch-none select-none items-center",
5534
+ children: [
5535
+ /* @__PURE__ */ jsx91(
5536
+ SliderPrimitive3.Track,
5537
+ {
5538
+ className: "relative h-3 w-full grow rounded-full",
5539
+ style: {
5540
+ backgroundImage: "linear-gradient(to right, hsl(0 100% 50%), hsl(60 100% 50%), hsl(120 100% 50%), hsl(180 100% 50%), hsl(240 100% 50%), hsl(300 100% 50%), hsl(360 100% 50%))"
5541
+ }
5542
+ }
5543
+ ),
5544
+ /* @__PURE__ */ jsx91(SliderPrimitive3.Thumb, { className: "block size-4 rounded-full border-2 border-white bg-background shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" })
5545
+ ]
5546
+ }
5547
+ ),
5548
+ alpha && /* @__PURE__ */ jsxs56(
5549
+ SliderPrimitive3.Root,
5550
+ {
5551
+ value: [Math.round(a * 100)],
5552
+ min: 0,
5553
+ max: 100,
5554
+ step: 1,
5555
+ onValueChange: ([na]) => commit(h, s, v, (na ?? a * 100) / 100),
5556
+ "aria-label": "Alpha",
5557
+ className: "relative flex h-4 w-full touch-none select-none items-center",
5558
+ children: [
5559
+ /* @__PURE__ */ jsx91(
5560
+ SliderPrimitive3.Track,
5561
+ {
5562
+ className: "relative h-3 w-full grow overflow-hidden rounded-full",
5563
+ style: {
5564
+ backgroundImage: "conic-gradient(#0000001a 90deg, transparent 90deg 180deg, #0000001a 180deg 270deg, transparent 270deg)",
5565
+ backgroundSize: "8px 8px"
5566
+ },
5567
+ children: /* @__PURE__ */ jsx91(
5568
+ "div",
5569
+ {
5570
+ className: "absolute inset-0",
5571
+ style: { backgroundImage: `linear-gradient(to right, transparent, hsl(${h} ${s}% ${v}%))` }
5572
+ }
5573
+ )
5574
+ }
5575
+ ),
5576
+ /* @__PURE__ */ jsx91(SliderPrimitive3.Thumb, { className: "block size-4 rounded-full border-2 border-white bg-background shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" })
5577
+ ]
5578
+ }
5579
+ ),
5580
+ /* @__PURE__ */ jsxs56("div", { className: "flex items-center gap-2", children: [
5581
+ /* @__PURE__ */ jsx91(
5582
+ "div",
5583
+ {
5584
+ className: "size-8 shrink-0 rounded-md border",
5585
+ style: {
5586
+ backgroundImage: "conic-gradient(#0000001a 90deg, transparent 90deg 180deg, #0000001a 180deg 270deg, transparent 270deg), linear-gradient(hsl(0 0% 0% / 0), hsl(0 0% 0% / 0))",
5587
+ backgroundSize: "8px 8px"
5588
+ },
5589
+ children: /* @__PURE__ */ jsx91("div", { className: "size-full rounded-md", style: { backgroundColor: value } })
5590
+ }
5591
+ ),
5592
+ /* @__PURE__ */ jsxs56("div", { className: "flex flex-1 items-center gap-1", children: [
5593
+ /* @__PURE__ */ jsx91("span", { className: "text-muted-foreground", children: "#" }),
5594
+ /* @__PURE__ */ jsx91(HexField, { value, alpha, onCommit: onChange })
5595
+ ] }),
5596
+ supportsEyeDropper && /* @__PURE__ */ jsx91(
5597
+ Button,
5598
+ {
5599
+ type: "button",
5600
+ variant: "Ghost",
5601
+ size: "icon",
5602
+ "aria-label": "Pick color from screen",
5603
+ onClick: pickFromScreen,
5604
+ children: /* @__PURE__ */ jsx91(Pipette, { className: "size-4" })
5605
+ }
5606
+ )
5607
+ ] }),
5608
+ swatches && swatches.length > 0 && /* @__PURE__ */ jsx91("div", { className: "flex flex-wrap gap-1.5", children: swatches.map((sw) => /* @__PURE__ */ jsx91(
5609
+ "button",
5610
+ {
5611
+ type: "button",
5612
+ "aria-label": sw,
5613
+ onClick: () => {
5614
+ const [r, g, b] = hexToRgba(sw);
5615
+ commit(...rgbToHsv(r, g, b), a);
5616
+ },
5617
+ className: "size-6 rounded-sm border transition-shadow hover:ring-1 hover:ring-ring hover:ring-offset-1",
5618
+ style: { backgroundColor: sw }
5619
+ },
5620
+ sw
5621
+ )) })
5622
+ ] });
5623
+ }
5624
+ );
5625
+ ColorPicker.displayName = "ColorPicker";
5626
+ function HexField({
5627
+ value,
5628
+ alpha,
5629
+ onCommit
5630
+ }) {
5631
+ const [draft, setDraft] = React86.useState(value.replace("#", "").toUpperCase());
5632
+ React86.useEffect(() => setDraft(value.replace("#", "").toUpperCase()), [value]);
5633
+ const commit = () => {
5634
+ const pattern = alpha ? HEX_PATTERN_ALPHA : HEX_PATTERN;
5635
+ if (pattern.test(draft)) {
5636
+ onCommit(`#${draft.toLowerCase()}`);
5637
+ } else {
5638
+ setDraft(value.replace("#", "").toUpperCase());
5639
+ }
5640
+ };
5641
+ return /* @__PURE__ */ jsx91(
5642
+ Input,
5643
+ {
5644
+ value: draft,
5645
+ onChange: (e) => setDraft(e.target.value.replace(/[^0-9a-fA-F]/g, "").slice(0, alpha ? 8 : 6)),
5646
+ onBlur: commit,
5647
+ onKeyDown: (e) => {
5648
+ if (e.key === "Enter") {
5649
+ commit();
5650
+ e.currentTarget.blur();
5651
+ }
5652
+ },
5653
+ className: "h-8 flex-1 px-2 py-1 font-mono text-xs uppercase"
5654
+ }
5655
+ );
5656
+ }
5657
+
5658
+ // src/components/kanban-board.tsx
5659
+ import * as React87 from "react";
5660
+ import {
5661
+ DndContext,
5662
+ DragOverlay,
5663
+ KeyboardSensor,
5664
+ PointerSensor,
5665
+ closestCenter,
5666
+ getFirstCollision,
5667
+ pointerWithin,
5668
+ rectIntersection,
5669
+ useDroppable,
5670
+ useSensor,
5671
+ useSensors
5672
+ } from "@dnd-kit/core";
5673
+ import {
5674
+ SortableContext,
5675
+ arrayMove,
5676
+ sortableKeyboardCoordinates,
5677
+ useSortable,
5678
+ verticalListSortingStrategy
5679
+ } from "@dnd-kit/sortable";
5680
+ import { CSS } from "@dnd-kit/utilities";
5681
+ import { jsx as jsx92, jsxs as jsxs57 } from "react/jsx-runtime";
5682
+ var KanbanBoard = React87.forwardRef(
5683
+ ({ columns, onColumnsChange, className, ...props }, ref) => {
5684
+ const sensors = useSensors(
5685
+ useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),
5686
+ useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
5687
+ );
5688
+ const [activeCard, setActiveCard] = React87.useState(null);
5689
+ const lastOverId = React87.useRef(null);
5690
+ const findColumn = React87.useCallback(
5691
+ (id) => columns.find((col) => col.id === id || col.cards.some((c) => c.id === id)),
5692
+ [columns]
5693
+ );
5694
+ const collisionDetectionStrategy = React87.useCallback(
5695
+ (args) => {
5696
+ const pointerIntersections = pointerWithin(args);
5697
+ const intersections = pointerIntersections.length > 0 ? pointerIntersections : rectIntersection(args);
5698
+ let overId = getFirstCollision(intersections, "id");
5699
+ if (overId != null) {
5700
+ const overColumn = columns.find((col) => col.id === overId);
5701
+ if (overColumn && overColumn.cards.length > 0) {
5702
+ const cardIds = new Set(overColumn.cards.map((c) => c.id));
5703
+ const cardCollision = getFirstCollision(
5704
+ closestCenter({
5705
+ ...args,
5706
+ droppableContainers: args.droppableContainers.filter((c) => cardIds.has(String(c.id)))
5707
+ }),
5708
+ "id"
5709
+ );
5710
+ if (cardCollision != null) overId = cardCollision;
5711
+ }
5712
+ lastOverId.current = String(overId);
5713
+ return [{ id: overId }];
5714
+ }
5715
+ return lastOverId.current ? [{ id: lastOverId.current }] : [];
5716
+ },
5717
+ [columns]
5718
+ );
5719
+ function handleDragStart(event) {
5720
+ lastOverId.current = String(event.active.id);
5721
+ const column = findColumn(String(event.active.id));
5722
+ const card = column?.cards.find((c) => c.id === event.active.id);
5723
+ setActiveCard(card ?? null);
5724
+ }
5725
+ function handleDragOver(event) {
5726
+ const { active, over } = event;
5727
+ if (!over) return;
5728
+ const activeId = String(active.id);
5729
+ const overId = String(over.id);
5730
+ if (activeId === overId) return;
5731
+ const activeColumn = findColumn(activeId);
5732
+ const overColumn = findColumn(overId);
5733
+ if (!activeColumn || !overColumn || activeColumn.id === overColumn.id) return;
5734
+ onColumnsChange(
5735
+ columns.map((col) => {
5736
+ if (col.id === activeColumn.id) {
5737
+ return { ...col, cards: col.cards.filter((c) => c.id !== activeId) };
5738
+ }
5739
+ if (col.id === overColumn.id) {
5740
+ const movedCard = activeColumn.cards.find((c) => c.id === activeId);
5741
+ if (!movedCard) return col;
5742
+ const overIndex = col.cards.findIndex((c) => c.id === overId);
5743
+ const insertAt = overIndex >= 0 ? overIndex : col.cards.length;
5744
+ const nextCards = [...col.cards];
5745
+ nextCards.splice(insertAt, 0, movedCard);
5746
+ return { ...col, cards: nextCards };
5747
+ }
5748
+ return col;
5749
+ })
5750
+ );
5751
+ }
5752
+ function handleDragEnd(event) {
5753
+ setActiveCard(null);
5754
+ lastOverId.current = null;
5755
+ const { active, over } = event;
5756
+ if (!over) return;
5757
+ const activeId = String(active.id);
5758
+ const overId = String(over.id);
5759
+ const activeColumn = findColumn(activeId);
5760
+ const overColumn = columns.find((col) => col.id === overId) ?? findColumn(overId);
5761
+ if (!activeColumn || !overColumn || activeColumn.id !== overColumn.id) return;
5762
+ const activeIndex = activeColumn.cards.findIndex((c) => c.id === activeId);
5763
+ const rawOverIndex = overColumn.cards.findIndex((c) => c.id === overId);
5764
+ const overIndex = rawOverIndex >= 0 ? rawOverIndex : overColumn.cards.length - 1;
5765
+ if (activeIndex === -1 || overIndex === -1 || activeIndex === overIndex) return;
5766
+ onColumnsChange(
5767
+ columns.map(
5768
+ (col) => col.id === activeColumn.id ? { ...col, cards: arrayMove(col.cards, activeIndex, overIndex) } : col
5769
+ )
5770
+ );
5771
+ }
5772
+ return /* @__PURE__ */ jsxs57(
5773
+ DndContext,
5774
+ {
5775
+ sensors,
5776
+ collisionDetection: collisionDetectionStrategy,
5777
+ onDragStart: handleDragStart,
5778
+ onDragOver: handleDragOver,
5779
+ onDragEnd: handleDragEnd,
5780
+ onDragCancel: () => {
5781
+ setActiveCard(null);
5782
+ lastOverId.current = null;
5783
+ },
5784
+ children: [
5785
+ /* @__PURE__ */ jsx92("div", { ref, role: "group", className: cn("flex items-start gap-4 overflow-x-auto font-sans", className), ...props, children: columns.map((column) => /* @__PURE__ */ jsx92(KanbanColumnView, { column }, column.id)) }),
5786
+ /* @__PURE__ */ jsx92(DragOverlay, { children: activeCard ? /* @__PURE__ */ jsx92(KanbanCardView, { card: activeCard }) : null })
5787
+ ]
5788
+ }
5789
+ );
5790
+ }
5791
+ );
5792
+ KanbanBoard.displayName = "KanbanBoard";
5793
+ function KanbanColumnView({ column }) {
5794
+ const { setNodeRef } = useDroppable({ id: column.id });
5795
+ return /* @__PURE__ */ jsxs57("div", { className: "flex w-64 shrink-0 flex-col gap-2 rounded-md border bg-muted/30 p-2", children: [
5796
+ /* @__PURE__ */ jsxs57("div", { className: "flex items-center justify-between px-1 py-1", children: [
5797
+ /* @__PURE__ */ jsx92("span", { className: "text-sm font-medium text-foreground", children: column.title }),
5798
+ /* @__PURE__ */ jsx92("span", { className: "text-xs text-muted-foreground", children: column.cards.length })
5799
+ ] }),
5800
+ /* @__PURE__ */ jsx92(SortableContext, { items: column.cards.map((c) => c.id), strategy: verticalListSortingStrategy, children: /* @__PURE__ */ jsx92("div", { ref: setNodeRef, className: "flex min-h-10 flex-col gap-2", children: column.cards.map((card) => /* @__PURE__ */ jsx92(SortableKanbanCard, { card }, card.id)) }) })
5801
+ ] });
5802
+ }
5803
+ function SortableKanbanCard({ card }) {
5804
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: card.id });
5805
+ const style = {
5806
+ transform: CSS.Transform.toString(transform),
5807
+ transition,
5808
+ opacity: isDragging ? 0.4 : 1
5809
+ };
5810
+ return /* @__PURE__ */ jsx92("div", { ref: setNodeRef, style, ...attributes, ...listeners, children: /* @__PURE__ */ jsx92(KanbanCardView, { card }) });
5811
+ }
5812
+ function KanbanCardView({ card }) {
5813
+ return /* @__PURE__ */ jsx92("div", { className: "cursor-grab touch-none rounded-md border bg-background p-3 text-sm text-foreground shadow-sm outline-none active:cursor-grabbing focus-visible:ring-1 focus-visible:ring-ring", children: card.content });
5814
+ }
5815
+
5816
+ // src/components/markdown-editor.tsx
5817
+ import * as React88 from "react";
5818
+ import { Bold, Code, Eye as Eye2, EyeOff as EyeOff2, Heading2, Italic, Link as LinkIcon, List as List4, ListOrdered, Quote as Quote2 } from "lucide-react";
5819
+ import { jsx as jsx93, jsxs as jsxs58 } from "react/jsx-runtime";
5820
+ function wrapSelection(text, start, end, before, after, placeholder) {
5821
+ const selected = text.slice(start, end) || placeholder;
5822
+ const next = text.slice(0, start) + before + selected + after + text.slice(end);
5823
+ return { text: next, selectionStart: start + before.length, selectionEnd: start + before.length + selected.length };
5824
+ }
5825
+ function prefixLines(text, start, end, prefix) {
5826
+ const lineStart = text.lastIndexOf("\n", Math.max(start - 1, 0)) + 1;
5827
+ let lineEnd = text.indexOf("\n", Math.max(end, lineStart));
5828
+ if (lineEnd === -1) lineEnd = text.length;
5829
+ const block = text.slice(lineStart, lineEnd);
5830
+ const lines = block.split("\n");
5831
+ const prefixed = lines.map((line, i) => prefix(i) + line).join("\n");
5832
+ return {
5833
+ text: text.slice(0, lineStart) + prefixed + text.slice(lineEnd),
5834
+ selectionStart: start + prefix(0).length,
5835
+ selectionEnd: end + (prefixed.length - block.length)
5836
+ };
5837
+ }
5838
+ function escapeHtml(s) {
5839
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
5840
+ }
5841
+ function renderInline(escaped) {
5842
+ return escaped.replace(/`([^`]+)`/g, "<code>$1</code>").replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\*([^*]+)\*/g, "<em>$1</em>").replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, label, url) => {
5843
+ const safe = /^(https?:|mailto:|\/|#)/i.test(url.trim());
5844
+ return safe ? `<a href="${url}" target="_blank" rel="noopener noreferrer">${label}</a>` : match;
5845
+ });
5846
+ }
5847
+ function matchMarker(line, markerRe) {
5848
+ const m = markerRe.exec(line);
5849
+ if (!m) return null;
5850
+ let i = m[0].length;
5851
+ if (line[i] !== " " && line[i] !== " ") return null;
5852
+ while (line[i] === " " || line[i] === " ") i++;
5853
+ return { marker: m[0], content: line.slice(i) };
5854
+ }
5855
+ function renderMarkdown(source) {
5856
+ const lines = source.split("\n");
5857
+ const out = [];
5858
+ let inCode = false;
5859
+ let codeLines = [];
5860
+ let listType = null;
5861
+ const closeList = () => {
5862
+ if (listType) {
5863
+ out.push(`</${listType}>`);
5864
+ listType = null;
5865
+ }
5866
+ };
5867
+ for (const line of lines) {
5868
+ if (line.trim().startsWith("```")) {
5869
+ if (inCode) {
5870
+ out.push(`<pre><code>${escapeHtml(codeLines.join("\n"))}</code></pre>`);
5871
+ codeLines = [];
5872
+ inCode = false;
5873
+ } else {
5874
+ closeList();
5875
+ inCode = true;
5876
+ }
5877
+ continue;
5878
+ }
5879
+ if (inCode) {
5880
+ codeLines.push(line);
5881
+ continue;
5882
+ }
5883
+ const heading = matchMarker(line, /^#{1,6}/);
5884
+ if (heading) {
5885
+ closeList();
5886
+ const level = heading.marker.length;
5887
+ out.push(`<h${level}>${renderInline(escapeHtml(heading.content))}</h${level}>`);
5888
+ continue;
5889
+ }
5890
+ const quote = line.match(/^>\s?(.*)$/);
5891
+ if (quote) {
5892
+ closeList();
5893
+ out.push(`<blockquote>${renderInline(escapeHtml(quote[1]))}</blockquote>`);
5894
+ continue;
5895
+ }
5896
+ const bullet = matchMarker(line, /^[-*]/);
5897
+ if (bullet) {
5898
+ if (listType !== "ul") {
5899
+ closeList();
5900
+ out.push("<ul>");
5901
+ listType = "ul";
5902
+ }
5903
+ out.push(`<li>${renderInline(escapeHtml(bullet.content))}</li>`);
5904
+ continue;
5905
+ }
5906
+ const numbered = matchMarker(line, /^\d+\./);
5907
+ if (numbered) {
5908
+ if (listType !== "ol") {
5909
+ closeList();
5910
+ out.push("<ol>");
5911
+ listType = "ol";
5912
+ }
5913
+ out.push(`<li>${renderInline(escapeHtml(numbered.content))}</li>`);
5914
+ continue;
5915
+ }
5916
+ closeList();
5917
+ if (line.trim() === "") continue;
5918
+ out.push(`<p>${renderInline(escapeHtml(line))}</p>`);
5919
+ }
5920
+ closeList();
5921
+ if (inCode) out.push(`<pre><code>${escapeHtml(codeLines.join("\n"))}</code></pre>`);
5922
+ return out.join("\n");
5923
+ }
5924
+ var MarkdownEditor = React88.forwardRef(
5925
+ ({ value, onChange, placeholder, rows = 10, className, ...props }, ref) => {
5926
+ const textareaRef = React88.useRef(null);
5927
+ const [showPreview, setShowPreview] = React88.useState(false);
5928
+ const pendingSelection = React88.useRef(null);
5929
+ React88.useEffect(() => {
5930
+ if (pendingSelection.current && textareaRef.current) {
5931
+ const { start, end } = pendingSelection.current;
5932
+ textareaRef.current.focus();
5933
+ textareaRef.current.setSelectionRange(start, end);
5934
+ pendingSelection.current = null;
5935
+ }
5936
+ }, [value]);
5937
+ const applyInline = (before, after, placeholder2) => {
5938
+ const el = textareaRef.current;
5939
+ if (!el) return;
5940
+ const result = wrapSelection(value, el.selectionStart, el.selectionEnd, before, after, placeholder2);
5941
+ pendingSelection.current = { start: result.selectionStart, end: result.selectionEnd };
5942
+ onChange(result.text);
5943
+ };
5944
+ const applyLinePrefix = (prefix) => {
5945
+ const el = textareaRef.current;
5946
+ if (!el) return;
5947
+ const result = prefixLines(value, el.selectionStart, el.selectionEnd, prefix);
5948
+ pendingSelection.current = { start: result.selectionStart, end: result.selectionEnd };
5949
+ onChange(result.text);
5950
+ };
5951
+ return /* @__PURE__ */ jsxs58("div", { ref, className: cn("overflow-hidden rounded-md border font-sans", className), ...props, children: [
5952
+ /* @__PURE__ */ jsxs58("div", { role: "toolbar", "aria-label": "Formatting", className: "flex items-center gap-0.5 border-b bg-muted/40 p-1", children: [
5953
+ /* @__PURE__ */ jsx93(ToolbarButton, { label: "Bold", onClick: () => applyInline("**", "**", "bold text"), children: /* @__PURE__ */ jsx93(Bold, { className: "size-4" }) }),
5954
+ /* @__PURE__ */ jsx93(ToolbarButton, { label: "Italic", onClick: () => applyInline("*", "*", "italic text"), children: /* @__PURE__ */ jsx93(Italic, { className: "size-4" }) }),
5955
+ /* @__PURE__ */ jsx93(ToolbarButton, { label: "Heading", onClick: () => applyLinePrefix(() => "## "), children: /* @__PURE__ */ jsx93(Heading2, { className: "size-4" }) }),
5956
+ /* @__PURE__ */ jsx93(ToolbarButton, { label: "Link", onClick: () => applyInline("[", "](https://)", "link text"), children: /* @__PURE__ */ jsx93(LinkIcon, { className: "size-4" }) }),
5957
+ /* @__PURE__ */ jsx93(ToolbarButton, { label: "Bulleted list", onClick: () => applyLinePrefix(() => "- "), children: /* @__PURE__ */ jsx93(List4, { className: "size-4" }) }),
5958
+ /* @__PURE__ */ jsx93(ToolbarButton, { label: "Numbered list", onClick: () => applyLinePrefix((i) => `${i + 1}. `), children: /* @__PURE__ */ jsx93(ListOrdered, { className: "size-4" }) }),
5959
+ /* @__PURE__ */ jsx93(ToolbarButton, { label: "Code", onClick: () => applyInline("`", "`", "code"), children: /* @__PURE__ */ jsx93(Code, { className: "size-4" }) }),
5960
+ /* @__PURE__ */ jsx93(ToolbarButton, { label: "Quote", onClick: () => applyLinePrefix(() => "> "), children: /* @__PURE__ */ jsx93(Quote2, { className: "size-4" }) }),
5961
+ /* @__PURE__ */ jsx93(
5962
+ ToolbarButton,
5963
+ {
5964
+ label: showPreview ? "Hide preview" : "Show preview",
5965
+ onClick: () => setShowPreview((v) => !v),
5966
+ active: showPreview,
5967
+ className: "ml-auto",
5968
+ children: showPreview ? /* @__PURE__ */ jsx93(EyeOff2, { className: "size-4" }) : /* @__PURE__ */ jsx93(Eye2, { className: "size-4" })
5969
+ }
5970
+ )
5971
+ ] }),
5972
+ /* @__PURE__ */ jsxs58("div", { className: cn("grid", showPreview && "grid-cols-2 divide-x"), children: [
5973
+ /* @__PURE__ */ jsx93(
5974
+ Textarea,
5975
+ {
5976
+ ref: textareaRef,
5977
+ value,
5978
+ onChange: (e) => onChange(e.target.value),
5979
+ placeholder,
5980
+ rows,
5981
+ className: "resize-none rounded-none border-0 font-mono text-[13px] focus-visible:border-transparent focus-visible:shadow-none"
5982
+ }
5983
+ ),
5984
+ showPreview && /* @__PURE__ */ jsx93(
5985
+ "div",
5986
+ {
5987
+ className: "overflow-auto p-3 text-sm [&_a]:text-primary [&_a]:underline [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground [&_code]:rounded-sm [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-[0.9em] [&_h1]:text-xl [&_h1]:font-semibold [&_h2]:text-lg [&_h2]:font-semibold [&_h3]:font-semibold [&_li]:ml-5 [&_ol]:list-decimal [&_p]:mb-2 [&_pre]:overflow-auto [&_pre]:rounded-sm [&_pre]:bg-muted [&_pre]:p-2 [&_ul]:list-disc",
5988
+ dangerouslySetInnerHTML: { __html: renderMarkdown(value) }
5989
+ }
5990
+ )
5991
+ ] })
5992
+ ] });
5993
+ }
5994
+ );
5995
+ MarkdownEditor.displayName = "MarkdownEditor";
5996
+ function ToolbarButton({
5997
+ label,
5998
+ onClick,
5999
+ active,
6000
+ className,
6001
+ children
6002
+ }) {
6003
+ return /* @__PURE__ */ jsx93(
6004
+ "button",
6005
+ {
6006
+ type: "button",
6007
+ onClick,
6008
+ "aria-label": label,
6009
+ "aria-pressed": active,
6010
+ className: cn(
6011
+ "inline-flex size-7 items-center justify-center rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground",
6012
+ active && "bg-accent text-foreground",
6013
+ className
6014
+ ),
6015
+ children
6016
+ }
6017
+ );
6018
+ }
4333
6019
  export {
4334
6020
  Accordion,
4335
6021
  AccordionContent,
@@ -4397,6 +6083,7 @@ export {
4397
6083
  Collapsible,
4398
6084
  CollapsibleContent2 as CollapsibleContent,
4399
6085
  CollapsibleTrigger2 as CollapsibleTrigger,
6086
+ ColorPicker,
4400
6087
  Command,
4401
6088
  CommandDialog,
4402
6089
  CommandEmpty,
@@ -4422,6 +6109,7 @@ export {
4422
6109
  ContextMenuSubContent,
4423
6110
  ContextMenuSubTrigger,
4424
6111
  ContextMenuTrigger,
6112
+ DataGrid,
4425
6113
  DataTable,
4426
6114
  DatePicker,
4427
6115
  DescriptionList,
@@ -4436,6 +6124,7 @@ export {
4436
6124
  DialogPortal,
4437
6125
  DialogTitle,
4438
6126
  DialogTrigger,
6127
+ DiffViewer,
4439
6128
  Drawer,
4440
6129
  DrawerClose,
4441
6130
  DrawerContent,
@@ -4501,11 +6190,14 @@ export {
4501
6190
  InputOTPGroup,
4502
6191
  InputOTPSeparator,
4503
6192
  InputOTPSlot,
6193
+ JsonViewer,
6194
+ KanbanBoard,
4504
6195
  Kbd,
4505
6196
  KbdGroup,
4506
6197
  Label2 as Label,
4507
6198
  List3 as List,
4508
6199
  ListItem,
6200
+ MarkdownEditor,
4509
6201
  Marquee,
4510
6202
  Menubar,
4511
6203
  MenubarCheckboxItem,
@@ -4523,8 +6215,10 @@ export {
4523
6215
  MenubarSubContent,
4524
6216
  MenubarSubTrigger,
4525
6217
  MenubarTrigger,
6218
+ MessageBubble,
4526
6219
  Metric,
4527
6220
  Modal,
6221
+ MultiSelect,
4528
6222
  NativeSelect,
4529
6223
  NativeSelectOptGroup,
4530
6224
  NativeSelectOption,
@@ -4537,6 +6231,10 @@ export {
4537
6231
  NavigationMenuList,
4538
6232
  NavigationMenuTrigger,
4539
6233
  NavigationMenuViewport,
6234
+ NotificationCenter,
6235
+ NotificationCenterContent,
6236
+ NotificationCenterTrigger,
6237
+ NotificationItem,
4540
6238
  NumberInput,
4541
6239
  PageHeader,
4542
6240
  Pagination,
@@ -4629,6 +6327,11 @@ export {
4629
6327
  TooltipContent,
4630
6328
  TooltipProvider,
4631
6329
  TooltipTrigger,
6330
+ Tour,
6331
+ TreeItem,
6332
+ TreeView,
6333
+ TypingIndicator,
6334
+ VirtualList,
4632
6335
  badgeVariants,
4633
6336
  bannerVariants,
4634
6337
  buttonVariants,