{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"relative-time","type":"registry:ui","title":"Relative Time","description":"A moment as people say it, \"3 minutes ago\", in the reader's language and kept current.","dependencies":[],"registryDependencies":["https://dashral.com/r/utils.json","https://dashral.com/r/lib-locale.json"],"files":[{"path":"registry/dashral/relative-time.tsx","type":"registry:file","target":"components/dashral/relative-time.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\n\nimport { useLocale } from \"./lib/locale\"\nimport { cn } from \"./lib/utils\"\n\ntype RelativeTimeProps = Omit<React.ComponentProps<\"time\">, \"children\" | \"dateTime\"> & {\n  /** The moment: a Date, an ISO string (\"2026-11-06T09:30:00Z\") or milliseconds since 1970. Past or future. */\n  date: Date | string | number\n  /** long: \"3 minutes ago\"; short: \"3 min. ago\"; narrow: \"3m ago\" (as the locale writes them). */\n  style?: Intl.RelativeTimeFormatStyle\n  /** Past this many days, the date itself (\"Oct 12\") instead of \"5 weeks ago\". Never by default. */\n  absoluteAfterDays?: number\n  /** How the date is written past absoluteAfterDays. */\n  formatOptions?: Intl.DateTimeFormatOptions\n  /** The full date and time shown on hover (the title). */\n  titleFormatOptions?: Intl.DateTimeFormatOptions\n  /** Keeps it current as time passes: each 15 seconds under a minute, each 30 under an hour, then less often. On by default. */\n  live?: boolean\n  /** BCP 47 locale; the LocaleProvider's by default. */\n  locale?: string\n}\n\nconst SECOND = 1000\nconst MINUTE = 60 * SECOND\nconst HOUR = 60 * MINUTE\nconst DAY = 24 * HOUR\n\n/** The largest unit that reads naturally: \"now\", \"5 minutes\", \"3 hours\", \"yesterday\", \"2 weeks\", \"4 months\", \"2 years\". */\nfunction pick(diff: number): [number, Intl.RelativeTimeFormatUnit] {\n  const abs = Math.abs(diff)\n  const sign = diff < 0 ? -1 : 1\n  if (abs < 45 * SECOND) return [0, \"second\"]\n  if (abs < 45 * MINUTE) return [sign * Math.max(1, Math.round(abs / MINUTE)), \"minute\"]\n  if (abs < 22 * HOUR) return [sign * Math.round(abs / HOUR), \"hour\"]\n  if (abs < 7 * DAY) return [sign * Math.max(1, Math.round(abs / DAY)), \"day\"]\n  if (abs < 30 * DAY) return [sign * Math.round(abs / (7 * DAY)), \"week\"]\n  if (abs < 345 * DAY) return [sign * Math.max(1, Math.round(abs / (30.44 * DAY))), \"month\"]\n  return [sign * Math.max(1, Math.round(abs / (365.25 * DAY))), \"year\"]\n}\n\n/** How long until the text could change, so a timer wakes no more than it must. */\nfunction nextTick(diff: number) {\n  const abs = Math.abs(diff)\n  if (abs < MINUTE) return 15 * SECOND\n  if (abs < HOUR) return 30 * SECOND\n  if (abs < DAY) return 5 * MINUTE\n  return HOUR\n}\n\n/**\n * A moment as people say it, in one tag: `<RelativeTime date={comment.createdAt} />` reads \"3 minutes ago\", \"yesterday\"\n * or \"in 2 days\", in the reader's language, and keeps itself current. A real <time> with the exact moment in\n * dateTime, and the full date and time on hover. The server writes it from its clock and the browser corrects it as it\n * loads, without a hydration warning.\n */\nfunction RelativeTime({\n  date,\n  style = \"long\",\n  absoluteAfterDays,\n  formatOptions = { dateStyle: \"medium\" },\n  titleFormatOptions = { dateStyle: \"full\", timeStyle: \"short\" },\n  live = true,\n  locale: localeProp,\n  className,\n  ...props\n}: RelativeTimeProps) {\n  const { locale: contextLocale } = useLocale()\n  const locale = localeProp ?? contextLocale\n  const time = new Date(date).getTime()\n  const [now, setNow] = React.useState(() => Date.now())\n\n  React.useEffect(() => {\n    setNow(Date.now())\n    if (!live) return\n    let timer: ReturnType<typeof setTimeout>\n    const schedule = () => {\n      timer = setTimeout(() => {\n        setNow(Date.now())\n        schedule()\n      }, nextTick(time - Date.now()))\n    }\n    schedule()\n    return () => clearTimeout(timer)\n  }, [time, live])\n\n  if (Number.isNaN(time)) return null\n  const diff = time - now\n  const absolute = absoluteAfterDays !== undefined && Math.abs(diff) > absoluteAfterDays * DAY\n  const [value, unit] = pick(diff)\n  const text = absolute\n    ? new Intl.DateTimeFormat(locale, formatOptions).format(time)\n    : new Intl.RelativeTimeFormat(locale, { numeric: \"auto\", style }).format(value, unit)\n\n  return (\n    <time\n      data-slot=\"relative-time\"\n      dateTime={new Date(time).toISOString()}\n      title={new Intl.DateTimeFormat(locale, titleFormatOptions).format(time)}\n      // The server's clock (and time zone) aren't the reader's: the text is corrected right after it loads.\n      suppressHydrationWarning\n      className={cn(\"tabular-nums\", className)}\n      {...props}\n    >\n      {text}\n    </time>\n  )\n}\n\nexport { RelativeTime, type RelativeTimeProps }\n"}]}