{
  "name": "relative-time-card",
  "type": "registry:ui",
  "dependencies": [
    "cn",
    "radix-ui"
  ],
  "registryDependencies": [
    "hover-card"
  ],
  "files": [
    {
      "path": "ui/relative-time-card.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"cn\";\nimport { Slot as SlotPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\n\nimport {\n  HoverCard,\n  HoverCardContent,\n  HoverCardTrigger,\n} from \"@/registry/bases/radix/ui/hover-card\";\n\nfunction pluralize(n: number, word: string) {\n  return `${n} ${word}${n === 1 ? \"\" : \"s\"}`;\n}\n\nfunction formatRelativeTime(date: Date): string {\n  const now = new Date();\n  const diff = now.getTime() - date.getTime();\n  const isInFuture = diff < 0;\n  const absDiff = Math.abs(diff);\n\n  const seconds = Math.floor(absDiff / 1000);\n  const minutes = Math.floor(seconds / 60);\n  const hours = Math.floor(minutes / 60);\n  const days = Math.floor(hours / 24);\n\n  if (seconds < 5) return \"just now\";\n\n  if (isInFuture) {\n    if (seconds < 60) return `in ${pluralize(seconds, \"second\")}`;\n    if (minutes < 60) return `in ${pluralize(minutes, \"minute\")}`;\n    if (hours < 24) return `in ${pluralize(hours, \"hour\")}`;\n    if (days < 7) return `in ${pluralize(days, \"day\")}`;\n    return date.toLocaleDateString();\n  }\n\n  if (seconds < 60) return `${pluralize(seconds, \"second\")} ago`;\n  if (minutes < 60)\n    return `${pluralize(minutes, \"minute\")} ${pluralize(seconds % 60, \"second\")} ago`;\n  if (hours < 24) return `${pluralize(hours, \"hour\")} ago`;\n  if (days < 7) return `${pluralize(days, \"day\")} ago`;\n  return date.toLocaleDateString();\n}\n\ninterface TimezoneCardProps extends React.ComponentProps<\"div\"> {\n  date: Date;\n  timezone?: string;\n}\n\nfunction TimezoneCard(props: TimezoneCardProps) {\n  const { date, timezone, ...cardProps } = props;\n\n  const locale = React.useMemo(\n    () => Intl.DateTimeFormat().resolvedOptions().locale,\n    [],\n  );\n\n  const timezoneName = React.useMemo(\n    () =>\n      timezone ??\n      new Intl.DateTimeFormat(locale, { timeZoneName: \"shortOffset\" })\n        .formatToParts(date)\n        .find((part) => part.type === \"timeZoneName\")?.value,\n    [date, timezone, locale],\n  );\n\n  const { formattedDate, formattedTime } = React.useMemo(\n    () => ({\n      formattedDate: new Intl.DateTimeFormat(locale, {\n        month: \"long\",\n        day: \"numeric\",\n        year: \"numeric\",\n        timeZone: timezone,\n      }).format(date),\n      formattedTime: new Intl.DateTimeFormat(locale, {\n        hour: \"2-digit\",\n        minute: \"2-digit\",\n        second: \"2-digit\",\n        hour12: true,\n        timeZone: timezone,\n      }).format(date),\n    }),\n    [date, timezone, locale],\n  );\n\n  return (\n    <div\n      role=\"region\"\n      aria-label={`Time in ${timezoneName}: ${formattedDate} ${formattedTime}`}\n      {...cardProps}\n      className=\"flex items-center justify-between gap-2 text-sm text-muted-foreground\"\n    >\n      <span className=\"w-fit rounded bg-accent px-1 text-xs font-medium\">\n        {timezoneName}\n      </span>\n      <div className=\"flex items-center gap-2\">\n        <time dateTime={date.toISOString()}>{formattedDate}</time>\n        <time className=\"tabular-nums\" dateTime={date.toISOString()}>\n          {formattedTime}\n        </time>\n      </div>\n    </div>\n  );\n}\n\nconst triggerVariants = cva(\n  \"inline-flex w-fit items-center justify-center text-sm text-foreground/70 transition-colors hover:text-foreground/90 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none\",\n  {\n    variants: {\n      variant: {\n        default: \"\",\n        muted: \"text-foreground/50 hover:text-foreground/70\",\n        ghost: \"hover:underline\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  },\n);\n\ninterface RelativeTimeCardProps\n  extends\n    React.ComponentProps<\"button\">,\n    React.ComponentProps<typeof HoverCard>,\n    Pick<\n      React.ComponentProps<typeof HoverCardContent>,\n      | \"align\"\n      | \"side\"\n      | \"alignOffset\"\n      | \"sideOffset\"\n      | \"avoidCollisions\"\n      | \"collisionBoundary\"\n      | \"collisionPadding\"\n      | \"asChild\"\n    >,\n    VariantProps<typeof triggerVariants> {\n  date: Date | string | number;\n  timezones?: string[];\n  updateInterval?: number;\n}\n\nfunction RelativeTimeCard(props: RelativeTimeCardProps) {\n  const {\n    date: dateProp,\n    variant,\n    timezones = [\"UTC\"],\n    open,\n    defaultOpen,\n    onOpenChange,\n    openDelay = 500,\n    closeDelay = 300,\n    align,\n    side,\n    alignOffset,\n    sideOffset,\n    avoidCollisions,\n    collisionBoundary,\n    collisionPadding,\n    updateInterval = 1000,\n    asChild,\n    children,\n    className,\n    ...triggerProps\n  } = props;\n\n  const date = React.useMemo(\n    () => (dateProp instanceof Date ? dateProp : new Date(dateProp)),\n    [dateProp],\n  );\n\n  const locale = React.useMemo(\n    () => Intl.DateTimeFormat().resolvedOptions().locale,\n    [],\n  );\n\n  const [formattedTime, setFormattedTime] = React.useState<string>(() =>\n    date.toLocaleDateString(),\n  );\n\n  React.useEffect(() => {\n    setFormattedTime(formatRelativeTime(date));\n    const timer = setInterval(() => {\n      setFormattedTime(formatRelativeTime(date));\n    }, updateInterval);\n\n    return () => clearInterval(timer);\n  }, [date, updateInterval]);\n\n  const TriggerPrimitive = asChild ? SlotPrimitive.Slot : \"button\";\n\n  return (\n    <HoverCard\n      open={open}\n      defaultOpen={defaultOpen}\n      onOpenChange={onOpenChange}\n      openDelay={openDelay}\n      closeDelay={closeDelay}\n    >\n      <HoverCardTrigger asChild>\n        <TriggerPrimitive\n          {...triggerProps}\n          className={cn(triggerVariants({ variant, className }))}\n        >\n          {children ?? (\n            <time dateTime={date.toISOString()} suppressHydrationWarning>\n              {new Intl.DateTimeFormat(locale, {\n                month: \"short\",\n                day: \"numeric\",\n                year: \"numeric\",\n                hour: \"2-digit\",\n                minute: \"2-digit\",\n              }).format(date)}\n            </time>\n          )}\n        </TriggerPrimitive>\n      </HoverCardTrigger>\n      <HoverCardContent\n        side={side}\n        align={align}\n        sideOffset={sideOffset}\n        alignOffset={alignOffset}\n        avoidCollisions={avoidCollisions}\n        collisionBoundary={collisionBoundary}\n        collisionPadding={collisionPadding}\n        className=\"flex w-full max-w-[420px] flex-col gap-2 p-3\"\n      >\n        <time\n          dateTime={date.toISOString()}\n          className=\"text-sm text-muted-foreground\"\n        >\n          {formattedTime}\n        </time>\n        <div role=\"list\" className=\"flex flex-col gap-1\">\n          {timezones.map((timezone) => (\n            <TimezoneCard\n              key={timezone}\n              role=\"listitem\"\n              date={date}\n              timezone={timezone}\n            />\n          ))}\n          <TimezoneCard role=\"listitem\" date={date} />\n        </div>\n      </HoverCardContent>\n    </HoverCard>\n  );\n}\n\nexport { RelativeTimeCard };\n",
      "target": ""
    }
  ]
}