GrRelativeTime
Берут, когда свежесть важнее точности.
Когда брать
- свежесть важнее точности — лента, комментарии, уведомления, аудит-лог: «5 минут назад» читается быстрее, чем «12.08.2026, 14:32»;
- метка обязана оставаться верной —
liveпересчитывает текст, и «только что» не висит час спустя; - меток на странице много — сотня меток стоит одного
setInterval: такт общий и на скрытой вкладке снимается; - нужны обе формы — точная дата уезжает в
titleи в слот, поэтому наведение и скринридер дают абсолютный момент.
Когда взять другое
| Нужно | Берите |
|---|---|
| Выбрать дату, а не показать | GrDatePicker |
| Выбрать момент со временем | GrDateTimePicker |
| Показать последовательность событий | GrTimeline |
| Показать обратный отсчёт до дедлайна | своя обвязка над useChronoNow |
Точные значения так не показывают. Договор, платёж, расписание рейса
требуют даты: «через 2 дня» не отвечает на вопрос «какого числа». cutoff
задаёт порог, после которого компонент сам переходит на абсолютную форму, — и
для таких мест его выставляют, а не отключают живой пересчёт.
Единица выбирается двумя способами, и это видно на границах
До суток разрыв меряется прошедшим временем — секунда есть секунда, календарь тут ни при чём. От суток и дальше — календарными кортежами: дни, недели, полные месяцы, годы.
Отсюда два следствия. Месяц остаётся месяцем и в феврале, и в июле, потому что считается календарём. А сутки, длившиеся из-за перевода часов 23 часа, называются часами — потому что столько времени и правда прошло; иначе двухчасовой разрыв через полночь каждую ночь назывался бы вчерашним.
Такт зависит от единицы
Секунды пересчитываются раз в пять секунд, месяцы — раз в час. Одинаковый такт для всех означал бы либо секунду, дёргающую сотню меток впустую, либо минуту, из-за которой «только что» держится дольше, чем это правда.
Установка
npm i @feugene/granularity-chronoИмпорт
import { GrRelativeTime } from '@feugene/granularity-chrono/components/GrRelativeTime'API
API этого компонента ещё не посчитан: генератор витрины пока обходит только ядро. Пока его нет, справочник — в документации пакета.
Примеры 3
Cutoff
<script setup lang="ts">
// `GrRelativeTime` подставляется авто-импортом (`unplugin-vue-components`).
const now = new Date(2026, 7, 12, 12, 0)
const posts = [
new Date(2026, 7, 12, 9, 30),
new Date(2026, 7, 5, 12, 0),
new Date(2026, 6, 2, 12, 0),
new Date(2025, 10, 20, 12, 0),
]
/** Вид абсолютной даты — опциями `Intl`, а не строкой-паттерном. */
const format: Intl.DateTimeFormatOptions = { day: 'numeric', month: 'short', year: 'numeric' }
</script>
<template>
<div class="grid gap-2">
<p class="showcase-demo-text text-sm opacity-70">
Старше 30 дней — обычная дата: «347 дней назад» не помогает никому.
</p>
<div v-for="post in posts" :key="post.toISOString()" class="text-sm">
<GrRelativeTime :value="post" :base="now" :cutoff="30" :format="format" />
</div>
</div>
</template>Live
<script setup lang="ts">
import { ref } from 'vue'
// `GrRelativeTime` подставляется авто-импортом (`unplugin-vue-components`).
import { GrButton } from '@feugene/granularity'
/**
* Здесь `base` не задан: отсчёт идёт от общих часов пакета, и текст обновляется
* сам. Такт компонент выбирает по единице — секунды пересчитываются часто,
* месяцы редко, а таймер на такт в приложении один на всех.
*/
const events = ref<Date[]>([new Date()])
function add(): void {
events.value = [new Date(), ...events.value].slice(0, 5)
}
</script>
<template>
<div class="grid gap-3 justify-items-start">
<GrButton size="sm" @click="add">
Отметить событие
</GrButton>
<ul class="grid gap-1 text-sm">
<li v-for="event in events" :key="event.toISOString()">
<GrRelativeTime :value="event" />
</li>
</ul>
</div>
</template>Scale
<script setup lang="ts">
// `GrRelativeTime` подставляется авто-импортом (`unplugin-vue-components`).
/**
* Момент отсчёта задан пропом `base` — от него и считается вся шкала. Так
* пример показывает одно и то же в любой день и не зависит от часов машины,
* на которой открыт.
*/
const now = new Date(2026, 7, 12, 12, 0)
function ago(ms: number): Date {
return new Date(now.getTime() - ms)
}
const scale = [
{ label: 'секунды', value: ago(3 * 1000) },
{ label: 'минуты', value: ago(3 * 60_000) },
{ label: 'часы', value: ago(4 * 3_600_000) },
{ label: 'вчера', value: new Date(2026, 7, 11, 12, 0) },
{ label: 'недели', value: new Date(2026, 6, 29, 12, 0) },
{ label: 'месяцы', value: new Date(2026, 4, 12, 12, 0) },
{ label: 'годы', value: new Date(2024, 7, 12, 12, 0) },
{ label: 'будущее', value: new Date(2026, 7, 14, 12, 0) },
]
</script>
<template>
<div class="grid gap-2">
<div v-for="row in scale" :key="row.label" class="flex items-baseline gap-3 text-sm">
<span class="showcase-demo-text w-24 shrink-0 opacity-70">{{ row.label }}</span>
<GrRelativeTime :value="row.value" :base="now" />
<GrRelativeTime :value="row.value" :base="now" width="short" class="opacity-70" />
</div>
</div>
</template>