Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7d77380

Browse files
dabowmanclaude
andcommitted
Components: Refine Avatar and AvatarGroup for PR readiness
- Remove built-in `.is-active` status override; all statuses now get uniform dimming so the consumer controls differentiation via the dynamic `is-{status}` class. - Preserve Tooltip on badge avatars when `label` differs from `name`, so hovering reveals the full name even when the badge shows a contextual label like "You". - Replace `cloneElement` z-index injection in AvatarGroup with a CSS `@for` loop on `:nth-child`, removing the legacy React API and its type assertion. - Add `role="group"` to AvatarGroup for screen reader context; consumers provide `aria-label` via props. - Add `aria-label` to the overflow indicator so "+2" reads as "2 more". - Add MixedBadgeAndTooltip story covering badge, tooltip, and label combinations in a group. - Improve JSDoc: clarify `badge` requires `name`, describe `status` as an open-ended styling hook. - Add CHANGELOG entry. Co-Authored-By: Claude Opus 4.6 <[email protected]>
1 parent f16792c commit 7d77380

9 files changed

Lines changed: 74 additions & 37 deletions

File tree

packages/components/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
### Internal
1919

20+
- `Avatar`, `AvatarGroup`: Add new private components ([#75591](https://github.com/WordPress/gutenberg/pull/75591)).
2021
- Remove `Picker` from private APIs ([#75394](https://github.com/WordPress/gutenberg/pull/75394)).
2122
- Expose `useDrag` from `@use-gesture/react` package via private API's ([#66735](https://github.com/WordPress/gutenberg/pull/66735)).
2223
- `Disabled`, `Modal`, `Popover`, `Tooltip`: Move context code to separate files to help docgen prop extraction ([#75316](https://github.com/WordPress/gutenberg/pull/75316)).

packages/components/src/avatar-group/component.tsx

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import clsx from 'clsx';
66
/**
77
* WordPress dependencies
88
*/
9-
import { Children, cloneElement, isValidElement } from '@wordpress/element';
9+
import { Children } from '@wordpress/element';
1010

1111
/**
1212
* Internal dependencies
@@ -26,25 +26,16 @@ function AvatarGroup( {
2626

2727
return (
2828
<div
29+
role="group"
2930
className={ clsx( 'components-avatar-group', className ) }
3031
{ ...props }
3132
>
32-
{ visible.map( ( child, index ) =>
33-
isValidElement( child )
34-
? cloneElement( child, {
35-
style: {
36-
...( (
37-
child.props as {
38-
style?: React.CSSProperties;
39-
}
40-
).style ?? {} ),
41-
zIndex: visible.length - index,
42-
},
43-
} )
44-
: child
45-
) }
33+
{ visible }
4634
{ overflowCount > 0 && (
47-
<span className="components-avatar-group__overflow">
35+
<span
36+
className="components-avatar-group__overflow"
37+
aria-label={ `${ overflowCount } more` }
38+
>
4839
{ `+${ overflowCount }` }
4940
</span>
5041
) }

packages/components/src/avatar-group/stories/index.story.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,34 @@ export const WithBadges: Story = {
8888
},
8989
};
9090

91+
export const MixedBadgeAndTooltip: Story = {
92+
render: ( args ) => (
93+
<AvatarGroup { ...args }>
94+
<Avatar
95+
src={ avatarUrl( 'aaa' ) }
96+
name="Alice"
97+
borderColor="#3858e9"
98+
badge
99+
/>
100+
<Avatar src={ avatarUrl( 'bbb' ) } name="Bob" />
101+
<Avatar
102+
src={ avatarUrl( 'ccc' ) }
103+
name="Charlie"
104+
borderColor="#58e938"
105+
badge
106+
label="You"
107+
/>
108+
</AvatarGroup>
109+
),
110+
parameters: {
111+
docs: {
112+
description: {
113+
story: 'Mixing badge avatars with tooltip-only avatars. The third avatar uses `label` to show "You" in the badge while preserving the full name in the tooltip.',
114+
},
115+
},
116+
},
117+
};
118+
91119
export const CustomMax: Story = {
92120
args: {
93121
max: 2,

packages/components/src/avatar-group/styles.scss

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,17 @@
1010
margin-inline-start: -$grid-unit-10;
1111
}
1212

13-
// Elevate hovered avatar so the expanding badge renders on top.
13+
// Stack earlier avatars on top of later ones so the overlap
14+
// is visually correct, and elevate on hover for badge expansion.
1415
> .components-avatar {
1516
position: relative;
1617

18+
@for $i from 1 through 10 {
19+
&:nth-child(#{$i}) {
20+
z-index: #{11 - $i};
21+
}
22+
}
23+
1724
&:hover {
1825
z-index: 100;
1926
}
@@ -24,6 +31,6 @@
2431
margin-inline-start: $grid-unit-05;
2532
font-size: $font-size-small;
2633
line-height: $font-line-height-small;
27-
color: $gray-700;
34+
color: $gray-900;
2835
white-space: nowrap;
2936
}

packages/components/src/avatar-group/test/index.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ describe( 'AvatarGroup', () => {
4444
expect(
4545
screen.queryByRole( 'img', { name: 'Charlie' } )
4646
).not.toBeInTheDocument();
47-
expect( screen.getByText( '+2' ) ).toBeInTheDocument();
47+
const overflow = screen.getByText( '+2' );
48+
expect( overflow ).toBeInTheDocument();
49+
expect( overflow ).toHaveAttribute( 'aria-label', '2 more' );
4850
} );
4951

5052
it( 'should default max to 3', () => {
@@ -87,4 +89,16 @@ describe( 'AvatarGroup', () => {
8789
expect( group ).toHaveClass( 'components-avatar-group' );
8890
expect( group ).toHaveClass( 'custom' );
8991
} );
92+
93+
it( 'should have group role and support aria-label', () => {
94+
render(
95+
<AvatarGroup aria-label="Collaborators">
96+
<Avatar name="A" />
97+
</AvatarGroup>
98+
);
99+
const group = screen.getByRole( 'group', {
100+
name: 'Collaborators',
101+
} );
102+
expect( group ).toBeInTheDocument();
103+
} );
90104
} );

packages/components/src/avatar/component.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ function Avatar( {
7272
</div>
7373
);
7474

75-
if ( ! showBadge && name ) {
75+
if ( name && ( ! showBadge || label ) ) {
7676
return <Tooltip text={ name }>{ avatar }</Tooltip>;
7777
}
7878

packages/components/src/avatar/styles.scss

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -128,16 +128,6 @@
128128
background-color: var(--components-avatar-border-color);
129129
}
130130

131-
.components-avatar.is-active.has-src > .components-avatar__image {
132-
&::before {
133-
opacity: 0.2;
134-
}
135-
}
136-
137-
.components-avatar.is-active:not(.has-src) > .components-avatar__image {
138-
color: rgba($white, 0.2);
139-
}
140-
141131
.components-avatar__status-indicator {
142132
position: absolute;
143133
inset: 0;

packages/components/src/avatar/test/index.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,14 @@ describe( 'Avatar', () => {
122122
const avatar = screen.getByRole( 'img', { name: 'Jane Doe' } );
123123
expect( avatar ).toBeInTheDocument();
124124
} );
125+
126+
it( 'should wrap in tooltip when label differs from name', () => {
127+
render( <Avatar name="Jane Doe" label="You" badge /> );
128+
const avatar = screen.getByRole( 'img', { name: 'Jane Doe' } );
129+
// The Tooltip's Ariakit.TooltipAnchor makes the element
130+
// focusable so the tooltip can be triggered via keyboard.
131+
expect( avatar ).toHaveAttribute( 'tabindex', '0' );
132+
} );
125133
} );
126134

127135
describe( 'status', () => {

packages/components/src/avatar/types.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export type AvatarProps = {
2020
label?: string;
2121
/**
2222
* Whether to show the hover-expand badge that reveals the user's
23-
* name (or `label`) on hover.
23+
* name (or `label`) on hover. Requires `name` to be set.
2424
*
2525
* @default false
2626
*/
@@ -38,15 +38,13 @@ export type AvatarProps = {
3838
* CSS color value for an accent border ring around the avatar.
3939
*
4040
* When not provided, no border is rendered and the hover badge
41-
* uses the admin theme color as its background.
41+
* and avatar status uses the admin theme color as its background.
4242
*/
4343
borderColor?: string;
4444
/**
45-
* The current status of the user. When set, the avatar image is
46-
* dimmed to indicate a non-default state.
47-
*
48-
* - `'active'`: 20% image opacity (e.g. typing, editing).
49-
* - `'idle'`: 30% image opacity (e.g. session timeout).
45+
* A status string applied to the avatar. When set, the image is
46+
* dimmed to indicate a non-default state. A corresponding
47+
* `is-{status}` class is added for custom styling.
5048
*/
5149
status?: string;
5250
/**

0 commit comments

Comments
 (0)