198 lines
5.8 KiB
TypeScript
198 lines
5.8 KiB
TypeScript
import { useEffect, useRef, useState } from 'react';
|
|
import { FolderPlus, Upload, FolderUp, Scissors, Copy, ClipboardPaste, Trash2, X, Check } from 'lucide-react';
|
|
|
|
type ToolbarProps = {
|
|
onCreateDir: (name: string) => void;
|
|
onUpload: (files: FileList) => void;
|
|
selectionCount: number;
|
|
hasClipboard: boolean;
|
|
onCut: () => void;
|
|
onCopy: () => void;
|
|
onPaste: () => void;
|
|
onDeleteSelected: () => void;
|
|
onClearSelection: () => void;
|
|
};
|
|
|
|
export const Toolbar = ({
|
|
onCreateDir,
|
|
onUpload,
|
|
selectionCount,
|
|
hasClipboard,
|
|
onCut,
|
|
onCopy,
|
|
onPaste,
|
|
onDeleteSelected,
|
|
onClearSelection,
|
|
}: ToolbarProps) => {
|
|
const [showInput, setShowInput] = useState(false);
|
|
const [folderName, setFolderName] = useState('');
|
|
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
|
const folderInputRef = useRef<HTMLInputElement | null>(null);
|
|
|
|
useEffect(() => {
|
|
const input = folderInputRef.current;
|
|
if (!input) return;
|
|
input.setAttribute('webkitdirectory', '');
|
|
}, []);
|
|
|
|
const handleCreate = () => {
|
|
const name = folderName.trim();
|
|
if (!name) return;
|
|
onCreateDir(name);
|
|
setFolderName('');
|
|
setShowInput(false);
|
|
};
|
|
|
|
if (selectionCount > 0) {
|
|
return (
|
|
<div className="flex items-center gap-1">
|
|
<span className="text-sm font-medium text-duck-dark/70 mr-1">
|
|
{selectionCount}
|
|
<span className="hidden md:inline"> selected</span>
|
|
</span>
|
|
<button
|
|
onClick={onCut}
|
|
title="Cut"
|
|
className="p-1.5 rounded-md text-duck-dark/50 hover:bg-duck-dark/5 cursor-pointer transition-colors"
|
|
>
|
|
<Scissors className="h-4 w-4" />
|
|
</button>
|
|
<button
|
|
onClick={onCopy}
|
|
title="Copy"
|
|
className="p-1.5 rounded-md text-duck-dark/50 hover:bg-duck-dark/5 cursor-pointer transition-colors"
|
|
>
|
|
<Copy className="h-4 w-4" />
|
|
</button>
|
|
{hasClipboard && (
|
|
<button
|
|
onClick={onPaste}
|
|
title="Paste"
|
|
className="p-1.5 rounded-md text-duck-dark/50 hover:bg-duck-dark/5 cursor-pointer transition-colors"
|
|
>
|
|
<ClipboardPaste className="h-4 w-4" />
|
|
</button>
|
|
)}
|
|
<button
|
|
onClick={onDeleteSelected}
|
|
title="Delete"
|
|
className="p-1.5 rounded-md text-red-500 hover:bg-red-50 cursor-pointer transition-colors"
|
|
>
|
|
<Trash2 className="h-4 w-4" />
|
|
</button>
|
|
<button
|
|
onClick={onClearSelection}
|
|
title="Clear selection"
|
|
className="p-1.5 rounded-md text-duck-dark/50 hover:bg-duck-dark/5 cursor-pointer transition-colors"
|
|
>
|
|
<X className="h-4 w-4" />
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="flex items-center gap-1">
|
|
{showInput ? (
|
|
<form
|
|
onSubmit={(ev) => {
|
|
ev.preventDefault();
|
|
handleCreate();
|
|
}}
|
|
className="flex items-center gap-2"
|
|
>
|
|
<input
|
|
autoFocus
|
|
value={folderName}
|
|
onChange={(ev) => setFolderName(ev.target.value)}
|
|
placeholder="Folder name"
|
|
className="h-8 w-40 text-sm rounded-md border border-duck-dark/20 bg-white/60 text-duck-dark placeholder:text-duck-dark/40 outline-none focus:border-duck-teal/50 px-2"
|
|
onKeyDown={(ev) => {
|
|
if (ev.key === 'Escape') {
|
|
setShowInput(false);
|
|
setFolderName('');
|
|
}
|
|
}}
|
|
/>
|
|
<button
|
|
type="submit"
|
|
className="p-1.5 rounded-md text-duck-teal hover:bg-duck-dark/5 cursor-pointer transition-colors"
|
|
title="Create"
|
|
>
|
|
<Check className="h-4 w-4" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
setShowInput(false);
|
|
setFolderName('');
|
|
}}
|
|
className="p-1.5 rounded-md text-duck-dark/50 hover:bg-duck-dark/5 cursor-pointer transition-colors"
|
|
title="Cancel"
|
|
>
|
|
<X className="h-4 w-4" />
|
|
</button>
|
|
</form>
|
|
) : (
|
|
<button
|
|
onClick={() => setShowInput(true)}
|
|
title="New folder"
|
|
className="p-1.5 rounded-md text-duck-dark/50 hover:bg-duck-dark/5 cursor-pointer transition-colors"
|
|
>
|
|
<FolderPlus className="h-4 w-4" />
|
|
</button>
|
|
)}
|
|
|
|
<button
|
|
onClick={() => fileInputRef.current?.click()}
|
|
title="Upload files"
|
|
className="p-1.5 rounded-md text-duck-dark/50 hover:bg-duck-dark/5 cursor-pointer transition-colors"
|
|
>
|
|
<Upload className="h-4 w-4" />
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => folderInputRef.current?.click()}
|
|
title="Upload folder"
|
|
className="p-1.5 rounded-md text-duck-dark/50 hover:bg-duck-dark/5 cursor-pointer transition-colors"
|
|
>
|
|
<FolderUp className="h-4 w-4" />
|
|
</button>
|
|
|
|
{hasClipboard && (
|
|
<button
|
|
onClick={onPaste}
|
|
title="Paste"
|
|
className="p-1.5 rounded-md text-duck-dark/50 hover:bg-duck-dark/5 cursor-pointer transition-colors"
|
|
>
|
|
<ClipboardPaste className="h-4 w-4" />
|
|
</button>
|
|
)}
|
|
|
|
<input
|
|
ref={fileInputRef}
|
|
type="file"
|
|
multiple
|
|
className="hidden"
|
|
onChange={(ev) => {
|
|
if (ev.target.files?.length) {
|
|
onUpload(ev.target.files);
|
|
ev.target.value = '';
|
|
}
|
|
}}
|
|
/>
|
|
<input
|
|
ref={folderInputRef}
|
|
type="file"
|
|
className="hidden"
|
|
onChange={(ev) => {
|
|
if (ev.target.files?.length) {
|
|
onUpload(ev.target.files);
|
|
ev.target.value = '';
|
|
}
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|