Skip to content

Commit

Permalink
feat(tooltip): restore animation and use new measure hook
Browse files Browse the repository at this point in the history
  • Loading branch information
plouc committed Jun 26, 2020
1 parent 3e337cd commit 691125c
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 67 deletions.
1 change: 0 additions & 1 deletion packages/tooltip/package.json
Expand Up @@ -15,7 +15,6 @@
"dist/"
],
"dependencies": {
"react-measure": "^2.2.4",
"react-spring": "^8.0.27"
},
"peerDependencies": {
Expand Down
79 changes: 25 additions & 54 deletions packages/tooltip/src/components/TooltipWrapper.js
Expand Up @@ -6,11 +6,10 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import React, { memo, useMemo, useState, useRef, useEffect } from 'react'
import React, { memo, useMemo } from 'react'
import PropTypes from 'prop-types'
import Measure from 'react-measure'
import { useSpring, animated } from 'react-spring'
import { useTheme, useMotionConfig } from '@nivo/core'
import { useTheme, useMotionConfig, useMeasure } from '@nivo/core'

const TOOLTIP_OFFSET = 14

Expand All @@ -22,84 +21,56 @@ const tooltipStyle = {
left: 0,
}

const TooltipWrapper = memo(({ position, anchor, children }) => {
const TooltipWrapper = ({ position, anchor, children }) => {
const theme = useTheme()
const { config: springConfig } = useMotionConfig()

const [dimensions, setDimensions] = useState(null)
const previousDimensions = useRef(null)
useEffect(() => {
previousDimensions.current = dimensions
})
const { animate, config: springConfig } = useMotionConfig()
const [measureRef, bounds] = useMeasure()

let x = Math.round(position[0])
let y = Math.round(position[1])
if (dimensions !== null) {

const hasDimension = bounds.width > 0 && bounds.height > 0
if (hasDimension !== null) {
if (anchor === 'top') {
x -= dimensions[0] / 2
y -= dimensions[1] + TOOLTIP_OFFSET
x -= bounds.width / 2
y -= bounds.height + TOOLTIP_OFFSET
} else if (anchor === 'right') {
x += TOOLTIP_OFFSET
y -= dimensions[1] / 2
y -= bounds.height / 2
} else if (anchor === 'bottom') {
x -= dimensions[0] / 2
x -= bounds.width / 2
y += TOOLTIP_OFFSET
} else if (anchor === 'left') {
x -= dimensions[0] + TOOLTIP_OFFSET
y -= dimensions[1] / 2
x -= bounds.width + TOOLTIP_OFFSET
y -= bounds.height / 2
} else if (anchor === 'center') {
x -= dimensions[0] / 2
y -= dimensions[1] / 2
x -= bounds.width / 2
y -= bounds.height / 2
}
}

const isInitializing = dimensions === null || previousDimensions.current === null

const animatedProps = useSpring({
transform: `translate(${x}px, ${y}px)`,
config: springConfig,
// the way the animated tooltip works is not ideal for now
immediate: true, // !animate || isInitializing,
immediate: !animate || !hasDimension,
})

const style = useMemo(
() => ({
...tooltipStyle,
...theme.tooltip,
opacity: isInitializing ? 0 : 1,
transform: animatedProps.transform,
opacity: hasDimension ? 1 : 0,
}),
[dimensions, theme.tooltip, isInitializing]
[hasDimension, theme.tooltip, animatedProps.transform]
)

// 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
return (
<Measure
client={false}
offset={false}
bounds={true}
margin={false}
onResize={({ bounds }) => {
setDimensions([bounds.width, bounds.height])
}}
>
{({ measureRef }) => {
return (
<animated.div
ref={measureRef}
style={{
...style,
...animatedProps,
}}
>
{children}
</animated.div>
)
}}
</Measure>
<animated.div ref={measureRef} style={style}>
{children}
</animated.div>
)
})
}

TooltipWrapper.displayName = 'TooltipWrapper'
TooltipWrapper.propTypes = {
Expand All @@ -111,4 +82,4 @@ TooltipWrapper.defaultProps = {
anchor: 'top',
}

export default TooltipWrapper
export default memo(TooltipWrapper)
39 changes: 27 additions & 12 deletions packages/tooltip/src/hooks.js
Expand Up @@ -6,7 +6,7 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { useState, useContext, useCallback } from 'react'
import { useState, useContext, useCallback, useMemo } from 'react'
import { tooltipContext } from './context'

export const useTooltipHandlers = container => {
Expand All @@ -16,14 +16,17 @@ export const useTooltipHandlers = container => {
position: {},
})

const showTooltipAt = useCallback((content, [x, y], anchor) => {
setState({
isVisible: true,
position: [x, y],
anchor,
content,
})
}, [])
const showTooltipAt = useCallback(
(content, [x, y], anchor) => {
setState({
isVisible: true,
position: [x, y],
anchor,
content,
})
},
[setState]
)

const showTooltipFromEvent = useCallback(
(content, event, anchor) => {
Expand All @@ -43,12 +46,12 @@ export const useTooltipHandlers = container => {
content,
})
},
[container]
[container, setState]
)

const hideTooltip = useCallback(() => {
setState({ isVisible: false, content: null })
})
}, [setState])

return {
showTooltipAt,
Expand All @@ -61,4 +64,16 @@ export const useTooltipHandlers = container => {
}
}

export const useTooltip = () => useContext(tooltipContext)
export const useTooltip = () => {
const context = useContext(tooltipContext)

const memoizedContext = useMemo(() => {
return {
showTooltipAt: context.showTooltipAt,
showTooltipFromEvent: context.showTooltipFromEvent,
hideTooltip: context.hideTooltip,
}
}, [context.showTooltipAt, context.showTooltipFromEvent, context.hideTooltip])

return memoizedContext
}

0 comments on commit 691125c

Please sign in to comment.