import React, { useMemo, useCallback, useEffect, useState } from "react"; import { Tooltip } from "@mui/material"; import { getWeekDateRange } from "../utils/weeklyCalendar"; import { getHeatmapColorIntensity } from "../utils/heatmapColors"; type WeeklyActivity = { date: string; count: number; level: number; items?: string[]; }; type WeeklyHeatmapProps = { data: WeeklyActivity[]; color: string; }; const WeeklyHeatmap: React.FC = ({ data, color }) => { // Track theme changes const [isDark, setIsDark] = useState(false); useEffect(() => { const checkTheme = () => { setIsDark(document.documentElement.classList.contains('dark')); }; // Initial check checkTheme(); // Watch for theme changes const observer = new MutationObserver(checkTheme); observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); return () => observer.disconnect(); }, []); // Memoize the grouped data to avoid recalculation const groupedData = useMemo(() => { return data.reduce((acc, activity) => { const date = new Date(activity.date); const yearMonth = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; if (!acc[yearMonth]) { acc[yearMonth] = []; } acc[yearMonth].push(activity); return acc; }, {} as Record); }, [data]); // Memoize sorted months const sortedMonths = useMemo(() => Object.keys(groupedData).sort(), [groupedData]); // Memoize the color intensity function with theme awareness const getColorIntensity = useCallback((level: number) => { return getHeatmapColorIntensity(level, color, isDark) || undefined; }, [color, isDark]); // Get the exact same empty dot color as ActivityCalendar const emptyDotColor = useMemo(() => { return isDark ? '#374151' : '#d1d5db'; }, [isDark]); // Get month names const getMonthName = (yearMonth: string) => { const [year, month] = yearMonth.split('-'); const date = new Date(parseInt(year), parseInt(month) - 1); return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); }; return (
{sortedMonths.map((yearMonth) => { const monthData = groupedData[yearMonth]; return (
{getMonthName(yearMonth)}
{monthData.map((activity, index) => { const itemsText = activity.items && activity.items.length > 0 ? activity.items.join(', ') : 'No releases'; const tooltipTitle = activity.count > 0 ? `${activity.count} new repos in week of ${getWeekDateRange(activity.date)}: ${itemsText}` : `No repos in week of ${getWeekDateRange(activity.date)}`; return (
); })}
); })}
{/* Legend */}
Less
{[0, 1, 2, 3, 4].map((level) => (
))}
More
); }; export default React.memo(WeeklyHeatmap);