refactor: add composable avatar slot wrappers (#34058)

This commit is contained in:
yyh 2026-03-25 14:16:37 +08:00 committed by GitHub
parent af3069e3be
commit b4af0d0f9a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,8 +1,9 @@
import type { ImageLoadingStatus } from '@base-ui/react/avatar'
import type * as React from 'react'
import { Avatar as BaseAvatar } from '@base-ui/react/avatar'
import { cn } from '@/utils/classnames'
export const avatarSizeClasses = {
const avatarSizeClasses = {
'xxs': { root: 'size-4', text: 'text-[7px]' },
'xs': { root: 'size-5', text: 'text-[8px]' },
'sm': { root: 'size-6', text: 'text-[10px]' },
@ -15,8 +16,6 @@ export const avatarSizeClasses = {
export type AvatarSize = keyof typeof avatarSizeClasses
export const getAvatarSizeClassNames = (size: AvatarSize) => avatarSizeClasses[size]
export type AvatarProps = {
name: string
avatar: string | null
@ -25,13 +24,61 @@ export type AvatarProps = {
onLoadingStatusChange?: (status: ImageLoadingStatus) => void
}
export const AvatarRoot = BaseAvatar.Root
export const AvatarImage = BaseAvatar.Image
export const AvatarFallback = BaseAvatar.Fallback
export type AvatarRootProps = React.ComponentPropsWithRef<typeof BaseAvatar.Root> & {
size?: AvatarSize
}
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 function AvatarRoot({
size = 'md',
className,
...props
}: AvatarRootProps) {
return (
<BaseAvatar.Root
className={cn(
'relative inline-flex shrink-0 select-none items-center justify-center overflow-hidden rounded-full bg-primary-600',
avatarSizeClasses[size].root,
className,
)}
{...props}
/>
)
}
export type AvatarImageProps = React.ComponentPropsWithRef<typeof BaseAvatar.Image>
export function AvatarImage({
className,
...props
}: AvatarImageProps) {
return (
<BaseAvatar.Image
className={cn('absolute inset-0 size-full object-cover', className)}
{...props}
/>
)
}
export type AvatarFallbackProps = React.ComponentPropsWithRef<typeof BaseAvatar.Fallback> & {
size?: AvatarSize
}
export function AvatarFallback({
size = 'md',
className,
...props
}: AvatarFallbackProps) {
return (
<BaseAvatar.Fallback
className={cn(
'flex size-full items-center justify-center font-medium text-white',
avatarSizeClasses[size].text,
className,
)}
{...props}
/>
)
}
export const Avatar = ({
name,
@ -40,19 +87,16 @@ export const Avatar = ({
className,
onLoadingStatusChange,
}: AvatarProps) => {
const sizeClassNames = getAvatarSizeClassNames(size)
return (
<AvatarRoot className={cn(ROOT_CLASS_NAME, sizeClassNames.root, className)}>
<AvatarRoot size={size} className={className}>
{avatar && (
<AvatarImage
src={avatar}
alt={name}
className={IMAGE_CLASS_NAME}
onLoadingStatusChange={onLoadingStatusChange}
/>
)}
<AvatarFallback className={cn(FALLBACK_CLASS_NAME, sizeClassNames.text)}>
<AvatarFallback size={size}>
{name?.[0]?.toLocaleUpperCase()}
</AvatarFallback>
</AvatarRoot>