@skyscanner/backpack-web 42.5.0 → 42.5.1
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/bpk-component-bottom-sheet/src/BpkBottomSheet.module.css +1 -1
- package/bpk-component-modal/src/BpkModalV3/BpkModalV3Root/BpkModalV3Root.js +1 -2
- package/bpk-react-utils/index.d.ts +3 -1
- package/bpk-react-utils/index.js +4 -2
- package/bpk-react-utils/src/BpkDialogWrapper/BpkDialogWrapper.js +2 -21
- package/{bpk-component-modal/src/BpkModalV3/BpkModalV3Root → bpk-react-utils/src}/useBodyLock.d.ts +5 -0
- package/{bpk-component-modal/src/BpkModalV3/BpkModalV3Root → bpk-react-utils/src}/useBodyLock.js +33 -23
- package/package.json +1 -1
|
@@ -15,4 +15,4 @@
|
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/
|
|
18
|
-
.bpk-bottom-sheet{z-index:1100;width:100%;max-width:32rem;margin:auto;
|
|
18
|
+
.bpk-bottom-sheet{z-index:1100;width:100%;max-width:32rem;margin:auto;transition:opacity 200ms ease-in-out,transform 200ms ease-in-out;outline:0;background-color:#fff;opacity:1;overflow:hidden;overflow-y:scroll;-ms-overflow-style:none;scrollbar-width:none;-webkit-tap-highlight-color:rgba(0,0,0,0);box-shadow:0px 12px 50px 0px rgba(37,32,31,.25);border-radius:.5rem}@media(max-width: 32rem){.bpk-bottom-sheet{position:fixed;bottom:0;height:fit-content;max-height:90%;margin-bottom:0;border-radius:1.5rem 1.5rem 0 0;overflow-x:hidden}}.bpk-bottom-sheet::-webkit-scrollbar{display:none}.bpk-bottom-sheet--appear{animation-duration:200ms;animation-name:slide-up;animation-timing-function:ease-in-out}@media(min-width: 32.0625rem){.bpk-bottom-sheet--appear{transform:scale(0.9);opacity:0;animation:none}}@media(min-width: 32.0625rem){.bpk-bottom-sheet--appear-active{transform:scale(1);opacity:1}}.bpk-bottom-sheet--exit{animation-fill-mode:forwards;animation-duration:200ms;animation-name:slide-down;animation-timing-function:ease-in-out}@media(min-width: 32.0625rem){.bpk-bottom-sheet--exit{animation:none}}.bpk-bottom-sheet--content{padding:0;flex:1;overflow-y:auto}.bpk-bottom-sheet--padding-base-top{padding-top:1rem}.bpk-bottom-sheet--padding-base-bottom{padding-bottom:1rem}.bpk-bottom-sheet--padding-base-start{padding-inline-start:1rem}.bpk-bottom-sheet--padding-base-end{padding-inline-end:1rem}.bpk-bottom-sheet--padding-lg-top{padding-top:1.5rem}.bpk-bottom-sheet--padding-lg-bottom{padding-bottom:1.5rem}.bpk-bottom-sheet--padding-lg-start{padding-inline-start:1.5rem}.bpk-bottom-sheet--padding-lg-end{padding-inline-end:1.5rem}.bpk-bottom-sheet--padding-xxl-top{padding-top:2.5rem}.bpk-bottom-sheet--padding-xxl-bottom{padding-bottom:2.5rem}.bpk-bottom-sheet--padding-xxl-start{padding-inline-start:2.5rem}.bpk-bottom-sheet--padding-xxl-end{padding-inline-end:2.5rem}.bpk-bottom-sheet--padding-xxxl-top{padding-top:4rem}.bpk-bottom-sheet--padding-xxxl-bottom{padding-bottom:4rem}.bpk-bottom-sheet--padding-xxxl-start{padding-inline-start:4rem}.bpk-bottom-sheet--padding-xxxl-end{padding-inline-end:4rem}@media(min-width: 32.0625rem){.bpk-bottom-sheet--wide{max-width:64rem}}.bpk-bottom-sheet--header-wrapper{position:sticky;top:0;z-index:899;background-color:#fff;background-color:var(--bpk-navigation-bar-background-color, rgb(255, 255, 255))}.bpk-bottom-sheet--header{min-height:fit-content;padding:1.5rem}@keyframes slide-up{0%{transform:translateY(100%)}100%{transform:translateY(0%)}}@keyframes slide-down{0%{transform:translateY(0%)}100%{transform:translateY(100%)}}
|
|
@@ -19,10 +19,9 @@
|
|
|
19
19
|
import { useEffect, useState } from 'react';
|
|
20
20
|
import { Dialog } from '@ark-ui/react';
|
|
21
21
|
import { durationBase } from '@skyscanner/bpk-foundations-web/tokens/base.es6';
|
|
22
|
-
import { getDataComponentAttribute } from "../../../../bpk-react-utils";
|
|
22
|
+
import { getDataComponentAttribute, useBodyLock } from "../../../../bpk-react-utils";
|
|
23
23
|
import { ModalTypeProvider } from "../BpkModalV3Context";
|
|
24
24
|
import { MODAL_V3_TYPES } from "../common-types";
|
|
25
|
-
import useBodyLock from "./useBodyLock";
|
|
26
25
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
27
26
|
const BpkModalV3Root = ({
|
|
28
27
|
children,
|
|
@@ -8,10 +8,11 @@ import { getDataComponentAttribute } from './src/getDataComponentAttribute';
|
|
|
8
8
|
import isRTL from './src/isRTL';
|
|
9
9
|
import { setNativeValue } from './src/nativeEventHandler';
|
|
10
10
|
import { SURFACE_COLORS } from './src/surfaceColors';
|
|
11
|
+
import useBodyLock from './src/useBodyLock';
|
|
11
12
|
import withDefaultProps from './src/withDefaultProps';
|
|
12
13
|
import wrapDisplayName from './src/wrapDisplayName';
|
|
13
14
|
export type { SurfaceBgColor } from './src/surfaceColors';
|
|
14
|
-
export { Portal, TransitionInitialMount, cssModules, deprecated, withDefaultProps, wrapDisplayName, isDeviceIphone, isDeviceIpad, isDeviceIos, isRTL, BpkDialogWrapper, setNativeValue, getDataComponentAttribute, SURFACE_COLORS, };
|
|
15
|
+
export { Portal, TransitionInitialMount, cssModules, deprecated, withDefaultProps, wrapDisplayName, isDeviceIphone, isDeviceIpad, isDeviceIos, isRTL, BpkDialogWrapper, setNativeValue, getDataComponentAttribute, SURFACE_COLORS, useBodyLock, };
|
|
15
16
|
declare const _default: {
|
|
16
17
|
Portal: typeof Portal;
|
|
17
18
|
TransitionInitialMount: ({ appearActiveClassName, appearClassName, children, transitionTimeout, }: {
|
|
@@ -55,5 +56,6 @@ declare const _default: {
|
|
|
55
56
|
readonly surfaceLowContrast: "surface-low-contrast";
|
|
56
57
|
readonly surfaceTint: "surface-tint";
|
|
57
58
|
};
|
|
59
|
+
useBodyLock: (isLocked: boolean) => void;
|
|
58
60
|
};
|
|
59
61
|
export default _default;
|
package/bpk-react-utils/index.js
CHANGED
|
@@ -27,9 +27,10 @@ import { getDataComponentAttribute } from "./src/getDataComponentAttribute";
|
|
|
27
27
|
import isRTL from "./src/isRTL";
|
|
28
28
|
import { setNativeValue } from "./src/nativeEventHandler";
|
|
29
29
|
import { SURFACE_COLORS } from "./src/surfaceColors";
|
|
30
|
+
import useBodyLock from "./src/useBodyLock";
|
|
30
31
|
import withDefaultProps from "./src/withDefaultProps";
|
|
31
32
|
import wrapDisplayName from "./src/wrapDisplayName";
|
|
32
|
-
export { Portal, TransitionInitialMount, cssModules, deprecated, withDefaultProps, wrapDisplayName, isDeviceIphone, isDeviceIpad, isDeviceIos, isRTL, BpkDialogWrapper, setNativeValue, getDataComponentAttribute, SURFACE_COLORS };
|
|
33
|
+
export { Portal, TransitionInitialMount, cssModules, deprecated, withDefaultProps, wrapDisplayName, isDeviceIphone, isDeviceIpad, isDeviceIos, isRTL, BpkDialogWrapper, setNativeValue, getDataComponentAttribute, SURFACE_COLORS, useBodyLock };
|
|
33
34
|
export default {
|
|
34
35
|
Portal,
|
|
35
36
|
TransitionInitialMount,
|
|
@@ -44,5 +45,6 @@ export default {
|
|
|
44
45
|
BpkDialogWrapper,
|
|
45
46
|
setNativeValue,
|
|
46
47
|
getDataComponentAttribute,
|
|
47
|
-
SURFACE_COLORS
|
|
48
|
+
SURFACE_COLORS,
|
|
49
|
+
useBodyLock
|
|
48
50
|
};
|
|
@@ -20,20 +20,12 @@ import { useEffect, useRef, useState } from 'react';
|
|
|
20
20
|
import CSSTransition from 'react-transition-group/CSSTransition';
|
|
21
21
|
import cssModules from "../cssModules";
|
|
22
22
|
import { getDataComponentAttribute } from "../getDataComponentAttribute";
|
|
23
|
+
import useBodyLock from "../useBodyLock";
|
|
23
24
|
import STYLES from "./BpkDialogWrapper.module.css";
|
|
24
25
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
25
26
|
const getClassName = cssModules(STYLES);
|
|
26
27
|
// TODO: this check if the browser support the HTML dialog element. We can remove it once we drop support as a business for Safari 14
|
|
27
28
|
const dialogSupported = typeof HTMLDialogElement === 'function';
|
|
28
|
-
const setPageProperties = ({
|
|
29
|
-
isDialogOpen
|
|
30
|
-
}) => {
|
|
31
|
-
document.body.style.overflowY = isDialogOpen ? 'hidden' : 'visible';
|
|
32
|
-
if (!dialogSupported) {
|
|
33
|
-
document.body.style.position = isDialogOpen ? 'fixed' : 'relative';
|
|
34
|
-
document.body.style.width = isDialogOpen ? '100%' : 'auto';
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
29
|
export const BpkDialogWrapper = ({
|
|
38
30
|
children,
|
|
39
31
|
closeOnEscPressed = false,
|
|
@@ -100,18 +92,7 @@ export const BpkDialogWrapper = ({
|
|
|
100
92
|
window.removeEventListener('keydown', handleKeyDown);
|
|
101
93
|
};
|
|
102
94
|
}, [id, isOpen, onClose, closeOnEscPressed, closeOnScrimClick]);
|
|
103
|
-
|
|
104
|
-
// Lock the scroll of the page when the dialog is open
|
|
105
|
-
useEffect(() => {
|
|
106
|
-
setPageProperties({
|
|
107
|
-
isDialogOpen: isOpen
|
|
108
|
-
});
|
|
109
|
-
return () => {
|
|
110
|
-
setPageProperties({
|
|
111
|
-
isDialogOpen: false
|
|
112
|
-
});
|
|
113
|
-
};
|
|
114
|
-
}, [isOpen]);
|
|
95
|
+
useBodyLock(isOpen);
|
|
115
96
|
const aria = {
|
|
116
97
|
...('ariaLabelledby' in ariaProps ? {
|
|
117
98
|
'aria-labelledby': ariaProps.ariaLabelledby
|
package/{bpk-component-modal/src/BpkModalV3/BpkModalV3Root → bpk-react-utils/src}/useBodyLock.d.ts
RENAMED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
* Locks body scroll when the modal is open, preventing background scroll and
|
|
3
3
|
* iOS Safari bounce effects. Restores all body styles and scroll position on
|
|
4
4
|
* cleanup.
|
|
5
|
+
*
|
|
6
|
+
* Uses reference counting so multiple concurrent callers (e.g. Modal + BottomSheet)
|
|
7
|
+
* coordinate correctly: styles are locked on the first caller and only restored
|
|
8
|
+
* when the last caller unlocks.
|
|
9
|
+
*
|
|
5
10
|
* @param {boolean} isLocked - Whether the body scroll should be locked.
|
|
6
11
|
* @returns {void}
|
|
7
12
|
*/
|
package/{bpk-component-modal/src/BpkModalV3/BpkModalV3Root → bpk-react-utils/src}/useBodyLock.js
RENAMED
|
@@ -16,18 +16,25 @@
|
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { useEffect
|
|
19
|
+
import { useEffect } from 'react';
|
|
20
|
+
// Module-level shared state for reference counting across concurrent callers
|
|
21
|
+
let lockCount = 0;
|
|
22
|
+
let savedBodyStyles = null;
|
|
23
|
+
let savedScrollY = 0;
|
|
20
24
|
|
|
21
25
|
/**
|
|
22
26
|
* Locks body scroll when the modal is open, preventing background scroll and
|
|
23
27
|
* iOS Safari bounce effects. Restores all body styles and scroll position on
|
|
24
28
|
* cleanup.
|
|
29
|
+
*
|
|
30
|
+
* Uses reference counting so multiple concurrent callers (e.g. Modal + BottomSheet)
|
|
31
|
+
* coordinate correctly: styles are locked on the first caller and only restored
|
|
32
|
+
* when the last caller unlocks.
|
|
33
|
+
*
|
|
25
34
|
* @param {boolean} isLocked - Whether the body scroll should be locked.
|
|
26
35
|
* @returns {void}
|
|
27
36
|
*/
|
|
28
37
|
const useBodyLock = isLocked => {
|
|
29
|
-
const savedScrollYRef = useRef(0);
|
|
30
|
-
const savedBodyStylesRef = useRef(null);
|
|
31
38
|
useEffect(() => {
|
|
32
39
|
if (!isLocked) {
|
|
33
40
|
return undefined;
|
|
@@ -35,33 +42,36 @@ const useBodyLock = isLocked => {
|
|
|
35
42
|
const {
|
|
36
43
|
body
|
|
37
44
|
} = document;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
45
|
+
if (lockCount === 0) {
|
|
46
|
+
savedScrollY = window.scrollY;
|
|
47
|
+
savedBodyStyles = {
|
|
48
|
+
overflow: body.style.overflow || '',
|
|
49
|
+
position: body.style.position || '',
|
|
50
|
+
top: body.style.top || '',
|
|
51
|
+
width: body.style.width || '',
|
|
52
|
+
touchAction: body.style.touchAction || '',
|
|
53
|
+
overscrollBehavior: body.style.overscrollBehavior || ''
|
|
54
|
+
};
|
|
55
|
+
body.style.overflow = 'hidden';
|
|
56
|
+
body.style.position = 'fixed';
|
|
57
|
+
body.style.top = `-${savedScrollY}px`;
|
|
58
|
+
body.style.width = '100%';
|
|
59
|
+
body.style.touchAction = 'none';
|
|
60
|
+
body.style.overscrollBehavior = 'contain';
|
|
61
|
+
}
|
|
62
|
+
lockCount += 1;
|
|
54
63
|
return () => {
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
lockCount -= 1;
|
|
65
|
+
if (lockCount === 0 && savedBodyStyles) {
|
|
66
|
+
const saved = savedBodyStyles;
|
|
57
67
|
body.style.overflow = saved.overflow;
|
|
58
68
|
body.style.position = saved.position;
|
|
59
69
|
body.style.top = saved.top;
|
|
60
70
|
body.style.width = saved.width;
|
|
61
71
|
body.style.touchAction = saved.touchAction;
|
|
62
72
|
body.style.overscrollBehavior = saved.overscrollBehavior;
|
|
63
|
-
|
|
64
|
-
window.scrollTo(0,
|
|
73
|
+
savedBodyStyles = null;
|
|
74
|
+
window.scrollTo(0, savedScrollY);
|
|
65
75
|
}
|
|
66
76
|
};
|
|
67
77
|
}, [isLocked]);
|