FileUploadZone
fileuploadzone-1779379671764.tsx
'use client'
import { useState, useCallback } from 'react'
interface UploadedFile {
id: number
name: string
size: number
type: string
progress: number
done: boolean
error?: string
}
const ALLOWED = ['image/png', 'image/jpeg', 'image/webp', 'image/svg+xml', 'application/pdf', 'text/plain', 'application/json']
const MAX_SIZE = 5 * 1024 * 1024
function formatSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
}
function fileIcon(type: string): string {
if (type.startsWith('image/')) return '🖼'
if (type === 'application/pdf') return '📄'
if (type.includes('json')) return '{ }'
return '📝'
}
let nextId = 1
export default function FileUploadZone() {
const [files, setFiles] = useState<UploadedFile[]>([])
const [dragging, setDragging] = useState(false)
const processFiles = useCallback((fileList: FileList) => {
Array.from(fileList).forEach(f => {
const id = nextId++
const error = !ALLOWED.includes(f.type) ? 'File type not allowed' : f.size > MAX_SIZE ? 'File too large (max 5 MB)' : undefined
const entry: UploadedFile = { id, name: f.name, size: f.size, type: f.type, progress: 0, done: false, error }
setFiles(fs => [...fs, entry])
if (!error) {
let prog = 0
const interval = setInterval(() => {
prog += Math.random() * 25 + 10
const done = prog >= 100
setFiles(fs => fs.map(x => x.id === id ? { ...x, progress: Math.min(100, prog), done } : x))
if (done) clearInterval(interval)
}, 200)
}
})
}, [])
const onDrop = useCallback((e: React.DragEvent) => {
e.preventDefault()
setDragging(false)
processFiles(e.dataTransfer.files)
}, [processFiles])
return (
<div style={{ padding: 32, background: '#0A0A0A', display: 'flex', flexDirection: 'column', gap: 16 }}>
{/* Drop zone */}
<div onDragEnter={() => setDragging(true)} onDragOver={e => { e.preventDefault(); setDragging(true) }}
onDragLeave={() => setDragging(false)} onDrop={onDrop}
style={{ border: `2px dashed ${dragging ? '#C9A84C' : 'rgba(255,255,255,0.12)'}`, borderRadius: 16, padding: '40px 24px', textAlign: 'center', background: dragging ? 'rgba(201,168,76,0.04)' : 'rgba(255,255,255,0.02)', transition: 'all 0.2s', cursor: 'pointer' }}
onClick={() => { const el = document.createElement('input'); el.type = 'file'; el.multiple = true; el.accept = ALLOWED.join(','); el.onchange = e => e.target && processFiles((e.target as HTMLInputElement).files!); el.click() }}>
<div style={{ fontSize: 40, marginBottom: 12 }}>📁</div>
<div style={{ color: '#F5F5F0', fontSize: 15, fontWeight: 600, marginBottom: 6 }}>Drop files here or click to browse</div>
<div style={{ color: 'rgba(255,255,255,0.35)', fontSize: 12 }}>PNG, JPG, PDF, JSON, TXT up to 5 MB</div>
</div>
{/* File list */}
{files.length > 0 && (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{files.map(f => (
<div key={f.id} style={{ background: '#111', border: `1px solid ${f.error ? 'rgba(239,68,68,0.2)' : f.done ? 'rgba(34,197,94,0.2)' : 'rgba(255,255,255,0.06)'}`, borderRadius: 10, padding: '12px 14px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: f.error || !f.done ? 8 : 0 }}>
<span style={{ fontSize: 18 }}>{fileIcon(f.type)}</span>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ color: '#F5F5F0', fontSize: 13, fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{f.name}</div>
<div style={{ color: 'rgba(255,255,255,0.3)', fontSize: 11 }}>{formatSize(f.size)}</div>
</div>
<button onClick={() => setFiles(fs => fs.filter(x => x.id !== f.id))} style={{ background: 'none', border: 'none', color: 'rgba(255,255,255,0.25)', cursor: 'pointer', fontSize: 16 }}>×</button>
</div>
{f.error && <div style={{ color: '#ef4444', fontSize: 11 }}>⚠ {f.error}</div>}
{!f.error && !f.done && (
<div style={{ height: 4, background: 'rgba(255,255,255,0.06)', borderRadius: 2, overflow: 'hidden' }}>
<div style={{ height: '100%', width: `${f.progress}%`, background: 'linear-gradient(90deg, #C9A84C, #6366f1)', borderRadius: 2, transition: 'width 0.15s' }} />
</div>
)}
{f.done && <div style={{ color: '#22c55e', fontSize: 11 }}>✓ Upload complete</div>}
</div>
))}
<button onClick={() => setFiles([])} style={{ alignSelf: 'flex-start', background: 'none', border: '1px solid rgba(255,255,255,0.1)', color: 'rgba(255,255,255,0.4)', borderRadius: 6, padding: '5px 12px', cursor: 'pointer', fontSize: 12 }}>Clear all</button>
</div>
)}
</div>
)
}Component info
CategoryUtility
Frameworkreact
TierFREE
Views0
Copies0
About
Drag-and-drop file upload zone with type validation, progress simulation, preview, and multi-file
More from Utility
'use client'
import { useState } from 'react'
const PRESETS = [
{ name: 'Gold', primary: '#C9A84C', bg: '#0A0A0A', accent: '#6366f1' },
{ name: 'Neon', primary: '#22d3ee', bg: '#030712', accent: '#a78bfa' },
{ name: 'Crimson', primary: '#ef444ThemeCustomizer
Utility
'use client'
import { useState } from 'react'
interface ClipItem {
id: number
content: string
type: 'text' | 'code' | 'url' | 'email'
pinned: boolean
time: number
}
const INITIAL: ClipItem[] = [
{ id: 1, content: 'npx empire-ui-mcp --stClipboardManager
Utility