Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | 2x 2x 54x 54x 54x 42x 9x 5x 4x 4x 4x 4x 33x 54x 4x 4x 3x 54x 1x 1x 54x 2x | import type { ChangeEvent } from 'react';
import { forwardRef, useEffect, useRef, useState } from 'react';
import { Upload, User } from 'lucide-react';
import { cn } from '@/lib/utils';
import { Avatar, AvatarImage, AvatarFallback } from './avatar';
export interface UploadAvatarProps {
value?: string | File;
onValueChange?: (value: File | null) => void;
className?: string;
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
accept?: string;
}
const sizeMap = {
sm: 'h-16 w-16',
md: 'h-24 w-24',
lg: 'h-32 w-32',
};
const UploadAvatar = forwardRef<HTMLDivElement, UploadAvatarProps>(
(
{
className,
value,
onValueChange,
size = 'md',
disabled = false,
accept = 'image/*',
...props
},
ref
) => {
const [preview, setPreview] = useState<string>('');
const fileInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (value) {
if (typeof value === 'string') {
setPreview(value);
} else Eif (value instanceof File) {
const objectUrl = URL.createObjectURL(value);
setPreview(objectUrl);
return () => URL.revokeObjectURL(objectUrl);
}
} else {
setPreview('');
}
}, [value]);
const handleFileSelect = (event: ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file && onValueChange) {
onValueChange(file);
}
};
const handleClick = () => {
Eif (!disabled) {
fileInputRef.current?.click();
}
};
return (
<div
ref={ref}
className={cn('relative inline-block', className)}
{...props}
>
<div
className={cn(
'relative cursor-pointer group',
sizeMap[size],
disabled && 'cursor-not-allowed opacity-50'
)}
onClick={handleClick}
>
<Avatar className={cn('h-full w-full', sizeMap[size])}>
{preview ? (
<AvatarImage src={preview} alt="Avatar preview" />
) : (
<AvatarFallback>
<User className="h-1/2 w-1/2 text-muted-foreground" />
</AvatarFallback>
)}
</Avatar>
{/* Upload Overlay */}
<div
className={cn(
'absolute inset-0 flex items-center justify-center rounded-full bg-black/60 opacity-0 transition-opacity group-hover:opacity-100',
disabled && 'group-hover:opacity-0'
)}
>
<Upload className="h-6 w-6 text-white" />
</div>
</div>
{/* Hidden File Input */}
<input
ref={fileInputRef}
type="file"
accept={accept}
onChange={handleFileSelect}
className="hidden"
disabled={disabled}
/>
</div>
);
}
);
UploadAvatar.displayName = 'UploadAvatar';
export { UploadAvatar };
|