Files
WebFTP/components/FileActionModals.tsx

201 lines
8.5 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { FolderPlus, FilePenLine, Trash2, X, AlertTriangle } from 'lucide-react';
// --- Create Folder Modal ---
interface CreateFolderModalProps {
isOpen: boolean;
onClose: () => void;
onConfirm: (name: string) => void;
}
export const CreateFolderModal: React.FC<CreateFolderModalProps> = ({ isOpen, onClose, onConfirm }) => {
const [folderName, setFolderName] = useState('');
const [error, setError] = useState('');
useEffect(() => {
if (isOpen) {
setFolderName('');
setError('');
// Auto focus hack
setTimeout(() => document.getElementById('new-folder-input')?.focus(), 50);
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}
}, [isOpen, onClose]);
const handleConfirm = () => {
if (!folderName.trim()) {
setError('폴더 이름을 입력해주세요.');
return;
}
onConfirm(folderName);
};
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/20 backdrop-blur-sm p-4">
<div className="bg-white border border-slate-200 rounded-lg shadow-xl w-full max-w-sm animate-in fade-in zoom-in-95 duration-200">
<div className="p-3 border-b border-slate-100 flex justify-between items-center bg-slate-50 rounded-t-lg">
<h3 className="text-sm font-bold text-slate-800 flex items-center gap-2">
<FolderPlus size={16} className="text-blue-500" />
</h3>
<button onClick={onClose}><X size={16} className="text-slate-400 hover:text-slate-600" /></button>
</div>
<div className="p-4 space-y-4">
<div>
<label className="block text-xs text-slate-500 mb-1"> :</label>
<input
id="new-folder-input"
type="text"
value={folderName}
onChange={(e) => { setFolderName(e.target.value); setError(''); }}
onKeyDown={(e) => e.key === 'Enter' && handleConfirm()}
className={`w-full bg-white border rounded px-3 py-2 text-sm text-slate-800 focus:outline-none ${error ? 'border-red-500 focus:border-red-500' : 'border-slate-300 focus:border-blue-500'}`}
/>
{error && <p className="text-xs text-red-500 mt-1">{error}</p>}
</div>
<div className="flex justify-end gap-2">
<button onClick={onClose} className="px-3 py-1.5 text-xs text-slate-600 hover:text-slate-900 bg-slate-100 hover:bg-slate-200 rounded"></button>
<button onClick={handleConfirm} className="px-3 py-1.5 text-xs bg-blue-600 hover:bg-blue-500 text-white rounded shadow-md shadow-blue-500/20"></button>
</div>
</div>
</div>
</div>
);
};
// --- Rename Modal ---
interface RenameModalProps {
isOpen: boolean;
currentName: string;
onClose: () => void;
onConfirm: (newName: string) => void;
}
export const RenameModal: React.FC<RenameModalProps> = ({ isOpen, currentName, onClose, onConfirm }) => {
const [newName, setNewName] = useState('');
const [error, setError] = useState('');
useEffect(() => {
if (isOpen) {
setNewName(currentName);
setError('');
setTimeout(() => document.getElementById('rename-input')?.focus(), 50);
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}
}, [isOpen, currentName, onClose]);
const handleConfirm = () => {
if (!newName.trim()) {
setError('새 이름을 입력해주세요.');
return;
}
if (newName.trim() === currentName) {
setError('변경된 내용이 없습니다.');
return;
}
onConfirm(newName);
};
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/20 backdrop-blur-sm p-4">
<div className="bg-white border border-slate-200 rounded-lg shadow-xl w-full max-w-sm animate-in fade-in zoom-in-95 duration-200">
<div className="p-3 border-b border-slate-100 flex justify-between items-center bg-slate-50 rounded-t-lg">
<h3 className="text-sm font-bold text-slate-800 flex items-center gap-2">
<FilePenLine size={16} className="text-yellow-500" /> /
</h3>
<button onClick={onClose}><X size={16} className="text-slate-400 hover:text-slate-600" /></button>
</div>
<div className="p-4 space-y-4">
<div>
<label className="block text-xs text-slate-500 mb-1"> :</label>
<div className="text-sm text-slate-600 bg-slate-50 p-2 rounded border border-slate-200 mb-3 select-all">{currentName}</div>
<label className="block text-xs text-slate-500 mb-1"> :</label>
<input
id="rename-input"
type="text"
value={newName}
onChange={(e) => { setNewName(e.target.value); setError(''); }}
onKeyDown={(e) => e.key === 'Enter' && handleConfirm()}
className={`w-full bg-white border rounded px-3 py-2 text-sm text-slate-800 focus:outline-none ${error ? 'border-red-500 focus:border-red-500' : 'border-slate-300 focus:border-blue-500'}`}
/>
{error && <p className="text-xs text-red-500 mt-1">{error}</p>}
</div>
<div className="flex justify-end gap-2">
<button onClick={onClose} className="px-3 py-1.5 text-xs text-slate-600 hover:text-slate-900 bg-slate-100 hover:bg-slate-200 rounded"></button>
<button onClick={handleConfirm} className="px-3 py-1.5 text-xs bg-blue-600 hover:bg-blue-500 text-white rounded shadow-md shadow-blue-500/20"></button>
</div>
</div>
</div>
</div>
);
};
// --- Delete Modal ---
interface DeleteModalProps {
isOpen: boolean;
fileCount: number;
fileNames: string[];
onClose: () => void;
onConfirm: () => void;
}
export const DeleteModal: React.FC<DeleteModalProps> = ({ isOpen, fileCount, fileNames, onClose, onConfirm }) => {
useEffect(() => {
if (isOpen) {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}
}, [isOpen, onClose]);
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/20 backdrop-blur-sm p-4">
<div className="bg-white border border-slate-200 rounded-lg shadow-xl w-full max-w-sm animate-in fade-in zoom-in-95 duration-200">
<div className="p-3 border-b border-slate-100 flex justify-between items-center bg-slate-50 rounded-t-lg">
<h3 className="text-sm font-bold text-slate-800 flex items-center gap-2">
<Trash2 size={16} className="text-red-500" />
</h3>
<button onClick={onClose}><X size={16} className="text-slate-400 hover:text-slate-600" /></button>
</div>
<div className="p-4 space-y-4">
<div className="flex gap-3">
<div className="bg-red-50 p-2 rounded h-fit shrink-0">
<AlertTriangle size={24} className="text-red-500" />
</div>
<div className="text-sm text-slate-600 min-w-0">
<p className="mb-2"> {fileCount} ?</p>
<ul className="list-disc list-inside text-xs text-slate-500 max-h-32 overflow-y-auto bg-slate-50 p-2 rounded border border-slate-200 mb-2">
{fileNames.map((name, i) => (
<li key={i} className="truncate">{name}</li>
))}
</ul>
<p className="text-xs text-red-500 font-semibold"> .</p>
</div>
</div>
<div className="flex justify-end gap-2">
<button onClick={onClose} className="px-3 py-1.5 text-xs text-slate-600 hover:text-slate-900 bg-slate-100 hover:bg-slate-200 rounded"></button>
<button onClick={onConfirm} className="px-3 py-1.5 text-xs bg-red-600 hover:bg-red-500 text-white rounded shadow-md shadow-red-500/20"></button>
</div>
</div>
</div>
</div>
);
};