react-toolbox/components/tooltip/Tooltip.js

208 lines
6.4 KiB
JavaScript
Raw Normal View History

2016-05-30 08:07:50 +03:00
import React, { Component, PropTypes } from 'react';
2016-08-07 19:55:31 +03:00
import Portal from '../hoc/Portal';
2016-05-26 22:17:25 +03:00
import classnames from 'classnames';
import { themr } from 'react-css-themr';
2016-05-30 08:07:50 +03:00
import { TOOLTIP } from '../identifiers.js';
2016-08-07 19:55:31 +03:00
import events from '../utils/events';
2016-08-07 21:51:31 +03:00
import utils from '../utils/utils';
const POSITION = {
BOTTOM: 'bottom',
HORIZONTAL: 'horizontal',
LEFT: 'left',
RIGHT: 'right',
TOP: 'top',
VERTICAL: 'vertical'
};
2016-08-07 19:55:31 +03:00
const defaults = {
className: '',
delay: 0,
hideOnClick: true,
2016-08-07 21:51:31 +03:00
position: POSITION.VERTICAL,
2016-08-07 19:55:31 +03:00
theme: {}
};
const tooltipFactory = (options = {}) => {
const {
className: defaultClassName,
delay: defaultDelay,
hideOnClick: defaultHideOnClick,
2016-08-07 21:51:31 +03:00
position: defaultPosition,
2016-08-07 19:55:31 +03:00
theme: defaultTheme
} = {...defaults, ...options};
return ComposedComponent => {
2016-05-30 08:07:50 +03:00
class TooltippedComponent extends Component {
static propTypes = {
children: PropTypes.any,
className: PropTypes.string,
onClick: PropTypes.func,
onMouseEnter: PropTypes.func,
onMouseLeave: PropTypes.func,
theme: PropTypes.shape({
tooltip: PropTypes.string,
tooltipActive: PropTypes.string,
tooltipWrapper: PropTypes.string
}),
tooltip: PropTypes.string,
tooltipDelay: PropTypes.number,
2016-08-07 21:51:31 +03:00
tooltipHideOnClick: PropTypes.bool,
tooltipPosition: PropTypes.oneOf(Object.keys(POSITION).map(key => POSITION[key]))
2016-05-30 08:07:50 +03:00
};
2016-05-30 08:07:50 +03:00
static defaultProps = {
2016-08-07 19:55:31 +03:00
className: defaultClassName,
tooltipDelay: defaultDelay,
2016-08-07 21:51:31 +03:00
tooltipHideOnClick: defaultHideOnClick,
tooltipPosition: defaultPosition
2016-05-30 08:07:50 +03:00
};
2016-05-30 08:07:50 +03:00
state = {
2016-08-07 19:55:31 +03:00
active: false,
2016-08-07 21:51:31 +03:00
position: this.props.tooltipPosition,
2016-08-07 19:55:31 +03:00
visible: false
2016-05-30 08:07:50 +03:00
};
2016-08-07 19:55:31 +03:00
componentWillUnmount () {
if (this.refs.tooltip) {
events.removeEventListenerOnTransitionEnded(this.refs.tooltip, this.onTransformEnd);
}
}
2016-08-07 21:51:31 +03:00
activate ({ top, left, position }) {
2016-08-07 19:55:31 +03:00
if (this.timeout) clearTimeout(this.timeout);
2016-08-07 21:51:31 +03:00
this.setState({ visible: true, position });
2016-08-07 19:55:31 +03:00
this.timeout = setTimeout(() => {
this.setState({ active: true, top, left });
}, this.props.tooltipDelay);
}
deactivate () {
2016-05-30 08:07:50 +03:00
if (this.timeout) clearTimeout(this.timeout);
2016-08-07 19:55:31 +03:00
if (this.state.active) {
events.addEventListenerOnTransitionEnded(this.refs.tooltip, this.onTransformEnd);
this.setState({ active: false });
} else if (this.state.visible) {
this.setState({ visible: false });
}
}
2016-08-07 21:51:31 +03:00
getPosition (element) {
const { tooltipPosition } = this.props;
if (tooltipPosition === POSITION.HORIZONTAL) {
const origin = element.getBoundingClientRect();
const { width: ww } = utils.getViewport();
const toRight = origin.left < ((ww / 2) - origin.width / 2);
return toRight ? POSITION.RIGHT : POSITION.LEFT;
} else if (tooltipPosition === POSITION.VERTICAL) {
const origin = element.getBoundingClientRect();
const { height: wh } = utils.getViewport();
const toBottom = origin.top < ((wh / 2) - origin.height / 2);
return toBottom ? POSITION.BOTTOM : POSITION.TOP;
} else {
return tooltipPosition;
}
}
calculatePosition (element) {
const position = this.getPosition(element);
const { top, left, height, width } = element.getBoundingClientRect();
const xOffset = window.scrollX || window.pageXOffset;
const yOffset = window.scrollY || window.pageYOffset;
if (position === POSITION.BOTTOM) {
return {
top: top + height + yOffset,
left: left + (width / 2) + xOffset,
position
};
} else if (position === POSITION.TOP) {
return {
top: top + yOffset,
left: left + (width / 2) + xOffset,
position
};
} else if (position === POSITION.LEFT) {
return {
top: top + (height / 2) + yOffset,
left: left + xOffset,
position
};
} else if (position === POSITION.RIGHT) {
return {
top: top + (height / 2) + yOffset,
left: left + width + xOffset,
position
};
}
}
2016-08-07 19:55:31 +03:00
onTransformEnd = (e) => {
if (e.propertyName === 'transform') {
events.removeEventListenerOnTransitionEnded(this.refs.tooltip, this.onTransformEnd);
this.setState({ visible: false });
}
};
handleMouseEnter = (event) => {
2016-08-07 21:51:31 +03:00
this.activate(this.calculatePosition(event.target));
2016-05-30 08:07:50 +03:00
if (this.props.onMouseEnter) this.props.onMouseEnter(event);
};
2016-05-30 08:07:50 +03:00
handleMouseLeave = (event) => {
2016-08-07 19:55:31 +03:00
this.deactivate();
2016-05-30 08:07:50 +03:00
if (this.props.onMouseLeave) this.props.onMouseLeave(event);
};
2016-05-30 08:07:50 +03:00
handleClick = (event) => {
2016-08-07 19:55:31 +03:00
if (this.props.tooltipHideOnClick) this.deactivate();
2016-05-30 08:07:50 +03:00
if (this.props.onClick) this.props.onClick(event);
};
2016-05-30 08:07:50 +03:00
render () {
2016-08-07 21:51:31 +03:00
const { active, left, top, position, visible } = this.state;
const positionClass = `tooltip${position.charAt(0).toUpperCase() + position.slice(1)}`;
2016-08-07 19:55:31 +03:00
const {
children,
className,
theme,
tooltip,
tooltipDelay, //eslint-disable-line no-unused-vars
tooltipHideOnClick, //eslint-disable-line no-unused-vars
2016-08-07 21:51:31 +03:00
tooltipPosition, //eslint-disable-line no-unused-vars
2016-08-07 19:55:31 +03:00
...other
} = this.props;
2016-08-07 21:51:31 +03:00
const _className = classnames(theme.tooltip, {
[theme.tooltipActive]: active,
[theme[positionClass]]: theme[positionClass]
});
2016-05-30 08:07:50 +03:00
return (
<ComposedComponent
{...other}
2016-08-07 19:55:31 +03:00
className={className}
2016-05-30 08:07:50 +03:00
onClick={this.handleClick}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
2016-08-07 19:55:31 +03:00
theme={theme}
2016-05-30 08:07:50 +03:00
>
{children ? children : null}
2016-08-07 19:55:31 +03:00
{visible && (
<Portal>
2016-08-07 21:51:31 +03:00
<span ref="tooltip" className={_className} data-react-toolbox="tooltip" style={{top, left}}>
<span className={theme.tooltipInner}>{tooltip}</span>
</span>
2016-08-07 19:55:31 +03:00
</Portal>
)}
2016-05-30 08:07:50 +03:00
</ComposedComponent>
);
}
2016-05-26 22:17:25 +03:00
}
2016-05-30 08:07:50 +03:00
return themr(TOOLTIP, defaultTheme)(TooltippedComponent);
};
2015-11-29 14:39:55 +03:00
};
2016-08-07 19:55:31 +03:00
export default tooltipFactory;