@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.cjs CHANGED
@@ -76,6 +76,7 @@ __export(index_exports, {
76
76
  FILE_VIEWER_CSV_ROW_LIMIT: () => FILE_VIEWER_CSV_ROW_LIMIT,
77
77
  FILE_VIEWER_TEXT_LIMIT: () => FILE_VIEWER_TEXT_LIMIT,
78
78
  FeatureGate: () => FeatureGate,
79
+ FileBrowser: () => FileBrowser,
79
80
  FileChip: () => FileChip,
80
81
  FileGrid: () => FileGrid,
81
82
  FileKit: () => FileKit,
@@ -8672,6 +8673,30 @@ function BudgetReallocator({
8672
8673
  setDraft(spend);
8673
8674
  if (onCancel) onCancel();
8674
8675
  };
8676
+ const trackRef = React39.useRef(null);
8677
+ const chipRef = React39.useRef(null);
8678
+ const [chipLeft, setChipLeft] = React39.useState(null);
8679
+ React39.useLayoutEffect(() => {
8680
+ const track = trackRef.current;
8681
+ if (!dirty || !track) {
8682
+ setChipLeft(null);
8683
+ return;
8684
+ }
8685
+ const measure = () => {
8686
+ const chip = chipRef.current;
8687
+ if (!chip) return;
8688
+ const trackWidth = track.clientWidth;
8689
+ const chipWidth = chip.offsetWidth;
8690
+ const half = chipWidth / 2;
8691
+ const desired = pct / 100 * trackWidth;
8692
+ setChipLeft(Math.min(Math.max(desired, half), Math.max(trackWidth - half, half)));
8693
+ };
8694
+ measure();
8695
+ if (typeof ResizeObserver !== "function") return;
8696
+ const ro = new ResizeObserver(measure);
8697
+ ro.observe(track);
8698
+ return () => ro.disconnect();
8699
+ }, [dirty, pct]);
8675
8700
  return /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)(
8676
8701
  "div",
8677
8702
  {
@@ -8686,9 +8711,20 @@ function BudgetReallocator({
8686
8711
  /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(CsiBadge, { band: nextBand, crp: nextCrp, size: "medium", preview: true })
8687
8712
  ] }) : null
8688
8713
  ] }),
