diff --git a/.changeset/tiny-bananas-beam.md b/.changeset/tiny-bananas-beam.md new file mode 100644 index 0000000000..0cb528ad53 --- /dev/null +++ b/.changeset/tiny-bananas-beam.md @@ -0,0 +1,5 @@ +--- +'@commercetools-uikit/tooltip': patch +--- + +Prevents image clipping when Tooltip component is hovered. diff --git a/packages/components/tooltip/src/tooltip.tsx b/packages/components/tooltip/src/tooltip.tsx index 8494da7d89..ad7b8addb9 100644 --- a/packages/components/tooltip/src/tooltip.tsx +++ b/packages/components/tooltip/src/tooltip.tsx @@ -106,6 +106,12 @@ export type TTooltipProps = { * Provides a way to fine-tune an appearance of underlying Popper tooltip element. For more information, please check [Popper.js documentation](https://popper.js.org/popper-documentation.html#modifiers). */ modifiers?: Modifiers; + /** + * Use CSS `position: fixed` for the popper element instead of `position: absolute`. + * Recommended when the tooltip body is rendered in a portal (e.g. via `TooltipWrapperComponent`) + * to prevent clipping caused by scrolled ancestors. + */ + positionFixed?: boolean; /** * Customize the appearance of certain elements of the tooltip. */ @@ -185,6 +191,7 @@ const Tooltip = ({ const { reference, popper, popperInstance } = usePopper({ placement: placement, modifiers: props.modifiers, + positionFixed: props.positionFixed, }); const [state, setState] = useState('closed'); @@ -373,6 +380,21 @@ const Tooltip = ({ }; }, [state, off, isControlled, popperInstance, handleLeave]); + // Recompute position when the tooltip body resizes (e.g. an image that loads + // after the initial position was calculated from a 0-height container). + useEffect(() => { + if (!tooltipIsOpen) return; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const inst = popperInstance as any; + const popperEl = inst?.popper as HTMLElement | null; + if (!popperEl) return; + const ro = new ResizeObserver(() => { + inst.scheduleUpdate?.(); + }); + ro.observe(popperEl); + return () => ro.disconnect(); + }, [tooltipIsOpen, popperInstance]); + const childrenProps = { // don't pass event listeners to children onFocus: null, diff --git a/test/setup-tests.js b/test/setup-tests.js index 16b80cb794..08256d0c75 100644 --- a/test/setup-tests.js +++ b/test/setup-tests.js @@ -29,6 +29,11 @@ Object.defineProperty(window, 'TextDecoder', { writable: true, value: TextDecoder, }); +global.ResizeObserver = class ResizeObserver { + observe() {} + unobserve() {} + disconnect() {} +}; const silenceConsoleWarnings = []; const notThrowWarnings = [