@comicrelief/component-library 8.52.1 → 8.52.2

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.
@@ -103,6 +103,7 @@ const DynamicGallery = _ref => {
103
103
 
104
104
  // handle selected gallery node
105
105
  const [selectedNode, setSelectedNode] = (0, _react.useState)(null);
106
+ const [focusedNode, setFocusedNode] = (0, _react.useState)(null);
106
107
 
107
108
  // handle next/previous node events from the lightbox
108
109
  function handleNextNode(node) {
@@ -128,6 +129,8 @@ const DynamicGallery = _ref => {
128
129
  const nodeIndex = +event.target.dataset.nodeIndex;
129
130
  if (Number.isNaN(nodeIndex)) return;
130
131
  setSelectedNode(nodes[nodeIndex]);
132
+ // also store the focused node for focus restoration when the lightbox closes
133
+ setFocusedNode(event.target.closest('.gallery-node'));
131
134
  }
132
135
  break;
133
136
  }
@@ -190,7 +193,9 @@ const DynamicGallery = _ref => {
190
193
  selectedNode,
191
194
  setSelectedNode,
192
195
  nextNode: handleNextNode,
193
- previousNode: handlePreviousNode
196
+ previousNode: handlePreviousNode,
197
+ focusedNode,
198
+ setFocusedNode
194
199
  }
195
200
  }, /*#__PURE__*/_react.default.createElement(_DynamicGallery.ImageGrid, {
196
201
  className: "gallery-grid",
@@ -205,7 +210,8 @@ const DynamicGallery = _ref => {
205
210
  columnCount: columnCount,
206
211
  nodes: nodes.slice(0, imageCount),
207
212
  imageRatio: imageRatio,
208
- updateTabOrder: throttledUpdateTabOrder.current
213
+ updateTabOrder: throttledUpdateTabOrder.current,
214
+ focusOutlineColour: textColour
209
215
  })), /*#__PURE__*/_react.default.createElement(_DynamicGallery.EmptyMessage, {
210
216
  isEmpty: !hasNodes
211
217
  }, "No images to display")), /*#__PURE__*/_react.default.createElement(_Lightbox.default, null), /*#__PURE__*/_react.default.createElement("div", {
@@ -213,6 +219,6 @@ const DynamicGallery = _ref => {
213
219
  tabIndex: 0
214
220
  })), imageCount < nodes.length && /*#__PURE__*/_react.default.createElement(_Button.default, {
215
221
  onClick: () => handleLoadMore()
216
- }, "Load more"));
222
+ }, "Show more"));
217
223
  };
218
224
  var _default = exports.default = DynamicGallery;
@@ -4,7 +4,7 @@ var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWild
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.Title = exports.InteractiveGalleryNode = exports.ImageGrid = exports.ImageContainer = exports.GalleryNode = exports.EmptyMessage = exports.Details = exports.Container = exports.Column = exports.Caption = void 0;
7
+ exports.InteractiveGalleryNode = exports.ImageGrid = exports.ImageContainer = exports.GalleryNode = exports.EmptyMessage = exports.Details = exports.Container = exports.Column = void 0;
8
8
  var _styledComponents = _interopRequireWildcard(require("styled-components"));
9
9
  const Container = exports.Container = _styledComponents.default.div.withConfig({
10
10
  displayName: "DynamicGallerystyle__Container",
@@ -60,7 +60,12 @@ const EmptyMessage = exports.EmptyMessage = _styledComponents.default.div.withCo
60
60
  } = _ref7;
61
61
  return isEmpty ? 'block' : 'none';
62
62
  });
