evijit HF Staff commited on
Commit
3857bd9
·
verified ·
1 Parent(s): 98fcc8e

fix colors (#5)

Browse files

- Fix custom colors in heatmaps and consistent empty dot theming (372711a02f76d8d2262947cba80b4161c1fa59ff)

src/components/Heatmap.tsx CHANGED
@@ -1,10 +1,10 @@
1
- import React, { useMemo, useCallback } from "react";
2
  import ActivityCalendar from "react-activity-calendar";
3
  import { Tooltip, Avatar } from "@mui/material";
4
  import Link from "next/link";
5
  import { aggregateToWeeklyData } from "../utils/weeklyCalendar";
6
- import { getHeatmapTheme } from "../utils/heatmapColors";
7
  import WeeklyHeatmap from "./WeeklyHeatmap";
 
8
 
9
  type ViewMode = 'daily' | 'weekly';
10
 
@@ -29,24 +29,32 @@ const Heatmap: React.FC<HeatmapProps> = ({
29
  showHeader = true,
30
  viewMode
31
  }) => {
32
- // Memoize the weekly data processing to avoid recalculation on every render
33
- const weeklyData = useMemo(() => aggregateToWeeklyData(data), [data]);
34
 
35
- // Choose data based on view mode
36
- const processedData = viewMode === 'weekly' ? weeklyData : data;
37
 
38
- // Memoize the theme to prevent recreation
39
- const theme = useMemo(() => getHeatmapTheme(color), [color]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
- // Memoize the render block callback
42
- const renderBlock = useCallback((block: React.ReactElement, activity: any) => (
43
- <Tooltip
44
- title={`${activity.count} new repos on ${activity.date}`}
45
- arrow
46
- >
47
- {block}
48
- </Tooltip>
49
- ), []);
50
 
51
  return (
52
  <div className="flex flex-col items-center w-full mx-auto">
@@ -75,11 +83,21 @@ const Heatmap: React.FC<HeatmapProps> = ({
75
  ) : (
76
  <ActivityCalendar
77
  data={processedData}
78
- theme={theme}
 
 
 
79
  blockSize={11}
80
  blockMargin={2}
81
  hideTotalCount
82
- renderBlock={renderBlock}
 
 
 
 
 
 
 
83
  />
84
  )}
85
  </div>
@@ -87,4 +105,4 @@ const Heatmap: React.FC<HeatmapProps> = ({
87
  );
88
  };
89
 
90
- export default React.memo(Heatmap);
 
1
+ import React, { useEffect, useState } from "react";
2
  import ActivityCalendar from "react-activity-calendar";
3
  import { Tooltip, Avatar } from "@mui/material";
4
  import Link from "next/link";
5
  import { aggregateToWeeklyData } from "../utils/weeklyCalendar";
 
6
  import WeeklyHeatmap from "./WeeklyHeatmap";
7
+ import { getHeatmapTheme } from "../utils/heatmapColors";
8
 
9
  type ViewMode = 'daily' | 'weekly';
10
 
 
29
  showHeader = true,
30
  viewMode
31
  }) => {
32
+ // Process data based on view mode
33
+ const processedData = viewMode === 'weekly' ? aggregateToWeeklyData(data) : data;
34
 
35
+ // Track theme state for proper ActivityCalendar theming
36
+ const [isDarkMode, setIsDarkMode] = useState(false);
37
 
38
+ useEffect(() => {
39
+ // Check initial theme
40
+ const checkTheme = () => {
41
+ setIsDarkMode(document.documentElement.classList.contains('dark'));
42
+ };
43
+
44
+ checkTheme();
45
+
46
+ // Watch for theme changes
47
+ const observer = new MutationObserver(checkTheme);
48
+ observer.observe(document.documentElement, {
49
+ attributes: true,
50
+ attributeFilter: ['class']
51
+ });
52
+
53
+ return () => observer.disconnect();
54
+ }, []);
55
 
56
+ // Use theme-aware colors
57
+ const emptyColor = isDarkMode ? "#374151" : "#d1d5db";
 
 
 
 
 
 
 
58
 
59
  return (
60
  <div className="flex flex-col items-center w-full mx-auto">
 
83
  ) : (
84
  <ActivityCalendar
85
  data={processedData}
86
+ theme={{
87
+ light: [emptyColor, color],
88
+ dark: [emptyColor, color]
89
+ }}
90
  blockSize={11}
91
  blockMargin={2}
92
  hideTotalCount
93
+ renderBlock={(block, activity) => (
94
+ <Tooltip
95
+ title={`${activity.count} new repos on ${activity.date}`}
96
+ arrow
97
+ >
98
+ {block}
99
+ </Tooltip>
100
+ )}
101
  />
102
  )}
103
  </div>
 
105
  );
106
  };
107
 
