Spaces:
Running
Running
| import React, { useState } from "react"; | |
| import { motion, AnimatePresence } from "framer-motion"; | |
| import { Upload, FileText, Image, FileSpreadsheet, X, Sparkles } from "lucide-react"; | |
| import { cn } from "@/lib/utils"; | |
| export default function UploadZone({ onFileSelect, selectedFile, onClear }) { | |
| const [isDragging, setIsDragging] = useState(false); | |
| const handleDragOver = (e) => { | |
| e.preventDefault(); | |
| setIsDragging(true); | |
| }; | |
| const handleDragLeave = () => { | |
| setIsDragging(false); | |
| }; | |
| const handleDrop = (e) => { | |
| e.preventDefault(); | |
| setIsDragging(false); | |
| const file = e.dataTransfer.files[0]; | |
| if (file) onFileSelect(file); | |
| }; | |
| const getFileIcon = (type) => { | |
| if (type?.includes("image")) return Image; | |
| if (type?.includes("spreadsheet") || type?.includes("excel")) return FileSpreadsheet; | |
| return FileText; | |
| }; | |
| const FileIcon = selectedFile ? getFileIcon(selectedFile.type) : FileText; | |
| return ( | |
| <div className="w-full"> | |
| <AnimatePresence mode="wait"> | |
| {!selectedFile ? ( | |
| <motion.div | |
| key="upload" | |
| initial={{ opacity: 0, y: 10 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| exit={{ opacity: 0, y: -10 }} | |
| transition={{ duration: 0.2 }} | |
| onDragOver={handleDragOver} | |
| onDragLeave={handleDragLeave} | |
| onDrop={handleDrop} | |
| className={cn( | |
| "relative group cursor-pointer", | |
| "border-2 border-dashed rounded-2xl", | |
| "transition-all duration-300 ease-out", | |
| isDragging | |
| ? "border-indigo-400 bg-indigo-50/50" | |
| : "border-slate-200 hover:border-indigo-300 hover:bg-slate-50/50" | |
| )} | |
| > | |
| <label className="flex flex-col items-center justify-center py-16 px-8 cursor-pointer"> | |
| <motion.div | |
| animate={isDragging ? { scale: 1.1, y: -5 } : { scale: 1, y: 0 }} | |
| className={cn( | |
| "h-16 w-16 rounded-2xl flex items-center justify-center mb-6 transition-colors duration-300", | |
| isDragging | |
| ? "bg-indigo-100" | |
| : "bg-gradient-to-br from-slate-100 to-slate-50 group-hover:from-indigo-100 group-hover:to-violet-50" | |
| )} | |
| > | |
| <Upload | |
| className={cn( | |
| "h-7 w-7 transition-colors duration-300", | |
| isDragging ? "text-indigo-600" : "text-slate-400 group-hover:text-indigo-500" | |
| )} | |
| /> | |
| </motion.div> | |
| <div className="text-center"> | |
| <p className="text-lg font-semibold text-slate-700 mb-1"> | |
| {isDragging ? "Drop your file here" : "Drop your file here, or browse"} | |
| </p> | |
| <p className="text-sm text-slate-400"> | |
| Supports PDF, PNG, JPG, TIFF, DOCX up to 50MB | |
| </p> | |
| </div> | |
| <div className="flex items-center gap-2 mt-6"> | |
| <div className="flex -space-x-1"> | |
| {[ | |
| "bg-red-100 text-red-600", | |
| "bg-blue-100 text-blue-600", | |
| "bg-green-100 text-green-600", | |
| "bg-amber-100 text-amber-600", | |
| ].map((color, i) => ( | |
| <div | |
| key={i} | |
| className={`h-8 w-8 rounded-lg ${color.split(" ")[0]} flex items-center justify-center border-2 border-white`} | |
| > | |
| <FileText className={`h-4 w-4 ${color.split(" ")[1]}`} /> | |
| </div> | |
| ))} | |
| </div> | |
| <span className="text-xs text-slate-400 ml-2">Multiple formats supported</span> | |
| </div> | |
| <input | |
| type="file" | |
| className="hidden" | |
| accept=".pdf,.png,.jpg,.jpeg,.tiff,.docx,.xlsx" | |
| onChange={(e) => e.target.files[0] && onFileSelect(e.target.files[0])} | |
| /> | |
| </label> | |
| {/* Decorative gradient border on hover */} | |
| <div className="absolute inset-0 -z-10 rounded-2xl bg-gradient-to-r from-indigo-500 via-violet-500 to-purple-500 opacity-0 group-hover:opacity-10 blur-xl transition-opacity duration-500" /> | |
| </motion.div> | |
| ) : ( | |
| <motion.div | |
| key="selected" | |
| initial={{ opacity: 0, scale: 0.95 }} | |
| animate={{ opacity: 1, scale: 1 }} | |
| exit={{ opacity: 0, scale: 0.95 }} | |
| className="relative bg-gradient-to-br from-indigo-50 to-violet-50 rounded-xl p-3 border border-indigo-100" | |
| > | |
| <div className="flex items-center gap-3"> | |
| <div className="h-10 w-10 rounded-lg bg-white shadow-sm flex items-center justify-center flex-shrink-0"> | |
| <FileIcon className="h-5 w-5 text-indigo-600" /> | |
| </div> | |
| <div className="flex-1 min-w-0"> | |
| <p className="font-medium text-slate-800 truncate text-sm">{selectedFile.name}</p> | |
| <div className="flex items-center gap-2 text-xs text-slate-500"> | |
| <span>{(selectedFile.size / 1024 / 1024).toFixed(2)} MB</span> | |
| <span className="text-indigo-500">•</span> | |
| <span className="text-indigo-600 flex items-center gap-1"> | |
| <Sparkles className="h-3 w-3" /> | |
| Ready for extraction | |
| </span> | |
| </div> | |
| </div> | |
| <button | |
| onClick={onClear} | |
| className="h-8 w-8 rounded-lg bg-white hover:bg-red-50 border border-slate-200 hover:border-red-200 flex items-center justify-center text-slate-400 hover:text-red-500 transition-colors" | |
| > | |
| <X className="h-4 w-4" /> | |
| </button> | |
| </div> | |
| </motion.div> | |
| )} | |
| </AnimatePresence> | |
| </div> | |
| ); | |
| } | |