@flytedan/flytebot-design-system 0.12.15 → 0.14.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
@@ -8464,6 +8464,30 @@ function BudgetReallocator({
8464
8464
  setDraft(spend);
8465
8465
  if (onCancel) onCancel();
8466
8466
  };
8467
+ const trackRef = React39.useRef(null);
8468
+ const chipRef = React39.useRef(null);
8469
+ const [chipLeft, setChipLeft] = React39.useState(null);
8470
+ React39.useLayoutEffect(() => {
8471
+ const track = trackRef.current;
8472
+ if (!dirty || !track) {
8473
+ setChipLeft(null);
8474
+ return;
8475
+ }
8476
+ const measure = () => {
8477
+ const chip = chipRef.current;
8478
+ if (!chip) return;
8479
+ const trackWidth = track.clientWidth;
8480
+ const chipWidth = chip.offsetWidth;
8481
+ const half = chipWidth / 2;
8482
+ const desired = pct / 100 * trackWidth;
8483
+ setChipLeft(Math.min(Math.max(desired, half), Math.max(trackWidth - half, half)));
8484
+ };
8485
+ measure();
8486
+ if (typeof ResizeObserver !== "function") return;
8487
+ const ro = new ResizeObserver(measure);
8488
+ ro.observe(track);
8489
+ return () => ro.disconnect();
8490
+ }, [dirty, pct]);
8467
8491
  return /* @__PURE__ */ jsxs64(
8468
8492
  "div",
8469
8493
  {
@@ -8478,9 +8502,20 @@ function BudgetReallocator({
8478
8502
  /* @__PURE__ */ jsx68(CsiBadge, { band: nextBand, crp: nextCrp, size: "medium", preview: true })
8479
8503
  ] }) : null
8480
8504
  ] }),
