packages/interface/src/components/modals/FileOperationModal.README.md
The File Operation Modal provides a clean, interactive UI for copying and moving files with validation-based confirmations. It's designed to work with the core validation system and can be extended to show simulation previews in the future.
import { useFileOperationDialog } from '@sd/interface';
function MyComponent() {
const openFileOperation = useFileOperationDialog();
const handleCopyFiles = () => {
openFileOperation({
operation: "copy",
sources: [
{ Physical: { device_slug: "", path: "/source/file1.txt" } },
{ Physical: { device_slug: "", path: "/source/file2.txt" } },
],
destination: { Physical: { device_slug: "", path: "/destination/" } },
onComplete: () => {
console.log("Operation completed!");
},
});
};
return <button onClick={handleCopyFiles}>Copy Files</button>;
}
The LocationsSection component already has drag-drop enabled:
// Just drop files onto a location in the sidebar
// The modal will automatically open with validation
Use DropZoneFile for folders that can receive drops:
import { DropZoneFile, useFileOperationDialog } from '@sd/interface';
function FolderGrid({ folders, selectedFiles }) {
const openFileOperation = useFileOperationDialog();
const handleFilesDropped = (
sources: SdPath[],
destination: SdPath,
operation: "copy" | "move"
) => {
openFileOperation({
operation,
sources,
destination,
});
};
return (
<div className="grid grid-cols-4 gap-4">
{folders.map((folder) => (
<DropZoneFile
key={folder.id}
file={folder}
selectedFiles={selectedFiles}
onFilesDropped={handleFilesDropped}
>
<File.Thumb file={folder} />
<File.Title file={folder} />
</DropZoneFile>
))}
</div>
);
}
The File component is already draggable by default:
import { File } from '@sd/interface';
// Files are draggable by default
<File file={file} selectedFiles={allSelectedFiles}>
<File.Thumb file={file} />
<File.Title file={file} />
</File>
// Disable dragging if needed
<File file={file} draggable={false}>
...
</File>
Drag behavior:
Shows a spinner while checking for conflicts and validating the operation.
Displays conflict resolution options when destination files exist:
When simulation is implemented, this phase will show:
Shows progress bar and current operation status.
Brief success message before auto-closing.
Displays error message with option to close.
The modal integrates with the core validation system:
ValidationResultresolve_confirmation(choice_index)When simulation is added, the flow will be:
The modal is designed to smoothly add simulation data without breaking the current flow.
The modal uses semantic Tailwind classes:
bg-app-box / bg-app for backgroundstext-ink / text-ink-dull / text-ink-faint for text hierarchyborder-app-line for bordersbg-accent / text-accent for primary actionsbg-yellow-500/10, bg-green-500, bg-red-500/10// Drag files from explorer onto a location in sidebar
// Modal opens automatically with validation
// Hold Alt/Option and drag files onto a folder
// Modal opens with move operation
const openFileOp = useFileOperationDialog();
openFileOp({
operation: "copy",
sources: selectedFiles.map(f => f.sd_path),
destination: folderPath,
});
FileOperationModal
├── Uses: useLibraryMutation('files.copy')
├── State: DialogPhase (validating → confirmation → ready → executing → completed)
├── Validation: Integrates with core ValidationResult
└── Future: Will display SimulationResult when available
DropZoneFile (for folders)
├── Uses: useGlobalFileDrag hook
├── Detects: Folder kind only
├── Visual: Ring/border on drag over
└── Triggers: FileOperationModal on drop
DropZoneSidebarItem (for locations)
├── Uses: useGlobalFileDrag hook
├── Accepts: Any file drops
├── Visual: Ring/border on drag over
└── Triggers: FileOperationModal on drop
File (base component)
├── Draggable: By default
├── Multi-select: Drags all selected files
└── Operation: Copy (default) or Move (Alt/Option)
validate endpoint when available