GrStatistic

  • ядро
  • Данные
  • API · 21 props

Берут, когда один показатель — главный.

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

Обзор

Когда брать

  • один показатель — главный — выручка, число заказов, конверсия: крупная цифра читается первой;
  • рядом нужна динамика — trend со стрелкой и подписью «+12 % к прошлой неделе»;
  • число длинное — группировка разрядов и precision по локали, без ручного форматирования;
  • значение меняется на глазах — animate доводит цифру до нового значения, а не подменяет её;
  • плитка кликабельна — href/clickable ведут в раздел с подробностями.

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

НужноБерите
Показателей несколько и их сравниваютGrChartBar
Важен ход во времениGrChartLine / GrSparkline
Показана доля от целогоGrProgressCircle
Значение — статус, а не числоGrBadge
Величина стоит внутри строки текста, а не плиткойGrDelta

Компонент презентационный: данные не грузит и дельту не считает — только форматирует и подаёт. Тренд приходит готовым, потому что «к чему сравнивать» знает приложение, а не плитка.

Варианты все тона и виды — настоящим компонентом

tone

  • primary
  • neutral
  • success
  • warning
  • danger
  • info
  • slate
  • azure

Размеры

  • xs
  • sm
  • md
  • lg

Состояния

  • обычная
  • loading

Матрица живая: наводите и нажимайте. Состояний наведения, фокуса и нажатия здесь нет — они за браузером, а не за пропом.

Playground контролы выводятся из API, а не пишутся руками

Сцена

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

0 изменено· состояние — в адресе

Примеры 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 change

    Daily revenue

    Thousands of euros

API порождается из пакета · разойтись не может

Props

PropTypeпо умолчаниюОписание
tone
"primary" | "neutral" | "success" | "warning" | "danger" | ""primary" | "neutral" | "success" | "warning" | "danger" | "info" | "slate" | "azure" | undefined
undefinedТон значения; точечно перекрывается `--gr-statistic-value-color`.
titlestring | undefinedundefinedПодпись над значением.
iconstring | Component | undefinedundefinedИконка слева от блока: Vue-компонент либо класс иконки (`'i-lucide-users'` — правило для класса заводит приложение: фабрике движка или провайдером, см. `docs/installation.md#иконки`).
size"xs" | "sm" | "md" | "lg" | undefinedundefinedКегль значения, а с ним заголовка, приписок и строки динамики. Не задан — из `GrConfigProvider`, иначе `md`.
loadingboolean | undefinedfalseСостояние загрузки: вместо значения — плейсхолдер.
animateboolean | undefinedfalseПеребирать числа при появлении и при смене значения. Нечисловое значение ставится сразу, под `prefers-reduced-motion: reduce` — тоже.
asstring | Component | undefinedundefinedСвой корневой тег: компонент-ссылка (`RouterLink`, `Link` от Inertia) или семантический тег страницы (`section`, `article`). Сильнее `href`. Неинтерактивный тег — только замена `div`: ни подсветки, ни кольца фокуса.
hrefstring | undefinedundefinedПоказатель-ссылка: переход к деталям.
clickableboolean | undefinedfalseПоказатель-кнопка: интерактивна вся плитка.
localestring | undefinedundefinedBCP-47 локаль форматирования. Не задана — берётся из i18n-адаптера; нет и адаптера — работают ручные разделители.
precisionnumber | undefinedundefinedЧисло знаков после запятой.
prefixstring | undefinedundefinedПриписка перед значением (валюта, знак).
suffixstring | undefinedundefinedПриписка после значения (единица измерения, `%`).
polarityGrDeltaPolarity | undefinedundefinedЧто считать хорошим для **значения со знаком** — сальдо, прирост, разница: тон выводится из знака самого значения. Для выручки рост — успех, для себестоимости и оттока — наоборот. Явный `tone` сильнее: выведенный тон — умолчание, а не диктат. Абсолютный показатель (отток 2.4 %, время ответа) знака не несёт — для него полярность задают динамике, `trendPolarity`, а значение остаётся нейтральным.

Слоты

