@comicrelief/component-library 8.51.8 → 8.52.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/dist/components/Atoms/Icons/Cross.js +40 -0
- package/dist/components/Atoms/Picture/Picture.js +3 -1
- package/dist/components/Molecules/CTA/CTAMultiCard/__snapshots__/CTAMultiCard.test.js.snap +12 -12
- package/dist/components/Molecules/CTA/shared/CTACard.style.js +1 -1
- package/dist/components/Organisms/DynamicGallery/DynamicGallery.js +218 -0
- package/dist/components/Organisms/DynamicGallery/DynamicGallery.md +30 -0
- package/dist/components/Organisms/DynamicGallery/DynamicGallery.style.js +97 -0
- package/dist/components/Organisms/DynamicGallery/DynamicGallery.test.js +33 -0
- package/dist/components/Organisms/DynamicGallery/_DynamicGalleryColumn.js +111 -0
- package/dist/components/Organisms/DynamicGallery/_Lightbox.js +218 -0
- package/dist/components/Organisms/DynamicGallery/_Lightbox.style.js +86 -0
- package/dist/components/Organisms/DynamicGallery/_ScrollFix.js +57 -0
- package/dist/components/Organisms/DynamicGallery/__snapshots__/DynamicGallery.test.js.snap +1113 -0
- package/dist/components/Organisms/DynamicGallery/_types.js +18 -0
- package/dist/components/Organisms/DynamicGallery/_utils.js +24 -0
- package/dist/index.js +8 -1
- package/dist/styleguide/assets/tall.jpg +0 -0
- package/dist/styleguide/assets/wide.jpg +0 -0
- package/package.json +1 -1
- package/playwright/components/organisms/dynamicGallery.spec.js +9 -0
- package/src/components/Atoms/Icons/Cross.js +37 -0
- package/src/components/Atoms/Picture/Picture.js +4 -1
- package/src/components/Molecules/CTA/CTAMultiCard/__snapshots__/CTAMultiCard.test.js.snap +12 -12
- package/src/components/Molecules/CTA/shared/CTACard.style.js +1 -1
- package/src/components/Organisms/DynamicGallery/DynamicGallery.js +243 -0
- package/src/components/Organisms/DynamicGallery/DynamicGallery.md +30 -0
- package/src/components/Organisms/DynamicGallery/DynamicGallery.style.js +107 -0
- package/src/components/Organisms/DynamicGallery/DynamicGallery.test.js +34 -0
- package/src/components/Organisms/DynamicGallery/_DynamicGalleryColumn.js +144 -0
- package/src/components/Organisms/DynamicGallery/_Lightbox.js +242 -0
- package/src/components/Organisms/DynamicGallery/_Lightbox.style.js +159 -0
- package/src/components/Organisms/DynamicGallery/_ScrollFix.js +60 -0
- package/src/components/Organisms/DynamicGallery/__snapshots__/DynamicGallery.test.js.snap +1113 -0
- package/src/components/Organisms/DynamicGallery/_types.js +12 -0
- package/src/components/Organisms/DynamicGallery/_utils.js +28 -0
- package/src/index.js +1 -0
- package/src/styleguide/assets/tall.jpg +0 -0
- package/src/styleguide/assets/wide.jpg +0 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
|
|
4
|
+
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
|
|
5
|
+
Object.defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports.default = exports.LightboxContext = void 0;
|
|
9
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
10
|
+
var _PulseLoader = _interopRequireDefault(require("react-spinners/PulseLoader"));
|
|
11
|
+
var _Arrow = _interopRequireDefault(require("../../Atoms/Icons/Arrow"));
|
|
12
|
+
var _Cross = _interopRequireDefault(require("../../Atoms/Icons/Cross"));
|
|
13
|
+
var _Picture = _interopRequireDefault(require("../../Atoms/Picture/Picture"));
|
|
14
|
+
var _Lightbox = require("./_Lightbox.style");
|
|
15
|
+
var _ScrollFix = _interopRequireDefault(require("./_ScrollFix"));
|
|
16
|
+
/**
|
|
17
|
+
* lightbox context:
|
|
18
|
+
* - selectedNode: the node that is currently selected
|
|
19
|
+
* - setSelectedNode: set the selected node
|
|
20
|
+
* - nextNode/previousNode: navigate to the next/previous node
|
|
21
|
+
*/
|
|
22
|
+
const LightboxContext = exports.LightboxContext = /*#__PURE__*/_react.default.createContext(null);
|
|
23
|
+
|
|
24
|
+
// get all focusable elements within the dialog
|
|
25
|
+
function getFocusableElements(element) {
|
|
26
|
+
if (!(element instanceof Element)) return [];
|
|
27
|
+
const focusableSelectors = ['button:not([disabled])', '[href]', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', '[tabindex]:not([tabindex="-1"])'].join(', ');
|
|
28
|
+
return Array.from(element.querySelectorAll(focusableSelectors));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* the Lightbox component is a modal that displays a single image,
|
|
33
|
+
* along with UI to navigate through the gallery
|
|
34
|
+
* .
|
|
35
|
+
* accessibility features like tabbing and focus management are currently implemented here;
|
|
36
|
+
* a better long-term approach would be to use an established modal/dialog library,
|
|
37
|
+
* but to avoid friction with the build process we've gone custom for now
|
|
38
|
+
* .
|
|
39
|
+
* TODO: hide the main window scroll bar in a nicer way, see:
|
|
40
|
+
* https://www.npmjs.com/package/react-remove-scroll
|
|
41
|
+
*/
|
|
42
|
+
const Lightbox = () => {
|
|
43
|
+
const {
|
|
44
|
+
selectedNode,
|
|
45
|
+
setSelectedNode,
|
|
46
|
+
nextNode,
|
|
47
|
+
previousNode
|
|
48
|
+
} = (0, _react.useContext)(LightboxContext);
|
|
49
|
+
const hasNode = Boolean(selectedNode);
|
|
50
|
+
const dialogRef = (0, _react.useRef)(null);
|
|
51
|
+
const previousFocusRef = (0, _react.useRef)(null);
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* handle keyboard events within the lightbox;
|
|
55
|
+
* - trapped focus between UI elements
|
|
56
|
+
* - navigation between images
|
|
57
|
+
* - closing the lightbox
|
|
58
|
+
*/
|
|
59
|
+
(0, _react.useEffect)(() => {
|
|
60
|
+
// trap focus within the dialog
|
|
61
|
+
function handleTabKey(event) {
|
|
62
|
+
if (!hasNode) return;
|
|
63
|
+
const focusableElements = getFocusableElements(dialogRef.current);
|
|
64
|
+
if (focusableElements.length === 0) return;
|
|
65
|
+
const firstElement = focusableElements[0];
|
|
66
|
+
const lastElement = focusableElements[focusableElements.length - 1];
|
|
67
|
+
const currentElement = document.activeElement;
|
|
68
|
+
|
|
69
|
+
// if shift+tab is pressed and we're on the first element, move to the last
|
|
70
|
+
if (event.shiftKey && currentElement === firstElement) {
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
lastElement.focus();
|
|
73
|
+
} else if (!event.shiftKey && currentElement === lastElement) {
|
|
74
|
+
// if tab is pressed and we're on the last element, move to the first
|
|
75
|
+
event.preventDefault();
|
|
76
|
+
firstElement.focus();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function handleKeyDown(event) {
|
|
80
|
+
switch (event.key) {
|
|
81
|
+
case 'Escape':
|
|
82
|
+
setSelectedNode(null);
|
|
83
|
+
break;
|
|
84
|
+
case 'Tab':
|
|
85
|
+
handleTabKey(event);
|
|
86
|
+
break;
|
|
87
|
+
case 'ArrowLeft':
|
|
88
|
+
previousNode(selectedNode);
|
|
89
|
+
break;
|
|
90
|
+
case 'ArrowRight':
|
|
91
|
+
nextNode(selectedNode);
|
|
92
|
+
break;
|
|
93
|
+
default:
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (hasNode) {
|
|
98
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
99
|
+
}
|
|
100
|
+
return () => {
|
|
101
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
102
|
+
};
|
|
103
|
+
}, [hasNode, selectedNode, setSelectedNode, previousNode, nextNode]);
|
|
104
|
+
|
|
105
|
+
// handle focus management when dialog opens/closes
|
|
106
|
+
(0, _react.useEffect)(() => {
|
|
107
|
+
// when the lightbox is opened, store the previously focused element
|
|
108
|
+
// and move focus to the first focusable element in the dialog
|
|
109
|
+
if (hasNode) {
|
|
110
|
+
// store the previously focused element
|
|
111
|
+
previousFocusRef.current = document.activeElement;
|
|
112
|
+
// move focus to the first focusable element in the dialog
|
|
113
|
+
setTimeout(() => {
|
|
114
|
+
const focusableElements = getFocusableElements(dialogRef.current);
|
|
115
|
+
if (focusableElements.length > 0) {
|
|
116
|
+
focusableElements[0].focus();
|
|
117
|
+
}
|
|
118
|
+
}, 0);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// when the lightbox is closed, restore focus to the previously focused element
|
|
123
|
+
if (previousFocusRef.current && typeof previousFocusRef.current.focus === 'function') {
|
|
124
|
+
previousFocusRef.current.focus();
|
|
125
|
+
previousFocusRef.current = null;
|
|
126
|
+
}
|
|
127
|
+
}, [hasNode]);
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* close the lightbox when the backdrop is clicked
|
|
131
|
+
*/
|
|
132
|
+
function handleBackdropClick() {
|
|
133
|
+
setSelectedNode(null);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// handle transitions between images nicely;
|
|
137
|
+
const [imageDimensions, setImageDimensions] = (0, _react.useState)({
|
|
138
|
+
width: '0px',
|
|
139
|
+
height: '0px'
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* when the image loads, check to see how best we can fit it on screen,
|
|
144
|
+
* then set width and height on the element;
|
|
145
|
+
* this lets us transition nicely to the new size
|
|
146
|
+
*/
|
|
147
|
+
function onLoad(event) {
|
|
148
|
+
const {
|
|
149
|
+
target
|
|
150
|
+
} = event;
|
|
151
|
+
const imageWidth = target.naturalWidth;
|
|
152
|
+
const imageHeight = target.naturalHeight;
|
|
153
|
+
const maxWidth = Math.min.apply(null, [imageWidth, 1024, window.innerWidth * 0.85]);
|
|
154
|
+
const maxHeight = Math.min.apply(null, [imageHeight, 1024, window.innerHeight * 0.5]);
|
|
155
|
+
const scaleX = maxWidth / imageWidth;
|
|
156
|
+
const scaleY = maxHeight / imageHeight;
|
|
157
|
+
const scale = Math.min(scaleX, scaleY);
|
|
158
|
+
const width = imageWidth * scale;
|
|
159
|
+
const height = imageHeight * scale;
|
|
160
|
+
|
|
161
|
+
// set the width and height on the image element, and make it visible
|
|
162
|
+
setImageDimensions({
|
|
163
|
+
width: `${width}px`,
|
|
164
|
+
height: `${height}px`
|
|
165
|
+
});
|
|
166
|
+
target.style.opacity = '1';
|
|
167
|
+
}
|
|
168
|
+
return /*#__PURE__*/_react.default.createElement(_Lightbox.Container, {
|
|
169
|
+
isOpen: hasNode
|
|
170
|
+
}, /*#__PURE__*/_react.default.createElement(_Lightbox.Backdrop, {
|
|
171
|
+
onPointerUp: () => handleBackdropClick()
|
|
172
|
+
}), /*#__PURE__*/_react.default.createElement(_Lightbox.Dialog, {
|
|
173
|
+
ref: dialogRef,
|
|
174
|
+
"aria-labelledby": "lightboxTitle",
|
|
175
|
+
"aria-describedby": "lightboxDescription"
|
|
176
|
+
}, hasNode && /*#__PURE__*/_react.default.createElement(_ScrollFix.default, null), /*#__PURE__*/_react.default.createElement(_Lightbox.LightboxContent, null, /*#__PURE__*/_react.default.createElement(_Lightbox.LightboxImage, {
|
|
177
|
+
className: "lightbox-image"
|
|
178
|
+
}, /*#__PURE__*/_react.default.createElement(_Lightbox.LightboxSpinner, null, /*#__PURE__*/_react.default.createElement(_PulseLoader.default, {
|
|
179
|
+
height: 16,
|
|
180
|
+
width: 2,
|
|
181
|
+
color: "#E1E2E3"
|
|
182
|
+
})), hasNode && /*#__PURE__*/_react.default.createElement(_Picture.default, {
|
|
183
|
+
key: selectedNode === null || selectedNode === void 0 ? void 0 : selectedNode.image,
|
|
184
|
+
alt: selectedNode === null || selectedNode === void 0 ? void 0 : selectedNode.title,
|
|
185
|
+
image: selectedNode === null || selectedNode === void 0 ? void 0 : selectedNode.image,
|
|
186
|
+
width: imageDimensions.width,
|
|
187
|
+
height: imageDimensions.height,
|
|
188
|
+
objectFit: "contain",
|
|
189
|
+
onLoad: event => onLoad(event)
|
|
190
|
+
})), /*#__PURE__*/_react.default.createElement(_Lightbox.LightboxDetails, {
|
|
191
|
+
id: "lightboxDescription",
|
|
192
|
+
"aria-live": "polite",
|
|
193
|
+
"aria-atomic": "true"
|
|
194
|
+
}, /*#__PURE__*/_react.default.createElement("div", {
|
|
195
|
+
id: "lightboxTitle"
|
|
196
|
+
}, selectedNode === null || selectedNode === void 0 ? void 0 : selectedNode.title), (selectedNode === null || selectedNode === void 0 ? void 0 : selectedNode.caption) && /*#__PURE__*/_react.default.createElement("div", null, selectedNode === null || selectedNode === void 0 ? void 0 : selectedNode.caption), (selectedNode === null || selectedNode === void 0 ? void 0 : selectedNode.body) && /*#__PURE__*/_react.default.createElement("div", null, selectedNode.body)), /*#__PURE__*/_react.default.createElement(_Lightbox.CloseButton, {
|
|
197
|
+
type: "button",
|
|
198
|
+
onClick: () => setSelectedNode(null)
|
|
199
|
+
}, /*#__PURE__*/_react.default.createElement(_Lightbox.ScreenReaderOnly, null, "Close"), /*#__PURE__*/_react.default.createElement(_Cross.default, {
|
|
200
|
+
colour: "black",
|
|
201
|
+
size: 16
|
|
202
|
+
})), /*#__PURE__*/_react.default.createElement(_Lightbox.PreviousButton, {
|
|
203
|
+
type: "button",
|
|
204
|
+
onClick: () => previousNode(selectedNode)
|
|
205
|
+
}, /*#__PURE__*/_react.default.createElement(_Lightbox.ScreenReaderOnly, null, "Previous"), /*#__PURE__*/_react.default.createElement(_Arrow.default, {
|
|
206
|
+
direction: "left",
|
|
207
|
+
colour: "black",
|
|
208
|
+
size: 16
|
|
209
|
+
})), /*#__PURE__*/_react.default.createElement(_Lightbox.NextButton, {
|
|
210
|
+
type: "button",
|
|
211
|
+
onClick: () => nextNode(selectedNode)
|
|
212
|
+
}, /*#__PURE__*/_react.default.createElement(_Lightbox.ScreenReaderOnly, null, "Next"), /*#__PURE__*/_react.default.createElement(_Arrow.default, {
|
|
213
|
+
direction: "right",
|
|
214
|
+
colour: "black",
|
|
215
|
+
size: 16
|
|
216
|
+
})))));
|
|
217
|
+
};
|
|
218
|
+
var _default = exports.default = Lightbox;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.ScreenReaderOnly = exports.PreviousButton = exports.NextButton = exports.NavButton = exports.LightboxSpinner = exports.LightboxImage = exports.LightboxDetails = exports.LightboxContent = exports.Dialog = exports.Container = exports.CloseButton = exports.Backdrop = void 0;
|
|
8
|
+
var _styledComponents = _interopRequireDefault(require("styled-components"));
|
|
9
|
+
const Container = exports.Container = _styledComponents.default.div.withConfig({
|
|
10
|
+
displayName: "_Lightboxstyle__Container",
|
|
11
|
+
componentId: "sc-twdy7x-0"
|
|
12
|
+
})(["position:fixed;top:0;left:0;width:100%;height:100%;display:flex;justify-content:center;align-items:center;z-index:2000;visibility:", ";"], _ref => {
|
|
13
|
+
let {
|
|
14
|
+
isOpen
|
|
15
|
+
} = _ref;
|
|
16
|
+
return isOpen ? 'visible' : 'hidden';
|
|
17
|
+
});
|
|
18
|
+
const Backdrop = exports.Backdrop = _styledComponents.default.div.withConfig({
|
|
19
|
+
displayName: "_Lightboxstyle__Backdrop",
|
|
20
|
+
componentId: "sc-twdy7x-1"
|
|
21
|
+
})(["position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.8);z-index:0;"]);
|
|
22
|
+
const Dialog = exports.Dialog = _styledComponents.default.dialog.withConfig({
|
|
23
|
+
displayName: "_Lightboxstyle__Dialog",
|
|
24
|
+
componentId: "sc-twdy7x-2"
|
|
25
|
+
})(["display:block;padding:0.5rem;background:transparent;border:none;z-index:1;margin-top:72px;@media ", "{margin-top:84px;}"], _ref2 => {
|
|
26
|
+
let {
|
|
27
|
+
theme
|
|
28
|
+
} = _ref2;
|
|
29
|
+
return theme.breakpoints2026('L');
|
|
30
|
+
});
|
|
31
|
+
const LightboxContent = exports.LightboxContent = _styledComponents.default.div.withConfig({
|
|
32
|
+
displayName: "_Lightboxstyle__LightboxContent",
|
|
33
|
+
componentId: "sc-twdy7x-3"
|
|
34
|
+
})(["display:flex;flex-direction:column;align-items:center;gap:1rem;position:relative;padding:1rem;background:#ffffff;border-radius:1rem;"]);
|
|
35
|
+
const LightboxImage = exports.LightboxImage = _styledComponents.default.div.withConfig({
|
|
36
|
+
displayName: "_Lightboxstyle__LightboxImage",
|
|
37
|
+
componentId: "sc-twdy7x-4"
|
|
38
|
+
})(["position:relative;display:flex;align-items:center;justify-content:center;min-width:128px;min-height:32px;border-radius:0.6rem;overflow:hidden;& > div{display:flex;align-items:center;justify-content:center;transition:width 0.3s ease-in-out,height 0.3s ease-in-out;}& img{opacity:0;transition:opacity 0.1s ease-out 0.3s;}"]);
|
|
39
|
+
const LightboxSpinner = exports.LightboxSpinner = _styledComponents.default.div.withConfig({
|
|
40
|
+
displayName: "_Lightboxstyle__LightboxSpinner",
|
|
41
|
+
componentId: "sc-twdy7x-5"
|
|
42
|
+
})(["position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);"]);
|
|
43
|
+
const LightboxDetails = exports.LightboxDetails = _styledComponents.default.div.withConfig({
|
|
44
|
+
displayName: "_Lightboxstyle__LightboxDetails",
|
|
45
|
+
componentId: "sc-twdy7x-6"
|
|
46
|
+
})(["display:flex;flex-direction:column;align-items:stretch;gap:0.5rem;width:100%;padding:0 1rem;"]);
|
|
47
|
+
const NavButton = exports.NavButton = _styledComponents.default.button.withConfig({
|
|
48
|
+
displayName: "_Lightboxstyle__NavButton",
|
|
49
|
+
componentId: "sc-twdy7x-7"
|
|
50
|
+
})(["position:absolute;display:flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;border-radius:0.5rem;border:none;background-color:white;cursor:pointer;z-index:10;svg{transition:all 0.1s ease-out;}&:hover{svg{fill:", ";}}&:focus-visible{outline:2px solid ", ";}"], _ref3 => {
|
|
51
|
+
let {
|
|
52
|
+
theme
|
|
53
|
+
} = _ref3;
|
|
54
|
+
return theme.color('red');
|
|
55
|
+
}, _ref4 => {
|
|
56
|
+
let {
|
|
57
|
+
theme
|
|
58
|
+
} = _ref4;
|
|
59
|
+
return theme.color('red');
|
|
60
|
+
});
|
|
61
|
+
const CloseButton = exports.CloseButton = (0, _styledComponents.default)(NavButton).withConfig({
|
|
62
|
+
displayName: "_Lightboxstyle__CloseButton",
|
|
63
|
+
componentId: "sc-twdy7x-8"
|
|
64
|
+
})(["top:0;right:0;"]);
|
|
65
|
+
const PreviousButton = exports.PreviousButton = (0, _styledComponents.default)(NavButton).withConfig({
|
|
66
|
+
displayName: "_Lightboxstyle__PreviousButton",
|
|
67
|
+
componentId: "sc-twdy7x-9"
|
|
68
|
+
})(["top:30%;left:0;transform:translate(0,-50%);border-top-left-radius:0;border-bottom-left-radius:0;@media ", "{position:fixed;top:50%;}"], _ref5 => {
|
|
69
|
+
let {
|
|
70
|
+
theme
|
|
71
|
+
} = _ref5;
|
|
72
|
+
return theme.breakpoints2026('L');
|
|
73
|
+
});
|
|
74
|
+
const NextButton = exports.NextButton = (0, _styledComponents.default)(NavButton).withConfig({
|
|
75
|
+
displayName: "_Lightboxstyle__NextButton",
|
|
76
|
+
componentId: "sc-twdy7x-10"
|
|
77
|
+
})(["top:30%;right:0;transform:translate(0,-50%);border-top-right-radius:0;border-bottom-right-radius:0;@media ", "{position:fixed;top:50%;}"], _ref6 => {
|
|
78
|
+
let {
|
|
79
|
+
theme
|
|
80
|
+
} = _ref6;
|
|
81
|
+
return theme.breakpoints2026('L');
|
|
82
|
+
});
|
|
83
|
+
const ScreenReaderOnly = exports.ScreenReaderOnly = _styledComponents.default.span.withConfig({
|
|
84
|
+
displayName: "_Lightboxstyle__ScreenReaderOnly",
|
|
85
|
+
componentId: "sc-twdy7x-11"
|
|
86
|
+
})(["position:absolute;width:1px;height:1px;margin:-1px;border:0;padding:0;white-space:nowrap;clip-path:inset(100%);clip:rect(0 0 0 0);overflow:hidden;"]);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = ScrollFix;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
let scrollPadding;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* small utility component that allows us to lock the main window scrolling,
|
|
12
|
+
* while also avoiding flicker/judder as the scrollbar is added and removed from the DOM.
|
|
13
|
+
* used in the Lightbox component to ensure the scrollbar is hidden when the lightbox is open.
|
|
14
|
+
*/
|
|
15
|
+
function ScrollFix() {
|
|
16
|
+
// create a DOM element with a known width and a scrollbar,
|
|
17
|
+
// then measure an inner element to find the missing width taken up by the scrollbar
|
|
18
|
+
if (scrollPadding === undefined) {
|
|
19
|
+
const container = document.createElement('div');
|
|
20
|
+
container.className = 'scroll-fix-container';
|
|
21
|
+
container.style.setProperty('position', 'absolute');
|
|
22
|
+
container.style.setProperty('top', '0');
|
|
23
|
+
container.style.setProperty('left', '-9999px');
|
|
24
|
+
container.style.setProperty('width', '100px');
|
|
25
|
+
container.style.setProperty('height', '100px');
|
|
26
|
+
container.style.setProperty('overflow-y', 'scroll');
|
|
27
|
+
const inner = document.createElement('div');
|
|
28
|
+
inner.style.setProperty('width', '100%');
|
|
29
|
+
container.appendChild(inner);
|
|
30
|
+
// edge needs the element to be in the DOM to measure the scrollbar width
|
|
31
|
+
document.body.appendChild(container);
|
|
32
|
+
scrollPadding = 100 - inner.getBoundingClientRect().width;
|
|
33
|
+
// remove the element from the DOM
|
|
34
|
+
document.body.removeChild(container);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// when the component mounts, add a padding to the document to compensate for the scrollbar;
|
|
38
|
+
// this is then removed when the component unmounts
|
|
39
|
+
(0, _react.useLayoutEffect)(() => {
|
|
40
|
+
function resetScrollbar() {
|
|
41
|
+
document.documentElement.style.setProperty('overflow', 'auto');
|
|
42
|
+
document.documentElement.style.setProperty('padding-right', '0px');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// check that the page content is longer than the viewport
|
|
46
|
+
if (document.documentElement.scrollHeight <= window.innerHeight) {
|
|
47
|
+
resetScrollbar();
|
|
48
|
+
} else {
|
|
49
|
+
document.documentElement.style.setProperty('overflow', 'hidden');
|
|
50
|
+
document.documentElement.style.setProperty('padding-right', `${scrollPadding}px`);
|
|
51
|
+
}
|
|
52
|
+
return () => {
|
|
53
|
+
resetScrollbar();
|
|
54
|
+
};
|
|
55
|
+
}, []);
|
|
56
|
+
return null;
|
|
57
|
+
}
|