fix(web): portal timeline hover tooltips

This commit is contained in:
2026-03-30 13:19:43 +02:00
parent e20bf64eef
commit 5a345cd2e4
@@ -1,5 +1,6 @@
"use client"; "use client";
import { createPortal } from "react-dom";
import { formatCents, formatDateLong } from "~/lib/format.js"; import { formatCents, formatDateLong } from "~/lib/format.js";
function getVacationTitle(vacation: VacationHoverData): string { function getVacationTitle(vacation: VacationHoverData): string {
@@ -78,9 +79,11 @@ export function TimelineTooltip({
demandHover, demandHover,
}: TimelineTooltipProps) { }: TimelineTooltipProps) {
const vacationTitle = vacationHover ? getVacationTitle(vacationHover) : null; const vacationTitle = vacationHover ? getVacationTitle(vacationHover) : null;
const renderTooltip = (content: React.ReactNode) =>
typeof document === "undefined" ? content : createPortal(content, document.body);
if (demandHover && demandTooltipRef && demandTooltipPos) { if (demandHover && demandTooltipRef && demandTooltipPos) {
return ( return renderTooltip(
<div <div
ref={demandTooltipRef} ref={demandTooltipRef}
style={{ style={{
@@ -150,13 +153,13 @@ export function TimelineTooltip({
</div> </div>
) : null} ) : null}
</div> </div>
</div> </div>,
); );
} }
// When both are active, render a single merged tooltip using the heatmap position // When both are active, render a single merged tooltip using the heatmap position
if (heatmapHover && vacationHover) { if (heatmapHover && vacationHover) {
return ( return renderTooltip(
<div <div
ref={(el) => { ref={(el) => {
// Wire both refs to the same element so position updates work from either handler // Wire both refs to the same element so position updates work from either handler
@@ -230,13 +233,13 @@ export function TimelineTooltip({
<div className="mt-1 text-[11px] text-amber-200/60">{vacationHover.note}</div> <div className="mt-1 text-[11px] text-amber-200/60">{vacationHover.note}</div>
) : null} ) : null}
</div> </div>
</div> </div>,
); );
} }
// Heatmap only // Heatmap only
if (heatmapHover) { if (heatmapHover) {
return ( return renderTooltip(
<div <div
ref={heatmapTooltipRef} ref={heatmapTooltipRef}
style={{ style={{
@@ -289,13 +292,13 @@ export function TimelineTooltip({
<div className="text-[11px] text-gray-400">No bookings on this day.</div> <div className="text-[11px] text-gray-400">No bookings on this day.</div>
)} )}
</div> </div>
</div> </div>,
); );
} }
// Vacation only // Vacation only
if (vacationHover) { if (vacationHover) {
return ( return renderTooltip(
<div <div
ref={vacationTooltipRef} ref={vacationTooltipRef}
style={{ style={{
@@ -312,7 +315,7 @@ export function TimelineTooltip({
{vacationHover.note && vacationHover.type !== "PUBLIC_HOLIDAY" ? ( {vacationHover.note && vacationHover.type !== "PUBLIC_HOLIDAY" ? (
<div className="mt-2 text-[11px] text-amber-100/80">{vacationHover.note}</div> <div className="mt-2 text-[11px] text-amber-100/80">{vacationHover.note}</div>
) : null} ) : null}
</div> </div>,
); );
} }