Skip to content

Commit 6e55277

Browse files
Merge pull request #594 from catho/QTM-714
feat(QTM-714): Migrated Tooltip component to css modules
2 parents b69051b + ad6c0ce commit 6e55277

10 files changed

+465
-493
lines changed

components/Dropdown/Dropdown.jsx

-1
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,6 @@ const Dropdown = ({
230230
text={_buttonLabel}
231231
hasLabel={hasLabel}
232232
id={_id}
233-
autocomplete={autocomplete}
234233
skin={skin}
235234
className={dropdownInputClass}
236235
{...rest}

components/Dropdown/__snapshots__/Dropdown.unit.test.jsx.snap

+2
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,7 @@ exports[`Dropdown component should match the snapshot 8`] = `
777777
<input
778778
aria-autocomplete="list"
779779
aria-labelledby="downshift-7-label"
780+
autocomplete="off"
780781
class="TextInput-module__text-input___VCylJ Dropdown-module__dropdown-input___2Gm7H Dropdown-module__dropdown-input-autocomplete___6Jjs5"
781782
id="dropdown-7"
782783
placeholder="Select an option"
@@ -1949,6 +1950,7 @@ exports[`Dropdown component should match the snapshot with dark skin 8`] = `
19491950
<input
19501951
aria-autocomplete="list"
19511952
aria-labelledby="downshift-19-label"
1953+
autocomplete="off"
19521954
class="TextInput-module__text-input___VCylJ Dropdown-module__dropdown-input___2Gm7H Dropdown-module__dropdown-input-autocomplete___6Jjs5"
19531955
dark="skin"
19541956
id="dropdown-19"

components/Tooltip/Tooltip.jsx

+21-83
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,7 @@
11
import { Component } from 'react';
22
import PropTypes from 'prop-types';
3-
import styled from 'styled-components';
4-
import placementConfig from './options';
5-
import {
6-
colors,
7-
spacing,
8-
baseFontSize as defaultBaseFontSize,
9-
} from '../shared/theme';
10-
11-
const Tip = styled.div`
12-
border-radius: 4px;
13-
font-weight: bold;
14-
opacity: ${({ visible }) => (visible ? '1' : '0')};
15-
visibility: ${({ visible }) => (visible ? 'visible' : 'hidden')};
16-
position: absolute;
17-
line-height: 0;
18-
text-align: center;
19-
transition:
20-
opacity 0.2s ease-in-out,
21-
visibility 0.2s ease-in-out;
22-
z-index: 100;
23-
24-
${({
25-
theme: {
26-
colors: { neutral },
27-
spacing: { xsmall },
28-
baseFontSize,
29-
},
30-
}) => `
31-
background-color: ${neutral[700]};
32-
border-color: ${neutral[700]};
33-
color: ${neutral[100]};
34-
font-size: ${baseFontSize}px;
35-
padding: ${xsmall}px;
36-
`}
37-
38-
${({ placement }) => placementConfig.tipPosition[placement]};
39-
40-
&:before {
41-
content: '';
42-
position: absolute;
43-
${({ placement }) => placementConfig.arrowPosition[placement]};
44-
}
45-
`;
46-
47-
const TipText = styled.span`
48-
display: inline-block;
49-
max-width: ${({ multiline }) => (multiline ? 'unset' : '250px')};
50-
overflow: hidden;
51-
text-overflow: ellipsis;
52-
white-space: ${({ multiline }) => (multiline ? 'pre' : 'nowrap')};
53-
line-height: 20px;
54-
text-align: ${({ multiline }) => (multiline ? 'left' : 'unset')};
55-
`;
56-
57-
const Wrapper = styled.div`
58-
position: relative;
59-
float: left;
60-
clear: left;
61-
width: ${({ multiline }) => (multiline ? 'max-content' : 'unset')};
62-
`;
3+
import classNames from 'classnames';
4+
import styles from './Tooltip.module.css';
635

646
class Tooltip extends Component {
657
constructor(props) {
@@ -73,37 +15,43 @@ class Tooltip extends Component {
7315
render() {
7416
const {
7517
children,
18+
className,
7619
placement,
7720
text,
7821
visible: visibleProp,
79-
theme,
8022
multiline,
8123
...rest
8224
} = this.props;
8325
const { visible: visibleState } = this.state;
8426

27+
const wrapperClass = classNames(
28+
styles['tooltip-wrapper'],
29+
{ [styles['tooltip-wrapper-multiline']]: multiline },
30+
className,
31+
);
32+
const tipClass = classNames(styles.tip, styles[`tip-${placement}`], {
33+
[styles['tip-visible']]: visibleProp || visibleState,
34+
});
35+
const tipTextClass = classNames(styles['tip-text'], {
36+
[styles['tip-text-multiline']]: multiline,
37+
});
38+
8539
return (
86-
<Wrapper
40+
<div
8741
onMouseEnter={() => this.isVisible(true)}
8842
onMouseLeave={() => this.isVisible(false)}
89-
multiline={multiline}
43+
className={wrapperClass}
9044
{...rest}
9145
>
92-
<Tip
93-
placement={placement}
94-
visible={visibleProp || visibleState}
95-
theme={theme}
96-
>
97-
<TipText multiline={multiline}>{text}</TipText>
98-
</Tip>
46+
<div className={tipClass}>
47+
<span className={tipTextClass}>{text}</span>
48+
</div>
9949
{children}
100-
</Wrapper>
50+
</div>
10151
);
10252
}
10353
}
10454

105-
Tip.displayName = 'Tip';
106-
10755
Tooltip.propTypes = {
10856
/** Content the tooltip will show */
10957
text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
@@ -115,22 +63,12 @@ Tooltip.propTypes = {
11563
PropTypes.arrayOf(PropTypes.node),
11664
PropTypes.node,
11765
]).isRequired,
118-
theme: PropTypes.shape({
119-
spacing: PropTypes.object,
120-
colors: PropTypes.object,
121-
baseFontSize: PropTypes.number,
122-
}),
12366
};
12467

12568
Tooltip.defaultProps = {
12669
placement: 'top',
12770
visible: false,
12871
multiline: false,
129-
theme: {
130-
spacing,
131-
colors,
132-
baseFontSize: defaultBaseFontSize,
133-
},
13472
};
13573

13674
export default Tooltip;

components/Tooltip/Tooltip.module.css

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
:root {
2+
--tip-arrow-size: 6px;
3+
--tip-box-margin: 6px;
4+
--tip-percentage-x: 50%;
5+
--tip-percentage-y: 100%;
6+
--tip-position-distance: calc(var(--tip-percentage-y) + var(--tip-arrow-size) + var(--tip-box-margin));
7+
}
8+
9+
.tooltip-wrapper {
10+
position: relative;
11+
float: left;
12+
clear: left;
13+
width: unset;
14+
}
15+
16+
.tooltip-wrapper-multiline {
17+
width: max-content;
18+
}
19+
20+
.tip {
21+
border-radius: 4px;
22+
font-weight: bold;
23+
opacity: 0;
24+
visibility: hidden;
25+
position: absolute;
26+
line-height: 0;
27+
text-align: center;
28+
transition:
29+
opacity 0.2s ease-in-out,
30+
visibility 0.2s ease-in-out;
31+
z-index: 100;
32+
background-color: var(--qtm-colors-neutral-700);
33+
border-color: var(--qtm-colors-neutral-700);
34+
color: var(--qtm-colors-neutral-100);
35+
font-size: var(--qtm-base-font-size);
36+
padding: var(--qtm-spacing-xsmall);
37+
}
38+
39+
.tip-top {
40+
left: var(--tip-percentage-x);
41+
bottom: var(--tip-position-distance);
42+
transform: translateX(calc(var(--tip-percentage-x) * -1));
43+
}
44+
45+
.tip-bottom {
46+
left: var(--tip-percentage-x);
47+
top: var(--tip-position-distance);
48+
transform: translateX(calc(var(--tip-percentage-x) * -1));
49+
}
50+
51+
.tip-left {
52+
right: var(--tip-position-distance);
53+
top: var(--tip-percentage-x);
54+
transform: translateY(calc(var(--tip-percentage-x) * -1));
55+
}
56+
57+
.tip-right {
58+
left: var(--tip-position-distance);
59+
top: var(--tip-percentage-x);
60+
transform: translateY(calc(var(--tip-percentage-x) * -1));
61+
}
62+
63+
.tip:before {
64+
content: '';
65+
position: absolute;
66+
}
67+
68+
.tip-top:before {
69+
border-left: var(--tip-arrow-size) solid transparent;
70+
border-right: var(--tip-arrow-size) solid transparent;
71+
left: 50%;
72+
transform: translateX(-50%);
73+
74+
border-top: var(--tip-arrow-size) solid;
75+
border-top-color: inherit;
76+
bottom: calc((var(--tip-arrow-size) - 1px) * -1);
77+
}
78+
79+
.tip-bottom:before {
80+
border-left: var(--tip-arrow-size) solid transparent;
81+
border-right: var(--tip-arrow-size) solid transparent;
82+
left: 50%;
83+
transform: translateX(-50%);
84+
85+
border-bottom: var(--tip-arrow-size) solid;
86+
border-bottom-color: inherit;
87+
top: calc((var(--tip-arrow-size) - 1px) * -1);
88+
}
89+
90+
.tip-right:before {
91+
border-top: var(--tip-arrow-size) solid transparent;
92+
border-bottom: var(--tip-arrow-size) solid transparent;
93+
top: 50%;
94+
transform: translateY(-50%);
95+
96+
border-right: var(--tip-arrow-size) solid;
97+
border-right-color: inherit;
98+
left: calc((var(--tip-arrow-size) - 1px) * -1);
99+
}
100+
101+
.tip-left:before {
102+
border-top: var(--tip-arrow-size) solid transparent;
103+
border-bottom: var(--tip-arrow-size) solid transparent;
104+
top: 50%;
105+
transform: translateY(-50%);
106+
107+
border-left: var(--tip-arrow-size) solid;
108+
border-left-color: inherit;
109+
right: calc((var(--tip-arrow-size) - 1px) * -1);
110+
}
111+
112+
.tip-visible {
113+
opacity: 1;
114+
visibility: visible;
115+
}
116+
117+
.tip-text {
118+
display: inline-block;
119+
max-width: 250px;
120+
overflow: hidden;
121+
text-overflow: ellipsis;
122+
white-space: nowrap;
123+
line-height: 20px;
124+
text-align: unset;
125+
}
126+
127+
.tip-text-multiline {
128+
max-width: unset;
129+
white-space: pre;
130+
text-align: left;
131+
}

components/Tooltip/Tooltip.unit.test.jsx

+7-6
Original file line numberDiff line numberDiff line change
@@ -88,17 +88,18 @@ describe('Tooltip component ', () => {
8888
});
8989

9090
it('should toggle visibility when mouse enter and mouse leave', () => {
91+
const classContainedInClassList = (expression, classList) =>
92+
Array.from(classList).some((className) => expression.test(className));
93+
9194
render(<Tooltip text={TOOLTIP_TEXT}>{TOOLTIP_TRIGGER}</Tooltip>);
9295

9396
const tooltipTrigger = screen.getByText(TOOLTIP_TRIGGER);
94-
fireEvent.mouseEnter(tooltipTrigger);
97+
const tip = tooltipTrigger.firstChild;
9598

96-
const tip = screen.getByText(TOOLTIP_TEXT);
99+
expect(classContainedInClassList(/visible/, tip.classList)).toBe(false);
97100

98-
expect(tip.parentNode).toHaveStyleRule('visibility', 'visible');
99-
100-
fireEvent.mouseLeave(tooltipTrigger);
101+
fireEvent.mouseEnter(tooltipTrigger);
101102

102-
expect(tip.parentNode).toHaveStyleRule('visibility', 'hidden');
103+
expect(classContainedInClassList(/visible/, tip.classList)).toBe(true);
103104
});
104105
});

0 commit comments

Comments
 (0)