Skip to content

feat(avatar): add Avatar compound atom (size × shape, AvatarGroup) — D14#77

Merged
dgraciac merged 4 commits into
mainfrom
feat/avatar
May 11, 2026
Merged

feat(avatar): add Avatar compound atom (size × shape, AvatarGroup) — D14#77
dgraciac merged 4 commits into
mainfrom
feat/avatar

Conversation

@dgraciac
Copy link
Copy Markdown
Member

Summary

Adds the ninth Pharos atom: a compound Avatar + AvatarImage + AvatarFallback + AvatarGroup following the shadcn / Radix / Base UI contract. Decision D14 documented in full in NAMING-decisions.md.

State-of-the-art validation ahead of the decision: 8/10 top-tier DS ship Avatar as a dedicated atom (shadcn, Radix Primitives, Base UI, MUI, Chakra, Mantine, Ant Design, Polaris). The two that do not in core (Material 3, Carbon) delegate to their implementer library. Cardinal-rule bar (6/8) cleared with margin.

API highlights

  • Three named sizes (sm / md / lg = 32 / 40 / 48 px) aligned to the IconButton height grid.
  • Numeric escape size={number} for one-off cases outside the canonical grid (108 px profile picture, 20 px compact stack — directly motivated by the Alexandria audit).
  • Two shapes: circle (default, person-shaped) and square (radius-md, for orgs / products).
  • Explicit fallback composition (Escuela 1, D11) — no magic name prop, no built-in initials computation. Consumer puts text / icon / image inside <AvatarFallback>.
  • AvatarGroup ships in v1 (Alexandria's AvatarList / PeopleList already render avatar stacks): max caps visible avatars with a +N overflow terminal, size / shape cascade to children, ring renders as box-shadow via --pharos-avatar-group-ring (defaults to base-white, override-able for tinted surfaces).
  • render prop on the root composes the Avatar as a different element (<Avatar render={<a href="..." />} />).

What v1 leaves out (deliberate)

  • No status badge primitive — composition with a future Badge positioning helper.
  • No name → initials derivation — composition stays explicit.
  • No color / variant axis — single neutral fallback surface.

Quality

  • 136 tests pass (22 new for Avatar covering root / image fallback chain / group cascade / overflow).
  • pnpm typecheck, pnpm lint, pnpm format:check, pnpm build, pnpm verify:dist-types all green.
  • Changeset added (.changeset/avatar-atom.md) — minor bump → 0.11.0.
  • Branch rebased on top of main (post Dependabot batch).

Storybook

New stories: Components/Avatar → Playground, Default, Sizes (3 named + 108 numeric), Shapes (circle / square), Fallbacks (initials / icon / image-error → initials), Group (3-avatar + 5-avatar with max=3), RenderAsLink, Matrix (sizes × shapes).

Alexandria mapping (preview, no adoption PR yet)

Documented in NAMING-decisions.md § Avatar:

  • AvatarList (20 px overlap) → <AvatarGroup size={20} max={N}>
  • PeopleList (32 px overlap) → <AvatarGroup size="sm" max={N}>
  • UserMenu, MobileHeader, RevieweeTable, ForkedAssessmentCard, ModifyRevieweeRow, TeamMembers<Avatar size="md">
  • ProfileSummary, UserProfileBox<Avatar size={108}> (numeric escape)
  • request-access org logo → <Avatar size={100} shape="square">

Adoption PR opens in alexandria-web-application once this releases on npm and the CTO greenlights (pausa Alexandria respected).

Test plan

  • CI green (lint + typecheck + build + tests).
  • Chromatic baseline reviewed for new Avatar stories.
  • Visual review of compound parts: image load, fallback gate, group overlap, overflow +N.
  • Spot-check data-pharos-* attrs from Storybook DOM (size / shape / loading status all stable).

🤖 Generated with Claude Code

Compound `Avatar` + `AvatarImage` + `AvatarFallback` + `AvatarGroup`
following the shadcn / Radix / Base UI contract. State-of-the-art
validation: 8/10 top-tier DS ship Avatar as a dedicated atom; the
two that do not (Material 3, Carbon core) delegate to their
implementer library which does. Cardinal-rule bar (6/8) cleared.

API highlights:

- 3 named sizes (sm/md/lg = 32/40/48 px) aligned to the IconButton
  grid + numeric `size={number}` escape for one-off cases
  (108 px profile picture, 20 px compact stack).
- 2 shapes: circle (default) for people, square (radius-md) for
  orgs / products.
- Explicit fallback composition (Escuela 1, D11) — no magic `name`
  prop, no built-in initials computation.
- AvatarGroup with `max` overflow ("+N" terminal avatar) and
  `--pharos-avatar-group-ring` overrideable via CSS var.
- `render` prop on the root for link composition.
- No status badge primitive in v1 — composition with future Badge
  positioning when needed.

22 new tests (136 total green). NAMING-decisions.md documents D14
in full with Alexandria mapping (AvatarList, PeopleList, PersonView,
UserAvatar, ProfileSummary, assessment tables, team / access pages).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@chromatic-com
Copy link
Copy Markdown

chromatic-com Bot commented May 11, 2026

Tip

All tests passed and all changes approved!

🟢 UI Tests: 9 visual and accessibility changes accepted as baselines
🟢 UI Review: Approved by David Gracia
Storybook icon Storybook Publish: 67 stories published

dgraciac and others added 3 commits May 11, 2026 16:49
Bug: with the root in `display: inline-flex`, `<AvatarImage>` and
`<AvatarFallback>` rendered as flex siblings during the load
window. Both carry `width: 100%`, so the browser shrank them to
50/50 inside the avatar — visible as a fractured chrome at size=lg
and on any docs page rendering multiple Avatars before the
browsers decoded the pictures (the deployed Storybook docs route
showed this; the Chromatic snapshots looked fine because they wait
for image decode before snapping).

Fix: lift the image out of the flex flow with `position: absolute`
and anchor it via `position: relative` on the root. The fallback
stays as the single flex item that centres its content; the image
fades in on top once the pixels arrive. Same layering pattern
Radix Primitives and Base UI Avatar document.

136 tests still green; no API change, only CSS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Overview gallery (`Pharos/Overview` story) is the single page
where humans eyeball every published component side by side, and
the single Chromatic snapshot that catches cross-component
regressions. Each new Phase 2 atom adds a Showcase entry per the
master plan's 2.X.3 checklist; Avatar joins the Primitives section.

Showcase content covers the three composition paths the atom
documents: image + initials fallback (default circle person),
icon fallback inside a square shape (org / product), initials-only
fallback (no source), and an AvatarGroup with `max={3}` overflow.

Note: Textarea, Spinner, and IconButton remain absent from the
gallery — that drift predates this PR and is logged for a
follow-up. Adding Avatar restores the cadence for everything
shipped from Card onward.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Phase 2 atoms that landed between Card and Avatar
(`Textarea`, `Spinner`, `IconButton`) never made it into the
`Pharos/Overview` gallery — drift that broke the master plan's
2.X.3 rule ("each new atom adds a Showcase entry"). The previous
commit added Avatar; this one closes the gap retroactively so
the gallery again lists every published primitive side by side
and the cross-component Chromatic snapshot covers them.

Showcase contracts mirror the existing patterns:
- Textarea: 4 states (default / readonly / invalid / disabled),
  parallel to InputShowcase.
- Spinner: 3 sizes (sm / md / lg), the only axis the atom exposes.
- IconButton: 4 intents + a loading example.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dgraciac dgraciac merged commit 584821e into main May 11, 2026
10 checks passed
@dgraciac dgraciac deleted the feat/avatar branch May 11, 2026 23:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant