{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "polar-sponsor-grid",
  "title": "Polar Sponsor Grid",
  "description": "Grid of sponsor avatars grouped by tier with customizable colors and avatar sizes.",
  "dependencies": [
    "@polar-sh/sdk"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/blocks/polar/polar-sponsor-grid/components/elements/polar-sponsor-grid.tsx",
      "content": "\"use client\";\n\nimport { useMemo } from \"react\";\n\nfunction cn(...classes: (string | boolean | undefined)[]) {\n  return classes.filter(Boolean).join(\" \");\n}\n\nexport interface Sponsor {\n  id: string;\n  name: string;\n  avatarUrl?: string;\n  profileUrl?: string;\n  tier?: string;\n  amount?: number;\n}\n\nexport interface SponsorTier {\n  name: string;\n  minAmount: number;\n  color?: string;\n}\n\nexport interface PolarSponsorGridProps {\n  sponsors: Sponsor[];\n  tiers?: SponsorTier[];\n  showTierLabels?: boolean;\n  avatarSize?: \"sm\" | \"md\" | \"lg\";\n  maxDisplay?: number;\n  className?: string;\n}\n\nconst DEFAULT_TIERS: SponsorTier[] = [\n  { name: \"Platinum\", minAmount: 100, color: \"#E5E4E2\" },\n  { name: \"Gold\", minAmount: 50, color: \"#FFD700\" },\n  { name: \"Silver\", minAmount: 25, color: \"#C0C0C0\" },\n  { name: \"Bronze\", minAmount: 10, color: \"#CD7F32\" },\n  { name: \"Supporter\", minAmount: 0, color: \"#94a3b8\" },\n];\n\nconst AVATAR_SIZES = {\n  sm: \"h-8 w-8 text-xs\",\n  md: \"h-10 w-10 text-sm\",\n  lg: \"h-12 w-12 text-base\",\n};\n\nconst TIER_AVATAR_SIZES = {\n  Platinum: \"h-14 w-14 text-lg\",\n  Gold: \"h-12 w-12 text-base\",\n  Silver: \"h-10 w-10 text-sm\",\n  Bronze: \"h-9 w-9 text-xs\",\n  Supporter: \"h-8 w-8 text-xs\",\n};\n\nfunction getInitials(name: string): string {\n  return name\n    .split(\" \")\n    .map((n) => n[0])\n    .join(\"\")\n    .toUpperCase()\n    .slice(0, 2);\n}\n\nfunction stringToColor(str: string): string {\n  let hash = 0;\n  for (let i = 0; i < str.length; i++) {\n    hash = str.charCodeAt(i) + ((hash << 5) - hash);\n  }\n  const hue = hash % 360;\n  return `hsl(${hue}, 65%, 45%)`;\n}\n\nexport function PolarSponsorGrid({\n  sponsors,\n  tiers = DEFAULT_TIERS,\n  showTierLabels = true,\n  avatarSize,\n  maxDisplay,\n  className,\n}: PolarSponsorGridProps) {\n  const groupedSponsors = useMemo(() => {\n    const sorted = [...sponsors].sort((a, b) => (b.amount ?? 0) - (a.amount ?? 0));\n    const limited = maxDisplay ? sorted.slice(0, maxDisplay) : sorted;\n\n    const groups: Record<string, Sponsor[]> = {};\n\n    for (const sponsor of limited) {\n      const tier = tiers.find(\n        (t) => (sponsor.amount ?? 0) >= t.minAmount\n      ) ?? tiers[tiers.length - 1];\n      const tierName = sponsor.tier || tier.name;\n\n      if (!groups[tierName]) {\n        groups[tierName] = [];\n      }\n      groups[tierName].push(sponsor);\n    }\n\n    return groups;\n  }, [sponsors, tiers, maxDisplay]);\n\n  const orderedTiers = tiers.filter((t) => groupedSponsors[t.name]?.length > 0);\n\n  if (sponsors.length === 0) {\n    return (\n      <div\n        data-slot=\"polar-sponsor-grid\"\n        className={cn(\n          \"flex flex-col items-center justify-center rounded-lg border border-dashed border-border p-8 text-center\",\n          className\n        )}\n      >\n        <p className=\"text-sm text-muted-foreground\">No sponsors yet</p>\n        <p className=\"mt-1 text-xs text-muted-foreground/70\">\n          Be the first to support this project!\n        </p>\n      </div>\n    );\n  }\n\n  return (\n    <div\n      data-slot=\"polar-sponsor-grid\"\n      className={cn(\"space-y-6\", className)}\n    >\n      {orderedTiers.map((tier) => (\n        <div key={tier.name} data-slot=\"tier-group\">\n          {showTierLabels && (\n            <div\n              data-slot=\"tier-label\"\n              className=\"mb-3 flex items-center gap-2\"\n            >\n              <div\n                className=\"h-2 w-2 rounded-full\"\n                style={{ backgroundColor: tier.color }}\n              />\n              <span className=\"text-sm font-medium text-muted-foreground\">\n                {tier.name}\n              </span>\n              <span className=\"text-xs text-muted-foreground/70\">\n                ({groupedSponsors[tier.name].length})\n              </span>\n            </div>\n          )}\n          <div\n            data-slot=\"sponsor-list\"\n            className=\"flex flex-wrap gap-2\"\n          >\n            {groupedSponsors[tier.name].map((sponsor) => {\n              const size = avatarSize\n                ? AVATAR_SIZES[avatarSize]\n                : TIER_AVATAR_SIZES[tier.name as keyof typeof TIER_AVATAR_SIZES] ||\n                  AVATAR_SIZES.md;\n\n              const avatar = (\n                <div\n                  data-slot=\"sponsor-avatar\"\n                  className={cn(\n                    \"relative rounded-full ring-2 ring-offset-2 ring-offset-background transition-transform hover:scale-110\",\n                    size\n                  )}\n                  style={{ \"--tw-ring-color\": tier.color } as React.CSSProperties}\n                  title={`${sponsor.name}${sponsor.amount ? ` - $${sponsor.amount}/mo` : \"\"}`}\n                >\n                  {sponsor.avatarUrl ? (\n                    <img\n                      src={sponsor.avatarUrl}\n                      alt={sponsor.name}\n                      className=\"h-full w-full rounded-full object-cover\"\n                    />\n                  ) : (\n                    <div\n                      className=\"flex h-full w-full items-center justify-center rounded-full font-medium text-white\"\n                      style={{ backgroundColor: stringToColor(sponsor.name) }}\n                    >\n                      {getInitials(sponsor.name)}\n                    </div>\n                  )}\n                </div>\n              );\n\n              if (sponsor.profileUrl) {\n                return (\n                  <a\n                    key={sponsor.id}\n                    href={sponsor.profileUrl}\n                    target=\"_blank\"\n                    rel=\"noopener noreferrer\"\n                    className=\"focus:outline-none focus-visible:ring-2 focus-visible:ring-primary rounded-full\"\n                  >\n                    {avatar}\n                  </a>\n                );\n              }\n\n              return <div key={sponsor.id}>{avatar}</div>;\n            })}\n          </div>\n        </div>\n      ))}\n    </div>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "docs": "Automatically groups sponsors by contribution amount. Supports custom tiers, avatar fallbacks with initials, and profile links.",
  "categories": [
    "polar"
  ],
  "type": "registry:block"
}