{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "points-levels-list",
  "title": "Points Levels List",
  "description": "React levels list with optional progress bar between tiers. Built on shadcn/ui + Tailwind. Open source.",
  "dependencies": [
    "lucide-react"
  ],
  "files": [
    {
      "path": "registry/trophy/ui/points-levels-list.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  BadgeCheck,\n  Medal,\n  Shield,\n  ShieldCheck,\n  ShieldHalf,\n  Sparkles,\n  Trophy,\n  type LucideIcon,\n} from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport type PointsLevelListIconType =\n  | \"beginner\"\n  | \"novice\"\n  | \"intermediate\"\n  | \"professional\"\n  | \"expert\"\n  | \"master\"\n  | \"grand-master\"\n  | \"enlightened\"\n\nexport interface PointsSubLevelList {\n  name: string\n  points: number\n}\n\nexport interface PointsLevelList {\n  id: string\n  key?: string\n  name: string\n  description?: string\n  badgeUrl?: string | null\n  points: number\n  iconType?: PointsLevelListIconType\n  subLevels?: PointsSubLevelList[]\n}\n\ninterface PointsLevelsListProps extends React.HTMLAttributes<HTMLDivElement> {\n  levels: PointsLevelList[]\n  currentPoints?: number\n  showProgressBar?: boolean\n  currentLevelLabel?: string\n  formatPoints?: (value: number) => string\n}\n\nconst pointsLevelListIconMap: Record<PointsLevelListIconType, LucideIcon> = {\n  beginner: Shield,\n  novice: ShieldHalf,\n  intermediate: BadgeCheck,\n  professional: ShieldCheck,\n  expert: Medal,\n  master: Trophy,\n  \"grand-master\": Trophy,\n  enlightened: Sparkles,\n}\n\nfunction formatRange(\n  points: number,\n  nextLevelPoints: number | null | undefined,\n  formatPoints?: (value: number) => string\n) {\n  const format = formatPoints ?? ((value: number) => value.toLocaleString())\n  if (typeof nextLevelPoints === \"number\") {\n    return `${format(points)}-${format(nextLevelPoints - 1)}`\n  }\n  return `${format(points)}+`\n}\n\nconst PointsLevelsList = React.forwardRef<\n  HTMLDivElement,\n  PointsLevelsListProps\n>(\n  (\n    {\n      className,\n      levels,\n      currentPoints,\n      showProgressBar = false,\n      currentLevelLabel = \"Your current level\",\n      formatPoints,\n      ...props\n    },\n    ref\n  ) => {\n    const currentLevelIndex =\n      typeof currentPoints === \"number\"\n        ? levels.reduce((matchedIndex, level, index) => {\n            if (currentPoints >= level.points) {\n              return index\n            }\n            return matchedIndex\n          }, -1)\n        : -1\n    const currentLevel =\n      currentLevelIndex >= 0 ? levels[currentLevelIndex] : undefined\n    const nextLevel =\n      currentLevelIndex >= 0 && currentLevelIndex < levels.length - 1\n        ? levels[currentLevelIndex + 1]\n        : undefined\n    const shouldShowProgressBar =\n      showProgressBar &&\n      typeof currentPoints === \"number\" &&\n      Boolean(currentLevel && nextLevel)\n\n    let progressPercent = 0\n    let pointsUntilNextLevel = 0\n    if (shouldShowProgressBar && currentLevel && nextLevel) {\n      const start = currentLevel.points\n      const end = nextLevel.points\n      const ratio = end > start ? (currentPoints - start) / (end - start) : 1\n      progressPercent = Math.max(0, Math.min(ratio * 100, 100))\n      pointsUntilNextLevel = Math.max(0, end - currentPoints)\n    }\n\n    const format = formatPoints ?? ((value: number) => value.toLocaleString())\n\n    return (\n      <div\n        ref={ref}\n        className={cn(\"bg-card w-full rounded-xl border\", className)}\n        {...props}\n      >\n        {shouldShowProgressBar && currentLevel && nextLevel ? (\n          <div className=\"space-y-3 border-b px-4 py-4\">\n            <p className=\"text-foreground\">\n              <span className=\"font-semibold\">{format(currentPoints)}</span>{\" \"}\n              points.{\" \"}\n              <span className=\"font-semibold\">\n                {format(pointsUntilNextLevel)}\n              </span>{\" \"}\n              until <span className=\"font-semibold\">{nextLevel.name}</span>\n            </p>\n            <div className=\"flex items-center gap-3\">\n              <span\n                aria-hidden=\"true\"\n                className=\"bg-foreground text-background inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-full\"\n              >\n                {React.createElement(\n                  pointsLevelListIconMap[currentLevel.iconType ?? \"beginner\"],\n                  {\n                    className: \"h-4 w-4\",\n                  }\n                )}\n              </span>\n              <div\n                className=\"bg-muted h-2 flex-1 overflow-hidden rounded-full\"\n                role=\"progressbar\"\n                aria-label={`Progress from ${currentLevel.name} to ${nextLevel.name}`}\n                aria-valuenow={Math.round(progressPercent)}\n                aria-valuemin={0}\n                aria-valuemax={100}\n              >\n                <div\n                  className=\"bg-primary h-full rounded-full transition-all duration-300\"\n                  style={{ width: `${progressPercent}%` }}\n                />\n              </div>\n              <span\n                aria-hidden=\"true\"\n                className=\"bg-foreground text-background inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-full\"\n              >\n                {React.createElement(\n                  pointsLevelListIconMap[nextLevel.iconType ?? \"beginner\"],\n                  {\n                    className: \"h-4 w-4\",\n                  }\n                )}\n              </span>\n            </div>\n          </div>\n        ) : null}\n        <div\n          role=\"list\"\n          aria-label=\"Points levels\"\n          className=\"divide-border divide-y\"\n        >\n          {levels.map((level, index) => {\n            const Icon = pointsLevelListIconMap[level.iconType ?? \"beginner\"]\n            const nextLevelPoints =\n              index < levels.length - 1 ? levels[index + 1].points : null\n\n            return (\n              <div\n                key={level.id}\n                role=\"listitem\"\n                className=\"grid grid-cols-[14rem_1fr_12rem] items-center gap-4 px-4 py-3\"\n              >\n                <div className=\"flex items-center gap-3\">\n                  <span\n                    aria-hidden=\"true\"\n                    className={cn(\n                      \"inline-flex h-6 w-6 items-center justify-center rounded-full\",\n                      !currentPoints ||\n                        (currentPoints && currentPoints >= level.points)\n                        ? \"bg-primary text-background\"\n                        : \"bg-muted text-muted-foreground\"\n                    )}\n                  >\n                    <Icon className=\"h-3.5 w-3.5\" />\n                  </span>\n                  <span className=\"text-foreground text-sm tabular-nums\">\n                    {formatRange(level.points, nextLevelPoints, formatPoints)}\n                  </span>\n                </div>\n\n                <span className=\"text-foreground truncate text-sm font-semibold whitespace-nowrap\">\n                  {level.name}\n                </span>\n\n                <span className=\"text-muted-foreground text-sm\">\n                  {currentLevelIndex >= 0 &&\n                  levels[currentLevelIndex]?.id === level.id\n                    ? currentLevelLabel\n                    : \"\"}\n                </span>\n              </div>\n            )\n          })}\n        </div>\n      </div>\n    )\n  }\n)\n\nPointsLevelsList.displayName = \"PointsLevelsList\"\n\nexport { PointsLevelsList }\nexport type { PointsLevelsListProps }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}