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 ClassNames from 'classnames';
import FontIcon from '../font_icon'; import FontIcon from '../font_icon';
import Ripple from '../ripple'; import Ripple from '../ripple';
import Tooltip from '../tooltip';
import style from './style'; import style from './style';
import events from '../utils/events';
class Button extends React.Component { class Button extends React.Component {
static propTypes = { static propTypes = {
@ -22,8 +20,6 @@ class Button extends React.Component {
primary: React.PropTypes.bool, primary: React.PropTypes.bool,
raised: React.PropTypes.bool, raised: React.PropTypes.bool,
ripple: React.PropTypes.bool, ripple: React.PropTypes.bool,
tooltip: React.PropTypes.string,
tooltipDelay: React.PropTypes.number,
type: React.PropTypes.string type: React.PropTypes.string
}; };
@ -39,15 +35,17 @@ class Button extends React.Component {
}; };
handleMouseDown = (event) => { handleMouseDown = (event) => {
events.pauseEvent(event);
if (this.refs.ripple) this.refs.ripple.start(event); if (this.refs.ripple) this.refs.ripple.start(event);
if (this.props.onMouseDown) this.props.onMouseDown(event); if (this.props.onMouseDown) this.props.onMouseDown(event);
}; };
handleMouseUp = () => {
this.refs.button.blur();
};
render () { render () {
const {accent, className, flat, floating, href, icon, inverse, label, const { accent, children, className, flat, floating, href, icon,
mini, primary, raised, ripple, inverse, label, mini, primary, raised, ripple, ...others} = this.props;
tooltip, tooltipDelay, ...others} = this.props;
const element = href ? 'a' : 'button'; const element = href ? 'a' : 'button';
const level = primary ? 'primary' : accent ? 'accent' : 'neutral'; const level = primary ? 'primary' : accent ? 'accent' : 'neutral';
const shape = flat ? 'flat' : raised ? 'raised' : floating ? 'floating' : 'flat'; const shape = flat ? 'flat' : raised ? 'raised' : floating ? 'floating' : 'flat';
@ -60,17 +58,19 @@ class Button extends React.Component {
const props = { const props = {
...others, ...others,
href, href,
ref: 'button',
className: classes, className: classes,
disabled: this.props.disabled, disabled: this.props.disabled,
onMouseDown: this.handleMouseDown, onMouseDown: this.handleMouseDown,
onMouseUp: this.handleMouseUp,
'data-react-toolbox': 'button' 'data-react-toolbox': 'button'
}; };
return React.createElement(element, props, return React.createElement(element, props,
ripple ? <Ripple ref='ripple' /> : null, ripple ? <Ripple ref='ripple' /> : null,
tooltip ? <Tooltip className={style.tooltip} delay={tooltipDelay} label={tooltip}/> : null,
icon ? <FontIcon className={style.icon} value={icon}/> : 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 ClassNames from 'classnames';
import FontIcon from '../font_icon'; import FontIcon from '../font_icon';
import Ripple from '../ripple'; import Ripple from '../ripple';
import Tooltip from '../tooltip';
import style from './style'; import style from './style';
import events from '../utils/events';
class Button extends React.Component { class Button extends React.Component {
static propTypes = { static propTypes = {
@ -17,8 +15,6 @@ class Button extends React.Component {
inverse: React.PropTypes.bool, inverse: React.PropTypes.bool,
primary: React.PropTypes.bool, primary: React.PropTypes.bool,
ripple: React.PropTypes.bool, ripple: React.PropTypes.bool,
tooltip: React.PropTypes.string,
tooltipDelay: React.PropTypes.number,
type: React.PropTypes.string type: React.PropTypes.string
}; };
@ -30,14 +26,16 @@ class Button extends React.Component {
}; };
handleMouseDown = (event) => { handleMouseDown = (event) => {
events.pauseEvent(event);
if (this.refs.ripple) this.refs.ripple.start(event); if (this.refs.ripple) this.refs.ripple.start(event);
if (this.props.onMouseDown) this.props.onMouseDown(event); if (this.props.onMouseDown) this.props.onMouseDown(event);
}; };
handleMouseUp = () => {
this.refs.button.blur();
};
render () { render () {
const {accent, children, className, href, icon, inverse, const {accent, children, className, href, icon, inverse, primary, ripple, ...others} = this.props;
primary, ripple, tooltip, tooltipDelay, ...others} = this.props;
const element = href ? 'a' : 'button'; const element = href ? 'a' : 'button';
const level = primary ? 'primary' : accent ? 'accent' : 'neutral'; const level = primary ? 'primary' : accent ? 'accent' : 'neutral';
const classes = ClassNames([style.toggle, style[level]], {[style.inverse]: inverse}, className); const classes = ClassNames([style.toggle, style[level]], {[style.inverse]: inverse}, className);
@ -45,15 +43,16 @@ class Button extends React.Component {
const props = { const props = {
...others, ...others,
href, href,
ref: 'button',
className: classes, className: classes,
disabled: this.props.disabled, disabled: this.props.disabled,
onMouseDown: this.handleMouseDown, onMouseDown: this.handleMouseDown,
onMouseUp: this.handleMouseUp,
'data-react-toolbox': 'button' 'data-react-toolbox': 'button'
}; };
return React.createElement(element, props, return React.createElement(element, props,
ripple ? <Ripple ref='ripple' centered /> : null, ripple ? <Ripple ref='ripple' centered /> : null,
tooltip ? <Tooltip className={style.tooltip} delay={tooltipDelay} label={tooltip}/> : null,
icon ? <FontIcon className={style.icon} value={icon}/> : children 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.| | `primary` | `false` | `false` | Indicates if the button should have primary color.|
| `raised` | `Boolean` | `false` | If true, the button will have a raised look. | | `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.| | `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. 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 { &::-moz-focus-inner {
border: 0; border: 0;
} }
> span:not(.tooltip) { > span:not([data-react-toolbox="tooltip"]) {
display: inline-block; display: inline-block;
line-height: $button-height; line-height: $button-height;
vertical-align: middle; vertical-align: middle;

View File

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

View File

@ -45,8 +45,6 @@ class InputTest extends React.Component {
| `onFocus` | `Function` | | Callback function that is fired when components is focused.| | `onFocus` | `Function` | | Callback function that is fired when components is focused.|
| `onKeyPress` | `Function` | | Callback function that is fired when a key is pressed.| | `onKeyPress` | `Function` | | Callback function that is fired when a key is pressed.|
| `required` | `Boolean` | `false` | If true, the html input has a required value.| | `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| | `type` | `String` | `text` | Type of the input element. It can be a valid HTML5 input type|
| `value` | `String` | | Current value of the input element.| | `value` | `String` | | Current value of the input element.|

View File

@ -1,69 +1,67 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom';
import ClassNames from 'classnames'; import ClassNames from 'classnames';
import style from './style'; import style from './style';
const HIDE_TIMEOUT = 100; const Tooltip = (ComposedComponent) => class extends React.Component {
class Tooltip extends React.Component {
static propTypes = { static propTypes = {
children: React.PropTypes.any,
className: React.PropTypes.string, className: React.PropTypes.string,
delay: React.PropTypes.number, onClick: React.PropTypes.func,
label: React.PropTypes.string onMouseEnter: React.PropTypes.func,
onMouseLeave: React.PropTypes.func,
tooltip: React.PropTypes.string,
tooltipDelay: React.PropTypes.number,
tooltipHideOnClick: React.PropTypes.bool
}; };
static defaultProps = { static defaultProps = {
className: '', className: '',
delay: 0 tooltipDelay: 0,
tooltipHideOnClick: true
}; };
state = { state = {
active: false active: false
}; };
componentDidMount = () => { handleMouseEnter = () => {
const parent = ReactDOM.findDOMNode(this).parentNode; if (this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(() =>this.setState({active: true}), this.props.tooltipDelay);
if (parent.style.position !== 'relative' && parent.style.position !== 'absolute'){ if (this.props.onMouseEnter) this.props.onMouseEnter();
parent.style.position = 'relative';
}
parent.onmouseover = this.handleParentMouseOver;
parent.onmouseout = this.handleParentMouseOut;
}; };
handleParentMouseOver = () => { handleMouseLeave = () => {
setTimeout(() => { if (this.timeout) clearTimeout(this.timeout);
if (this.deferredHide) clearTimeout(this.deferredHide); if (this.state.active) this.setState({active: false});
const node = ReactDOM.findDOMNode(this); if (this.props.onMouseLeave) this.props.onMouseLeave();
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);
}; };
handleParentMouseOut = () => { handleClick = () => {
if (this.state.active) { if (this.timeout) clearTimeout(this.timeout);
this.deferredHide = setTimeout(() => { this.setState({active: false}); }, HIDE_TIMEOUT); if (this.props.tooltipHideOnClick) this.setState({active: false});
} if (this.props.onClick) this.props.onClick();
}; }
render () { 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 [style.active]: this.state.active
}, this.props.className); });
return ( return (
<span data-react-toolbox='tooltip' className={className}> <ComposedComponent
{this.props.label} {...other}
</span> 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; export default Tooltip;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import Button from '../../components/button'; import Button from '../../components/button';
import Dialog from '../../components/dialog'; import Dialog from '../../components/dialog';
import Tooltip from '../../components/tooltip';
class DialogTest extends React.Component { class DialogTest extends React.Component {
state = { state = {
@ -31,7 +30,7 @@ class DialogTest extends React.Component {
title="Use Google's location service?" title="Use Google's location service?"
onOverlayClick={this.handleToggle} 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> </Dialog>
</section> </section>
); );

View File

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