New approach for Tooltips

old
Javi Velasco 2015-11-29 12:39:55 +01:00
parent de921b0438
commit 1c57370651
14 changed files with 118 additions and 120 deletions

View File

@ -2,9 +2,7 @@ import React from 'react';
import ClassNames from 'classnames';
import FontIcon from '../font_icon';
import Ripple from '../ripple';
import Tooltip from '../tooltip';
import style from './style';
import events from '../utils/events';
class Button extends React.Component {
static propTypes = {
@ -22,8 +20,6 @@ class Button extends React.Component {
primary: React.PropTypes.bool,
raised: React.PropTypes.bool,
ripple: React.PropTypes.bool,
tooltip: React.PropTypes.string,
tooltipDelay: React.PropTypes.number,
type: React.PropTypes.string
};
@ -39,15 +35,17 @@ class Button extends React.Component {
};
handleMouseDown = (event) => {
events.pauseEvent(event);
if (this.refs.ripple) this.refs.ripple.start(event);
if (this.props.onMouseDown) this.props.onMouseDown(event);
};
handleMouseUp = () => {
this.refs.button.blur();
};
render () {
const {accent, className, flat, floating, href, icon, inverse, label,
mini, primary, raised, ripple,
tooltip, tooltipDelay, ...others} = this.props;
const { accent, children, className, flat, floating, href, icon,
inverse, label, mini, primary, raised, ripple, ...others} = this.props;
const element = href ? 'a' : 'button';
const level = primary ? 'primary' : accent ? 'accent' : 'neutral';
const shape = flat ? 'flat' : raised ? 'raised' : floating ? 'floating' : 'flat';
@ -60,17 +58,19 @@ class Button extends React.Component {
const props = {
...others,
href,
ref: 'button',
className: classes,
disabled: this.props.disabled,
onMouseDown: this.handleMouseDown,
onMouseUp: this.handleMouseUp,
'data-react-toolbox': 'button'
};
return React.createElement(element, props,
ripple ? <Ripple ref='ripple' /> : null,
tooltip ? <Tooltip className={style.tooltip} delay={tooltipDelay} label={tooltip}/> : null,
icon ? <FontIcon className={style.icon} value={icon}/> : null,
label ? label : this.props.children
label,
children
);
}
}

View File

@ -2,9 +2,7 @@ import React from 'react';
import ClassNames from 'classnames';
import FontIcon from '../font_icon';
import Ripple from '../ripple';
import Tooltip from '../tooltip';
import style from './style';
import events from '../utils/events';
class Button extends React.Component {
static propTypes = {
@ -17,8 +15,6 @@ class Button extends React.Component {
inverse: React.PropTypes.bool,
primary: React.PropTypes.bool,
ripple: React.PropTypes.bool,
tooltip: React.PropTypes.string,
tooltipDelay: React.PropTypes.number,
type: React.PropTypes.string
};
@ -30,14 +26,16 @@ class Button extends React.Component {
};
handleMouseDown = (event) => {
events.pauseEvent(event);
if (this.refs.ripple) this.refs.ripple.start(event);
if (this.props.onMouseDown) this.props.onMouseDown(event);
};
handleMouseUp = () => {
this.refs.button.blur();
};
render () {
const {accent, children, className, href, icon, inverse,
primary, ripple, tooltip, tooltipDelay, ...others} = this.props;
const {accent, children, className, href, icon, inverse, primary, ripple, ...others} = this.props;
const element = href ? 'a' : 'button';
const level = primary ? 'primary' : accent ? 'accent' : 'neutral';
const classes = ClassNames([style.toggle, style[level]], {[style.inverse]: inverse}, className);
@ -45,15 +43,16 @@ class Button extends React.Component {
const props = {
...others,
href,
ref: 'button',
className: classes,
disabled: this.props.disabled,
onMouseDown: this.handleMouseDown,
onMouseUp: this.handleMouseUp,
'data-react-toolbox': 'button'
};
return React.createElement(element, props,
ripple ? <Ripple ref='ripple' centered /> : null,
tooltip ? <Tooltip className={style.tooltip} delay={tooltipDelay} label={tooltip}/> : null,
icon ? <FontIcon className={style.icon} value={icon}/> : children
);
}

View File

@ -46,8 +46,6 @@ const TestButtons = () => (
| `primary` | `false` | `false` | Indicates if the button should have primary color.|
| `raised` | `Boolean` | `false` | If true, the button will have a raised look. |
| `ripple` | `Boolean` | `true` | If true, component will have a ripple effect on click.|
| `tooptip` | `String` | | The value will be shown as a tooltip when the button is hovered. |
| `tooltipDelay` | `Number` | | Amount of time in milliseconds before the tooltip is visible.|
By default it will have neutral colors and a flat aspect even though the `flat` property is `false` by default. Also, some properties exclude others, for example a button cannot be `flat` and `raised` at the same time.

View File

@ -23,7 +23,7 @@
&::-moz-focus-inner {
border: 0;
}
> span:not(.tooltip) {
> span:not([data-react-toolbox="tooltip"]) {
display: inline-block;
line-height: $button-height;
vertical-align: middle;

View File

@ -1,11 +1,11 @@
import React from 'react';
import ClassNames from 'classnames';
import FontIcon from '../font_icon';
import Tooltip from '../tooltip';
import style from './style';
class Input extends React.Component {
static propTypes = {
children: React.PropTypes.any,
className: React.PropTypes.string,
disabled: React.PropTypes.bool,
error: React.PropTypes.string,
@ -19,8 +19,6 @@ class Input extends React.Component {
onFocus: React.PropTypes.func,
onKeyPress: React.PropTypes.func,
required: React.PropTypes.bool,
tooltip: React.PropTypes.string,
tooltipDelay: React.PropTypes.number,
type: React.PropTypes.string,
value: React.PropTypes.any
};
@ -61,10 +59,11 @@ class Input extends React.Component {
}
render () {
const {disabled, error, icon, floating, label: labelText,
maxLength, tooltip, tooltipDelay, type, value} = this.props;
const { children, disabled, error, floating, icon,
label: labelText, maxLength, multiline, type, value, ...others} = this.props;
const length = maxLength && value ? value.length : 0;
const labelClassName = ClassNames(style.label, {[style.fixed]: !floating});
const className = ClassNames(style.root, {
[style.disabled]: disabled,
[style.errored]: error,
@ -72,15 +71,24 @@ class Input extends React.Component {
[style.withIcon]: icon
}, this.props.className);
const InputElement = React.createElement(multiline ? 'textarea' : 'input', {
...others,
className: ClassNames(style.input, {[style.filled]: value}),
onChange: this.handleChange,
ref: 'input',
role: 'input',
value
});
return (
<div data-react-toolbox='input' className={className}>
{this.renderInput()}
{InputElement}
{icon ? <FontIcon className={style.icon} value={icon} /> : null}
<span className={style.bar}></span>
{labelText ? <label className={labelClassName}>{labelText}</label> : null}
{error ? <span className={style.error}>{error}</span> : null}
{maxLength ? <span className={style.counter}>{length}/{maxLength}</span> : null}
{tooltip ? <Tooltip label={tooltip} delay={tooltipDelay}/> : null}
{children}
</div>
);
}

View File

@ -45,8 +45,6 @@ class InputTest extends React.Component {
| `onFocus` | `Function` | | Callback function that is fired when components is focused.|
| `onKeyPress` | `Function` | | Callback function that is fired when a key is pressed.|
| `required` | `Boolean` | `false` | If true, the html input has a required value.|
| `tooptip` | `String` | | The value will be shown as a tooltip when the button is hovered. |
| `tooltipDelay` | `Number` | | Amount of time in milliseconds before the tooltip is visible.|
| `type` | `String` | `text` | Type of the input element. It can be a valid HTML5 input type|
| `value` | `String` | | Current value of the input element.|

View File

@ -1,69 +1,67 @@
import React from 'react';
import ReactDOM from 'react-dom';
import ClassNames from 'classnames';
import style from './style';
const HIDE_TIMEOUT = 100;
class Tooltip extends React.Component {
const Tooltip = (ComposedComponent) => class extends React.Component {
static propTypes = {
children: React.PropTypes.any,
className: React.PropTypes.string,
delay: React.PropTypes.number,
label: React.PropTypes.string
onClick: React.PropTypes.func,
onMouseEnter: React.PropTypes.func,
onMouseLeave: React.PropTypes.func,
tooltip: React.PropTypes.string,
tooltipDelay: React.PropTypes.number,
tooltipHideOnClick: React.PropTypes.bool
};
static defaultProps = {
className: '',
delay: 0
tooltipDelay: 0,
tooltipHideOnClick: true
};
state = {
active: false
};
componentDidMount = () => {
const parent = ReactDOM.findDOMNode(this).parentNode;
if (parent.style.position !== 'relative' && parent.style.position !== 'absolute'){
parent.style.position = 'relative';
}
parent.onmouseover = this.handleParentMouseOver;
parent.onmouseout = this.handleParentMouseOut;
handleMouseEnter = () => {
if (this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(() =>this.setState({active: true}), this.props.tooltipDelay);
if (this.props.onMouseEnter) this.props.onMouseEnter();
};
handleParentMouseOver = () => {
setTimeout(() => {
if (this.deferredHide) clearTimeout(this.deferredHide);
const node = ReactDOM.findDOMNode(this);
const parent = node.parentNode;
const parentStyle = parent.currentStyle || window.getComputedStyle(parent);
const offset = parseFloat(parentStyle['margin-bottom']) + parseFloat(parentStyle['padding-bottom']);
const position = parent.getBoundingClientRect();
node.style.top = `${position.height - offset}px`;
node.style.left = `${parseInt((position.width / 2) - (node.offsetWidth / 2))}px`;
if (!this.state.active) this.setState({ active: true});
}, this.props.delay);
handleMouseLeave = () => {
if (this.timeout) clearTimeout(this.timeout);
if (this.state.active) this.setState({active: false});
if (this.props.onMouseLeave) this.props.onMouseLeave();
};
handleParentMouseOut = () => {
if (this.state.active) {
this.deferredHide = setTimeout(() => { this.setState({active: false}); }, HIDE_TIMEOUT);
}
};
handleClick = () => {
if (this.timeout) clearTimeout(this.timeout);
if (this.props.tooltipHideOnClick) this.setState({active: false});
if (this.props.onClick) this.props.onClick();
}
render () {
const className = ClassNames(style.root, {
const {children, className, tooltip, tooltipDelay, tooltipHideOnClick, ...other} = this.props;
const composedClassName = ClassNames(style.root, className);
const tooltipClassName = ClassNames(style.tooltip, {
[style.active]: this.state.active
}, this.props.className);
});
return (
<span data-react-toolbox='tooltip' className={className}>
{this.props.label}
</span>
<ComposedComponent
{...other}
className={composedClassName}
onClick={this.handleClick}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
>
{children ? children : null}
<span data-react-toolbox="tooltip" className={tooltipClassName}>{tooltip}</span>
</ComposedComponent>
);
}
}
};
export default Tooltip;

View File

@ -1,8 +1,8 @@
$tooltip-background: rgba(97,97,97,.9);
$tooltip-margin: 0.5 * $unit;
$tooltip-border-radius: .2 * $unit;
$tooltip-color: #fff;
$tooltip-font-size: $unit;
$tooltip-max-width: 17 * $unit;
$tooltip-padding: .8 * $unit;
$tooltip-animation-duration: 2000ms;
$tooltip-background: rgba(97,97,97,.9) !default;
$tooltip-margin: 0.5 * $unit !default;
$tooltip-border-radius: .2 * $unit !default;
$tooltip-color: #fff !default;
$tooltip-font-size: $unit !default;
$tooltip-max-width: 17 * $unit !default;
$tooltip-padding: .8 * $unit !default;
$tooltip-animation-duration: 200ms !default;

View File

@ -1,24 +1,30 @@
# Tooltip
A tooltip is Useful for show information on hover in any kind of component. Out of the box react-toolbox offers you a property `tooltip` in the component `<Button>`.
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.
<!-- example -->
```jsx
import Button from 'react-toolbox/lib/button';
import Tooltip from 'react-toolbox/lib/tooltip';
const TooltipButton = Tooltip(Button);
const TooltipInput = Tooltip(Input);
const TooltipTest = () => (
<div>
<p>Lorem ipsum dolor sit amet, <strong>consectetur<Tooltip label='This is a auto show tooltip' /></strong> adipiscing elit.</p>
<Button label='Button with tooltip' raised accent tooltip='This is a tooltip by property' />
<TooltipButton label='Bookmark' icon='bookmark' raised primary tooltip='Bookmark Tooltip' tooltipDelay={1000} />
<TooltipButton icon='add' floating accent tooltip='Floating Tooltip' />
<TooltipInput tooltip='lorem ipsum...' />
</div>
);
```
## Properties
In any component you decorate with the Tooltip you'd get some additional props:
| Name | Type | Default | Description|
|:-----|:-----|:-----|:-----|
| `className` | `String` | `''` | Set a class to style the Component.|
| `delay` | `Number` | | Amount of time in miliseconds before the tooltip is visible.|
| `label` | `String` | | The text string to use for the tooltip.|
| `tooltip` | `String` | | The text string to use for the tooltip.|
| `tooltipDelay` | `Number` | | Amount of time in miliseconds spent before the tooltip is visible.|
| `tooltipHideOnClick` | `Boolean` | `true` | If true, the Tooltip hides after a click in the host component. |

View File

@ -3,7 +3,13 @@
@import "./config";
.root {
position: relative;
}
.tooltip {
position: absolute;
top: 100%;
left: 50%;
z-index: $z-index-higher;
display: block;
max-width: $tooltip-max-width;
@ -17,28 +23,10 @@
text-transform: none;
background: $tooltip-background;
border-radius: $tooltip-border-radius;
transform: scale(0);
transform-origin: top center;
animation-duration: $tooltip-animation-duration;
animation-timing-function: $animation-curve-default;
animation-iteration-count: 1;
animation-direction: forwards;
transition: $animation-curve-default $tooltip-animation-duration transform;
transform: scale(0) translateX(-50%);
transform-origin: top left;
&.active {
animation-name: tooltip-animation;
}
&.large {
padding: 2 * $tooltip-padding;
font-size: $font-size-small;
}
@keyframes tooltip-animation {
0% {
transform: scale(0);
}
10%, 99% {
transform: scale(1);
}
100% {
transform: scale(0);
}
transform: scale(1) translateX(-50%);
}
}

View File

@ -1,7 +1,11 @@
const TooltipButton = Tooltip(Button);
const TooltipInput = Tooltip(Input);
const TooltipTest = () => (
<div>
<p>Lorem ipsum dolor sit amet, <strong>consectetur<Tooltip label='This is a auto show tooltip' delay={500}/></strong> adipiscing elit.</p>
<Button label='Button with tooltip' raised accent tooltip='This is a tooltip by property' tooltipDelay={100}/>
<TooltipButton label='Bookmark' icon='bookmark' raised primary tooltip='Bookmark Tooltip' tooltipDelay={1000} />
<TooltipButton icon='add' floating accent tooltip='Floating Tooltip' />
<TooltipInput tooltip='lorem ipsum...' />
</div>
);

View File

@ -21,9 +21,7 @@ const ButtonTest = () => (
<IconButton icon='favorite' inverse />
<IconButton icon='favorite' />
<IconButton icon='favorite' disabled />
<IconButton primary tooltip='Bookmark Tooltip' tooltipDelay={1000}>
<GithubIcon/>
</IconButton>
<IconButton primary><GithubIcon/></IconButton>
<Button icon='add' label='Add this' flat primary />
<Button icon='add' label='Add this' flat disabled />
</section>

View File

@ -1,7 +1,6 @@
import React from 'react';
import Button from '../../components/button';
import Dialog from '../../components/dialog';
import Tooltip from '../../components/tooltip';
class DialogTest extends React.Component {
state = {
@ -31,7 +30,7 @@ class DialogTest extends React.Component {
title="Use Google's location service?"
onOverlayClick={this.handleToggle}
>
<p>Let Google help apps <strong><Tooltip label='location' />determine location</strong>. This means sending anonymous location data to Google, even when no apps are running.</p>
<p>Let Google help apps <strong>determine location</strong>. This means sending anonymous location data to Google, even when no apps are running.</p>
</Dialog>
</section>
);

View File

@ -3,17 +3,19 @@ import Button from '../../components/button';
import Input from '../../components/input';
import Tooltip from '../../components/tooltip';
const TooltipButton = Tooltip(Button);
const TooltipInput = Tooltip(Input);
const TooltipStrong = Tooltip(({children, ...other}) => <strong {...other}>{children}</strong>);
const TooltipTest = () => (
<section>
<h5>Tooltip</h5>
<p>Give information on :hover</p>
<Button label='Bookmark' icon='bookmark' raised primary tooltip='Bookmark Tooltip' tooltipDelay={1000} />
<Button icon='add' floating accent tooltip='Floating Tooltip'/>
<Button icon='add' floating disabled tooltip='Floating can not be shown' />
<Input tooltip='lorem ipsum...' />
<p>
Lorem ipsum dolor sit amet, <strong>consectetur<Tooltip label='This is a auto show tooltip' delay={300} /></strong> adipiscing elit.
</p>
<TooltipButton label='Bookmark' icon='bookmark' raised primary tooltip='Bookmark Tooltip' tooltipDelay={1000} />
<TooltipButton icon='add' floating accent tooltip='Floating Tooltip' />
<TooltipButton icon='add' floating disabled tooltip='Floating can not be shown' />
<TooltipInput tooltip='lorem ipsum...' />
<p>Lorem ipsum dolor sit amet, <TooltipStrong tooltip='This is a auto show tooltip'>consectetur</TooltipStrong> adipiscing elit.</p>
</section>
);