import React, { useRef, useState } from 'react' import { AnimatePresence, motion, useReducedMotion } from 'framer-motion' function getExtension(fileName = '') { const parts = String(fileName).toLowerCase().split('.') return parts.length > 1 ? parts.pop() : '' } function detectPrimaryType(file) { if (!file) return 'unknown' const extension = getExtension(file.name) const mime = String(file.type || '').toLowerCase() const imageExt = new Set(['jpg', 'jpeg', 'png', 'webp']) const archiveExt = new Set(['zip', 'rar', '7z', 'tar', 'gz']) const imageMime = new Set(['image/jpeg', 'image/png', 'image/webp']) const archiveMime = new Set([ 'application/zip', 'application/x-zip-compressed', 'application/x-rar-compressed', 'application/vnd.rar', 'application/x-7z-compressed', 'application/x-tar', 'application/gzip', 'application/x-gzip', 'application/octet-stream', ]) if (imageMime.has(mime) || imageExt.has(extension)) return 'image' if (archiveMime.has(mime) || archiveExt.has(extension)) return 'archive' return 'unsupported' } export default function UploadDropzone({ title = 'Upload file', description = 'Drop file here or click to browse', fileName = '', fileHint = 'No file selected yet', previewUrl = '', fileMeta = null, errors = [], invalid = false, showLooksGood = false, looksGoodText = 'Looks good', locked = false, onPrimaryFileChange, onValidationResult, }) { const [dragging, setDragging] = useState(false) const inputRef = useRef(null) const prefersReducedMotion = useReducedMotion() const dragTransition = prefersReducedMotion ? { duration: 0 } : { duration: 0.2, ease: 'easeOut' } const emitFile = (file) => { const detectedType = detectPrimaryType(file) if (typeof onPrimaryFileChange === 'function') { onPrimaryFileChange(file, { detectedType }) } if (typeof onValidationResult === 'function') { onValidationResult({ file, detectedType }) } } return (
{/* Intended props: file, dragState, accept, onDrop, onBrowse, onReset, disabled */} { if (locked) return inputRef.current?.click() }} onKeyDown={(event) => { if (locked) return if (event.key === 'Enter' || event.key === ' ') { event.preventDefault() inputRef.current?.click() } }} onDragOver={(event) => { if (locked) return event.preventDefault() setDragging(true) }} onDragLeave={() => setDragging(false)} onDrop={(event) => { if (locked) return event.preventDefault() setDragging(false) const droppedFile = event.dataTransfer?.files?.[0] if (droppedFile) emitFile(droppedFile) }} animate={prefersReducedMotion ? undefined : { scale: dragging ? 1.01 : 1 }} transition={dragTransition} className={`group rounded-[26px] border-2 border-dashed border-white/15 px-5 py-7 text-center transition hover:border-accent/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-300/70 sm:px-6 ${locked ? 'cursor-default bg-white/5 opacity-75' : 'cursor-pointer'} ${invalid ? 'border-red-300/70 bg-red-500/10 shadow-[0_0_0_1px_rgba(248,113,113,0.2)]' : dragging ? 'border-cyan-300 bg-cyan-500/20 shadow-[0_0_0_1px_rgba(103,232,249,0.35)]' : locked ? 'bg-white/5' : 'bg-[linear-gradient(180deg,rgba(14,165,233,0.08),rgba(255,255,255,0.02))] hover:bg-sky-500/12'}`} >

{title}

{description}

{previewUrl ? (
Selected preview
Click to replace
) : ( <>
JPG, PNG, WEBP ZIP, RAR, 7Z 50MB images 200MB archives
Click to browse files )} { const selectedFile = event.target.files?.[0] if (selectedFile) { emitFile(selectedFile) } }} /> {(previewUrl || (fileMeta && String(fileMeta.type || '').startsWith('image/'))) && (
Selected file
{fileName || fileHint}
{fileMeta && (
Type: {fileMeta.type || '—'} · Size: {fileMeta.size || '—'} · Resolution: {fileMeta.resolution || '—'}
)}
)} {showLooksGood && (
{looksGoodText}
)} {errors.length > 0 && (

Please fix the following

    {errors.map((error, index) => (
  • {error}
  • ))}
)}
) }