avatar · minimal · 灵感来源:Refactoring UI(实底圆 + 发丝边)
三档尺寸(无图时取名字)
状态点:颜色之外还有读屏文案
图片加载失败回落首字母
Avatar with fallback · 极简头像 · 头像 / Minimal
做一个 Avatar with fallback(极简头像):
- 同一份 props(name / src / size / status)
- 实底圆 + 发丝边,图片边缘不套滤镜
- 首字母回落与状态读屏文案与玻璃头像一致
用这些 CSS 变量:--color-divider --color-success-fg --color-surface --color-text-muted --color-text-subtle --color-warning-fg --text-2xs --text-xs
灵感参考:Refactoring UI(实底圆 + 发丝边)--color-divider --color-success-fg --color-surface --color-text-muted --color-text-subtle --color-warning-fg --text-2xs --text-xs
(无必缺状态)
(尚未接进整页场景)
/r/avatar--minimal.json
免外壳的 markdown 版:/llms/components/avatar--minimal.md--color-divider 发丝边;无模糊,图片边缘干净import { useState } from 'react';
interface Props {
name: string;
src?: string;
size?: 'sm' | 'md' | 'lg';
status?: 'online' | 'busy' | 'offline';
className?: string;
}
const SIZE = {
sm: 'size-6 text-[length:var(--text-2xs)]',
md: 'size-8 text-[length:var(--text-xs)]',
lg: 'size-11 text-sm',
};
const STATUS = {
online: 'bg-[var(--color-success-fg)]',
busy: 'bg-[var(--color-warning-fg)]',
offline: 'bg-[var(--color-text-subtle)]',
};
const STATUS_LABEL = {
online: '在线',
busy: '忙碌',
offline: '离线',
};
/** 中文在小尺寸只留姓,lg 才放两个字;西文取首字母缩写 */
function initials(name: string, size: 'sm' | 'md' | 'lg') {
const parts = name.trim().split(/\s+/);
if (parts.length > 1) return (parts[0][0] + parts[1][0]).toUpperCase();
if (/[\u4e00-\u9fa5]/.test(name)) return size === 'lg' ? name.slice(0, 2) : name.slice(0, 1);
return name.slice(0, 2).toUpperCase();
}
export function MinimalAvatar({ name, src, size = 'md', status, className = '' }: Props) {
const [broken, setBroken] = useState(false);
const showImage = src && !broken;
return (
<span className={`relative inline-flex shrink-0 ${className}`}>
<span
className={`
${SIZE[size]} rounded-full overflow-hidden relative
inline-flex items-center justify-center
bg-[var(--color-surface)] border border-[var(--color-divider)]
text-[var(--color-text-muted)] font-medium
`}
>
<span aria-hidden="true" className="absolute inset-0 grid place-items-center">
{initials(name, size)}
</span>
{showImage && (
<img
src={src}
alt=""
className="absolute inset-0 size-full object-cover"
onError={() => setBroken(true)}
/>
)}
<span className="sr-only">{name}</span>
</span>
{status && (
<>
<span
className={`
absolute -bottom-0 -right-0 size-2 rounded-full
ring-2 ring-[var(--color-surface)]
${STATUS[status]}
`}
aria-hidden="true"
/>
<span className="sr-only">{STATUS_LABEL[status]}</span>
</>
)}
</span>
);
}
src/components/avatar/minimal/