Skip to content

Commit 0ddbed5

Browse files
committed
feat(Tooltips): add component and utils
1 parent 71dfe3d commit 0ddbed5

5 files changed

Lines changed: 625 additions & 1 deletion

File tree

lib/Tooltip.jsx

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import React, { PropTypes } from 'react';
2+
import TetherContent from './TetherContent';
3+
import { getTetherAttachments, tetherAttachements } from './utils';
4+
const propTypes = {
5+
placement: React.PropTypes.oneOf(tetherAttachements),
6+
target: PropTypes.string.isRequired,
7+
isOpen: PropTypes.bool
8+
};
9+
10+
const defaultProps = {
11+
isOpen: false,
12+
placement: 'bottom'
13+
};
14+
15+
const defaultTetherConfig = {
16+
classPrefix: 'bs-tether',
17+
classes: { element: 'tooltip in', enabled: 'open' },
18+
constraints: [
19+
{ to: 'scrollParent', attachment: 'together none' },
20+
{ to: 'window', attachment: 'together none' }
21+
]
22+
};
23+
24+
class Tooltip extends React.Component {
25+
constructor(props) {
26+
super(props);
27+
28+
this.addTargetEvents = this.addTargetEvents.bind(this);
29+
this.getTetherConfig = this.getTetherConfig.bind(this);
30+
this.handleDocumentClick = this.handleDocumentClick.bind(this);
31+
this.removeTargetEvents = this.removeTargetEvents.bind(this);
32+
this.toggle = this.toggle.bind(this);
33+
this.onMouseOverTooltip = this.onMouseOverTooltip.bind(this);
34+
this.onMouseLeaveTooltip = this.onMouseLeaveTooltip.bind(this);
35+
this.onTimeout = this.onTimeout.bind(this);
36+
}
37+
38+
componentDidMount() {
39+
this._target = document.getElementById(this.props.target);
40+
this.addTargetEvents();
41+
}
42+
43+
componentWillUnmount() {
44+
this.removeTargetEvents();
45+
}
46+
47+
onMouseOverTooltip() {
48+
if (this._hoverTimeout) {
49+
clearTimeout(this._hoverTimeout);
50+
}
51+
52+
if (!this.props.isOpen) {
53+
this.toggle();
54+
}
55+
}
56+
57+
onMouseLeaveTooltip() {
58+
this._hoverTimeout = setTimeout(this.onTimeout, 250);
59+
}
60+
61+
onTimeout() {
62+
if (this.props.isOpen) {
63+
this.toggle();
64+
}
65+
}
66+
67+
getTetherConfig() {
68+
const attachments = getTetherAttachments(this.props.placement);
69+
return {
70+
...defaultTetherConfig,
71+
...attachments,
72+
target: '#' + this.props.target,
73+
...this.props.tether
74+
};
75+
}
76+
77+
handleDocumentClick(e) {
78+
if (e.target === this._target || this._target.contains(e.target)) {
79+
if (this._hoverTimeout) {
80+
clearTimeout(this._hoverTimeout);
81+
}
82+
83+
if (!this.props.isOpen) {
84+
this.toggle();
85+
}
86+
}
87+
}
88+
89+
addTargetEvents() {
90+
this._target.addEventListener('mouseover', this.onMouseOverTooltip);
91+
this._target.addEventListener('mouseout', this.onMouseLeaveTooltip);
92+
document.addEventListener('click', this.handleDocumentClick);
93+
}
94+
95+
removeTargetEvents() {
96+
this._target.removeEventListener('mouseover', this.onMouseOverTooltip);
97+
this._target.removeEventListener('mouseout', this.onMouseLeaveTooltip);
98+
document.removeEventListener('click', this.handleDocumentClick);
99+
}
100+
101+
toggle(e) {
102+
if (this.props.disabled) {
103+
return e && e.preventDefault();
104+
}
105+
106+
this.props.toggle();
107+
}
108+
109+
render() {
110+
if (!this.props.isOpen) {
111+
return null;
112+
}
113+
114+
let tetherConfig = this.getTetherConfig();
115+
116+
return (
117+
<TetherContent
118+
onMouseOver={this.onMouseOverTooltip}
119+
onMouseLeave={this.onMouseLeaveTooltip}
120+
arrow="tooltip"
121+
tether={tetherConfig}
122+
isOpen={this.props.isOpen}
123+
toggle={this.toggle}>
124+
<div className="tooltip-inner">
125+
{this.props.children}
126+
</div>
127+
</TetherContent>
128+
);
129+
}
130+
}
131+
132+
Tooltip.propTypes = propTypes;
133+
Tooltip.defaultProps = defaultProps;
134+
135+
export default Tooltip;

