1. /** @flow */
    
  2. 
    
  3. import * as React from 'react';
    
  4. import {useRef} from 'react';
    
  5. 
    
  6. import styles from './Tooltip.css';
    
  7. 
    
  8. const initialTooltipState = {height: 0, mouseX: 0, mouseY: 0, width: 0};
    
  9. 
    
  10. export default function Tooltip({
    
  11.   children,
    
  12.   className,
    
  13.   label,
    
  14.   style,
    
  15. }: any): React.Node {
    
  16.   const containerRef = useRef(null);
    
  17.   const tooltipRef = useRef(null);
    
  18. 
    
  19.   // update the position of the tooltip based on current mouse position
    
  20.   const updateTooltipPosition = (event: SyntheticMouseEvent<EventTarget>) => {
    
  21.     const element = tooltipRef.current;
    
  22.     if (element != null) {
    
  23.       // first find the mouse position
    
  24.       const mousePosition = getMousePosition(containerRef.current, event);
    
  25.       // use the mouse position to find the position of tooltip
    
  26.       const {left, top} = getTooltipPosition(element, mousePosition);
    
  27.       // update tooltip position
    
  28.       element.style.left = left;
    
  29.       element.style.top = top;
    
  30.     }
    
  31.   };
    
  32. 
    
  33.   const onMouseMove = (event: SyntheticMouseEvent<EventTarget>) => {
    
  34.     updateTooltipPosition(event);
    
  35.   };
    
  36. 
    
  37.   const tooltipClassName = label === null ? styles.hidden : '';
    
  38. 
    
  39.   return (
    
  40.     <div
    
  41.       className={styles.Container}
    
  42.       onMouseMove={onMouseMove}
    
  43.       ref={containerRef}>
    
  44.       <div
    
  45.         className={`${styles.Tooltip} ${tooltipClassName} ${className || ''}`}
    
  46.         ref={tooltipRef}
    
  47.         style={style}>
    
  48.         {label}
    
  49.       </div>
    
  50.       {children}
    
  51.     </div>
    
  52.   );
    
  53. }
    
  54. 
    
  55. const TOOLTIP_OFFSET = 5;
    
  56. 
    
  57. // Method used to find the position of the tooltip based on current mouse position
    
  58. function getTooltipPosition(
    
  59.   element: empty,
    
  60.   mousePosition: {
    
  61.     height: number,
    
  62.     mouseX: number,
    
  63.     mouseY: number,
    
  64.     width: number,
    
  65.   },
    
  66. ) {
    
  67.   const {height, mouseX, mouseY, width} = mousePosition;
    
  68.   let top: number | string = 0;
    
  69.   let left: number | string = 0;
    
  70. 
    
  71.   if (mouseY + TOOLTIP_OFFSET + element.offsetHeight >= height) {
    
  72.     if (mouseY - TOOLTIP_OFFSET - element.offsetHeight > 0) {
    
  73.       top = `${mouseY - element.offsetHeight - TOOLTIP_OFFSET}px`;
    
  74.     } else {
    
  75.       top = '0px';
    
  76.     }
    
  77.   } else {
    
  78.     top = `${mouseY + TOOLTIP_OFFSET}px`;
    
  79.   }
    
  80. 
    
  81.   if (mouseX + TOOLTIP_OFFSET + element.offsetWidth >= width) {
    
  82.     if (mouseX - TOOLTIP_OFFSET - element.offsetWidth > 0) {
    
  83.       left = `${mouseX - element.offsetWidth - TOOLTIP_OFFSET}px`;
    
  84.     } else {
    
  85.       left = '0px';
    
  86.     }
    
  87.   } else {
    
  88.     left = `${mouseX + TOOLTIP_OFFSET * 2}px`;
    
  89.   }
    
  90. 
    
  91.   return {left, top};
    
  92. }
    
  93. 
    
  94. // method used to find the current mouse position inside the container
    
  95. function getMousePosition(
    
  96.   relativeContainer: null,
    
  97.   mouseEvent: SyntheticMouseEvent<EventTarget>,
    
  98. ) {
    
  99.   if (relativeContainer !== null) {
    
  100.     // Position within the nearest position:relative container.
    
  101.     let targetContainer = relativeContainer;
    
  102.     while (targetContainer.parentElement != null) {
    
  103.       if (targetContainer.style.position === 'relative') {
    
  104.         break;
    
  105.       } else {
    
  106.         targetContainer = targetContainer.parentElement;
    
  107.       }
    
  108.     }
    
  109. 
    
  110.     const {height, left, top, width} = targetContainer.getBoundingClientRect();
    
  111. 
    
  112.     const mouseX = mouseEvent.clientX - left;
    
  113.     const mouseY = mouseEvent.clientY - top;
    
  114. 
    
  115.     return {height, mouseX, mouseY, width};
    
  116.   } else {
    
  117.     return initialTooltipState;
    
  118.   }
    
  119. }