99 lines
2.4 KiB
TypeScript
99 lines
2.4 KiB
TypeScript
/**
|
||
* 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>
|
||
)
|
||
}
|
||
|