{
  "name": "github-calendar",
  "type": "registry:ui",
  "dependencies": [
    "framer-motion"
  ],
  "registryDependencies": [],
  "description": "A premium, customizable visualization of GitHub contribution graphs with variants and scaling support.",
  "files": [
    {
      "path": "components/ui/github-calendar.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { motion, AnimatePresence } from \"framer-motion\"\nimport { cn } from \"@/lib/utils\"\n\ninterface ContributionDay {\n    color: string\n    contributionCount: number\n    contributionLevel: \"NONE\" | \"FIRST_QUARTILE\" | \"SECOND_QUARTILE\" | \"THIRD_QUARTILE\" | \"FOURTH_QUARTILE\"\n    date: string\n}\n\ninterface GithubContributionData {\n    contributions: ContributionDay[][]\n    totalContributions: number\n}\n\ninterface GithubCalendarProps {\n    username: string\n    variant?: \"default\" | \"city-lights\" | \"minimal\"\n    shape?: \"square\" | \"rounded\" | \"circle\" | \"squircle\"\n    glowIntensity?: number\n    className?: string\n    showTotal?: boolean\n    colorSchema?: \"green\" | \"blue\" | \"purple\" | \"orange\" | \"gray\"\n}\n\n// Color schemas for custom styling\nconst colorSchemas = {\n    gray: {\n        level0: \"bg-zinc-100 dark:bg-zinc-900\",\n        level1: \"bg-zinc-300 dark:bg-zinc-800\",\n        level2: \"bg-zinc-400 dark:bg-zinc-700\",\n        level3: \"bg-zinc-600 dark:bg-zinc-500\",\n        level4: \"bg-zinc-800 dark:bg-zinc-300\",\n    },\n    green: {\n        level0: \"bg-zinc-100 dark:bg-zinc-900\",\n        level1: \"bg-emerald-200 dark:bg-emerald-900\",\n        level2: \"bg-emerald-300 dark:bg-emerald-700\",\n        level3: \"bg-emerald-400 dark:bg-emerald-500\",\n        level4: \"bg-emerald-500 dark:bg-emerald-400\",\n    },\n    blue: {\n        level0: \"bg-zinc-100 dark:bg-zinc-900\",\n        level1: \"bg-blue-200 dark:bg-blue-900\",\n        level2: \"bg-blue-300 dark:bg-blue-700\",\n        level3: \"bg-blue-400 dark:bg-blue-500\",\n        level4: \"bg-blue-500 dark:bg-blue-400\",\n    },\n    purple: {\n        level0: \"bg-zinc-100 dark:bg-zinc-900\",\n        level1: \"bg-purple-200 dark:bg-purple-900\",\n        level2: \"bg-purple-300 dark:bg-purple-700\",\n        level3: \"bg-purple-400 dark:bg-purple-500\",\n        level4: \"bg-purple-500 dark:bg-purple-400\",\n    },\n    orange: {\n        level0: \"bg-zinc-100 dark:bg-zinc-900\",\n        level1: \"bg-orange-200 dark:bg-orange-900\",\n        level2: \"bg-orange-300 dark:bg-orange-700\",\n        level3: \"bg-orange-400 dark:bg-orange-500\",\n        level4: \"bg-orange-500 dark:bg-orange-400\",\n    },\n}\n\nfunction getLevelClass(level: string, schema: keyof typeof colorSchemas = \"green\") {\n    const s = colorSchemas[schema]\n    switch (level) {\n        case \"FIRST_QUARTILE\":\n            return s.level1\n        case \"SECOND_QUARTILE\":\n            return s.level2\n        case \"THIRD_QUARTILE\":\n            return s.level3\n        case \"FOURTH_QUARTILE\":\n            return s.level4\n        case \"NONE\":\n        default:\n            return s.level0\n    }\n}\n\nfunction getShapeClass(shape: string) {\n    switch (shape) {\n        case \"circle\":\n            return \"rounded-full\"\n        case \"square\":\n            return \"rounded-none\"\n        case \"squircle\":\n            return \"rounded-sm\" // Approximation\n        case \"rounded\":\n        default:\n            return \"rounded-[2px]\"\n    }\n}\n\nexport function GithubCalendar({\n    username,\n    variant = \"default\",\n    shape = \"rounded\",\n    glowIntensity = 5,\n    className,\n    showTotal = true,\n    colorSchema = \"green\",\n}: GithubCalendarProps) {\n    const [data, setData] = React.useState<GithubContributionData | null>(null)\n    const [loading, setLoading] = React.useState(true)\n    const [error, setError] = React.useState<string | null>(null)\n    const [hoveredDate, setHoveredDate] = React.useState<string | null>(null)\n    const [hoveredCount, setHoveredCount] = React.useState<number | null>(null)\n    const [mousePos, setMousePos] = React.useState({ x: 0, y: 0 })\n\n    React.useEffect(() => {\n        const fetchData = async () => {\n            try {\n                setLoading(true)\n                const response = await fetch(\n                    `https://github-contributions-api.deno.dev/${username}.json`\n                )\n                if (!response.ok) {\n                    throw new Error(\"Failed to fetch GitHub data\")\n                }\n                const jsonData = await response.json()\n                setData(jsonData)\n            } catch (err) {\n                setError(err instanceof Error ? err.message : \"An error occurred\")\n            } finally {\n                setLoading(false)\n            }\n        }\n\n        if (username) {\n            fetchData()\n        }\n    }, [username])\n\n    if (error) {\n        return (\n            <div className={cn(\"p-4 rounded-lg border border-red-200 bg-red-50 text-red-500 text-sm\", className)}>\n                Error: {error}\n            </div>\n        )\n    }\n\n    if (loading) {\n        return (\n            <div className={cn(\"w-full h-32 animate-pulse bg-zinc-100 dark:bg-zinc-800 rounded-xl\", className)} />\n        )\n    }\n\n    const weeks = data?.contributions || []\n\n    return (\n        <div className={cn(\"w-max max-w-full flex flex-col gap-4\", className)}>\n            {showTotal && (\n                <div className=\"flex items-center justify-between\">\n                    <div className=\"flex items-center gap-2\">\n                        <svg height=\"16\" aria-hidden=\"true\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" data-view-component=\"true\" className=\"fill-current text-muted-foreground\">\n                            <path d=\"M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z\"></path>\n                        </svg>\n                        <span className=\"font-semibold text-sm\">@{username}</span>\n                    </div>\n                    <span className=\"text-sm text-muted-foreground\">\n                        {data?.totalContributions} contributions in the last year\n                    </span>\n                </div>\n            )}\n\n            <div\n                className=\"relative flex flex-nowrap gap-[3px] w-max max-w-full\"\n                onMouseLeave={() => {\n                    setHoveredDate(null)\n                    setHoveredCount(null)\n                }}\n            >\n                {/* Simple Tooltip */}\n                <AnimatePresence>\n                    {hoveredDate && (\n                        <motion.div\n                            initial={{ opacity: 0, y: 10, scale: 0.9 }}\n                            animate={{ opacity: 1, y: 0, scale: 1 }}\n                            exit={{ opacity: 0, y: 5, scale: 0.9 }}\n                            transition={{ duration: 0.2 }}\n                            className=\"absolute z-50 pointer-events-none px-3 py-1.5 bg-zinc-900 text-white dark:bg-white dark:text-zinc-900 text-xs rounded-md shadow-xl whitespace-nowrap\"\n                            style={{\n                                left: mousePos.x,\n                                top: mousePos.y - 40,\n                                transform: \"translateX(-50%)\"\n                            }}\n                        >\n                            <span className=\"font-bold mr-1\">{hoveredCount}</span>\n                            <span className=\"text-zinc-400 dark:text-zinc-500\">contributions on {hoveredDate}</span>\n                        </motion.div>\n                    )}\n                </AnimatePresence>\n\n                {weeks.map((week, weekIndex) => (\n                    <div key={weekIndex} className=\"flex flex-col gap-[3px] w-[14px]\">\n                        {week.map((day, dayIndex) => {\n                            const isGlowing = variant === \"city-lights\" && day.contributionCount > 0;\n                            const isMinimal = variant === \"minimal\";\n                            const shapeClass = getShapeClass(shape);\n\n                            return (\n                                <motion.div\n                                    key={day.date}\n                                    initial={{ opacity: 0, scale: 0 }}\n                                    animate={{ opacity: 1, scale: 1 }}\n                                    transition={{\n                                        delay: weekIndex * 0.01 + dayIndex * 0.01,\n                                        type: \"spring\",\n                                        stiffness: 260,\n                                        damping: 20\n                                    }}\n                                    onMouseEnter={(e) => {\n                                        setHoveredDate(day.date)\n                                        setHoveredCount(day.contributionCount)\n                                        const rect = e.currentTarget.getBoundingClientRect()\n                                        const parentRect = e.currentTarget.offsetParent!.getBoundingClientRect()\n                                        setMousePos({\n                                            x: rect.left - parentRect.left + rect.width / 2,\n                                            y: rect.top - parentRect.top\n                                        })\n                                    }}\n                                    className={cn(\n                                        \"w-full aspect-square transition-colors duration-200\",\n                                        getLevelClass(day.contributionLevel, colorSchema),\n                                        isGlowing && \"z-10\",\n                                        shapeClass,\n                                        isMinimal && \"rounded-full scale-75\",\n                                    )}\n                                    style={\n                                        isGlowing ? {\n                                            boxShadow: day.contributionLevel !== \"NONE\"\n                                                ? `0 0 ${day.contributionCount > 3 ? `${glowIntensity * 1.5}px` : `${glowIntensity}px`} ${colorSchema === 'green' ? '#10b981' :\n                                                    colorSchema === 'blue' ? '#3b82f6' :\n                                                        colorSchema === 'purple' ? '#a855f7' :\n                                                            '#f97316'\n                                                }`\n                                                : 'none'\n                                        } : undefined\n                                    }\n                                />\n                            )\n                        })}\n                    </div>\n                ))}\n            </div>\n        </div>\n    )\n}\n",
      "type": "registry:ui"
    }
  ]
}