) => {
@@ -123,39 +202,29 @@ export function ContributionGraph({
const col = Math.floor(svgPt.x / CELL_STEP);
const row = Math.floor(svgPt.y / CELL_STEP);
- // Check the point is within a cell, not in the gap
- const cellX = svgPt.x - col * CELL_STEP;
- const cellY = svgPt.y - row * CELL_STEP;
- if (cellX > CELL_SIZE || cellY > CELL_SIZE || cellX < 0 || cellY < 0) {
- setTooltip(null);
- return;
- }
+ mouseX.set(e.clientX);
+ mouseY.set(e.clientY + 8);
- const week = calendar.weeks[col];
- if (!week) {
- setTooltip(null);
- return;
- }
-
- const day = week.days.find((d) => new Date(d.date).getUTCDay() === row);
- if (!day) {
- setTooltip(null);
- return;
- }
-
- const cell = cellsByDate.get(day.date);
+ const cell = cellsByPosition.get(`${col}:${row}`);
if (!cell) {
- setTooltip(null);
+ if (activeCellRef.current !== null) {
+ activeCellRef.current = null;
+ setActiveCell(null);
+ }
return;
}
- setTooltip({ cell, pageX: e.clientX, pageY: e.clientY });
+ if (activeCellRef.current?.date !== cell.date) {
+ activeCellRef.current = cell;
+ setActiveCell(cell);
+ }
},
- [calendar.weeks, cellsByDate],
+ [cellsByPosition, mouseX, mouseY],
);
const handleMouseLeave = useCallback(() => {
- setTooltip(null);
+ activeCellRef.current = null;
+ setActiveCell(null);
}, []);
return (
@@ -175,76 +244,72 @@ export function ContributionGraph({
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
>
- {cells.map((cell) => {
- const col = cell.x / CELL_STEP;
- const row = cell.y / CELL_STEP;
- return (
+ {cellElements}
+
+ {activeCell && (
- );
- })}
+ )}
+
- {tooltip &&
- createPortal(
-
-
- {tooltip.cell.count} contribution
- {tooltip.cell.count !== 1 ? "s" : ""}
- {" "}
- on {formatDate(tooltip.cell.date)}
-
,
- document.body,
- )}
-
-
+ {createPortal(
+
+ {activeCell && dateParts && (
+
+
+ {" "}
+ contribution{activeCell.count !== 1 ? "s" : ""}
+ {" "}
+ on{" "}
+
+
+ {dateParts.month}
+
+ {" "}
+
+ , {dateParts.year}
+
+ )}
+ ,
+ document.body,
+ )}
+
+
);
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 71a6f3b..11500b3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -38,6 +38,9 @@ importers:
'@diffkit/ui':
specifier: workspace:*
version: link:../../packages/ui
+ '@number-flow/react':
+ specifier: ^0.6.0
+ version: 0.6.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@pierre/diffs':
specifier: ^1.1.12
version: 1.1.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -1480,6 +1483,12 @@ packages:
resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==}
engines: {node: '>= 20.19.0'}
+ '@number-flow/react@0.6.0':
+ resolution: {integrity: sha512-77Yfc9+zkV2UDSP8phhZzxJGuwxi/Tt1TikmipL+1r3e9GFKEYDZ1XwInj67NoSt3OnOB0KLvvcl3lfPZgBHVQ==}
+ peerDependencies:
+ react: ^18 || ^19
+ react-dom: ^18 || ^19
+
'@octokit/app@16.1.2':
resolution: {integrity: sha512-8j7sEpUYVj18dxvh0KWj6W/l6uAiVRBl1JBDVRqH1VHKAO/G5eRVl4yEoYACjakWers1DjUkcCHyJNQK47JqyQ==}
engines: {node: '>= 20'}
@@ -3555,6 +3564,9 @@ packages:
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
engines: {node: '>=12'}
+ esm-env@1.2.2:
+ resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==}
+
esprima@4.0.1:
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
engines: {node: '>=4'}
@@ -4136,6 +4148,9 @@ packages:
nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
+ number-flow@0.6.0:
+ resolution: {integrity: sha512-K8flNq2Wqus53vjp/btVo3qXFkagF8dIdYavreBfE7hlvFFG/b1HMGEH6nZL+mlrJ+4lbLP9OmPv3t2rmRkpSQ==}
+
nuqs@2.8.9:
resolution: {integrity: sha512-8ou6AEwsxMWSYo2qkfZtYFVzngwbKmg4c00HVxC1fF6CEJv3Fwm6eoZmfVPALB+vw8Udo7KL5uy96PFcYe1BIQ==}
peerDependencies:
@@ -5748,6 +5763,13 @@ snapshots:
'@noble/hashes@2.0.1': {}
+ '@number-flow/react@0.6.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ esm-env: 1.2.2
+ number-flow: 0.6.0
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+
'@octokit/app@16.1.2':
dependencies:
'@octokit/auth-app': 8.2.0
@@ -7871,6 +7893,8 @@ snapshots:
escape-string-regexp@5.0.0: {}
+ esm-env@1.2.2: {}
+
esprima@4.0.1: {}
estree-util-is-identifier-name@3.0.0: {}
@@ -8669,6 +8693,10 @@ snapshots:
dependencies:
boolbase: 1.0.0
+ number-flow@0.6.0:
+ dependencies:
+ esm-env: 1.2.2
+
nuqs@2.8.9(@tanstack/react-router@1.168.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4):
dependencies:
'@standard-schema/spec': 1.0.0