{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "github-contributions",
  "title": "GitHub Contributions",
  "description": "Display contribution activity grid like GitHub's profile",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/blocks/github/github-contributions/components/elements/github-contributions.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\ninterface GitHubContributionsProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  owner: string;\n  repo: string;\n  staticContributions?: number;\n  staticData?: number[][];\n}\n\nfunction generateRandomGrid(rows: number, cols: number): number[][] {\n  return Array.from({ length: rows }, () =>\n    Array.from({ length: cols }, () => Math.random()),\n  );\n}\n\ninterface HeatmapGridProps {\n  data: number[][];\n  className?: string;\n}\n\nconst HEATMAP_ROWS = 7;\nconst HEATMAP_COLS = 12;\nconst HEATMAP_CELL_SIZE = 14;\nconst HEATMAP_GAP = 4;\nconst HEATMAP_VIEWBOX_WIDTH =\n  HEATMAP_COLS * HEATMAP_CELL_SIZE + (HEATMAP_COLS - 1) * HEATMAP_GAP;\nconst HEATMAP_VIEWBOX_HEIGHT =\n  HEATMAP_ROWS * HEATMAP_CELL_SIZE + (HEATMAP_ROWS - 1) * HEATMAP_GAP;\n\nfunction HeatmapGrid({ data, className }: HeatmapGridProps) {\n  const gridData = useMemo(() => {\n    if (!data || data.length === 0) {\n      return generateRandomGrid(HEATMAP_ROWS, HEATMAP_COLS);\n    }\n    return data;\n  }, [data]);\n\n  const rows = gridData.length;\n  const cols = gridData[0]?.length || 0;\n\n  const cells = useMemo(() => {\n    const result: Array<{\n      x: number;\n      y: number;\n      opacity: number;\n      key: string;\n    }> = [];\n\n    for (let row = 0; row < rows; row++) {\n      for (let col = 0; col < cols; col++) {\n        const value = gridData[row]?.[col] ?? 0;\n        result.push({\n          x: col * (HEATMAP_CELL_SIZE + HEATMAP_GAP),\n          y: row * (HEATMAP_CELL_SIZE + HEATMAP_GAP),\n          opacity: Math.max(0.05, Math.min(1, value)),\n          key: `${row}-${col}`,\n        });\n      }\n    }\n\n    return result;\n  }, [gridData, rows, cols]);\n\n  return (\n    <svg\n      viewBox={`0 0 ${HEATMAP_VIEWBOX_WIDTH} ${HEATMAP_VIEWBOX_HEIGHT}`}\n      fill=\"none\"\n      preserveAspectRatio=\"xMidYMid meet\"\n      className={cn(\"w-full h-auto text-muted-foreground\", className)}\n      aria-hidden=\"true\"\n    >\n      <g opacity={0.5}>\n        {cells.map((cell) => (\n          <rect\n            key={cell.key}\n            x={cell.x}\n            y={cell.y}\n            width={HEATMAP_CELL_SIZE}\n            height={HEATMAP_CELL_SIZE}\n            fill=\"currentColor\"\n            opacity={cell.opacity}\n          />\n        ))}\n      </g>\n    </svg>\n  );\n}\n\nexport function GitHubContributions({\n  owner,\n  repo,\n  staticContributions,\n  staticData,\n  className,\n  ...props\n}: GitHubContributionsProps) {\n  const [data, setData] = useState<number[][]>(staticData || []);\n  const [contributors, setContributors] = useState<number | null>(\n    staticContributions ?? null,\n  );\n  const [loading, setLoading] = useState(!staticData);\n  const [error, setError] = useState<string | null>(null);\n\n  useEffect(() => {\n    if (staticData && staticContributions !== undefined) return;\n\n    async function fetchData() {\n      try {\n        setLoading(true);\n        setError(null);\n\n        const activityResponse = await fetch(\n          `https://api.github.com/repos/${owner}/${repo}/stats/commit_activity`,\n        );\n\n        if (activityResponse.status === 202) {\n          setTimeout(() => fetchData(), 1000);\n          return;\n        }\n\n        if (!activityResponse.ok) {\n          throw new Error(\"Failed to fetch contribution data\");\n        }\n\n        const activityData = await activityResponse.json();\n\n        if (!activityData || !Array.isArray(activityData)) {\n          setData(\n            Array(7)\n              .fill(null)\n              .map(() => Array(12).fill(0.1)),\n          );\n        } else {\n          const weeks = activityData.slice(-12);\n          const allCounts = weeks.flatMap((w: { days: number[] }) => w.days);\n          const maxCount = Math.max(...allCounts, 1);\n\n          const grid: number[][] = [];\n          for (let day = 0; day < 7; day++) {\n            const row: number[] = [];\n            for (const week of weeks) {\n              const count = week.days[day] || 0;\n              row.push(count / maxCount);\n            }\n            grid.push(row);\n          }\n          setData(grid);\n        }\n\n        const contributorsResponse = await fetch(\n          `https://api.github.com/repos/${owner}/${repo}/contributors?per_page=1`,\n        );\n\n        if (contributorsResponse.ok) {\n          const linkHeader = contributorsResponse.headers.get(\"link\");\n          if (linkHeader) {\n            const match = linkHeader.match(/page=(\\d+)>; rel=\"last\"/);\n            if (match) {\n              setContributors(parseInt(match[1], 10));\n            }\n          } else {\n            const contributorData = await contributorsResponse.json();\n            setContributors(contributorData.length);\n          }\n        }\n      } catch (err) {\n        setError(err instanceof Error ? err.message : \"Unknown error\");\n      } finally {\n        setLoading(false);\n      }\n    }\n\n    fetchData();\n  }, [owner, repo, staticContributions, staticData]);\n\n  if (loading) {\n    return (\n      <div\n        data-slot=\"github-contributions\"\n        className={cn(\n          \"flex flex-col gap-4 p-6 border border-border rounded-lg bg-card\",\n          className,\n        )}\n        {...props}\n      >\n        <div className=\"flex flex-col gap-1\">\n          <div className=\"h-10 w-16 bg-muted rounded animate-pulse\" />\n          <div className=\"h-4 w-44 bg-muted rounded animate-pulse\" />\n        </div>\n        <svg\n          viewBox={`0 0 ${HEATMAP_VIEWBOX_WIDTH} ${HEATMAP_VIEWBOX_HEIGHT}`}\n          preserveAspectRatio=\"xMidYMid meet\"\n          className=\"w-full h-auto text-muted\"\n          aria-hidden=\"true\"\n        >\n          <rect\n            width={HEATMAP_VIEWBOX_WIDTH}\n            height={HEATMAP_VIEWBOX_HEIGHT}\n            rx={4}\n            fill=\"currentColor\"\n            className=\"animate-pulse\"\n          />\n        </svg>\n      </div>\n    );\n  }\n\n  if (error) {\n    return (\n      <div\n        data-slot=\"github-contributions\"\n        className={cn(\n          \"flex flex-col gap-4 p-6 border border-destructive/50 rounded-lg bg-destructive/10\",\n          className,\n        )}\n        {...props}\n      >\n        <p className=\"text-sm text-destructive\">{error}</p>\n      </div>\n    );\n  }\n\n  return (\n    <div\n      data-slot=\"github-contributions\"\n      className={cn(\n        \"flex flex-col gap-4 p-6 border border-border rounded-lg bg-card\",\n        className,\n      )}\n      {...props}\n    >\n      <div className=\"flex flex-col gap-1\">\n        <span className=\"text-4xl font-bold tracking-tight\">\n          {contributors !== null ? contributors.toLocaleString() : \"—\"}\n        </span>\n        <span className=\"text-sm text-muted-foreground\">\n          Contributors to {owner}/{repo}\n        </span>\n        <span className=\"text-xs text-muted-foreground/60\">Last 12 weeks</span>\n      </div>\n      <HeatmapGrid\n        data={data}\n        className=\"text-neutral-500 dark:text-neutral-400\"\n      />\n    </div>\n  );\n}\n",
      "type": "registry:component"
    },
    {
      "path": "registry/default/blocks/github/github-contributions/routes/layout.tsx",
      "content": "import { Geist, Geist_Mono } from \"next/font/google\";\n\nconst geistSans = Geist({\n  variable: \"--font-geist-sans\",\n  subsets: [\"latin\"],\n});\n\nconst geistMono = Geist_Mono({\n  variable: \"--font-geist-mono\",\n  subsets: [\"latin\"],\n});\n\nexport default function RootLayout({\n  children,\n}: Readonly<{\n  children: React.ReactNode;\n}>) {\n  return (\n    <html lang=\"en\">\n      <body\n        className={`${geistSans.variable} ${geistMono.variable} antialiased`}\n      >\n        {children}\n      </body>\n    </html>\n  );\n}\n",
      "type": "registry:page",
      "target": "app/layout.tsx"
    },
    {
      "path": "registry/default/blocks/github/github-contributions/routes/page.tsx",
      "content": "import { GitHubContributions } from \"@/registry/default/blocks/github/github-contributions/components/elements/github-contributions\";\n\nexport default function GitHubContributionsPage() {\n  return (\n    <div className=\"min-h-screen bg-background text-foreground\">\n      <div className=\"container mx-auto px-4 py-16\">\n        <div className=\"max-w-4xl mx-auto space-y-12\">\n          <div className=\"text-center space-y-4\">\n            <h1 className=\"text-3xl font-bold\">GitHub Contributions</h1>\n            <p className=\"text-muted-foreground\">\n              Display contribution activity grid like GitHub&apos;s profile.\n            </p>\n          </div>\n\n          <div className=\"grid grid-cols-1 md:grid-cols-2 gap-8\">\n            <GitHubContributions owner=\"shadcn-ui\" repo=\"ui\" />\n            <GitHubContributions owner=\"vercel\" repo=\"next.js\" />\n          </div>\n\n          <div className=\"bg-card border rounded-lg p-6 text-left space-y-4\">\n            <h2 className=\"text-lg font-semibold\">Features</h2>\n            <ul className=\"space-y-2 text-sm text-muted-foreground\">\n              <li>Fetches real-time contribution data from GitHub API</li>\n              <li>Shows commit activity over last 12 weeks</li>\n              <li>Uses HeatmapGrid primitive for visualization</li>\n              <li>Loading and error states included</li>\n              <li>Handles GitHub API rate limiting gracefully</li>\n            </ul>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:page",
      "target": "app/github-contributions/page.tsx"
    }
  ],
  "categories": [
    "github"
  ],
  "type": "registry:block"
}