GrIcon

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

Берут, когда иконке нужен единый размер.

Когда брать

  • иконке нужен единый размер--gr-icon-size вместо ширины и высоты в каждом месте;
  • иконка берёт цвет из палитрыtone вместо своего fill;
  • иконка значимаlabel даёт ей имя; без него она скрыта от скринридера, и это верно по умолчанию;
  • иконка вращаетсяspin для состояния ожидания без своей анимации.

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

НужноБерите
Иконка внутри кнопкиGrButton с square
Аватар человекаGrAvatar
Клавиша на клавиатуреGrKbd
Индикатор ожиданияGrLoading

Саму иконку компонент не рисует — она приходит слотом из любого набора (unplugin-icons, свой SVG, спрайт). Своего набора иконок пакет не поставляет и не будет: дублировать поставку вредно.

Декоративная и значимая

По умолчанию иконка декоративна: компонент сам ставит aria-hidden="true". В библиотеке иконка почти всегда сопровождает текст, и раньше атрибут писали руками в каждом месте вызова — достаточно забыть один раз, и диктор прочитает <title> из SVG.

Значимой иконку делает label: появляются role="img" и aria-label, а скрытие снимается.

<!-- рядом есть текст «Сохранено» — иконка декоративна -->
<GrIcon>
<IconCheck />
</GrIcon>

<!-- иконка сама несёт смысл -->
<GrIcon label="Проверено">
<IconCheck />
</GrIcon>

Редкий случай «иконка значима, но имя даёт соседний элемент» выражается обычным атрибутом: fallthrough сильнее собственной привязки компонента.

Размер

size — шкала пакета (xs | sm | md | lg, по умолчанию md) или произвольное число в пикселях. Шкала читается из GrConfigProvider, число — локальный escape-hatch мимо конфига.

Значения шкалы живут токенами --gr-icon-size-xs…lg, поэтому размер иконок настраивается темой, а не пересборкой пакета. Сама переменная --gr-icon-size — точка кастомизации на месте: и обёртка, и вложенный SVG считают себя от неё.

Тон и вращение

tone красит иконку токеном текста (current по умолчанию — цвет наследуется от родителя). Насыщенный тон как цвет текста в пакете запрещён: на светлой подложке он даёт контраст около 2:1, поэтому используются -text роли, как в GrAlert.

spin включает вращение для спиннеров. Отдельной поддержки prefers-reduced-motion компоненту не нужно: глобальный кламп в base.css зажимает анимации, и вращение замирает в исходном положении, а не под случайным углом.

Стиль намеренно глобальный

Правило .gr-icon не scoped: SVG приходит слотом потребителя, и scoped-стиль до него не достаёт — пришлось бы писать :deep в каждом месте вызова.

Playground 2

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

Код
<GrIcon />

Установка

npm i @feugene/granularity

Импорт

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

API

Props

PropTypeпо умолчаниюОписание
toneGrIconTone | undefined"current"Цвет из палитры. `current` — наследовать цвет текста родителя.
sizenumber | "xs" | "sm" | "md" | "lg" | undefinedundefinedРазмер по шкале пакета либо произвольный в пикселях.
labelstring | undefinedundefinedИмя значимой иконки. Задан — иконка объявляется `role="img"` и перестаёт быть скрытой. Не задан — иконка декоративна.
spinboolean | undefinedfalseВращение — для спиннеров.

Slots

SlotTypeОписание
defaultanyСвоя разметка иконки вместо имени из набора.

Примеры 4

Шкала размеров

На странице важно показать, как GrIcon ведёт себя на разных размерах и почему он удобен как sizing-wrapper вокруг inline svg.

12px
16px
20px
28px
36px

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

const sizes = [12, 16, 20, 28, 36]
</script>

<template>
  <div class="flex flex-wrap items-end gap-5">
    <div
        v-for="size in sizes"
        :key="size"
        class="flex flex-col items-center gap-2 text-xs text-[var(--gr-muted-fg)]"
    >
      <GrIcon :size="size">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" class="h-full w-full">
          <path d="M12 3 14.8 8.7 21 9.6 16.5 14l1 6.1L12 17.1 6.5 20.1l1-6.1L3 9.6l6.2-.9L12 3Z" />
        </svg>
      </GrIcon>

      <span>{{ size }}px</span>
    </div>
  </div>
</template>

Иконка в строке текста и рядом со ссылкой

Показываем, что GrIcon можно встраивать в copy blocks, helper rows и рядом с GrLink, не ломая baseline текста.

Sync billing status every 5 minutes
Re-run failed payout webhooks automatically
Publish audit log snapshots to support inbox
Explore icon usage inside inline content (откроется в новой вкладке)

