| | import React, { useEffect, useState } from "react"; |
| | import ActivityCalendar from "react-activity-calendar"; |
| | import { Tooltip, Avatar } from "@mui/material"; |
| | import Link from "next/link"; |
| | import { aggregateToWeeklyData } from "../utils/weeklyCalendar"; |
| | import WeeklyHeatmap from "./WeeklyHeatmap"; |
| | import { getHeatmapTheme, getHeatmapColorIntensity } from "../utils/heatmapColors"; |
| |
|
| | type ViewMode = 'daily' | 'weekly'; |
| |
|
| | type HeatmapProps = { |
| | data: Array<{ date: string; count: number; level: number }>; |
| | color: string; |
| | providerName: string; |
| | fullName: string; |
| | avatarUrl: string; |
| | authorId: string; |
| | showHeader?: boolean; |
| | viewMode: ViewMode; |
| | }; |
| |
|
| | const Heatmap: React.FC<HeatmapProps> = ({ |
| | data, |
| | color, |
| | providerName, |
| | fullName, |
| | avatarUrl, |
| | authorId, |
| | showHeader = true, |
| | viewMode |
| | }) => { |
| | |
| | const processedData = viewMode === 'weekly' ? aggregateToWeeklyData(data) : data; |
| | |
| | |
| | const [isDarkMode, setIsDarkMode] = useState(false); |
| | |
| | useEffect(() => { |
| | |
| | const checkTheme = () => { |
| | setIsDarkMode(document.documentElement.classList.contains('dark')); |
| | }; |
| | |
| | checkTheme(); |
| | |
| | |
| | const observer = new MutationObserver(checkTheme); |
| | observer.observe(document.documentElement, { |
| | attributes: true, |
| | attributeFilter: ['class'] |
| | }); |
| | |
| | return () => observer.disconnect(); |
| | }, []); |
| | |
| | |
| | const emptyColor = isDarkMode ? "#374151" : "#d1d5db"; |
| | |
| | |
| | const lightIntensityColors = [ |
| | emptyColor, |
| | getHeatmapColorIntensity(1, color, false), |
| | getHeatmapColorIntensity(2, color, false), |
| | getHeatmapColorIntensity(3, color, false), |
| | getHeatmapColorIntensity(4, color, false) |
| | ].filter((color): color is string => color !== null); |
| | |
| | const darkIntensityColors = [ |
| | emptyColor, |
| | getHeatmapColorIntensity(1, color, true), |
| | getHeatmapColorIntensity(2, color, true), |
| | getHeatmapColorIntensity(3, color, true), |
| | getHeatmapColorIntensity(4, color, true) |
| | ].filter((color): color is string => color !== null); |
| | |
| | return ( |
| | <div className="flex flex-col items-center w-full mx-auto"> |
| | {showHeader && ( |
| | <div className="flex flex-col sm:flex-row items-center mb-4 w-full justify-center"> |
| | {avatarUrl && ( |
| | <Avatar src={avatarUrl} alt={fullName} className="mb-2 sm:mb-0 sm:mr-4" sx={{ width: 48, height: 48 }} /> |
| | )} |
| | <div className="text-center sm:text-left"> |
| | <h2 className="text-lg font-semibold"> |
| | <Link |
| | href={`https://huggingface.co/${authorId}`} |
| | target="_blank" |
| | rel="noopener noreferrer" |
| | className="hover:text-blue-500 hover:underline" |
| | > |
| | {fullName} |
| | </Link> |
| | </h2> |
| | </div> |
| | </div> |
| | )} |
| | <div className="w-full overflow-x-auto flex justify-center"> |
| | {viewMode === 'weekly' ? ( |
| | <WeeklyHeatmap data={processedData} color={color} /> |
| | ) : ( |
| | <ActivityCalendar |
| | data={processedData} |
| | theme={{ |
| | light: lightIntensityColors, |
| | dark: darkIntensityColors |
| | }} |
| | blockSize={11} |
| | blockMargin={2} |
| | hideTotalCount |
| | renderBlock={(block, activity) => ( |
| | <Tooltip |
| | title={`${activity.count} new repos on ${activity.date}`} |
| | arrow |
| | > |
| | {block} |
| | </Tooltip> |
| | )} |
| | /> |
| | )} |
| | </div> |
| | </div> |
| | ); |
| | }; |
| |
|
| | export default Heatmap; |
| |
|