Skip to content

Commit fbf1e54

Browse files
authored
feat!: Revamped Avatar component (#1048)
* feat: add avatar utility primitives * fix: preserve unicode avatar initials * fix: preserve avatar utility type checks * fix: normalize avatar initials before comparison * feat: replace avatar primitive API * fix: retry avatar image after prop reset * docs: update avatar stories * docs: add avatar product wrappers * fix: preserve avatar initials and fallback examples * fix: cap avatar initials after uppercase expansion * fix: address avatar review findings * fix: type avatar story playground args * refactor: inline avatar accessibility props * refactor: rely on native avatar srcset selection * fix: retry avatar srcset candidates after load failure * refactor: simplify avatar initials generation * docs: document avatar public api * refactor: clarify avatar image identity key * refactor: Clean up and clarify * refactor: Use Box props where possible, restructure CSS * feat: Implement new stories * fix: Add inset border to avatars * fix: handle empty avatar state * refactor: Create improved avatar docs * Update avatar meta color tokens * refactor: Rework meta color CSS custom props * docs: add avatar migration guidance * fix: Add ref + passthrough props support * fix: Only set aria-hidden on container * test: add avatar axe coverage * fix: normalize avatar fallback labels * refactor: apply avatar meta colors to initials * refactor: simplify avatar initials splitting * perf: skip avatar source filtering when unchanged * refactor: reuse avatar size constants in stories * docs: render avatar migration table as markdown * feat: support polymorphic avatar root * refactor: move avatar size styles to css * refactor: reset only AvatarImage when key changes * refactor: More aria-hidden to AvatarImage * test: extract avatar image failure helper
1 parent 305f600 commit fbf1e54

11 files changed

Lines changed: 1734 additions & 387 deletions

File tree

src/avatar/__snapshots__/avatar.test.tsx.snap

Lines changed: 0 additions & 11 deletions
This file was deleted.

src/avatar/avatar.mdx

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import {
2+
Canvas,
3+
ColorItem,
4+
ColorPalette,
5+
Controls,
6+
Markdown,
7+
Meta,
8+
Subtitle,
9+
Title,
10+
} from '@storybook/addon-docs/blocks'
11+
12+
import * as AvatarStories from './avatar.stories'
13+
14+
<Meta of={AvatarStories} />
15+
16+
<Title />
17+
18+
<Subtitle>Image, initials, and empty-state avatar primitive.</Subtitle>
19+
20+
## Basic usage
21+
22+
Use `Avatar` for people by default. Pass `size`, `name`, and an optional
23+
`image`; `name` supplies the default accessible label, the initials fallback,
24+
and the deterministic meta color used when initials render.
25+
26+
<Canvas of={AvatarStories.Default} />
27+
28+
## Migrating from the legacy API
29+
30+
The previous Avatar API accepted `user`, `avatarUrl`, `colorList`, string or
31+
responsive `size` values, and a deprecated `className`. The current API uses
32+
direct identity props instead:
33+
34+
<Markdown>{`
35+
| Legacy prop | Current API |
36+
| ------------------------------ | ------------------------------------------------------------------------ |
37+
| \`user.name\` | \`name\` |
38+
| \`avatarUrl\` | \`image\` |
39+
| \`user.email\` | No replacement. Email is no longer used for initials or color selection. |
40+
| \`colorList\` | Customize the CSS custom properties listed below. |
41+
| \`size="l"\` or responsive sizes | Pass one supported numeric CSS-pixel \`size\`. |
42+
| \`className\` | \`exceptionallySetClassName\` |
43+
`}</Markdown>
44+
45+
```tsx
46+
<Avatar size={36} name={user.name} image={avatarUrl} exceptionallySetClassName={className} />
47+
```
48+
49+
## Initials fallback
50+
51+
When `image` is not supplied, cannot be resolved, or every responsive image
52+
candidate fails, Avatar falls back to initials derived from `name`. Names are
53+
normalized before initials are generated.
54+
55+
<Canvas of={AvatarStories.InitialsFallback} />
56+
57+
## Workspace avatars
58+
59+
Use `shape="rounded"` for workspace-like entities. Product code can wrap
60+
Avatar with a small convention component when a surface always represents the
61+
same kind of entity.
62+
63+
<Canvas of={AvatarStories.WorkspaceAvatar} />
64+
65+
## Image sources
66+
67+
Pass a string for a single image URL, or a source map keyed by intrinsic image
68+
width. Source maps render native `srcSet` width descriptors and a `sizes` hint
69+
based on the selected avatar size.
70+
71+
<Canvas of={AvatarStories.ImageSources} />
72+
73+
## Sizes
74+
75+
Avatar supports a fixed set of CSS pixel sizes. Use one of the supported
76+
numeric values instead of styling the avatar dimensions from the outside.
77+
78+
<Canvas of={AvatarStories.Sizes} />
79+
80+
## Accessibility
81+
82+
Images default to `name` for alt text. Pass `alt` when the visual needs a more
83+
specific label, and pass `alt=""` when the avatar is decorative.
84+
85+
<Canvas of={AvatarStories.Accessibility} />
86+
87+
## Playground
88+
89+
Use the controls to inspect the component API and common image/name
90+
combinations.
91+
92+
<Canvas of={AvatarStories.Playground} />
93+
94+
### API
95+
96+
<Controls of={AvatarStories.Playground} />
97+
98+
## Custom properties
99+
100+
The following CSS custom properties are available to customize the avatar
101+
component appearance. The values shown below are the default values.
102+
103+
<Canvas of={AvatarStories.MetaColors} />
104+
105+
### Customizable properties
106+
107+
#### Avatar colors
108+
109+
<ColorPalette>
110+
<ColorItem title="--reactist-avatar-initials-color" colors={['#ffffff']} />
111+
<ColorItem title="--reactist-avatar-border-tint" colors={['#0000001a']} />
112+
<ColorItem title="--reactist-avatar-empty-fill" colors={['#e6e6e6']} />
113+
</ColorPalette>
114+
115+
#### Avatar meta colors
116+
117+
<ColorPalette>
118+
<ColorItem title="--reactist-avatar-meta-0-fill" colors={['#b8255f']} />
119+
<ColorItem title="--reactist-avatar-meta-0-on-idle-tint" colors={['#ffffff']} />
120+
<ColorItem title="--reactist-avatar-meta-1-fill" colors={['#dc4c3e']} />
121+
<ColorItem title="--reactist-avatar-meta-1-on-idle-tint" colors={['#ffffff']} />
122+
<ColorItem title="--reactist-avatar-meta-2-fill" colors={['#f48318']} />
123+
<ColorItem title="--reactist-avatar-meta-2-on-idle-tint" colors={['#ffffff']} />
124+
<ColorItem title="--reactist-avatar-meta-3-fill" colors={['#fecf05']} />
125+
<ColorItem title="--reactist-avatar-meta-3-on-idle-tint" colors={['#202020']} />
126+
<ColorItem title="--reactist-avatar-meta-4-fill" colors={['#aeb83a']} />
127+
<ColorItem title="--reactist-avatar-meta-4-on-idle-tint" colors={['#ffffff']} />
128+
<ColorItem title="--reactist-avatar-meta-5-fill" colors={['#7ecc48']} />
129+
<ColorItem title="--reactist-avatar-meta-5-on-idle-tint" colors={['#ffffff']} />
130+
<ColorItem title="--reactist-avatar-meta-6-fill" colors={['#369307']} />
131+
<ColorItem title="--reactist-avatar-meta-6-on-idle-tint" colors={['#ffffff']} />
132+
<ColorItem title="--reactist-avatar-meta-7-fill" colors={['#52ccb8']} />
133+
<ColorItem title="--reactist-avatar-meta-7-on-idle-tint" colors={['#ffffff']} />
134+
<ColorItem title="--reactist-avatar-meta-8-fill" colors={['#148fad']} />
135+
<ColorItem title="--reactist-avatar-meta-8-on-idle-tint" colors={['#ffffff']} />
136+
<ColorItem title="--reactist-avatar-meta-9-fill" colors={['#3ab9e2']} />
137+
<ColorItem title="--reactist-avatar-meta-9-on-idle-tint" colors={['#202020']} />
138+
<ColorItem title="--reactist-avatar-meta-10-fill" colors={['#96c3eb']} />
139+
<ColorItem title="--reactist-avatar-meta-10-on-idle-tint" colors={['#ffffff']} />
140+
<ColorItem title="--reactist-avatar-meta-11-fill" colors={['#2a67e2']} />
141+
<ColorItem title="--reactist-avatar-meta-11-on-idle-tint" colors={['#ffffff']} />
142+
<ColorItem title="--reactist-avatar-meta-12-fill" colors={['#692ec2']} />
143+
<ColorItem title="--reactist-avatar-meta-12-on-idle-tint" colors={['#ffffff']} />
144+
<ColorItem title="--reactist-avatar-meta-13-fill" colors={['#ac30cc']} />
145+
<ColorItem title="--reactist-avatar-meta-13-on-idle-tint" colors={['#ffffff']} />
146+
<ColorItem title="--reactist-avatar-meta-14-fill" colors={['#eb96c8']} />
147+
<ColorItem title="--reactist-avatar-meta-14-on-idle-tint" colors={['#ffffff']} />
148+
<ColorItem title="--reactist-avatar-meta-15-fill" colors={['#e05095']} />
149+
<ColorItem title="--reactist-avatar-meta-15-on-idle-tint" colors={['#ffffff']} />
150+
<ColorItem title="--reactist-avatar-meta-16-fill" colors={['#c9766f']} />
151+
<ColorItem title="--reactist-avatar-meta-16-on-idle-tint" colors={['#ffffff']} />
152+
<ColorItem title="--reactist-avatar-meta-17-fill" colors={['#808080']} />
153+
<ColorItem title="--reactist-avatar-meta-17-on-idle-tint" colors={['#ffffff']} />
154+
<ColorItem title="--reactist-avatar-meta-18-fill" colors={['#999999']} />
155+
<ColorItem title="--reactist-avatar-meta-18-on-idle-tint" colors={['#ffffff']} />
156+
<ColorItem title="--reactist-avatar-meta-19-fill" colors={['#ccae96']} />
157+
<ColorItem title="--reactist-avatar-meta-19-on-idle-tint" colors={['#ffffff']} />
158+
</ColorPalette>
159+
160+
### Component-owned variables
161+
162+
Avatar's size classes set these variables from the `size` prop. They are
163+
listed for completeness, but consumers should prefer the component props
164+
instead of overriding them directly.
165+
166+
```css
167+
.avatar {
168+
--reactist-avatar-size: 36px;
169+
--reactist-avatar-rounded-radius: 5px;
170+
}
171+
```
172+
173+
## What the consumer owns
174+
175+
- **Identity data** — choose the `name`, `image`, and any custom `alt` text.
176+
- **Source selection** — provide either one URL or a width-keyed source map.
177+
- **Entity convention** — choose `shape="circle"` for people and
178+
`shape="rounded"` for workspace-like entities.
179+
- **Decorative usage** — pass `alt=""` when surrounding UI already names the
180+
represented entity.
181+
- **Persistence and fetching** — Avatar does not load, cache, or persist remote
182+
user/workspace data.
183+
184+
## Accessibility
185+
186+
- `name` becomes the default image `alt` text and initials `aria-label`.
187+
- `alt` overrides the accessible label for both image and initials rendering.
188+
- `alt=""` marks image and initials avatars as decorative.
189+
- An avatar with no `name` and no `image` renders as an empty decorative visual.

0 commit comments

Comments
 (0)