@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.d.ts +344 -1
- package/dist/index.js +1705 -2
- package/package.json +5 -2
- package/src/components/color-picker.tsx +352 -0
- package/src/components/data-grid.tsx +304 -0
- package/src/components/diff-viewer.tsx +155 -0
- package/src/components/json-viewer.tsx +168 -0
- package/src/components/kanban-board.tsx +246 -0
- package/src/components/markdown-editor.tsx +288 -0
- package/src/components/message-bubble.tsx +83 -0
- package/src/components/multi-select.tsx +134 -0
- package/src/components/notification-center.tsx +119 -0
- package/src/components/tour.tsx +156 -0
- package/src/components/tree-view.tsx +273 -0
- package/src/components/virtual-list.tsx +92 -0
- package/src/index.ts +20 -0
- package/src/stories/ColorPicker.stories.tsx +28 -0
- package/src/stories/DataGrid.stories.tsx +54 -0
- package/src/stories/DiffViewer.stories.tsx +26 -0
- package/src/stories/JsonViewer.stories.tsx +31 -0
- package/src/stories/KanbanBoard.stories.tsx +44 -0
- package/src/stories/MarkdownEditor.stories.tsx +25 -0
- package/src/stories/MessageBubble.stories.tsx +32 -0
- package/src/stories/MultiSelect.stories.tsx +35 -0
- package/src/stories/NotificationCenter.stories.tsx +41 -0
- package/src/stories/Tour.stories.tsx +70 -0
- package/src/stories/TreeView.stories.tsx +38 -0
- package/src/stories/VirtualList.stories.tsx +36 -0
- package/tailwind.config.ts +2 -0
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
|
|
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 =
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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,
|