2e14873f2d
Track application source and tests; exclude local env, user workspaces, and runtime data via .gitignore. Co-authored-by: Cursor <cursoragent@cursor.com>
57 lines
1.5 KiB
TypeScript
57 lines
1.5 KiB
TypeScript
type UserAvatarProps = {
|
|
avatarUrl?: string | null;
|
|
size?: 'sm' | 'md';
|
|
className?: string;
|
|
onClick?: () => void;
|
|
title?: string;
|
|
};
|
|
|
|
function DefaultUserAvatar() {
|
|
return (
|
|
<svg viewBox="0 0 40 40" fill="none" aria-hidden="true">
|
|
<rect width="40" height="40" rx="6" fill="#c8c8c8" />
|
|
<circle cx="20" cy="15" r="6.5" fill="#f5f5f5" />
|
|
<path d="M9 31c2.5-5.5 7-8.5 11-8.5s8.5 3 11 8.5" fill="#f5f5f5" />
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
export function UserAvatar({
|
|
avatarUrl,
|
|
size = 'sm',
|
|
className = '',
|
|
onClick,
|
|
title,
|
|
}: UserAvatarProps) {
|
|
const sizeClass = size === 'md' ? 'user-avatar-md' : 'user-avatar-sm';
|
|
const interactive = Boolean(onClick);
|
|
|
|
return (
|
|
<div
|
|
className={`user-avatar ${sizeClass} ${interactive ? 'user-avatar-interactive' : ''} ${className}`.trim()}
|
|
role={interactive ? 'button' : undefined}
|
|
tabIndex={interactive ? 0 : undefined}
|
|
aria-label={interactive ? title ?? '更换头像' : undefined}
|
|
title={title}
|
|
onClick={onClick}
|
|
onKeyDown={
|
|
interactive
|
|
? (e) => {
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
e.preventDefault();
|
|
onClick?.();
|
|
}
|
|
}
|
|
: undefined
|
|
}
|
|
>
|
|
{avatarUrl ? (
|
|
<img src={avatarUrl} alt="" className="user-avatar-img" />
|
|
) : (
|
|
<DefaultUserAvatar />
|
|
)}
|
|
{interactive && <span className="user-avatar-edit-badge" aria-hidden="true" />}
|
|
</div>
|
|
);
|
|
}
|