Files
Calculateur-Talents-Wow/src/components/Tooltip/Controller.tsx
T
2019-07-29 22:12:32 +02:00

163 lines
3.9 KiB
TypeScript

import React from 'react'
import ReactDOM from 'react-dom'
import { createPortal } from 'react-dom'
import { Map } from 'immutable'
import { Trigger } from './Trigger';
const TOOLTIP_ROOT = document.getElementById('tooltip-root')
interface Props {
position?: TooltipPosition
}
export class Controller extends React.PureComponent<Props> {
static defaultProps = {
position: 'top-right'
}
tooltip = React.createRef<HTMLSpanElement>()
trigger = React.createRef<React.ReactInstance>()
state = {
isVisible: false,
tooltipDimensions: Map({
width: 0,
height: 0
}),
style: Map({
position: 'absolute' as 'absolute',
left: 0,
top: 0,
}),
triggerRect: Map({
left: 0,
top: 0,
width: 0,
height: 0
})
}
handleMouseEnter = () => {
this.setState({
isVisible: true,
style: this.state.style.merge(this.getPosition())
})
}
handleMouseLeave = () => {
this.setState({ isVisible: false })
}
componentDidUpdate() {
if (this.state.isVisible) {
const { width, height } = (ReactDOM.findDOMNode(this.tooltip.current) as HTMLElement).getBoundingClientRect()
const { style, tooltipDimensions } = this.state
this.setState({
tooltipDimensions: tooltipDimensions.merge({ width, height }),
style: style.merge({ ...this.getPosition() })
})
}
}
getPosition = (): { top: number, left: number } => {
const { tooltipDimensions, triggerRect } = this.state
return calculatePosition(this.props.position, triggerRect, tooltipDimensions)
}
updateTriggerRect = (triggerRect: { left: number, top: number, width: number, height: number }) => {
this.setState({
triggerRect: this.state.triggerRect.merge(triggerRect)
})
}
render() {
const { children } = this.props
const { isVisible, style } = this.state
return React.Children.map(children, (child: React.ReactElement) => {
if (child.type === Trigger) {
return React.cloneElement(child, {
ref: this.trigger,
resize: this.updateTriggerRect,
onMouseEnter: this.handleMouseEnter,
onMouseLeave: this.handleMouseLeave,
})
} else {
// Tooltip
return isVisible && createPortal(
<span style={style.toJS()} ref={this.tooltip}>
{React.cloneElement(child)}
</span>,
TOOLTIP_ROOT
)
}
})
}
}
const TOOLTIP_BOUNDING_PADDING = 10
const calculatePosition = (pos: TooltipPosition, trigger: Map<string, number>, tooltip: Map<string, number>) => {
const { innerWidth: windowWidth } = window
let top = 0
let left = 0
const triggerTop = trigger.get('top') + window.pageYOffset
const triggerLeft = trigger.get('left') + window.pageXOffset
// Top
switch (pos) {
case 'top-left':
case 'top-right': {
top = triggerTop - (tooltip.get('height') + 5)
break
}
case 'bottom-left':
case 'bottom-right': {
top = triggerTop + trigger.get('height') + 5
break
}
}
// Left
switch (pos) {
case 'left':
case 'bottom-left':
case 'top-left': {
left = triggerLeft - tooltip.get('width')
break
}
case 'right':
case 'bottom-right':
case 'top-right': {
left = triggerLeft + trigger.get('width')
break
}
}
const overflowsRight = left + tooltip.get('width') + TOOLTIP_BOUNDING_PADDING > windowWidth
const overflowsTop = top - (window.scrollY + TOOLTIP_BOUNDING_PADDING) < 0
// Validate
switch (pos) {
case 'top-right': {
if (overflowsRight && !overflowsTop) return calculatePosition('top-left', trigger, tooltip)
if (!overflowsRight && overflowsTop) return calculatePosition('bottom-right', trigger, tooltip)
if (overflowsRight && overflowsTop) return calculatePosition('bottom-left', trigger, tooltip)
break
}
case 'top-left': {
break
}
}
return { top, left }
}