Skip to content

Commit c65161c

Browse files
author
vikasrohit
committed
Merge branch 'Colinh84-Colinh84-tooltip-component'
2 parents 7f64b92 + 56960b5 commit c65161c

File tree

6 files changed

+375
-1
lines changed

6 files changed

+375
-1
lines changed

components/ExampleNav/ExampleNavContainer.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ const navs = {
2424
'ImageViewerExamples',
2525
'LoaderExamples',
2626
'PanelExamples',
27-
'StandardListItemExamples'
27+
'StandardListItemExamples',
28+
'TooltipExamples'
2829
],
2930
ManageSteps: [
3031
'ManageStepsExamples',

components/Router/Router.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import NavbarExample from '../Navbar/NavbarExample.jsx'
2727
import TCFooterExamples from '../TCFooter/TCFooterExamples.jsx'
2828
import SubTrackDetailsExample from '../SubTrackDetails/SubTrackDetailsExample.jsx'
2929
import PrizeExamples from '../Prize/PrizeExamples.jsx'
30+
import TooltipExamples from '../Tooltip/TooltipExamples.jsx'
3031

3132
const Component = () => (
3233
<Provider store={store}>
@@ -76,6 +77,8 @@ const Component = () => (
7677

7778
<Route path="/SubTrackDetailsExample" component={SubTrackDetailsExample} />
7879

80+
<Route path="/TooltipExamples" component={TooltipExamples} />
81+
7982
<Route path="/PrizeExamples" component={PrizeExamples}/>
8083
</Route>
8184
</Router>

components/Tooltip/Tooltip.jsx

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
require('./Tooltip.scss')
2+
3+
import React, { Component, PropTypes } from 'react'
4+
import classNames from 'classnames'
5+
import ReactDOM from 'react-dom'
6+
7+
class Tooltip extends Component {
8+
constructor(props) {
9+
super(props)
10+
this.state = { isActive: false }
11+
this.showTooltip = this.showTooltip.bind(this)
12+
this.hideTooltip = this.hideTooltip.bind(this)
13+
this.onClick = this.onClick.bind(this)
14+
}
15+
16+
showTooltip(evt) {
17+
const pointerWidth = this.props.pointerWidth
18+
const tooltipMargin = this.props.tooltipMargin
19+
const pointerGap = this.props.pointerGap
20+
const tooltipPadding = this.props.tooltipPadding
21+
const tooltipDelay = this.props.tooltipDelay
22+
const tooltip = ReactDOM.findDOMNode(this)
23+
const ttContainer = tooltip.querySelector('.tooltip-container')
24+
const tooltipPointer = ttContainer.querySelector('.tooltip-pointer')
25+
const targetRect = evt.currentTarget.getBoundingClientRect()
26+
const targetRectCenterX = (targetRect.width / 2) + targetRect.left + window.scrollX
27+
const targetRectCenterY = (targetRect.height / 2) + targetRect.top + window.scrollY
28+
ttContainer.style.padding = tooltipPadding + 'px'
29+
tooltipPointer.style.width = pointerWidth + 'px'
30+
tooltipPointer.style.height = pointerWidth + 'px'
31+
const ttBorderRadius = getComputedStyle(ttContainer).getPropertyValue('border-top-left-radius').replace(/[^-\d\.]/g, '')
32+
//if right side of tooltip will fit above/below target on screen when centered to target
33+
if (targetRectCenterX + (tooltip.clientWidth / 2) + tooltipMargin < document.body.clientWidth) {
34+
//if left side of tooltip will not fit in available screen space when centered to target
35+
if (targetRectCenterX < (tooltip.clientWidth / 2) + tooltipMargin) {
36+
//push tooltip to left side of screen plus margin
37+
tooltip.style.left = tooltipMargin + 'px'
38+
//else, center tooltip to target
39+
} else {
40+
tooltip.style.left = targetRectCenterX - (tooltip.clientWidth / 2) + 'px'
41+
}
42+
//else, push tooltip to right edge of screen plus margin
43+
} else {
44+
tooltip.style.left = document.body.clientWidth - tooltip.clientWidth - tooltipMargin + 'px'
45+
}
46+
let tooltipRect = tooltip.getBoundingClientRect()
47+
let tooltipPointerLeft = 0
48+
if (targetRectCenterX + tooltipMargin + (pointerWidth/2) >= document.body.clientWidth) {
49+
tooltipPointerLeft = tooltip.clientWidth - (pointerWidth * Math.sqrt(2)) - ttBorderRadius + 'px'
50+
} else if (targetRectCenterX < tooltipMargin + (pointerWidth/2)) {
51+
tooltipPointerLeft = ttBorderRadius + 'px'
52+
} else {
53+
tooltipPointerLeft = targetRectCenterX - tooltipRect.left - (pointerWidth/2) + 'px'
54+
}
55+
//if target is too close to top of page to fit default tooltip bubble
56+
if (targetRect.top < tooltip.clientHeight + tooltipMargin + (pointerWidth/2) + pointerGap) {
57+
//if tooltip is wider than tooltip target plus available screen space when centered above/below target,
58+
//and screen has not been scrolled down past the top of the tooltip,
59+
//and there is enough space to put tooltip to the right of target on screen
60+
if (targetRectCenterX < (tooltip.clientWidth / 2) + tooltipMargin && window.scrollY < (targetRectCenterY - (tooltip.clientHeight / 2)) && targetRect.right + tooltip.clientWidth + tooltipMargin + (pointerWidth/2) + pointerGap < document.body.clientWidth) {
61+
tooltip.style.left = targetRect.right + pointerGap + (pointerWidth/2) + 'px'
62+
tooltip.style.top = Math.max((targetRectCenterY - (tooltip.clientHeight/2)), tooltipMargin) + 'px'
63+
tooltipRect = tooltip.getBoundingClientRect()
64+
tooltipPointer.style.bottom = 'auto'
65+
tooltipPointer.style.top = Math.max((((pointerWidth * Math.sqrt(2))/2) - (pointerWidth/2)), (targetRectCenterY - tooltipRect.top - window.scrollY - (pointerWidth/2))) + 'px'
66+
tooltipPointerLeft = - (pointerWidth/2) + 'px'
67+
} else {
68+
tooltip.style.top = targetRect.bottom + pointerGap + (pointerWidth/2) + window.scrollY + 'px'
69+
tooltipPointer.style.top = 'auto'
70+
tooltipPointer.style.bottom = tooltip.clientHeight - (pointerWidth/2) + 'px'
71+
}
72+
} else {
73+
tooltip.style.top = targetRect.top - tooltip.clientHeight + window.scrollY - pointerGap - (pointerWidth/2) + 'px'
74+
tooltipPointer.style.bottom = - (pointerWidth/2) + 'px'
75+
tooltipPointer.style.top = 'auto'
76+
}
77+
tooltipPointer.style.left = tooltipPointerLeft
78+
79+
if (tooltip.classList.contains('tooltip-hide')) {
80+
tooltip.classList.remove('tooltip-hide')
81+
tooltip.style.transition = 'opacity ' + tooltipDelay + 'ms linear'
82+
tooltip.style.opacity = '1'
83+
}
84+
}
85+
86+
hideTooltip() {
87+
const tooltip = ReactDOM.findDOMNode(this)
88+
if (!tooltip.classList.contains('tooltip-hide')) {
89+
tooltip.classList.add('tooltip-hide')
90+
tooltip.style.transition = 'opacity 0s linear'
91+
tooltip.style.opacity = '0'
92+
}
93+
}
94+
95+
onClick(evt) {
96+
if (this.state.isActive) {
97+
this.hideTooltip()
98+
} else {
99+
this.showTooltip(evt)
100+
}
101+
102+
this.setState({ isActive: !this.state.isActive })
103+
}
104+
105+
componentDidMount() {
106+
const targetId = this.props.tooltipId
107+
const target = document.getElementById(targetId)
108+
if (this.props.popMethod === 'hover') {
109+
target.addEventListener('mouseenter', this.showTooltip)
110+
target.addEventListener('mouseleave', this.hideTooltip)
111+
} else if (this.props.popMethod === 'click') {
112+
target.classList.add('click-pointer')
113+
target.addEventListener('click', this.onClick)
114+
}
115+
}
116+
117+
componentWillUnmount() {
118+
const targetId = this.props.tooltipId
119+
const target = document.getElementById(targetId)
120+
target.removeEventListener('mouseenter', this.showTooltip)
121+
target.removeEventListener('mouseleave', this.hideTooltip)
122+
123+
target.removeEventListener('click', this.onClick)
124+
125+
}
126+
127+
render() {
128+
const body = (
129+
<div className="tooltip-body">
130+
{React.Children.map(this.props.children, (child) => {
131+
return child.props.children
132+
})}
133+
</div>
134+
)
135+
const ttClasses = classNames(
136+
'Tooltip', 'tooltip-hide', this.props.theme, this.props.className
137+
)
138+
return (
139+
<div className={ttClasses}>
140+
<div className="tooltip-container">
141+
<div className="tooltip-pointer">
142+
</div>
143+
{body}
144+
</div>
145+
</div>
146+
)
147+
}
148+
}
149+
150+
Tooltip.propTypes = {
151+
children : PropTypes.object.isRequired,
152+
tooltipId : PropTypes.string.isRequired,
153+
pointerWidth : PropTypes.number,
154+
tooltipMargin : PropTypes.number,
155+
pointerGap : PropTypes.number,
156+
tooltipPadding : PropTypes.number,
157+
tooltipDelay : PropTypes.number,
158+
theme : PropTypes.string,
159+
popMethod : PropTypes.string
160+
}
161+
162+
Tooltip.defaultProps = {
163+
pointerWidth : 10,
164+
tooltipMargin : 10,
165+
pointerGap : 5,
166+
tooltipPadding : 15,
167+
tooltipDelay : 0,
168+
theme : 'default',
169+
popMethod : 'hover'
170+
}
171+
172+
export default Tooltip

components/Tooltip/Tooltip.scss

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
@import 'topcoder/tc-includes';
2+
3+
$tc-gray-80: #47474f;
4+
5+
.hastooltip {
6+
cursor: default;
7+
8+
&.click-pointer {
9+
cursor: pointer;
10+
}
11+
}
12+
13+
.Tooltip {
14+
position: absolute;
15+
white-space: nowrap;
16+
display: block;
17+
visibility: visible;
18+
opacity: 1;
19+
transition: opacity 0s linear;
20+
z-index: 1000;
21+
left: -9999px;
22+
top: -9999px;
23+
24+
&.tooltip-hide {
25+
visibility: hidden;
26+
opacity: 0;
27+
}
28+
29+
.tooltip-container {
30+
background-color: $tc-gray-80;
31+
color: #fff;
32+
width: 100%;
33+
height: 100%;
34+
padding: 15px;
35+
-webkit-border-radius: 5px;
36+
-moz-border-radius: 5px;
37+
border-radius: 5px;
38+
39+
.tooltip-pointer {
40+
position: absolute;
41+
height: 10px;
42+
width: 10px;
43+
background-color: $tc-gray-80;
44+
transform: rotate(45deg);
45+
bottom: -5px;
46+
left: calc(50% - 5px);
47+
z-index:-1;
48+
}
49+
50+
.tooltip-body {
51+
position: relative;
52+
color: #fff;
53+
font-size: 14px;
54+
padding: 0;
55+
margin: 0;
56+
z-index:2;
57+
}
58+
}
59+
//tooltip theme styling
60+
&.default {
61+
.tooltip-container {
62+
//additional theme styling goes here
63+
.tooltip-pointer {
64+
65+
}
66+
.tooltip-body {
67+
68+
}
69+
}
70+
}
71+
//custom theme
72+
&.blue-round {
73+
.tooltip-container {
74+
background-color: #0000CC;
75+
border: 3px solid #000;
76+
-webkit-border-radius: 50%;
77+
-moz-border-radius: 50%;
78+
border-radius: 50%;
79+
.tooltip-pointer {
80+
background-color: #0000CC;
81+
}
82+
.tooltip-body {
83+
color: #FFF;
84+
}
85+
}
86+
}
87+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import Tooltip from './Tooltip'
2+
import React from 'react'
3+
4+
require('./TooltipExamples.scss')
5+
6+
const TooltipExamples = () => (
7+
<div className="tooltip-examples-container">
8+
<div className="hastooltip" id="tooltip-1">Default Mouseover Tooltip Example</div>
9+
10+
<Tooltip tooltipId="tooltip-1">
11+
<div className="tooltip-body">
12+
<p>This is a tooltip using the default theme.</p>
13+
</div>
14+
</Tooltip>
15+
16+
<div className="hastooltip" id="tooltip-2">Click Tooltip with Custom Classes Example</div>
17+
18+
<Tooltip tooltipId="tooltip-2" popMethod="click" className="TestClassOne CustomClass2 AnotherCustomClass">
19+
<div className="tooltip-body">
20+
<p>This is a tooltip activated with a mouse click.</p>
21+
<p>This tooltip also contains custom classes passed through the tooltip component render.</p>
22+
<p>Click Target again to close tooltip.</p>
23+
</div>
24+
</Tooltip>
25+
26+
<div className="hastooltip" id="tooltip-3">Themed Tooltip Example</div>
27+
28+
<Tooltip tooltipId="tooltip-3" theme="blue-round">
29+
<div className="tooltip-body">
30+
<p>This is a tooltip using a custom theme.</p>
31+
</div>
32+
</Tooltip>
33+
34+
<div className="hastooltip" id="tooltip-4">Delayed Tooltip Example</div>
35+
36+
<Tooltip tooltipId="tooltip-4" tooltipDelay={3000}>
37+
<div className="tooltip-body">
38+
<p>This is a tooltip with a 3 second popup delay and a transition effect.</p>
39+
</div>
40+
</Tooltip>
41+
42+
<div className="hastooltip" id="tooltip-5">Alternative Spacing Tooltip Example</div>
43+
44+
<Tooltip tooltipId="tooltip-5" pointerWidth={20} tooltipMargin={25} tooltipPadding={5} pointerGap={1}>
45+
<div className="tooltip-body">
46+
<p>This is a tooltip with alternative padding, margin, pointer size and gap sized configured through tooltip custom attributes.</p>
47+
</div>
48+
</Tooltip>
49+
50+
<img src="http://placekitten.com/200/200" className="hastooltip" id="tooltip-6" alt="tooltip on image target example" />
51+
52+
<Tooltip tooltipId="tooltip-6">
53+
<div className="tooltip-body">
54+
<p>This is a tooltip on an image and also containing an image.</p>
55+
<p>Tooltips can be applied to any HTML tag, and contain any content.</p>
56+
<img src="http://placekitten.com/100/100" alt="kittens" />
57+
<p>More text.</p>
58+
</div>
59+
</Tooltip>
60+
</div>
61+
)
62+
63+
module.exports = TooltipExamples

0 commit comments

Comments
 (0)