@vryzel/file-next-ui 0.5.0 → 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 +134 -85
- 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 +134 -85
- 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));
|
|
@@ -2182,14 +2202,18 @@ function FileExplorerInner(props) {
|
|
|
2182
2202
|
() => [...sourceFiles].sort((a, b) => compareFiles(a, b, sortKey, sortDir)),
|
|
2183
2203
|
[sourceFiles, sortKey, sortDir]
|
|
2184
2204
|
);
|
|
2205
|
+
const refreshSeen = react.useRef(refreshKey);
|
|
2185
2206
|
react.useEffect(() => {
|
|
2186
|
-
if (refreshKey === void 0) return;
|
|
2207
|
+
if (refreshKey === void 0 || refreshSeen.current === refreshKey) return;
|
|
2208
|
+
refreshSeen.current = refreshKey;
|
|
2187
2209
|
void reload();
|
|
2188
|
-
}, [refreshKey]);
|
|
2210
|
+
}, [refreshKey, reload]);
|
|
2189
2211
|
const handleActivate = react.useCallback(
|
|
2190
2212
|
(file) => {
|
|
2191
2213
|
if (file.id === DRAFT_FOLDER_ID) return;
|
|
2192
2214
|
if (file.kind === "folder") {
|
|
2215
|
+
if (openingFolderIdRef.current === file.id) return;
|
|
2216
|
+
openingFolderIdRef.current = file.id;
|
|
2193
2217
|
onOpenFolder?.(file);
|
|
2194
2218
|
return;
|
|
2195
2219
|
}
|
|
@@ -2320,6 +2344,7 @@ function FileExplorerInner(props) {
|
|
|
2320
2344
|
}
|
|
2321
2345
|
if (event.key === "F2" && selected && !trashOpen) {
|
|
2322
2346
|
event.preventDefault();
|
|
2347
|
+
if (locksFor(selected).rename) return;
|
|
2323
2348
|
setRenameTarget(selected);
|
|
2324
2349
|
return;
|
|
2325
2350
|
}
|
|
@@ -2329,7 +2354,8 @@ function FileExplorerInner(props) {
|
|
|
2329
2354
|
if (actions.purgeNode) setPurgeTargets(selectedFiles);
|
|
2330
2355
|
return;
|
|
2331
2356
|
}
|
|
2332
|
-
if (selectedFiles.some((file) => protectedSet.has(file.id)))
|
|
2357
|
+
if (selectedFiles.some((file) => protectedSet.has(file.id) || locksFor(file).delete))
|
|
2358
|
+
return;
|
|
2333
2359
|
setDeleteTargets(selectedFiles);
|
|
2334
2360
|
return;
|
|
2335
2361
|
}
|
|
@@ -2343,6 +2369,7 @@ function FileExplorerInner(props) {
|
|
|
2343
2369
|
clearSelection,
|
|
2344
2370
|
copyToClipboard,
|
|
2345
2371
|
handleActivate,
|
|
2372
|
+
locksFor,
|
|
2346
2373
|
pasteClipboard,
|
|
2347
2374
|
protectedSet,
|
|
2348
2375
|
selected,
|
|
@@ -2381,6 +2408,7 @@ function FileExplorerInner(props) {
|
|
|
2381
2408
|
...sortedFiles
|
|
2382
2409
|
];
|
|
2383
2410
|
}, [draftFolderName, parentId, sortedFiles, tenantId]);
|
|
2411
|
+
const listing = overlayLoading || pageStatus === "loading";
|
|
2384
2412
|
const renamingId = draftFolderName != null ? DRAFT_FOLDER_ID : renameTarget?.id ?? null;
|
|
2385
2413
|
const openUpload = () => uploadInputRef.current?.click();
|
|
2386
2414
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -2445,79 +2473,95 @@ function FileExplorerInner(props) {
|
|
|
2445
2473
|
}
|
|
2446
2474
|
)
|
|
2447
2475
|
] }),
|
|
2448
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2449
|
-
/* @__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 }),
|
|
2450
|
-
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
|
|
2451
|
-
] }) : explorer.view === "list" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2452
|
-
"div",
|
|
2453
|
-
{
|
|
2454
|
-
onClick: (event) => {
|
|
2455
|
-
if (event.target.closest("[data-file-id]")) return;
|
|
2456
|
-
clearSelection();
|
|
2457
|
-
},
|
|
2458
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2459
|
-
ExplorerListView,
|
|
2460
|
-
{
|
|
2461
|
-
files: displayFiles,
|
|
2462
|
-
protectedIds: protectedSet,
|
|
2463
|
-
selectedIds,
|
|
2464
|
-
onSelect: handleSelect,
|
|
2465
|
-
onActivate: handleActivate,
|
|
2466
|
-
onContextMenu: handleContextMenu,
|
|
2467
|
-
onDragStart: explorer.beginDrag,
|
|
2468
|
-
onDragEnd: explorer.endDrag,
|
|
2469
|
-
onDrop: handleDropOnFolder,
|
|
2470
|
-
draggingId: explorer.draggingId,
|
|
2471
|
-
draggingIds,
|
|
2472
|
-
dropTargetId: explorer.dropTargetId,
|
|
2473
|
-
onDragOverRow: explorer.setDropTarget,
|
|
2474
|
-
sortKey,
|
|
2475
|
-
sortDirection: sortDir,
|
|
2476
|
-
onSortChange: handleSortChange,
|
|
2477
|
-
renamingId,
|
|
2478
|
-
onRenameCommit: commitRename,
|
|
2479
|
-
onRenameCancel: cancelRename,
|
|
2480
|
-
scrollRef,
|
|
2481
|
-
hasMore,
|
|
2482
|
-
loadingMore: loadingMore || overlayMore,
|
|
2483
|
-
onLoadMore: loadNext
|
|
2484
|
-
}
|
|
2485
|
-
)
|
|
2486
|
-
}
|
|
2487
|
-
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2476
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2488
2477
|
"div",
|
|
2489
2478
|
{
|
|
2490
|
-
|
|
2491
|
-
|
|
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
|
-
|
|
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
|
+
]
|
|
2519
2563
|
}
|
|
2520
|
-
)
|
|
2564
|
+
),
|
|
2521
2565
|
/* @__PURE__ */ jsxRuntime.jsxs("footer", { className: "flex shrink-0 items-center justify-between gap-3 border-t border-border px-4 py-2", children: [
|
|
2522
2566
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-mono text-[10px] uppercase tracking-[0.12em] text-muted-foreground", children: [
|
|
2523
2567
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-primary tabular-nums", children: [
|
|
@@ -2538,6 +2582,8 @@ function FileExplorerInner(props) {
|
|
|
2538
2582
|
target: menu,
|
|
2539
2583
|
mode: trashOpen ? "trash" : "browse",
|
|
2540
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,
|
|
2541
2587
|
extraFileAction,
|
|
2542
2588
|
actions: {
|
|
2543
2589
|
deleteFile: async (input) => {
|
|
@@ -2572,7 +2618,10 @@ function FileExplorerInner(props) {
|
|
|
2572
2618
|
ExplorerSelectionToast,
|
|
2573
2619
|
{
|
|
2574
2620
|
files: selectedFiles,
|
|
2575
|
-
protectedIds:
|
|
2621
|
+
protectedIds: /* @__PURE__ */ new Set([
|
|
2622
|
+
...protectedSet,
|
|
2623
|
+
...selectedFiles.filter((file) => locksFor(file).delete).map((file) => file.id)
|
|
2624
|
+
]),
|
|
2576
2625
|
trashOpen,
|
|
2577
2626
|
onDownload,
|
|
2578
2627
|
onCopy: trashOpen ? void 0 : () => copyToClipboard(selectedFiles),
|