import { OverlayContainer } from '@react-native-aria/overlays'; import React from 'react'; import { useControllableState, useKeyboardDismissable } from '../../../hooks'; import { Popper } from '../Popper'; import { composeEventHandlers, mergeRefs } from '../../../utils'; import { PresenceTransition } from '../Transitions'; import { Platform, StyleSheet } from 'react-native'; import { usePropsResolution } from '../../../hooks'; import Box from '../../primitives/Box'; import type { ITooltipProps } from './types'; import { useId } from '@react-native-aria/utils'; import { useHasResponsiveProps } from '../../../hooks/useHasResponsiveProps'; import uniqueId from 'lodash.uniqueid'; import { ResponsiveQueryContext } from '../../../utils/useResponsiveQuery/ResponsiveQueryProvider'; export const Tooltip = ({ label, children, onClose, onOpen, defaultIsOpen, placement, openDelay = 0, closeDelay = 0, closeOnClick = true, offset, isDisabled, hasArrow, arrowSize = 12, isOpen: isOpenProp, ...props }: ITooltipProps) => { if (hasArrow && offset === undefined) { offset = 0; } else { offset = 6; } const resolvedProps = usePropsResolution('Tooltip', props); const [isOpen, setIsOpen] = useControllableState({ value: isOpenProp, defaultValue: defaultIsOpen, onChange: (value) => { value ? onOpen && onOpen() : onClose && onClose(); }, }); const arrowBg = props.backgroundColor ?? props.bgColor ?? props.bg ?? resolvedProps.bg; const targetRef = React.useRef(null); const enterTimeout = React.useRef(); const exitTimeout = React.useRef(); // const tooltipID = ''; // const tooltipID = useId(); let tooltipID = uniqueId(); // let id = uniqueId(); const responsiveQueryContext = React.useContext(ResponsiveQueryContext); const disableCSSMediaQueries = responsiveQueryContext.disableCSSMediaQueries; if (!disableCSSMediaQueries) { // This if statement technically breaks the rules of hooks, but is safe // because the condition never changes after mounting. // eslint-disable-next-line react-hooks/rules-of-hooks tooltipID = useId(); } const openWithDelay = React.useCallback(() => { if (!isDisabled) { enterTimeout.current = setTimeout(() => setIsOpen(true), openDelay); } }, [isDisabled, setIsOpen, openDelay]); const closeWithDelay = React.useCallback(() => { if (enterTimeout.current) { clearTimeout(enterTimeout.current); } exitTimeout.current = setTimeout(() => setIsOpen(false), closeDelay); }, [closeDelay, setIsOpen]); React.useEffect( () => () => { clearTimeout(enterTimeout.current); clearTimeout(exitTimeout.current); }, [] ); let newChildren = children; if (typeof children === 'string') { newChildren = {children}; } newChildren = React.cloneElement(newChildren, { 'onPress': composeEventHandlers(newChildren.props.onPress, () => { if (closeOnClick) { closeWithDelay(); } }), 'onFocus': composeEventHandlers( newChildren.props.onFocus, openWithDelay ), 'onBlur': composeEventHandlers( newChildren.props.onBlur, closeWithDelay ), 'onMouseEnter': composeEventHandlers( newChildren.props.onMouseEnter, openWithDelay ), 'onMouseLeave': composeEventHandlers( newChildren.props.onMouseLeave, closeWithDelay ), 'ref': mergeRefs([newChildren.ref, targetRef]), 'aria-describedby': isOpen ? tooltipID : undefined, }); useKeyboardDismissable({ enabled: isOpen, callback: () => setIsOpen(false), }); //TODO: refactor for responsive prop if (useHasResponsiveProps(props)) { return null; } return ( <> {newChildren} {isOpen && ( setIsOpen(false)} placement={placement} offset={offset} > {hasArrow && ( )} {label} )} ); };