Component reuse, Dialog component
This commit is contained in:
@@ -4,6 +4,8 @@ import type { ApiSettings } from "../../types";
|
||||
import Button from "../../components/Button";
|
||||
import ConfirmDialog from "../../components/ConfirmDialog";
|
||||
import DismissibleAlert from "../../components/DismissibleAlert";
|
||||
import FormField from "../../components/FormField";
|
||||
import FieldLabel from "../../components/help/FieldLabel";
|
||||
import ToggleSwitch from "../../components/ToggleSwitch";
|
||||
import LoadingIndicator from "../../components/LoadingIndicator";
|
||||
import {
|
||||
@@ -1326,12 +1328,11 @@ export default function FilesPage({ settings }: { settings: ApiSettings }) {
|
||||
|
||||
{dialog === "create-folder" && (
|
||||
<FileDialog title="Create folder" onClose={closeDialog}>
|
||||
<label className="field-block">
|
||||
<span>Folder name</span>
|
||||
<FormField label="Folder name" help="Create a folder below the selected destination.">
|
||||
<input autoFocus value={newFolderName} placeholder="e.g. invoices" onChange={(event) => { setNewFolderName(event.target.value); setNewFolderError(""); }} onKeyDown={(event) => { if (event.key === "Enter") void handleCreateFolder(); }} />
|
||||
<small>Created inside {activeDialogTarget?.folderPath || "Root"}.</small>
|
||||
{newFolderError && <small className="field-error">{newFolderError}</small>}
|
||||
</label>
|
||||
</FormField>
|
||||
<div className="button-row compact-actions align-end">
|
||||
<Button onClick={closeDialog} disabled={busy}>Cancel</Button>
|
||||
<Button variant="primary" onClick={() => void handleCreateFolder()} disabled={busy || !newFolderName.trim()}>Create Folder</Button>
|
||||
@@ -1343,14 +1344,13 @@ export default function FilesPage({ settings }: { settings: ApiSettings }) {
|
||||
<FileDialog title={`${transferMode === "copy" ? "Copy" : "Move"} selection`} onClose={closeDialog}>
|
||||
<p className="muted">Choose a destination space and folder. Folders are transferred recursively.</p>
|
||||
<div className="form-grid two-column-form-grid">
|
||||
<label className="field-block">
|
||||
<span>Destination space</span>
|
||||
<FormField label="Destination space" help="Select the space that will receive the selected file(s) or folder(s).">
|
||||
<select value={transferDialogState.targetSpaceId} onChange={(event) => setTransferDialogState((current) => current ? { ...current, targetSpaceId: event.target.value, targetFolder: "" } : current)}>
|
||||
{spaces.map((space) => <option key={space.id} value={space.id}>{space.label}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
</FormField>
|
||||
<div className="field-block">
|
||||
<span>Destination folder</span>
|
||||
<FieldLabel className="form-label" help="Choose the target folder. Folders are transferred recursively.">Destination folder</FieldLabel>
|
||||
<TransferFolderSelector
|
||||
space={findSpace(transferDialogState.targetSpaceId)}
|
||||
nodes={buildFolderTree(filesBySpace[transferDialogState.targetSpaceId] ?? EMPTY_FILES, foldersBySpace[transferDialogState.targetSpaceId] ?? EMPTY_FOLDERS)}
|
||||
@@ -1369,11 +1369,9 @@ export default function FilesPage({ settings }: { settings: ApiSettings }) {
|
||||
|
||||
{dialog === "single-rename" && (
|
||||
<FileDialog title="Rename item" onClose={closeDialog}>
|
||||
<label className="field-block">
|
||||
<span>New name</span>
|
||||
<FormField label="New name" help="Only the visible name changes. Immutable blobs stay stable for audit.">
|
||||
<input autoFocus value={singleRenameName} onChange={(event) => setSingleRenameName(event.target.value)} onKeyDown={(event) => { if (event.key === "Enter") void runSingleRename(); }} />
|
||||
<small>Only the visible name changes. Immutable blobs stay stable for audit.</small>
|
||||
</label>
|
||||
</FormField>
|
||||
<div className="button-row compact-actions align-end">
|
||||
<Button onClick={closeDialog} disabled={busy}>Cancel</Button>
|
||||
<Button variant="primary" onClick={() => void runSingleRename()} disabled={busy || !singleRenameName.trim()}>Rename</Button>
|
||||
@@ -1385,17 +1383,16 @@ export default function FilesPage({ settings }: { settings: ApiSettings }) {
|
||||
<FileDialog title="Bulk rename selected items" onClose={closeDialog}>
|
||||
<p className="muted">Bulk rename changes managed display paths only. Immutable blobs stay stable for audit.</p>
|
||||
<div className="form-grid two-column-form-grid">
|
||||
<label className="field-block">
|
||||
<span>Mode</span>
|
||||
<FormField label="Mode" help="Choose how the selected names should be changed.">
|
||||
<select value={renameMode} onChange={(event) => setRenameMode(event.target.value as RenameMode)}>
|
||||
<option value="prefix">Add prefix</option>
|
||||
<option value="suffix">Add suffix before extension</option>
|
||||
<option value="replace">Find and replace</option>
|
||||
</select>
|
||||
</label>
|
||||
{renameMode === "prefix" && <label className="field-block"><span>Prefix</span><input value={renamePrefix} onChange={(event) => setRenamePrefix(event.target.value)} /></label>}
|
||||
{renameMode === "suffix" && <label className="field-block"><span>Suffix</span><input value={renameSuffix} onChange={(event) => setRenameSuffix(event.target.value)} /></label>}
|
||||
{renameMode === "replace" && <><label className="field-block"><span>Find</span><input value={renameFind} onChange={(event) => setRenameFind(event.target.value)} /></label><label className="field-block"><span>Replacement</span><input value={renameReplacement} onChange={(event) => setRenameReplacement(event.target.value)} /></label></>}
|
||||
</FormField>
|
||||
{renameMode === "prefix" && <FormField label="Prefix"><input value={renamePrefix} onChange={(event) => setRenamePrefix(event.target.value)} /></FormField>}
|
||||
{renameMode === "suffix" && <FormField label="Suffix"><input value={renameSuffix} onChange={(event) => setRenameSuffix(event.target.value)} /></FormField>}
|
||||
{renameMode === "replace" && <><FormField label="Find"><input value={renameFind} onChange={(event) => setRenameFind(event.target.value)} /></FormField><FormField label="Replacement"><input value={renameReplacement} onChange={(event) => setRenameReplacement(event.target.value)} /></FormField></>}
|
||||
</div>
|
||||
{selectedFolderPaths.size > 0 && (
|
||||
<ToggleSwitch label="Apply recursively to folder contents" checked={renameRecursive} onChange={setRenameRecursive} disabled={busy} />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { DragEvent as ReactDragEvent, MouseEvent as ReactMouseEvent, ReactNode } from "react";
|
||||
import { Copy, Download, Folder, FolderOpen, Home, MoveRight, Plus, Trash2, UploadCloud } from "lucide-react";
|
||||
import Button from "../../../components/Button";
|
||||
import Dialog from "../../../components/Dialog";
|
||||
import type { ConflictAction, FileSpace, RenameResponse } from "../../../api/files";
|
||||
import type { ConflictDialogState, FileActionTarget, FileConflictItem, FolderNode, ContextMenuState } from "../types";
|
||||
import { isPathUnderOrSame, normalizeFolder, treeNodeKey } from "../utils/fileManagerUtils";
|
||||
@@ -227,15 +228,18 @@ export function RenamePreviewList({
|
||||
|
||||
export function FileDialog({ title, onClose, children }: { title: string; onClose: () => void; children: ReactNode }) {
|
||||
return (
|
||||
<div className="file-dialog-backdrop" role="presentation" onMouseDown={(event) => { if (event.target === event.currentTarget) onClose(); }}>
|
||||
<div className="file-dialog" role="dialog" aria-modal="true" aria-label={title}>
|
||||
<div className="file-dialog-header">
|
||||
<h3>{title}</h3>
|
||||
<button type="button" onClick={onClose} aria-label="Close">×</button>
|
||||
</div>
|
||||
<div className="file-dialog-body">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
<Dialog
|
||||
open
|
||||
title={title}
|
||||
onClose={onClose}
|
||||
backdropClassName="file-dialog-backdrop"
|
||||
className="file-dialog"
|
||||
headerClassName="file-dialog-header"
|
||||
titleClassName="file-dialog-title"
|
||||
bodyClassName="file-dialog-body"
|
||||
>
|
||||
{children}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user