{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "points-awards",
  "title": "Points Awards",
  "description": "React points activity feed with dated events, totals, and trigger types for gamification timelines. Useful for XP and currency ledgers in dashboards. Built on shadcn/ui + Tailwind. Open source.",
  "dependencies": [
    "@radix-ui/react-tooltip",
    "lucide-react"
  ],
  "files": [
    {
      "path": "registry/trophy/ui/points-awards.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\"\nimport {\n  Award,\n  Clock,\n  Flame,\n  Sparkles,\n  Target,\n  UserPlus,\n  type LucideIcon,\n} from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface PointsAwardTrigger {\n  id: string\n  type: string\n  points: number\n  metricName?: string | null\n  metricThreshold?: number | null\n  achievementName?: string | null\n  streakLengthThreshold?: number | null\n  timeUnit?: \"hour\" | \"day\"\n  timeInterval?: number | null\n}\n\nexport interface PointsAward {\n  id: string\n  awarded: number\n  /** ISO 8601 datetime */\n  date: string\n  /** User's total points after this award */\n  total: number\n  trigger: PointsAwardTrigger\n}\n\ninterface PointsAwardsProps extends React.HTMLAttributes<HTMLDivElement> {\n  awards: PointsAward[]\n  formatTotalPoints?: (value: number) => string\n  formatAwardedPoints?: (value: number) => string\n  /** Format the award `date` for the first column (default: short locale date). */\n  formatDate?: (isoDate: string) => string\n}\n\nconst TooltipProvider = TooltipPrimitive.Provider\nconst Tooltip = TooltipPrimitive.Root\nconst TooltipTrigger = TooltipPrimitive.Trigger\nconst TooltipContent = React.forwardRef<\n  React.ElementRef<typeof TooltipPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>\n>(({ className, sideOffset = 6, ...props }, ref) => (\n  <TooltipPrimitive.Content\n    ref={ref}\n    sideOffset={sideOffset}\n    className={cn(\n      \"bg-popover text-popover-foreground z-50 max-w-xs overflow-hidden rounded-md border px-3 py-1.5 text-xs shadow-md\",\n      \"animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95\",\n      \"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n      className\n    )}\n    {...props}\n  />\n))\nTooltipContent.displayName = TooltipPrimitive.Content.displayName\n\nconst triggerIconMap: Record<string, LucideIcon> = {\n  metric: Target,\n  achievement: Award,\n  streak: Flame,\n  time: Clock,\n  user_creation: UserPlus,\n}\n\nfunction triggerIcon(type: string): LucideIcon {\n  return triggerIconMap[type] ?? Sparkles\n}\n\n/** Human-readable action line for tooltips — prefers `metricName` when present. */\nfunction awardActionDescription(trigger: PointsAwardTrigger): string {\n  if (trigger.metricName) {\n    if (\n      trigger.metricThreshold != null &&\n      trigger.metricThreshold !== undefined\n    ) {\n      return `${trigger.metricName} · threshold ${Number(trigger.metricThreshold).toLocaleString()}`\n    }\n    return trigger.metricName\n  }\n  if (trigger.type === \"achievement\") {\n    return trigger.achievementName ?? \"Achievement\"\n  }\n  if (trigger.type === \"streak\") {\n    return trigger.streakLengthThreshold != null\n      ? `Streak · ${trigger.streakLengthThreshold.toLocaleString()}`\n      : \"Streak\"\n  }\n  if (\n    trigger.type === \"time\" &&\n    trigger.timeInterval != null &&\n    trigger.timeUnit\n  ) {\n    return `Every ${trigger.timeInterval} ${trigger.timeUnit}(s)`\n  }\n  if (trigger.type === \"user_creation\") {\n    return \"Account created\"\n  }\n  return trigger.type.replace(/_/g, \" \")\n}\n\nfunction defaultFormatAwardedPoints(value: number) {\n  return value > 0 ? `+${value.toLocaleString()}` : value.toLocaleString()\n}\n\nfunction defaultFormatAwardDate(iso: string) {\n  const d = new Date(iso)\n  if (Number.isNaN(d.getTime())) {\n    return iso.length >= 10 ? iso.slice(0, 10) : iso\n  }\n  return d.toLocaleDateString(undefined, {\n    month: \"short\",\n    day: \"numeric\",\n    year: \"numeric\",\n  })\n}\n\nconst PointsAwards = React.forwardRef<HTMLDivElement, PointsAwardsProps>(\n  (\n    {\n      className,\n      awards,\n      formatTotalPoints,\n      formatAwardedPoints,\n      formatDate,\n      ...props\n    },\n    ref\n  ) => {\n    const formatRowDate = formatDate ?? defaultFormatAwardDate\n\n    return (\n      <div\n        ref={ref}\n        className={cn(\"bg-card w-full rounded-xl border\", className)}\n        {...props}\n      >\n        <TooltipProvider>\n          <div\n            role=\"list\"\n            aria-label=\"Points awards history\"\n            className=\"divide-border divide-y\"\n          >\n            {awards.map((award) => {\n              const awardedLabel = formatAwardedPoints\n                ? formatAwardedPoints(award.awarded)\n                : defaultFormatAwardedPoints(award.awarded)\n              const totalLabel = formatTotalPoints\n                ? formatTotalPoints(award.total)\n                : award.total.toLocaleString()\n              const description = awardActionDescription(award.trigger)\n              const tooltip = `${awardedLabel} — ${description}`\n              const Icon = triggerIcon(award.trigger.type)\n\n              return (\n                <div\n                  key={award.id}\n                  role=\"listitem\"\n                  className=\"grid grid-cols-[7rem_minmax(0,1fr)_minmax(0,1fr)] items-center gap-4 px-3 py-3\"\n                >\n                  <span className=\"text-muted-foreground truncate text-sm\">\n                    {formatRowDate(award.date)}\n                  </span>\n\n                  <p className=\"flex items-center gap-2\">\n                    <span className=\"text-foreground justify-self-center font-bold tabular-nums\">\n                      {totalLabel}\n                    </span>\n                    <span className=\"text-success font-medium tabular-nums\">\n                      {awardedLabel}\n                    </span>\n                  </p>\n\n                  <div className=\"flex items-center justify-end gap-2\">\n                    <Tooltip>\n                      <TooltipTrigger asChild>\n                        <span\n                          aria-label={tooltip}\n                          className=\"bg-muted text-foreground inline-flex h-6 w-6 items-center justify-center rounded-full\"\n                        >\n                          <Icon className=\"h-3 w-3\" aria-hidden=\"true\" />\n                        </span>\n                      </TooltipTrigger>\n                      <TooltipContent side=\"top\">{tooltip}</TooltipContent>\n                    </Tooltip>\n                  </div>\n                </div>\n              )\n            })}\n          </div>\n        </TooltipProvider>\n      </div>\n    )\n  }\n)\n\nPointsAwards.displayName = \"PointsAwards\"\n\nexport { PointsAwards }\nexport type { PointsAwardsProps }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}