fix(day): keep the pinned heatmap tooltip off the right edge

The tooltip shrinks to the room right of its clamped left edge and
wraps, so it never ran past the screen, but near the right border it
filled that room to the last pixel. Cap its width at the room less the
8 px gutter the clamp was meant to leave.
This commit is contained in:
Marc-André Bombeck committed 2026-10-08 22:57:47 +02:00
1 parent b4e3e22d43
commit facfcad107
2 files changed
+45 -6

No files matched your search

@@ -0,0 +1,27 @@
/**
* The pinned heatmap tooltip on a 390 px phone. It shrinks to the room right
* of its left edge and wraps, so it never runs past the screen; measured in
* Chromium, a tap at the right border left it flush against the edge. The
* box keeps the 8 px gutter the clamp meant to leave.
*/
import { describe, expect, it } from "vitest";
import { tooltipBox } from "../heatmap-day";
describe("tooltipBox", () => {
it("keeps an 8 px gutter at the right edge for a tap near the border", () => {
const { left, maxWidth } = tooltipBox(380, 390);
expect(left).toBe(182);
expect(left + maxWidth).toBe(390 - 8);
});
it("sits beside the pointer away from the border", () => {
const { left, maxWidth } = tooltipBox(40, 390);
expect(left).toBe(50);
expect(left + maxWidth).toBe(382);
});
it("never starts left of the 8 px gutter", () => {
expect(tooltipBox(-20, 390).left).toBe(8);
});
});
+18 -6
View File
@@ -52,6 +52,21 @@ export interface HeatmapCellInput {
describe: () => string;
}
/**
* Where the heatmap tooltip sits beside the pointer: 10 px to its right,
* clamped so a tap near the right border keeps at least 200 px of room, and
* never wider than the room to the right edge less an 8 px gutter. The box
* shrinks to fit that room and wraps its text, so it cannot run past the
* edge; the width cap keeps the gutter the clamp meant to leave.
*/
export function tooltipBox(
x: number,
viewportWidth: number,
): { left: number; maxWidth: number } {
const left = Math.max(8, Math.min(x + 10, viewportWidth - 200 - 8));
return { left, maxWidth: viewportWidth - left - 8 };
}
export function useHeatmapDay(containerRef: RefObject<HTMLElement | null>) {
const [tooltip, setTooltip] = useState<HeatmapTooltip | null>(null);
// The pointer type of the last press decides whether the click that
@@ -139,13 +154,10 @@ export function useHeatmapDay(containerRef: RefObject<HTMLElement | null>) {
className={`bg-popover text-popover-foreground border-border fixed z-50 max-w-[calc(100vw-1rem)] rounded-md border px-2.5 py-1.5 text-xs shadow-md ${
tooltip.day ? "pointer-events-auto" : "pointer-events-none"
}`}
// Clamp the left edge so a tap near the right border doesn't push
// the pinned label off-screen on a narrow viewport.
style={{
left:
typeof window !== "undefined"
? Math.max(8, Math.min(tooltip.x + 10, window.innerWidth - 200 - 8))
: tooltip.x + 10,
...(typeof window !== "undefined"
? tooltipBox(tooltip.x, window.innerWidth)
: { left: tooltip.x + 10 }),
top: tooltip.y - 30,
}}
>