Skip to content

Commit

Permalink
fix: enable tooltip children ref
Browse files Browse the repository at this point in the history
refs: CDS-153 (#186)
  • Loading branch information
CataldoMazzilli authored May 23, 2023
1 parent 1c11e5d commit b3e9b06
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 15 deletions.
13 changes: 13 additions & 0 deletions src/components/display/Tooltip.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,17 @@ describe('Tooltip', () => {
});
expect(screen.queryByText(messageText)).not.toBeInTheDocument();
});

test('Ref for children is set through the prop triggerRef', () => {
const childRef = React.createRef<HTMLElement>();
const triggerRef = React.createRef<HTMLElement>();
render(
<Tooltip label={'tooltip label'} triggerRef={triggerRef}>
<span ref={childRef}>Trigger tooltip</span>
</Tooltip>
);
expect(childRef.current).toBeNull();
expect(triggerRef.current).not.toBeNull();
expect(screen.getByText('Trigger tooltip')).toBe(triggerRef.current);
});
});
34 changes: 19 additions & 15 deletions src/components/display/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import React, {
useLayoutEffect,
useCallback,
useRef,
cloneElement
cloneElement,
createRef
} from 'react';

import { createPopper, Instance, Placement } from '@popperjs/core';
Expand Down Expand Up @@ -79,6 +80,8 @@ interface TooltipProps extends TextProps {
children: React.ReactElement;
/** time before tooltip shows, in milliseconds */
triggerDelay?: number;
/** trigger ref that can be used instead of lost children ref caused by cloneElement */
triggerRef?: React.Ref<HTMLElement>;
}

const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(function TooltipFn(
Expand All @@ -92,18 +95,19 @@ const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(function TooltipF
disablePortal = false,
overflowTooltip = false,
triggerDelay = 500,
triggerRef = createRef<HTMLElement>(),
...rest
},
ref
) {
const [open, setOpen] = useState(false);
const popperInstanceRef = useRef<Instance>();
const triggerRef = useRef<HTMLElement>();
const combinedTriggerRef = useCombinedRefs<HTMLElement>(triggerRef);
const tooltipRef = useCombinedRefs<HTMLDivElement>(ref);
const timeoutRef = useRef<null | ReturnType<typeof setTimeout>>(null);

const showTooltip = useCallback(() => {
const triggerElement = triggerRef.current;
const triggerElement = combinedTriggerRef.current;
if (triggerElement) {
const textIsCropped =
(triggerElement.className.slice(0, 4) === 'Text' &&
Expand All @@ -116,16 +120,16 @@ const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(function TooltipF
}, triggerDelay);
}
}
}, [overflowTooltip, triggerRef, triggerDelay]);
}, [overflowTooltip, combinedTriggerRef, triggerDelay]);

const hideTooltip = useCallback(() => {
setOpen(false);
timeoutRef.current && clearTimeout(timeoutRef.current);
}, []);

useLayoutEffect(() => {
if (open && !disabled && triggerRef.current && tooltipRef.current) {
popperInstanceRef.current = createPopper(triggerRef.current, tooltipRef.current, {
if (open && !disabled && combinedTriggerRef.current && tooltipRef.current) {
popperInstanceRef.current = createPopper(combinedTriggerRef.current, tooltipRef.current, {
placement,
modifiers: [
{
Expand All @@ -145,19 +149,19 @@ const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(function TooltipF
} else if (popperInstanceRef.current) {
popperInstanceRef.current.destroy();
}
}, [disabled, fallbackPlacements, open, placement, tooltipRef]);
}, [disabled, fallbackPlacements, open, placement, tooltipRef, combinedTriggerRef]);

useEffect(() => {
// Added timeout to fix Preact weird bug
const timeout = setTimeout(() => {
if (triggerRef.current && !disabled) {
triggerRef.current.addEventListener('focus', showTooltip);
triggerRef.current.addEventListener('blur', hideTooltip);
triggerRef.current.addEventListener('mouseenter', showTooltip);
triggerRef.current.addEventListener('mouseleave', hideTooltip);
if (combinedTriggerRef.current && !disabled) {
combinedTriggerRef.current.addEventListener('focus', showTooltip);
combinedTriggerRef.current.addEventListener('blur', hideTooltip);
combinedTriggerRef.current.addEventListener('mouseenter', showTooltip);
combinedTriggerRef.current.addEventListener('mouseleave', hideTooltip);
}
}, 1);
const refSave = triggerRef.current;
const refSave = combinedTriggerRef.current;
return (): void => {
if (refSave) {
refSave.removeEventListener('focus', showTooltip);
Expand All @@ -167,7 +171,7 @@ const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(function TooltipF
}
clearTimeout(timeout);
};
}, [triggerRef, showTooltip, hideTooltip, disabled]);
}, [combinedTriggerRef, showTooltip, hideTooltip, disabled]);

useEffect(
() => (): void => {
Expand All @@ -180,7 +184,7 @@ const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(function TooltipF

return (
<>
{cloneElement(children, { ref: triggerRef })}
{cloneElement(children, { ref: combinedTriggerRef })}
<Portal show={open && !disabled} disablePortal={disablePortal}>
<TooltipWrapperWithCss ref={tooltipRef} open={open} $maxWidth={maxWidth} {...rest}>
{label}
Expand Down

0 comments on commit b3e9b06

Please sign in to comment.