@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.
@@ -1,109 +1,8 @@
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';
1
+ import { forwardRef } from 'react';
2
+ import { jsx } from '@emotion/react/jsx-runtime';
3
+ import { Popup } from '../../internalComponents/Popup/index.js';
7
4
 
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
- 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
- } = _ref13;
119
- const childrenRef = useRef(null);
120
- useImperativeHandle(innerRef, () => childrenRef.current);
121
- const tempTooltipRef = useRef(null);
122
- const innerTooltipRef = tooltipRef || tempTooltipRef;
123
- const timer = useRef();
124
-
125
- // Debounce timer will be used to prevent the tooltip from flickering when the user moves the mouse out and in the children element.
126
- const debounceTimer = useRef();
127
- const [visibleInDom, setVisibleInDom] = useState(false);
128
- const [reverseAnimation, setReverseAnimation] = useState(false);
129
- const [positions, setPositions] = useState({
130
- ...DEFAULT_POSITIONS
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.6.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.8",
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.0"
70
+ "@ultraviolet/themes": "1.2.1"
71
71
  }
72
72
  }