@flytedan/flytebot-design-system 0.13.0 → 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.d.cts CHANGED
@@ -3441,6 +3441,7 @@ interface FileBrowserPermissions {
3441
3441
  canMove?: boolean;
3442
3442
  canUpload?: boolean;
3443
3443
  canRemoveFile?: boolean;
3444
+ canDeleteFolder?: boolean;
3444
3445
  }
3445
3446
  interface FileBrowserProps {
3446
3447
  folders: FileBrowserFolder[];
@@ -3454,6 +3455,16 @@ interface FileBrowserProps {
3454
3455
  onMoveFile?: (fileId: string, targetFolderId: string | null) => void;
3455
3456
  onOpenFile?: (file: FileBrowserFile) => void;
3456
3457
  onRemoveFile?: (file: FileBrowserFile) => void;
3458
+ /** MP-190: same contract as `onRenameFolder`/`onMoveFolder` — no fetching, no emptiness
3459
+ * pre-check (the host's server is the one source of truth for "empty"; see the folder
3460
+ * kit doc comment above), fired only once the confirm dialog is accepted. Sibling
3461
+ * callbacks (`onRenameFolder`, `onMoveFolder`, `onRemoveFile`) are all plain, synchronous,
3462
+ * void callbacks with no promise/rejection handling anywhere in this component, so this
3463
+ * one is void too rather than inventing an async contract none of its siblings have — a
3464
+ * host that needs to show the server's `folder_not_empty` refusal does so the same way it
3465
+ * already must for a failed rename/move/remove: by feeding the error back through its own
3466
+ * state, outside this component. */
3467
+ onDeleteFolder?: (folder: FileBrowserFolder) => void;
3457
3468
  /** Fired with the raw picked/dropped files and the folder they landed in — FileBrowser
3458
3469
  * does no fetching and stages nothing itself (R-8/"generic, controlled" — see MP-135):
3459
3470
  * the host runs its own `useStagedFiles`/`FileUploadHandler` and feeds the result back
@@ -3484,7 +3495,7 @@ interface FileBrowserProps {
3484
3495
  * region per pane either way (R-8): the tree pane and the main pane each own their own
3485
3496
  * `overflow`, the head/breadcrumb bar never scrolls.
3486
3497
  */
3487
- declare function FileBrowser({ folders, files, currentFolderId, onNavigate, onCreateFolder, onRenameFolder, onMoveFolder, onMoveFile, onOpenFile, onRemoveFile, onUpload, loading, permissions, className, }: FileBrowserProps): React.JSX.Element;
3498
+ declare function FileBrowser({ folders, files, currentFolderId, onNavigate, onCreateFolder, onRenameFolder, onMoveFolder, onMoveFile, onOpenFile, onRemoveFile, onDeleteFolder, onUpload, loading, permissions, className, }: FileBrowserProps): React.JSX.Element;
3488
3499
 
3489
3500
  interface TableQuery {
3490
3501
  /** 1-indexed. */
package/dist/index.d.ts CHANGED
@@ -3441,6 +3441,7 @@ interface FileBrowserPermissions {
3441
3441
  canMove?: boolean;
3442
3442
  canUpload?: boolean;
3443
3443
  canRemoveFile?: boolean;
3444
+ canDeleteFolder?: boolean;
3444
3445
  }
3445
3446
  interface FileBrowserProps {
3446
3447
  folders: FileBrowserFolder[];
@@ -3454,6 +3455,16 @@ interface FileBrowserProps {
3454
3455
  onMoveFile?: (fileId: string, targetFolderId: string | null) => void;
3455
3456
  onOpenFile?: (file: FileBrowserFile) => void;
3456
3457
  onRemoveFile?: (file: FileBrowserFile) => void;
3458
+ /** MP-190: same contract as `onRenameFolder`/`onMoveFolder` — no fetching, no emptiness
3459
+ * pre-check (the host's server is the one source of truth for "empty"; see the folder
3460
+ * kit doc comment above), fired only once the confirm dialog is accepted. Sibling
3461
+ * callbacks (`onRenameFolder`, `onMoveFolder`, `onRemoveFile`) are all plain, synchronous,
3462
+ * void callbacks with no promise/rejection handling anywhere in this component, so this
3463
+ * one is void too rather than inventing an async contract none of its siblings have — a
3464
+ * host that needs to show the server's `folder_not_empty` refusal does so the same way it
3465
+ * already must for a failed rename/move/remove: by feeding the error back through its own
3466
+ * state, outside this component. */
3467
+ onDeleteFolder?: (folder: FileBrowserFolder) => void;
3457
3468
  /** Fired with the raw picked/dropped files and the folder they landed in — FileBrowser
3458
3469
  * does no fetching and stages nothing itself (R-8/"generic, controlled" — see MP-135):
3459
3470
  * the host runs its own `useStagedFiles`/`FileUploadHandler` and feeds the result back
@@ -3484,7 +3495,7 @@ interface FileBrowserProps {
3484
3495
  * region per pane either way (R-8): the tree pane and the main pane each own their own
3485
3496
  * `overflow`, the head/breadcrumb bar never scrolls.
3486
3497
  */
3487
- declare function FileBrowser({ folders, files, currentFolderId, onNavigate, onCreateFolder, onRenameFolder, onMoveFolder, onMoveFile, onOpenFile, onRemoveFile, onUpload, loading, permissions, className, }: FileBrowserProps): React.JSX.Element;
3498
+ declare function FileBrowser({ folders, files, currentFolderId, onNavigate, onCreateFolder, onRenameFolder, onMoveFolder, onMoveFile, onOpenFile, onRemoveFile, onDeleteFolder, onUpload, loading, permissions, className, }: FileBrowserProps): React.JSX.Element;
3488
3499
 
3489
3500
  interface TableQuery {
3490
3501
  /** 1-indexed. */
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
  {
@@ -10648,6 +10683,7 @@ function FileBrowser({
10648
10683
  onMoveFile,
10649
10684
  onOpenFile,
10650
10685
  onRemoveFile,
10686
+ onDeleteFolder,
10651
10687
  onUpload,
10652
10688
  loading = false,
10653
10689
  permissions,
@@ -10659,10 +10695,12 @@ function FileBrowser({
10659
10695
  const canMove = perm.canMove !== false && (!!onMoveFolder || !!onMoveFile);
10660
10696
  const canUpload = perm.canUpload !== false && !!onUpload;
10661
10697
  const canRemoveFile = perm.canRemoveFile !== false && !!onRemoveFile;
10698
+ const canDeleteFolder = perm.canDeleteFolder !== false && !!onDeleteFolder;
10662
10699
  const [createOpen, setCreateOpen] = React48.useState(false);
10663
10700
  const [createName, setCreateName] = React48.useState("");
10664
10701
  const [renameTarget, setRenameTarget] = React48.useState(null);
10665
10702
  const [renameValue, setRenameValue] = React48.useState("");
10703
+ const [deleteTarget, setDeleteTarget] = React48.useState(null);
10666
10704
  const uploadInputRef = React48.useRef(null);
10667
10705
  const crumbFolders = React48.useMemo(() => ancestorsOf(folders, currentFolderId), [folders, currentFolderId]);
10668
10706
  const subfolders = React48.useMemo(() => childrenOf(folders, currentFolderId), [folders, currentFolderId]);
@@ -10698,6 +10736,13 @@ function FileBrowser({
10698
10736
  if (name && renameTarget && onRenameFolder) onRenameFolder(renameTarget.id, name);
10699
10737
  setRenameTarget(null);
10700
10738
  };
10739
+ const openDelete = (folder) => {
10740
+ setDeleteTarget(folder);
10741
+ };
10742
+ const confirmDelete = () => {
10743
+ if (deleteTarget && onDeleteFolder) onDeleteFolder(deleteTarget);
10744
+ setDeleteTarget(null);
10745
+ };
10701
10746
  const content = /* @__PURE__ */ jsxs74("div", { className: ["fd-fb", className].filter(Boolean).join(" "), children: [
10702
10747
  /* @__PURE__ */ jsxs74("div", { className: "fd-fb-head", children: [
10703
10748
  /* @__PURE__ */ jsxs74("nav", { className: "fd-fb-crumbs", "aria-label": "Folder path", children: [
@@ -10801,7 +10846,8 @@ function FileBrowser({
10801
10846
  destinations: flattenDestinations(folders, f.id),
10802
10847
  onPick: (target) => moveIntoFolder({ kind: "folder", id: f.id }, target)
10803
10848
  }
10804
- ) : null
10849
+ ) : null,
10850
+ canDeleteFolder ? /* @__PURE__ */ jsx78(IconButton, { icon: "trash-simple", label: "Delete " + f.name, size: "sm", variant: "ghost", onClick: () => openDelete(f) }) : null
10805
10851
  ] })
10806
10852
  },
10807
10853
  f.id
@@ -10865,7 +10911,20 @@ function FileBrowser({
10865
10911
  "aria-label": "Folder name",
10866
10912
  autoFocus: true
10867
10913
  }
10868
- ) })
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
+ )
10869
10928
  ] });
10870
10929
  if (!canUpload) return content;
10871
10930
  return /* @__PURE__ */ jsx78(Dropzone, { className: "fd-fb-dropzone", onFiles: (picked) => onUpload && onUpload(picked, currentFolderId), label: "Drop files to upload", children: content });