refactor(web): expose avatar primitives for composition (#34057)

This commit is contained in:
yyh 2026-03-25 13:43:46 +08:00 committed by GitHub
parent 5f82ccc750
commit b1cfd835f5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -2,7 +2,7 @@ import type { ImageLoadingStatus } from '@base-ui/react/avatar'
import { Avatar as BaseAvatar } from '@base-ui/react/avatar' import { Avatar as BaseAvatar } from '@base-ui/react/avatar'
import { cn } from '@/utils/classnames' import { cn } from '@/utils/classnames'
const SIZES = { export const avatarSizeClasses = {
'xxs': { root: 'size-4', text: 'text-[7px]' }, 'xxs': { root: 'size-4', text: 'text-[7px]' },
'xs': { root: 'size-5', text: 'text-[8px]' }, 'xs': { root: 'size-5', text: 'text-[8px]' },
'sm': { root: 'size-6', text: 'text-[10px]' }, 'sm': { root: 'size-6', text: 'text-[10px]' },
@ -13,7 +13,9 @@ const SIZES = {
'3xl': { root: 'size-16', text: 'text-2xl' }, '3xl': { root: 'size-16', text: 'text-2xl' },
} as const } as const
export type AvatarSize = keyof typeof SIZES export type AvatarSize = keyof typeof avatarSizeClasses
export const getAvatarSizeClassNames = (size: AvatarSize) => avatarSizeClasses[size]
export type AvatarProps = { export type AvatarProps = {
name: string name: string
@ -23,7 +25,13 @@ export type AvatarProps = {
onLoadingStatusChange?: (status: ImageLoadingStatus) => void onLoadingStatusChange?: (status: ImageLoadingStatus) => void
} }
const BASE_CLASS = 'relative inline-flex shrink-0 select-none items-center justify-center overflow-hidden rounded-full bg-primary-600' export const AvatarRoot = BaseAvatar.Root
export const AvatarImage = BaseAvatar.Image
export const AvatarFallback = BaseAvatar.Fallback
const ROOT_CLASS_NAME = 'relative inline-flex shrink-0 select-none items-center justify-center overflow-hidden rounded-full bg-primary-600'
const IMAGE_CLASS_NAME = 'absolute inset-0 size-full object-cover'
const FALLBACK_CLASS_NAME = 'flex size-full items-center justify-center font-medium text-white'
export const Avatar = ({ export const Avatar = ({
name, name,
@ -32,21 +40,21 @@ export const Avatar = ({
className, className,
onLoadingStatusChange, onLoadingStatusChange,
}: AvatarProps) => { }: AvatarProps) => {
const sizeConfig = SIZES[size] const sizeClassNames = getAvatarSizeClassNames(size)
return ( return (
<BaseAvatar.Root className={cn(BASE_CLASS, sizeConfig.root, className)}> <AvatarRoot className={cn(ROOT_CLASS_NAME, sizeClassNames.root, className)}>
{avatar && ( {avatar && (
<BaseAvatar.Image <AvatarImage
src={avatar} src={avatar}
alt={name} alt={name}
className="absolute inset-0 size-full object-cover" className={IMAGE_CLASS_NAME}
onLoadingStatusChange={onLoadingStatusChange} onLoadingStatusChange={onLoadingStatusChange}
/> />
)} )}
<BaseAvatar.Fallback className={cn('font-medium text-white', sizeConfig.text)}> <AvatarFallback className={cn(FALLBACK_CLASS_NAME, sizeClassNames.text)}>
{name?.[0]?.toLocaleUpperCase()} {name?.[0]?.toLocaleUpperCase()}
</BaseAvatar.Fallback> </AvatarFallback>
</BaseAvatar.Root> </AvatarRoot>
) )
} }