diff --git a/web/src/components/RelativeModal.jsx b/web/src/components/RelativeModal.jsx index 644a0b875..2398f643f 100644 --- a/web/src/components/RelativeModal.jsx +++ b/web/src/components/RelativeModal.jsx @@ -2,7 +2,7 @@ import { h, Fragment } from 'preact'; import { createPortal } from 'preact/compat'; import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks'; -const WINDOW_PADDING = 10; +const WINDOW_PADDING = 20; export default function RelativeModal({ className, @@ -13,7 +13,7 @@ export default function RelativeModal({ relativeTo, widthRelative = false, }) { - const [position, setPosition] = useState({ top: -999, left: -999 }); + const [position, setPosition] = useState({ top: -9999, left: -9999 }); const [show, setShow] = useState(false); const portalRoot = portalRootID && document.getElementById(portalRootID); const ref = useRef(null); @@ -53,33 +53,43 @@ export default function RelativeModal({ const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; const { width: menuWidth, height: menuHeight } = ref.current.getBoundingClientRect(); - const { x, y, width: relativeWidth, height } = relativeTo.current.getBoundingClientRect(); + const { + x: relativeToX, + y: relativeToY, + width: relativeToWidth, + // height: relativeToHeight, + } = relativeTo.current.getBoundingClientRect(); - const width = widthRelative ? relativeWidth : menuWidth; + const _width = widthRelative ? relativeToWidth : menuWidth; + const width = _width * 1.1; + + const left = relativeToX + window.scrollX; + const top = relativeToY + window.scrollY; + + let newTop = top; + let newLeft = left; - let top = y + height; - let left = x; // too far right - if (left + width >= windowWidth - WINDOW_PADDING) { - left = windowWidth - width - WINDOW_PADDING; + if (newLeft + width + WINDOW_PADDING >= windowWidth - WINDOW_PADDING) { + newLeft = windowWidth - width - WINDOW_PADDING; } // too far left else if (left < WINDOW_PADDING) { - left = WINDOW_PADDING; + newLeft = WINDOW_PADDING; } // too close to bottom - if (top + menuHeight > windowHeight - WINDOW_PADDING) { - top = y - menuHeight; + if (top + menuHeight > windowHeight - WINDOW_PADDING + window.scrollY) { + newTop = relativeToY - menuHeight; } - if (top <= WINDOW_PADDING) { - top = WINDOW_PADDING; + if (top <= WINDOW_PADDING + window.scrollY) { + newTop = WINDOW_PADDING; } const maxHeight = windowHeight - WINDOW_PADDING * 2 > menuHeight ? null : windowHeight - WINDOW_PADDING * 2; - const newPosition = { left: left + window.scrollX, top: top + window.scrollY, maxHeight }; + const newPosition = { left: newLeft, top: newTop, maxHeight }; if (widthRelative) { - newPosition.width = relativeWidth; + newPosition.width = relativeToWidth; } setPosition(newPosition); const focusable = ref.current.querySelector('[tabindex]'); @@ -89,7 +99,9 @@ export default function RelativeModal({ useEffect(() => { if (position.top >= 0) { - setShow(true); + window.requestAnimationFrame(() => { + setShow(true); + }); } else { setShow(false); } @@ -100,13 +112,13 @@ export default function RelativeModal({
= 0 ? position : null} + style={position} > {children}
diff --git a/web/src/components/Tooltip.jsx b/web/src/components/Tooltip.jsx index b0faadaef..034623af2 100644 --- a/web/src/components/Tooltip.jsx +++ b/web/src/components/Tooltip.jsx @@ -1,15 +1,15 @@ import { h } from 'preact'; import { createPortal } from 'preact/compat'; -import { useEffect, useRef, useState } from 'preact/hooks'; +import { useLayoutEffect, useRef, useState } from 'preact/hooks'; const TIP_SPACE = 20; export default function Tooltip({ relativeTo, text }) { - const [position, setPosition] = useState({ top: -Infinity, left: -Infinity }); + const [position, setPosition] = useState({ top: -9999, left: -9999 }); const portalRoot = document.getElementById('tooltips'); const ref = useRef(); - useEffect(() => { + useLayoutEffect(() => { if (ref && ref.current && relativeTo && relativeTo.current) { const windowWidth = window.innerWidth; const { @@ -18,7 +18,9 @@ export default function Tooltip({ relativeTo, text }) { width: relativeToWidth, height: relativeToHeight, } = relativeTo.current.getBoundingClientRect(); - const { width: tipWidth, height: tipHeight } = ref.current.getBoundingClientRect(); + const { width: _tipWidth, height: _tipHeight } = ref.current.getBoundingClientRect(); + const tipWidth = _tipWidth * 1.1; + const tipHeight = _tipHeight * 1.1; const left = relativeToX + Math.round(relativeToWidth / 2) + window.scrollX; const top = relativeToY + Math.round(relativeToHeight / 2) + window.scrollY; @@ -47,11 +49,11 @@ export default function Tooltip({ relativeTo, text }) { const tooltip = (
= 0 ? 'opacity-100' : '' + className={`shadow max-w-lg absolute pointer-events-none bg-gray-900 dark:bg-gray-200 bg-opacity-80 rounded px-2 py-1 transition-transform transition-opacity duration-75 transform scale-90 opacity-0 text-gray-100 dark:text-gray-900 text-sm ${ + position.top >= 0 ? 'opacity-100 scale-100' : '' }`} ref={ref} - style={position.top >= 0 ? position : null} + style={position} > {text}
diff --git a/web/src/components/__tests__/Toolltip.test.jsx b/web/src/components/__tests__/Toolltip.test.jsx index d2d8e0c63..d538f6865 100644 --- a/web/src/components/__tests__/Toolltip.test.jsx +++ b/web/src/components/__tests__/Toolltip.test.jsx @@ -26,8 +26,8 @@ describe('Tooltip', () => { const tooltip = await screen.findByRole('tooltip'); const style = window.getComputedStyle(tooltip); - expect(style.left).toEqual('105px'); - expect(style.top).toEqual('70px'); + expect(style.left).toEqual('103px'); + expect(style.top).toEqual('68.5px'); }); test('if too far right, renders to the left', async () => { @@ -54,7 +54,7 @@ describe('Tooltip', () => { const tooltip = await screen.findByRole('tooltip'); const style = window.getComputedStyle(tooltip); - expect(style.left).toEqual('942px'); + expect(style.left).toEqual('937px'); expect(style.top).toEqual('97px'); }); @@ -109,7 +109,7 @@ describe('Tooltip', () => { const tooltip = await screen.findByRole('tooltip'); const style = window.getComputedStyle(tooltip); - expect(style.left).toEqual('87px'); - expect(style.top).toEqual('160px'); + expect(style.left).toEqual('84px'); + expect(style.top).toEqual('158.5px'); }); });