@ultraviolet/ui 1.6.0 → 1.7.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 +9 -5
- package/dist/src/components/DateInput/index.js +3 -1
- package/dist/src/components/Popover/index.js +11 -7
- package/dist/src/components/Tooltip/index.js +17 -259
- package/dist/src/internalComponents/Popup/index.js +278 -0
- package/package.json +2 -2
- /package/dist/src/{components/Tooltip → internalComponents/Popup}/helpers.js +0 -0
package/dist/index.d.ts
CHANGED
|
@@ -1717,9 +1717,9 @@ type PieChartProps = {
|
|
|
1717
1717
|
*/
|
|
1718
1718
|
declare const PieChart: ({ height, width, data, emptyLegend, content, withLegend, margin, chartProps, }: PieChartProps) => _emotion_react_jsx_runtime.JSX.Element;
|
|
1719
1719
|
|
|
1720
|
-
type
|
|
1720
|
+
type PopupPlacement = 'top' | 'right' | 'bottom' | 'left' | 'auto';
|
|
1721
1721
|
|
|
1722
|
-
type TooltipProps = {
|
|
1722
|
+
type TooltipProps$1 = {
|
|
1723
1723
|
/**
|
|
1724
1724
|
* Id is automatically generated if not set. It is used for associating tooltip wrapper with tooltip portal.
|
|
1725
1725
|
*/
|
|
@@ -1736,7 +1736,7 @@ type TooltipProps = {
|
|
|
1736
1736
|
/**
|
|
1737
1737
|
* `auto` placement will change the position of the tooltip if it doesn't fit in the viewport.
|
|
1738
1738
|
*/
|
|
1739
|
-
placement?:
|
|
1739
|
+
placement?: PopupPlacement;
|
|
1740
1740
|
/**
|
|
1741
1741
|
* Content of the tooltip, preferably text inside.
|
|
1742
1742
|
*/
|
|
@@ -1749,8 +1749,9 @@ type TooltipProps = {
|
|
|
1749
1749
|
innerRef?: Ref<HTMLDivElement | null>;
|
|
1750
1750
|
role?: string;
|
|
1751
1751
|
'data-testid'?: string;
|
|
1752
|
+
hasArrow?: boolean;
|
|
1752
1753
|
};
|
|
1753
|
-
declare const
|
|
1754
|
+
declare const Popup: react.ForwardRefExoticComponent<TooltipProps$1 & react.RefAttributes<HTMLDivElement>>;
|
|
1754
1755
|
|
|
1755
1756
|
type SentimentType = 'neutral' | 'primary';
|
|
1756
1757
|
declare const SIZES_WIDTH: {
|
|
@@ -1768,7 +1769,7 @@ type PopoverProps = {
|
|
|
1768
1769
|
onClose: () => void;
|
|
1769
1770
|
className?: string;
|
|
1770
1771
|
'data-testid'?: string;
|
|
1771
|
-
} & Pick<ComponentProps<typeof
|
|
1772
|
+
} & Pick<ComponentProps<typeof Popup>, 'placement'>;
|
|
1772
1773
|
declare const Popover: ({ visible, children, placement, content, title, sentiment, size, onClose, className, "data-testid": dataTestId, }: PopoverProps) => _emotion_react_jsx_runtime.JSX.Element;
|
|
1773
1774
|
|
|
1774
1775
|
declare const progressBarSentiments: readonly ["primary", "success", "warning", "info"];
|
|
@@ -2410,6 +2411,9 @@ type ToggleProps = {
|
|
|
2410
2411
|
};
|
|
2411
2412
|
declare const Toggle: react.ForwardRefExoticComponent<ToggleProps & react.RefAttributes<HTMLInputElement>>;
|
|
2412
2413
|
|
|
2414
|
+
type TooltipProps = Pick<ComponentProps<typeof Popup>, 'id' | 'children' | 'maxWidth' | 'placement' | 'text' | 'className' | 'visible' | 'innerRef' | 'role' | 'data-testid'>;
|
|
2415
|
+
declare const Tooltip: react.ForwardRefExoticComponent<TooltipProps & react.RefAttributes<HTMLDivElement>>;
|
|
2416
|
+
|
|
2413
2417
|
type VerificationCodeProps = {
|
|
2414
2418
|
disabled?: boolean;
|
|
2415
2419
|
error?: boolean;
|
|
@@ -163,6 +163,8 @@ const DateInput = _ref24 => {
|
|
|
163
163
|
className,
|
|
164
164
|
'data-testid': dataTestId
|
|
165
165
|
} = _ref24;
|
|
166
|
+
// Linked to: https://github.com/Hacker0x01/react-datepicker/issues/3834
|
|
167
|
+
const ReactDatePicker = DatePicker.default ?? DatePicker;
|
|
166
168
|
const localeCode = (typeof locale === 'string' ? locale : locale?.code) ?? 'en-GB';
|
|
167
169
|
if (typeof locale === 'object') {
|
|
168
170
|
registerLocale(localeCode, locale);
|
|
@@ -171,7 +173,7 @@ const DateInput = _ref24 => {
|
|
|
171
173
|
children: [jsx(Global, {
|
|
172
174
|
styles: css_248z
|
|
173
175
|
}), jsx(StyledWrapper, {
|
|
174
|
-
children: jsx(
|
|
176
|
+
children: jsx(ReactDatePicker, {
|
|
175
177
|
"data-testid": dataTestId,
|
|
176
178
|
className: className,
|
|
177
179
|
autoFocus: autoFocus,
|
|
@@ -3,15 +3,15 @@ import { useRef, useCallback, useEffect } from 'react';
|
|
|
3
3
|
import { Button } from '../Button/index.js';
|
|
4
4
|
import { Stack } from '../Stack/index.js';
|
|
5
5
|
import { Text } from '../Text/index.js';
|
|
6
|
-
import { Tooltip } from '../Tooltip/index.js';
|
|
7
6
|
import { jsx, jsxs } from '@emotion/react/jsx-runtime';
|
|
7
|
+
import { Popup } from '../../internalComponents/Popup/index.js';
|
|
8
8
|
|
|
9
9
|
const SIZES_WIDTH = {
|
|
10
10
|
small: 320,
|
|
11
11
|
medium: 420,
|
|
12
12
|
large: 520
|
|
13
13
|
};
|
|
14
|
-
const
|
|
14
|
+
const StyledPopup = /*#__PURE__*/_styled(Popup, {
|
|
15
15
|
shouldForwardProp: prop => !['sentiment', 'size'].includes(prop),
|
|
16
16
|
target: "ejpxv5a0"
|
|
17
17
|
})("padding:", _ref => {
|
|
@@ -38,16 +38,20 @@ const StyledTooltip = /*#__PURE__*/_styled(Tooltip, {
|
|
|
38
38
|
return `
|
|
39
39
|
background: ${theme.colors.neutral.background};
|
|
40
40
|
box-shadow: ${theme.shadows.popover};
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
&[data-has-arrow='true'] {
|
|
42
|
+
&::after {
|
|
43
|
+
border-color: ${theme.colors.neutral.background} transparent transparent transparent;
|
|
44
|
+
}
|
|
43
45
|
}
|
|
44
46
|
`;
|
|
45
47
|
}
|
|
46
48
|
return `
|
|
47
49
|
background: ${theme.colors.primary.backgroundStrong};
|
|
48
50
|
box-shadow: ${theme.shadows.popover};
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
&[data-has-arrow='true'] {
|
|
52
|
+
&::after {
|
|
53
|
+
border-color: ${theme.colors.primary.backgroundStrong} transparent transparent transparent;
|
|
54
|
+
}
|
|
51
55
|
}
|
|
52
56
|
`;
|
|
53
57
|
}, ";");
|
|
@@ -109,7 +113,7 @@ const Popover = _ref6 => {
|
|
|
109
113
|
document.removeEventListener('click', handleClickOutside, true);
|
|
110
114
|
};
|
|
111
115
|
}, [handleClickOutside]);
|
|
112
|
-
return jsx(
|
|
116
|
+
return jsx(StyledPopup, {
|
|
113
117
|
visible: visible,
|
|
114
118
|
placement: placement,
|
|
115
119
|
text: jsx(ContentWrapper, {
|
|
@@ -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.7.0",
|
|
4
4
|
"description": "Ultraviolet UI",
|
|
5
5
|
"homepage": "https://github.com/scaleway/ultraviolet#readme",
|
|
6
6
|
"repository": {
|
|
@@ -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
|