import React, { useEffect, useMemo, useRef, useState } from 'react' import TooltipContent, { TooltipIconOffset } from './tooltip-content' import useClickAway from '../utils/use-click-away' import { TriggerTypes, Placement, SnippetTypes } from '../utils/prop-types' import { withScale } from '../use-scale' import { getRect } from './helper' import useClasses from '../use-classes' export type TooltipOnVisibleChange = (visible: boolean) => void export type TooltipTypes = SnippetTypes export type TooltipTriggers = TriggerTypes export type TooltipPlacement = Placement interface Props { text: string | React.ReactNode type?: TooltipTypes placement?: TooltipPlacement visible?: boolean initialVisible?: boolean hideArrow?: boolean trigger?: TooltipTriggers enterDelay?: number leaveDelay?: number offset?: number className?: string portalClassName?: string onVisibleChange?: TooltipOnVisibleChange } const defaultProps = { initialVisible: false, hideArrow: false, type: 'default' as TooltipTypes, trigger: 'hover' as TooltipTriggers, placement: 'top' as TooltipPlacement, enterDelay: 100, leaveDelay: 150, offset: 12, className: '', portalClassName: '', onVisibleChange: (_visible: false) => { return } } type NativeAttrs = Omit, keyof Props> export type TooltipProps = Props & NativeAttrs const TooltipComponent: React.FC> = ({ children, initialVisible, text, offset, placement, portalClassName, enterDelay, leaveDelay, trigger, type, className, onVisibleChange, hideArrow, visible: customVisible, ...props }) => { const timer = useRef() const ref = useRef(null) const [visible, setVisible] = useState(!!initialVisible) const iconOffset = useMemo(() => { if (!ref?.current) return { x: '0.75em', y: '0.75em' } const rect = getRect(ref) return { x: `${rect.width ? rect.width / 2 : 0}px`, y: `${rect.height ? rect.height / 2 : 0}px` } }, [ref?.current]) const contentProps = { type, visible, offset, placement, hideArrow, iconOffset, parent: ref, className: portalClassName } const changeVisible = (nextState: boolean) => { const clear = () => { clearTimeout(timer.current) timer.current = undefined } const handler = (nextState: boolean) => { setVisible(nextState) if (onVisibleChange) { onVisibleChange(nextState) } clear() } clear() if (nextState) { timer.current = window.setTimeout(() => handler(true), enterDelay) return } const leaveDelayWithoutClick = trigger === 'click' ? 0 : leaveDelay timer.current = window.setTimeout(() => handler(false), leaveDelayWithoutClick) } const mouseEventHandler = (next: boolean) => trigger === 'hover' && changeVisible(next) const clickEventHandler = () => trigger === 'click' && changeVisible(!visible) useClickAway(ref, () => trigger === 'click' && changeVisible(false)) useEffect(() => { if (customVisible === undefined) return changeVisible(customVisible) }, [customVisible]) return (
mouseEventHandler(true)} onMouseLeave={() => mouseEventHandler(false)} {...props} > {children} {text}
) } TooltipComponent.defaultProps = defaultProps TooltipComponent.displayName = 'HuiTooltip' const Tooltip = withScale(TooltipComponent) export default Tooltip