8481
- /* @__PURE__ */ jsxs64("div", { className: "fd-slider", children: [
8505
+ /* @__PURE__ */ jsxs64("div", { className: "fd-slider", ref: trackRef, children: [
8482
8506
  /* @__PURE__ */ jsx68("span", { className: "fd-slider-fill", style: { width: pct + "%" } }),
8483
- dirty ? /* @__PURE__ */ jsx68("span", { className: "fd-slider-chip", style: { left: pct + "%", background: delta < 0 ? "var(--danger-solid)" : "var(--ok-solid)" }, children: (delta < 0 ? "\u2212$" : "+$") + Math.abs(delta).toLocaleString() }) : null,
8507
+ dirty ? /* @__PURE__ */ jsx68(
8508
+ "span",
8509
+ {
8510
+ ref: chipRef,
8511
+ className: "fd-slider-chip",
8512
+ style: {
8513
+ left: chipLeft === null ? pct + "%" : chipLeft,
8514
+ background: delta < 0 ? "var(--danger-solid)" : "var(--ok-solid)"
8515
+ },
8516
+ children: (delta < 0 ? "\u2212$" : "+$") + Math.abs(delta).toLocaleString()
8517
+ }
8518
+ ) : null,
8484
8519
  /* @__PURE__ */ jsx68(
8485
8520
  "input",
8486
8521
  {
@@ -10418,8 +10453,520 @@ function FileViewer({ file, resolveUrl, sidePanel, sidePanelLabel = "Details", o
10418
10453
  );
10419
10454
  }
10420
10455
 
10421
- // src/kits/query.ts
10456
+ // src/components/files/FileBrowser.tsx
10422
10457
  import * as React48 from "react";
10458
+ import { Fragment as Fragment23, jsx as jsx78, jsxs as jsxs74 } from "react/jsx-runtime";
10459
+ function childrenOf(folders, parentId) {
10460
+ return folders.filter((f) => f.parentId === parentId).sort((a, b) => a.name.localeCompare(b.name));
10461
+ }
10462
+ function ancestorsOf(folders, id) {
10463
+ const byId = new Map(folders.map((f) => [f.id, f]));
10464
+ const chain = [];
10465
+ let cur = id != null ? byId.get(id) : void 0;
10466
+ const seen = /* @__PURE__ */ new Set();
10467
+ while (cur && !seen.has(cur.id)) {
10468
+ seen.add(cur.id);
10469
+ chain.unshift(cur);
10470
+ cur = cur.parentId != null ? byId.get(cur.parentId) : void 0;
10471
+ }
10472
+ return chain;
10473
+ }
10474
+ function isSelfOrDescendant(folders, ancestorId, nodeId) {
10475
+ if (nodeId == null) return false;
10476
+ if (nodeId === ancestorId) return true;
10477
+ const byParent = /* @__PURE__ */ new Map();
10478
+ folders.forEach((f) => {
10479
+ const key = f.parentId ?? "";
10480
+ if (!byParent.has(key)) byParent.set(key, []);
10481
+ byParent.get(key).push(f);
10482
+ });
10483
+ const stack = [...byParent.get(ancestorId) || []];
10484
+ while (stack.length) {
10485
+ const next = stack.pop();
10486
+ if (next.id === nodeId) return true;
10487
+ stack.push(...byParent.get(next.id) || []);
10488
+ }
10489
+ return false;
10490
+ }
10491
+ function flattenDestinations(folders, excludeSubtreeOf) {
10492
+ const out = [{ id: null, name: "Home", depth: 0 }];
10493
+ const walk = (parentId, depth) => {
10494
+ childrenOf(folders, parentId).forEach((f) => {
10495
+ if (excludeSubtreeOf && isSelfOrDescendant(folders, excludeSubtreeOf, f.id)) return;
10496
+ out.push({ id: f.id, name: f.name, depth });
10497
+ walk(f.id, depth + 1);
10498
+ });
10499
+ };
10500
+ walk(null, 1);
10501
+ return out;
10502
+ }
10503
+ var DND_MIME = "application/x-fd-filebrowser";
10504
+ function writeDragPayload(e, payload) {
10505
+ const s = JSON.stringify(payload);
10506
+ e.dataTransfer.setData(DND_MIME, s);
10507
+ e.dataTransfer.setData("text/plain", s);
10508
+ e.dataTransfer.effectAllowed = "move";
10509
+ }
10510
+ function readDragPayload(e) {
10511
+ const raw = e.dataTransfer.getData(DND_MIME) || e.dataTransfer.getData("text/plain");
10512
+ if (!raw) return null;
10513
+ try {
10514
+ const parsed = JSON.parse(raw);
10515
+ if (parsed && (parsed.kind === "file" || parsed.kind === "folder") && typeof parsed.id === "string") return parsed;
10516
+ } catch {
10517
+ }
10518
+ return null;
10519
+ }
10520
+ function hasInternalPayload(e) {
10521
+ return Array.from(e.dataTransfer.types || []).indexOf(DND_MIME) !== -1;
10522
+ }
10523
+ function FolderPickerMenu({
10524
+ label,
10525
+ destinations,
10526
+ onPick
10527
+ }) {
10528
+ const [open, setOpen] = React48.useState(false);
10529
+ const zIndex = useOverlayLayer(open);
10530
+ const ref = React48.useRef(null);
10531
+ React48.useEffect(() => {
10532
+ if (!open) return;
10533
+ const away = (e) => {
10534
+ if (ref.current && !ref.current.contains(e.target)) setOpen(false);
10535
+ };
10536
+ const esc = (e) => {
10537
+ if (e.key === "Escape") setOpen(false);
10538
+ };
10539
+ document.addEventListener("pointerdown", away);
10540
+ document.addEventListener("keydown", esc);
10541
+ return () => {
10542
+ document.removeEventListener("pointerdown", away);
10543
+ document.removeEventListener("keydown", esc);
10544
+ };
10545
+ }, [open]);
10546
+ return /* @__PURE__ */ jsxs74("span", { className: "fd-fb-menu-anchor", ref, children: [
10547
+ /* @__PURE__ */ jsx78(
10548
+ IconButton,
10549
+ {
10550
+ icon: "arrows-out-cardinal",
10551
+ label,
10552
+ size: "sm",
10553
+ variant: "ghost",
10554
+ "aria-haspopup": "menu",
10555
+ "aria-expanded": open,
10556
+ onClick: () => setOpen((v) => !v)
10557
+ }
10558
+ ),
10559
+ open ? /* @__PURE__ */ jsx78(LayerProvider, { zIndex, children: /* @__PURE__ */ jsxs74("div", { className: "fd-fb-menu", role: "menu", "aria-label": label, style: { zIndex }, children: [
10560
+ /* @__PURE__ */ jsx78("span", { className: "fd-overline fd-muted fd-fb-menu-title", children: "Move to\u2026" }),
10561
+ destinations.map((d) => /* @__PURE__ */ jsxs74(
10562
+ "button",
10563
+ {
10564
+ type: "button",
10565
+ role: "menuitem",
10566
+ className: "fd-fb-menu-item",
10567
+ style: { paddingLeft: 10 + d.depth * 14 },
10568
+ onClick: () => {
10569
+ onPick(d.id);
10570
+ setOpen(false);
10571
+ },
10572
+ children: [
10573
+ /* @__PURE__ */ jsx78("i", { className: "ph ph-" + (d.id == null ? "house-simple" : "folder-simple"), "aria-hidden": "true" }),
10574
+ d.name
10575
+ ]
10576
+ },
10577
+ d.id ?? "__root__"
10578
+ ))
10579
+ ] }) }) : null
10580
+ ] });
10581
+ }
10582
+ function RowShell({ icon, name, meta, onOpen, draggableId, isDropTarget, onDropPayload, actions, className = "" }) {
10583
+ const [over, setOver] = React48.useState(false);
10584
+ const dragCounter = React48.useRef(0);
10585
+ const dropHandlers = isDropTarget ? {
10586
+ onDragEnter: (e) => {
10587
+ if (!hasInternalPayload(e)) return;
10588
+ dragCounter.current++;
10589
+ setOver(true);
10590
+ },
10591
+ onDragOver: (e) => {
10592
+ if (!hasInternalPayload(e)) return;
10593
+ e.preventDefault();
10594
+ e.dataTransfer.dropEffect = "move";
10595
+ },
10596
+ onDragLeave: () => {
10597
+ dragCounter.current = Math.max(0, dragCounter.current - 1);
10598
+ if (!dragCounter.current) setOver(false);
10599
+ },
10600
+ onDrop: (e) => {
10601
+ if (!hasInternalPayload(e)) return;
10602
+ e.preventDefault();
10603
+ dragCounter.current = 0;
10604
+ setOver(false);
10605
+ const payload = readDragPayload(e);
10606
+ if (payload && onDropPayload) onDropPayload(payload);
10607
+ }
10608
+ } : {};
10609
+ return /* @__PURE__ */ jsxs74("div", { className: ["fd-fb-row", over ? "is-drop-target" : "", className].filter(Boolean).join(" "), ...dropHandlers, children: [
10610
+ /* @__PURE__ */ jsxs74(
10611
+ "button",
10612
+ {
10613
+ type: "button",
10614
+ className: "fd-fb-row-main",
10615
+ onClick: onOpen,
10616
+ draggable: !!draggableId,
10617
+ onDragStart: draggableId ? (e) => writeDragPayload(e, draggableId) : void 0,
10618
+ children: [
10619
+ /* @__PURE__ */ jsx78("span", { className: "fd-fb-row-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx78("i", { className: "ph ph-" + icon }) }),
10620
+ /* @__PURE__ */ jsxs74("span", { className: "fd-fb-row-text", children: [
10621
+ /* @__PURE__ */ jsx78("span", { className: "fd-fb-row-name", title: name, children: name }),
10622
+ meta ? /* @__PURE__ */ jsx78("span", { className: "fd-fb-row-meta", children: meta }) : null
10623
+ ] })
10624
+ ]
10625
+ }
10626
+ ),
10627
+ actions ? /* @__PURE__ */ jsx78("span", { className: "fd-fb-row-actions", children: actions }) : null
10628
+ ] });
10629
+ }
10630
+ function TreeNode({ folder, depth, folders, currentFolderId, onNavigate, onMoveInto, canMove }) {
10631
+ const kids = childrenOf(folders, folder.id);
10632
+ const [expanded, setExpanded] = React48.useState(() => currentFolderId != null && isSelfOrDescendant(folders, folder.id, currentFolderId));
10633
+ React48.useEffect(() => {
10634
+ if (currentFolderId != null && isSelfOrDescendant(folders, folder.id, currentFolderId)) setExpanded(true);
10635
+ }, [currentFolderId, folder.id, folders]);
10636
+ const isCurrent = folder.id === currentFolderId;
10637
+ return /* @__PURE__ */ jsxs74("div", { className: "fd-fb-tree-node", children: [
10638
+ /* @__PURE__ */ jsx78(
10639
+ RowShell,
10640
+ {
10641
+ icon: folder.id === currentFolderId ? "folder-open" : "folder-simple",
10642
+ name: folder.name,
10643
+ onOpen: () => onNavigate(folder.id),
10644
+ draggableId: canMove ? { kind: "folder", id: folder.id } : void 0,
10645
+ isDropTarget: canMove,
10646
+ onDropPayload: (payload) => onMoveInto(payload, folder.id),
10647
+ className: ["fd-fb-tree-row", isCurrent ? "is-current" : ""].filter(Boolean).join(" "),
10648
+ actions: kids.length ? /* @__PURE__ */ jsx78(
10649
+ IconButton,
10650
+ {
10651
+ icon: expanded ? "caret-down" : "caret-right",
10652
+ label: (expanded ? "Collapse " : "Expand ") + folder.name,
10653
+ size: "sm",
10654
+ variant: "ghost",
10655
+ onClick: () => setExpanded((v) => !v)
10656
+ }
10657
+ ) : void 0
10658
+ }
10659
+ ),
10660
+ expanded && kids.length ? /* @__PURE__ */ jsx78("div", { className: "fd-fb-tree-children", style: { paddingLeft: 14 }, children: kids.map((k) => /* @__PURE__ */ jsx78(
10661
+ TreeNode,
10662
+ {
10663
+ folder: k,
10664
+ depth: depth + 1,
10665
+ folders,
10666
+ currentFolderId,
10667
+ onNavigate,
10668
+ onMoveInto,
10669
+ canMove
10670
+ },
10671
+ k.id
10672
+ )) }) : null
10673
+ ] });
10674
+ }
10675
+ function FileBrowser({
10676
+ folders,
10677
+ files,
10678
+ currentFolderId,
10679
+ onNavigate,
10680
+ onCreateFolder,
10681
+ onRenameFolder,
10682
+ onMoveFolder,
10683
+ onMoveFile,
10684
+ onOpenFile,
10685
+ onRemoveFile,
10686
+ onDeleteFolder,
10687
+ onUpload,
10688
+ loading = false,
10689
+ permissions,
10690
+ className = ""
10691
+ }) {
10692
+ const perm = permissions || {};
10693
+ const canCreateFolder = perm.canCreateFolder !== false && !!onCreateFolder;
10694
+ const canRenameFolder = perm.canRenameFolder !== false && !!onRenameFolder;
10695
+ const canMove = perm.canMove !== false && (!!onMoveFolder || !!onMoveFile);
10696
+ const canUpload = perm.canUpload !== false && !!onUpload;
10697
+ const canRemoveFile = perm.canRemoveFile !== false && !!onRemoveFile;
10698
+ const canDeleteFolder = perm.canDeleteFolder !== false && !!onDeleteFolder;
10699
+ const [createOpen, setCreateOpen] = React48.useState(false);
10700
+ const [createName, setCreateName] = React48.useState("");
10701
+ const [renameTarget, setRenameTarget] = React48.useState(null);
10702
+ const [renameValue, setRenameValue] = React48.useState("");
10703
+ const [deleteTarget, setDeleteTarget] = React48.useState(null);
10704
+ const uploadInputRef = React48.useRef(null);
10705
+ const crumbFolders = React48.useMemo(() => ancestorsOf(folders, currentFolderId), [folders, currentFolderId]);
10706
+ const subfolders = React48.useMemo(() => childrenOf(folders, currentFolderId), [folders, currentFolderId]);
10707
+ const currentFiles = React48.useMemo(
10708
+ () => files.filter((f) => f.folderId === currentFolderId).sort((a, b) => a.name.localeCompare(b.name)),
10709
+ [files, currentFolderId]
10710
+ );
10711
+ const isEmpty = !subfolders.length && !currentFiles.length;
10712
+ const moveIntoFolder = (payload, targetFolderId) => {
10713
+ if (payload.kind === "folder") {
10714
+ if (payload.id === targetFolderId) return;
10715
+ if (isSelfOrDescendant(folders, payload.id, targetFolderId)) return;
10716
+ onMoveFolder && onMoveFolder(payload.id, targetFolderId);
10717
+ } else {
10718
+ onMoveFile && onMoveFile(payload.id, targetFolderId);
10719
+ }
10720
+ };
10721
+ const openCreate = () => {
10722
+ setCreateName("");
10723
+ setCreateOpen(true);
10724
+ };
10725
+ const submitCreate = () => {
10726
+ const name = createName.trim();
10727
+ if (name && onCreateFolder) onCreateFolder(currentFolderId, name);
10728
+ setCreateOpen(false);
10729
+ };
10730
+ const openRename = (folder) => {
10731
+ setRenameValue(folder.name);
10732
+ setRenameTarget(folder);
10733
+ };
10734
+ const submitRename = () => {
10735
+ const name = renameValue.trim();
10736
+ if (name && renameTarget && onRenameFolder) onRenameFolder(renameTarget.id, name);
10737
+ setRenameTarget(null);
10738
+ };
10739
+ const openDelete = (folder) => {
10740
+ setDeleteTarget(folder);
10741
+ };
10742
+ const confirmDelete = () => {
10743
+ if (deleteTarget && onDeleteFolder) onDeleteFolder(deleteTarget);
10744
+ setDeleteTarget(null);
10745
+ };
10746
+ const content = /* @__PURE__ */ jsxs74("div", { className: ["fd-fb", className].filter(Boolean).join(" "), children: [
10747
+ /* @__PURE__ */ jsxs74("div", { className: "fd-fb-head", children: [
10748
+ /* @__PURE__ */ jsxs74("nav", { className: "fd-fb-crumbs", "aria-label": "Folder path", children: [
10749
+ /* @__PURE__ */ jsx78(
10750
+ BreadcrumbCrumb,
10751
+ {
10752
+ label: "Home",
10753
+ isCurrent: currentFolderId == null,
10754
+ onClick: () => onNavigate(null),
10755
+ isDropTarget: canMove,
10756
+ onDropPayload: (p) => moveIntoFolder(p, null)
10757
+ }
10758
+ ),
10759
+ crumbFolders.map((f, i) => /* @__PURE__ */ jsx78(
10760
+ BreadcrumbCrumb,
10761
+ {
10762
+ label: f.name,
10763
+ isCurrent: i === crumbFolders.length - 1,
10764
+ onClick: () => onNavigate(f.id),
10765
+ isDropTarget: canMove,
10766
+ onDropPayload: (p) => moveIntoFolder(p, f.id)
10767
+ },
10768
+ f.id
10769
+ ))
10770
+ ] }),
10771
+ /* @__PURE__ */ jsxs74("span", { className: "fd-fb-actions", children: [
10772
+ canCreateFolder ? /* @__PURE__ */ jsx78(Button, { variant: "secondary", size: "sm", icon: "folder-simple-plus", onClick: openCreate, children: "New folder" }) : null,
10773
+ canUpload ? /* @__PURE__ */ jsxs74(Fragment23, { children: [
10774
+ /* @__PURE__ */ jsx78(Button, { variant: "secondary", size: "sm", icon: "upload-simple", onClick: () => uploadInputRef.current && uploadInputRef.current.click(), children: "Upload" }),
10775
+ /* @__PURE__ */ jsx78(
10776
+ "input",
10777
+ {
10778
+ ref: uploadInputRef,
10779
+ type: "file",
10780
+ multiple: true,
10781
+ className: "fd-sr",
10782
+ "aria-label": "Upload files",
10783
+ onChange: (e) => {
10784
+ const picked = Array.from(e.target.files || []);
10785
+ if (picked.length && onUpload) onUpload(picked, currentFolderId);
10786
+ e.target.value = "";
10787
+ }
10788
+ }
10789
+ )
10790
+ ] }) : null
10791
+ ] })
10792
+ ] }),
10793
+ /* @__PURE__ */ jsxs74("div", { className: "fd-fb-body", children: [
10794
+ /* @__PURE__ */ jsxs74("div", { className: "fd-fb-tree", role: "region", "aria-label": "Folder tree", children: [
10795
+ /* @__PURE__ */ jsx78(
10796
+ RowShell,
10797
+ {
10798
+ icon: "house-simple",
10799
+ name: "Home",
10800
+ onOpen: () => onNavigate(null),
10801
+ isDropTarget: canMove,
10802
+ onDropPayload: (p) => moveIntoFolder(p, null),
10803
+ className: ["fd-fb-tree-row", currentFolderId == null ? "is-current" : ""].filter(Boolean).join(" ")
10804
+ }
10805
+ ),
10806
+ childrenOf(folders, null).map((f) => /* @__PURE__ */ jsx78(
10807
+ TreeNode,
10808
+ {
10809
+ folder: f,
10810
+ depth: 0,
10811
+ folders,
10812
+ currentFolderId,
10813
+ onNavigate,
10814
+ onMoveInto: moveIntoFolder,
10815
+ canMove
10816
+ },
10817
+ f.id
10818
+ ))
10819
+ ] }),
10820
+ /* @__PURE__ */ jsx78("div", { className: "fd-fb-main", role: "region", "aria-label": "Folder contents", children: /* @__PURE__ */ jsx78(
10821
+ LoadingRegion,
10822
+ {
10823
+ state: loading ? "loading" : "ready",
10824
+ label: "Loading folder contents",
10825
+ skeleton: /* @__PURE__ */ jsxs74("div", { className: "fd-stack", style: { gap: 8, padding: 12 }, children: [
10826
+ /* @__PURE__ */ jsx78(Skeleton, { h: 44, radius: "10px" }),
10827
+ /* @__PURE__ */ jsx78(Skeleton, { h: 44, radius: "10px" }),
10828
+ /* @__PURE__ */ jsx78(Skeleton, { h: 44, radius: "10px" })
10829
+ ] }),
10830
+ children: isEmpty ? /* @__PURE__ */ jsx78(EmptyState, { icon: "folder-open", title: "This folder is empty", children: canUpload ? "Drop files here, or use Upload above." : "Nothing has been filed here yet." }) : /* @__PURE__ */ jsxs74("div", { className: "fd-fb-list", children: [
10831
+ subfolders.map((f) => /* @__PURE__ */ jsx78(
10832
+ RowShell,
10833
+ {
10834
+ icon: "folder-simple",
10835
+ name: f.name,
10836
+ onOpen: () => onNavigate(f.id),
10837
+ draggableId: canMove ? { kind: "folder", id: f.id } : void 0,
10838
+ isDropTarget: canMove,
10839
+ onDropPayload: (p) => moveIntoFolder(p, f.id),
10840
+ actions: /* @__PURE__ */ jsxs74(Fragment23, { children: [
10841
+ canRenameFolder ? /* @__PURE__ */ jsx78(IconButton, { icon: "pencil-simple", label: "Rename " + f.name, size: "sm", variant: "ghost", onClick: () => openRename(f) }) : null,
10842
+ canMove ? /* @__PURE__ */ jsx78(
10843
+ FolderPickerMenu,
10844
+ {
10845
+ label: "Move " + f.name,
10846
+ destinations: flattenDestinations(folders, f.id),
10847
+ onPick: (target) => moveIntoFolder({ kind: "folder", id: f.id }, target)
10848
+ }
10849
+ ) : null,
10850
+ canDeleteFolder ? /* @__PURE__ */ jsx78(IconButton, { icon: "trash-simple", label: "Delete " + f.name, size: "sm", variant: "ghost", onClick: () => openDelete(f) }) : null
10851
+ ] })
10852
+ },
10853
+ f.id
10854
+ )),
10855
+ currentFiles.map((file) => /* @__PURE__ */ jsx78(
10856
+ RowShell,
10857
+ {
10858
+ icon: iconForMime(file.mime, file.name),
10859
+ name: file.name,
10860
+ meta: file.size ? formatBytes(file.size) : void 0,
10861
+ onOpen: onOpenFile ? () => onOpenFile(file) : void 0,
10862
+ draggableId: canMove ? { kind: "file", id: file.id } : void 0,
10863
+ actions: /* @__PURE__ */ jsxs74(Fragment23, { children: [
10864
+ canMove ? /* @__PURE__ */ jsx78(
10865
+ FolderPickerMenu,
10866
+ {
10867
+ label: "Move " + file.name,
10868
+ destinations: flattenDestinations(folders),
10869
+ onPick: (target) => onMoveFile && onMoveFile(file.id, target)
10870
+ }
10871
+ ) : null,
10872
+ canRemoveFile ? /* @__PURE__ */ jsx78(IconButton, { icon: "trash-simple", label: "Remove " + file.name, size: "sm", variant: "ghost", onClick: () => onRemoveFile && onRemoveFile(file) }) : null
10873
+ ] })
10874
+ },
10875
+ file.id
10876
+ ))
10877
+ ] })
10878
+ }
10879
+ ) })
10880
+ ] }),
10881
+ /* @__PURE__ */ jsx78(Modal, { open: createOpen, title: "New folder", onClose: () => setCreateOpen(false), footer: /* @__PURE__ */ jsxs74(Fragment23, { children: [
10882
+ /* @__PURE__ */ jsx78(Button, { variant: "secondary", onClick: () => setCreateOpen(false), children: "Cancel" }),
10883
+ /* @__PURE__ */ jsx78(Button, { onClick: submitCreate, disabled: !createName.trim(), children: "Create" })
10884
+ ] }), children: /* @__PURE__ */ jsx78(
10885
+ "input",
10886
+ {
10887
+ className: "fd-fb-input",
10888
+ value: createName,
10889
+ onChange: (e) => setCreateName(e.target.value),
10890
+ onKeyDown: (e) => {
10891
+ if (e.key === "Enter") submitCreate();
10892
+ },
10893
+ placeholder: "Folder name",
10894
+ "aria-label": "Folder name",
10895
+ autoFocus: true
10896
+ }
10897
+ ) }),
10898
+ /* @__PURE__ */ jsx78(Modal, { open: renameTarget != null, title: "Rename folder", onClose: () => setRenameTarget(null), footer: /* @__PURE__ */ jsxs74(Fragment23, { children: [
10899
+ /* @__PURE__ */ jsx78(Button, { variant: "secondary", onClick: () => setRenameTarget(null), children: "Cancel" }),
10900
+ /* @__PURE__ */ jsx78(Button, { onClick: submitRename, disabled: !renameValue.trim(), children: "Save" })
10901
+ ] }), children: /* @__PURE__ */ jsx78(
10902
+ "input",
10903
+ {
10904
+ className: "fd-fb-input",
10905
+ value: renameValue,
10906
+ onChange: (e) => setRenameValue(e.target.value),
10907
+ onKeyDown: (e) => {
10908
+ if (e.key === "Enter") submitRename();
10909
+ },
10910
+ placeholder: "Folder name",
10911
+ "aria-label": "Folder name",
10912
+ autoFocus: true
10913
+ }
10914
+ ) }),
10915
+ /* @__PURE__ */ jsx78(
10916
+ Modal,
10917
+ {
10918
+ open: deleteTarget != null,
10919
+ title: deleteTarget ? "Delete \u201C" + deleteTarget.name + "\u201D?" : "Delete folder?",
10920
+ description: "This can't be undone. The folder must be empty.",
10921
+ onClose: () => setDeleteTarget(null),
10922
+ footer: /* @__PURE__ */ jsxs74(Fragment23, { children: [
10923
+ /* @__PURE__ */ jsx78(Button, { variant: "secondary", onClick: () => setDeleteTarget(null), children: "Cancel" }),
10924
+ /* @__PURE__ */ jsx78(Button, { variant: "danger", onClick: confirmDelete, children: "Delete" })
10925
+ ] })
10926
+ }
10927
+ )
10928
+ ] });
10929
+ if (!canUpload) return content;
10930
+ return /* @__PURE__ */ jsx78(Dropzone, { className: "fd-fb-dropzone", onFiles: (picked) => onUpload && onUpload(picked, currentFolderId), label: "Drop files to upload", children: content });
10931
+ }
10932
+ function BreadcrumbCrumb({
10933
+ label,
10934
+ isCurrent,
10935
+ onClick,
10936
+ isDropTarget,
10937
+ onDropPayload
10938
+ }) {
10939
+ const [over, setOver] = React48.useState(false);
10940
+ const dragCounter = React48.useRef(0);
10941
+ const dropHandlers = isDropTarget ? {
10942
+ onDragEnter: (e) => {
10943
+ if (!hasInternalPayload(e)) return;
10944
+ dragCounter.current++;
10945
+ setOver(true);
10946
+ },
10947
+ onDragOver: (e) => {
10948
+ if (!hasInternalPayload(e)) return;
10949
+ e.preventDefault();
10950
+ e.dataTransfer.dropEffect = "move";
10951
+ },
10952
+ onDragLeave: () => {
10953
+ dragCounter.current = Math.max(0, dragCounter.current - 1);
10954
+ if (!dragCounter.current) setOver(false);
10955
+ },
10956
+ onDrop: (e) => {
10957
+ if (!hasInternalPayload(e)) return;
10958
+ e.preventDefault();
10959
+ dragCounter.current = 0;
10960
+ setOver(false);
10961
+ const payload = readDragPayload(e);
10962
+ if (payload && onDropPayload) onDropPayload(payload);
10963
+ }
10964
+ } : {};
10965
+ return /* @__PURE__ */ jsx78("span", { className: ["fd-fb-crumb", over ? "is-drop-target" : ""].filter(Boolean).join(" "), ...dropHandlers, children: isCurrent ? /* @__PURE__ */ jsx78("span", { className: "fd-fb-crumb-current", "aria-current": "page", children: label }) : /* @__PURE__ */ jsx78("button", { type: "button", className: "fd-fb-crumb-link", onClick, children: label }) });
10966
+ }
10967
+
10968
+ // src/kits/query.ts
10969
+ import * as React49 from "react";
10423
10970
  function eqFilter(get2) {
10424
10971
  return (row, value) => Array.isArray(value) ? value.includes(get2(row)) : get2(row) === value;
10425
10972
  }
@@ -10451,22 +10998,22 @@ function compare(a, b, dir) {
10451
10998
  var API = null;
10452
10999
  var PREFS = null;
10453
11000
  function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
10454
- const [query, setQuery] = React48.useState(() => {
11001
+ const [query, setQuery] = React49.useState(() => {
10455
11002
  const store = PREFS || window.PlannerPrefs;
10456
11003
  const saved = prefsKey && store ? store.getTable(prefsKey) : {};
10457
11004
  return { ...DEFAULTS, ...defaults || {}, ...saved.pageSize ? { pageSize: saved.pageSize } : {}, ...saved.sort ? { sort: saved.sort, dir: saved.dir || "desc" } : {} };
10458
11005
  });
10459
- const savePref = React48.useCallback((patch2) => {
11006
+ const savePref = React49.useCallback((patch2) => {
10460
11007
  const store = PREFS || window.PlannerPrefs;
10461
11008
  if (prefsKey && store) store.setTable(prefsKey, patch2);
10462
11009
  }, [prefsKey]);
10463
- const [res, setRes] = React48.useState(null);
10464
- const [loading, setLoading] = React48.useState(true);
10465
- const seq2 = React48.useRef(0);
11010
+ const [res, setRes] = React49.useState(null);
11011
+ const [loading, setLoading] = React49.useState(true);
11012
+ const seq2 = React49.useRef(0);
10466
11013
  const depKey = (deps || []).join("|");
10467
11014
  const paramKey = JSON.stringify(params || {});
10468
11015
  const queryKey = JSON.stringify(query);
10469
- React48.useEffect(() => {
11016
+ React49.useEffect(() => {
10470
11017
  const id = ++seq2.current;
10471
11018
  setLoading(true);
10472
11019
  const t = setTimeout(() => {
@@ -10810,6 +11357,7 @@ export {
10810
11357
  FILE_VIEWER_CSV_ROW_LIMIT,
10811
11358
  FILE_VIEWER_TEXT_LIMIT,
10812
11359
  FeatureGate,
11360
+ FileBrowser,
10813
11361
  FileChip,
10814
11362
  FileGrid,
10815
11363
  FileKit,