import React, { useMemo } from "react"; import CalendarHeatmap from "react-calendar-heatmap"; import { cn } from "~/lib/utils"; import { Popover, PopoverAnchor, PopoverContent } from "../ui/popover"; interface ContributionGraphProps { data: Array<{ date: string; count: number; status?: string; }>; className?: string; } export function ContributionGraph({ data, className }: ContributionGraphProps) { const [open, setOpen] = React.useState(false); const [anchor, setAnchor] = React.useState<{ x: number; y: number } | null>( null, ); const [active, setActive] = React.useState(null); const containerRef = React.useRef(null); const processedData = useMemo(() => { const endDate = new Date(); const startDate = new Date(); startDate.setFullYear(endDate.getFullYear() - 1); return data.map((item) => ({ date: item.date, count: item.count, status: item.status, })); }, [data]); const getClassForValue = (value: any) => { if (!value || value.count === 0) { return "fill-background dark:fill-background"; } const count = value.count; if (count >= 20) return "fill-success"; if (count >= 15) return "fill-success/85"; if (count >= 10) return "fill-success/70"; if (count >= 5) return "fill-success/50"; return "fill-success/30"; }; const getTitleForValue = (value: any) => { if (!value || value.count === 0) { return `No activity on ${value?.date || "this date"}`; } const count = value.count; const date = new Date(value.date).toLocaleDateString(); return `${count} ${count === 1 ? "activity" : "activities"} on ${date}`; }; const endDate = new Date(); const startDate = new Date(); startDate.setFullYear(endDate.getFullYear() - 1); // Position helpers: convert client coords to container-local coords const getLocalPoint = (e: React.MouseEvent) => { const rect = containerRef.current?.getBoundingClientRect(); if (!rect) return { x: e.clientX, y: e.clientY }; return { x: e.clientX, y: e.clientY }; }; return (
{anchor && ( )} {active ? (
{new Date(active.date).toDateString()}
{active.count ?? 0} events
{active.meta?.notes && (

{active.meta.notes}

)}
) : (
No data
)}
{ // React clones the . We add handlers to open the shared popover. return React.cloneElement(element, { onClick: (e: React.MouseEvent) => { setActive(value); setAnchor(getLocalPoint(e)); setOpen(true); }, onMouseEnter: (e: React.MouseEvent) => { // If you want hover popovers, uncomment: setActive(value); setAnchor(getLocalPoint(e)); setOpen(true); }, onMouseLeave: () => { // For hover behavior, you might want a small delay instead of closing immediately. setOpen(false); }, style: { cursor: "pointer" }, }); }} />
); }