8689
- /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("div", { className: "fd-slider", children: [
8714
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("div", { className: "fd-slider", ref: trackRef, children: [
8690
8715
  /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("span", { className: "fd-slider-fill", style: { width: pct + "%" } }),
8691
- dirty ? /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("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,
8716
+ dirty ? /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(
8717
+ "span",
8718
+ {
8719
+ ref: chipRef,
8720
+ className: "fd-slider-chip",
8721
+ style: {
8722
+ left: chipLeft === null ? pct + "%" : chipLeft,
8723
+ background: delta < 0 ? "var(--danger-solid)" : "var(--ok-solid)"
8724
+ },
8725
+ children: (delta < 0 ? "\u2212$" : "+$") + Math.abs(delta).toLocaleString()
8726
+ }
8727
+ ) : null,
8692
8728
  /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(
8693
8729
  "input",
8694
8730
  {
@@ -10626,8 +10662,520 @@ function FileViewer({ file, resolveUrl, sidePanel, sidePanelLabel = "Details", o
10626
10662
  );
10627
10663
  }
10628
10664
 
10629
- // src/kits/query.ts
10665
+ // src/components/files/FileBrowser.tsx
10630
10666
  var React48 = __toESM(require("react"), 1);
10667
+ var import_jsx_runtime79 = require("react/jsx-runtime");
10668
+ function childrenOf(folders, parentId) {
10669
+ return folders.filter((f) => f.parentId === parentId).sort((a, b) => a.name.localeCompare(b.name));
10670
+ }
10671
+ function ancestorsOf(folders, id) {
10672
+ const byId = new Map(folders.map((f) => [f.id, f]));
10673
+ const chain = [];
10674
+ let cur = id != null ? byId.get(id) : void 0;
10675
+ const seen = /* @__PURE__ */ new Set();
10676
+ while (cur && !seen.has(cur.id)) {
10677
+ seen.add(cur.id);
10678
+ chain.unshift(cur);
10679
+ cur = cur.parentId != null ? byId.get(cur.parentId) : void 0;
10680
+ }
10681
+ return chain;
10682
+ }
10683
+ function isSelfOrDescendant(folders, ancestorId, nodeId) {
10684
+ if (nodeId == null) return false;
10685
+ if (nodeId === ancestorId) return true;
10686
+ const byParent = /* @__PURE__ */ new Map();
10687
+ folders.forEach((f) => {
10688
+ const key = f.parentId ?? "";
10689
+ if (!byParent.has(key)) byParent.set(key, []);
10690
+ byParent.get(key).push(f);
10691
+ });
10692
+ const stack = [...byParent.get(ancestorId) || []];
10693
+ while (stack.length) {
10694
+ const next = stack.pop();
10695
+ if (next.id === nodeId) return true;
10696
+ stack.push(...byParent.get(next.id) || []);
10697
+ }
10698
+ return false;
10699
+ }
10700
+ function flattenDestinations(folders, excludeSubtreeOf) {
10701
+ const out = [{ id: null, name: "Home", depth: 0 }];
10702
+ const walk = (parentId, depth) => {
10703
+ childrenOf(folders, parentId).forEach((f) => {
10704
+ if (excludeSubtreeOf && isSelfOrDescendant(folders, excludeSubtreeOf, f.id)) return;
10705
+ out.push({ id: f.id, name: f.name, depth });
10706
+ walk(f.id, depth + 1);
10707
+ });
10708
+ };
10709
+ walk(null, 1);
10710
+ return out;
10711
+ }
10712
+ var DND_MIME = "application/x-fd-filebrowser";
10713
+ function writeDragPayload(e, payload) {
10714
+ const s = JSON.stringify(payload);
10715
+ e.dataTransfer.setData(DND_MIME, s);
10716
+ e.dataTransfer.setData("text/plain", s);
10717
+ e.dataTransfer.effectAllowed = "move";
10718
+ }
10719
+ function readDragPayload(e) {
10720
+ const raw = e.dataTransfer.getData(DND_MIME) || e.dataTransfer.getData("text/plain");
10721
+ if (!raw) return null;
10722
+ try {
10723
+ const parsed = JSON.parse(raw);
10724
+ if (parsed && (parsed.kind === "file" || parsed.kind === "folder") && typeof parsed.id === "string") return parsed;
10725
+ } catch {
10726
+ }
10727
+ return null;
10728
+ }
10729
+ function hasInternalPayload(e) {
10730
+ return Array.from(e.dataTransfer.types || []).indexOf(DND_MIME) !== -1;
10731
+ }
10732
+ function FolderPickerMenu({
10733
+ label,
10734
+ destinations,
10735
+ onPick
10736
+ }) {
10737
+ const [open, setOpen] = React48.useState(false);
10738
+ const zIndex = useOverlayLayer(open);
10739
+ const ref = React48.useRef(null);
10740
+ React48.useEffect(() => {
10741
+ if (!open) return;
10742
+ const away = (e) => {
10743
+ if (ref.current && !ref.current.contains(e.target)) setOpen(false);
10744
+ };
10745
+ const esc = (e) => {
10746
+ if (e.key === "Escape") setOpen(false);
10747
+ };
10748
+ document.addEventListener("pointerdown", away);
10749
+ document.addEventListener("keydown", esc);
10750
+ return () => {
10751
+ document.removeEventListener("pointerdown", away);
10752
+ document.removeEventListener("keydown", esc);
10753
+ };
10754
+ }, [open]);
10755
+ return /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("span", { className: "fd-fb-menu-anchor", ref, children: [
10756
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
10757
+ IconButton,
10758
+ {
10759
+ icon: "arrows-out-cardinal",
10760
+ label,
10761
+ size: "sm",
10762
+ variant: "ghost",
10763
+ "aria-haspopup": "menu",
10764
+ "aria-expanded": open,
10765
+ onClick: () => setOpen((v) => !v)
10766
+ }
10767
+ ),
10768
+ open ? /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(LayerProvider, { zIndex, children: /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: "fd-fb-menu", role: "menu", "aria-label": label, style: { zIndex }, children: [
10769
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("span", { className: "fd-overline fd-muted fd-fb-menu-title", children: "Move to\u2026" }),
10770
+ destinations.map((d) => /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)(
10771
+ "button",
10772
+ {
10773
+ type: "button",
10774
+ role: "menuitem",
10775
+ className: "fd-fb-menu-item",
10776
+ style: { paddingLeft: 10 + d.depth * 14 },
10777
+ onClick: () => {
10778
+ onPick(d.id);
10779
+ setOpen(false);
10780
+ },
10781
+ children: [
10782
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("i", { className: "ph ph-" + (d.id == null ? "house-simple" : "folder-simple"), "aria-hidden": "true" }),
10783
+ d.name
10784
+ ]
10785
+ },
10786
+ d.id ?? "__root__"
10787
+ ))
10788
+ ] }) }) : null
10789
+ ] });
10790
+ }
10791
+ function RowShell({ icon, name, meta, onOpen, draggableId, isDropTarget, onDropPayload, actions, className = "" }) {
10792
+ const [over, setOver] = React48.useState(false);
10793
+ const dragCounter = React48.useRef(0);
10794
+ const dropHandlers = isDropTarget ? {
10795
+ onDragEnter: (e) => {
10796
+ if (!hasInternalPayload(e)) return;
10797
+ dragCounter.current++;
10798
+ setOver(true);
10799
+ },
10800
+ onDragOver: (e) => {
10801
+ if (!hasInternalPayload(e)) return;
10802
+ e.preventDefault();
10803
+ e.dataTransfer.dropEffect = "move";
10804
+ },
10805
+ onDragLeave: () => {
10806
+ dragCounter.current = Math.max(0, dragCounter.current - 1);
10807
+ if (!dragCounter.current) setOver(false);
10808
+ },
10809
+ onDrop: (e) => {
10810
+ if (!hasInternalPayload(e)) return;
10811
+ e.preventDefault();
10812
+ dragCounter.current = 0;
10813
+ setOver(false);
10814
+ const payload = readDragPayload(e);
10815
+ if (payload && onDropPayload) onDropPayload(payload);
10816
+ }
10817
+ } : {};
10818
+ return /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: ["fd-fb-row", over ? "is-drop-target" : "", className].filter(Boolean).join(" "), ...dropHandlers, children: [
10819
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)(
10820
+ "button",
10821
+ {
10822
+ type: "button",
10823
+ className: "fd-fb-row-main",
10824
+ onClick: onOpen,
10825
+ draggable: !!draggableId,
10826
+ onDragStart: draggableId ? (e) => writeDragPayload(e, draggableId) : void 0,
10827
+ children: [
10828
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("span", { className: "fd-fb-row-icon", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("i", { className: "ph ph-" + icon }) }),
10829
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("span", { className: "fd-fb-row-text", children: [
10830
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("span", { className: "fd-fb-row-name", title: name, children: name }),
10831
+ meta ? /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("span", { className: "fd-fb-row-meta", children: meta }) : null
10832
+ ] })
10833
+ ]
10834
+ }
10835
+ ),
10836
+ actions ? /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("span", { className: "fd-fb-row-actions", children: actions }) : null
10837
+ ] });
10838
+ }
10839
+ function TreeNode({ folder, depth, folders, currentFolderId, onNavigate, onMoveInto, canMove }) {
10840
+ const kids = childrenOf(folders, folder.id);
10841
+ const [expanded, setExpanded] = React48.useState(() => currentFolderId != null && isSelfOrDescendant(folders, folder.id, currentFolderId));
10842
+ React48.useEffect(() => {
10843
+ if (currentFolderId != null && isSelfOrDescendant(folders, folder.id, currentFolderId)) setExpanded(true);
10844
+ }, [currentFolderId, folder.id, folders]);
10845
+ const isCurrent = folder.id === currentFolderId;
10846
+ return /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: "fd-fb-tree-node", children: [
10847
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
10848
+ RowShell,
10849
+ {
10850
+ icon: folder.id === currentFolderId ? "folder-open" : "folder-simple",
10851
+ name: folder.name,
10852
+ onOpen: () => onNavigate(folder.id),
10853
+ draggableId: canMove ? { kind: "folder", id: folder.id } : void 0,
10854
+ isDropTarget: canMove,
10855
+ onDropPayload: (payload) => onMoveInto(payload, folder.id),
10856
+ className: ["fd-fb-tree-row", isCurrent ? "is-current" : ""].filter(Boolean).join(" "),
10857
+ actions: kids.length ? /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
10858
+ IconButton,
10859
+ {
10860
+ icon: expanded ? "caret-down" : "caret-right",
10861
+ label: (expanded ? "Collapse " : "Expand ") + folder.name,
10862
+ size: "sm",
10863
+ variant: "ghost",
10864
+ onClick: () => setExpanded((v) => !v)
10865
+ }
10866
+ ) : void 0
10867
+ }
10868
+ ),
10869
+ expanded && kids.length ? /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("div", { className: "fd-fb-tree-children", style: { paddingLeft: 14 }, children: kids.map((k) => /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
10870
+ TreeNode,
10871
+ {
10872
+ folder: k,
10873
+ depth: depth + 1,
10874
+ folders,
10875
+ currentFolderId,
10876
+ onNavigate,
10877
+ onMoveInto,
10878
+ canMove
10879
+ },
10880
+ k.id
10881
+ )) }) : null
10882
+ ] });
10883
+ }
10884
+ function FileBrowser({
10885
+ folders,
10886
+ files,
10887
+ currentFolderId,
10888
+ onNavigate,
10889
+ onCreateFolder,
10890
+ onRenameFolder,
10891
+ onMoveFolder,
10892
+ onMoveFile,
10893
+ onOpenFile,
10894
+ onRemoveFile,
10895
+ onDeleteFolder,
10896
+ onUpload,
10897
+ loading = false,
10898
+ permissions,
10899
+ className = ""
10900
+ }) {
10901
+ const perm = permissions || {};
10902
+ const canCreateFolder = perm.canCreateFolder !== false && !!onCreateFolder;
10903
+ const canRenameFolder = perm.canRenameFolder !== false && !!onRenameFolder;
10904
+ const canMove = perm.canMove !== false && (!!onMoveFolder || !!onMoveFile);
10905
+ const canUpload = perm.canUpload !== false && !!onUpload;
10906
+ const canRemoveFile = perm.canRemoveFile !== false && !!onRemoveFile;
10907
+ const canDeleteFolder = perm.canDeleteFolder !== false && !!onDeleteFolder;
10908
+ const [createOpen, setCreateOpen] = React48.useState(false);
10909
+ const [createName, setCreateName] = React48.useState("");
10910
+ const [renameTarget, setRenameTarget] = React48.useState(null);
10911
+ const [renameValue, setRenameValue] = React48.useState("");
10912
+ const [deleteTarget, setDeleteTarget] = React48.useState(null);
10913
+ const uploadInputRef = React48.useRef(null);
10914
+ const crumbFolders = React48.useMemo(() => ancestorsOf(folders, currentFolderId), [folders, currentFolderId]);
10915
+ const subfolders = React48.useMemo(() => childrenOf(folders, currentFolderId), [folders, currentFolderId]);
10916
+ const currentFiles = React48.useMemo(
10917
+ () => files.filter((f) => f.folderId === currentFolderId).sort((a, b) => a.name.localeCompare(b.name)),
10918
+ [files, currentFolderId]
10919
+ );
10920
+ const isEmpty = !subfolders.length && !currentFiles.length;
10921
+ const moveIntoFolder = (payload, targetFolderId) => {
10922
+ if (payload.kind === "folder") {
10923
+ if (payload.id === targetFolderId) return;
10924
+ if (isSelfOrDescendant(folders, payload.id, targetFolderId)) return;
10925
+ onMoveFolder && onMoveFolder(payload.id, targetFolderId);
10926
+ } else {
10927
+ onMoveFile && onMoveFile(payload.id, targetFolderId);
10928
+ }
10929
+ };
10930
+ const openCreate = () => {
10931
+ setCreateName("");
10932
+ setCreateOpen(true);
10933
+ };
10934
+ const submitCreate = () => {
10935
+ const name = createName.trim();
10936
+ if (name && onCreateFolder) onCreateFolder(currentFolderId, name);
10937
+ setCreateOpen(false);
10938
+ };
10939
+ const openRename = (folder) => {
10940
+ setRenameValue(folder.name);
10941
+ setRenameTarget(folder);
10942
+ };
10943
+ const submitRename = () => {
10944
+ const name = renameValue.trim();
10945
+ if (name && renameTarget && onRenameFolder) onRenameFolder(renameTarget.id, name);
10946
+ setRenameTarget(null);
10947
+ };
10948
+ const openDelete = (folder) => {
10949
+ setDeleteTarget(folder);
10950
+ };
10951
+ const confirmDelete = () => {
10952
+ if (deleteTarget && onDeleteFolder) onDeleteFolder(deleteTarget);
10953
+ setDeleteTarget(null);
10954
+ };
10955
+ const content = /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: ["fd-fb", className].filter(Boolean).join(" "), children: [
10956
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: "fd-fb-head", children: [
10957
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("nav", { className: "fd-fb-crumbs", "aria-label": "Folder path", children: [
10958
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
10959
+ BreadcrumbCrumb,
10960
+ {
10961
+ label: "Home",
10962
+ isCurrent: currentFolderId == null,
10963
+ onClick: () => onNavigate(null),
10964
+ isDropTarget: canMove,
10965
+ onDropPayload: (p) => moveIntoFolder(p, null)
10966
+ }
10967
+ ),
10968
+ crumbFolders.map((f, i) => /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
10969
+ BreadcrumbCrumb,
10970
+ {
10971
+ label: f.name,
10972
+ isCurrent: i === crumbFolders.length - 1,
10973
+ onClick: () => onNavigate(f.id),
10974
+ isDropTarget: canMove,
10975
+ onDropPayload: (p) => moveIntoFolder(p, f.id)
10976
+ },
10977
+ f.id
10978
+ ))
10979
+ ] }),
10980
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("span", { className: "fd-fb-actions", children: [
10981
+ canCreateFolder ? /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(Button, { variant: "secondary", size: "sm", icon: "folder-simple-plus", onClick: openCreate, children: "New folder" }) : null,
10982
+ canUpload ? /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)(import_jsx_runtime79.Fragment, { children: [
10983
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(Button, { variant: "secondary", size: "sm", icon: "upload-simple", onClick: () => uploadInputRef.current && uploadInputRef.current.click(), children: "Upload" }),
10984
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
10985
+ "input",
10986
+ {
10987
+ ref: uploadInputRef,
10988
+ type: "file",
10989
+ multiple: true,
10990
+ className: "fd-sr",
10991
+ "aria-label": "Upload files",
10992
+ onChange: (e) => {
10993
+ const picked = Array.from(e.target.files || []);
10994
+ if (picked.length && onUpload) onUpload(picked, currentFolderId);
10995
+ e.target.value = "";
10996
+ }
10997
+ }
10998
+ )
10999
+ ] }) : null
11000
+ ] })
11001
+ ] }),
11002
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: "fd-fb-body", children: [
11003
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: "fd-fb-tree", role: "region", "aria-label": "Folder tree", children: [
11004
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
11005
+ RowShell,
11006
+ {
11007
+ icon: "house-simple",
11008
+ name: "Home",
11009
+ onOpen: () => onNavigate(null),
11010
+ isDropTarget: canMove,
11011
+ onDropPayload: (p) => moveIntoFolder(p, null),
11012
+ className: ["fd-fb-tree-row", currentFolderId == null ? "is-current" : ""].filter(Boolean).join(" ")
11013
+ }
11014
+ ),
11015
+ childrenOf(folders, null).map((f) => /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
11016
+ TreeNode,
11017
+ {
11018
+ folder: f,
11019
+ depth: 0,
11020
+ folders,
11021
+ currentFolderId,
11022
+ onNavigate,
11023
+ onMoveInto: moveIntoFolder,
11024
+ canMove
11025
+ },
11026
+ f.id
11027
+ ))
11028
+ ] }),
11029
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("div", { className: "fd-fb-main", role: "region", "aria-label": "Folder contents", children: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
11030
+ LoadingRegion,
11031
+ {
11032
+ state: loading ? "loading" : "ready",
11033
+ label: "Loading folder contents",
11034
+ skeleton: /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: "fd-stack", style: { gap: 8, padding: 12 }, children: [
11035
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(Skeleton, { h: 44, radius: "10px" }),
11036
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(Skeleton, { h: 44, radius: "10px" }),
11037
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(Skeleton, { h: 44, radius: "10px" })
11038
+ ] }),
11039
+ children: isEmpty ? /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(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__ */ (0, import_jsx_runtime79.jsxs)("div", { className: "fd-fb-list", children: [
11040
+ subfolders.map((f) => /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
11041
+ RowShell,
11042
+ {
11043
+ icon: "folder-simple",
11044
+ name: f.name,
11045
+ onOpen: () => onNavigate(f.id),
11046
+ draggableId: canMove ? { kind: "folder", id: f.id } : void 0,
11047
+ isDropTarget: canMove,
11048
+ onDropPayload: (p) => moveIntoFolder(p, f.id),
11049
+ actions: /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)(import_jsx_runtime79.Fragment, { children: [
11050
+ canRenameFolder ? /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(IconButton, { icon: "pencil-simple", label: "Rename " + f.name, size: "sm", variant: "ghost", onClick: () => openRename(f) }) : null,
11051
+ canMove ? /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
11052
+ FolderPickerMenu,
11053
+ {
11054
+ label: "Move " + f.name,
11055
+ destinations: flattenDestinations(folders, f.id),
11056
+ onPick: (target) => moveIntoFolder({ kind: "folder", id: f.id }, target)
11057
+ }
11058
+ ) : null,
11059
+ canDeleteFolder ? /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(IconButton, { icon: "trash-simple", label: "Delete " + f.name, size: "sm", variant: "ghost", onClick: () => openDelete(f) }) : null
11060
+ ] })
11061
+ },
11062
+ f.id
11063
+ )),
11064
+ currentFiles.map((file) => /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
11065
+ RowShell,
11066
+ {
11067
+ icon: iconForMime(file.mime, file.name),
11068
+ name: file.name,
11069
+ meta: file.size ? formatBytes(file.size) : void 0,
11070
+ onOpen: onOpenFile ? () => onOpenFile(file) : void 0,
11071
+ draggableId: canMove ? { kind: "file", id: file.id } : void 0,
11072
+ actions: /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)(import_jsx_runtime79.Fragment, { children: [
11073
+ canMove ? /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
11074
+ FolderPickerMenu,
11075
+ {
11076
+ label: "Move " + file.name,
11077
+ destinations: flattenDestinations(folders),
11078
+ onPick: (target) => onMoveFile && onMoveFile(file.id, target)
11079
+ }
11080
+ ) : null,
11081
+ canRemoveFile ? /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(IconButton, { icon: "trash-simple", label: "Remove " + file.name, size: "sm", variant: "ghost", onClick: () => onRemoveFile && onRemoveFile(file) }) : null
11082
+ ] })
11083
+ },
11084
+ file.id
11085
+ ))
11086
+ ] })
11087
+ }
11088
+ ) })
11089
+ ] }),
11090
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(Modal, { open: createOpen, title: "New folder", onClose: () => setCreateOpen(false), footer: /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)(import_jsx_runtime79.Fragment, { children: [
11091
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(Button, { variant: "secondary", onClick: () => setCreateOpen(false), children: "Cancel" }),
11092
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(Button, { onClick: submitCreate, disabled: !createName.trim(), children: "Create" })
11093
+ ] }), children: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
11094
+ "input",
11095
+ {
11096
+ className: "fd-fb-input",
11097
+ value: createName,
11098
+ onChange: (e) => setCreateName(e.target.value),
11099
+ onKeyDown: (e) => {
11100
+ if (e.key === "Enter") submitCreate();
11101
+ },
11102
+ placeholder: "Folder name",
11103
+ "aria-label": "Folder name",
11104
+ autoFocus: true
11105
+ }
11106
+ ) }),
11107
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(Modal, { open: renameTarget != null, title: "Rename folder", onClose: () => setRenameTarget(null), footer: /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)(import_jsx_runtime79.Fragment, { children: [
11108
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(Button, { variant: "secondary", onClick: () => setRenameTarget(null), children: "Cancel" }),
11109
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(Button, { onClick: submitRename, disabled: !renameValue.trim(), children: "Save" })
11110
+ ] }), children: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
11111
+ "input",
11112
+ {
11113
+ className: "fd-fb-input",
11114
+ value: renameValue,
11115
+ onChange: (e) => setRenameValue(e.target.value),
11116
+ onKeyDown: (e) => {
11117
+ if (e.key === "Enter") submitRename();
11118
+ },
11119
+ placeholder: "Folder name",
11120
+ "aria-label": "Folder name",
11121
+ autoFocus: true
11122
+ }
11123
+ ) }),
11124
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
11125
+ Modal,
11126
+ {
11127
+ open: deleteTarget != null,
11128
+ title: deleteTarget ? "Delete \u201C" + deleteTarget.name + "\u201D?" : "Delete folder?",
11129
+ description: "This can't be undone. The folder must be empty.",
11130
+ onClose: () => setDeleteTarget(null),
11131
+ footer: /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)(import_jsx_runtime79.Fragment, { children: [
11132
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(Button, { variant: "secondary", onClick: () => setDeleteTarget(null), children: "Cancel" }),
11133
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(Button, { variant: "danger", onClick: confirmDelete, children: "Delete" })
11134
+ ] })
11135
+ }
11136
+ )
11137
+ ] });
11138
+ if (!canUpload) return content;
11139
+ return /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(Dropzone, { className: "fd-fb-dropzone", onFiles: (picked) => onUpload && onUpload(picked, currentFolderId), label: "Drop files to upload", children: content });
11140
+ }
11141
+ function BreadcrumbCrumb({
11142
+ label,
11143
+ isCurrent,
11144
+ onClick,
11145
+ isDropTarget,
11146
+ onDropPayload
11147
+ }) {
11148
+ const [over, setOver] = React48.useState(false);
11149
+ const dragCounter = React48.useRef(0);
11150
+ const dropHandlers = isDropTarget ? {
11151
+ onDragEnter: (e) => {
11152
+ if (!hasInternalPayload(e)) return;
11153
+ dragCounter.current++;
11154
+ setOver(true);
11155
+ },
11156
+ onDragOver: (e) => {
11157
+ if (!hasInternalPayload(e)) return;
11158
+ e.preventDefault();
11159
+ e.dataTransfer.dropEffect = "move";
11160
+ },
11161
+ onDragLeave: () => {
11162
+ dragCounter.current = Math.max(0, dragCounter.current - 1);
11163
+ if (!dragCounter.current) setOver(false);
11164
+ },
11165
+ onDrop: (e) => {
11166
+ if (!hasInternalPayload(e)) return;
11167
+ e.preventDefault();
11168
+ dragCounter.current = 0;
11169
+ setOver(false);
11170
+ const payload = readDragPayload(e);
11171
+ if (payload && onDropPayload) onDropPayload(payload);
11172
+ }
11173
+ } : {};
11174
+ return /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("span", { className: ["fd-fb-crumb", over ? "is-drop-target" : ""].filter(Boolean).join(" "), ...dropHandlers, children: isCurrent ? /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("span", { className: "fd-fb-crumb-current", "aria-current": "page", children: label }) : /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("button", { type: "button", className: "fd-fb-crumb-link", onClick, children: label }) });
11175
+ }
11176
+
11177
+ // src/kits/query.ts
11178
+ var React49 = __toESM(require("react"), 1);
10631
11179
  function eqFilter(get2) {
10632
11180
  return (row, value) => Array.isArray(value) ? value.includes(get2(row)) : get2(row) === value;
10633
11181
  }
