import { Button } from '@/ui/Button'; import { Icon } from '@/ui/Icon'; import { Box } from '@/ui/primitives/Box'; import { Text } from '@/ui/Text'; import { AlertCircle, CheckCircle2, File, Upload, X } from 'lucide-react'; import React, { useRef, useState } from 'react'; export interface UploadDropzoneProps { onFilesSelected: (files: File[]) => void; accept?: string; multiple?: boolean; maxSize?: number; // in bytes isLoading?: boolean; error?: string; } export function UploadDropzone({ onFilesSelected, accept, multiple = false, maxSize, isLoading, error, }: UploadDropzoneProps) { const [isDragging, setIsDragging] = useState(false); const [selectedFiles, setSelectedFiles] = useState([]); const fileInputRef = useRef(null); const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(true); }; const handleDragLeave = () => { setIsDragging(false); }; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); const files = Array.from(e.dataTransfer.files); validateAndSelectFiles(files); }; const handleFileSelect = (e: React.ChangeEvent) => { if (e.target.files) { const files = Array.from(e.target.files); validateAndSelectFiles(files); } }; const validateAndSelectFiles = (files: File[]) => { let filteredFiles = files; if (accept) { const acceptedTypes = accept.split(',').map(t => t.trim()); filteredFiles = filteredFiles.filter(file => { return acceptedTypes.some(type => { if (type.startsWith('.')) { return file.name.endsWith(type); } if (type.endsWith('/*')) { return file.type.startsWith(type.replace('/*', '')); } return file.type === type; }); }); } if (maxSize) { filteredFiles = filteredFiles.filter(file => file.size <= maxSize); } if (!multiple) { filteredFiles = filteredFiles.slice(0, 1); } setSelectedFiles(filteredFiles); onFilesSelected(filteredFiles); }; const removeFile = (index: number) => { const newFiles = [...selectedFiles]; newFiles.splice(index, 1); setSelectedFiles(newFiles); onFilesSelected(newFiles); }; return ( fileInputRef.current?.click()} display="flex" flexDirection="col" alignItems="center" justifyContent="center" p={8} border borderStyle="dashed" borderColor={isDragging ? 'border-blue-500' : error ? 'border-amber-500' : 'border-charcoal-outline'} bg={isDragging ? 'bg-blue-500/5' : 'bg-charcoal-outline/10'} rounded="xl" cursor="pointer" transition hoverBg="bg-charcoal-outline/20" > 0 ? CheckCircle2 : Upload)} size={10} color={isDragging ? 'text-blue-500' : error ? 'text-amber-500' : 'text-gray-500'} animate={isLoading ? 'pulse' : 'none'} mb={4} /> {isDragging ? 'Drop files here' : 'Click or drag to upload'} {accept ? `Accepted formats: ${accept}` : 'All file types accepted'} {maxSize && ` (Max ${Math.round(maxSize / 1024 / 1024)}MB)`} {error && ( {error} )} {selectedFiles.length > 0 && ( {selectedFiles.map((file, index) => ( {file.name} {Math.round(file.size / 1024)} KB ))} )} ); }