File size: 4,536 Bytes
98fcc8e cd201c7 98fcc8e cd201c7 66f319d cd201c7 98fcc8e cd201c7 98fcc8e cd201c7 98fcc8e fbae805 98fcc8e fbae805 98fcc8e cd201c7 66f319d cd201c7 98fcc8e cd201c7 98fcc8e |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
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<WeeklyHeatmapProps> = ({ 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<string, WeeklyActivity[]>);
}, [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 (
<div className="w-full">
<div className="flex flex-wrap gap-4 justify-center">
{sortedMonths.map((yearMonth) => {
const monthData = groupedData[yearMonth];
return (
<div key={yearMonth} className="flex flex-col items-center">
<div className="text-xs mb-2 text-gray-600 dark:text-gray-400">
{getMonthName(yearMonth)}
</div>
<div className="flex gap-1">
{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 (
<Tooltip
key={`${yearMonth}-${index}`}
title={tooltipTitle}
arrow
>
<div
className="w-3 h-3 rounded-sm cursor-pointer transition-opacity hover:opacity-80"
style={{
backgroundColor: activity.level === 0 ? emptyDotColor : getColorIntensity(activity.level),
}}
/>
</Tooltip>
);
})}
</div>
</div>
);
})}
</div>
{/* Legend */}
<div className="flex items-center justify-center mt-4 text-xs text-gray-600 dark:text-gray-400">
<span className="mr-2">Less</span>
<div className="flex gap-1">
{[0, 1, 2, 3, 4].map((level) => (
<div
key={level}
className="w-2.5 h-2.5 rounded-sm"
style={{
backgroundColor: level === 0 ? emptyDotColor : getColorIntensity(level),
}}
/>
))}
</div>
<span className="ml-2">More</span>
</div>
</div>
);
};
export default React.memo(WeeklyHeatmap); |