Better tooltips

old
Javi Velasco 2016-08-07 18:55:31 +02:00
parent f1a3031a57
commit fcc7902839
4 changed files with 90 additions and 33 deletions

View File

@ -1,10 +1,26 @@
import React, { Component, PropTypes } from 'react';
import Portal from '../hoc/Portal';
import classnames from 'classnames';
import { themr } from 'react-css-themr';
import { TOOLTIP } from '../identifiers.js';
import events from '../utils/events';
const factory = (defaultTheme = {}) => {
const Tooltip = (ComposedComponent) => {
const defaults = {
className: '',
delay: 0,
hideOnClick: true,
theme: {}
};
const tooltipFactory = (options = {}) => {
const {
className: defaultClassName,
delay: defaultDelay,
hideOnClick: defaultHideOnClick,
theme: defaultTheme
} = {...defaults, ...options};
return ComposedComponent => {
class TooltippedComponent extends Component {
static propTypes = {
children: PropTypes.any,
@ -23,51 +39,98 @@ const factory = (defaultTheme = {}) => {
};
static defaultProps = {
className: '',
tooltipDelay: 0,
tooltipHideOnClick: true
className: defaultClassName,
tooltipDelay: defaultDelay,
tooltipHideOnClick: defaultHideOnClick
};
state = {
active: false
active: false,
visible: false
};
componentWillUnmount () {
if (this.refs.tooltip) {
events.removeEventListenerOnTransitionEnded(this.refs.tooltip, this.onTransformEnd);
}
}
activate (top, left) {
if (this.timeout) clearTimeout(this.timeout);
this.setState({ visible: true });
this.timeout = setTimeout(() => {
this.setState({ active: true, top, left });
}, this.props.tooltipDelay);
}
deactivate () {
if (this.timeout) clearTimeout(this.timeout);
if (this.state.active) {
events.addEventListenerOnTransitionEnded(this.refs.tooltip, this.onTransformEnd);
this.setState({ active: false });
} else if (this.state.visible) {
this.setState({ visible: false });
}
}
onTransformEnd = (e) => {
if (e.propertyName === 'transform') {
events.removeEventListenerOnTransitionEnded(this.refs.tooltip, this.onTransformEnd);
this.setState({ visible: false });
}
};
handleMouseEnter = (event) => {
if (this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(() =>this.setState({active: true}), this.props.tooltipDelay);
const yOffset = window.scrollY || window.pageYOffset;
const xOffset = window.scrollX || window.pageXOffset;
const { top, left, height, width } = event.target.getBoundingClientRect();
this.activate(top + height + yOffset, left + (width / 2) + xOffset);
if (this.props.onMouseEnter) this.props.onMouseEnter(event);
};
handleMouseLeave = (event) => {
if (this.timeout) clearTimeout(this.timeout);
if (this.state.active) this.setState({active: false});
this.deactivate();
if (this.props.onMouseLeave) this.props.onMouseLeave(event);
};
handleClick = (event) => {
if (this.timeout) clearTimeout(this.timeout);
if (this.props.tooltipHideOnClick) this.setState({active: false});
if (this.props.tooltipHideOnClick) this.deactivate();
if (this.props.onClick) this.props.onClick(event);
};
render () {
const {children, className, tooltip,
tooltipDelay, tooltipHideOnClick, ...other} = this.props; //eslint-disable-line no-unused-vars
const composedClassName = classnames(this.props.theme.tooltipWrapper, className);
const tooltipClassName = classnames(this.props.theme.tooltip, {
[this.props.theme.tooltipActive]: this.state.active
});
const { active, left, top, visible } = this.state;
const {
children,
className,
theme,
tooltip,
tooltipDelay, //eslint-disable-line no-unused-vars
tooltipHideOnClick, //eslint-disable-line no-unused-vars
...other
} = this.props;
return (
<ComposedComponent
{...other}
className={composedClassName}
className={className}
onClick={this.handleClick}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
theme={theme}
>
{children ? children : null}
<span data-react-toolbox="tooltip" className={tooltipClassName}>{tooltip}</span>
{visible && (
<Portal>
<span
ref="tooltip"
children={tooltip}
className={classnames(theme.tooltip, {[theme.tooltipActive]: active})}
data-react-toolbox="tooltip"
style={{ top, left }}
/>
</Portal>
)}
</ComposedComponent>
);
}
@ -75,9 +138,6 @@ const factory = (defaultTheme = {}) => {
return themr(TOOLTIP, defaultTheme)(TooltippedComponent);
};
return Tooltip;
};
export default factory();
export { factory as tooltipFactory };
export default tooltipFactory;

View File

@ -1,4 +1,6 @@
import { tooltipFactory } from './Tooltip.js';
import tooltipFactory from './Tooltip.js';
import theme from './theme.scss';
export default tooltipFactory(theme);
const themedTooltipFactory = (options) => tooltipFactory({ ...options, theme });
export default tooltipFactory({ theme });
export { themedTooltipFactory as tooltipFactory };

View File

@ -1,6 +1,6 @@
# Tooltip
A Tooltip is useful to show information on hover in any kind of component. We have a component that can be used as a **decorator** for any kind of component. You just have to take into account that the overflow in the component should be visible.
A Tooltip is useful to show information on hover in any kind of component. We have a component that can be used as a **decorator** for any kind of component. Also, it's factory function is exposed so you can create your own decorator with specific properties.
<!-- example -->
```jsx

View File

@ -3,14 +3,8 @@
@import "../mixins";
@import "./config";
.tooltipWrapper {
position: relative;
}
.tooltip {
position: absolute;
top: 100%;
left: 50%;
z-index: $z-index-higher;
display: block;
max-width: $tooltip-max-width;
@ -22,6 +16,7 @@
line-height: $font-size-small;
color: $tooltip-color;
text-align: center;
pointer-events: none;
text-transform: none;
background: $tooltip-background;
border-radius: $tooltip-border-radius;