63
- const GalleryNodeBase = (0, _styledComponents.css)(["display:flex;flex-direction:column;gap:0.8rem;padding:0;margin:0;background:none;border:none;text-align:left;"]);
63
+ const GalleryNodeBase = (0, _styledComponents.css)(["display:flex;flex-direction:column;gap:0.9rem;padding:0;margin:0;background:none;border:none;text-align:left;&:focus-visible{outline:2px solid ", ";outline-offset:0.5rem;border-radius:1rem;}"], _ref8 => {
64
+ let {
65
+ focusOutlineColour
66
+ } = _ref8;
67
+ return focusOutlineColour;
68
+ });
64
69
  const GalleryNode = exports.GalleryNode = _styledComponents.default.div.withConfig({
65
70
  displayName: "DynamicGallerystyle__GalleryNode",
66
71
  componentId: "sc-1kgt7yr-4"
@@ -68,30 +73,22 @@ const GalleryNode = exports.GalleryNode = _styledComponents.default.div.withConf
68
73
  const InteractiveGalleryNode = exports.InteractiveGalleryNode = _styledComponents.default.button.withConfig({
69
74
  displayName: "DynamicGallerystyle__InteractiveGalleryNode",
70
75
  componentId: "sc-1kgt7yr-5"
71
- })(["", " cursor:pointer;color:inherit;& div:first-child{transition:all 0.1s ease-out;}&:focus-visible{outline:2px solid #000000;}& > div:first-child{&:hover{box-shadow:0px 3px 10px 0px rgba(0,0,0,0.4);}}"], GalleryNodeBase);
76
+ })(["", " cursor:pointer;color:inherit;& div:first-child{transition:all 0.1s ease-out;}& > div:first-child{&:hover{box-shadow:0px 3px 10px 0px rgba(0,0,0,0.4);}}"], GalleryNodeBase);
72
77
  const ImageContainer = exports.ImageContainer = _styledComponents.default.div.withConfig({
73
78
  displayName: "DynamicGallerystyle__ImageContainer",
74
79
  componentId: "sc-1kgt7yr-6"
75
- })(["display:flex;height:auto;width:100%;min-height:", ";max-height:", ";overflow:hidden;border-radius:1rem;background:rgba(0,0,0,0.05);box-shadow:0px 2px 8px 0px rgba(0,0,0,0.2);img{height:100%;opacity:0;transition:opacity 0.1s ease-out 0.3s;}"], _ref8 => {
80
+ })(["display:flex;height:auto;width:100%;min-height:", ";max-height:", ";overflow:hidden;border-radius:1rem;background:rgba(0,0,0,0.05);box-shadow:0px 2px 8px 0px rgba(0,0,0,0.2);img{height:100%;opacity:0;transition:opacity 0.1s ease-out 0.3s;}"], _ref9 => {
76
81
  let {
77
82
  minHeight
78
- } = _ref8;
83
+ } = _ref9;
79
84
  return minHeight;
80
- }, _ref9 => {
85
+ }, _ref10 => {
81
86
  let {
82
87
  maxHeight
83
- } = _ref9;
88
+ } = _ref10;
84
89
  return maxHeight;
85
90
  });
86
91
  const Details = exports.Details = _styledComponents.default.div.withConfig({
87
92
  displayName: "DynamicGallerystyle__Details",
88
93
  componentId: "sc-1kgt7yr-7"
89
- })(["display:flex;flex-direction:column;gap:0.5rem;padding:0 1rem;"]);
90
- const Title = exports.Title = _styledComponents.default.div.withConfig({
91
- displayName: "DynamicGallerystyle__Title",
92
- componentId: "sc-1kgt7yr-8"
93
- })(["&:first-child{margin-bottom:0;}"]);
94
- const Caption = exports.Caption = _styledComponents.default.div.withConfig({
95
- displayName: "DynamicGallerystyle__Caption",
96
- componentId: "sc-1kgt7yr-9"
97
- })(["line-height:1;"]);
94
+ })(["display:flex;flex-direction:column;gap:0.5rem;padding:0 1rem;"]);
@@ -12,12 +12,14 @@ var _Picture = _interopRequireDefault(require("../../Atoms/Picture/Picture"));
12
12
  var _Lightbox = require("./_Lightbox");
13
13
  var _DynamicGallery = require("./DynamicGallery.style");
14
14
  var _types = require("./_types");
15
+ var _utils = require("./_utils");
15
16
  /**
16
17
  * a separate component to handle columns of images;
17
18
  * this component handles aspect ratio calculations to enfore a min/max ratio for its images
18
19
  */