108
+ export default Heatmap;
src/styles/globals.css CHANGED
@@ -88,3 +88,64 @@
88
  display: none; /* Safari and Chrome */
89
  }
90
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  display: none; /* Safari and Chrome */
89
  }
90
  }
91
+
92
+ /* Activity Calendar theme overrides for consistent empty dot colors */
93
+ /* Try multiple possible selectors for react-activity-calendar */
94
+ .react-activity-calendar rect[data-level="0"] {
95
+ fill: #d1d5db !important; /* Light theme empty dots */
96
+ }
97
+
98
+ .dark .react-activity-calendar rect[data-level="0"] {
99
+ fill: #374151 !important; /* Dark theme empty dots */
100
+ }
101
+
102
+ /* Alternative selectors in case the structure is different */
103
+ .react-activity-calendar .react-activity-calendar__block[data-level="0"] {
104
+ fill: #d1d5db !important;
105
+ }
106
+
107
+ .dark .react-activity-calendar .react-activity-calendar__block[data-level="0"] {
108
+ fill: #374151 !important;
109
+ }
110
+
111
+ /* More general fallback selectors */
112
+ .react-activity-calendar [data-level="0"] {
113
+ fill: #d1d5db !important;
114
+ }
115
+
116
+ .dark .react-activity-calendar [data-level="0"] {
117
+ fill: #374151 !important;
118
+ }
119
+
120
+ /* SVG rect elements specifically */
121
+ .react-activity-calendar svg rect[data-level="0"] {
122
+ fill: #d1d5db !important;
123
+ }
124
+
125
+ .dark .react-activity-calendar svg rect[data-level="0"] {
126
+ fill: #374151 !important;
127
+ }
128
+
129
+ /* Override any background-color styles as well */
130
+ .react-activity-calendar [data-level="0"] {
131
+ fill: #d1d5db !important;
132
+ background-color: #d1d5db !important;
133
+ }
134
+
135
+ .dark .react-activity-calendar [data-level="0"] {
136
+ fill: #374151 !important;
137
+ background-color: #374151 !important;
138
+ }
139
+
140
+ /* Even more specific selectors with higher specificity */
141
+ html.dark .react-activity-calendar svg rect[data-level="0"],
142
+ html.dark .react-activity-calendar [data-level="0"] {
143
+ fill: #374151 !important;
144
+ background-color: #374151 !important;
145
+ }
146
+
147
+ html:not(.dark) .react-activity-calendar svg rect[data-level="0"],
148
+ html:not(.dark) .react-activity-calendar [data-level="0"] {
149
+ fill: #d1d5db !important;
150
+ background-color: #d1d5db !important;
151
+ }
src/utils/heatmapColors.ts CHANGED
@@ -14,9 +14,37 @@ export const getHeatmapColorIntensity = (level: number, primaryColor: string) =>
14
  return null; // Will use CSS classes for theme-aware empty state
15
  }
16
 
17
- // Use different intensities of the primary color or default green scale
18
- const greenIntensities = ['#0e4429', '#006d32', '#26a641', '#39d353'];
19
- return greenIntensities[Math.min(level - 1, 3)] || primaryColor;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  };
21
 
22
  export const getEmptyDotColors = () => ({
 
14
  return null; // Will use CSS classes for theme-aware empty state
15
  }
16
 
17
+ // Create different intensities of the primary color
18
+ const hexToRgb = (hex: string) => {
19
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
20
+ return result ? {
21
+ r: parseInt(result[1], 16),
22
+ g: parseInt(result[2], 16),
23
+ b: parseInt(result[3], 16)
24
+ } : null;
25
+ };
26
+
27
+ const rgbToHex = (r: number, g: number, b: number) => {
28
+ return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
29
+ };
30
+
31
+ const rgb = hexToRgb(primaryColor);
32
+ if (!rgb) {
33
+ // Fallback to green scale if color parsing fails
34
+ const greenIntensities = ['#0e4429', '#006d32', '#26a641', '#39d353'];
35
+ return greenIntensities[Math.min(level - 1, 3)];
36
+ }
37
+
38
+ // Create intensity levels by adjusting brightness
39
+ // Level 1: 40% intensity, Level 2: 60%, Level 3: 80%, Level 4: 100%
40
+ const intensityMultipliers = [0.4, 0.6, 0.8, 1.0];
41
+ const multiplier = intensityMultipliers[Math.min(level - 1, 3)];
42
+
43
+ const adjustedR = Math.round(rgb.r * multiplier);
44
+ const adjustedG = Math.round(rgb.g * multiplier);
45
+ const adjustedB = Math.round(rgb.b * multiplier);
46
+
47
+ return rgbToHex(adjustedR, adjustedG, adjustedB);
48
  };
49
 
50
  export const getEmptyDotColors = () => ({