Particular.Tech
Data displayui

Relative time

"3 min ago" that stays current, with the absolute time in a tooltip.

  • Deployed
  • Deployed
  • Deployed
  • Deployed
  • Deployed

Installation

npx shadcn@latest add @particular/relative-time
npm dependencies
  • cn@^0.4.0
Installs these registry items

Source

ui/relative-time.tsx
"use client"

import * as React from "react"
import { cn } from "cn"

import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"

const units: Array<[Intl.RelativeTimeFormatUnit, number]> = [
  ["year", 365 * 24 * 60 * 60],
  ["month", 30 * 24 * 60 * 60],
  ["week", 7 * 24 * 60 * 60],
  ["day", 24 * 60 * 60],
  ["hour", 60 * 60],
  ["minute", 60],
]

function formatRelativeTime(date: Date, now: Date, locale = "en-US"): string {
  const seconds = Math.round((date.getTime() - now.getTime()) / 1000)
  if (Math.abs(seconds) < 45) return "just now"
  const format = new Intl.RelativeTimeFormat(locale, { numeric: "auto", style: "short" })
  for (const [unit, size] of units) {
    if (Math.abs(seconds) >= size) return format.format(Math.round(seconds / size), unit)
  }
  return format.format(Math.round(seconds / 60), "minute")
}

function useNow(intervalMs: number) {
  const [now, setNow] = React.useState(() => new Date())
  React.useEffect(() => {
    const id = setInterval(() => setNow(new Date()), intervalMs)
    return () => clearInterval(id)
  }, [intervalMs])
  return now
}

function RelativeTime({
  date,
  locale = "en-US",
  updateInterval = 30_000,
  className,
  ...props
}: Omit<React.ComponentProps<"time">, "dateTime" | "children"> & {
  date: Date | string | number
  locale?: string
  updateInterval?: number
}) {
  const value = new Date(date)
  const now = useNow(updateInterval)
  const absolute = new Intl.DateTimeFormat(locale, { dateStyle: "medium", timeStyle: "long" }).format(value)

  return (
    <Tooltip>
      <TooltipTrigger
        render={
          <time
            dateTime={value.toISOString()}
            suppressHydrationWarning
            className={cn("whitespace-nowrap tabular-nums underline-offset-2 hover:underline hover:decoration-dotted", className)}
            {...props}
          />
        }
      >
        {formatRelativeTime(value, now, locale)}
      </TooltipTrigger>
      <TooltipContent>
        <span suppressHydrationWarning>{absolute}</span>
      </TooltipContent>
    </Tooltip>
  )
}

export { RelativeTime, formatRelativeTime }