import React , { FunctionComponent , MutableRefObject , useEffect , useRef , useState } from 'react' ; import * as ReactDOM from 'react-dom' ; import memoizeOne from 'memoize-one' ; import './tooltip.scss' ; interface TooltipProps { tooltip ? : string | React . ReactElement ; maxWidth ? : number | string ; verticalSpace ? : number ; className ? : string ; disabled ? : boolean ; } /**
 * This code is adapted from https://codepen.io/davidgilbertson/pen/ooXVyw
 */ const getStyle = memoizeOne ( ( tooltipElement , containerElement ? , maxWidth ? , height = 5 ) => { if ( ! tooltipElement || ! containerElement ) { return undefined ; } let width = tooltipElement . offsetWidth ; const dimensions = containerElement . getBoundingClientRect ( ) ; const targetDocument = containerElement . ownerDocument ? containerElement . ownerDocument : document ; const { clientWidth , clientHeight } = targetDocument . body ; if ( typeof ( maxWidth ) === 'string' ) { const percent = parseInt ( maxWidth ) ; maxWidth = clientWidth * percent / 100 ; } if ( maxWidth === undefined || maxWidth > clientWidth ) { maxWidth = clientWidth ; } const style = { } ; if ( width > maxWidth ) { width = maxWidth ; style . width = width ; } style . left = ( dimensions . left + ( dimensions . width / 2 ) ) - ( width / 2 ) ; style . left = Math . max ( 0 , style . left ) ; style . left = Math . min ( style . left , clientWidth - width - 1 ) ; if ( dimensions . top + dimensions . height < clientHeight / 2 ) { style . top = dimensions . top + dimensions . height + height ; } else { style . bottom = ( clientHeight - dimensions . top ) + height ; } return style ; } ) ; const getElement = memoizeOne ( ( containerRef ) : HTMLElement => { const elementId = 'tooltip-container' ; const targetDocument = containerRef . current && containerRef . current . ownerDocument ? containerRef . current . ownerDocument : document ; let element = targetDocument . getElementById ( elementId ) ; if ( ! element ) { element = targetDocument . createElement ( 'div' ) ; element . id = elementId ; targetDocument . body . appendChild ( element ) ; } return element ; } ) ; const Tooltip = ( props ) => { const [ openTimeout , setOpenTimeout ] = useState < number | undefined > ( ) ; useEffect ( ( ) => ( ) => { openTimeout && window . clearTimeout ( openTimeout ) ; } , [ openTimeout ] ) ; const [ closeTimeout , setCloseTimeout ] = useState < number | undefined > ( ) ; useEffect ( ( ) => ( ) => { closeTimeout && window . clearTimeout ( closeTimeout ) ; } , [ closeTimeout ] ) ; const [ visible , setVisible ] = useState ( false ) ; const containerRef = useRef < HTMLSpanElement | null > ( null ) ; const tooltipRef = useRef < HTMLDivElement | null > ( null ) ; return ! props . tooltip ? ( < span className = { props . className } > { props . children } < /span>) : (<span className={props.className} ref={containerRef} onPointerOver={() => { setCloseTimeout ( undefined ) ; if ( ! visible ) { setOpenTimeout ( window . setTimeout ( ( ) => { setVisible ( true ) ; setOpenTimeout ( undefined ) ; } , 500 ) ) ; } } } onPointerOut = { ( evt ) => { setOpenTimeout ( undefined ) ; if ( visible ) { setCloseTimeout ( window . setTimeout ( ( ) => { setVisible ( false ) ; setCloseTimeout ( undefined ) ; } , evt . pointerType === 'touch' ? 1000 : 50 ) ) ; } } } > { props . children } { ! visible || props . disabled ? null : ( ReactDOM . createPortal ( ( < div className = 'tooltip-body' ref = { tooltipRef } style = { getStyle ( tooltipRef . current , containerRef . current , props . maxWidth , props . verticalSpace ) } > { props . tooltip } < /div>), getElement(containerRef)))} < /span>); } ; export default Tooltip ;