- Docs/
- Components/
- file-uploader
Drag and drop a file or click to select
npm i framer-motion lucid-react
import React, { useState, useRef } from 'react'; import { motion } from 'framer-motion'; import { FileIcon, UploadCloudIcon } from 'lucide-react'; import { cn } from "@/lib/utils" const FileUploader = ({ onFileSelect, onUploadProgress, accept = '*', maxSize = Infinity, className = '', iconSize = 'w-16 h-16', iconColor = 'text-teal-500', progressColor = 'bg-teal-500', progressBgColor = 'bg-teal-200', }) => { const [file, setFile] = useState(null); const [progress, setProgress] = useState(0); const [error, setError] = useState(''); const fileInputRef = useRef(null); const handleFileChange = (event) => { const selectedFile = event.target.files[0]; if (selectedFile && validateFile(selectedFile)) { setFile(selectedFile); setError(''); onFileSelect(selectedFile); simulateUpload(); } }; const validateFile = (file) => { if (file.size > maxSize) { setError(`File size should not exceed ${formatBytes(maxSize)}`); return false; } return true; }; const simulateUpload = () => { let progress = 0; const interval = setInterval(() => { progress += 10; setProgress(progress); onUploadProgress(progress); if (progress >= 100) { clearInterval(interval); } }, 500); }; const handleDragOver = (event) => { event.preventDefault(); }; const handleDrop = (event) => { event.preventDefault(); const droppedFile = event.dataTransfer.files[0]; if (validateFile(droppedFile)) { setFile(droppedFile); setError(''); onFileSelect(droppedFile); simulateUpload(); } }; const formatBytes = (bytes, decimals = 2) => { if (bytes === 0) return '0 Bytes'; const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; }; return ( <div className={cn("w-full max-w-md mx-auto mt-10", className)}> <div className="border-2 border-dashed border-neutral-600 rounded-lg p-6 cursor-pointer" onClick={()=> fileInputRef.current.click()} onDragOver={handleDragOver} onDrop={handleDrop} > <input type="file" ref={fileInputRef} onChange={handleFileChange} accept={accept} className="hidden" /> <div className="flex flex-col items-center"> {file ? ( <FileIcon className={`${iconSize} ${iconColor} mb-4`} /> ) : ( <UploadCloudIcon className={`${iconSize} text-gray-400 mb-4`} /> )} <p className="text-center text-sm text-neutral-400"> {file ? file.name : "Drag and drop a file or click to select"} </p> {error && <p className="text-red-500 mt-2">{error}</p>} </div> </div> {file && ( <div className="mt-4 w-full"> <div className="relative pt-1"> <div className="flex mb-2 items-center justify-between"> <div> <span className={`text-xs font-semibold border border-emerald-600 inline-block py-1 px-2 uppercase rounded-full ${iconColor} ${progressBgColor}`}> Uploading </span> </div> <div className="text-right"> <span className={`text-xs font-semibold inline-block ${iconColor}`}> {progress}% </span> </div> </div> <div className={`overflow-hidden h-2 mb-4 text-xs flex rounded ${progressBgColor}`}> <motion.div className={`shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center ${progressColor}`} initial={{ width: 0 }} animate={{ width: `${progress}%` }} transition={{ duration: 0.5 }} ></motion.div> </div> </div> </div> )} </div> ); }; export default FileUploader;