-
-
Notifications
You must be signed in to change notification settings - Fork 1k
/
TooltipWrapper.js
129 lines (120 loc) · 3.83 KB
/
TooltipWrapper.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/*
* This file is part of the nivo project.
*
* Copyright 2016-present, Raphaël Benitte.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import React, { memo, useMemo, useState } from 'react'
import PropTypes from 'prop-types'
import Measure from 'react-measure'
import { Motion, spring } from 'react-motion'
import { useTheme, useMotionConfig } from '@nivo/core'
const TOOLTIP_OFFSET = 14
const tooltipStyle = {
pointerEvents: 'none',
position: 'absolute',
zIndex: 10,
top: 0,
left: 0,
}
const TooltipWrapper = memo(({ position, anchor, children }) => {
const [dimensions, setDimensions] = useState(null)
const theme = useTheme()
const { animate, springConfig } = useMotionConfig()
let x = Math.round(position[0])
let y = Math.round(position[1])
if (dimensions !== null) {
if (anchor === 'top') {
x -= dimensions[0] / 2
y -= dimensions[1] + TOOLTIP_OFFSET
} else if (anchor === 'right') {
x += TOOLTIP_OFFSET
y -= dimensions[1] / 2
} else if (anchor === 'bottom') {
x -= dimensions[0] / 2
y += TOOLTIP_OFFSET
} else if (anchor === 'left') {
x -= dimensions[0] + TOOLTIP_OFFSET
y -= dimensions[1] / 2
} else if (anchor === 'center') {
x -= dimensions[0] / 2
y -= dimensions[1] / 2
}
}
const style = useMemo(
() => ({
...tooltipStyle,
...theme.tooltip,
transform: `translate(${x}px, ${y}px)`,
opacity: dimensions === null ? 0 : 1,
}),
[x, y, dimensions, theme.tooltip]
)
// if we don't have dimensions yet, we use
// the non animated version with a 0 opacity
// to avoid a flash effect and weird initial transition
if (animate !== true || dimensions === null) {
return (
<Measure
client={false}
offset={false}
bounds={true}
margin={false}
onResize={({ bounds }) => {
setDimensions([bounds.width, bounds.height])
}}
>
{({ measureRef }) => (
<div ref={measureRef} style={style}>
{children}
</div>
)}
</Measure>
)
}
return (
<Motion
style={{
x: spring(x, springConfig),
y: spring(y, springConfig),
}}
>
{animatedPosition => (
<Measure
client={false}
offset={false}
bounds={true}
margin={false}
onResize={({ bounds }) => {
setDimensions([bounds.width, bounds.height])
}}
>
{({ measureRef }) => (
<div
ref={measureRef}
style={{
...tooltipStyle,
...theme.tooltip,
transform: `translate3d(${animatedPosition.x}px, ${animatedPosition.y}px, 0)`,
}}
>
{children}
</div>
)}
</Measure>
)}
</Motion>
)
})
TooltipWrapper.displayName = 'TooltipWrapper'
TooltipWrapper.propTypes = {
position: PropTypes.array.isRequired,
anchor: PropTypes.oneOf(['top', 'right', 'bottom', 'left', 'center']).isRequired,
children: PropTypes.node.isRequired,
}
TooltipWrapper.defaultProps = {
anchor: 'top',
}
export default TooltipWrapper