{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "time-ago",
  "title": "Time Ago",
  "description": "Auto-updating relative timestamp — \"3 minutes ago\", \"just now\", \"in 2 days\" — that re-renders on a timer so the label stays fresh without a reload. Use it wherever a raw date would be noise and recency is what matters: comment/post/message timestamps, a notification or activity feed, \"last seen\"/\"last updated\"/\"last synced\" labels, commit or deploy history, table rows (created/modified), or a chat's message time. shadcn/ui ships no time-ago/relative-time component. Pass date as a Date, an ISO string, or epoch milliseconds. Wording comes from the platform's own Intl.RelativeTimeFormat, so it localizes for free via the locale prop and reads correctly for both past and future times; set numeric=\"auto\" to get \"yesterday\"/\"tomorrow\" instead of \"1 day ago\", and format to \"short\" or \"narrow\" for compact \"3 min. ago\"/\"3m ago\". Anything newer than justNowThreshold seconds (default 45) shows justNowLabel (\"just now\"). The tick rate adapts — every 15s while under a minute old, per-minute under an hour, then hourly — or pin it with updateInterval. Renders a semantic <time> element with a machine-readable dateTime and a title tooltip carrying the full localized date, and is SSR/hydration-safe. Theme-aware via shadcn's text-muted-foreground token; depends only on your cn util — no date library, no extra packages.",
  "files": [
    {
      "path": "registry/ui/time-ago.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\n// Each step is \"how many of me make one of the next unit up\".\nconst DIVISIONS: { amount: number; unit: Intl.RelativeTimeFormatUnit }[] = [\n  { amount: 60, unit: \"second\" },\n  { amount: 60, unit: \"minute\" },\n  { amount: 24, unit: \"hour\" },\n  { amount: 7, unit: \"day\" },\n  { amount: 4.34524, unit: \"week\" },\n  { amount: 12, unit: \"month\" },\n  { amount: Number.POSITIVE_INFINITY, unit: \"year\" },\n]\n\ninterface TimeAgoProps\n  extends Omit<\n    React.ComponentPropsWithoutRef<\"time\">,\n    \"dateTime\" | \"children\"\n  > {\n  /** The moment to describe. Accepts a Date, an ISO string, or epoch milliseconds. */\n  date: Date | string | number\n  /** Show justNowLabel for anything more recent than this many seconds (default 45). Set 0 to always show seconds. */\n  justNowThreshold?: number\n  /** Text shown inside the justNowThreshold window (default \"just now\"). */\n  justNowLabel?: string\n  /** Force a fixed re-render cadence in ms. By default it adapts: every 15s while under a minute old, every minute under an hour, then hourly. */\n  updateInterval?: number\n  /** BCP-47 locale(s) for the wording (default: the runtime locale). */\n  locale?: Intl.LocalesArgument\n  /** Passed to Intl.RelativeTimeFormat — numeric \"auto\" says \"yesterday\" instead of \"1 day ago\". Default \"always\". */\n  numeric?: Intl.RelativeTimeFormatNumeric\n  /** \"long\" (3 minutes ago), \"short\" (3 min. ago), or \"narrow\" (3m ago). Default \"long\". */\n  format?: Intl.RelativeTimeFormatStyle\n  /** Override the hover tooltip. Defaults to the full localized date and time. */\n  title?: string\n}\n\nfunction relativeLabel(\n  targetMs: number,\n  nowMs: number,\n  rtf: Intl.RelativeTimeFormat\n) {\n  // Negative while the target is in the past, so Intl says \"3 minutes ago\";\n  // positive gives \"in 3 minutes\".\n  let duration = (targetMs - nowMs) / 1000\n  for (const division of DIVISIONS) {\n    if (Math.abs(duration) < division.amount) {\n      return rtf.format(Math.round(duration), division.unit)\n    }\n    duration /= division.amount\n  }\n  return rtf.format(Math.round(duration), \"year\")\n}\n\n/**\n * Auto-updating relative timestamp — \"3 minutes ago\", \"in 2 days\" — rendered in\n * a semantic <time> element. shadcn/ui ships no time-ago component.\n */\nexport const TimeAgo = React.forwardRef<HTMLTimeElement, TimeAgoProps>(\n  function TimeAgo(\n    {\n      className,\n      date,\n      justNowThreshold = 45,\n      justNowLabel = \"just now\",\n      updateInterval,\n      locale,\n      numeric = \"always\",\n      format = \"long\",\n      title,\n      ...props\n    },\n    ref\n  ) {\n    const target = React.useMemo(() => new Date(date), [date])\n    const targetMs = target.getTime()\n    const valid = !Number.isNaN(targetMs)\n\n    // Re-render on a timer so the label stays fresh. It starts null so the\n    // markup is stable; a fresh Date.now() fills in below (see the note on\n    // suppressHydrationWarning) and the effect keeps it ticking after mount.\n    const [now, setNow] = React.useState<number | null>(null)\n\n    React.useEffect(() => {\n      if (!valid) return\n      let timer: ReturnType<typeof setTimeout>\n      function tick() {\n        const current = Date.now()\n        setNow(current)\n        const elapsed = Math.abs(current - targetMs)\n        const delay =\n          updateInterval ??\n          (elapsed < 60_000\n            ? 15_000\n            : elapsed < 3_600_000\n              ? 60_000\n              : 3_600_000)\n        timer = setTimeout(tick, delay)\n      }\n      tick()\n      return () => clearTimeout(timer)\n    }, [targetMs, valid, updateInterval])\n\n    const rtf = React.useMemo(\n      () => new Intl.RelativeTimeFormat(locale, { numeric, style: format }),\n      [locale, numeric, format]\n    )\n\n    const label = React.useMemo(() => {\n      if (!valid) return \"\"\n      // Fall back to a render-time clock before mount; server and client can\n      // land a few seconds apart, which suppressHydrationWarning absorbs.\n      const reference = now ?? Date.now()\n      if (\n        justNowThreshold > 0 &&\n        Math.abs(targetMs - reference) < justNowThreshold * 1000\n      ) {\n        return justNowLabel\n      }\n      return relativeLabel(targetMs, reference, rtf)\n    }, [valid, now, targetMs, justNowThreshold, justNowLabel, rtf])\n\n    if (!valid) return null\n\n    const fullTitle =\n      title ??\n      new Intl.DateTimeFormat(locale, {\n        dateStyle: \"full\",\n        timeStyle: \"short\",\n      }).format(target)\n\n    return (\n      <time\n        ref={ref}\n        dateTime={target.toISOString()}\n        title={fullTitle}\n        suppressHydrationWarning\n        className={cn(\"text-muted-foreground\", className)}\n        {...props}\n      >\n        {label}\n      </time>\n    )\n  }\n)\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}