@vryzel/file-next-ui 0.5.1 → 0.5.2
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 +130 -83
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +130 -83
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -1041,6 +1041,8 @@ function ExplorerContextMenu(props) {
|
|
|
1041
1041
|
mode = "browse",
|
|
1042
1042
|
actions,
|
|
1043
1043
|
protectedItem = false,
|
|
1044
|
+
renameLocked = false,
|
|
1045
|
+
deleteLocked = false,
|
|
1044
1046
|
onClose,
|
|
1045
1047
|
onPreview,
|
|
1046
1048
|
onDownload,
|
|
@@ -1089,7 +1091,7 @@ function ExplorerContextMenu(props) {
|
|
|
1089
1091
|
swallowClickThrough();
|
|
1090
1092
|
onClose();
|
|
1091
1093
|
};
|
|
1092
|
-
const itemClass = "flex w-full min-h-11 cursor-pointer items-center gap-2 rounded-[10px] px-3 py-2.5 text-left text-sm outline-none hover:bg-accent disabled:opacity-40 sm:min-h-0 sm:px-2.5 sm:py-1.5";
|
|
1094
|
+
const itemClass = "flex w-full min-h-11 cursor-pointer items-center gap-2 rounded-[10px] px-3 py-2.5 text-left text-sm outline-none hover:bg-accent disabled:cursor-not-allowed disabled:opacity-40 sm:min-h-0 sm:px-2.5 sm:py-1.5";
|
|
1093
1095
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1094
1096
|
"div",
|
|
1095
1097
|
{
|
|
@@ -1166,13 +1168,24 @@ function ExplorerContextMenu(props) {
|
|
|
1166
1168
|
close();
|
|
1167
1169
|
extraFileAction.onSelect(target.file);
|
|
1168
1170
|
}, children: extraFileAction.label }) : null,
|
|
1169
|
-
onRename ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1171
|
+
onRename ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1172
|
+
"button",
|
|
1173
|
+
{
|
|
1174
|
+
type: "button",
|
|
1175
|
+
role: "menuitem",
|
|
1176
|
+
className: itemClass,
|
|
1177
|
+
disabled: renameLocked,
|
|
1178
|
+
onClick: () => {
|
|
1179
|
+
if (renameLocked) return;
|
|
1180
|
+
close();
|
|
1181
|
+
onRename(target.file);
|
|
1182
|
+
},
|
|
1183
|
+
children: [
|
|
1184
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pencil, { "aria-hidden": "true", className: "size-4" }),
|
|
1185
|
+
labels.rename
|
|
1186
|
+
]
|
|
1187
|
+
}
|
|
1188
|
+
) : null,
|
|
1176
1189
|
onCopy ? /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", role: "menuitem", className: itemClass, onClick: () => {
|
|
1177
1190
|
close();
|
|
1178
1191
|
onCopy(target.file);
|
|
@@ -1211,10 +1224,10 @@ function ExplorerContextMenu(props) {
|
|
|
1211
1224
|
{
|
|
1212
1225
|
type: "button",
|
|
1213
1226
|
role: "menuitem",
|
|
1214
|
-
disabled: protectedItem,
|
|
1227
|
+
disabled: protectedItem || deleteLocked,
|
|
1215
1228
|
className: cn(itemClass, "text-destructive"),
|
|
1216
1229
|
onClick: () => {
|
|
1217
|
-
if (protectedItem) return;
|
|
1230
|
+
if (protectedItem || deleteLocked) return;
|
|
1218
1231
|
close();
|
|
1219
1232
|
if (onRequestDelete) onRequestDelete(target.file);
|
|
1220
1233
|
else void actions.deleteFile({ id: target.file.id });
|
|
@@ -1966,10 +1979,15 @@ function FileExplorerInner(props) {
|
|
|
1966
1979
|
onPreview,
|
|
1967
1980
|
onDownload,
|
|
1968
1981
|
extraFileAction,
|
|
1969
|
-
protectedIds
|
|
1982
|
+
protectedIds,
|
|
1983
|
+
itemLocks
|
|
1970
1984
|
} = props;
|
|
1971
1985
|
const labels = useExplorerLabels();
|
|
1972
1986
|
const protectedSet = react.useMemo(() => new Set(protectedIds ?? []), [protectedIds]);
|
|
1987
|
+
const locksFor = react.useCallback(
|
|
1988
|
+
(file) => itemLocks?.(file) ?? {},
|
|
1989
|
+
[itemLocks]
|
|
1990
|
+
);
|
|
1973
1991
|
const explorer = fileNextHeadless.useFileExplorer({
|
|
1974
1992
|
listFiles: ((input) => listFiles({ parentId: input.parentId, limit: PAGE_SIZE })),
|
|
1975
1993
|
tenantId,
|
|
@@ -1991,6 +2009,7 @@ function FileExplorerInner(props) {
|
|
|
1991
2009
|
const browseCursor = react.useRef(void 0);
|
|
1992
2010
|
const overlayCursor = react.useRef(void 0);
|
|
1993
2011
|
const loadingMoreRef = react.useRef(false);
|
|
2012
|
+
const openingFolderIdRef = react.useRef(null);
|
|
1994
2013
|
const overlayMoreRef = react.useRef(false);
|
|
1995
2014
|
const browseGen = react.useRef(0);
|
|
1996
2015
|
const [overlayFiles, setOverlayFiles] = react.useState(null);
|
|
@@ -2063,6 +2082,7 @@ function FileExplorerInner(props) {
|
|
|
2063
2082
|
setLoadingMore(false);
|
|
2064
2083
|
return;
|
|
2065
2084
|
}
|
|
2085
|
+
if (mode === "reset") openingFolderIdRef.current = null;
|
|
2066
2086
|
if (result.ok) {
|
|
2067
2087
|
browseCursor.current = result.value.nextCursor;
|
|
2068
2088
|
setBrowseHasMore(Boolean(result.value.nextCursor));
|
|
@@ -2192,6 +2212,8 @@ function FileExplorerInner(props) {
|
|
|
2192
2212
|
(file) => {
|
|
2193
2213
|
if (file.id === DRAFT_FOLDER_ID) return;
|
|
2194
2214
|
if (file.kind === "folder") {
|
|
2215
|
+
if (openingFolderIdRef.current === file.id) return;
|
|
2216
|
+
openingFolderIdRef.current = file.id;
|
|
2195
2217
|
onOpenFolder?.(file);
|
|
2196
2218
|
return;
|
|
2197
2219
|
}
|
|
@@ -2322,6 +2344,7 @@ function FileExplorerInner(props) {
|
|
|
2322
2344
|
}
|
|
2323
2345
|
if (event.key === "F2" && selected && !trashOpen) {
|
|
2324
2346
|
event.preventDefault();
|
|
2347
|
+
if (locksFor(selected).rename) return;
|
|
2325
2348
|
setRenameTarget(selected);
|
|
2326
2349
|
return;
|
|
2327
2350
|
}
|
|
@@ -2331,7 +2354,8 @@ function FileExplorerInner(props) {
|
|
|
2331
2354
|
if (actions.purgeNode) setPurgeTargets(selectedFiles);
|
|
2332
2355
|
return;
|
|
2333
2356
|
}
|
|
2334
|
-
if (selectedFiles.some((file) => protectedSet.has(file.id)))
|
|
2357
|
+
if (selectedFiles.some((file) => protectedSet.has(file.id) || locksFor(file).delete))
|
|
2358
|
+
return;
|
|
2335
2359
|
setDeleteTargets(selectedFiles);
|
|
2336
2360
|
return;
|
|
2337
2361
|
}
|
|
@@ -2345,6 +2369,7 @@ function FileExplorerInner(props) {
|
|
|
2345
2369
|
clearSelection,
|
|
2346
2370
|
copyToClipboard,
|
|
2347
2371
|
handleActivate,
|
|
2372
|
+
locksFor,
|
|
2348
2373
|
pasteClipboard,
|
|
2349
2374
|
protectedSet,
|
|
2350
2375
|
selected,
|
|
@@ -2383,6 +2408,7 @@ function FileExplorerInner(props) {
|
|
|
2383
2408
|
...sortedFiles
|
|
2384
2409
|
];
|
|
2385
2410
|
}, [draftFolderName, parentId, sortedFiles, tenantId]);
|
|
2411
|
+
const listing = overlayLoading || pageStatus === "loading";
|
|
2386
2412
|
const renamingId = draftFolderName != null ? DRAFT_FOLDER_ID : renameTarget?.id ?? null;
|
|
2387
2413
|
const openUpload = () => uploadInputRef.current?.click();
|
|
2388
2414
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -2447,79 +2473,95 @@ function FileExplorerInner(props) {
|
|
|
2447
2473
|
}
|
|
2448
2474
|
)
|
|
2449
2475
|
] }),
|
|
2450
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2451
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-mono text-[10px] uppercase tracking-[0.12em] text-muted-foreground", children: trashOpen ? labels.emptyTrash : query.trim() ? labels.noMatches : labels.emptyFolder }),
|
|
2452
|
-
trashOpen ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: labels.trashRetention }) : !query.trim() ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: labels.dropHint }) : null
|
|
2453
|
-
] }) : explorer.view === "list" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2454
|
-
"div",
|
|
2455
|
-
{
|
|
2456
|
-
onClick: (event) => {
|
|
2457
|
-
if (event.target.closest("[data-file-id]")) return;
|
|
2458
|
-
clearSelection();
|
|
2459
|
-
},
|
|
2460
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2461
|
-
ExplorerListView,
|
|
2462
|
-
{
|
|
2463
|
-
files: displayFiles,
|
|
2464
|
-
protectedIds: protectedSet,
|
|
2465
|
-
selectedIds,
|
|
2466
|
-
onSelect: handleSelect,
|
|
2467
|
-
onActivate: handleActivate,
|
|
2468
|
-
onContextMenu: handleContextMenu,
|
|
2469
|
-
onDragStart: explorer.beginDrag,
|
|
2470
|
-
onDragEnd: explorer.endDrag,
|
|
2471
|
-
onDrop: handleDropOnFolder,
|
|
2472
|
-
draggingId: explorer.draggingId,
|
|
2473
|
-
draggingIds,
|
|
2474
|
-
dropTargetId: explorer.dropTargetId,
|
|
2475
|
-
onDragOverRow: explorer.setDropTarget,
|
|
2476
|
-
sortKey,
|
|
2477
|
-
sortDirection: sortDir,
|
|
2478
|
-
onSortChange: handleSortChange,
|
|
2479
|
-
renamingId,
|
|
2480
|
-
onRenameCommit: commitRename,
|
|
2481
|
-
onRenameCancel: cancelRename,
|
|
2482
|
-
scrollRef,
|
|
2483
|
-
hasMore,
|
|
2484
|
-
loadingMore: loadingMore || overlayMore,
|
|
2485
|
-
onLoadMore: loadNext
|
|
2486
|
-
}
|
|
2487
|
-
)
|
|
2488
|
-
}
|
|
2489
|
-
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2476
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2490
2477
|
"div",
|
|
2491
2478
|
{
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2479
|
+
ref: scrollRef,
|
|
2480
|
+
"aria-busy": listing,
|
|
2481
|
+
className: cn(
|
|
2482
|
+
"min-h-0 flex-1 overflow-y-auto overflow-x-auto overscroll-x-contain",
|
|
2483
|
+
// Stale rows stay mounted while the next listing loads, so dim them and
|
|
2484
|
+
// swallow clicks instead of letting the user re-open the same folder.
|
|
2485
|
+
listing && displayFiles.length > 0 && "pointer-events-none opacity-60"
|
|
2486
|
+
),
|
|
2487
|
+
children: [
|
|
2488
|
+
listing && displayFiles.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": true, className: "h-0.5 w-full overflow-hidden bg-muted", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-1/3 animate-pulse bg-primary" }) }) : null,
|
|
2489
|
+
listing && displayFiles.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-4 py-12 text-center font-mono text-[10px] uppercase tracking-[0.12em] text-muted-foreground", children: labels.loading }) : overlayError || pageStatus === "error" && pageError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-destructive/30 bg-destructive/5 px-4 py-3 text-sm text-destructive", children: (overlayError ?? pageError)?.message ?? labels.folderCreateFailed }) : displayFiles.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-full flex-col justify-center px-4 py-16 text-center", children: [
|
|
2490
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-mono text-[10px] uppercase tracking-[0.12em] text-muted-foreground", children: trashOpen ? labels.emptyTrash : query.trim() ? labels.noMatches : labels.emptyFolder }),
|
|
2491
|
+
trashOpen ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: labels.trashRetention }) : !query.trim() ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: labels.dropHint }) : null
|
|
2492
|
+
] }) : explorer.view === "list" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2493
|
+
"div",
|
|
2494
|
+
{
|
|
2495
|
+
onClick: (event) => {
|
|
2496
|
+
if (event.target.closest("[data-file-id]")) return;
|
|
2497
|
+
clearSelection();
|
|
2498
|
+
},
|
|
2499
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2500
|
+
ExplorerListView,
|
|
2501
|
+
{
|
|
2502
|
+
files: displayFiles,
|
|
2503
|
+
protectedIds: protectedSet,
|
|
2504
|
+
selectedIds,
|
|
2505
|
+
onSelect: handleSelect,
|
|
2506
|
+
onActivate: handleActivate,
|
|
2507
|
+
onContextMenu: handleContextMenu,
|
|
2508
|
+
onDragStart: explorer.beginDrag,
|
|
2509
|
+
onDragEnd: explorer.endDrag,
|
|
2510
|
+
onDrop: handleDropOnFolder,
|
|
2511
|
+
draggingId: explorer.draggingId,
|
|
2512
|
+
draggingIds,
|
|
2513
|
+
dropTargetId: explorer.dropTargetId,
|
|
2514
|
+
onDragOverRow: explorer.setDropTarget,
|
|
2515
|
+
sortKey,
|
|
2516
|
+
sortDirection: sortDir,
|
|
2517
|
+
onSortChange: handleSortChange,
|
|
2518
|
+
renamingId,
|
|
2519
|
+
onRenameCommit: commitRename,
|
|
2520
|
+
onRenameCancel: cancelRename,
|
|
2521
|
+
scrollRef,
|
|
2522
|
+
hasMore,
|
|
2523
|
+
loadingMore: loadingMore || overlayMore,
|
|
2524
|
+
onLoadMore: loadNext
|
|
2525
|
+
}
|
|
2526
|
+
)
|
|
2527
|
+
}
|
|
2528
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2529
|
+
"div",
|
|
2530
|
+
{
|
|
2531
|
+
onClick: (event) => {
|
|
2532
|
+
if (event.target.closest("[data-file-id]")) return;
|
|
2533
|
+
clearSelection();
|
|
2534
|
+
},
|
|
2535
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2536
|
+
ExplorerGridView,
|
|
2537
|
+
{
|
|
2538
|
+
files: displayFiles,
|
|
2539
|
+
protectedIds: protectedSet,
|
|
2540
|
+
selectedIds,
|
|
2541
|
+
onSelect: handleSelect,
|
|
2542
|
+
onActivate: handleActivate,
|
|
2543
|
+
onContextMenu: handleContextMenu,
|
|
2544
|
+
onDragStart: explorer.beginDrag,
|
|
2545
|
+
onDragEnd: explorer.endDrag,
|
|
2546
|
+
onDrop: handleDropOnFolder,
|
|
2547
|
+
draggingId: explorer.draggingId,
|
|
2548
|
+
draggingIds,
|
|
2549
|
+
dropTargetId: explorer.dropTargetId,
|
|
2550
|
+
onDragOverRow: explorer.setDropTarget,
|
|
2551
|
+
renamingId,
|
|
2552
|
+
onRenameCommit: commitRename,
|
|
2553
|
+
onRenameCancel: cancelRename,
|
|
2554
|
+
scrollRef,
|
|
2555
|
+
hasMore,
|
|
2556
|
+
loadingMore: loadingMore || overlayMore,
|
|
2557
|
+
onLoadMore: loadNext
|
|
2558
|
+
}
|
|
2559
|
+
)
|
|
2560
|
+
}
|
|
2561
|
+
)
|
|
2562
|
+
]
|
|
2521
2563
|
}
|
|
2522
|
-
)
|
|
2564
|
+
),
|
|
2523
2565
|
/* @__PURE__ */ jsxRuntime.jsxs("footer", { className: "flex shrink-0 items-center justify-between gap-3 border-t border-border px-4 py-2", children: [
|
|
2524
2566
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-mono text-[10px] uppercase tracking-[0.12em] text-muted-foreground", children: [
|
|
2525
2567
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-primary tabular-nums", children: [
|
|
@@ -2540,6 +2582,8 @@ function FileExplorerInner(props) {
|
|
|
2540
2582
|
target: menu,
|
|
2541
2583
|
mode: trashOpen ? "trash" : "browse",
|
|
2542
2584
|
protectedItem: menu?.kind === "item" ? protectedSet.has(menu.file.id) : false,
|
|
2585
|
+
renameLocked: menu?.kind === "item" ? Boolean(locksFor(menu.file).rename) : false,
|
|
2586
|
+
deleteLocked: menu?.kind === "item" ? Boolean(locksFor(menu.file).delete) : false,
|
|
2543
2587
|
extraFileAction,
|
|
2544
2588
|
actions: {
|
|
2545
2589
|
deleteFile: async (input) => {
|
|
@@ -2574,7 +2618,10 @@ function FileExplorerInner(props) {
|
|
|
2574
2618
|
ExplorerSelectionToast,
|
|
2575
2619
|
{
|
|
2576
2620
|
files: selectedFiles,
|
|
2577
|
-
protectedIds:
|
|
2621
|
+
protectedIds: /* @__PURE__ */ new Set([
|
|
2622
|
+
...protectedSet,
|
|
2623
|
+
...selectedFiles.filter((file) => locksFor(file).delete).map((file) => file.id)
|
|
2624
|
+
]),
|
|
2578
2625
|
trashOpen,
|
|
2579
2626
|
onDownload,
|
|
2580
2627
|
onCopy: trashOpen ? void 0 : () => copyToClipboard(selectedFiles),
|