@ultraviolet/ui 1.24.2 → 1.26.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 +48 -24
- package/dist/src/components/Banner/index.js +6 -12
- package/dist/src/components/MenuV2/index.js +6 -23
- package/dist/src/components/Modal/Dialog.js +3 -3
- package/dist/src/components/Popover/index.js +5 -20
- package/dist/src/components/Popup/animations.js +24 -0
- package/dist/src/components/Popup/helpers.js +72 -44
- package/dist/src/components/Popup/index.js +109 -85
- package/dist/src/components/Snippet/index.js +5 -4
- package/dist/src/components/Tabs/Tab.js +54 -21
- package/dist/src/components/Tabs/TabMenu.js +2 -0
- package/dist/src/components/Tabs/index.js +21 -6
- package/dist/src/components/Tooltip/index.js +3 -1
- package/package.json +5 -5
- package/dist/src/components/Banner/assets/intro-compact-left-pattern.svg.js +0 -5
- package/dist/src/components/Banner/assets/intro-compact-right-pattern.svg.js +0 -5
- package/dist/src/components/Banner/assets/intro-pattern.svg.js +0 -5
- package/dist/src/components/Banner/assets/promotion-compact-left-pattern.svg.js +0 -5
- package/dist/src/components/Banner/assets/promotion-compact-right-pattern.svg.js +0 -5
- package/dist/src/components/Banner/assets/promotion-pattern.svg.js +0 -5
|
@@ -1,57 +1,75 @@
|
|
|
1
1
|
const ARROW_WIDTH = 8; // in px
|
|
2
2
|
const SPACE = 4; // in px
|
|
3
|
-
const TOTAL_USED_SPACE =
|
|
3
|
+
const TOTAL_USED_SPACE = 0; // in px
|
|
4
4
|
const DEFAULT_POSITIONS = {
|
|
5
5
|
arrowLeft: -999,
|
|
6
6
|
arrowTop: -999,
|
|
7
7
|
arrowTransform: 'translate(-50%, -50)',
|
|
8
8
|
placement: 'top',
|
|
9
9
|
rotate: 135,
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
popupInitialPosition: 'translate3d(-999px, -999px, 0)',
|
|
11
|
+
popupPosition: 'translate3d(-999px, -999px, 0)'
|
|
12
12
|
};
|
|
13
13
|
/**
|
|
14
|
-
* This function will find the best placement in a window for
|
|
14
|
+
* This function will find the best placement in a window for popup based on children position and popup size
|
|
15
15
|
*/
|
|
16
16
|
const computePlacement = _ref => {
|
|
17
17
|
let {
|
|
18
18
|
childrenStructuredRef,
|
|
19
|
-
|
|
19
|
+
popupStructuredRef,
|
|
20
|
+
offsetParentRect,
|
|
21
|
+
popupPortalTarget
|
|
20
22
|
} = _ref;
|
|
21
23
|
const {
|
|
22
|
-
top:
|
|
23
|
-
left:
|
|
24
|
+
top: childrenTop,
|
|
25
|
+
left: childrenLeft,
|
|
24
26
|
right: childrenRight
|
|
25
27
|
} = childrenStructuredRef;
|
|
26
28
|
const {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
top: parentTop,
|
|
30
|
+
left: parentLeft,
|
|
31
|
+
right: parentRight
|
|
32
|
+
} = offsetParentRect;
|
|
33
|
+
const overloadedChildrenLeft = popupPortalTarget === document.body ? childrenLeft : childrenLeft - parentLeft;
|
|
34
|
+
const overloadedChildrenTop = popupPortalTarget === document.body ? childrenTop : childrenTop - parentTop;
|
|
35
|
+
const overloadedChildrenRight = popupPortalTarget === document.body ? childrenRight : childrenRight - parentRight;
|
|
36
|
+
const {
|
|
37
|
+
width: popupWidth,
|
|
38
|
+
height: popupHeight
|
|
39
|
+
} = popupStructuredRef;
|
|
40
|
+
if (overloadedChildrenTop - popupHeight - TOTAL_USED_SPACE < 0) {
|
|
31
41
|
return 'bottom';
|
|
32
42
|
}
|
|
33
|
-
if (
|
|
43
|
+
if (overloadedChildrenLeft - popupWidth - TOTAL_USED_SPACE < 0) {
|
|
34
44
|
return 'right';
|
|
35
45
|
}
|
|
36
|
-
if (
|
|
46
|
+
if (overloadedChildrenRight + popupWidth + TOTAL_USED_SPACE > window.innerWidth) {
|
|
37
47
|
return 'left';
|
|
38
48
|
}
|
|
39
49
|
return 'top';
|
|
40
50
|
};
|
|
41
51
|
/**
|
|
42
|
-
* This function will compute the positions of
|
|
52
|
+
* This function will compute the positions of popup and arrow based on children position and popup size
|
|
43
53
|
*/
|
|
44
54
|
const computePositions = _ref2 => {
|
|
45
55
|
let {
|
|
46
56
|
placement,
|
|
47
57
|
childrenRef,
|
|
48
|
-
|
|
58
|
+
popupRef,
|
|
59
|
+
popupPortalTarget
|
|
49
60
|
} = _ref2;
|
|
50
61
|
const childrenStructuredRef = childrenRef.current.getBoundingClientRect();
|
|
51
|
-
const
|
|
62
|
+
const offsetParentRect = childrenRef?.current?.offsetParent?.getBoundingClientRect() ?? {
|
|
63
|
+
top: 0,
|
|
64
|
+
left: 0,
|
|
65
|
+
right: 0
|
|
66
|
+
};
|
|
67
|
+
const popupStructuredRef = popupRef.current.getBoundingClientRect();
|
|
52
68
|
const placementBasedOnWindowSize = placement === 'auto' ? computePlacement({
|
|
53
69
|
childrenStructuredRef,
|
|
54
|
-
|
|
70
|
+
popupStructuredRef,
|
|
71
|
+
offsetParentRect: offsetParentRect,
|
|
72
|
+
popupPortalTarget
|
|
55
73
|
}) : placement;
|
|
56
74
|
const {
|
|
57
75
|
top: childrenTop,
|
|
@@ -61,68 +79,78 @@ const computePositions = _ref2 => {
|
|
|
61
79
|
height: childrenHeight
|
|
62
80
|
} = childrenStructuredRef;
|
|
63
81
|
const {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
82
|
+
top: parentTop,
|
|
83
|
+
left: parentLeft,
|
|
84
|
+
right: parentRight
|
|
85
|
+
} = offsetParentRect;
|
|
86
|
+
const {
|
|
87
|
+
width: popupWidth,
|
|
88
|
+
height: popupHeight
|
|
89
|
+
} = popupStructuredRef;
|
|
90
|
+
|
|
91
|
+
// It will get how much scroll is done on the page to compute the position of the popup
|
|
92
|
+
const scrollTopValue = popupPortalTarget === document.body ? document.documentElement.scrollTop : 0;
|
|
67
93
|
|
|
68
|
-
//
|
|
69
|
-
const
|
|
94
|
+
// We need to compute the position of the popup based on the parent element in the case the popup is not in the body
|
|
95
|
+
const overloadedChildrenLeft = popupPortalTarget === document.body ? childrenLeft : childrenLeft - parentLeft;
|
|
96
|
+
const overloadedChildrenTop = popupPortalTarget === document.body ? childrenTop : childrenTop - parentTop;
|
|
97
|
+
const overloadedChildrenRight = popupPortalTarget === document.body ? childrenRight : childrenRight + childrenWidth + ARROW_WIDTH + SPACE - parentRight / 2;
|
|
70
98
|
switch (placementBasedOnWindowSize) {
|
|
71
99
|
case 'bottom':
|
|
72
100
|
{
|
|
73
|
-
const positionX =
|
|
74
|
-
const positionY =
|
|
101
|
+
const positionX = overloadedChildrenLeft + childrenWidth / 2 - popupWidth / 2;
|
|
102
|
+
const positionY = overloadedChildrenTop + scrollTopValue + childrenHeight + ARROW_WIDTH + SPACE;
|
|
75
103
|
return {
|
|
76
|
-
arrowLeft:
|
|
104
|
+
arrowLeft: popupWidth / 2,
|
|
77
105
|
arrowTop: -ARROW_WIDTH - 5,
|
|
78
106
|
arrowTransform: '',
|
|
79
107
|
placement: 'bottom',
|
|
80
108
|
rotate: 180,
|
|
81
|
-
|
|
82
|
-
|
|
109
|
+
popupInitialPosition: `translate3d(${positionX}px, ${positionY - TOTAL_USED_SPACE}px, 0)`,
|
|
110
|
+
popupPosition: `translate3d(${positionX}px, ${positionY}px, 0)`
|
|
83
111
|
};
|
|
84
112
|
}
|
|
85
113
|
case 'left':
|
|
86
114
|
{
|
|
87
|
-
const positionX =
|
|
88
|
-
const positionY =
|
|
115
|
+
const positionX = overloadedChildrenLeft - popupWidth - ARROW_WIDTH - SPACE * 2;
|
|
116
|
+
const positionY = overloadedChildrenTop + scrollTopValue - popupHeight / 2 + childrenHeight / 2;
|
|
89
117
|
return {
|
|
90
|
-
arrowLeft:
|
|
91
|
-
arrowTop:
|
|
118
|
+
arrowLeft: popupWidth + ARROW_WIDTH + 5,
|
|
119
|
+
arrowTop: popupHeight / 2,
|
|
92
120
|
arrowTransform: 'translate(-50%, -50%)',
|
|
93
121
|
placement: 'left',
|
|
94
122
|
rotate: -90,
|
|
95
|
-
|
|
96
|
-
|
|
123
|
+
popupInitialPosition: `translate3d(${positionX + TOTAL_USED_SPACE}px, ${positionY}px, 0)`,
|
|
124
|
+
popupPosition: `translate3d(${positionX}px, ${positionY}px, 0)`
|
|
97
125
|
};
|
|
98
126
|
}
|
|
99
127
|
case 'right':
|
|
100
128
|
{
|
|
101
|
-
const positionX =
|
|
102
|
-
const positionY =
|
|
129
|
+
const positionX = overloadedChildrenRight + ARROW_WIDTH + SPACE * 2;
|
|
130
|
+
const positionY = overloadedChildrenTop + scrollTopValue - popupHeight / 2 + childrenHeight / 2;
|
|
103
131
|
return {
|
|
104
132
|
arrowLeft: -ARROW_WIDTH - 5,
|
|
105
|
-
arrowTop:
|
|
133
|
+
arrowTop: popupHeight / 2,
|
|
106
134
|
arrowTransform: 'translate(50%, -50%)',
|
|
107
135
|
placement: 'right',
|
|
108
136
|
rotate: 90,
|
|
109
|
-
|
|
110
|
-
|
|
137
|
+
popupInitialPosition: `translate3d(${positionX - TOTAL_USED_SPACE}px, ${positionY}px, 0)`,
|
|
138
|
+
popupPosition: `translate3d(${positionX}px, ${positionY}px, 0)`
|
|
111
139
|
};
|
|
112
140
|
}
|
|
113
141
|
default:
|
|
114
142
|
{
|
|
115
143
|
// top placement is default value
|
|
116
|
-
const positionX =
|
|
117
|
-
const positionY =
|
|
144
|
+
const positionX = overloadedChildrenLeft + childrenWidth / 2 - popupWidth / 2;
|
|
145
|
+
const positionY = overloadedChildrenTop + scrollTopValue - popupHeight - ARROW_WIDTH - SPACE;
|
|
118
146
|
return {
|
|
119
|
-
arrowLeft:
|
|
120
|
-
arrowTop:
|
|
147
|
+
arrowLeft: popupWidth / 2,
|
|
148
|
+
arrowTop: popupHeight - 1,
|
|
121
149
|
arrowTransform: '',
|
|
122
150
|
placement: 'top',
|
|
123
151
|
rotate: 0,
|
|
124
|
-
|
|
125
|
-
|
|
152
|
+
popupInitialPosition: `translate3d(${positionX}px, ${positionY + TOTAL_USED_SPACE}px, 0)`,
|
|
153
|
+
popupPosition: `translate3d(${positionX}px, ${positionY}px, 0)`
|
|
126
154
|
};
|
|
127
155
|
}
|
|
128
156
|
}
|
|
@@ -1,36 +1,18 @@
|
|
|
1
1
|
import _styled from '@emotion/styled/base';
|
|
2
|
-
import { css
|
|
3
|
-
import { forwardRef, useRef, useImperativeHandle, useState, useId, useCallback, useEffect } from 'react';
|
|
2
|
+
import { css } from '@emotion/react';
|
|
3
|
+
import { forwardRef, useRef, useImperativeHandle, useMemo, useState, useId, useCallback, useEffect } from 'react';
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
|
+
import { animation, exitAnimation } from './animations.js';
|
|
5
6
|
import { DEFAULT_POSITIONS, computePositions, ARROW_WIDTH } from './helpers.js';
|
|
6
7
|
import { jsx, Fragment, jsxs } from '@emotion/react/jsx-runtime';
|
|
7
8
|
|
|
8
9
|
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
10
|
const DEFAULT_ANIMATION_DURATION = 230; // in ms
|
|
10
|
-
const DEFAULT_DEBOUNCE_DURATION = 200;
|
|
11
|
+
const DEFAULT_DEBOUNCE_DURATION = 200; // in ms
|
|
12
|
+
|
|
11
13
|
function noop() {}
|
|
12
|
-
const
|
|
13
|
-
|
|
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', 'maxHeight', 'animationDuration'].includes(prop),
|
|
14
|
+
const StyledPopup = /*#__PURE__*/_styled('div', {
|
|
15
|
+
shouldForwardProp: prop => !['maxWidth', 'positions', 'reverseAnimation', 'maxHeight', 'animationDuration', 'isDialog'].includes(prop),
|
|
34
16
|
target: "e4h1g861"
|
|
35
17
|
})("background:", _ref => {
|
|
36
18
|
let {
|
|
@@ -67,11 +49,11 @@ const StyledTooltip = /*#__PURE__*/_styled('div', {
|
|
|
67
49
|
maxHeight
|
|
68
50
|
} = _ref7;
|
|
69
51
|
return maxHeight ? 'auto' : undefined;
|
|
70
|
-
}, ";overflow-wrap:break-word;font-size:0.8rem;inset:0 auto auto 0;top:0;left:0;transform:", _ref8 => {
|
|
52
|
+
}, ";overflow-wrap:break-word;font-size:0.8rem;inset:0 auto auto 0;top:0;left:0;z-index:1;transform:", _ref8 => {
|
|
71
53
|
let {
|
|
72
54
|
positions
|
|
73
55
|
} = _ref8;
|
|
74
|
-
return positions.
|
|
56
|
+
return positions.popupPosition;
|
|
75
57
|
}, ";animation:", _ref9 => {
|
|
76
58
|
let {
|
|
77
59
|
positions,
|
|
@@ -131,7 +113,7 @@ const Popup = /*#__PURE__*/forwardRef((_ref15, ref) => {
|
|
|
131
113
|
maxHeight,
|
|
132
114
|
visible,
|
|
133
115
|
innerRef,
|
|
134
|
-
role = '
|
|
116
|
+
role = 'popup',
|
|
135
117
|
'data-testid': dataTestId,
|
|
136
118
|
hasArrow = true,
|
|
137
119
|
onClose,
|
|
@@ -140,18 +122,34 @@ const Popup = /*#__PURE__*/forwardRef((_ref15, ref) => {
|
|
|
140
122
|
'aria-haspopup': ariaHasPopup,
|
|
141
123
|
hideOnClickOutside = false,
|
|
142
124
|
needDebounce = true,
|
|
143
|
-
disableAnimation = false
|
|
125
|
+
disableAnimation = false,
|
|
126
|
+
portalTarget
|
|
144
127
|
} = _ref15;
|
|
145
128
|
const childrenRef = useRef(null);
|
|
146
129
|
useImperativeHandle(innerRef, () => childrenRef.current);
|
|
147
|
-
const
|
|
148
|
-
useImperativeHandle(ref, () =>
|
|
130
|
+
const innerPopupRef = useRef(null);
|
|
131
|
+
useImperativeHandle(ref, () => innerPopupRef.current);
|
|
149
132
|
const timer = useRef();
|
|
133
|
+
const popupPortalTarget = useMemo(() => {
|
|
134
|
+
if (role === 'dialog') {
|
|
135
|
+
if (portalTarget) return portalTarget;
|
|
136
|
+
if (childrenRef.current) return childrenRef.current;
|
|
137
|
+
if (typeof window !== 'undefined') return document.body;
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// We check if window exists for SSR
|
|
142
|
+
if (typeof window !== 'undefined') {
|
|
143
|
+
return document.body;
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
147
|
+
}, [portalTarget, role, childrenRef.current]);
|
|
150
148
|
|
|
151
149
|
// There are some issue when mixing animation and maxHeight on some browsers, so we disable animation if maxHeight is set.
|
|
152
150
|
const animationDuration = disableAnimation || maxHeight ? 0 : DEFAULT_ANIMATION_DURATION;
|
|
153
151
|
|
|
154
|
-
// Debounce timer will be used to prevent the
|
|
152
|
+
// Debounce timer will be used to prevent the popup from flickering when the user moves the mouse out and in the children element.
|
|
155
153
|
const debounceTimer = useRef();
|
|
156
154
|
const [visibleInDom, setVisibleInDom] = useState(false);
|
|
157
155
|
const [reverseAnimation, setReverseAnimation] = useState(false);
|
|
@@ -161,70 +159,71 @@ const Popup = /*#__PURE__*/forwardRef((_ref15, ref) => {
|
|
|
161
159
|
const uniqueId = useId();
|
|
162
160
|
const generatedId = id ?? uniqueId;
|
|
163
161
|
const isControlled = visible !== undefined;
|
|
164
|
-
const
|
|
165
|
-
if (childrenRef.current &&
|
|
162
|
+
const generatePopupPositions = useCallback(() => {
|
|
163
|
+
if (childrenRef.current && innerPopupRef.current) {
|
|
166
164
|
setPositions(computePositions({
|
|
167
165
|
childrenRef,
|
|
168
166
|
placement,
|
|
169
|
-
|
|
167
|
+
popupRef: innerPopupRef,
|
|
168
|
+
popupPortalTarget: popupPortalTarget
|
|
170
169
|
}));
|
|
171
170
|
}
|
|
172
|
-
}, [
|
|
171
|
+
}, [placement, popupPortalTarget]);
|
|
173
172
|
|
|
174
173
|
/**
|
|
175
|
-
* This function is called when we need to recompute positions of
|
|
174
|
+
* This function is called when we need to recompute positions of popup due to window scroll or resize.
|
|
176
175
|
*/
|
|
177
176
|
const onWindowChangeDetected = useCallback(() => {
|
|
178
177
|
// We remove animation on scroll or the animation will restart on every scroll
|
|
179
|
-
if (
|
|
180
|
-
|
|
178
|
+
if (innerPopupRef.current) {
|
|
179
|
+
innerPopupRef.current.style.animation = 'none';
|
|
181
180
|
}
|
|
182
|
-
|
|
183
|
-
}, [
|
|
181
|
+
generatePopupPositions();
|
|
182
|
+
}, [generatePopupPositions, innerPopupRef]);
|
|
184
183
|
|
|
185
184
|
/**
|
|
186
|
-
* This function is called when we need to remove
|
|
185
|
+
* This function is called when we need to remove popup portal from DOM and remove event listener to it.
|
|
187
186
|
*/
|
|
188
|
-
const
|
|
187
|
+
const unmountPopupFromDom = useCallback(() => {
|
|
189
188
|
setVisibleInDom(false);
|
|
190
189
|
setReverseAnimation(false);
|
|
191
190
|
window.removeEventListener('scroll', onWindowChangeDetected, true);
|
|
192
191
|
}, [onWindowChangeDetected]);
|
|
193
192
|
|
|
194
193
|
/**
|
|
195
|
-
* This function is called when we need to hide
|
|
196
|
-
*
|
|
194
|
+
* This function is called when we need to hide popup. A timeout is set to allow animation end, then remove
|
|
195
|
+
* popup from dom.
|
|
197
196
|
*/
|
|
198
|
-
const
|
|
197
|
+
const closePopup = useCallback(() => {
|
|
199
198
|
debounceTimer.current = setTimeout(() => {
|
|
200
199
|
setReverseAnimation(true);
|
|
201
200
|
timer.current = setTimeout(() => {
|
|
202
|
-
|
|
201
|
+
unmountPopupFromDom();
|
|
203
202
|
onClose?.();
|
|
204
203
|
}, animationDuration);
|
|
205
204
|
}, needDebounce && !disableAnimation ? DEFAULT_DEBOUNCE_DURATION : 0);
|
|
206
|
-
}, [animationDuration, disableAnimation, needDebounce, onClose,
|
|
205
|
+
}, [animationDuration, disableAnimation, needDebounce, onClose, unmountPopupFromDom]);
|
|
207
206
|
|
|
208
207
|
/**
|
|
209
|
-
* When mouse hover or stop hovering children this function display or hide
|
|
210
|
-
* end, then remove
|
|
208
|
+
* When mouse hover or stop hovering children this function display or hide popup. A timeout is set to allow animation
|
|
209
|
+
* end, then remove popup from dom.
|
|
211
210
|
*/
|
|
212
211
|
const onPointerEvent = useCallback(isVisible => () => {
|
|
213
|
-
// This condition is for when we want to unmount the
|
|
214
|
-
// There is debounce in order to avoid
|
|
212
|
+
// This condition is for when we want to unmount the popup
|
|
213
|
+
// There is debounce in order to avoid popup to flicker when we move the mouse from children to popup
|
|
215
214
|
// Timer is used to follow the animation duration
|
|
216
|
-
if (!isVisible &&
|
|
217
|
-
|
|
215
|
+
if (!isVisible && innerPopupRef.current && !debounceTimer.current) {
|
|
216
|
+
closePopup();
|
|
218
217
|
} else if (isVisible) {
|
|
219
|
-
// This condition is for when we want to mount the
|
|
220
|
-
// If the timer exists it means the
|
|
221
|
-
// so we clear the timer and the
|
|
218
|
+
// This condition is for when we want to mount the popup
|
|
219
|
+
// If the timer exists it means the popup was about to umount, but we hovered the children again,
|
|
220
|
+
// so we clear the timer and the popup will not be unmounted
|
|
222
221
|
if (timer.current) {
|
|
223
222
|
setReverseAnimation(false);
|
|
224
223
|
clearTimeout(timer.current);
|
|
225
224
|
timer.current = undefined;
|
|
226
225
|
}
|
|
227
|
-
// And here is when we currently are in a debounce timer, it means
|
|
226
|
+
// And here is when we currently are in a debounce timer, it means popup was hovered during
|
|
228
227
|
// that period, and so we can clear debounce timer
|
|
229
228
|
if (debounceTimer.current) {
|
|
230
229
|
clearTimeout(debounceTimer.current);
|
|
@@ -232,20 +231,21 @@ const Popup = /*#__PURE__*/forwardRef((_ref15, ref) => {
|
|
|
232
231
|
}
|
|
233
232
|
setVisibleInDom(true);
|
|
234
233
|
}
|
|
235
|
-
}, [
|
|
234
|
+
}, [closePopup, innerPopupRef]);
|
|
236
235
|
|
|
237
236
|
/**
|
|
238
|
-
* Once
|
|
237
|
+
* Once popup is visible in the dom we can compute positions, then set it visible on screen and add event to
|
|
239
238
|
* recompute positions on scroll or screen resize.
|
|
240
239
|
*/
|
|
241
240
|
useEffect(() => {
|
|
242
241
|
if (visibleInDom) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
242
|
+
generatePopupPositions();
|
|
243
|
+
if (popupPortalTarget === document.body) {
|
|
244
|
+
// We want to detect scroll and resize in order to recompute positions of popup
|
|
245
|
+
// Adding true as third parameter to event listener will detect nested scrolls.
|
|
246
|
+
window.addEventListener('scroll', onWindowChangeDetected, true);
|
|
247
|
+
window.addEventListener('resize', onWindowChangeDetected, true);
|
|
248
|
+
}
|
|
249
249
|
}
|
|
250
250
|
return () => {
|
|
251
251
|
window.removeEventListener('scroll', onWindowChangeDetected, true);
|
|
@@ -255,22 +255,17 @@ const Popup = /*#__PURE__*/forwardRef((_ref15, ref) => {
|
|
|
255
255
|
timer.current = undefined;
|
|
256
256
|
}
|
|
257
257
|
};
|
|
258
|
-
}, [
|
|
258
|
+
}, [generatePopupPositions, onWindowChangeDetected, visibleInDom, maxWidth, popupPortalTarget]);
|
|
259
259
|
|
|
260
260
|
/**
|
|
261
|
-
* If
|
|
262
|
-
* In this cas we don't want to display
|
|
261
|
+
* If popup has `visible` prop it means the popup is manually controlled through this prop.
|
|
262
|
+
* In this cas we don't want to display popup on hover, but only when `visible` is true.
|
|
263
263
|
*/
|
|
264
264
|
useEffect(() => {
|
|
265
265
|
if (isControlled) {
|
|
266
266
|
onPointerEvent(visible)();
|
|
267
267
|
}
|
|
268
268
|
}, [isControlled, onPointerEvent, visible]);
|
|
269
|
-
const onLocalKeyDown = useCallback(event => {
|
|
270
|
-
if (event.code === 'Escape') {
|
|
271
|
-
unmountTooltip();
|
|
272
|
-
}
|
|
273
|
-
}, [unmountTooltip]);
|
|
274
269
|
|
|
275
270
|
// Handle hide on esc press and hide on click outside
|
|
276
271
|
useEffect(() => {
|
|
@@ -278,17 +273,17 @@ const Popup = /*#__PURE__*/forwardRef((_ref15, ref) => {
|
|
|
278
273
|
if (event.key === 'Escape') {
|
|
279
274
|
event.preventDefault();
|
|
280
275
|
event.stopPropagation();
|
|
281
|
-
|
|
276
|
+
closePopup();
|
|
282
277
|
}
|
|
283
278
|
};
|
|
284
279
|
const handleClickOutside = event => {
|
|
285
|
-
const
|
|
280
|
+
const popupCurrent = innerPopupRef.current;
|
|
286
281
|
const childrenCurrent = childrenRef.current;
|
|
287
|
-
if (
|
|
288
|
-
if (event.target && event.target !==
|
|
282
|
+
if (popupCurrent && hideOnClickOutside && !event.defaultPrevented) {
|
|
283
|
+
if (event.target && event.target !== popupCurrent && event.target !== childrenCurrent && !childrenCurrent?.contains(event.target) && !popupCurrent.contains(event.target)) {
|
|
289
284
|
event.preventDefault();
|
|
290
285
|
event.stopPropagation();
|
|
291
|
-
|
|
286
|
+
closePopup();
|
|
292
287
|
}
|
|
293
288
|
}
|
|
294
289
|
};
|
|
@@ -300,7 +295,35 @@ const Popup = /*#__PURE__*/forwardRef((_ref15, ref) => {
|
|
|
300
295
|
document.body.removeEventListener('keyup', handleEscPress);
|
|
301
296
|
document.body.removeEventListener('click', handleClickOutside);
|
|
302
297
|
};
|
|
303
|
-
}, [
|
|
298
|
+
}, [closePopup, visibleInDom, innerPopupRef, childrenRef, hideOnClickOutside]);
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* This event will occur only for dialog and will trap focus inside the dialog.
|
|
302
|
+
*/
|
|
303
|
+
const handleFocusTrap = useCallback(event => {
|
|
304
|
+
const isTabPressed = event.key === 'Tab';
|
|
305
|
+
if (!isTabPressed) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
event.stopPropagation();
|
|
309
|
+
const focusableEls = innerPopupRef.current?.querySelectorAll('a[href]:not([disabled]), button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled])') ?? [];
|
|
310
|
+
|
|
311
|
+
// Handle case when no interactive element are within the modal (including close icon)
|
|
312
|
+
if (focusableEls.length === 0) {
|
|
313
|
+
event.preventDefault();
|
|
314
|
+
}
|
|
315
|
+
const firstFocusableEl = focusableEls[0];
|
|
316
|
+
const lastFocusableEl = focusableEls[focusableEls.length - 1];
|
|
317
|
+
if (event.shiftKey) {
|
|
318
|
+
if (document.activeElement === firstFocusableEl || document.activeElement === innerPopupRef.current) {
|
|
319
|
+
lastFocusableEl.focus();
|
|
320
|
+
event.preventDefault();
|
|
321
|
+
}
|
|
322
|
+
} else if (document.activeElement === lastFocusableEl || document.activeElement === innerPopupRef.current) {
|
|
323
|
+
firstFocusableEl.focus();
|
|
324
|
+
event.preventDefault();
|
|
325
|
+
}
|
|
326
|
+
}, []);
|
|
304
327
|
|
|
305
328
|
/**
|
|
306
329
|
* Will render children conditionally if children is a function or not.
|
|
@@ -326,13 +349,12 @@ const Popup = /*#__PURE__*/forwardRef((_ref15, ref) => {
|
|
|
326
349
|
tabIndex: tabIndex,
|
|
327
350
|
onKeyDown: event => {
|
|
328
351
|
onKeyDown?.(event);
|
|
329
|
-
onLocalKeyDown(event);
|
|
330
352
|
},
|
|
331
353
|
"data-container-full-width": containerFullWidth,
|
|
332
354
|
"aria-haspopup": ariaHasPopup,
|
|
333
355
|
children: children
|
|
334
356
|
});
|
|
335
|
-
}, [ariaHasPopup, children, containerFullWidth, generatedId, isControlled, onKeyDown,
|
|
357
|
+
}, [ariaHasPopup, children, containerFullWidth, generatedId, isControlled, onKeyDown, onPointerEvent, tabIndex]);
|
|
336
358
|
if (!text) {
|
|
337
359
|
if (typeof children === 'function') return null;
|
|
338
360
|
return jsx(Fragment, {
|
|
@@ -347,8 +369,8 @@ const Popup = /*#__PURE__*/forwardRef((_ref15, ref) => {
|
|
|
347
369
|
event.nativeEvent.stopImmediatePropagation();
|
|
348
370
|
};
|
|
349
371
|
return jsxs(Fragment, {
|
|
350
|
-
children: [renderChildren(), visibleInDom ? /*#__PURE__*/createPortal(jsx(
|
|
351
|
-
ref:
|
|
372
|
+
children: [renderChildren(), visibleInDom ? /*#__PURE__*/createPortal(jsx(StyledPopup, {
|
|
373
|
+
ref: innerPopupRef,
|
|
352
374
|
positions: positions,
|
|
353
375
|
maxWidth: maxWidth,
|
|
354
376
|
maxHeight: maxHeight,
|
|
@@ -360,8 +382,10 @@ const Popup = /*#__PURE__*/forwardRef((_ref15, ref) => {
|
|
|
360
382
|
"data-has-arrow": hasArrow,
|
|
361
383
|
onClick: stopClickPropagation,
|
|
362
384
|
animationDuration: animationDuration,
|
|
385
|
+
onKeyDown: role === 'dialog' ? handleFocusTrap : undefined,
|
|
386
|
+
isDialog: role === 'dialog',
|
|
363
387
|
children: text
|
|
364
|
-
}),
|
|
388
|
+
}), popupPortalTarget) : null]
|
|
365
389
|
});
|
|
366
390
|
});
|
|
367
391
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import _styled from '@emotion/styled/base';
|
|
2
2
|
import { Icon } from '@ultraviolet/icons';
|
|
3
|
-
import {
|
|
3
|
+
import { useReducer, Children } from 'react';
|
|
4
4
|
import { CopyButton } from '../CopyButton/index.js';
|
|
5
5
|
import { Expandable } from '../Expandable/index.js';
|
|
6
6
|
import { Stack } from '../Stack/index.js';
|
|
@@ -196,9 +196,10 @@ const Snippet = _ref19 => {
|
|
|
196
196
|
hideText = 'Hide',
|
|
197
197
|
prefix,
|
|
198
198
|
className,
|
|
199
|
-
'data-testid': dataTestId
|
|
199
|
+
'data-testid': dataTestId,
|
|
200
|
+
initiallyExpanded
|
|
200
201
|
} = _ref19;
|
|
201
|
-
const [showMore, setShowMore] =
|
|
202
|
+
const [showMore, setShowMore] = useReducer(value => !value, initiallyExpanded ?? false);
|
|
202
203
|
const lines = children.split(LINES_BREAK_REGEX).filter(Boolean);
|
|
203
204
|
const numberOfLines = lines.length;
|
|
204
205
|
const multiline = numberOfLines > 1;
|
|
@@ -235,7 +236,7 @@ const Snippet = _ref19 => {
|
|
|
235
236
|
showMore: showMore,
|
|
236
237
|
children: jsx(StyledButton, {
|
|
237
238
|
type: "button",
|
|
238
|
-
onClick:
|
|
239
|
+
onClick: setShowMore,
|
|
239
240
|
"aria-expanded": showMore,
|
|
240
241
|
children: jsxs(AlignCenterText, {
|
|
241
242
|
as: "span",
|