Inline Copy
<script setup lang="ts">
import { GrIcon, GrLink } from '@feugene/granularity'

const items = [
  'Sync billing status every 5 minutes',
  'Re-run failed payout webhooks automatically',
  'Publish audit log snapshots to support inbox',
]
</script>

<template>
  <div class="grid gap-3">
    <div
      v-for="item in items"
      :key="item"
      class="flex items-start gap-3 rounded-xl border border-[var(--gr-brd)] bg-[var(--gr-bg)] p-4"
    >
      <GrIcon size="md" class="mt-0.5 text-[var(--gr-primary-text)]">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" class="h-full w-full">
          <path d="M4 12h16" />
          <path d="m12 5 7 7-7 7" />
        </svg>
      </GrIcon>

      <div class="min-w-0 text-sm text-[var(--gr-fg)]">
        {{ item }}
      </div>
    </div>

    <GrLink href="https://example.com" external size="md">
      Explore icon usage inside inline content
    </GrLink>
  </div>
</template>

Карточки статуса и плитки показателей

Отдельный сценарий для dashboards: GrIcon помогает собирать status cards и KPI summaries с предсказуемым tone/size contract.

Status Cardзависит от окружения витрины
<script setup lang="ts">
import { GrBadge, GrCard, GrIcon } from '@feugene/granularity'
import IconCircleCheck from '~icons/lucide/circle-check-big'

const stats = [
  { label: 'Healthy integrations', value: '18', tone: 'text-emerald-500' },
  { label: 'Needs review', value: '4', tone: 'text-amber-500' },
  { label: 'Failed checks', value: '1', tone: 'text-rose-500' },
]
</script>

<template>
  <div class="grid gap-3 sm:grid-cols-3">
    <GrCard
        v-for="stat in stats"
        :key="stat.label"
        class="grid gap-3 p-4"
    >
      <div class="flex items-center justify-between gap-3">
        <GrIcon size="lg" :class="stat.tone">
          <IconCircleCheck />
        </GrIcon>

        <GrBadge size="sm" tone="neutral">
          snapshot
        </GrBadge>
      </div>

      <div>
        <div class="text-2xl font-700 text-[var(--gr-fg)]">
          {{ stat.value }}
        </div>
        <div class="text-sm text-[var(--gr-muted-fg)]">
          {{ stat.label }}
        </div>
      </div>
    </GrCard>
  </div>
</template>

Декоративная и значимая, тон и вращение

Иконка декоративна по умолчанию — компонент сам ставит aria-hidden. Значимой её делает label: появляются role="img" и имя. tone красит токеном текста (насыщенный тон как цвет текста в пакете запрещён — контраст падает до 2:1), spin крутит спиннер и сам замирает при prefers-reduced-motion.

Декоративная и значимая иконка
Сохранено
Тон из палитры (`-text`-роли токенов)
Вращение
Загружаем…

Semantics
<script setup lang="ts">
import { GrIcon, GR_TONES } from '@feugene/granularity'
</script>

<template>
  <div class="grid gap-5">
    <div class="grid gap-2">
      <div class="text-xs text-[var(--gr-muted-fg)]">
        Декоративная и значимая иконка
      </div>
      <div class="flex flex-wrap items-center gap-5 text-sm">
        <!-- Рядом есть текст — иконка декоративна, компонент скрывает её сам. -->
        <span class="inline-flex items-center gap-2">
          <GrIcon>
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
              <path d="m5 13 4 4L19 7" />
            </svg>
          </GrIcon>
          Сохранено
        </span>

        <!-- Текста рядом нет: смысл несёт сама иконка, значит ей нужно имя. -->
        <GrIcon label="Проверено" tone="success">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
            <path d="m5 13 4 4L19 7" />
          </svg>
        </GrIcon>
      </div>
    </div>

    <div class="grid gap-2">
      <div class="text-xs text-[var(--gr-muted-fg)]">
        Тон из палитры (`-text`-роли токенов)
      </div>
      <div class="flex flex-wrap items-center gap-4">
        <GrIcon v-for="tone in GR_TONES" :key="tone" :tone="tone" size="lg" :label="tone">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
            <circle cx="12" cy="12" r="9" />
            <path d="M12 8v5" />
            <path d="M12 16h.01" />
          </svg>
        </GrIcon>
      </div>
    </div>

    <div class="grid gap-2">
      <div class="text-xs text-[var(--gr-muted-fg)]">
        Вращение
      </div>
      <span class="inline-flex items-center gap-2 text-sm">
        <GrIcon spin tone="primary">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
            <path d="M21 12a9 9 0 1 1-6.2-8.6" />
          </svg>
        </GrIcon>
        Загружаем…
      </span>
    </div>
  </div>
</template>

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