{ "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(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState(null)\n const [hoveredDate, setHoveredDate] = React.useState(null)\n const [hoveredCount, setHoveredCount] = React.useState(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
\n Error: {error}\n
\n )\n }\n\n if (loading) {\n return (\n
\n )\n }\n\n const weeks = data?.contributions || []\n\n return (\n
\n {showTotal && (\n
\n
\n \n \n \n @{username}\n
\n \n {data?.totalContributions} contributions in the last year\n \n
\n )}\n\n {\n setHoveredDate(null)\n setHoveredCount(null)\n }}\n >\n {/* Simple Tooltip */}\n \n {hoveredDate && (\n \n {hoveredCount}\n contributions on {hoveredDate}\n \n )}\n \n\n {weeks.map((week, weekIndex) => (\n
\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 {\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
\n ))}\n
\n
\n )\n}\n", "type": "registry:ui" } ] }