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