GrDelta

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

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

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

Обзор

Когда брать

  • число со знаком стоит в предложении — в описании операции, в ячейке таблицы, в подписи под графиком;
  • знак решает цвет — рост, падение и «не изменилось» видно до чтения самой величины;
  • рост бывает плохим — polarity="negative-good" для себестоимости, времени отклика и оттока;
  • величина может отсутствовать — «нет данных» и «ноль» рисуются по-разному.

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

НужноБерите
Крупный показатель плиткой, с подписью и динамикойGrStatistic
Число как статус или меткаGrBadge
Доля от целого полосой или кольцомGrProgressBar / GrProgressCircle
Ряд значений во времениGrSparkline
Пара «характеристика → значение»GrDescriptionList

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

Сцена

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

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

Примеры 4 у каждого — живое демо и его код

  • Изменение внутри фразы

    На ступени по умолчанию своего кегля у дельты нет: внутри заголовка она крупная, внутри фразы — кеглем фразы, а стрелка растёт вместе с числом. Ноль — «не изменилось» и остаётся нейтральным.

    Revenue in September +8,4%

    Recurring revenue reached €48,920, +8,2% week over week. Trial conversion slipped by -1,2 pp, and seats per account stayed at 4.2 — 0%, which is neither good nor bad news.

  • Нет данных — это не ноль

    null печатает empty-text без тона, стрелки и приписок — «€—» читалось бы как ноль евро. Знак ставит Intl, поэтому locale двигает разделители, а знак по-прежнему встаёт перед валютой.

    • Germany+12,6%
    • Poland0,0%
    • Sweden-3,4%
    • Portugalnew market
    • en-GB+€1,284.50
    • de-DE+1.284,50 €
    • fr-FR+1 284,50 €
  • Когда рост — плохая новость

    polarity="negative-good" переворачивает тон для себестоимости, задержки и оттока, но не знак: упавшая себестоимость всё ещё −€0.42, и она зелёная. none снимает тон там, где знак не оценка.

    MetricThis monthChangepolarity
    Revenue€92,350+6,3%positive-good · more is better
    Cost per order€3.18-€0,42negative-good · less is better
    Latency, p95412 ms+38 msnegative-good · less is better
    Monthly churn2.1 %+0,4 ppnegative-good · less is better
    Cash balance shift€18,400-€1 240none · the sign is not a verdict
  • Ступени для ряда контролов, хуки для остального

    Рядом с контролами size="sm" совпадает с ними, а не с текстом вокруг. --gr-delta-suffix-color и --gr-delta-arrow-size приглушают единицу или увеличивают стрелку, а публичная deltaTone даёт бейджу ту же оценку, что и дельте.

    Orders +4,6% Refunds +0,3 pp
    Latency -12msFaster than last week

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

Props

PropTypeпо умолчаниюОписание
size"xs" | "sm" | "md" | "lg" | undefinedundefinedКегль по шкале контролов. По умолчанию `md` — кегль окружающей строки; `xs`, `sm`, `lg` нужны, когда величина стоит в ряду с контролами.
emptyTextstring | undefinedundefinedЧем печатать отсутствующую величину.
localestring | undefinedundefinedЛокаль форматирования. Не задана — локаль i18n-адаптера.
precisionnumber | undefinedundefinedЗнаков после запятой. Не задано — как отдаст локаль.
prefixstring | undefinedundefinedПриписка перед числом: валюта, единица.
suffixstring | undefinedundefinedПриписка после числа: `%`, `мс`.
polarityGrDeltaPolarity | undefinedundefinedЧто считать хорошим. Для выручки рост — успех, для себестоимости и времени отклика — наоборот; без этого компонент врал бы в половине случаев.
showSignboolean | undefinedundefinedСтавить `+` у положительных. Минус подставляет `Intl` сам.
showArrowboolean | undefinedundefinedСтрелка направления. Декоративна: направление уже в знаке.
valueобязательныйnumber | null—Величина. `null` — её нет; это не ноль.

Типы

Все экспортируемые типы подпути, по файлам, где они объявлены. Имена, дженерики и тела читаются из файлов деклараций пакета и разойтись не могут.

Импорт
import type { GrDeltaPolarity, GrDeltaTone, GrDeltaProps } from '@feugene/granularity/components/GrDelta'

Файлdist/types/components/GrDelta/config.d.ts

grDeltaConfigconst
const grDeltaConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrDelta">;

Файлdist/types/components/shared/deltaTone.d.ts

GrDeltaPolaritytype

Что считать хорошим ростом.

type GrDeltaPolarity = 'positive-good'
GrDeltaTonetype
type GrDeltaTone = 'success' | 'danger' | 'neutral';
deltaTonefunction

Тон величины по её знаку и полярности. Ноль нейтрален при любой полярности: «не изменилось» — третье состояние, и двумя цветами его не выразить. null величины нет вовсе — тона у неё тоже нет, это решает вызывающий.

function deltaTone(value: number, polarity?: GrDeltaPolarity): GrDeltaTone;
deltaDirectionfunction

Направление стрелки. Ноль стрелки не получает: двигаться было некуда.

function deltaDirection(value: number): 'up' | 'down' | 'flat';

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

GrDeltaPropsinterface

Величина со знаком и тоном **внутри строки текста**: «Дельта: +$0.004». Компонент презентационный: дельту не считает и периоды не сравнивает — «к чему сравнивать» знает приложение. Блочная плитка показателя — GrStatistic.

interface GrDeltaProps {
    /** Величина. `null` — её нет; это не ноль. */
    value: number | null;
    /** Знаков после запятой. Не задано — как отдаст локаль. */
    precision?: number;
    /** Приписка перед числом: валюта, единица. */
    prefix?: string;
    /** Приписка после числа: `%`, `мс`. */
    suffix?: string;
    /**
     * Что считать хорошим. Для выручки рост — успех, для себестоимости и времени
     * отклика — наоборот; без этого компонент врал бы в половине случаев.
     */
    polarity?: GrDeltaPolarity;
    /** Ставить `+` у положительных. Минус подставляет `Intl` сам. */
    showSign?: boolean;
    /** Стрелка направления. Декоративна: направление уже в знаке. */
    showArrow?: boolean;
    /** Чем печатать отсутствующую величину. */
    emptyText?: string;
    /** Локаль форматирования. Не задана — локаль i18n-адаптера. */
    locale?: string;
    /**
     * Кегль по шкале контролов. По умолчанию `md` — кегль окружающей строки; `xs`, `sm`, `lg`
     * нужны, когда величина стоит в ряду с контролами.
     */
    size?: GrComponentSize;
}

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

GrDeltaConfigurablePropsinterface

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

interface GrDeltaConfigurableProps {
    size: GrComponentSize;
    polarity: GrDeltaPolarity;
    showSign: boolean;
    showArrow: boolean;
    emptyText: string;
}

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

grDeltaSafelistconst
const grDeltaSafelist: string[];

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

Ноль нейтрален, null — это не ноль

Два состояния, которые двумя цветами не выражаются, и оба уже приводили к ошибкам в приложениях:

  • ноль — «не изменилось». Ни успех, ни ошибка: тон нейтральный при любой polarity. Условие вида value < 0 ? danger : success красит нулевое движение зелёным, как поступление;
  • null — величины нет вовсе. Печатается прочерком (emptyText), без тона, без стрелки и без приписок: $— читается как «ноль долларов».

Выбор тона живёт отдельной чистой функцией и покрыт тестами без монтирования — именно этот switch расходился копиями по приложению.

Функция публичная: deltaTone(value, polarity) и deltaDirection(value) отдаются из пакета. Они нужны там, где разметка дельты не подходит, а правило то же — tone у GrStatistic и GrBadge в плитках отчёта. Переписанное в приложении, оно расходится с этим при первой же правке — что и произошло с нулём, покрашенным в зелёный как поступление.

