Skip to content

Instantly share code, notes, and snippets.

@emil-alexandrescu
Created February 26, 2019 16:06
Show Gist options
  • Save emil-alexandrescu/23dad56e970f71fab837e500599cf3d5 to your computer and use it in GitHub Desktop.
Save emil-alexandrescu/23dad56e970f71fab837e500599cf3d5 to your computer and use it in GitHub Desktop.
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