GrAvatar

Пакет: @feugene/granularityядроГруппа: Данные

Берут, когда человека или сущность узнают по картинке.

Когда брать

  • человека или сущность узнают по картинке — список участников, автор комментария, владелец задачи;
  • картинки может не быть — откат идёт по цепочке: src, fallbackSrc, инициалы из name, слот;
  • нужен статус присутствия — точка на аватаре вместо отдельного значка рядом;
  • участников несколькоGrAvatarGroup собирает их в ряд с наложением и счётчиком.

Когда взять другое

НужноБерите
Изображение нужно рассмотретьGrImageViewer
Показывается иконка, а не человекGrIcon
Нужен счётчик поверх контролаGrBadgeWrap
Статус текстомGrBadge

Что показывается

Порядок отката: srcfallbackSrc → инициалы из name → слот по умолчанию.

<GrAvatar src="https://cdn.example/u/42.png" fallback-src="/avatar.svg" name="Ada Lovelace" />

Битая ссылка обрабатывается: по @error компонент переходит к следующему варианту, и браузер не рисует иконку сломанного изображения. Смена src сбрасывает ошибку — новая ссылка не наследует судьбу прошлой.

Пока картинка едет, её место держит скелет: иначе ряд аватаров мигает пустыми кружками.

Имя и инициалы

name даёт инициалы (первые буквы двух первых слов) и доступное имя. Когда есть картинка, имя несёт её alt; без картинки роль img и имя берёт на себя сам аватар — иначе кружок с инициалами для диктора пуст. alt сильнее name, слот сильнее инициалов.

Функция инициалов доступна отдельно: import { initialsFrom } from '@feugene/granularity'.

Кегль считается от диаметра — треть, но не меньше 10px. Размер аватара бывает произвольным числом пикселей, поэтому карта по ступеням шкалы его не закрывает. Правило одно на инициалы, слотовый текст и счётчик «+N» у GrAvatarGroup: они стоят в одном ряду, и разные пропорции читались бы как сбой.

Статус

<GrAvatar name="Ada Lovelace" status="online" />

online | offline | busy | away. Точка декоративна (aria-hidden), а статус объявляется словом из локали (gr.avatar.status.*): цвет сам по себе смысл не передаёт.

Ряд участников

<GrAvatarGroup :max="3" :total="9" aria-label="Команда релиза">
  <GrAvatar v-for="member in team" :key="member.id" :name="member.name" />
</GrAvatarGroup>

max ограничивает число видимых аватаров, остаток сворачивается в «+N»; total нужен, когда в слоте лежит лишь часть участников. Группа объявлена role="group", а её имя содержит и подпись, и число скрытых — диктор слышит «Команда релиза, и ещё 6», а не набор безымянных картинок.

size и shape группы доходят до детей через контекст, чтобы ряд не расползался.

Размер и форма

size принимает ступень шкалы (xslg, читается из GrConfigProvider) или число пикселей — у аватара исторически произвольный диаметр. shape (circle | square) тоже настраивается глобально через componentDefaults.GrAvatar.shape.

Playground 5

Загружается…

Код
<GrAvatar />

Установка

npm i @feugene/granularity

Импорт

import { GrAvatar } from '@feugene/granularity/components/GrAvatar'

API

Props

PropTypeпо умолчаниюОписание
sizeGrSizeWithPx | undefinedundefinedРазмер по канонической шкале (`xs|sm|md|lg`) — тогда работает `GrConfigProvider`. Число — escape-hatch: аватар нужен произвольного диаметра (24px в строке списка, 96px в профиле), и четыре ступени шкалы этого не закрывают.
namestring | undefinedundefinedИмя участника: даёт инициалы и доступное имя аватара.
srcstring | undefinedundefined
fallbackSrcstring | undefinedundefinedРезервная картинка: показывается, когда `src` не загрузился.
altstring | undefinedundefined
shapeGrAvatarShape | undefinedundefined
status"online" | "offline" | "busy" | "away" | undefinedundefinedСтатус участника. Точка декоративна — рядом идёт скрытая подпись.

Slots

SlotTypeОписание
defaultanyСодержимое вместо картинки и инициалов.

Примеры 3

Размеры, круг и квадрат

Минимальный набор размеров и форм помогает быстро понять, как GrAvatar ведёт себя для людей и для team/workspace сущностей.

ADADADAD
QAPMUX

Size Shape
<script setup lang="ts">
import { GrAvatar } from '@feugene/granularity'
</script>