lib/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import DropdownItem from './DropdownItem';
77
import DropdownMenu from './DropdownMenu';
88
import DropdownToggle from './DropdownToggle';
99
import TetherContent from './TetherContent';
10+
import Tooltip from './Tooltip';
1011

1112
export {
1213
Button,
@@ -17,5 +18,6 @@ export {
1718
DropdownItem,
1819
DropdownMenu,
1920
DropdownToggle,
20-
TetherContent
21+
TetherContent,
22+
Tooltip
2123
};

lib/utils.js

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
export function getTetherAttachments(placement) {
2+
let attachments = {};
3+
switch (placement) {
4+
case 'top':
5+
case 'top center':
6+
attachments = {
7+
attachment: 'bottom center',
8+
targetAttachment: 'top center'
9+
};
10+
break;
11+
case 'bottom':
12+
case 'bottom center':
13+
attachments = {
14+
attachment: 'top center',
15+
targetAttachment: 'bottom center'
16+
};
17+
break;
18+
case 'left':
19+
case 'left center':
20+
attachments = {
21+
attachment: 'middle right',
22+
targetAttachment: 'middle left'
23+
};
24+
break;
25+
case 'right':
26+
case 'right center':
27+
attachments = {
28+
attachment: 'middle left',
29+
targetAttachment: 'middle right'
30+
};
31+
break;
32+
case 'top left':
33+
attachments = {
34+
attachment: 'bottom left',
35+
targetAttachment: 'top left'
36+
};
37+
break;
38+
case 'top right':
39+
attachments = {
40+
attachment: 'bottom right',
41+
targetAttachment: 'top right'
42+
};
43+
break;
44+
case 'bottom left':
45+
attachments = {
46+
attachment: 'top left',
47+
targetAttachment: 'bottom left'
48+
};
49+
break;
50+
case 'bottom right':
51+
attachments = {
52+
attachment: 'top right',
53+
targetAttachment: 'bottom right'
54+
};
55+
break;
56+
case 'right top':
57+
attachments = {
58+
attachment: 'top left',
59+
targetAttachment: 'top right'
60+
};
61+
break;
62+
case 'right bottom':
63+
attachments = {
64+
attachment: 'bottom left',
65+
targetAttachment: 'bottom right'
66+
};
67+
break;
68+
case 'left top':
69+
attachments = {
70+
attachment: 'top right',
71+
targetAttachment: 'top left'
72+
};
73+
break;
74+
case 'left bottom':
75+
attachments = {
76+
attachment: 'bottom right',
77+
targetAttachment: 'bottom left'
78+
};
79+
break;
80+
default:
81+
attachments = {
82+
attachment: 'top center',
83+
targetAttachment: 'bottom center'
84+
};
85+
}
86+
87+
return attachments;
88+
}
89+
90+
export const tetherAttachements = [
91+
'top',
92+
'bottom',
93+
'left',
94+
'right',
95+
'top left',
96+
'top center',
97+
'top right',
98+
'right top',
99+
'right middle',
100+
'right bottom',
101+
'bottom right',
102+
'bottom center',
103+
'bottom left',
104+
'left top',
105+
'left middle',
106+
'left bottom'
107+
];

0 commit comments

Comments
 (0)