GrDuration
Берут, когда сколько заняло.
Когда брать
- сколько заняло — длительность звонка, время сборки, простой оборудования: «2 ч 30 мин» читается быстрее, чем «9000»;
- сколько идёт прямо сейчас — передайте момент начала одной датой, и метка станет живой: таймер общий на всё приложение и снимается на скрытой вкладке;
- промежуток между двумя моментами — пара дат считается сама, вычитать миллисекунды руками не нужно;
- значение уезжает в разметку —
datetimeзаполняется точной длиной в ISO 8601, даже когда текст сокращён до двух единиц.
Когда взять другое
| Нужно | Берите |
|---|---|
| Показать, давно ли это было | GrRelativeTime |
| Выбрать момент, а не показать длину | GrDatePicker / GrTimePicker |
| Ввести длительность, а не показать | GrNumberInput |
| Показать долю прошедшего от целого | GrProgressBar |
Единиц четыре, и крупнее не будет
Сутки, часы, минуты, секунды. Месяца и года здесь нет намеренно: они
календарные — в феврале и в июле разной длины, — и вывести их из числа секунд
нельзя, не соврав. Разрыв, который правда меряется календарём, показывает
GrRelativeTime, а считает differenceInMonths.
Потолок, а не квота
maxUnits ограничивает число единиц сверху и ничего не добирает: ровно два часа
это «2 ч», а не «2 ч 0 мин». Младшее при этом отбрасывается, а не
округляется — «2 ч 59 мин 30 с» остаётся «2 ч 59 мин». Округление подняло бы
показ выше реально прошедшего времени, а этим значением обычно меряют именно
его.
Ноль — единственное исключение из отбрасывания: «0 с» печатается, потому что пустая строка читается как «нет данных», а ноль означает другое.
Живая метка — только у формы с одной датой
Число и пара дат задают длину целиком, тикать в них нечему. Момент начала — другое дело: до «сейчас» время идёт, и метка пересчитывается сама. Такт выбирается по младшей показанной единице, поэтому метка в часах не будит вкладку каждую секунду.
Живой показ выведен из часов, а значит серверный рендер и клиентский разойдутся.
Компонент помечает это data-allow-mismatch, и убирается пометка не флагом, а
данными: передайте base — рендер станет детерминированным.
Границы
Обратного отсчёта здесь нет: «осталось 2 ч 30 мин» — это та же длина, но дедлайн, ноль и переход в отрицательные значения принадлежат сценарию, а не метке. Считайте остаток сами и передавайте числом.
Установка
npm i @feugene/granularity-chronoИмпорт
import { GrDuration } from '@feugene/granularity-chrono/components/GrDuration'API
API этого компонента ещё не посчитан: генератор витрины пока обходит только ядро. Пока его нет, справочник — в документации пакета.
Примеры 1
Basic
<script setup lang="ts">
import { ref } from 'vue'
/**
* Три формы значения — три разных вопроса. Число это готовая длина, пара дат —
* промежуток между моментами, одна дата — время, которое идёт прямо сейчас.
*/
const started = ref(new Date())
const rows = [
{ title: 'Длительность звонка', value: 9000 },
{ title: 'Сборка заняла', value: 10_770 },
]
const meeting: [Date, Date] = [
new Date(2026, 7, 12, 9, 0),
new Date(2026, 7, 12, 11, 30),
]
</script>
<template>
<div class="grid gap-3">
<div v-for="row in rows" :key="row.title" class="flex items-baseline justify-between gap-6 text-sm">
<span class="opacity-70">{{ row.title }}</span>
<GrDuration :value="row.value" />
</div>
<div class="flex items-baseline justify-between gap-6 text-sm">
<span class="opacity-70">Совещание</span>
<GrDuration :value="meeting" />
</div>
<div class="flex items-baseline justify-between gap-6 text-sm">
<span class="opacity-70">Страница открыта</span>
<GrDuration :value="started" />
</div>
<p class="showcase-demo-text text-sm opacity-70">
«Сборка заняла» показывает <strong>2 ч 59 мин</strong>, а не «3 ч»: младшее
отбрасывается, а не округляется. Последняя строка тикает сама — такт
выбирается по младшей показанной единице, и таймер в приложении один на всех.
</p>
</div>
</template>