@ultraviolet/ui 1.6.1 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +16 -10
- package/dist/src/components/Banner/assets/default-image-small.svg.js +5 -0
- package/dist/src/components/Banner/assets/default-image.svg.js +5 -0
- package/dist/src/components/Banner/assets/intro-compact-left-pattern.svg.js +5 -0
- package/dist/src/components/Banner/assets/intro-compact-right-pattern.svg.js +5 -0
- package/dist/src/components/Banner/assets/intro-pattern.svg.js +5 -0
- package/dist/src/components/Banner/assets/promotion-compact-left-pattern.svg.js +5 -0
- package/dist/src/components/Banner/assets/promotion-compact-right-pattern.svg.js +5 -0
- package/dist/src/components/Banner/assets/promotion-pattern.svg.js +5 -0
- package/dist/src/components/Banner/index.js +83 -22
- package/dist/src/components/Popover/index.js +11 -7
- package/dist/src/components/Radio/index.js +104 -143
- package/dist/src/components/SelectableCard/index.js +24 -4
- package/dist/src/components/Text/index.js +5 -1
- package/dist/src/components/Tooltip/index.js +17 -259
- package/dist/src/internalComponents/Popup/index.js +278 -0
- package/package.json +3 -3
- /package/dist/src/{components/Tooltip → internalComponents/Popup}/helpers.js +0 -0
|
@@ -1,109 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { createPortal } from 'react-dom';
|
|
5
|
-
import { DEFAULT_POSITIONS, computePositions, ARROW_WIDTH } from './helpers.js';
|
|
6
|
-
import { jsx, Fragment, jsxs } from '@emotion/react/jsx-runtime';
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import { jsx } from '@emotion/react/jsx-runtime';
|
|
3
|
+
import { Popup } from '../../internalComponents/Popup/index.js';
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
const ANIMATION_DURATION = 230; // in ms
|
|
10
|
-
|
|
11
|
-
function noop() {}
|
|
12
|
-
const animation = positions => keyframes`
|
|
13
|
-
0% {
|
|
14
|
-
opacity: 0;
|
|
15
|
-
transform: ${positions.tooltipInitialPosition};
|
|
16
|
-
}
|
|
17
|
-
100% {
|
|
18
|
-
opacity: 1;
|
|
19
|
-
transform: ${positions.tooltipPosition};
|
|
20
|
-
}
|
|
21
|
-
`;
|
|
22
|
-
const exitAnimation = positions => keyframes`
|
|
23
|
-
0% {
|
|
24
|
-
opacity: 1;
|
|
25
|
-
transform: ${positions.tooltipPosition};
|
|
26
|
-
}
|
|
27
|
-
100% {
|
|
28
|
-
opacity: 0;
|
|
29
|
-
transform: ${positions.tooltipInitialPosition};
|
|
30
|
-
}
|
|
31
|
-
`;
|
|
32
|
-
const StyledTooltip = /*#__PURE__*/_styled("div", {
|
|
33
|
-
target: "e1h4zly11"
|
|
34
|
-
})("background:", _ref => {
|
|
35
|
-
let {
|
|
36
|
-
theme
|
|
37
|
-
} = _ref;
|
|
38
|
-
return theme.colors.neutral.backgroundStronger;
|
|
39
|
-
}, ";color:", _ref2 => {
|
|
40
|
-
let {
|
|
41
|
-
theme
|
|
42
|
-
} = _ref2;
|
|
43
|
-
return theme.colors.neutral.textStronger;
|
|
44
|
-
}, ";border-radius:", _ref3 => {
|
|
45
|
-
let {
|
|
46
|
-
theme
|
|
47
|
-
} = _ref3;
|
|
48
|
-
return theme.radii.default;
|
|
49
|
-
}, ";padding:", _ref4 => {
|
|
50
|
-
let {
|
|
51
|
-
theme
|
|
52
|
-
} = _ref4;
|
|
53
|
-
return `${theme.space['0.5']} ${theme.space['1']}`;
|
|
54
|
-
}, ";text-align:center;position:absolute;max-width:", _ref5 => {
|
|
55
|
-
let {
|
|
56
|
-
maxWidth
|
|
57
|
-
} = _ref5;
|
|
58
|
-
return maxWidth;
|
|
59
|
-
}, "px;font-size:0.8rem;inset:0 auto auto 0;top:0;left:0;transform:", _ref6 => {
|
|
60
|
-
let {
|
|
61
|
-
positions
|
|
62
|
-
} = _ref6;
|
|
63
|
-
return positions.tooltipPosition;
|
|
64
|
-
}, ";animation:", _ref7 => {
|
|
65
|
-
let {
|
|
66
|
-
positions,
|
|
67
|
-
reverseAnimation
|
|
68
|
-
} = _ref7;
|
|
69
|
-
return /*#__PURE__*/css(ANIMATION_DURATION, "ms ", !reverseAnimation ? animation(positions) : exitAnimation(positions), " forwards;");
|
|
70
|
-
}, ";&::after{content:' ';position:absolute;top:", _ref8 => {
|
|
71
|
-
let {
|
|
72
|
-
positions
|
|
73
|
-
} = _ref8;
|
|
74
|
-
return positions.arrowTop;
|
|
75
|
-
}, "px;left:", _ref9 => {
|
|
76
|
-
let {
|
|
77
|
-
positions
|
|
78
|
-
} = _ref9;
|
|
79
|
-
return positions.arrowLeft;
|
|
80
|
-
}, "px;transform:", _ref10 => {
|
|
81
|
-
let {
|
|
82
|
-
positions
|
|
83
|
-
} = _ref10;
|
|
84
|
-
return positions.arrowTransform;
|
|
85
|
-
}, " rotate(", _ref11 => {
|
|
86
|
-
let {
|
|
87
|
-
positions
|
|
88
|
-
} = _ref11;
|
|
89
|
-
return positions.rotate;
|
|
90
|
-
}, "deg);margin-left:-", ARROW_WIDTH, "px;border-width:", ARROW_WIDTH, "px;border-style:solid;border-color:", _ref12 => {
|
|
91
|
-
let {
|
|
92
|
-
theme
|
|
93
|
-
} = _ref12;
|
|
94
|
-
return theme.colors.neutral.backgroundStronger;
|
|
95
|
-
}, " transparent transparent transparent;pointer-events:none;}");
|
|
96
|
-
const StyledChildrenContainer = /*#__PURE__*/_styled("div", {
|
|
97
|
-
target: "e1h4zly10"
|
|
98
|
-
})(process.env.NODE_ENV === "production" ? {
|
|
99
|
-
name: "ck88l6",
|
|
100
|
-
styles: "display:inherit"
|
|
101
|
-
} : {
|
|
102
|
-
name: "ck88l6",
|
|
103
|
-
styles: "display:inherit",
|
|
104
|
-
toString: _EMOTION_STRINGIFIED_CSS_ERROR__
|
|
105
|
-
});
|
|
106
|
-
const Tooltip = /*#__PURE__*/forwardRef((_ref13, tooltipRef) => {
|
|
5
|
+
const Tooltip = /*#__PURE__*/forwardRef((_ref, tooltipRef) => {
|
|
107
6
|
let {
|
|
108
7
|
children,
|
|
109
8
|
text = '',
|
|
@@ -115,160 +14,19 @@ const Tooltip = /*#__PURE__*/forwardRef((_ref13, tooltipRef) => {
|
|
|
115
14
|
innerRef,
|
|
116
15
|
role = 'tooltip',
|
|
117
16
|
'data-testid': dataTestId
|
|
118
|
-
} =
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
});
|
|
132
|
-
const uniqueId = useId();
|
|
133
|
-
const generatedId = id ?? uniqueId;
|
|
134
|
-
const isControlled = visible !== undefined;
|
|
135
|
-
const generatePositions = useCallback(() => {
|
|
136
|
-
if (childrenRef.current && innerTooltipRef.current) {
|
|
137
|
-
setPositions(computePositions({
|
|
138
|
-
childrenRef,
|
|
139
|
-
placement,
|
|
140
|
-
tooltipRef: innerTooltipRef
|
|
141
|
-
}));
|
|
142
|
-
}
|
|
143
|
-
}, [innerTooltipRef, placement]);
|
|
144
|
-
const onScrollDetected = useCallback(() => {
|
|
145
|
-
// We remove animation on scroll or the animation will restart on every scroll
|
|
146
|
-
if (innerTooltipRef.current) {
|
|
147
|
-
innerTooltipRef.current.style.animation = 'none';
|
|
148
|
-
}
|
|
149
|
-
generatePositions();
|
|
150
|
-
}, [generatePositions, innerTooltipRef]);
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* This function is called when we need to remove tooltip portal from DOM and remove event listener to it.
|
|
154
|
-
*/
|
|
155
|
-
const unmountTooltip = useCallback(() => {
|
|
156
|
-
setVisibleInDom(false);
|
|
157
|
-
setReverseAnimation(false);
|
|
158
|
-
window.removeEventListener('scroll', onScrollDetected, true);
|
|
159
|
-
}, [onScrollDetected]);
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* When mouse hover or stop hovering children this function display or hide tooltip. A timeout is set to allow animation
|
|
163
|
-
* end, then remove tooltip from dom.
|
|
164
|
-
*/
|
|
165
|
-
const onPointerEvent = useCallback(isVisible => () => {
|
|
166
|
-
// This condition is for when we want to unmount the tooltip
|
|
167
|
-
// There is debounce in order to avoid tooltip to flicker when we move the mouse from children to tooltip
|
|
168
|
-
// Timer is used to follow the animation duration
|
|
169
|
-
if (!isVisible && innerTooltipRef.current && !debounceTimer.current) {
|
|
170
|
-
debounceTimer.current = setTimeout(() => {
|
|
171
|
-
setReverseAnimation(true);
|
|
172
|
-
timer.current = setTimeout(() => unmountTooltip(), ANIMATION_DURATION);
|
|
173
|
-
}, 200);
|
|
174
|
-
} else if (isVisible) {
|
|
175
|
-
// This condition is for when we want to mount the tooltip
|
|
176
|
-
// If the timer exists it means the tooltip was about to umount, but we hovered the children again,
|
|
177
|
-
// so we clear the timer and the tooltip will not be unmounted
|
|
178
|
-
if (timer.current) {
|
|
179
|
-
setReverseAnimation(false);
|
|
180
|
-
clearTimeout(timer.current);
|
|
181
|
-
timer.current = undefined;
|
|
182
|
-
}
|
|
183
|
-
// And here is when we currently are in a debounce timer, it means tooltip was hovered during
|
|
184
|
-
// that period, and so we can clear debounce timer
|
|
185
|
-
if (debounceTimer.current) {
|
|
186
|
-
clearTimeout(debounceTimer.current);
|
|
187
|
-
debounceTimer.current = undefined;
|
|
188
|
-
}
|
|
189
|
-
setVisibleInDom(true);
|
|
190
|
-
}
|
|
191
|
-
}, [innerTooltipRef, unmountTooltip]);
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Once tooltip is visible in the dom we can compute positions, then set it visible on screen and add event to
|
|
195
|
-
* recompute positions on scroll or screen resize.
|
|
196
|
-
*/
|
|
197
|
-
useEffect(() => {
|
|
198
|
-
if (visibleInDom) {
|
|
199
|
-
generatePositions();
|
|
200
|
-
|
|
201
|
-
// We want to detect scroll in order to recompute positions of tooltip
|
|
202
|
-
// Adding true as third parameter to event listener will detect nested scrolls.
|
|
203
|
-
window.addEventListener('scroll', onScrollDetected, true);
|
|
204
|
-
}
|
|
205
|
-
return () => {
|
|
206
|
-
window.removeEventListener('scroll', onScrollDetected, true);
|
|
207
|
-
if (timer.current) {
|
|
208
|
-
clearTimeout(timer.current);
|
|
209
|
-
timer.current = undefined;
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
}, [generatePositions, onScrollDetected, visibleInDom]);
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* If tooltip has `visible` prop it means the tooltip is manually controlled through this prop.
|
|
216
|
-
* In this cas we don't want to display tooltip on hover, but only when `visible` is true.
|
|
217
|
-
*/
|
|
218
|
-
useEffect(() => {
|
|
219
|
-
if (isControlled) {
|
|
220
|
-
onPointerEvent(visible)();
|
|
221
|
-
}
|
|
222
|
-
}, [isControlled, onPointerEvent, visible]);
|
|
223
|
-
const onKeyDown = useCallback(event => {
|
|
224
|
-
if (event.code === 'Escape') {
|
|
225
|
-
unmountTooltip();
|
|
226
|
-
}
|
|
227
|
-
}, [unmountTooltip]);
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Will render children conditionally if children is a function or not.
|
|
231
|
-
*/
|
|
232
|
-
const renderChildren = useCallback(() => {
|
|
233
|
-
if (typeof children === 'function') {
|
|
234
|
-
return children({
|
|
235
|
-
onBlur: !isControlled ? onPointerEvent(false) : noop,
|
|
236
|
-
onFocus: !isControlled ? onPointerEvent(true) : noop,
|
|
237
|
-
onPointerEnter: !isControlled ? onPointerEvent(true) : noop,
|
|
238
|
-
onPointerLeave: !isControlled ? onPointerEvent(false) : noop,
|
|
239
|
-
ref: childrenRef
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
return jsx(StyledChildrenContainer, {
|
|
243
|
-
"aria-describedby": generatedId,
|
|
244
|
-
onBlur: !isControlled ? onPointerEvent(false) : noop,
|
|
245
|
-
onFocus: !isControlled ? onPointerEvent(true) : noop,
|
|
246
|
-
onPointerEnter: !isControlled ? onPointerEvent(true) : noop,
|
|
247
|
-
onPointerLeave: !isControlled ? onPointerEvent(false) : noop,
|
|
248
|
-
ref: childrenRef,
|
|
249
|
-
tabIndex: 0,
|
|
250
|
-
onKeyDown: onKeyDown,
|
|
251
|
-
children: children
|
|
252
|
-
});
|
|
253
|
-
}, [children, generatedId, isControlled, onKeyDown, onPointerEvent]);
|
|
254
|
-
if (!text) {
|
|
255
|
-
if (typeof children === 'function') return null;
|
|
256
|
-
return jsx(Fragment, {
|
|
257
|
-
children: children
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
return jsxs(Fragment, {
|
|
261
|
-
children: [renderChildren(), visibleInDom ? /*#__PURE__*/createPortal(jsx(StyledTooltip, {
|
|
262
|
-
ref: innerTooltipRef,
|
|
263
|
-
positions: positions,
|
|
264
|
-
maxWidth: maxWidth,
|
|
265
|
-
role: role,
|
|
266
|
-
id: generatedId,
|
|
267
|
-
className: className,
|
|
268
|
-
reverseAnimation: reverseAnimation,
|
|
269
|
-
"data-testid": dataTestId,
|
|
270
|
-
children: text
|
|
271
|
-
}), document.body) : null]
|
|
17
|
+
} = _ref;
|
|
18
|
+
return jsx(Popup, {
|
|
19
|
+
id: id,
|
|
20
|
+
ref: tooltipRef,
|
|
21
|
+
role: role,
|
|
22
|
+
"data-testid": dataTestId,
|
|
23
|
+
className: className,
|
|
24
|
+
maxWidth: maxWidth,
|
|
25
|
+
visible: visible,
|
|
26
|
+
placement: placement,
|
|
27
|
+
text: text,
|
|
28
|
+
innerRef: innerRef,
|
|
29
|
+
children: children
|
|
272
30
|
});
|
|
273
31
|
});
|
|
274
32
|
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import _styled from '@emotion/styled/base';
|
|
2
|
+
import { css, keyframes } from '@emotion/react';
|
|
3
|
+
import { forwardRef, useRef, useImperativeHandle, useState, useId, useCallback, useEffect } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
5
|
+
import { DEFAULT_POSITIONS, computePositions, ARROW_WIDTH } from './helpers.js';
|
|
6
|
+
import { jsx, Fragment, jsxs } from '@emotion/react/jsx-runtime';
|
|
7
|
+
|
|
8
|
+
function _EMOTION_STRINGIFIED_CSS_ERROR__() { return "You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop)."; }
|
|
9
|
+
const ANIMATION_DURATION = 230; // in ms
|
|
10
|
+
|
|
11
|
+
function noop() {}
|
|
12
|
+
const animation = positions => keyframes`
|
|
13
|
+
0% {
|
|
14
|
+
opacity: 0;
|
|
15
|
+
transform: ${positions.tooltipInitialPosition};
|
|
16
|
+
}
|
|
17
|
+
100% {
|
|
18
|
+
opacity: 1;
|
|
19
|
+
transform: ${positions.tooltipPosition};
|
|
20
|
+
}
|
|
21
|
+
`;
|
|
22
|
+
const exitAnimation = positions => keyframes`
|
|
23
|
+
0% {
|
|
24
|
+
opacity: 1;
|
|
25
|
+
transform: ${positions.tooltipPosition};
|
|
26
|
+
}
|
|
27
|
+
100% {
|
|
28
|
+
opacity: 0;
|
|
29
|
+
transform: ${positions.tooltipInitialPosition};
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
32
|
+
const StyledTooltip = /*#__PURE__*/_styled('div', {
|
|
33
|
+
shouldForwardProp: prop => !['maxWidth', 'positions', 'reverseAnimation'].includes(prop),
|
|
34
|
+
target: "efkq4701"
|
|
35
|
+
})("background:", _ref => {
|
|
36
|
+
let {
|
|
37
|
+
theme
|
|
38
|
+
} = _ref;
|
|
39
|
+
return theme.colors.neutral.backgroundStronger;
|
|
40
|
+
}, ";color:", _ref2 => {
|
|
41
|
+
let {
|
|
42
|
+
theme
|
|
43
|
+
} = _ref2;
|
|
44
|
+
return theme.colors.neutral.textStronger;
|
|
45
|
+
}, ";border-radius:", _ref3 => {
|
|
46
|
+
let {
|
|
47
|
+
theme
|
|
48
|
+
} = _ref3;
|
|
49
|
+
return theme.radii.default;
|
|
50
|
+
}, ";padding:", _ref4 => {
|
|
51
|
+
let {
|
|
52
|
+
theme
|
|
53
|
+
} = _ref4;
|
|
54
|
+
return `${theme.space['0.5']} ${theme.space['1']}`;
|
|
55
|
+
}, ";text-align:center;position:absolute;max-width:", _ref5 => {
|
|
56
|
+
let {
|
|
57
|
+
maxWidth
|
|
58
|
+
} = _ref5;
|
|
59
|
+
return maxWidth;
|
|
60
|
+
}, "px;font-size:0.8rem;inset:0 auto auto 0;top:0;left:0;transform:", _ref6 => {
|
|
61
|
+
let {
|
|
62
|
+
positions
|
|
63
|
+
} = _ref6;
|
|
64
|
+
return positions.tooltipPosition;
|
|
65
|
+
}, ";animation:", _ref7 => {
|
|
66
|
+
let {
|
|
67
|
+
positions,
|
|
68
|
+
reverseAnimation
|
|
69
|
+
} = _ref7;
|
|
70
|
+
return /*#__PURE__*/css(ANIMATION_DURATION, "ms ", !reverseAnimation ? animation(positions) : exitAnimation(positions), " forwards;");
|
|
71
|
+
}, ";&[data-has-arrow='true']{&::after{content:' ';position:absolute;top:", _ref8 => {
|
|
72
|
+
let {
|
|
73
|
+
positions
|
|
74
|
+
} = _ref8;
|
|
75
|
+
return positions.arrowTop;
|
|
76
|
+
}, "px;left:", _ref9 => {
|
|
77
|
+
let {
|
|
78
|
+
positions
|
|
79
|
+
} = _ref9;
|
|
80
|
+
return positions.arrowLeft;
|
|
81
|
+
}, "px;transform:", _ref10 => {
|
|
82
|
+
let {
|
|
83
|
+
positions
|
|
84
|
+
} = _ref10;
|
|
85
|
+
return positions.arrowTransform;
|
|
86
|
+
}, " rotate(", _ref11 => {
|
|
87
|
+
let {
|
|
88
|
+
positions
|
|
89
|
+
} = _ref11;
|
|
90
|
+
return positions.rotate;
|
|
91
|
+
}, "deg);margin-left:-", ARROW_WIDTH, "px;border-width:", ARROW_WIDTH, "px;border-style:solid;border-color:", _ref12 => {
|
|
92
|
+
let {
|
|
93
|
+
theme
|
|
94
|
+
} = _ref12;
|
|
95
|
+
return theme.colors.neutral.backgroundStronger;
|
|
96
|
+
}, " transparent transparent transparent;pointer-events:none;}}");
|
|
97
|
+
const StyledChildrenContainer = /*#__PURE__*/_styled("div", {
|
|
98
|
+
target: "efkq4700"
|
|
99
|
+
})(process.env.NODE_ENV === "production" ? {
|
|
100
|
+
name: "ck88l6",
|
|
101
|
+
styles: "display:inherit"
|
|
102
|
+
} : {
|
|
103
|
+
name: "ck88l6",
|
|
104
|
+
styles: "display:inherit",
|
|
105
|
+
toString: _EMOTION_STRINGIFIED_CSS_ERROR__
|
|
106
|
+
});
|
|
107
|
+
const Popup = /*#__PURE__*/forwardRef((_ref13, tooltipRef) => {
|
|
108
|
+
let {
|
|
109
|
+
children,
|
|
110
|
+
text = '',
|
|
111
|
+
placement = 'auto',
|
|
112
|
+
id,
|
|
113
|
+
className,
|
|
114
|
+
maxWidth = 232,
|
|
115
|
+
visible,
|
|
116
|
+
innerRef,
|
|
117
|
+
role = 'tooltip',
|
|
118
|
+
'data-testid': dataTestId,
|
|
119
|
+
hasArrow = true
|
|
120
|
+
} = _ref13;
|
|
121
|
+
const childrenRef = useRef(null);
|
|
122
|
+
useImperativeHandle(innerRef, () => childrenRef.current);
|
|
123
|
+
const tempTooltipRef = useRef(null);
|
|
124
|
+
const innerTooltipRef = tooltipRef || tempTooltipRef;
|
|
125
|
+
const timer = useRef();
|
|
126
|
+
|
|
127
|
+
// Debounce timer will be used to prevent the tooltip from flickering when the user moves the mouse out and in the children element.
|
|
128
|
+
const debounceTimer = useRef();
|
|
129
|
+
const [visibleInDom, setVisibleInDom] = useState(false);
|
|
130
|
+
const [reverseAnimation, setReverseAnimation] = useState(false);
|
|
131
|
+
const [positions, setPositions] = useState({
|
|
132
|
+
...DEFAULT_POSITIONS
|
|
133
|
+
});
|
|
134
|
+
const uniqueId = useId();
|
|
135
|
+
const generatedId = id ?? uniqueId;
|
|
136
|
+
const isControlled = visible !== undefined;
|
|
137
|
+
const generatePositions = useCallback(() => {
|
|
138
|
+
if (childrenRef.current && innerTooltipRef.current) {
|
|
139
|
+
setPositions(computePositions({
|
|
140
|
+
childrenRef,
|
|
141
|
+
placement,
|
|
142
|
+
tooltipRef: innerTooltipRef
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
}, [innerTooltipRef, placement]);
|
|
146
|
+
const onScrollDetected = useCallback(() => {
|
|
147
|
+
// We remove animation on scroll or the animation will restart on every scroll
|
|
148
|
+
if (innerTooltipRef.current) {
|
|
149
|
+
innerTooltipRef.current.style.animation = 'none';
|
|
150
|
+
}
|
|
151
|
+
generatePositions();
|
|
152
|
+
}, [generatePositions, innerTooltipRef]);
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* This function is called when we need to remove tooltip portal from DOM and remove event listener to it.
|
|
156
|
+
*/
|
|
157
|
+
const unmountTooltip = useCallback(() => {
|
|
158
|
+
setVisibleInDom(false);
|
|
159
|
+
setReverseAnimation(false);
|
|
160
|
+
window.removeEventListener('scroll', onScrollDetected, true);
|
|
161
|
+
}, [onScrollDetected]);
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* When mouse hover or stop hovering children this function display or hide tooltip. A timeout is set to allow animation
|
|
165
|
+
* end, then remove tooltip from dom.
|
|
166
|
+
*/
|
|
167
|
+
const onPointerEvent = useCallback(isVisible => () => {
|
|
168
|
+
// This condition is for when we want to unmount the tooltip
|
|
169
|
+
// There is debounce in order to avoid tooltip to flicker when we move the mouse from children to tooltip
|
|
170
|
+
// Timer is used to follow the animation duration
|
|
171
|
+
if (!isVisible && innerTooltipRef.current && !debounceTimer.current) {
|
|
172
|
+
debounceTimer.current = setTimeout(() => {
|
|
173
|
+
setReverseAnimation(true);
|
|
174
|
+
timer.current = setTimeout(() => unmountTooltip(), ANIMATION_DURATION);
|
|
175
|
+
}, 200);
|
|
176
|
+
} else if (isVisible) {
|
|
177
|
+
// This condition is for when we want to mount the tooltip
|
|
178
|
+
// If the timer exists it means the tooltip was about to umount, but we hovered the children again,
|
|
179
|
+
// so we clear the timer and the tooltip will not be unmounted
|
|
180
|
+
if (timer.current) {
|
|
181
|
+
setReverseAnimation(false);
|
|
182
|
+
clearTimeout(timer.current);
|
|
183
|
+
timer.current = undefined;
|
|
184
|
+
}
|
|
185
|
+
// And here is when we currently are in a debounce timer, it means tooltip was hovered during
|
|
186
|
+
// that period, and so we can clear debounce timer
|
|
187
|
+
if (debounceTimer.current) {
|
|
188
|
+
clearTimeout(debounceTimer.current);
|
|
189
|
+
debounceTimer.current = undefined;
|
|
190
|
+
}
|
|
191
|
+
setVisibleInDom(true);
|
|
192
|
+
}
|
|
193
|
+
}, [innerTooltipRef, unmountTooltip]);
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Once tooltip is visible in the dom we can compute positions, then set it visible on screen and add event to
|
|
197
|
+
* recompute positions on scroll or screen resize.
|
|
198
|
+
*/
|
|
199
|
+
useEffect(() => {
|
|
200
|
+
if (visibleInDom) {
|
|
201
|
+
generatePositions();
|
|
202
|
+
|
|
203
|
+
// We want to detect scroll in order to recompute positions of tooltip
|
|
204
|
+
// Adding true as third parameter to event listener will detect nested scrolls.
|
|
205
|
+
window.addEventListener('scroll', onScrollDetected, true);
|
|
206
|
+
}
|
|
207
|
+
return () => {
|
|
208
|
+
window.removeEventListener('scroll', onScrollDetected, true);
|
|
209
|
+
if (timer.current) {
|
|
210
|
+
clearTimeout(timer.current);
|
|
211
|
+
timer.current = undefined;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
}, [generatePositions, onScrollDetected, visibleInDom]);
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* If tooltip has `visible` prop it means the tooltip is manually controlled through this prop.
|
|
218
|
+
* In this cas we don't want to display tooltip on hover, but only when `visible` is true.
|
|
219
|
+
*/
|
|
220
|
+
useEffect(() => {
|
|
221
|
+
if (isControlled) {
|
|
222
|
+
onPointerEvent(visible)();
|
|
223
|
+
}
|
|
224
|
+
}, [isControlled, onPointerEvent, visible]);
|
|
225
|
+
const onKeyDown = useCallback(event => {
|
|
226
|
+
if (event.code === 'Escape') {
|
|
227
|
+
unmountTooltip();
|
|
228
|
+
}
|
|
229
|
+
}, [unmountTooltip]);
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Will render children conditionally if children is a function or not.
|
|
233
|
+
*/
|
|
234
|
+
const renderChildren = useCallback(() => {
|
|
235
|
+
if (typeof children === 'function') {
|
|
236
|
+
return children({
|
|
237
|
+
onBlur: !isControlled ? onPointerEvent(false) : noop,
|
|
238
|
+
onFocus: !isControlled ? onPointerEvent(true) : noop,
|
|
239
|
+
onPointerEnter: !isControlled ? onPointerEvent(true) : noop,
|
|
240
|
+
onPointerLeave: !isControlled ? onPointerEvent(false) : noop,
|
|
241
|
+
ref: childrenRef
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
return jsx(StyledChildrenContainer, {
|
|
245
|
+
"aria-describedby": generatedId,
|
|
246
|
+
onBlur: !isControlled ? onPointerEvent(false) : noop,
|
|
247
|
+
onFocus: !isControlled ? onPointerEvent(true) : noop,
|
|
248
|
+
onPointerEnter: !isControlled ? onPointerEvent(true) : noop,
|
|
249
|
+
onPointerLeave: !isControlled ? onPointerEvent(false) : noop,
|
|
250
|
+
ref: childrenRef,
|
|
251
|
+
tabIndex: 0,
|
|
252
|
+
onKeyDown: onKeyDown,
|
|
253
|
+
children: children
|
|
254
|
+
});
|
|
255
|
+
}, [children, generatedId, isControlled, onKeyDown, onPointerEvent]);
|
|
256
|
+
if (!text) {
|
|
257
|
+
if (typeof children === 'function') return null;
|
|
258
|
+
return jsx(Fragment, {
|
|
259
|
+
children: children
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
return jsxs(Fragment, {
|
|
263
|
+
children: [renderChildren(), visibleInDom ? /*#__PURE__*/createPortal(jsx(StyledTooltip, {
|
|
264
|
+
ref: innerTooltipRef,
|
|
265
|
+
positions: positions,
|
|
266
|
+
maxWidth: maxWidth,
|
|
267
|
+
role: role,
|
|
268
|
+
id: generatedId,
|
|
269
|
+
className: className,
|
|
270
|
+
reverseAnimation: reverseAnimation,
|
|
271
|
+
"data-testid": dataTestId,
|
|
272
|
+
"data-has-arrow": hasArrow,
|
|
273
|
+
children: text
|
|
274
|
+
}), document.body) : null]
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
export { Popup };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultraviolet/ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Ultraviolet UI",
|
|
5
5
|
"homepage": "https://github.com/scaleway/ultraviolet#readme",
|
|
6
6
|
"repository": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"react-dom": "18.2.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@babel/core": "7.22.
|
|
42
|
+
"@babel/core": "7.22.9",
|
|
43
43
|
"@emotion/babel-plugin": "11.11.0",
|
|
44
44
|
"@emotion/react": "11.11.1",
|
|
45
45
|
"@emotion/styled": "11.11.0",
|
|
@@ -67,6 +67,6 @@
|
|
|
67
67
|
"react-toastify": "9.1.3",
|
|
68
68
|
"react-use-clipboard": "1.0.9",
|
|
69
69
|
"reakit": "1.3.11",
|
|
70
|
-
"@ultraviolet/themes": "1.2.
|
|
70
|
+
"@ultraviolet/themes": "1.2.1"
|
|
71
71
|
}
|
|
72
72
|
}
|
|
File without changes
|