fix: edge tooltips were hidden inside the box (#5)

* fix: edge tooltips were hidden inside the box

* Update README.md
This commit is contained in:
2026-04-12 20:51:56 +02:00
committed by GitHub
parent 80ba688f41
commit 26cc1dac8a
3 changed files with 121 additions and 32 deletions

View File

@@ -368,4 +368,4 @@ npm run check:pages # pages (astro check + tsc)
- [ ] Refine the status page to look... well... less IA generated.
- [ ] Initial support for incident management (manual status overrides, incident timeline).
- [x] Branded status page (simple custom banner).
- [] Add support for notifications other than webhooks.
- [ ] Add support for notifications other than webhooks.

View File

@@ -61,6 +61,8 @@ function getBarColor(uptimePercent: number | undefined): string {
gap: 1px;
width: 100%;
height: 14px;
position: relative;
overflow: visible;
}
.bar {
@@ -93,13 +95,17 @@ function getBarColor(uptimePercent: number | undefined): string {
background: var(--no-data);
}
/* Tooltip */
/* Tooltip - positioned below to avoid conflict with bar labels */
.bar {
position: relative;
}
.bar::before {
content: attr(data-tooltip);
position: absolute;
bottom: 100%;
top: calc(100% + 8px);
left: 50%;
transform: translateX(-50%) translateY(-8px);
transform: translateX(-50%);
background: var(--text);
color: var(--bg);
padding: 4px 8px;
@@ -112,16 +118,56 @@ function getBarColor(uptimePercent: number | undefined): string {
transition: opacity 0.2s ease;
pointer-events: none;
z-index: 1000;
/* Prevent tooltip overflow */
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
/* Ensure tooltip stays within screen bounds */
left: 50%;
transform: translateX(-50%);
/* Constrain to viewport */
max-width: min(200px, calc(100vw - 32px));
}
/* For left-edge bars, prevent left overflow */
.bar:nth-child(1)::before,
.bar:nth-child(2)::before,
.bar:nth-child(3)::before,
.bar:nth-child(4)::before,
.bar:nth-child(5)::before,
.bar:nth-child(6)::before,
.bar:nth-child(7)::before,
.bar:nth-child(8)::before,
.bar:nth-child(9)::before,
.bar:nth-child(10)::before {
left: 0;
transform: translateX(0);
}
/* For right-edge bars, prevent right overflow */
.bar:nth-last-child(1)::before,
.bar:nth-last-child(2)::before,
.bar:nth-last-child(3)::before,
.bar:nth-last-child(4)::before,
.bar:nth-last-child(5)::before,
.bar:nth-last-child(6)::before,
.bar:nth-last-child(7)::before,
.bar:nth-last-child(8)::before,
.bar:nth-last-child(9)::before,
.bar:nth-last-child(10)::before {
left: auto;
right: 0;
transform: translateX(0);
}
.bar::after {
content: '';
position: absolute;
bottom: 100%;
top: calc(100% + 2px);
left: 50%;
transform: translateX(-50%) translateY(-2px);
transform: translateX(-50%);
border: 4px solid transparent;
border-top-color: var(--text);
border-bottom-color: var(--text);
opacity: 0;
visibility: hidden;
transition: opacity 0.2s ease;
@@ -129,6 +175,21 @@ function getBarColor(uptimePercent: number | undefined): string {
z-index: 1000;
}
/* Adjust arrow position for edge bars */
.bar:nth-child(1)::after,
.bar:nth-child(2)::after,
.bar:nth-child(3)::after,
.bar:nth-child(4)::after,
.bar:nth-child(5)::after,
.bar:nth-child(6)::after,
.bar:nth-child(7)::after,
.bar:nth-child(8)::after,
.bar:nth-child(9)::after,
.bar:nth-child(10)::after {
left: 8px;
transform: translateX(0);
}
.bar:hover::before,
.bar:focus::before,
.bar:hover::after,
@@ -136,12 +197,4 @@ function getBarColor(uptimePercent: number | undefined): string {
opacity: 1;
visibility: visible;
}
.bar-labels {
display: flex;
justify-content: space-between;
font-size: 9px;
color: var(--text-dim);
margin-top: 3px;
}
</style>

View File

@@ -79,18 +79,18 @@ function tooltipPlugin(strokeColor: string): uPlot.Plugin {
tooltipElement = document.createElement('div');
tooltipElement.className = 'chart-tooltip';
tooltipElement.style.cssText = `
position: absolute;
pointer-events: none;
background: rgba(15, 23, 42, 0.95);
border: 1px solid ${strokeColor};
color: #e2e8f0;
padding: 4px 8px;
border-radius: 4px;
font: 500 10px 'Geist Mono', monospace;
display: none;
white-space: nowrap;
z-index: 10;
`;
position: absolute;
pointer-events: none;
background: rgba(15, 23, 42, 0.95);
border: 1px solid ${strokeColor};
color: #e2e8f0;
padding: 4px 8px;
border-radius: 4px;
font: 500 10px 'Geist Mono', monospace;
display: none;
white-space: nowrap;
z-index: 10;
`;
// Cast needed: @cloudflare/workers-types overrides DOM append() signature
(over as ParentNode).append(tooltipElement);
@@ -131,18 +131,54 @@ function tooltipPlugin(strokeColor: string): uPlot.Plugin {
const msString = Math.round(yValue) + ' ms';
tooltipElement.textContent = `${timeString} ${msString}`;
// Position tooltip, flipping side if near right edge
// Position tooltip, ensuring it stays within chart bounds
const tipWidth = tooltipElement.offsetWidth;
const tipHeight = tooltipElement.offsetHeight;
const plotWidth = over.clientWidth;
const plotHeight = over.clientHeight;
const shiftX = 12;
const shiftY = -10;
let posLeft = left + shiftX;
if (posLeft + tipWidth > plotWidth) {
// Calculate horizontal position - choose side with more space
let posLeft: number;
const spaceRight = plotWidth - (left + shiftX);
const spaceLeft = left - shiftX;
// Default to right side if there's enough space
if (spaceRight >= tipWidth) {
posLeft = left + shiftX;
}
// Try left side if there's more space there
else if (spaceLeft >= tipWidth) {
posLeft = left - tipWidth - shiftX;
}
// Not enough space on either side, choose the better option
else {
// If right side has more space than left, use right side clamped
if (spaceRight > spaceLeft) {
posLeft = plotWidth - tipWidth;
} else {
// Use left side clamped
posLeft = 0;
}
}
// Ensure we don't go outside bounds
posLeft = Math.max(0, Math.min(posLeft, plotWidth - tipWidth));
// Calculate vertical position
let posTop = (top ?? 0) + shiftY;
// Check if tooltip would overflow top edge
if (posTop < 0) {
posTop = 0;
}
// Check if tooltip would overflow bottom edge
else if (posTop + tipHeight > plotHeight) {
posTop = plotHeight - tipHeight;
}
tooltipElement.style.left = posLeft + 'px';
tooltipElement.style.top = (top ?? 0) + shiftY + 'px';
tooltipElement.style.top = posTop + 'px';
},
],
},