import { Tooltip as ChakraTooltip, Portal } from '@chakra-ui/react'; import { useClickAway } from '@uidotdev/usehooks'; import * as React from 'react'; import config from 'configs/app'; import useIsMobile from 'lib/hooks/useIsMobile'; export interface TooltipProps extends ChakraTooltip.RootProps { selected?: boolean; showArrow?: boolean; portalled?: boolean; portalRef?: React.RefObject; content: React.ReactNode; contentProps?: ChakraTooltip.ContentProps; triggerProps?: ChakraTooltip.TriggerProps; disabled?: boolean; disableOnMobile?: boolean; } export const Tooltip = React.forwardRef( function Tooltip(props, ref) { const { showArrow: showArrowProp, onOpenChange, variant, selected, children, disabled, disableOnMobile, portalled = true, content, contentProps, portalRef, defaultOpen = false, lazyMount = true, unmountOnExit = true, triggerProps, closeDelay = 100, openDelay = 100, interactive, ...rest } = props; const [ open, setOpen ] = React.useState(defaultOpen); const timeoutRef = React.useRef(null); const isMobile = useIsMobile(); const handleOpenChange = React.useCallback((details: { open: boolean }) => { setOpen(details.open); onOpenChange?.(details); }, [ onOpenChange ]); const handleOpenChangeManual = React.useCallback((nextOpen: boolean) => { timeoutRef.current && window.clearTimeout(timeoutRef.current); timeoutRef.current = window.setTimeout(() => { setOpen(nextOpen); onOpenChange?.({ open: nextOpen }); }, nextOpen ? openDelay : closeDelay); }, [ closeDelay, openDelay, onOpenChange ]); const handleClickAway = React.useCallback((event: Event) => { if (interactive) { const closest = (event.target as HTMLElement)?.closest('.chakra-tooltip__positioner'); if (closest) { return; } } handleOpenChangeManual(false); }, [ interactive, handleOpenChangeManual ]); const triggerRef = useClickAway(handleClickAway); const handleTriggerClick = React.useCallback(() => { handleOpenChangeManual(!open); }, [ handleOpenChangeManual, open ]); const handleContentClick = React.useCallback((event: React.MouseEvent) => { // otherwise, the event will be propagated to the trigger // and if the trigger is a link, navigation will be triggered event.stopPropagation(); if (interactive) { const closestLink = (event.target as HTMLElement)?.closest('a'); if (closestLink) { handleOpenChangeManual(false); } } }, [ interactive, handleOpenChangeManual ]); React.useEffect(() => { return () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } }; }, []); if (disabled || (disableOnMobile && isMobile)) return children; const defaultShowArrow = variant === 'popover' ? false : true; const showArrow = showArrowProp !== undefined ? showArrowProp : defaultShowArrow; const positioning = { ...rest.positioning, overflowPadding: 4, offset: { mainAxis: 4, ...rest.positioning?.offset, }, }; return ( { children } { showArrow && ( ) } { content } ); }, );