import { Box } from '@/ui/Box'; import { Icon } from '@/ui/Icon'; import { IconButton } from '@/ui/IconButton'; import { ListItem, ListItemActions, ListItemInfo } from '@/ui/ListItem'; import { Surface } from '@/ui/Surface'; import { Text } from '@/ui/Text'; import { AlertCircle, CheckCircle2, 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()} style={{ border: `2px dashed ${isDragging ? 'var(--ui-color-intent-primary)' : (error ? 'var(--ui-color-intent-critical)' : 'var(--ui-color-border-default)')}`, backgroundColor: isDragging ? 'rgba(25, 140, 255, 0.05)' : 'var(--ui-color-bg-surface-muted)', textAlign: 'center', cursor: 'pointer' }} > 0 ? CheckCircle2 : Upload)} size={10} intent={isDragging ? 'primary' : (error ? 'critical' : 'low')} animate={isLoading ? 'pulse' : 'none'} /> {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) => ( { e.stopPropagation(); removeFile(index); }} title="Remove" /> ))} )} ); }