From 11d4b86c9399e7bfb4294f48ce2b9baf00b6172b Mon Sep 17 00:00:00 2001 From: devendra-w Date: Wed, 20 May 2026 13:28:04 +0530 Subject: [PATCH 1/6] feat: add heatmap drill-down sheet on cell click --- src/components/ContributionHeatmap.tsx | 7 ++ src/components/DailyBreakdownSheet.tsx | 135 +++++++++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 src/components/DailyBreakdownSheet.tsx diff --git a/src/components/ContributionHeatmap.tsx b/src/components/ContributionHeatmap.tsx index 8c81db44..0a957eaf 100644 --- a/src/components/ContributionHeatmap.tsx +++ b/src/components/ContributionHeatmap.tsx @@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from "react"; import { useHeatmapTheme } from "@/hooks/useHeatmapTheme"; +import DailyBreakdownSheet from "@/components/DailyBreakdownSheet"; interface ContributionHeatmapProps { days?: number; @@ -77,6 +78,7 @@ export default function ContributionHeatmap({ const [error, setError] = useState(null); const [lastUpdated, setLastUpdated] = useState(null); const [minutesAgo, setMinutesAgo] = useState(0); + const [selectedDate, setSelectedDate] = useState(null); useEffect(() => { let active = true; @@ -286,6 +288,7 @@ export default function ContributionHeatmap({ title={isFuture ? "" : tooltip} aria-label={isFuture ? `${cell.dateKey}: future date` : tooltip} disabled={isFuture} + onClick={() => !isFuture && setSelectedDate(cell.dateKey)} className={`group relative z-0 h-3 w-3 rounded-[3px] border transition-transform hover:z-20 hover:scale-110 focus:z-20 focus:outline-none focus:ring-2 focus:ring-[var(--heatmap-focus-ring)] disabled:cursor-default disabled:opacity-30 ${ cell.inRange ? "" : "opacity-35" }`} @@ -333,6 +336,10 @@ export default function ContributionHeatmap({ )} + setSelectedDate(null)} + /> ); } \ No newline at end of file diff --git a/src/components/DailyBreakdownSheet.tsx b/src/components/DailyBreakdownSheet.tsx new file mode 100644 index 00000000..9201c48e --- /dev/null +++ b/src/components/DailyBreakdownSheet.tsx @@ -0,0 +1,135 @@ +"use client"; + +import { useEffect, useState } from "react"; + +interface DailyBreakdownSheetProps { + date: string | null; + onClose: () => void; +} + +interface RepoCommit { + repo: string; + count: number; + url: string; +} + +export default function DailyBreakdownSheet({ + date, + onClose, +}: DailyBreakdownSheetProps) { + const [commits, setCommits] = useState([]); + const [loading, setLoading] = useState(false); + const isOpen = date !== null; + + useEffect(() => { + if (!date) return; + setLoading(true); + fetch(`/api/metrics/contributions?days=365`) + .then((res) => res.json()) + .then((result) => { + // Extract per-repo breakdown for the selected date + const repoData = result.repos?.[date] ?? []; + setCommits(repoData); + }) + .catch(() => setCommits([])) + .finally(() => setLoading(false)); + }, [date]); + + // Close on Escape key + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose(); + }; + document.addEventListener("keydown", handleKeyDown); + return () => document.removeEventListener("keydown", handleKeyDown); + }, [onClose]); + + if (!isOpen) return null; + + const formattedDate = date + ? new Date(date + "T00:00:00").toLocaleDateString("en-US", { + weekday: "long", + month: "long", + day: "numeric", + year: "numeric", + }) + : ""; + + return ( + <> + {/* Backdrop */} +