A date field that writes the date back as today, tomorrow, in 3 days, last Friday or 2 weeks ago in the viewer's language, picking the unit that reads best. Use this when a date should be read at a glance as how far away it is, not only as a number.
registry/patterns/relative-date-label/relative-date-label.tsx
"use client"
import * as React from "react"
import { DateField } from "@/components/ui/date-field"
import {
diffDays,
resolveLocale,
today as todayISO,
} from "@/components/ui/date-time-core"
export interface RelativeDateLabelProps {
value?: string | null
defaultValue?: string | null
onValueChange?: (value: string | null) => void
today?: string
locale?: string
}
/** "tomorrow", "in 3 days", "2 weeks ago", "last year": the unit that reads best. */
export function relativeDateLabel(
date: string,
today: string,
locale?: string
) {
const n = diffDays(today, date)
const abs = Math.abs(n)
const rtf = new Intl.RelativeTimeFormat(resolveLocale(locale), {
numeric: "auto",
})
if (abs < 14) return rtf.format(n, "day")
if (abs < 60) return rtf.format(Math.round(n / 7), "week")
if (abs < 365) return rtf.format(Math.round(n / 30.44), "month")
return rtf.format(Math.round(n / 365.25), "year")
}
export function RelativeDateLabel({
value,
defaultValue = null,
onValueChange,
today,
locale,
}: RelativeDateLabelProps) {
const loc = resolveLocale(locale)
const now = today ?? todayISO()
const [inner, setInner] = React.useState<string | null>(defaultValue)
const date = value === undefined ? inner : value
return (
<div className="flex flex-wrap items-center gap-3">
<DateField
label="Date"
locale={loc}
value={date}
onValueChange={(next) => {
if (value === undefined) setInner(next)
onValueChange?.(next)
}}
/>
<p
role="status"
className="text-muted-foreground text-sm first-letter:uppercase"
>
{date ? relativeDateLabel(date, now, loc) : ""}
</p>
</div>
)
}