Oblivion
Oblivion
Docs
Go back
Component by
OV
Oblivion

Upload 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>
  )
}
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-glow
Dependenciesnpm install framer-motion lucide-react

Usage Example

tsx
import FileDropzoneGlow from "@/components/oblivion/file-dropzone-glow"

export default function Demo() {
  return (
    <div className="p-10 flex justify-center">
      <FileDropzoneGlow />
    </div>
  )
}