Skip to content

Commit fd1bfad

Browse files
committed
fix(emcn): keep a composed avatar's status announced and export fallback variants
1 parent 0ee3ae1 commit fd1bfad

2 files changed

Lines changed: 29 additions & 3 deletions

File tree

‎packages/emcn/src/components/avatar/avatar.test.tsx‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,18 @@ describe('Avatar', () => {
8585
)
8686
})
8787

88+
it('announces the status dot itself on a composed avatar', async () => {
89+
const view = await mount(
90+
<Avatar status='away'>
91+
<AvatarFallback>AL</AvatarFallback>
92+
</Avatar>
93+
)
94+
const dot = view.querySelector('[data-slot="avatar-status"]')
95+
expect(dot?.getAttribute('role')).toBe('img')
96+
expect(dot?.getAttribute('aria-label')).toBe('Status: away')
97+
expect(dot?.hasAttribute('aria-hidden')).toBe(false)
98+
})
99+
88100
it('never labels an image with an empty name', async () => {
89101
const view = await mount(<Avatar size='xs' name=' ' />)
90102
expect(view.querySelector('[role="img"]')).toBeNull()

‎packages/emcn/src/components/avatar/avatar.tsx‎

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,14 @@ const Avatar = React.forwardRef<React.ElementRef<typeof AvatarPrimitive.Root>, A
130130
({ className, size, status, name, src, children, ...props }, ref) => {
131131
const hidden = props['aria-hidden'] === true || props['aria-hidden'] === 'true'
132132
const label = name?.trim()
133+
const labelled = Boolean(label) && !hidden
133134
return (
134135
<AvatarSizeContext.Provider value={size}>
135136
<div className='relative inline-flex'>
136137
<AvatarPrimitive.Root
137138
ref={ref}
138139
className={cn(avatarVariants({ size }), className)}
139-
{...(label && !hidden
140+
{...(labelled
140141
? { role: 'img', 'aria-label': status ? `${label}, ${status}` : label }
141142
: {})}
142143
{...props}
@@ -150,11 +151,17 @@ const Avatar = React.forwardRef<React.ElementRef<typeof AvatarPrimitive.Root>, A
150151
</>
151152
)}
152153
</AvatarPrimitive.Root>
154+
{/**
155+
* A named avatar carries its status in its own label, and a hidden one
156+
* carries nothing; only a composed avatar announces the dot itself.
157+
*/}
153158
{status && (
154159
<span
155160
data-slot='avatar-status'
156161
className={cn(avatarStatusVariants({ status, size }))}
157-
aria-hidden
162+
{...(labelled || hidden
163+
? { 'aria-hidden': true }
164+
: { role: 'img', 'aria-label': `Status: ${status}` })}
158165
/>
159166
)}
160167
</div>
@@ -206,4 +213,11 @@ const AvatarFallback = React.forwardRef<
206213
})
207214
AvatarFallback.displayName = 'AvatarFallback'
208215

209-
export { Avatar, AvatarImage, AvatarFallback, avatarVariants, avatarStatusVariants }
216+
export {
217+
Avatar,
218+
AvatarImage,
219+
AvatarFallback,
220+
avatarVariants,
221+
avatarStatusVariants,
222+
avatarFallbackVariants,
223+
}

0 commit comments

Comments
 (0)