Skip to content

Commit

Permalink
feat(Tooltip): add ability to disable autohide on tooltip content ho…
Browse files Browse the repository at this point in the history
…ver (#149)

* feat(Tooltip): add ability to disable autohide on tooltip content hover

* docs(Tooltip): add example of disabling autohide
  • Loading branch information
eddywashere committed Sep 19, 2016
1 parent 0de52de commit 68a0ed7
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 5 deletions.
13 changes: 13 additions & 0 deletions docs/lib/Components/TooltipsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { PrismCode } from 'react-prism';
import Helmet from 'react-helmet';
import TooltipExample from '../examples/Tooltip';
const TooltipExampleSource = require('!!raw!../examples/Tooltip');
import TooltipAutoHideExample from '../examples/TooltipAutoHide';
const TooltipExampleAutoHideSource = require('!!raw!../examples/TooltipAutoHide');
import TooltipExampleMulti from '../examples/TooltipMulti';
const TooltipExampleMultiSource = require('!!raw!../examples/TooltipMulti');

Expand Down Expand Up @@ -39,6 +41,8 @@ export default class TooltipsPage extends React.Component {
PropTypes.number
]),
// optionally override show/hide delays - default { show: 0, hide: 250 }
autohide: PropTypes.bool,
// optionally hide tooltip when hovering over tooltip content - default true
placement: PropTypes.oneOf([
'top',
'bottom',
Expand All @@ -62,6 +66,15 @@ export default class TooltipsPage extends React.Component {
}`}
</PrismCode>
</pre>
<h3>Tooltip Disable Autohide</h3>
<div className="docs-example">
<TooltipAutoHideExample />
</div>
<pre>
<PrismCode className="language-jsx">
{TooltipExampleAutoHideSource}
</PrismCode>
</pre>
<h3>Tooltips List</h3>
<div className="docs-example">
<TooltipExampleMulti />
Expand Down
2 changes: 1 addition & 1 deletion docs/lib/examples/Tooltip.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint react/no-multi-comp: 0, react/prop-types: 0 */
import React from 'react';
import { Button, Tooltip } from 'reactstrap';
import { Tooltip } from 'reactstrap';

export default class Example extends React.Component {
constructor(props) {
Expand Down
31 changes: 31 additions & 0 deletions docs/lib/examples/TooltipAutoHide.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* eslint react/no-multi-comp: 0, react/prop-types: 0 */
import React from 'react';
import { Tooltip } from 'reactstrap';

export default class Example extends React.Component {
constructor(props) {
super(props);

this.toggle = this.toggle.bind(this);
this.state = {
tooltipOpen: false
};
}

toggle() {
this.setState({
tooltipOpen: !this.state.tooltipOpen
});
}

render() {
return (
<div>
<p>Sometimes you need to allow users to select text within a <a href="#" id="DisabledAutoHideExample">tooltip</a>.</p>
<Tooltip placement="top" isOpen={this.state.tooltipOpen} autohide={false} target="DisabledAutoHideExample" toggle={this.toggle}>
Try to select this text!
</Tooltip>
</div>
);
}
}
36 changes: 32 additions & 4 deletions src/Tooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const propTypes = {
tether: PropTypes.object,
toggle: PropTypes.func,
children: PropTypes.node,
autohide: PropTypes.bool,
delay: PropTypes.oneOfType([
PropTypes.shape({ show: PropTypes.number, hide: PropTypes.number }),
PropTypes.number
Expand All @@ -24,7 +25,8 @@ const DEFAULT_DELAYS = {
const defaultProps = {
isOpen: false,
placement: 'bottom',
delay: DEFAULT_DELAYS
delay: DEFAULT_DELAYS,
autohide: true
};

const defaultTetherConfig = {
Expand All @@ -47,6 +49,8 @@ class Tooltip extends React.Component {
this.toggle = this.toggle.bind(this);
this.onMouseOverTooltip = this.onMouseOverTooltip.bind(this);
this.onMouseLeaveTooltip = this.onMouseLeaveTooltip.bind(this);
this.onMouseOverTooltipContent = this.onMouseOverTooltipContent.bind(this);
this.onMouseLeaveTooltipContent = this.onMouseLeaveTooltipContent.bind(this);
this.show = this.show.bind(this);
this.hide = this.hide.bind(this);
}
Expand Down Expand Up @@ -74,6 +78,25 @@ class Tooltip extends React.Component {
this._hideTimeout = setTimeout(this.hide, this.getDelay('hide'));
}

onMouseOverTooltipContent() {
if (this.props.autohide) {
return;
}
if (this._hideTimeout) {
this.clearHideTimeout();
}
}

onMouseLeaveTooltipContent() {
if (this.props.autohide) {
return;
}
if (this._showTimeout) {
this.clearShowTimeout();
}
this._hideTimeout = setTimeout(this.hide, this.getDelay('hide'));
}

getDelay(key) {
const { delay } = this.props;
if (typeof delay === 'object') {
Expand All @@ -94,11 +117,14 @@ class Tooltip extends React.Component {

show() {
if (!this.props.isOpen) {
this.clearShowTimeout();
this.toggle();
}
}

hide() {
if (this.props.isOpen) {
this.clearHideTimeout();
this.toggle();
}
}
Expand Down Expand Up @@ -154,14 +180,16 @@ class Tooltip extends React.Component {

return (
<TetherContent
onMouseOver={this.onMouseOverTooltip}
onMouseLeave={this.onMouseLeaveTooltip}
arrow="tooltip"
tether={tetherConfig}
isOpen={this.props.isOpen}
toggle={this.toggle}
>
<div className="tooltip-inner">
<div
className="tooltip-inner"
onMouseOver={this.onMouseOverTooltipContent}
onMouseLeave={this.onMouseLeaveTooltipContent}
>
{this.props.children}
</div>
</TetherContent>
Expand Down
88 changes: 88 additions & 0 deletions test/Tooltip.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -398,4 +398,92 @@ describe('Tooltip', () => {
wrapper.detach();
});
});

describe('autohide', () => {
it('should keep tooltip around when false and onmouseleave from tooltip content', () => {
spyOn(Tooltip.prototype, 'toggle').and.callThrough();
isOpen = true;
const wrapper = mount(
<Tooltip target="target" autohide={false} isOpen={isOpen} toggle={toggle} delay={200}>
Tooltip Content
</Tooltip>,
{ attachTo: container }
);
const instance = wrapper.instance();

expect(isOpen).toBe(true);
expect(Tooltip.prototype.toggle).not.toHaveBeenCalled();

instance.onMouseLeaveTooltipContent();
jasmine.clock().tick(100);
expect(Tooltip.prototype.toggle).not.toHaveBeenCalled();
jasmine.clock().tick(200);
expect(Tooltip.prototype.toggle).toHaveBeenCalled();

wrapper.detach();
});

it('clears showTimeout in onMouseLeaveTooltipContent', () => {
spyOn(Tooltip.prototype, 'toggle').and.callThrough();
isOpen = true;
const wrapper = mount(
<Tooltip target="target" autohide={false} isOpen={isOpen} toggle={toggle} delay={200}>
Tooltip Content
</Tooltip>,
{ attachTo: container }
);
const instance = wrapper.instance();

instance.onMouseOverTooltip();
expect(instance._showTimeout).toBeTruthy();
instance.onMouseLeaveTooltipContent();
jasmine.clock().tick(300);
expect(instance._showTimeout).toBeFalsy();
wrapper.detach();
});

it('clears hide timeout in onMouseOverTooltipContent', () => {
spyOn(Tooltip.prototype, 'toggle').and.callThrough();
isOpen = true;
const wrapper = mount(
<Tooltip target="target" autohide={false} isOpen={isOpen} toggle={toggle} delay={200}>
Tooltip Content
</Tooltip>,
{ attachTo: container }
);
const instance = wrapper.instance();

expect(isOpen).toBe(true);
expect(Tooltip.prototype.toggle).not.toHaveBeenCalled();
instance.onMouseLeaveTooltipContent();
jasmine.clock().tick(100);
expect(instance._hideTimeout).toBeTruthy();
instance.onMouseOverTooltipContent();
expect(instance._hideTimeout).toBeFalsy();
instance.onMouseOverTooltipContent();
wrapper.detach();
});

it('should not keep tooltip around when autohide is true and tooltip content is hovered over', () => {
spyOn(Tooltip.prototype, 'toggle').and.callThrough();
isOpen = true;
const wrapper = mount(
<Tooltip target="target" autohide isOpen={isOpen} toggle={toggle} delay={200}>
Tooltip Content
</Tooltip>,
{ attachTo: container }
);
const instance = wrapper.instance();
expect(isOpen).toBe(true);
expect(Tooltip.prototype.toggle).not.toHaveBeenCalled();
instance.onMouseLeaveTooltip();
jasmine.clock().tick(100);
instance.onMouseOverTooltipContent();
jasmine.clock().tick(200);
expect(Tooltip.prototype.toggle).toHaveBeenCalled();
instance.onMouseLeaveTooltipContent();
expect(instance._hideTimeout).toBeFalsy();
wrapper.detach();
});
});
});

0 comments on commit 68a0ed7

Please sign in to comment.