Skip to content

Commit 430a1bf

Browse files
committed
feat(ui): add FileUpload drag-and-drop component
1 parent 6035eb9 commit 430a1bf

1 file changed

Lines changed: 30 additions & 0 deletions

File tree

src/components/ui/FileUpload.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React, { useRef, useState } from 'react';
2+
import { FaCloudUploadAlt, FaTimes, FaFile } from 'react-icons/fa';
3+
interface Props { onUpload: (files: File[]) => void; accept?: string; multiple?: boolean; maxSize?: number; }
4+
const FileUpload: React.FC<Props> = ({ onUpload, accept, multiple = false, maxSize = 10 * 1024 * 1024 }) => {
5+
const inputRef = useRef<HTMLInputElement>(null);
6+
const [dragActive, setDragActive] = useState(false);
7+
const [files, setFiles] = useState<File[]>([]);
8+
const handleFiles = (fileList: FileList) => { const valid = Array.from(fileList).filter(f => f.size <= maxSize); setFiles(prev => [...prev, ...valid]); onUpload(valid); };
9+
const removeFile = (index: number) => setFiles(f => f.filter((_, i) => i !== index));
10+
return (
11+
<div>
12+
<div onClick={() => inputRef.current?.click()} onDragOver={e => { e.preventDefault(); setDragActive(true); }} onDragLeave={() => setDragActive(false)}
13+
onDrop={e => { e.preventDefault(); setDragActive(false); handleFiles(e.dataTransfer.files); }}
14+
className={'border-2 border-dashed rounded-2xl p-8 text-center cursor-pointer transition-colors ' + (dragActive ? 'border-blue-500 bg-blue-50' : 'border-gray-300 dark:border-gray-600 hover:border-blue-400')}>
15+
<FaCloudUploadAlt className='text-4xl text-gray-400 mx-auto mb-3' />
16+
<p className='text-sm text-gray-600 dark:text-gray-400'>Drag & drop or click to upload</p>
17+
<p className='text-xs text-gray-400 mt-1'>Max size: {Math.round(maxSize / 1024 / 1024)}MB</p>
18+
<input ref={inputRef} type='file' accept={accept} multiple={multiple} onChange={e => e.target.files && handleFiles(e.target.files)} className='hidden' />
19+
</div>
20+
{files.length > 0 && <div className='mt-3 space-y-2'>{files.map((f, i) => (
21+
<div key={i} className='flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-800 rounded-lg text-sm'>
22+
<FaFile className='text-gray-400' /><span className='flex-1 truncate'>{f.name}</span>
23+
<span className='text-gray-400 text-xs'>{(f.size / 1024).toFixed(1)}KB</span>
24+
<button onClick={() => removeFile(i)}><FaTimes className='text-red-400' /></button>
25+
</div>
26+
))}</div>}
27+
</div>
28+
);
29+
};
30+
export default FileUpload;

0 commit comments

Comments
 (0)