@@ -10659,22 +11207,22 @@ function compare(a, b, dir) {
10659
11207
  var API = null;
10660
11208
  var PREFS = null;
10661
11209
  function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
10662
- const [query, setQuery] = React48.useState(() => {
11210
+ const [query, setQuery] = React49.useState(() => {
10663
11211
  const store = PREFS || window.PlannerPrefs;
10664
11212
  const saved = prefsKey && store ? store.getTable(prefsKey) : {};
10665
11213
  return { ...DEFAULTS, ...defaults || {}, ...saved.pageSize ? { pageSize: saved.pageSize } : {}, ...saved.sort ? { sort: saved.sort, dir: saved.dir || "desc" } : {} };
10666
11214
  });
10667
- const savePref = React48.useCallback((patch2) => {
11215
+ const savePref = React49.useCallback((patch2) => {
10668
11216
  const store = PREFS || window.PlannerPrefs;
10669
11217
  if (prefsKey && store) store.setTable(prefsKey, patch2);
10670
11218
  }, [prefsKey]);
10671
- const [res, setRes] = React48.useState(null);
10672
- const [loading, setLoading] = React48.useState(true);
10673
- const seq2 = React48.useRef(0);
11219
+ const [res, setRes] = React49.useState(null);
11220
+ const [loading, setLoading] = React49.useState(true);
11221
+ const seq2 = React49.useRef(0);
10674
11222
  const depKey = (deps || []).join("|");
10675
11223
  const paramKey = JSON.stringify(params || {});
10676
11224
  const queryKey = JSON.stringify(query);
10677
- React48.useEffect(() => {
11225
+ React49.useEffect(() => {
10678
11226
  const id = ++seq2.current;
10679
11227
  setLoading(true);
10680
11228
  const t = setTimeout(() => {
@@ -11019,6 +11567,7 @@ function createVersionStore(initialState, options) {
11019
11567
  FILE_VIEWER_CSV_ROW_LIMIT,
11020
11568
  FILE_VIEWER_TEXT_LIMIT,
11021
11569
  FeatureGate,
11570
+ FileBrowser,
11022
11571
  FileChip,
11023
11572
  FileGrid,
11024
11573
  FileKit,