19
20
  function DynamicGalleryColumn(_ref) {
20
21
  let {
22
+ focusOutlineColour,
21
23
  updateTabOrder,
22
24
  nodes,
23
25
  imageRatio,
@@ -79,33 +81,40 @@ function DynamicGalleryColumn(_ref) {
79
81
  return /*#__PURE__*/_react.default.createElement(_DynamicGallery.Column, {
80
82
  ref: elRef,
81
83
  className: "gallery-column"
82
- }, nodes === null || nodes === void 0 ? void 0 : nodes.filter((_, nodeIndex) => nodeIndex % columnCount === columnIndex).map((node, nodeIndex) => /*#__PURE__*/_react.default.createElement(NodeComponent, {
83
- key: String(nodeIndex) + node.title,
84
- className: "gallery-node",
85
- title: node.title,
86
- "aria-label": node.title,
87
- "data-node-index": nodeIndex,
88
- onPointerUp: useLightbox ? () => handlePointerUp(node) : undefined,
89
- tabIndex: 0
90
- }, /*#__PURE__*/_react.default.createElement(_DynamicGallery.ImageContainer, {
91
- className: "gallery-node-image"
92
- // eslint-disable-next-line prefer-template
93
- ,
94
- minHeight: String(minHeight) + 'px'
95
- // eslint-disable-next-line prefer-template
96
- ,
97
- maxHeight: String(maxHeight) + 'px'
98
- }, /*#__PURE__*/_react.default.createElement(_Picture.default, {
99
- image: node.image,
100
- objectFit: "cover",
101
- alt: node.title
102
- // animate image in on load
103
- ,
104
- onLoad: event => {
105
- event.target.closest('.gallery-node-image').querySelector('img').style.setProperty('opacity', '1');
84
+ }, nodes === null || nodes === void 0 ? void 0 : nodes.filter((_, nodeIndex) => nodeIndex % columnCount === columnIndex).map((node, nodeIndex) => {
85
+ const bodyText = (0, _utils.extractNodeText)(node.gridBody);
86
+ const key = String(nodeIndex) + bodyText;
87
+ return /*#__PURE__*/_react.default.createElement(NodeComponent, {
88
+ key: key,
89
+ className: "gallery-node",
90
+ caption: bodyText,
91
+ "aria-label": bodyText,
92
+ title: bodyText,
93
+ "data-node-index": nodeIndex,
94
+ focusOutlineColour: focusOutlineColour,
95
+ onPointerUp: useLightbox ? () => handlePointerUp(node) : undefined,
96
+ tabIndex: 0
97
+ }, /*#__PURE__*/_react.default.createElement(_DynamicGallery.ImageContainer, {
98
+ className: "gallery-node-image"
99
+ // eslint prefers template literals for strings, but they break the compiler
100
+ // eslint-disable-next-line prefer-template
101
+ ,
102
+ minHeight: String(minHeight) + 'px'
103
+ // eslint-disable-next-line prefer-template
104
+ ,
105
+ maxHeight: String(maxHeight) + 'px'
106
+ }, /*#__PURE__*/_react.default.createElement(_Picture.default, {
107
+ image: node.image,
108
+ objectFit: "cover",
109
+ alt: bodyText
110
+ // animate image in on load
111
+ ,
112
+ onLoad: event => {
113
+ event.target.closest('.gallery-node-image').querySelector('img').style.setProperty('opacity', '1');
106
114
 
107
- // update tab order once the image has loaded
108
- updateTabOrder();
109
- }
110
- })), /*#__PURE__*/_react.default.createElement(_DynamicGallery.Details, null, /*#__PURE__*/_react.default.createElement(_DynamicGallery.Title, null, node.title), node.caption && /*#__PURE__*/_react.default.createElement(_DynamicGallery.Caption, null, node.caption)))));
115
+ // update tab order once the image has loaded
116
+ updateTabOrder();
117
+ }
118
+ })), /*#__PURE__*/_react.default.createElement(_DynamicGallery.Details, null, node.gridBody && /*#__PURE__*/_react.default.createElement("div", null, node.gridBody), node.gridCaption && /*#__PURE__*/_react.default.createElement("div", null, node.gridCaption)));
119
+ }));
111
120
  }
@@ -13,6 +13,7 @@ var _Cross = _interopRequireDefault(require("../../Atoms/Icons/Cross"));
13
13
  var _Picture = _interopRequireDefault(require("../../Atoms/Picture/Picture"));
