Uplofile is open sourceStar on GitHub

Avatar Uploader

A circular avatar uploader with hover overlay for profile pictures.

Preview

Profile Picture

JPG, GIF or PNG. Max size of 800K

Code

1import {
2 UplofileRoot,
3 UplofileTrigger,
4 UplofilePreview,
5 type UploadFileItem,
6} from "@/components/ui/uplofile";
7import { IoPersonOutline, IoCameraOutline, IoReloadOutline, IoAlertCircleOutline, IoRefreshOutline } from "react-icons/io5";
8import { mockUpload, cn } from "@/lib/utils.ts";
9import { UplofileRetry } from "@/components/ui/uplofile";
10
11export default function AvatarUploaderDemo() {
12 return (
13 <div className="flex flex-col items-center justify-center gap-4">
14 <UplofileRoot upload={mockUpload} accept="image/*" multiple={false}>
15 <UplofilePreview
16 render={({ items }) => <AvatarPreview items={items} />}
17 />
18 </UplofileRoot>
19 <div className="text-center">
20 <p className="text-sm font-medium">Profile Picture</p>
21 <p className="text-xs text-muted-foreground text-balance">
22 JPG, GIF or PNG. Max size of 800K
23 </p>
24 </div>
25 </div>
26 );
27}
28
29function AvatarPreview({ items }: { items: UploadFileItem[] }) {
30 const item = items[0];
31
32 return (
33 <div className="relative group">
34 <div className="relative">
35 <UplofileTrigger>
36 <div
37 className={cn(
38 "relative w-32 h-32 rounded-full overflow-hidden cursor-pointer border-4 border-background shadow-xl bg-muted flex items-center justify-center transition-all hover:scale-[1.02] active:scale-[0.98]",
39 item?.status === "error" &&
40 "border-destructive/50 ring-4 ring-destructive/10",
41 )}
42 >
43 {item?.previewUrl ? (
44 <img
45 src={item.previewUrl}
46 alt="Avatar"
47 className={cn(
48 "w-full h-full object-cover animate-in fade-in duration-500",
49 item.status === "error" && "opacity-40 grayscale",
50 )}
51 />
52 ) : (
53 <IoPersonOutline className="w-16 h-16 text-muted-foreground/50" />
54 )}
55
56 <div className="absolute inset-0 bg-black/40 flex flex-col items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-200">
57 <IoCameraOutline className="w-8 h-8 text-white mb-1" />
58 <span className="text-white text-[10px] font-medium uppercase tracking-wider">
59 Change Photo
60 </span>
61 </div>
62
63 {item?.status === "uploading" && (
64 <div className="absolute inset-0 bg-black/60 flex flex-col items-center justify-center backdrop-blur-[2px]">
65 <IoReloadOutline className="w-8 h-8 text-white animate-spin mb-2" />
66 <span className="text-white text-xs font-bold">
67 {item.progress}%
68 </span>
69 </div>
70 )}
71
72 {item?.status === "error" && (
73 <div className="absolute inset-0 bg-destructive/60 flex flex-col items-center justify-center backdrop-blur-[1px]">
74 <IoAlertCircleOutline className="w-8 h-8 text-white mb-1" />
75 <span className="text-white text-[10px] font-bold uppercase">
76 Error
77 </span>
78 </div>
79 )}
80 </div>
81 </UplofileTrigger>
82
83 {item?.status === "done" && (
84 <div className="absolute -bottom-1 -right-1 w-8 h-8 bg-emerald-500 rounded-full border-4 border-background flex items-center justify-center shadow-lg animate-in zoom-in">
85 <div className="w-2 h-2 bg-white rounded-full" />
86 </div>
87 )}
88
89 {item?.status === "error" && (
90 <UplofileRetry uid={item.uid} asChild>
91 <button className="absolute -bottom-1 -right-1 w-8 h-8 bg-destructive text-white rounded-full border-4 border-background flex items-center justify-center shadow-lg animate-in zoom-in hover:scale-110 transition-transform">
92 <IoRefreshOutline className="w-4 h-4" />
93 </button>
94 </UplofileRetry>
95 )}
96 </div>
97 </div>
98 );
99}

Key Points

  • Single file upload (no multiple prop)
  • Hover overlay for change action
  • Circular crop with rounded-full
  • Uses item.previewUrl for instant preview