{ "name": "leaderboard-card", "type": "registry:ui", "registryDependencies": [], "dependencies": [ "lucide-react" ], "devDependencies": [], "tailwind": {}, "cssVars": { "light": {}, "dark": {} }, "files": [ { "path": "leaderboard-card.tsx", "content": "'use client';\n\nimport * as React from 'react';\nimport { cn } from '@/lib/utils';\nimport { Crown } from 'lucide-react';\n\ntype Tone = 'emerald' | 'blue' | 'zinc';\ntype Size = 'sm' | 'md' | 'lg';\n\nexport type LeaderboardCardProps = {\n name: string;\n amount?: number | string; // e.g. 8034 or \"$8,034\"\n amountPrefix?: string; // e.g. \"$\"\n avatarSrc?: string;\n avatarAlt?: string;\n rank?: number; // 1..n (shows small badge)\n score?: number; // 0..100 -> progress + right-aligned %\n label?: string; // left label under pill (default \"Score\")\n showCrown?: boolean; // show crown for #1\n tone?: Tone; // background + bar color\n size?: Size; // paddings/fonts\n className?: string;\n /** Optional custom avatar ring class (e.g., gradient ring) */\n avatarRingClassName?: string;\n /** Optional custom progress color class (overrides tone) */\n progressClassName?: string;\n /** Optional custom pill class */\n pillClassName?: string;\n};\n\nconst toneMap: Record<\n Tone,\n {\n card: string;\n pill: string;\n progressTrack: string;\n progressFill: string;\n name: string;\n }\n> = {\n emerald: {\n card: 'bg-emerald-50/70 dark:bg-emerald-900/20 ring-1 ring-emerald-100 dark:ring-emerald-800',\n pill: 'bg-emerald-600/90 text-emerald-50',\n progressTrack: 'bg-emerald-200/60 dark:bg-emerald-900/50',\n progressFill: 'bg-emerald-500',\n name: 'text-emerald-900 dark:text-emerald-100',\n },\n blue: {\n card: 'bg-blue-50/80 dark:bg-blue-900/20 ring-1 ring-blue-100 dark:ring-blue-800',\n pill: 'bg-blue-600/90 text-blue-50',\n progressTrack: 'bg-blue-200/60 dark:bg-blue-900/50',\n progressFill: 'bg-blue-600',\n name: 'text-blue-900 dark:text-blue-100',\n },\n zinc: {\n card: 'bg-zinc-100/70 dark:bg-zinc-900/30 ring-1 ring-zinc-200 dark:ring-zinc-800',\n pill: 'bg-zinc-800 text-zinc-50',\n progressTrack: 'bg-zinc-200/60 dark:bg-zinc-800',\n progressFill: 'bg-zinc-600',\n name: 'text-zinc-900 dark:text-zinc-100',\n },\n};\n\nconst sizeMap: Record<\n Size,\n { pad: string; name: string; pill: string; caption: string; avatar: string }\n> = {\n sm: {\n pad: 'p-4',\n name: 'text-base',\n pill: 'text-[11px] px-2.5 py-1',\n caption: 'text-xs',\n avatar: 'h-14 w-14',\n },\n md: {\n pad: 'p-6',\n name: 'text-lg',\n pill: 'text-xs px-3 py-1.5',\n caption: 'text-sm',\n avatar: 'h-16 w-16',\n },\n lg: {\n pad: 'p-8',\n name: 'text-xl',\n pill: 'text-sm px-3.5 py-1.5',\n caption: 'text-sm',\n avatar: 'h-20 w-20',\n },\n};\n\nfunction formatAmount(amount?: number | string, prefix?: string) {\n if (amount == null) return undefined;\n if (typeof amount === 'string') return amount;\n try {\n return `${prefix ?? ''}${amount.toLocaleString()}`;\n } catch {\n return `${prefix ?? ''}${amount}`;\n }\n}\n\nexport function LeaderboardCard({\n name,\n amount,\n amountPrefix = '$',\n avatarSrc,\n avatarAlt = name,\n rank,\n score = 0,\n label = 'Score',\n showCrown = rank === 1,\n tone = 'emerald',\n size = 'md',\n className,\n avatarRingClassName,\n progressClassName,\n pillClassName,\n}: LeaderboardCardProps) {\n const t = toneMap[tone];\n const s = sizeMap[size];\n const pct = Math.max(0, Math.min(100, score));\n const amountLabel = formatAmount(amount, amountPrefix);\n\n return (\n \n {/* avatar */}\n
\n
\n \n {avatarSrc ? (\n // eslint-disable-next-line @next/next/no-img-element\n \n ) : (\n
\n \n {name?.[0] ?? '?'}\n \n
\n )}\n {showCrown && (\n \n )}\n
\n {typeof rank === 'number' && (\n \n {rank}\n \n )}\n
\n\n {/* name + amount pill */}\n
\n {name}\n
\n {amountLabel && (\n \n {amountLabel}\n \n )}\n \n\n {/* score bar */}\n
\n
\n {label}\n {pct}%\n
\n \n \n
\n \n \n );\n}\n", "type": "registry:ui" } ] }