Created
February 26, 2019 16:06
-
-
Save emil-alexandrescu/23dad56e970f71fab837e500599cf3d5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component } from 'react'; | |
import PropTypes from 'prop-types'; | |
import classNames from 'classnames'; | |
import { Scrollbars } from 'react-custom-scrollbars'; | |
import { listenClick } from 'lib/utils'; | |
import './style.scss'; | |
export const Tooltip = ({ items }) => ( | |
<ul className="tooltip__list-tooltip"> | |
{items.map(item => | |
<li key={item.id}>{item.name}</li> | |
)} | |
</ul> | |
); | |
class TooltipTrigger extends Component { | |
static propTypes = { | |
className: PropTypes.string, | |
// A function that returns a JSX element that represents the tooltip content. | |
// Receives `onClose` param - a method that hides the tooltip. | |
// Will override `tooltip` if provided. | |
renderTooltip: PropTypes.func, | |
// A JSX element or an array of JSX elements that represents the tooltip content. | |
// Ignored if `renderTooltip` is provided. | |
tooltip: PropTypes.any, | |
// When to display the tooltip | |
// Default is 'hover' | |
triggerOn: PropTypes.oneOf(['hover', 'click']), | |
// Where to display the tooltip arrow | |
// Default is 'left' | |
side: PropTypes.oneOf(['left', 'right', 'center']), | |
// Prevents tooltip opening if `true` | |
disabled: PropTypes.bool, | |
// Do not display arrow | |
noArrow: PropTypes.bool, | |
// White theme with box-shadow | |
white: PropTypes.bool, | |
// A class name for the tooltip content | |
tooltipClassName: PropTypes.string, | |
// indicates if tooltip trigger has scroll | |
maxHeight: PropTypes.number, | |
// Close tooltip on click in content | |
closeOnInsideClick: PropTypes.bool, | |
// Don't hide content if disabled=true | |
keepOpenDisabled: PropTypes.bool, | |
onOpen: PropTypes.func, | |
onClose: PropTypes.func, | |
onVisibleChange: PropTypes.func | |
}; | |
static defaultProps = { | |
triggerOn: 'hover', | |
side: 'left', | |
closeOnInsideClick: false | |
}; | |
constructor(props) { | |
super(props); | |
this.state = { | |
isTooltipVisible: false | |
}; | |
} | |
componentWillReceiveProps(nextProps) { | |
if (nextProps.disabled && !this.props.disabled) { | |
this.setState({ isTooltipVisible: false }); | |
} | |
} | |
componentWillUnmount() { | |
if (this.props.triggerOn === 'click') { | |
document.removeEventListener('click', this.listenClick); | |
} | |
} | |
/** | |
* Hide the tooltip if clicked outside. | |
* | |
* @param {object} e - event object | |
*/ | |
@bind | |
listenClick(e) { | |
if (this.props.triggerOn !== 'click') { | |
return; | |
} | |
if (this.props.closeOnInsideClick) { | |
this.toggleTooltip(false); | |
} else { | |
listenClick(e, this.containerEl, () => this.toggleTooltip(false)); | |
} | |
} | |
@bind | |
handleMouseLeave() { | |
if (this.props.triggerOn === 'click') { | |
return; | |
} | |
this.toggleTooltip(false); | |
} | |
@bind | |
handleMouseEnter() { | |
if (this.props.triggerOn === 'click') { | |
return; | |
} | |
this.toggleTooltip(true); | |
} | |
@bind | |
handleClick() { | |
if (this.props.triggerOn !== 'click') { | |
return; | |
} | |
this.toggleTooltip(); | |
} | |
@bind | |
toggleTooltip(isVisible) { | |
if (this.props.disabled) { | |
return; | |
} | |
const nextIsVisible = typeof isVisible === 'boolean' ? isVisible : !this.state.isTooltipVisible; | |
if (nextIsVisible && this.props.triggerOn === 'click') { | |
document.addEventListener('click', this.listenClick); | |
} | |
else if (!nextIsVisible && this.props.triggerOn === 'click') { | |
document.removeEventListener('click', this.listenClick); | |
} | |
if (nextIsVisible && typeof this.props.onOpen === 'function') { | |
this.props.onOpen(); | |
} | |
if (!nextIsVisible && typeof this.props.onClose === 'function') { | |
this.props.onClose(); | |
} | |
this.setState({ | |
isTooltipVisible: nextIsVisible | |
}, () => { | |
if (this.props.onVisibleChange) { | |
this.props.onVisibleChange(nextIsVisible); | |
} | |
}); | |
} | |
renderThumbVertical(props) { | |
return ( | |
<div {...props} className="tooltip__thumb-vertical" /> | |
); | |
} | |
renderTooltipContent() { | |
const { tooltip, renderTooltip } = this.props; | |
if (renderTooltip) { | |
return renderTooltip(() => this.toggleTooltip(false)); | |
} else if (tooltip) { | |
return tooltip; | |
} else { | |
return null; | |
} | |
} | |
render() { | |
const { | |
className, | |
tooltipClassName, | |
side, | |
noArrow, | |
disabled, | |
white, | |
children, | |
maxHeight, | |
keepOpenDisabled | |
} = this.props; | |
const tooltipCN = classNames({ | |
'tooltip': true, | |
'tooltip--visible': this.state.isTooltipVisible && (keepOpenDisabled || !disabled), | |
'tooltip--left': side === 'left', | |
'tooltip--right': side === 'right', | |
'tooltip--center': side === 'center', | |
'tooltip--no-arrow': noArrow, | |
'tooltip--white': white, | |
'tooltip--disabled': disabled, | |
[className]: Boolean(className) | |
}); | |
const contentCN = classNames({ | |
'tooltip-content': true, | |
[tooltipClassName]: Boolean(tooltipClassName) | |
}); | |
const tooltipContent = this.renderTooltipContent(); | |
return ( | |
<div className={tooltipCN} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}> | |
<div className="tooltip-trigger" onClick={this.handleClick}> | |
{children} | |
</div> | |
<div className={contentCN} ref={node => this.containerEl = node}> | |
{!maxHeight ? tooltipContent : | |
<Scrollbars | |
autoHeight | |
autoHeightMin={0} | |
autoHeightMax={maxHeight} | |
thumbSize={30} | |
renderThumbVertical={this.renderThumbVertical} | |
> | |
{tooltipContent} | |
</Scrollbars> | |
} | |
</div> | |
</div> | |
); | |
} | |
} | |
export default TooltipTrigger; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment