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';
|
2017-01-05 04:42:18 +03:00
|
|
|
import { getViewport } from '../utils/utils';
|
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
|
|
|
|
|
|
|
const POSITION = {
|
|
|
|
BOTTOM: 'bottom',
|
|
|
|
HORIZONTAL: 'horizontal',
|
|
|
|
LEFT: 'left',
|
|
|
|
RIGHT: 'right',
|
|
|
|
TOP: 'top',
|
|
|
|
VERTICAL: 'vertical'
|
|
|
|
};
|
2015-11-22 23:41:28 +03:00
|
|
|
|
2016-08-07 19:55:31 +03:00
|
|
|
const defaults = {
|
|
|
|
className: '',
|
|
|
|
delay: 0,
|
|
|
|
hideOnClick: true,
|
2017-01-24 13:12:40 +03:00
|
|
|
passthrough: true,
|
2016-11-18 22:41:24 +03:00
|
|
|
showOnClick: false,
|
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-11-18 22:41:24 +03:00
|
|
|
showOnClick: defaultShowOnClick,
|
2017-01-24 13:12:40 +03:00
|
|
|
passthrough: defaultPassthrough,
|
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
|
|
|
|
}),
|
2016-09-27 01:00:20 +03:00
|
|
|
tooltip: PropTypes.oneOfType([
|
|
|
|
PropTypes.string,
|
|
|
|
PropTypes.node
|
|
|
|
]),
|
2016-05-30 08:07:50 +03:00
|
|
|
tooltipDelay: PropTypes.number,
|
2016-08-07 21:51:31 +03:00
|
|
|
tooltipHideOnClick: PropTypes.bool,
|
2016-11-18 22:41:24 +03:00
|
|
|
tooltipPosition: PropTypes.oneOf(Object.keys(POSITION).map(key => POSITION[key])),
|
|
|
|
tooltipShowOnClick: PropTypes.bool
|
2016-05-30 08:07:50 +03:00
|
|
|
};
|
2015-11-22 23:41:28 +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,
|
2016-11-18 22:41:24 +03:00
|
|
|
tooltipPosition: defaultPosition,
|
|
|
|
tooltipShowOnClick: defaultShowOnClick
|
2016-05-30 08:07:50 +03:00
|
|
|
};
|
2015-11-22 23:41:28 +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
|
|
|
};
|
2015-11-22 23:41:28 +03:00
|
|
|
|
2016-08-07 19:55:31 +03:00
|
|
|
componentWillUnmount () {
|
|
|
|
if (this.refs.tooltip) {
|
|
|
|
events.removeEventListenerOnTransitionEnded(this.refs.tooltip, this.onTransformEnd);
|
|
|
|
}
|
2017-01-05 04:42:18 +03:00
|
|
|
if (this.timeout) clearTimeout(this.timeout);
|
2016-08-07 19:55:31 +03:00
|
|
|
}
|
|
|
|
|
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();
|
2017-01-05 04:42:18 +03:00
|
|
|
const { width: ww } = getViewport();
|
2016-08-07 21:51:31 +03:00
|
|
|
const toRight = origin.left < ((ww / 2) - origin.width / 2);
|
|
|
|
return toRight ? POSITION.RIGHT : POSITION.LEFT;
|
|
|
|
} else if (tooltipPosition === POSITION.VERTICAL) {
|
|
|
|
const origin = element.getBoundingClientRect();
|
2017-01-05 04:42:18 +03:00
|
|
|
const { height: wh } = getViewport();
|
2016-08-07 21:51:31 +03:00
|
|
|
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-12-02 21:10:04 +03:00
|
|
|
this.activate(this.calculatePosition(event.currentTarget));
|
2016-05-30 08:07:50 +03:00
|
|
|
if (this.props.onMouseEnter) this.props.onMouseEnter(event);
|
|
|
|
};
|
2015-11-22 23:41:28 +03:00
|
|
|
|
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);
|
|
|
|
};
|
2015-11-22 23:41:28 +03:00
|
|
|
|
2016-05-30 08:07:50 +03:00
|
|
|
handleClick = (event) => {
|
2016-11-18 22:41:24 +03:00
|
|
|
if (this.props.tooltipHideOnClick && this.state.active) {
|
|
|
|
this.deactivate();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.props.tooltipShowOnClick && !this.state.active) {
|
2016-12-02 21:10:04 +03:00
|
|
|
this.activate(this.calculatePosition(event.currentTarget));
|
2016-11-18 22:41:24 +03:00
|
|
|
}
|
|
|
|
|
2016-05-30 08:07:50 +03:00
|
|
|
if (this.props.onClick) this.props.onClick(event);
|
|
|
|
};
|
2015-11-22 23:41:28 +03:00
|
|
|
|
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,
|
2017-01-24 13:12:40 +03:00
|
|
|
onClick, // eslint-disable-line no-unused-vars
|
|
|
|
onMouseEnter, // eslint-disable-line no-unused-vars
|
|
|
|
onMouseLeave, // eslint-disable-line no-unused-vars
|
2016-08-07 19:55:31 +03:00
|
|
|
tooltip,
|
2017-01-24 13:12:40 +03:00
|
|
|
tooltipDelay, // eslint-disable-line no-unused-vars
|
|
|
|
tooltipHideOnClick, // eslint-disable-line no-unused-vars
|
|
|
|
tooltipPosition, // eslint-disable-line no-unused-vars
|
|
|
|
tooltipShowOnClick, // eslint-disable-line no-unused-vars
|
2016-08-07 19:55:31 +03:00
|
|
|
...other
|
|
|
|
} = this.props;
|
2015-11-22 23:41:28 +03:00
|
|
|
|
2016-08-07 21:51:31 +03:00
|
|
|
const _className = classnames(theme.tooltip, {
|
|
|
|
[theme.tooltipActive]: active,
|
|
|
|
[theme[positionClass]]: theme[positionClass]
|
|
|
|
});
|
|
|
|
|
2017-01-24 13:12:40 +03:00
|
|
|
const childProps = {
|
|
|
|
...other,
|
|
|
|
className,
|
|
|
|
onClick: this.handleClick,
|
|
|
|
onMouseEnter: this.handleMouseEnter,
|
|
|
|
onMouseLeave: this.handleMouseLeave
|
|
|
|
};
|
|
|
|
|
|
|
|
const shouldPass = typeof ComposedComponent !== 'string' && defaultPassthrough;
|
|
|
|
const finalProps = shouldPass ? { ...childProps, theme } : childProps;
|
|
|
|
|
|
|
|
return React.createElement(ComposedComponent, finalProps, children,
|
|
|
|
visible && (
|
|
|
|
<Portal>
|
|
|
|
<span ref="tooltip" className={_className} data-react-toolbox="tooltip" style={{top, left}}>
|
|
|
|
<span className={theme.tooltipInner}>{tooltip}</span>
|
|
|
|
</span>
|
|
|
|
</Portal>
|
|
|
|
)
|
2016-05-30 08:07:50 +03:00
|
|
|
);
|
|
|
|
}
|
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
|
|
|
};
|
2015-11-22 23:41:28 +03:00
|
|
|
|
2016-08-07 19:55:31 +03:00
|
|
|
export default tooltipFactory;
|