<template>
  <div class="grid gap-4">
    <div class="flex flex-wrap items-center gap-4">
      <GrAvatar :size="32">AD</GrAvatar>
      <GrAvatar :size="40">AD</GrAvatar>
      <GrAvatar :size="56">AD</GrAvatar>
      <GrAvatar :size="72">AD</GrAvatar>
    </div>

    <div class="flex flex-wrap items-center gap-4">
      <GrAvatar :size="40" shape="square">QA</GrAvatar>
      <GrAvatar :size="56" shape="square">PM</GrAvatar>
      <GrAvatar :size="72" shape="square">UX</GrAvatar>
    </div>
  </div>
</template>

Картинка и откат в слот по умолчанию

Показываем основной contract компонента: src рендерит изображение, а при его отсутствии тот же размер сохраняется для fallback-контента.

в сети
Fallback contract
A broken `src` falls back to `fallbackSrc`, then to initials from `name` — the browser never shows its broken-image icon. Without `src` the default slot is rendered as before.

Image Fallback
<script setup lang="ts">
import { GrAvatar, GrCard } from '@feugene/granularity'

const avatarSvg = encodeURIComponent(`
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" fill="none">
    <rect width="96" height="96" fill="#dbeafe" />
    <circle cx="48" cy="36" r="16" fill="#2563eb" opacity="0.18" />
    <path d="M18 80c6-15 18-23 30-23s24 8 30 23" fill="#2563eb" opacity="0.26" />
    <circle cx="48" cy="36" r="13" fill="#2563eb" />
  </svg>
`)

const avatarImageSrc = `data:image/svg+xml;charset=UTF-8,${avatarSvg}`
</script>

<template>
  <div class="grid gap-4 lg:grid-cols-[minmax(0,1fr)_220px] lg:items-center">
    <div class="flex flex-wrap items-center gap-4">
      <GrAvatar :size="40" :src="avatarImageSrc" alt="Alex Doe" />
      <GrAvatar :size="56" :src="avatarImageSrc" alt="Alex Doe" status="online" />
      <GrAvatar :size="72" shape="square" :src="avatarImageSrc" alt="Alex Doe" />

      <!-- Битая ссылка — основной сценарий отказа: показываются инициалы из `name`. -->
      <GrAvatar :size="56" src="/broken-avatar.png" name="Alex Doe" />
      <GrAvatar :size="56" src="/broken-avatar.png" :fallback-src="avatarImageSrc" name="Alex Doe" />
    </div>

    <GrCard class="grid gap-2 p-4 text-sm text-[var(--gr-muted-fg)]">
      <div class="font-semibold text-[var(--gr-fg)]">
        Fallback contract
      </div>
      <div>
        A broken `src` falls back to `fallbackSrc`, then to initials from `name` — the browser never shows its
        broken-image icon. Without `src` the default slot is rendered as before.
      </div>
    </GrCard>
  </div>
</template>

Композиция в строке участника или команды

На практике GrAvatar почти всегда живёт рядом с именем, ролью и secondary text — поэтому документируем и такой composed layout.

ADв сети
Alex Doe
Engineering lead
Release squad
ADв сетиQAзанятSRотошёл
Стекинг с «+N»: группа объявляет диктору и имя, и число скрытых участников.

Team Row
<script setup lang="ts">
import { GrAvatar, GrAvatarGroup, GrCard } from '@feugene/granularity'

const team = [
  { name: 'Alex Doe', status: 'online' as const },
  { name: 'Quinn Ali', status: 'busy' as const },
  { name: 'Sam Rivera', status: 'away' as const },
  { name: 'Noor Haddad', status: 'offline' as const },
]
</script>

<template>
  <div class="grid gap-3">
    <GrCard class="grid gap-3 p-4">
      <div class="flex items-center gap-3">
        <GrAvatar :size="44" name="Alex Doe" status="online" />
        <div>
          <div class="text-sm font-semibold text-[var(--gr-fg)]">
            Alex Doe
          </div>
          <div class="text-sm text-[var(--gr-muted-fg)]">
            Engineering lead
          </div>
        </div>
      </div>
    </GrCard>

    <GrCard class="grid gap-3 p-4">
      <div class="text-sm font-semibold text-[var(--gr-fg)]">
        Release squad
      </div>
      <GrAvatarGroup :max="3" :total="9" size="md" aria-label="Release squad">
        <GrAvatar v-for="member in team" :key="member.name" :name="member.name" :status="member.status" />
      </GrAvatarGroup>
      <div class="text-sm text-[var(--gr-muted-fg)]">
        Стекинг с «+N»: группа объявляет диктору и имя, и число скрытых участников.
      </div>
    </GrCard>
  </div>
</template>

Документация компонентаВсе компоненты