{
  "name": "github-contributors",
  "dependencies": [
    "@radix-ui/react-tooltip",
    "lucide-react",
    "motion"
  ],
  "files": [
    {
      "path": "ui/github-contributors.tsx",
      "content": "\"use client\";\n\nimport { ExternalLink } from \"lucide-react\";\nimport { motion } from \"motion/react\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { Card, CardContent, CardTitle } from \"@/components/ui/card\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\n\ninterface Contributor {\n  id: number;\n  login: string;\n  avatar_url: string;\n  html_url: string;\n  contributions: number;\n}\n\ninterface GitHubContributorsProps {\n  repo: string; // e.g. \"vercel/next.js\"\n  limit?: number; // number of avatars to show (not counting the +more tile)\n  className?: string;\n  token?: string; // optional GitHub token to increase rate limit\n}\n\nexport function GitHubContributors({\n  repo,\n  limit = 12,\n  className = \"\",\n  token,\n}: GitHubContributorsProps) {\n  const [contributors, setContributors] = useState<Contributor[]>([]);\n  const [loading, setLoading] = useState(true);\n  const [error, setError] = useState<string | null>(null);\n  const [totalCount, setTotalCount] = useState<number | null>(null);\n\n  useEffect(() => {\n    if (!repo) return;\n    setLoading(true);\n    setError(null);\n    setTotalCount(null);\n\n    const headers: Record<string, string> = {\n      Accept: \"application/vnd.github.v3+json\",\n    };\n    if (token) headers.Authorization = `token ${token}`;\n\n    const fetchList = async () => {\n      try {\n        const listRes = await fetch(\n          `https://api.github.com/repos/${repo}/contributors?per_page=${limit}`,\n          { headers },\n        );\n        if (!listRes.ok)\n          throw new Error(\n            `GitHub API: ${listRes.status} ${listRes.statusText}`,\n          );\n        const listData: Contributor[] = await listRes.json();\n        setContributors(listData.slice(0, limit));\n\n        // Probe for total contributors (per_page=1 -> last page = total count)\n        try {\n          const probeRes = await fetch(\n            `https://api.github.com/repos/${repo}/contributors?per_page=1`,\n            { headers },\n          );\n          if (probeRes.ok) {\n            const link = probeRes.headers.get(\"link\");\n            if (link) {\n              const m = link.match(\n                /<[^>]+[?&]page=(\\d+)[^>]*>\\s*;\\s*rel=\"last\"/,\n              );\n              if (m?.[1]) {\n                const lastPage = parseInt(m[1], 10);\n                if (Number.isFinite(lastPage)) setTotalCount(lastPage);\n              }\n            } else {\n              const probeData = await probeRes.json();\n              if (Array.isArray(probeData)) setTotalCount(probeData.length);\n            }\n          }\n        } catch {\n          // ignore probe errors\n        }\n      } catch (err: unknown) {\n        setError((err as Error).message || \"Failed to load contributors\");\n        setContributors([]);\n      } finally {\n        setLoading(false);\n      }\n    };\n\n    fetchList();\n  }, [repo, limit, token]);\n\n  const shown = contributors.length;\n  const remaining =\n    totalCount !== null ? Math.max(0, totalCount - shown) : null;\n\n  // compute max contributions among shown contributors to render the progress bar\n  const maxContrib = useMemo(() => {\n    if (!contributors || contributors.length === 0) return 1;\n    return Math.max(...contributors.map((c) => c.contributions), 1);\n  }, [contributors]);\n\n  const repoUrl = `https://github.com/${repo}`;\n  const contributorsUrl = `${repoUrl}/graphs/contributors`;\n\n  return (\n    <Card\n      className={`overflow-hidden rounded-lg border bg-background shadow-sm ${className}`}\n      aria-live=\"polite\"\n    >\n      {/* Content */}\n      <CardContent className=\"px-4 py-3\">\n        {error && (\n          <p className=\"mb-2 text-destructive text-sm\">Failed: {error}</p>\n        )}\n\n        {loading ? (\n          <div className=\"grid grid-cols-5 gap-3 sm:grid-cols-8 md:grid-cols-10 lg:grid-cols-12\">\n            {Array.from({ length: limit }).map((_, i) => (\n              <div key={i} className=\"flex items-center justify-center\">\n                <Skeleton className=\"h-10 w-10 rounded-full\" />\n              </div>\n            ))}\n          </div>\n        ) : (\n          <div className=\"grid grid-cols-5 items-center gap-3 sm:grid-cols-8 md:grid-cols-10 lg:grid-cols-11\">\n            {contributors.map((c, idx) => {\n              const pct = Math.round((c.contributions / maxContrib) * 100);\n              const isTop = idx === 0; // highlight the top contributor in shown list\n              return (\n                <div\n                  key={c.id}\n                  className=\"relative flex items-center justify-center\"\n                >\n                  {/* top badge */}\n                  {isTop && (\n                    <div className=\"absolute -top-1 -right-1 z-10\">\n                      <div className=\"flex h-4 w-4 items-center justify-center rounded-full border border-white bg-yellow-400/90 font-semibold text-[10px] text-white shadow-sm\">\n                        <span>★</span>\n                      </div>\n                    </div>\n                  )}\n\n                  <Tooltip>\n                    <TooltipTrigger asChild>\n                      <motion.a\n                        href={c.html_url}\n                        target=\"_blank\"\n                        rel=\"noopener noreferrer\"\n                        title={`${c.login} — ${c.contributions} contributions`}\n                        className=\"relative flex h-10 w-10 items-center justify-center overflow-hidden rounded-full border bg-muted/10 transition hover:bg-muted focus:outline-none focus:ring-2 focus:ring-ring\"\n                        whileHover={isTop ? { scale: 1.07 } : { scale: 1.04 }}\n                        whileFocus={{ scale: 1.04 }}\n                        onClick={(e) => e.stopPropagation()}\n                        aria-label={`${c.login} GitHub profile`}\n                      >\n                        {/* biome-ignore lint/performance/noImgElement: next/image causes ESM issues with fumadocs-mdx */}\n                        <img\n                          src={c.avatar_url}\n                          alt={c.login}\n                          width={40}\n                          height={40}\n                          className=\"h-full w-full object-cover\"\n                        />\n                        {/* subtle ring on hover via pseudo element class; already handled by tailwind tokens */}\n                      </motion.a>\n                    </TooltipTrigger>\n\n                    <TooltipContent\n                      side=\"top\"\n                      align=\"center\"\n                      className=\"w-64 bg-transparent p-0 shadow-none\"\n                    >\n                      <motion.div\n                        initial={{ opacity: 0, scale: 0.96, y: 6 }}\n                        animate={{ opacity: 1, scale: 1, y: 0 }}\n                        transition={{ duration: 0.12 }}\n                        className=\"rounded-lg border bg-popover p-3 text-popover-foreground shadow-md\"\n                        role=\"dialog\"\n                        aria-label={`${c.login} contributor details`}\n                      >\n                        <div className=\"flex items-center gap-3\">\n                          <div className=\"h-12 w-12 flex-shrink-0 overflow-hidden rounded-md border\">\n                            {/* biome-ignore lint/performance/noImgElement: next/image causes ESM issues with fumadocs-mdx */}\n                            <img\n                              src={c.avatar_url}\n                              alt={c.login}\n                              width={48}\n                              height={48}\n                              className=\"object-cover\"\n                            />\n                          </div>\n\n                          <div className=\"min-w-0 flex-1\">\n                            <div className=\"flex items-center gap-2\">\n                              <div className=\"truncate font-medium text-foreground\">\n                                {c.login}\n                              </div>\n                              <div className=\"ml-auto font-mono text-muted-foreground text-xs\">\n                                #{c.id}\n                              </div>\n                            </div>\n\n                            <div className=\"mt-1 text-muted-foreground text-xs\">\n                              {c.contributions.toLocaleString()} contributions\n                            </div>\n\n                            {/* mini contribution bar */}\n                            <div className=\"mt-2\">\n                              <div className=\"h-2 w-full overflow-hidden rounded-full bg-muted\">\n                                <div\n                                  className=\"h-2 rounded-full bg-primary transition-all duration-300\"\n                                  style={{ width: `${pct}%` }}\n                                  aria-hidden\n                                />\n                              </div>\n                              <div className=\"mt-1 text-[11px] text-muted-foreground\">\n                                {pct}% of top contributor\n                              </div>\n                            </div>\n                          </div>\n                        </div>\n\n                        <div className=\"mt-3 flex items-center justify-between gap-2\">\n                          <a\n                            href={c.html_url}\n                            target=\"_blank\"\n                            rel=\"noopener noreferrer\"\n                            className=\"inline-flex items-center gap-2 rounded-md bg-secondary px-2 py-1 font-medium text-secondary-foreground text-sm transition-colors hover:bg-secondary/80\"\n                            onClick={(e) => e.stopPropagation()}\n                            aria-label={`Open ${c.login} on GitHub`}\n                          >\n                            <ExternalLink className=\"h-4 w-4\" />\n                            <span className=\"font-medium text-sm\">\n                              View profile\n                            </span>\n                          </a>\n\n                          <div className=\"text-muted-foreground text-xs\">\n                            Contributions:{\" \"}\n                            <span className=\"font-medium text-foreground\">\n                              {c.contributions}\n                            </span>\n                          </div>\n                        </div>\n                      </motion.div>\n                    </TooltipContent>\n                  </Tooltip>\n                </div>\n              );\n            })}\n\n            {/* +N tile (or generic + tile) */}\n            {remaining !== null && remaining > 0 ? (\n              <a\n                href={contributorsUrl}\n                target=\"_blank\"\n                rel=\"noopener noreferrer\"\n                className=\"flex h-10 w-10 items-center justify-center rounded-full border bg-muted/5 text-muted-foreground text-xs transition hover:bg-muted\"\n                aria-label={`View all ${totalCount} contributors`}\n              >\n                +{remaining}\n              </a>\n            ) : remaining === null &&\n              !loading &&\n              contributors.length === limit ? (\n              <a\n                href={contributorsUrl}\n                target=\"_blank\"\n                rel=\"noopener noreferrer\"\n                className=\"flex h-10 w-10 items-center justify-center rounded-full border bg-muted/5 text-muted-foreground text-xs transition hover:bg-muted\"\n                aria-label={`View more contributors`}\n              >\n                +\n              </a>\n            ) : null}\n          </div>\n        )}\n      </CardContent>\n\n      {/* Footer CTA */}\n      <div className=\"flex items-center justify-between gap-3 border-t bg-background/50 px-4 py-3\">\n        <div className=\"min-w-0\">\n          <CardTitle className=\"m-0 truncate font-semibold text-sm\">\n            {repo}\n          </CardTitle>\n          <div className=\"text-muted-foreground text-xs\">\n            {loading ? (\n              <span>Loading…</span>\n            ) : error ? (\n              <span className=\"text-destructive\">Failed to load</span>\n            ) : (\n              <span>\n                Showing <span className=\"font-medium\">{shown}</span>\n                {totalCount ? (\n                  <>\n                    {\" \"}\n                    of <span className=\"font-medium\">{totalCount}</span>\n                  </>\n                ) : shown === limit ? (\n                  <> (more)</>\n                ) : null}\n              </span>\n            )}\n          </div>\n        </div>\n\n        <div className=\"flex items-center gap-2\">\n          <a\n            href={repoUrl}\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            className=\"inline-flex items-center gap-2 rounded-md px-2 py-1 font-medium text-xs transition hover:bg-muted\"\n            aria-label={`Open ${repo} on GitHub`}\n          >\n            <ExternalLink className=\"h-4 w-4\" />\n            <span>Open repo</span>\n          </a>\n        </div>\n      </div>\n    </Card>\n  );\n}\n",
      "type": "registry:ui",
      "target": ""
    }
  ],
  "type": "registry:ui"
}