GrStatistic
Берут, когда один показатель — главный.
Обзор
Когда брать
- один показатель — главный — выручка, число заказов, конверсия: крупная цифра читается первой;
- рядом нужна динамика —
trendсо стрелкой и подписью «+12 % к прошлой неделе»; - число длинное — группировка разрядов и
precisionпо локали, без ручного форматирования; - значение меняется на глазах —
animateдоводит цифру до нового значения, а не подменяет её; - плитка кликабельна —
href/clickableведут в раздел с подробностями.
Когда взять другое
| Нужно | Берите |
|---|---|
| Показателей несколько и их сравнивают | GrChartBar |
| Важен ход во времени | GrChartLine / GrSparkline |
| Показана доля от целого | GrProgressCircle |
| Значение — статус, а не число | GrBadge |
| Величина стоит внутри строки текста, а не плиткой | GrDelta |
Компонент презентационный: данные не грузит и дельту не считает — только форматирует и подаёт. Тренд приходит готовым, потому что «к чему сравнивать» знает приложение, а не плитка.
Варианты все тона и виды — настоящим компонентом
tone
primaryneutralsuccesswarningdangerinfoslateazure
Размеры
xssmmdlg
Состояния
обычнаяloading
Матрица живая: наводите и нажимайте. Состояний наведения, фокуса и нажатия здесь нет — они за браузером, а не за пропом.
Playground контролы выводятся из API, а не пишутся руками
Загружается…
0 изменено· состояние — в адресе
<GrStatistic title="Revenue" :value="1284500" />Примеры 4 у каждого — живое демо и его код
Число с именем
Значение форматируется локалью, знак и единица живут в
prefixиsuffix, а заголовок — часть компонента: число без имени статистикой не является.- Revenue
- €92 350
- Active customers
- 18 342
- Conversion
- 3,1%
Рост рядом со значением
trend— направление:up,down,flat, аtrend-text— фраза под числом; само изменение приходит из метрики, компонент его не считает. По умолчанию рост — хорошая новость. Для оттока хорошая новость — падение:trend-polarity="negative-good"красит строку динамики в зелёный, а значение оставляет нейтральным.- Recurring revenue
- €48 920
Рост+8.2% this month- Trial conversion
- 18,6%
Падение−1.2 pp this month- Monthly churn
- 2,1%
Падение−0.4 pp this month- Seats per account
- 4,2
Без измененийNo changeОжидание и переход
loadingдержит место числа, а не показывает ноль;hrefпревращает плитку в ссылку на страницу, где это число можно разобрать.Support desk
Дашборд из нескольких цифр
Цифры читают вместе, а не по одной: четыре плитки за период и выручка по дням под ними. Переключите период —
animateперебирает от прежнего значения, и видно изменение, а не новое число из ниоткуда.Store overview
- Revenue
- €21 480
Рост+12.4% vs previous- Orders
- 486
Рост+6.1% vs previous- Conversion
- 3,4%
Падение−0.3 pp vs previous- Average basket
- €44,20
Без измененийNo changeDaily revenue
Thousands of euros
API порождается из пакета · разойтись не может
Props
| Prop | Type | по умолчанию | Описание |
|---|---|---|---|
tone |
| undefined | Тон значения; точечно перекрывается `--gr-statistic-value-color`. |
title | string | undefined | undefined | Подпись над значением. |
icon | string | Component | undefined | undefined | Иконка слева от блока: Vue-компонент либо класс иконки (`'i-lucide-users'` — правило для класса заводит приложение: фабрике движка или провайдером, см. `docs/installation.md#иконки`). |
size | "xs" | "sm" | "md" | "lg" | undefined | undefined | Кегль значения, а с ним заголовка, приписок и строки динамики. Не задан — из `GrConfigProvider`, иначе `md`. |
loading | boolean | undefined | false | Состояние загрузки: вместо значения — плейсхолдер. |
animate | boolean | undefined | false | Перебирать числа при появлении и при смене значения. Нечисловое значение ставится сразу, под `prefers-reduced-motion: reduce` — тоже. |
as | string | Component | undefined | undefined | Свой корневой тег: компонент-ссылка (`RouterLink`, `Link` от Inertia) или семантический тег страницы (`section`, `article`). Сильнее `href`. Неинтерактивный тег — только замена `div`: ни подсветки, ни кольца фокуса. |
href | string | undefined | undefined | Показатель-ссылка: переход к деталям. |
clickable | boolean | undefined | false | Показатель-кнопка: интерактивна вся плитка. |
locale | string | undefined | undefined | BCP-47 локаль форматирования. Не задана — берётся из i18n-адаптера; нет и адаптера — работают ручные разделители. |
precision | number | undefined | undefined | Число знаков после запятой. |
prefix | string | undefined | undefined | Приписка перед значением (валюта, знак). |
suffix | string | undefined | undefined | Приписка после значения (единица измерения, `%`). |
polarity | GrDeltaPolarity | undefined | undefined | Что считать хорошим для **значения со знаком** — сальдо, прирост, разница: тон выводится из знака самого значения. Для выручки рост — успех, для себестоимости и оттока — наоборот. Явный `tone` сильнее: выведенный тон — умолчание, а не диктат. Абсолютный показатель (отток 2.4 %, время ответа) знака не несёт — для него полярность задают динамике, `trendPolarity`, а значение остаётся нейтральным. |
decimalSeparator | string | undefined | undefined | Десятичный разделитель. Сильнее локали; без него и без локали — точка. |
groupSeparator | string | undefined | undefined | Разделитель разрядов. Сильнее локали; без него и без локали — узкий пробел. |
trendPolarity | GrDeltaPolarity | undefined | undefined | Что считать хорошим для **динамики**: `negative-good` красит рост в `danger`, падение — в `success`, `none` — без тона. Не задана — берётся `polarity`, а без неё рост хорош. |
trend | GrStatisticTrend | undefined | undefined | Направление динамики — задаёт иконку строки под значением, а с полярностью — и цвет. |
trendText | string | undefined | undefined | Текст динамики (например `+12,5% к прошлой неделе`). |
animateDuration | number | undefined | 600 | Длительность перебора в миллисекундах. Не токеном: шкала `--gr-duration-*` заканчивается на 300 мс и описывает смену состояния, а перебор чисел — другой жанр, и его число живёт там же, где твин. |
valueобязательный | string | number | — | Значение показателя. Нечисловая строка выводится как есть. |
Слоты
| Slot | Type | Описание |
|---|---|---|
default | any | Значение вместо пропа `value`. |
icon | any | Иконка перед подписью. |
title | any | Подпись показателя вместо пропа `title`. |
prefix | any | Приписка перед значением: знак валюты, стрелка. |
suffix | any | Приписка после значения: единица измерения, процент. |
trend | any | Динамика под значением вместо встроенного `GrDelta`. |
События
| Event | Сигнатура | Описание |
|---|---|---|
click | (event: "click", event: MouseEvent): void | — |
Типы
Все экспортируемые типы подпути, по файлам, где они объявлены. Имена, дженерики и тела читаются из файлов деклараций пакета и разойтись не могут.
import type { GrStatisticFormatOptions, GrStatisticProps, GrStatisticEmits } from '@feugene/granularity/components/GrStatistic'Файлdist/types/components/GrStatistic/config.d.ts
const grStatisticConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrStatistic">;Файлdist/types/components/GrStatistic/formatStatisticValue.d.ts
type GrStatisticFormatOptions = {
/** Число знаков после запятой. `undefined` — оставить как есть. */
precision?: number;
/** Разделитель групп разрядов. Сильнее локали. */
groupSeparator?: string;
/** Десятичный разделитель. Сильнее локали. */
decimalSeparator?: string;
/** BCP-47 локаль: разделители берутся из `Intl.NumberFormat`. */
locale?: string;
};Форматирует значение показателя: фиксирует точность и расставляет разделители.
Нечисловые значения возвращаются без изменений — это позволяет показывать в
GrStatistic уже готовые строки («2 ч 15 мин», «—») без обходных пропов.
Вынесено из SFC отдельным модулем, чтобы тестировалось без монтирования.
Порядок разрешения разделителей: явный проп → локаль → встроенный дефолт.
function formatStatisticValue(value: number | string, options?: GrStatisticFormatOptions): string;Файлdist/types/components/GrStatistic/GrStatistic.vue.d.ts
Публичный GR-примитив «Statistic» — крупный числовой показатель с подписью,
префиксом/суффиксом и строкой динамики.
Компонент презентационный: не грузит данные и не считает дельту, только
форматирует и подаёт. Форматирование — чистая функция formatStatisticValue.
interface GrStatisticProps {
/** Значение показателя. Нечисловая строка выводится как есть. */
value: number | string;
/** Подпись над значением. */
title?: string;
/** Число знаков после запятой. */
precision?: number;
/** Разделитель разрядов. Сильнее локали; без него и без локали — узкий пробел. */
groupSeparator?: string;
/** Десятичный разделитель. Сильнее локали; без него и без локали — точка. */
decimalSeparator?: string;
/**
* BCP-47 локаль форматирования. Не задана — берётся из i18n-адаптера; нет и
* адаптера — работают ручные разделители.
*/
locale?: string;
/** Приписка перед значением (валюта, знак). */
prefix?: string;
/** Приписка после значения (единица измерения, `%`). */
suffix?: string;
/**
* Иконка слева от блока: Vue-компонент либо класс иконки (`'i-lucide-users'` —
* правило для класса заводит приложение: фабрике движка или провайдером,
* см. `docs/installation.md#иконки`).
*/
icon?: string | Component;
/**
* Кегль значения, а с ним заголовка, приписок и строки динамики.
* Не задан — из `GrConfigProvider`, иначе `md`.
*/
size?: GrStatisticSize;
/** Тон значения; точечно перекрывается `--gr-statistic-value-color`. */
tone?: GrStatisticTone;
/**
* Что считать хорошим для **значения со знаком** — сальдо, прирост, разница:
* тон выводится из знака самого значения. Для выручки рост — успех, для
* себестоимости и оттока — наоборот. Явный `tone` сильнее: выведенный тон —
* умолчание, а не диктат.
*
* Абсолютный показатель (отток 2.4 %, время ответа) знака не несёт — для него
* полярность задают динамике, `trendPolarity`, а значение остаётся нейтральным.
*/
polarity?: GrDeltaPolarity;
/**
* Что считать хорошим для **динамики**: `negative-good` красит рост в
* `danger`, падение — в `success`, `none` — без тона. Не задана — берётся
* `polarity`, а без неё рост хорош.
*/
trendPolarity?: GrDeltaPolarity;
/** Направление динамики — задаёт иконку строки под значением, а с полярностью — и цвет. */
trend?: GrStatisticTrend;
/** Текст динамики (например `+12,5% к прошлой неделе`). */
trendText?: string;
/** Состояние загрузки: вместо значения — плейсхолдер. */
loading?: boolean;
/**
* Перебирать числа при появлении и при смене значения. Нечисловое значение
* ставится сразу, под `prefers-reduced-motion: reduce` — тоже.
*/
animate?: boolean;
/**
* Длительность перебора в миллисекундах. Не токеном: шкала `--gr-duration-*`
* заканчивается на 300 мс и описывает смену состояния, а перебор чисел —
* другой жанр, и его число живёт там же, где твин.
*/
animateDuration?: number;
/**
* Свой корневой тег: компонент-ссылка (`RouterLink`, `Link` от Inertia) или
* семантический тег страницы (`section`, `article`). Сильнее `href`.
* Неинтерактивный тег — только замена `div`: ни подсветки, ни кольца фокуса.
*/
as?: string | Component;
/** Показатель-ссылка: переход к деталям. */
href?: string;
/** Показатель-кнопка: интерактивна вся плитка. */
clickable?: boolean;
}interface GrStatisticEmits {
/** Клик по плитке — например, чтобы открыть детали. С клавиатуры приходит у `clickable` и ссылки. */
(e: 'click', event: MouseEvent): void;
}Файлdist/types/components/GrStatistic/grStatisticStyles.d.ts
type GrStatisticSize = GrComponentSize;type GrStatisticTone = GrTone;Направление динамики показателя.
type GrStatisticTrend = 'up' | 'down' | 'flat';Файлdist/types/components/GrStatistic/safelist.d.ts
const grStatisticSafelist: string[];Файлdist/types/components/GrStatistic/defaults.d.ts
Пропы GrStatistic, настраиваемые глобально через
<GrConfigProvider :component-defaults="{ GrStatistic: { … } }">.
Только оформление — см. GrButton/defaults.ts.
polarity здесь по той же причине, что и у GrDelta: на дашборде расходов
«рост — плохо» верно сразу для всех плиток ряда, и повторять это на каждой
значит однажды пропустить одну.
interface GrStatisticConfigurableProps {
size: GrStatisticSize;
polarity: GrDeltaPolarity;
trendPolarity: GrDeltaPolarity;
}Использование из прозы библиотеки
Форматирование по локали
<GrStatistic title="Выручка" :value="1234567.5" :precision="2" prefix="₽" />Разделители берутся из локали через Intl.NumberFormat, а локаль — из
i18n-адаптера пакета. Ничего настраивать не нужно: ru даст 1 234 567,50,
de-DE — 1.234.567,50.
Порядок разрешения: groupSeparator/decimalSeparator → проп locale →
локаль адаптера → встроенный дефолт (узкий пробел и точка). Явный разделитель
подменяет только свою часть, остальные правила локали — группировку по три или
по индийской схеме, знак минуса — компонент сохраняет.
Нечисловое значение выводится как есть: «2 ч 15 мин», «—» не требуют
обходных пропов. Форматирование живёт отдельной чистой функцией
formatStatisticValue и доступно из пакета.
Не доехавший обязательный value рисуется прочерком — тем же знаком пустоты,
которым GrDelta показывает отсутствующую величину, — и объясняется
предупреждением в dev-режиме. Плитка со словом undefined выглядит как данные,
а не как ошибка, поэтому такой вариант исключён.
Динамика
<GrStatistic :value="482" trend="up" trend-text="+12,5 % к прошлой неделе" />trend задаёт цвет и иконку строки, trendText — сам текст. Иконки
направления встроенные; проп icon слева от блока принимает Vue-компонент или
класс иконки вашей сборки (см. «Иконки»). Направление
дополнительно объявляется скрытой подписью («Рост», «Падение», «Без
изменений»): иконка декоративна, а «+12,5 %» без контекста рост от падения не
отличает — цвет диктору тоже не виден. Подпись остаётся и при своём слоте
#trend.
Приписки рисует GrValue
<GrStatistic title="Средний чек" :value="14.99" :precision="2" prefix="$" suffix="за заказ" />Запись величины — префикс, число, суффикс — плитка не рисует сама: этим занят
примитив GrValue, общий у неё с GrDelta. Оттуда же и
дефолты: слева приписка набирается как число ($14,99), справа — приглушённой и
мельче (42 %).
Плитка решает только тон и кегль, и ставит их на контейнер величины — приписки наследуют оба. Поэтому отрицательная сумма краснеет целиком, вместе со знаком валюты, а не числом отдельно от него.
Чем является приписка — валютой, единицей измерения или пометкой — не решает
никто: оформление обеих настраивается токенами --gr-value-*. Так получается
и валюта справа (1 284 500 ₽), которой дефолт суффикса не подходит, — рецепт
на странице примитива.
Тон по знаку и строка динамики — разные сигналы
<GrStatistic title="Маржа" :value="-1240" prefix="₽" polarity="positive-good" />polarity — для значения со знаком: сальдо, прирост, разница с планом. Она
красит значение по знаку самой величины: positive-good для маржи, negative-good
для перерасхода, none — знак ничего не говорит о качестве. Ноль нейтрален при
любой полярности, а нечисловое значение («2 ч 15 мин», «—») знака не имеет и
тона не получает.
trend задаёт строку под значением: направление — иконкой и скрытой
подписью, а цвет — полярностью, а не направлением. Рост оттока — плохая новость,
падение просрочки — хорошая; зелёной стрелка «вверх» бывает только там, где рост
и правда хорош. Тон считает тот же deltaTone: negative-good красит up в
danger, down — в success, none и flat — без тона.
<GrStatistic title="Отток" :value="2.4" suffix="%" trend="up" trend-text="+0.6 п. п." trend-polarity="negative-good" />Абсолютный показатель — отток 2.4 %, время ответа, число просроченных счетов —
знака не несёт: красить само число не за что. Ему полярность задают динамике,
trendPolarity, и значение остаётся нейтральным, а строка «↑ +0.6 п. п.» —
красной. Не задан trendPolarity — динамика берёт polarity; не задано ни то,
ни другое — рост хорош, как у выручки.
Явный tone сильнее polarity: выведенный тон — умолчание, а не диктат. Тот
же выбор тона доступен отдельной функцией deltaTone — см.
GrDelta, где он и живёт.
Анимация счётчика
<GrStatistic title="Выручка" :value="revenue" animate :animate-duration="900" />animate перебирает числа при появлении плитки (от нуля) и при каждой смене
значения — от прежнего числа, а не от нуля: перебор с нуля на каждом
обновлении дашборда читался бы как сброс данных.
Длительность задаёт animateDuration в миллисекундах (по умолчанию 600).
Токеном она не задаётся намеренно: шкала --gr-duration-* заканчивается на
300 мс и описывает смену состояния — цвет, рамку, появление слоя. Перебор чисел
— другой жанр, и его число живёт там же, где твин, — в JS.
Перебираются только числа: «2 ч 15 мин» и «—» ставятся сразу. Ширина строки
не дёргается — значение набрано tabular-nums.
prefers-reduced-motion компонент читает сам. Глобальный кламп в
base.css гасит CSS-анимации и переходы, но не JS-твин — см.
../motion.md. Под reduce значение ставится мгновенно, без
единого промежуточного кадра.
Диктор слышит конечное значение. Пока идёт перебор, видимое число помечено
aria-hidden, а рядом живёт визуально скрытый узел с итогом: «1 284 500» на
экране и «743 210» в ушах — не шум, а неверные данные.
Переход к деталям
<GrStatistic title="Заказы" :value="1284" href="/orders" />
<GrStatistic title="Заказы" :value="1284" clickable @click="drill" />
<GrStatistic title="Заказы" :value="1284" :as="RouterLink" :to="{ name: 'orders' }" />Тот же приём, что у GrCard и GrListItem: href даёт ссылку,
clickable — кнопку, as — свой тег или компонент роутера (сильнее href).
Интерактивная плитка получает курсор, подсветку и фокус-кольцо; своей заливки у
неё нет — показатель обычно уже лежит в карточке, и вторая поверхность спорила
бы с ней.
Отдельного ariaLabel нет: доступное имя собирается из содержимого, а подпись
показателя и есть его имя.
Загрузка
loading подменяет значение скелетоном той же высоты, чтобы блок не прыгал;
область помечена role="status" и содержит скрытый текст загрузки.
Оформление
| Точка | Что задаёт |
|---|---|
size (xs…lg) | лестницы кеглей подписи, значения, аффиксов и динамики; читается из GrConfigProvider |
tone | цвет значения; тона берутся из -text-токенов — насыщенный тон как текст не проходит по контрасту |
polarity | выводит tone из знака самого значения со знаком; читается из GrConfigProvider |
trendPolarity | что считать хорошим для динамики; не задан — polarity, иначе рост хорош; читается из GrConfigProvider |
--gr-statistic-value-color | цвет значения точечно, сильнее tone |
--gr-statistic-title-color | цвет подписи |
Слоты #icon, #title, #prefix, #suffix, #trend и слот по умолчанию
(вместо форматированного значения) заменяют соответствующие части разметкой.
Разметка: подпись и значение — пара
Подпись и значение выводятся как <dl> → <dt> → <dd>: это пара «термин —
значение», а не два соседних блока, которые диктор просто читает подряд. <dl>
появляется только вместе с подписью — список определений без <dt> был бы той
же бессвязностью, только с претензией на семантику.
Строка динамики остаётся снаружи <dl>: внутри списка определений
разрешены только группы dt/dd. Место в DOM у неё прежнее, вёрстка не
меняется. Поля <dl> и <dd> обнулены классом — preflight пакета сбрасывает
их только у body.
Все data-атрибуты (data-gr-statistic-title, data-gr-statistic-value, …)
сохранены: стили потребителя, написанные по ним, продолжают работать.
Пропы этого примера