feat: Add Help System, Local File Operations, Site Manager improvements, and UI refinements
This commit is contained in:
@@ -9,15 +9,31 @@ interface CreateFolderModalProps {
|
||||
}
|
||||
|
||||
export const CreateFolderModal: React.FC<CreateFolderModalProps> = ({ isOpen, onClose, onConfirm }) => {
|
||||
const [folderName, setFolderName] = useState('새 폴더');
|
||||
const [folderName, setFolderName] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setFolderName('새 폴더');
|
||||
// Auto focus hack
|
||||
setTimeout(() => document.getElementById('new-folder-input')?.focus(), 50);
|
||||
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]);
|
||||
}, [isOpen, onClose]);
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (!folderName.trim()) {
|
||||
setError('폴더 이름을 입력해주세요.');
|
||||
return;
|
||||
}
|
||||
onConfirm(folderName);
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
@@ -33,18 +49,19 @@ export const CreateFolderModal: React.FC<CreateFolderModalProps> = ({ isOpen, on
|
||||
<div className="p-4 space-y-4">
|
||||
<div>
|
||||
<label className="block text-xs text-slate-500 mb-1">새 디렉토리 이름:</label>
|
||||
<input
|
||||
<input
|
||||
id="new-folder-input"
|
||||
type="text"
|
||||
type="text"
|
||||
value={folderName}
|
||||
onChange={(e) => setFolderName(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && onConfirm(folderName)}
|
||||
className="w-full bg-white border border-slate-300 rounded px-3 py-2 text-sm text-slate-800 focus:border-blue-500 focus:outline-none"
|
||||
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={() => onConfirm(folderName)} 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>
|
||||
<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>
|
||||
@@ -62,13 +79,33 @@ interface RenameModalProps {
|
||||
|
||||
export const RenameModal: React.FC<RenameModalProps> = ({ isOpen, currentName, onClose, onConfirm }) => {
|
||||
const [newName, setNewName] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setNewName(currentName);
|
||||
setTimeout(() => document.getElementById('rename-input')?.focus(), 50);
|
||||
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]);
|
||||
}, [isOpen, currentName, onClose]);
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (!newName.trim()) {
|
||||
setError('새 이름을 입력해주세요.');
|
||||
return;
|
||||
}
|
||||
if (newName.trim() === currentName) {
|
||||
setError('변경된 내용이 없습니다.');
|
||||
return;
|
||||
}
|
||||
onConfirm(newName);
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
@@ -85,20 +122,21 @@ export const RenameModal: React.FC<RenameModalProps> = ({ isOpen, currentName, o
|
||||
<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
|
||||
<input
|
||||
id="rename-input"
|
||||
type="text"
|
||||
type="text"
|
||||
value={newName}
|
||||
onChange={(e) => setNewName(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && onConfirm(newName)}
|
||||
className="w-full bg-white border border-slate-300 rounded px-3 py-2 text-sm text-slate-800 focus:border-blue-500 focus:outline-none"
|
||||
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={() => onConfirm(newName)} 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>
|
||||
<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>
|
||||
@@ -116,6 +154,16 @@ interface DeleteModalProps {
|
||||
}
|
||||
|
||||
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 (
|
||||
@@ -129,18 +177,18 @@ export const DeleteModal: React.FC<DeleteModalProps> = ({ isOpen, fileCount, fil
|
||||
</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 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>
|
||||
|
||||
Reference in New Issue
Block a user