Video Uploader
A video-specific uploader with progress tracking, cancelable uploads, and retry logic for failed transfers.
Preview
Click or drag video to upload
Support for MP4, WebM or OGG up to 100MB
Code
1import {2 UplofileRoot,3 UplofileTrigger,4 UplofileDropzone,5 UplofilePreview,6 UplofileRemove,7 UplofileCancel,8 UplofileRetry,9 type UploadFileItem,10} from "@/components/ui/uplofile";11import {12 IoVideocamOutline,13 IoCloseOutline,14 IoRefreshOutline,15 IoBanOutline,16 IoCloudUploadOutline,17 IoCheckmarkCircleOutline,18 IoAlertCircleOutline,19 IoPlayOutline,20} from "react-icons/io5";21import { formatBytes, mockUpload } from "@/lib/utils.ts";2223export default function VideoUploaderDemo() {24 return (25 <div className="w-full max-w-2xl mx-auto">26 <style>{`27 @keyframes progress-stripe {28 from { background-position: 1rem 0; }29 to { background-position: 0 0; }30 }31 `}</style>32 <UplofileRoot33 upload={(file, signal, progress) =>34 mockUpload(file, signal, progress, 0.4)35 }36 multiple37 accept="video/*"38 >39 <div className="space-y-6">40 <UplofileDropzone asChild>41 <UplofileTrigger asChild>42 <div className="group relative flex flex-col items-center justify-center w-full h-52 border-2 border-dashed border-muted-foreground/25 rounded-3xl bg-muted/5 hover:bg-muted/10 hover:border-primary/50 transition-all cursor-pointer overflow-hidden data-[dragging=true]:border-primary data-[dragging=true]:bg-primary/10 data-[dragging=true]:scale-[0.98]">43 <div className="absolute inset-0 bg-gradient-to-br from-primary/5 via-transparent to-primary/5 opacity-0 group-hover:opacity-100 transition-opacity" />44 <div className="relative flex flex-col items-center gap-4 text-center p-6 transition-transform duration-300 group-data-[dragging=true]:scale-110">45 <div className="relative">46 <div className="absolute -inset-1 rounded-full bg-primary/20 blur opacity-0 group-hover:opacity-100 transition-opacity" />47 <div className="relative p-4 rounded-2xl bg-background border shadow-sm text-primary group-hover:scale-110 group-hover:-translate-y-1 transition-all duration-300">48 <IoVideocamOutline className="h-8 w-8" />49 </div>50 <div className="absolute -bottom-1 -right-1 p-1 rounded-lg bg-primary text-primary-foreground shadow-sm scale-0 group-hover:scale-100 transition-transform delay-100">51 <IoCloudUploadOutline className="h-3 w-3" />52 </div>53 </div>54 <div className="space-y-1">55 <p className="text-sm font-bold tracking-tight">56 Click or drag video to upload57 </p>58 <p className="text-xs text-muted-foreground max-w-[200px] mx-auto leading-relaxed">59 Support for MP4, WebM or OGG up to 100MB60 </p>61 </div>62 </div>63 </div>64 </UplofileTrigger>65 </UplofileDropzone>6667 <UplofilePreview68 render={({ items }) => (69 <div className="grid gap-4">70 {items.map((item) => (71 <VideoItem key={item.uid} item={item} />72 ))}73 </div>74 )}75 />76 </div>77 </UplofileRoot>78 </div>79 );80}8182function VideoItem({ item }: { item: UploadFileItem }) {83 return (84 <div className="group relative flex flex-col sm:flex-row gap-5 p-4 border rounded-3xl bg-card hover:shadow-md transition-all duration-300 overflow-hidden animate-in fade-in slide-in-from-bottom-4">85 {/* Video Thumbnail/Preview Area */}86 <div className="relative w-full sm:w-48 aspect-video shrink-0 rounded-2xl bg-muted flex items-center justify-center overflow-hidden border shadow-inner">87 {item.status === "done" ? (88 <video89 src={item.url}90 className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"91 />92 ) : item.previewUrl ? (93 <video94 src={item.previewUrl}95 className="w-full h-full object-cover opacity-40 blur-[1px]"96 />97 ) : (98 <div className="flex flex-col items-center gap-2 text-muted-foreground/30">99 <IoVideocamOutline className="h-10 w-10" />100 <span className="text-[10px] font-bold uppercase tracking-widest">101 No Preview102 </span>103 </div>104 )}105106 {item.status === "uploading" && (107 <div className="absolute inset-0 bg-background/60 backdrop-blur-sm flex items-center justify-center">108 <div className="relative h-12 w-12 flex items-center justify-center">109 <svg110 className="absolute inset-0 h-full w-full -rotate-90 transform"111 viewBox="0 0 100 100"112 >113 <circle114 cx="50"115 cy="50"116 r="45"117 fill="none"118 stroke="currentColor"119 strokeWidth="8"120 className="text-muted/20"121 />122 <circle123 cx="50"124 cy="50"125 r="45"126 fill="none"127 stroke="currentColor"128 strokeWidth="8"129 strokeDasharray={282.7}130 strokeDashoffset={131 282.7 - (282.7 * (item.progress ?? 0)) / 100132 }133 className="text-primary transition-all duration-300 ease-out"134 strokeLinecap="round"135 />136 </svg>137 <span className="text-[10px] font-bold tabular-nums">138 {item.progress}%139 </span>140 </div>141 </div>142 )}143144 {item.status === "done" && (145 <div className="absolute inset-0 flex items-center justify-center bg-black/20 opacity-0 group-hover:opacity-100 transition-all duration-300">146 <div className="p-3 rounded-full bg-white/20 backdrop-blur-md border border-white/30 text-white transform scale-90 group-hover:scale-100 transition-transform">147 <IoPlayOutline className="h-6 w-6 fill-current" />148 </div>149 </div>150 )}151152 {item.status === "error" && (153 <div className="absolute inset-0 bg-destructive/10 backdrop-blur-[1px] flex items-center justify-center">154 <IoAlertCircleOutline className="h-8 w-8 text-destructive/50" />155 </div>156 )}157 </div>158159 <div className="flex-1 min-w-0 flex flex-col justify-between py-1">160 <div>161 <div className="flex items-start justify-between gap-4 mb-2">162 <div className="space-y-1 min-w-0">163 <p className="text-sm font-bold truncate pr-2 text-foreground/90">164 {item.name}165 </p>166 <div className="flex items-center gap-2">167 <span className="px-1.5 py-0.5 rounded-md bg-muted text-[10px] font-bold uppercase tracking-wider text-muted-foreground">168 {item.file?.type.split("/")[1] || "video"}169 </span>170 <span className="text-[10px] text-muted-foreground/60 font-medium">171 {formatBytes(item.file?.size || 0)}172 </span>173 </div>174 </div>175 <UplofileRemove176 uid={item.uid}177 className="p-2 -mr-2 rounded-xl text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-all active:scale-90"178 >179 <IoCloseOutline className="h-4 w-4" />180 </UplofileRemove>181 </div>182 </div>183184 <div className="space-y-3">185 {item.status === "uploading" && (186 <div className="flex items-center justify-between gap-3">187 <div className="flex-1 h-1.5 bg-muted rounded-full overflow-hidden">188 <div189 className="h-full bg-primary transition-all duration-300 ease-out relative"190 style={{ width: `${item.progress}%` }}191 >192 <div className="absolute inset-0 bg-[linear-gradient(45deg,rgba(255,255,255,.15)_25%,transparent_25%,transparent_50%,rgba(255,255,255,.15)_50%,rgba(255,255,255,.15)_75%,transparent_75%,transparent)] bg-[length:1rem_1rem] animate-[progress-stripe_1s_linear_infinite]" />193 </div>194 </div>195 <UplofileCancel uid={item.uid} asChild>196 <button197 className="p-1.5 rounded-lg bg-muted hover:bg-destructive/10 hover:text-destructive transition-colors text-muted-foreground"198 title="Cancel upload"199 >200 <IoBanOutline className="h-3.5 w-3.5" />201 </button>202 </UplofileCancel>203 </div>204 )}205206 {item.status === "error" && (207 <div className="flex items-center justify-between gap-4">208 <div className="flex items-center gap-2 text-destructive">209 <div className="h-1.5 w-1.5 rounded-full bg-destructive animate-pulse" />210 <span className="text-[10px] font-bold uppercase tracking-wider">211 Upload Failed212 </span>213 </div>214 <UplofileRetry uid={item.uid} asChild>215 <button className="flex items-center gap-1.5 px-3 py-1.5 text-[10px] font-bold uppercase tracking-wider bg-destructive text-destructive-foreground rounded-xl hover:bg-destructive/90 transition-all shadow-sm active:scale-95">216 <IoRefreshOutline className="h-3 w-3" />217 Retry218 </button>219 </UplofileRetry>220 </div>221 )}222223 {item.status === "done" && (224 <div className="flex items-center justify-between">225 <div className="flex items-center gap-2 text-emerald-600 bg-emerald-500/10 px-2 py-1 rounded-lg border border-emerald-500/20">226 <IoCheckmarkCircleOutline className="h-3.5 w-3.5" />227 <span className="text-[10px] font-bold uppercase tracking-wider">228 Ready to publish229 </span>230 </div>231 <button className="text-[10px] font-bold text-primary hover:underline transition-all">232 Preview Video233 </button>234 </div>235 )}236 </div>237 </div>238 </div>239 );240}
Key Points
- → Uses
UplofileCancelto abort ongoing uploads - → Uses
UplofileRetryto restart failed uploads - → Video-specific previews using HTML5
<video>tags - → Custom status indicators for different upload states