flnxi-Wu/components/ui/Avatar.tsx

99 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Avatar 组件
*
* 统一的用户头像组件,遵循三层架构设计
* 支持图片头像和文字头像(显示用户名首字母)
* 提供多种尺寸(xs、sm、md、lg、xl)
* 支持点击事件和键盘导航
*
* @created 2025-11-22
* @component
*/
'use client'
import Image from 'next/image'
import { useTheme, cn } from '@/lib/ui-styles'
interface AvatarProps {
src?: string | null
alt?: string
name?: string
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
className?: string
onClick?: () => void
}
export default function Avatar({
src,
alt,
name = '',
size = 'md',
className = '',
onClick,
}: AvatarProps) {
const theme = useTheme()
const sizeClasses = {
xs: 'w-6 h-6 text-xs',
sm: 'w-8 h-8 text-sm',
md: 'w-10 h-10 text-base',
lg: 'w-12 h-12 text-lg',
xl: 'w-16 h-16 text-xl',
}
const getInitials = (name: string) => {
if (!name) return 'U'
const parts = name.trim().split(/\s+/)
if (parts.length >= 2) {
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase()
}
return name.charAt(0).toUpperCase()
}
const initials = getInitials(name)
// 根据主题选择颜色
const bgColor = theme === 'dark' ? 'bg-slate-700' : 'bg-slate-200'
const textColor = theme === 'dark' ? 'text-slate-200' : 'text-slate-700'
const borderColor = theme === 'dark' ? 'border-slate-600' : 'border-slate-300'
return (
<div
className={cn(
'rounded-full flex items-center justify-center font-semibold',
'border-2 shadow-sm transition-colors',
bgColor,
textColor,
borderColor,
sizeClasses[size],
onClick && 'cursor-pointer',
className
)}
onClick={onClick}
role={onClick ? 'button' : undefined}
tabIndex={onClick ? 0 : undefined}
onKeyDown={onClick ? (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
onClick()
}
} : undefined}
>
{src ? (
<Image
src={src}
alt={alt || name || 'Avatar'}
width={size === 'xs' ? 24 : size === 'sm' ? 32 : size === 'md' ? 40 : size === 'lg' ? 48 : 64}
height={size === 'xs' ? 24 : size === 'sm' ? 32 : size === 'md' ? 40 : size === 'lg' ? 48 : 64}
className="rounded-full object-cover w-full h-full"
unoptimized
/>
) : (
<span>{initials}</span>
)}
</div>
)
}