14
14
  var _Lightbox = require("./_Lightbox.style");
15
15
  var _ScrollFix = _interopRequireDefault(require("./_ScrollFix"));
16
+ var _utils = require("./_utils");
16
17
  /**
17
18
  * lightbox context:
18
19
  * - selectedNode: the node that is currently selected
@@ -44,11 +45,12 @@ const Lightbox = () => {
44
45
  selectedNode,
45
46
  setSelectedNode,
46
47
  nextNode,
47
- previousNode
48
+ previousNode,
49
+ focusedNode,
50
+ setFocusedNode
48
51
  } = (0, _react.useContext)(LightboxContext);
49
52
  const hasNode = Boolean(selectedNode);
50
53
  const dialogRef = (0, _react.useRef)(null);
51
- const previousFocusRef = (0, _react.useRef)(null);
52
54
 
53
55
  /**
54
56
  * handle keyboard events within the lightbox;
@@ -104,27 +106,20 @@ const Lightbox = () => {
104
106
 
105
107
  // handle focus management when dialog opens/closes
106
108
  (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
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
110
+ // move focus to the first focusable element in the dialog when it opens
113
111
  setTimeout(() => {
114
112
  const focusableElements = getFocusableElements(dialogRef.current);
115
113
  if (focusableElements.length > 0) {
116
114
  focusableElements[0].focus();
117
115
  }
118
116
  }, 0);
119
- return;
117
+ } else {
118
+ // restore focus to the previously focused element when lightbox closes
119
+ focusedNode === null || focusedNode === void 0 ? void 0 : focusedNode.focus();
120
+ setFocusedNode(null);
120
121
  }
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]);
122
+ }, [hasNode, focusedNode, setFocusedNode]);
128
123
 
129
124
  /**
130
125
  * close the lightbox when the backdrop is clicked
@@ -165,6 +160,7 @@ const Lightbox = () => {
165
160
  });
166
161
  target.style.opacity = '1';
167
162
  }
163
+ const bodyText = (0, _utils.extractNodeText)(selectedNode === null || selectedNode === void 0 ? void 0 : selectedNode.lightboxBody);
168
164
  return /*#__PURE__*/_react.default.createElement(_Lightbox.Container, {
169
165
  isOpen: hasNode
170
166
  }, /*#__PURE__*/_react.default.createElement(_Lightbox.Backdrop, {
@@ -181,7 +177,7 @@ const Lightbox = () => {
181
177
  color: "#E1E2E3"
182
178
  })), hasNode && /*#__PURE__*/_react.default.createElement(_Picture.default, {
183
179
  key: selectedNode === null || selectedNode === void 0 ? void 0 : selectedNode.image,
184
- alt: selectedNode === null || selectedNode === void 0 ? void 0 : selectedNode.title,
180
+ alt: bodyText,
185
181
  image: selectedNode === null || selectedNode === void 0 ? void 0 : selectedNode.image,
186
182
  width: imageDimensions.width,
187
183
  height: imageDimensions.height,
@@ -191,9 +187,9 @@ const Lightbox = () => {
191
187
  id: "lightboxDescription",
192
188
  "aria-live": "polite",
193
189
  "aria-atomic": "true"
194
- }, /*#__PURE__*/_react.default.createElement("div", {
190
+ }, (selectedNode === null || selectedNode === void 0 ? void 0 : selectedNode.lightboxBody) && /*#__PURE__*/_react.default.createElement("div", {
195
191
  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, {
192
+ }, selectedNode.lightboxBody), (selectedNode === null || selectedNode === void 0 ? void 0 : selectedNode.lightboxCaption) && /*#__PURE__*/_react.default.createElement("div", null, selectedNode === null || selectedNode === void 0 ? void 0 : selectedNode.lightboxCaption)), /*#__PURE__*/_react.default.createElement(_Lightbox.CloseButton, {
197
193
  type: "button",
198
194
  onClick: () => setSelectedNode(null)
199
195
  }, /*#__PURE__*/_react.default.createElement(_Lightbox.ScreenReaderOnly, null, "Close"), /*#__PURE__*/_react.default.createElement(_Cross.default, {