Полярность инвертирует тон, но не знак
vue
<GrDelta :value="-15" suffix="%" polarity="negative-good" />

Себестоимость упала на 15 % — это успех, и величина зелёная. Но она всё ещё −15 %: знак принадлежит числу, а не оценке. Стрелка тоже идёт за знаком — вниз, потому что величина уменьшилась.

polarity="none" снимает тон совсем: у сальдо и смещения знак ничего не говорит о качестве.

Знак ставит Intl, а не склейка строк

showSign включает signDisplay: 'exceptZero' в форматировании, а не дописывает '+' к готовой строке. Разница не стилистическая: склейка уже ломала GrStatistic — строка '+1,234' перестаёт быть числом, и разряды теряются на следующем шаге.

Локаль берётся из i18n-адаптера, если не задана пропом.

Знак стоит перед валютой

Вместе с prefix знак уезжает в отдельный узел перед припиской: +$0,0280, а не $+0,0280. Знак относится к величине целиком, а не к числу после символа валюты, и второй порядок не принят ни в одной типографской традиции.

Инвариант выше при этом не ослаблен: знак по-прежнему ставит Intl, компонент лишь вынимает его из formatToParts — строка не склеивается и разряды не пересобираются. Без prefix выносить некуда, и разметка остаётся прежней.

Настраивать положение знака нечем намеренно. Отдельная тонкость — RTL: там знаку предшествует невидимая метка направления, которой он и разворачивается относительно цифр; она уезжает вместе со знаком, потому что управляет именно им.

Сами приписки рисует GrValue — общий примитив с GrStatistic. Оттуда же их оформление и токены --gr-value-*: валюта слева набирается как число, единица справа приглушается. Валюта справа (+1 000 ₽) получается сменой дефолта суффикса, рецепт — на странице примитива.

Свои точки настройки

У дельты четыре собственных хука (--gr-delta-*, полный список — в ../tokens.md), и появились они не ради симметрии.

Приписку дельта не приглушает: «−15 %» набирается кеглем и цветом строки целиком, иначе процент читается подписью при числе. Это перекрытие дефолта GrValue, и оно применяется инлайн-стилем — а инлайн бьёт любой CSS потребителя. Задай компонент цвет литералом, и вернуть приглушение стало бы нечем совсем; поэтому перекрытие ссылается на --gr-delta-suffix-color и --gr-delta-suffix-size, оставляя решение тому, кто их задал:

vue
<GrDelta :value="-15" suffix="%" style="--gr-delta-suffix-color: var(--gr-muted-fg)" />

Стрелка настраивается --gr-delta-arrow-size и --gr-delta-arrow-gap. Размер — доля от кегля величины, а не ступень шкалы: на md своего кегля у дельты нет вовсе, и пиксельное значение оставило бы стрелку мелкой внутри заголовка. Отбивка логическая, поэтому в RTL встаёт с нужной стороны.

Кегль приходит из строки

Ступень по умолчанию (md) font-size не задаёт вовсе: величина набирается кеглем той строки, в которой стоит. Внутри заголовка она крупная, в подписи под графиком мелкая, в ячейке таблицы — ровно как остальной текст ячейки, и всё это без единого пропа.

vue
<h2 class="text-[length:var(--gr-text-3xl)]">
  Выручка за март <GrDelta :value="8.4" :precision="1" suffix="%" show-arrow />
</h2>

Края лестницы остаются явными и берут контрольную шкалу — xs 12 px, sm 13 px, lg 16 px. Они нужны в другом случае: когда величина стоит не в предложении, а в ряду с контролами, и обязана совпасть с ними, а не с окружающим текстом.

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

Стрелка декоративна

showArrow рисует направление, но помечает иконку aria-hidden: направление уже объявлено знаком, и дублировать его для скринридера незачем. По умолчанию стрелки нет — в строке текста она чаще шумит, чем помогает.

Границы

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