SlotTypeОписание
defaultanyЗначение вместо пропа `value`.
iconanyИконка перед подписью.
titleanyПодпись показателя вместо пропа `title`.
prefixanyПриписка перед значением: знак валюты, стрелка.
suffixanyПриписка после значения: единица измерения, процент.
trendanyДинамика под значением вместо встроенного `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

grStatisticConfigconst
const grStatisticConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrStatistic">;

Файлdist/types/components/GrStatistic/formatStatisticValue.d.ts

GrStatisticFormatOptionstype
type GrStatisticFormatOptions = {
    /** Число знаков после запятой. `undefined` — оставить как есть. */
    precision?: number;
    /** Разделитель групп разрядов. Сильнее локали. */
    groupSeparator?: string;
    /** Десятичный разделитель. Сильнее локали. */
    decimalSeparator?: string;
    /** BCP-47 локаль: разделители берутся из `Intl.NumberFormat`. */
    locale?: string;
};
formatStatisticValuefunction

Форматирует значение показателя: фиксирует точность и расставляет разделители. Нечисловые значения возвращаются без изменений — это позволяет показывать в GrStatistic уже готовые строки («2 ч 15 мин», «—») без обходных пропов. Вынесено из SFC отдельным модулем, чтобы тестировалось без монтирования. Порядок разрешения разделителей: явный проп → локаль → встроенный дефолт.

function formatStatisticValue(value: number | string, options?: GrStatisticFormatOptions): string;

Файлdist/types/components/GrStatistic/GrStatistic.vue.d.ts

GrStatisticPropsinterface

Публичный 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;
}
GrStatisticEmitsinterface
interface GrStatisticEmits {
    /** Клик по плитке — например, чтобы открыть детали. С клавиатуры приходит у `clickable` и ссылки. */
    (e: 'click', event: MouseEvent): void;
}

Файлdist/types/components/GrStatistic/grStatisticStyles.d.ts

GrStatisticSizetype
type GrStatisticSize = GrComponentSize;
GrStatisticTonetype
type GrStatisticTone = GrTone;
GrStatisticTrendtype

Направление динамики показателя.

type GrStatisticTrend = 'up' | 'down' | 'flat';

Файлdist/types/components/GrStatistic/safelist.d.ts

grStatisticSafelistconst
const grStatisticSafelist: string[];

Файлdist/types/components/GrStatistic/defaults.d.ts

GrStatisticConfigurablePropsinterface

Пропы GrStatistic, настраиваемые глобально через <GrConfigProvider :component-defaults="{ GrStatistic: { … } }">. Только оформление — см. GrButton/defaults.ts. polarity здесь по той же причине, что и у GrDelta: на дашборде расходов «рост — плохо» верно сразу для всех плиток ряда, и повторять это на каждой значит однажды пропустить одну.

interface GrStatisticConfigurableProps {
    size: GrStatisticSize;
    polarity: GrDeltaPolarity;
    trendPolarity: GrDeltaPolarity;
}

Использование из прозы библиотеки

Форматирование по локали
vue
<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 выглядит как данные, а не как ошибка, поэтому такой вариант исключён.

Динамика
vue
<GrStatistic :value="482" trend="up" trend-text="+12,5 % к прошлой неделе" />

trend задаёт цвет и иконку строки, trendText — сам текст. Иконки направления встроенные; проп icon слева от блока принимает Vue-компонент или класс иконки вашей сборки (см. «Иконки»). Направление дополнительно объявляется скрытой подписью («Рост», «Падение», «Без изменений»): иконка декоративна, а «+12,5 %» без контекста рост от падения не отличает — цвет диктору тоже не виден. Подпись остаётся и при своём слоте #trend.

Приписки рисует GrValue
vue
<GrStatistic title="Средний чек" :value="14.99" :precision="2" prefix="$" suffix="за заказ" />

Запись величины — префикс, число, суффикс — плитка не рисует сама: этим занят примитив GrValue, общий у неё с GrDelta. Оттуда же и дефолты: слева приписка набирается как число ($14,99), справа — приглушённой и мельче (42 %).

Плитка решает только тон и кегль, и ставит их на контейнер величины — приписки наследуют оба. Поэтому отрицательная сумма краснеет целиком, вместе со знаком валюты, а не числом отдельно от него.

Чем является приписка — валютой, единицей измерения или пометкой — не решает никто: оформление обеих настраивается токенами --gr-value-*. Так получается и валюта справа (1 284 500 ₽), которой дефолт суффикса не подходит, — рецепт на странице примитива.

Тон по знаку и строка динамики — разные сигналы
vue
<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 — без тона.

vue
<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, где он и живёт.

Анимация счётчика
vue
<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» в ушах — не шум, а неверные данные.

Переход к деталям
vue
<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, …) сохранены: стили потребителя, написанные по ним, продолжают работать.