Highly ambitious ATProtocol AppView service and sdks
at main 85 lines 2.5 kB view raw
1import { useState } from "react"; 2import { Avatar } from "./Avatar.tsx"; 3 4interface AvatarInputProps { 5 currentAvatarUrl?: string; 6 onChange: (file: File | null) => void; 7} 8 9export function AvatarInput( 10 { currentAvatarUrl, onChange }: AvatarInputProps, 11) { 12 const [previewUrl, setPreviewUrl] = useState<string | null>(null); 13 14 const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { 15 const file = e.target.files?.[0]; 16 if (file) { 17 // Create preview URL 18 const objectUrl = URL.createObjectURL(file); 19 setPreviewUrl(objectUrl); 20 onChange(file); 21 22 // Clean up previous preview URL 23 return () => { 24 if (previewUrl) { 25 URL.revokeObjectURL(previewUrl); 26 } 27 }; 28 } else { 29 setPreviewUrl(null); 30 onChange(null); 31 } 32 }; 33 34 const displayUrl = previewUrl || currentAvatarUrl; 35 36 return ( 37 <div> 38 <label 39 htmlFor="avatar" 40 className="block text-sm font-medium text-zinc-400 mb-2" 41 > 42 Avatar 43 </label> 44 <label htmlFor="avatar" className="cursor-pointer inline-block"> 45 <div className="border rounded-full border-zinc-700 w-16 h-16 mb-2 relative hover:border-zinc-600 transition-colors"> 46 {/* Camera icon overlay */} 47 <div className="absolute bottom-0 right-0 bg-zinc-700 rounded-full w-5 h-5 flex items-center justify-center z-10"> 48 <svg 49 className="w-3 h-3 text-white" 50 fill="currentColor" 51 viewBox="0 0 20 20" 52 > 53 <path 54 fillRule="evenodd" 55 d="M4 5a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V7a2 2 0 00-2-2h-1.586a1 1 0 01-.707-.293l-1.121-1.121A2 2 0 0011.172 3H8.828a2 2 0 00-1.414.586L6.293 4.707A1 1 0 015.586 5H4zm6 9a3 3 0 100-6 3 3 0 000 6z" 56 clipRule="evenodd" 57 /> 58 </svg> 59 </div> 60 61 {/* Avatar preview */} 62 <div className="w-full h-full rounded-full overflow-hidden"> 63 {displayUrl 64 ? ( 65 <img 66 src={displayUrl} 67 alt="Avatar preview" 68 className="w-full h-full object-cover" 69 /> 70 ) 71 : <Avatar alt="Default avatar" size="lg" />} 72 </div> 73 </div> 74 </label> 75 <input 76 type="file" 77 id="avatar" 78 name="avatar" 79 accept="image/*" 80 className="hidden" 81 onChange={handleFileChange} 82 /> 83 </div> 84 ); 85}