import { Fragment, ReactNode, useEffect, useReducer, useRef } from 'react'; import Box from '../Box'; import Layer from '../Layer'; import Controller from '../Popover/Controller'; import Text from '../Text'; import useDebouncedCallback from '../useDebouncedCallback'; import useExperimentalTheme from '../utils/useExperimentalTheme'; import { Indexable } from '../zIndex'; const noop = () => {}; const TIMEOUT = 100; const initialState = { hoveredIcon: false, hoveredText: false, isOpen: false } as const; const reducer = ( state: { hoveredIcon: boolean; hoveredText: boolean; isOpen: boolean; }, action: { type: 'hoverInIcon' | 'hoverInText' | 'hoverOutIcon' | 'hoverOutText'; disabled?: boolean; }, ) => { if (action.disabled) return { ...state, isOpen: false, hoveredIcon: false, hoveredText: false }; switch (action.type) { case 'hoverInIcon': return { ...state, hoveredIcon: true, isOpen: true, }; case 'hoverInText': return { ...state, hoveredText: true, isOpen: true, }; case 'hoverOutIcon': return { ...state, hoveredIcon: false, isOpen: !state.hoveredText ? false : state.isOpen, }; case 'hoverOutText': return { ...state, hoveredText: false, isOpen: !state.hoveredIcon ? false : state.isOpen, }; default: throw new Error(); } }; type Props = { accessibilityLabel?: string; children?: ReactNode; dataTestId?: string; /** * Whether to show the tooltip or not */ disabled?: boolean; idealDirection?: 'up' | 'right' | 'down' | 'left'; inline?: boolean; link?: ReactNode; text: string | ReadonlyArray; zIndex?: Indexable; }; export default function InternalTooltip({ accessibilityLabel, children, dataTestId, disabled, link, idealDirection, inline, text, zIndex, }: Props) { const [state, dispatch] = useReducer(reducer, initialState); const { isOpen } = state; const childRef = useRef(null); const { current: anchor } = childRef; const mouseLeaveDelay = link ? TIMEOUT : 0; const theme = useExperimentalTheme(); useEffect(() => { if (disabled === true) { dispatch({ type: 'hoverOutIcon', disabled }); } }, [disabled]); const handleIconMouseEnter = () => { dispatch({ type: 'hoverInIcon', disabled }); }; const handleIconMouseLeave = useDebouncedCallback(() => { dispatch({ type: 'hoverOutIcon', disabled }); }, mouseLeaveDelay); const handleTextMouseEnter = () => { dispatch({ type: 'hoverInText', disabled }); }; const handleTextMouseLeave = useDebouncedCallback(() => { dispatch({ type: 'hoverOutText', disabled }); }, mouseLeaveDelay); const getTooltipText = () => { if (Array.isArray(text)) { // first and last line should not have a

tag, (adds extra padding) const lines = text.map((line, idx) => { if (typeof line !== 'string') return ''; return text.length === 1 || idx === text.length - 1 ? ( line ) : ( {line}

); }); return lines; } return text; }; const accessibilityLabelFallback = typeof text === 'string' ? text : text.join(''); const dataTestIdText = dataTestId && `${dataTestId}-text`; return ( {children} {isOpen && !!anchor && ( {getTooltipText()} {Boolean(link) && {link}} )} ); } InternalTooltip.displayName = 'InternalTooltip';