Docs
Go back
Component by
OV
OblivionUpload your files
Drag and drop your files here, or click to browse. Support for images, docs, and PDFs.
TSX
file-dropzone-glow
"use client"
import React, { useState } from "react"
import { motion, AnimatePresence } from "framer-motion"
import { UploadCloud, File, CheckCircle2, X } from "lucide-react"
import { cn } from "@/lib/utils"
export const componentMeta = {
name: "File Dropzone Glow",
description: "A gorgeous glowing dashboard-style file uploader with smooth animations.",
props: {
className: { type: "string", description: "Additional CSS classes", default: '""' },
}
}
export default function FileDropzoneGlow({ className }: { className?: string }) {
const [isDragging, setIsDragging] = useState(false)
const [files, setFiles] = useState<File[]>([])
const [isUploading, setIsUploading] = useState(false)
const [progress, setProgress] = useState(0)
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault()
setIsDragging(true)
}
const handleDragLeave = () => setIsDragging(false)
const handleDrop = (e: React.DragEvent) => {
e.preventDefault()
setIsDragging(false)
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
setFiles(Array.from(e.dataTransfer.files))
simulateUpload()
}
}
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files.length > 0) {
setFiles(Array.from(e.target.files))
simulateUpload()
}
}
const simulateUpload = () => {
setIsUploading(true)
setProgress(0)
const interval = setInterval(() => {
setProgress(prev => {
if (prev >= 100) {
clearInterval(interval)
setTimeout(() => setIsUploading(false), 500)
return 100
}
return prev + Math.floor(Math.random() * 15)
})
}, 200)
}
const clearFiles = () => {
setFiles([])
setProgress(0)
}
return (
<div className={cn("w-full max-w-md mx-auto", className)}>
<motion.div
animate={{
boxShadow: isDragging
? "0 0 0 2px rgba(99, 102, 241, 0.4), 0 0 40px -10px rgba(99, 102, 241, 0.5)"
: "0 0 0 1px rgba(161, 161, 170, 0.2)",
borderColor: isDragging ? "rgba(99, 102, 241, 1)" : "rgba(161, 161, 170, 0.2)",
backgroundColor: isDragging ? "rgba(99, 102, 241, 0.04)" : "var(--dropzone-bg, rgba(20, 20, 22, 0))" // Use a CSS var for dark mode compatibility if needed, else transparent
}}
className={cn(
"relative overflow-hidden rounded-3xl border-2 border-dashed flex flex-col items-center justify-center p-10 transition-colors duration-300",
!isDragging && "hover:bg-zinc-50 dark:hover:bg-zinc-900/50"
)}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
<input
type="file"
multiple
onChange={handleFileChange}
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10"
accept="image/*,.pdf,.doc,.docx"
/>
<AnimatePresence mode="wait">
{files.length === 0 ? (
<motion.div
key="empty"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="flex flex-col items-center pointer-events-none"
>
<div className="w-16 h-16 mb-4 rounded-full bg-indigo-50 dark:bg-indigo-500/10 flex items-center justify-center">
<motion.div
animate={{ y: [0, -5, 0] }}
transition={{ repeat: Infinity, duration: 2, ease: "easeInOut" }}
>
<UploadCloud className="w-8 h-8 text-indigo-500" />
</motion.div>
</div>
<h3 className="text-lg font-bold text-zinc-900 dark:text-zinc-100 mb-1">Upload your files</h3>
<p className="text-sm text-zinc-500 dark:text-zinc-400 text-center px-4">
Drag and drop your files here, or click to browse. Support for images, docs, and PDFs.
</p>
</motion.div>
) : (
<motion.div
key="files"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
className="w-full flex flex-col gap-4 relative z-20 pointer-events-auto"
>
<div className="flex justify-between items-center mb-2">
<h4 className="text-sm font-semibold text-zinc-900 dark:text-zinc-100">{files.length} File{files.length !== 1 && 's'} Selected</h4>
{!isUploading && progress === 100 && (
<button onClick={clearFiles} className="text-xs font-semibold text-red-500 hover:text-red-600 transition flex items-center gap-1">
<X size={14} /> Clear
</button>
)}
</div>
<div className="space-y-3">
{files.slice(0, 3).map((file, i) => (
<div key={i} className="flex items-center gap-3 bg-white dark:bg-zinc-800 p-3 rounded-xl border border-zinc-200 dark:border-zinc-700 shadow-sm relative overflow-hidden">
<File className="w-6 h-6 text-zinc-400 flex-shrink-0" />
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-zinc-900 dark:text-zinc-100 truncate">{file.name}</p>
<p className="text-xs text-zinc-500">{(file.size / 1024 / 1024).toFixed(2)} MB</p>
</div>
{progress === 100 ? (
<CheckCircle2 className="w-5 h-5 text-green-500 flex-shrink-0" />
) : (
<div className="w-5 flex items-center justify-center">
<motion.div
animate={{ rotate: 360 }}
transition={{ repeat: Infinity, duration: 1, ease: "linear" }}
className="w-4 h-4 border-2 border-indigo-500 border-t-transparent rounded-full"
/>
</div>
)}
</div>
))}
{files.length > 3 && (
<p className="text-xs text-center text-zinc-500 italic">+ {files.length - 3} more files</p>
)}
</div>
{/* Progress Bar */}
{(isUploading || progress === 100) && (
<div className="mt-4 w-full h-2 bg-zinc-100 dark:bg-zinc-800 rounded-full overflow-hidden relative">
<motion.div
initial={{ width: 0 }}
animate={{ width: `${progress}%` }}
transition={{ ease: "easeOut" }}
className={cn(
"absolute top-0 left-0 h-full transition-colors duration-300",
progress === 100 ? "bg-green-500" : "bg-indigo-500 font-bold"
)}
/>
{progress > 0 && progress < 100 && (
<div className="absolute top-0 left-0 w-full h-full bg-[linear-gradient(45deg,transparent_25%,rgba(255,255,255,0.2)_25%,rgba(255,255,255,0.2)_50%,transparent_50%,transparent_75%,rgba(255,255,255,0.2)_75%,rgba(255,255,255,0.2)_100%)] bg-[length:16px_16px] animate-[ob-stripes_1s_linear_infinite]" />
)}
</div>
)}
</motion.div>
)}
</AnimatePresence>
</motion.div>
<style dangerouslySetInnerHTML={{ __html: `
@keyframes ob-stripes { from { background-position: 16px 0; } to { background-position: 0 0; } }
`}} />
</div>
)
}"use client"
import React, { useState } from "react"
import { motion, AnimatePresence } from "framer-motion"
import { UploadCloud, File, CheckCircle2, X } from "lucide-react"
import { cn } from "@/lib/utils"
export const componentMeta = {
name: "File Dropzone Glow",
description: "A gorgeous glowing dashboard-style file uploader with smooth animations.",
props: {
className: { type: "string", description: "Additional CSS classes", default: '""' },
}
}
export default function FileDropzoneGlow({ className }: { className?: string }) {
const [isDragging, setIsDragging] = useState(false)
const [files, setFiles] = useState<File[]>([])
const [isUploading, setIsUploading] = useState(false)
const [progress, setProgress] = useState(0)
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault()
setIsDragging(true)
}
const handleDragLeave = () => setIsDragging(false)
const handleDrop = (e: React.DragEvent) => {
e.preventDefault()
setIsDragging(false)
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
setFiles(Array.from(e.dataTransfer.files))
simulateUpload()
}
}
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files.length > 0) {
setFiles(Array.from(e.target.files))
simulateUpload()
}
}
const simulateUpload = () => {
setIsUploading(true)
setProgress(0)
const interval = setInterval(() => {
setProgress(prev => {
if (prev >= 100) {
clearInterval(interval)
setTimeout(() => setIsUploading(false), 500)
return 100
}
return prev + Math.floor(Math.random() * 15)
})
}, 200)
}
const clearFiles = () => {
setFiles([])
setProgress(0)
}
return (
<div className={cn("w-full max-w-md mx-auto", className)}>
<motion.div
animate={{
boxShadow: isDragging
? "0 0 0 2px rgba(99, 102, 241, 0.4), 0 0 40px -10px rgba(99, 102, 241, 0.5)"
: "0 0 0 1px rgba(161, 161, 170, 0.2)",
borderColor: isDragging ? "rgba(99, 102, 241, 1)" : "rgba(161, 161, 170, 0.2)",
backgroundColor: isDragging ? "rgba(99, 102, 241, 0.04)" : "var(--dropzone-bg, rgba(20, 20, 22, 0))" // Use a CSS var for dark mode compatibility if needed, else transparent
}}
className={cn(
"relative overflow-hidden rounded-3xl border-2 border-dashed flex flex-col items-center justify-center p-10 transition-colors duration-300",
!isDragging && "hover:bg-zinc-50 dark:hover:bg-zinc-900/50"
)}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
<input
type="file"
multiple
onChange={handleFileChange}
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10"
accept="image/*,.pdf,.doc,.docx"
/>
<AnimatePresence mode="wait">
{files.length === 0 ? (
<motion.div
key="empty"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="flex flex-col items-center pointer-events-none"
>
<div className="w-16 h-16 mb-4 rounded-full bg-indigo-50 dark:bg-indigo-500/10 flex items-center justify-center">
<motion.div
animate={{ y: [0, -5, 0] }}
transition={{ repeat: Infinity, duration: 2, ease: "easeInOut" }}
>
<UploadCloud className="w-8 h-8 text-indigo-500" />
</motion.div>
</div>
<h3 className="text-lg font-bold text-zinc-900 dark:text-zinc-100 mb-1">Upload your files</h3>
<p className="text-sm text-zinc-500 dark:text-zinc-400 text-center px-4">
Drag and drop your files here, or click to browse. Support for images, docs, and PDFs.
</p>
</motion.div>
) : (
<motion.div
key="files"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
className="w-full flex flex-col gap-4 relative z-20 pointer-events-auto"
>
<div className="flex justify-between items-center mb-2">
<h4 className="text-sm font-semibold text-zinc-900 dark:text-zinc-100">{files.length} File{files.length !== 1 && 's'} Selected</h4>
{!isUploading && progress === 100 && (
<button onClick={clearFiles} className="text-xs font-semibold text-red-500 hover:text-red-600 transition flex items-center gap-1">
<X size={14} /> Clear
</button>
)}
</div>
<div className="space-y-3">
{files.slice(0, 3).map((file, i) => (
<div key={i} className="flex items-center gap-3 bg-white dark:bg-zinc-800 p-3 rounded-xl border border-zinc-200 dark:border-zinc-700 shadow-sm relative overflow-hidden">
<File className="w-6 h-6 text-zinc-400 flex-shrink-0" />
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-zinc-900 dark:text-zinc-100 truncate">{file.name}</p>
<p className="text-xs text-zinc-500">{(file.size / 1024 / 1024).toFixed(2)} MB</p>
</div>
{progress === 100 ? (
<CheckCircle2 className="w-5 h-5 text-green-500 flex-shrink-0" />
) : (
<div className="w-5 flex items-center justify-center">
<motion.div
animate={{ rotate: 360 }}
transition={{ repeat: Infinity, duration: 1, ease: "linear" }}
className="w-4 h-4 border-2 border-indigo-500 border-t-transparent rounded-full"
/>
</div>
)}
</div>
))}
{files.length > 3 && (
<p className="text-xs text-center text-zinc-500 italic">+ {files.length - 3} more files</p>
)}
</div>
{/* Progress Bar */}
{(isUploading || progress === 100) && (
<div className="mt-4 w-full h-2 bg-zinc-100 dark:bg-zinc-800 rounded-full overflow-hidden relative">
<motion.div
initial={{ width: 0 }}
animate={{ width: `${progress}%` }}
transition={{ ease: "easeOut" }}
className={cn(
"absolute top-0 left-0 h-full transition-colors duration-300",
progress === 100 ? "bg-green-500" : "bg-indigo-500 font-bold"
)}
/>
{progress > 0 && progress < 100 && (
<div className="absolute top-0 left-0 w-full h-full bg-[linear-gradient(45deg,transparent_25%,rgba(255,255,255,0.2)_25%,rgba(255,255,255,0.2)_50%,transparent_50%,transparent_75%,rgba(255,255,255,0.2)_75%,rgba(255,255,255,0.2)_100%)] bg-[length:16px_16px] animate-[ob-stripes_1s_linear_infinite]" />
)}
</div>
)}
</motion.div>
)}
</AnimatePresence>
</motion.div>
<style dangerouslySetInnerHTML={{ __html: `
@keyframes ob-stripes { from { background-position: 16px 0; } to { background-position: 0 0; } }
`}} />
</div>
)
}Save to favoritesCopy to Figma
React
File Dropzone Glow
A gorgeous glowing dashboard-style file uploader with smooth animations.
Installation
$ npx @cosmoo/oblivion add file-dropzone-glowDependencies
npm install framer-motion lucide-reactUsage Example
tsx
import FileDropzoneGlow from "@/components/oblivion/file-dropzone-glow"
export default function Demo() {
return (
<div className="p-10 flex justify-center">
<FileDropzoneGlow />
</div>
)
}import FileDropzoneGlow from "@/components/oblivion/file-dropzone-glow"
export default function Demo() {
return (
<div className="p-10 flex justify-center">
<FileDropzoneGlow />
</div>
)
}