@comicrelief/component-library 8.51.7 → 8.52.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.
Files changed (42) hide show
  1. package/dist/components/Atoms/Icons/Cross.js +40 -0
  2. package/dist/components/Atoms/Picture/Picture.js +3 -1
  3. package/dist/components/Molecules/CTA/CTAMultiCard/CTAMultiCard.js +11 -7
  4. package/dist/components/Molecules/CTA/CTAMultiCard/CTAMultiCard.md +42 -0
  5. package/dist/components/Molecules/CTA/CTAMultiCard/CTAMultiCard.style.js +23 -33
  6. package/dist/components/Molecules/CTA/CTAMultiCard/__snapshots__/CTAMultiCard.test.js.snap +12 -12
  7. package/dist/components/Organisms/DynamicGallery/DynamicGallery.js +218 -0
  8. package/dist/components/Organisms/DynamicGallery/DynamicGallery.md +30 -0
  9. package/dist/components/Organisms/DynamicGallery/DynamicGallery.style.js +97 -0
  10. package/dist/components/Organisms/DynamicGallery/DynamicGallery.test.js +33 -0
  11. package/dist/components/Organisms/DynamicGallery/_DynamicGalleryColumn.js +111 -0
  12. package/dist/components/Organisms/DynamicGallery/_Lightbox.js +218 -0
  13. package/dist/components/Organisms/DynamicGallery/_Lightbox.style.js +86 -0
  14. package/dist/components/Organisms/DynamicGallery/_ScrollFix.js +57 -0
  15. package/dist/components/Organisms/DynamicGallery/__snapshots__/DynamicGallery.test.js.snap +1113 -0
  16. package/dist/components/Organisms/DynamicGallery/_types.js +18 -0
  17. package/dist/components/Organisms/DynamicGallery/_utils.js +24 -0
  18. package/dist/index.js +8 -1
  19. package/dist/styleguide/assets/tall.jpg +0 -0
  20. package/dist/styleguide/assets/wide.jpg +0 -0
  21. package/package.json +1 -1
  22. package/playwright/components/organisms/dynamicGallery.spec.js +9 -0
  23. package/src/components/Atoms/Icons/Cross.js +37 -0
  24. package/src/components/Atoms/Picture/Picture.js +4 -1
  25. package/src/components/Molecules/CTA/CTAMultiCard/CTAMultiCard.js +7 -4
  26. package/src/components/Molecules/CTA/CTAMultiCard/CTAMultiCard.md +42 -0
  27. package/src/components/Molecules/CTA/CTAMultiCard/CTAMultiCard.style.js +15 -25
  28. package/src/components/Molecules/CTA/CTAMultiCard/__snapshots__/CTAMultiCard.test.js.snap +12 -12
  29. package/src/components/Organisms/DynamicGallery/DynamicGallery.js +243 -0
  30. package/src/components/Organisms/DynamicGallery/DynamicGallery.md +30 -0
  31. package/src/components/Organisms/DynamicGallery/DynamicGallery.style.js +107 -0
  32. package/src/components/Organisms/DynamicGallery/DynamicGallery.test.js +34 -0
  33. package/src/components/Organisms/DynamicGallery/_DynamicGalleryColumn.js +144 -0
  34. package/src/components/Organisms/DynamicGallery/_Lightbox.js +242 -0
  35. package/src/components/Organisms/DynamicGallery/_Lightbox.style.js +159 -0
  36. package/src/components/Organisms/DynamicGallery/_ScrollFix.js +60 -0
  37. package/src/components/Organisms/DynamicGallery/__snapshots__/DynamicGallery.test.js.snap +1113 -0
  38. package/src/components/Organisms/DynamicGallery/_types.js +12 -0
  39. package/src/components/Organisms/DynamicGallery/_utils.js +28 -0
  40. package/src/index.js +1 -0
  41. package/src/styleguide/assets/tall.jpg +0 -0
  42. package/src/styleguide/assets/wide.jpg +0 -0
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+
3
+ var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
4
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.default = void 0;
9
+ var _react = _interopRequireDefault(require("react"));
10
+ var _styledComponents = _interopRequireWildcard(require("styled-components"));
11
+ const Icon = _styledComponents.default.svg.withConfig({
12
+ displayName: "Cross__Icon",
13
+ componentId: "sc-1px9lqa-0"
14
+ })(["fill:", ";"], _ref => {
15
+ let {
16
+ colour,
17
+ theme
18
+ } = _ref;
19
+ return theme.color(colour);
20
+ });
21
+ const Cross = _ref2 => {
22
+ let {
23
+ colour = 'black',
24
+ mobileColour = null,
25
+ theme,
26
+ size = 24,
27
+ ...rest
28
+ } = _ref2;
29
+ return /*#__PURE__*/_react.default.createElement(Icon, Object.assign({
30
+ width: size,
31
+ height: size,
32
+ colour: colour,
33
+ mobileColour: mobileColour,
34
+ xmlns: "http://www.w3.org/2000/svg",
35
+ viewBox: "0 0 96 96"
36
+ }, rest), /*#__PURE__*/_react.default.createElement("polygon", {
37
+ points: "85.48 17.59 78.41 10.52 48 40.93 17.59 10.52 10.52 17.59 40.93 48 10.52 78.41 17.59 85.48 48 55.07 78.41 85.48 85.48 78.41 55.07 48 85.48 17.59"
38
+ }));
39
+ };
40
+ var _default = exports.default = (0, _styledComponents.withTheme)(Cross);
@@ -88,6 +88,7 @@ const Picture = _ref11 => {
88
88
  isBackgroundImage = false,
89
89
  smallBreakpointRowLayout = null,
90
90
  mediumBreakpointRowLayout = null,
91
+ onLoad,
91
92
  ...rest
92
93
  } = _ref11;
93
94
  const document = typeof window !== 'undefined' ? window.document : null;
@@ -122,7 +123,8 @@ const Picture = _ref11 => {
122
123
  objectFit: objectFit,
123
124
  "data-src": image,
124
125
  className: "lazyload",
125
- objFitState: objFitState
126
+ objFitState: objFitState,
127
+ onLoad: onLoad
126
128
  }));
127
129
  }
128
130
  return /*#__PURE__*/_react.default.createElement(Wrapper, Object.assign({
@@ -59,11 +59,8 @@ const CTAMultiCard = _ref => {
59
59
  return /*#__PURE__*/_react.default.createElement(_CTAMultiCard.CardsQueryWrapper, null, /*#__PURE__*/_react.default.createElement(_CTAMultiCard.CardsSection, {
60
60
  backgroundColor: cardsBackground,
61
61
  paddingAbove: paddingAbove,
62
- paddingBelow: paddingBelow,
63
- isCarousel: carouselOfCards
64
- }, /*#__PURE__*/_react.default.createElement(_CTAMultiCard.CardsInner, {
65
- isCarousel: carouselOfCards
66
- }, /*#__PURE__*/_react.default.createElement(_CTAMultiCard.default, {
62
+ paddingBelow: paddingBelow
63
+ }, /*#__PURE__*/_react.default.createElement(_CTAMultiCard.CardsInner, null, /*#__PURE__*/_react.default.createElement(_CTAMultiCard.default, {
67
64
  columns: columns,
68
65
  isCarousel: carouselOfCards,
69
66
  useSplideCarousel: useSplideCarousel
@@ -71,8 +68,15 @@ const CTAMultiCard = _ref => {
71
68
  options: {
72
69
  arrows: false,
73
70
  pagination: false,
74
- drag: true,
75
- dragMinThreshold: 10,
71
+ // Reduce swipe "throw" as Matt felt the defaults are too much
72
+ // See https://splidejs.com/guides/options/
73
+ drag: 'free',
74
+ flickPower: 50,
75
+ perMove: 1,
76
+ dragMinThreshold: {
77
+ mouse: 50,
78
+ touch: 50
79
+ },
76
80
  gap: '1rem',
77
81
  fixedWidth: '309px',
78
82
  padding: {
@@ -68,6 +68,48 @@ const data = {
68
68
  </div>;
69
69
  ```
70
70
 
71
+ ### CTAMultiCard: Carousel with just 2 cards (2 columns layout)
72
+
73
+ ```js
74
+ import CTAMultiCard from './CTAMultiCard';
75
+ import Text from '../../../Atoms/Text/Text';
76
+ import challengeExampleImage from '../../../../styleguide/assets/challenge-1.jpg';
77
+ const exampleData = require('./example_data.json');
78
+
79
+ // Map cards to include pre-rendered body content and processed image/link data
80
+ const cardsWithRenderedBody = exampleData.cards.map(card => ({
81
+ ...card,
82
+ body: (
83
+ <Text tag="p">
84
+ {card.body}
85
+ </Text>
86
+ ),
87
+ fallback: challengeExampleImage,
88
+ imageLow: "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAPABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAcIBAb/xAAjEAACAgIBBAIDAAAAAAAAAAABAgMEABEGBRIhMQdBE1Fh/8QAFQEBAQAAAAAAAAAAAAAAAAAAAgT/xAAaEQADAQADAAAAAAAAAAAAAAAAAQIDERIT/9oADAMBAAIRAxEAPwBzRcrjVY+0tonyT41nG8y+SLFTkgpQTVFpoqiRZGHc2/egf4RrMM12OHpNi3LsrAjO2vsKCcQtvkbTW570sMUt6xphJKnd+Ma9A78ZRWcS+SWNLpNMqAdQidVaSxErEA6ZgCNj9YZNPTOTpJW7+ovdlnLHyjgAD6GGPug+bP/Z",
89
+ images: `${challengeExampleImage} 678w`,
90
+ bgColour: "white",
91
+ description: "",
92
+ target: "self",
93
+ external: null
94
+ }));
95
+
96
+ const cardsTwo = cardsWithRenderedBody.slice(0, 2);
97
+
98
+ const dataTwoCards = {
99
+ ...exampleData,
100
+ cards: cardsTwo,
101
+ layout: "2 columns",
102
+ carouselOfCards: true,
103
+ backgroundColour: "grey_medium",
104
+ paddingAbove: '0rem',
105
+ paddingBelow: '0rem'
106
+ };
107
+
108
+ <div style={{ background: '#E1E2E3', width: '100%' }}>
109
+ <CTAMultiCard data={dataTwoCards} />
110
+ </div>;
111
+ ```
112
+
71
113
  ### CTAMultiCard: Desktop Grid View (2 columns) with large padding
72
114
 
73
115
  **NB: One card contains a lot of lorem ipsum text to demonstrate that all cards will match the height of the tallest sibling card. In mobile view, this example displays as a carousel. This example also demonstrates larger vertical padding via `paddingAbove` / `paddingBelow` set to `4rem`, so it will appear with more space above and below the cards.**
@@ -14,7 +14,7 @@ const CardsQueryWrapper = exports.CardsQueryWrapper = _styledComponents.default.
14
14
  const CardsSection = exports.CardsSection = _styledComponents.default.div.withConfig({
15
15
  displayName: "CTAMultiCardstyle__CardsSection",
16
16
  componentId: "sc-gsdqzv-1"
17
- })(["width:100%;background:", ";padding-top:", ";padding-bottom:", ";"], _ref => {
17
+ })(["width:100%;background:", ";padding-top:", ";padding-bottom:", ";padding-inline:1rem;@media ", "{padding-inline:2rem;}@media ", "{padding-inline:4rem;}"], _ref => {
18
18
  let {
19
19
  theme,
20
20
  backgroundColor
@@ -30,59 +30,49 @@ const CardsSection = exports.CardsSection = _styledComponents.default.div.withCo
30
30
  paddingBelow
31
31
  } = _ref3;
32
32
  return paddingBelow;
33
- });
34
- const CardsInner = exports.CardsInner = _styledComponents.default.div.withConfig({
35
- displayName: "CTAMultiCardstyle__CardsInner",
36
- componentId: "sc-gsdqzv-2"
37
- })(["width:100%;max-width:1152px;margin:0 auto;", " ", ""], _ref4 => {
33
+ }, _ref4 => {
38
34
  let {
39
- isCarousel
35
+ theme
40
36
  } = _ref4;
41
- return !isCarousel && (0, _styledComponents.css)(["padding-inline:1rem;@media ", "{padding-inline:2rem;}"], _ref5 => {
42
- let {
43
- theme
44
- } = _ref5;
45
- return theme.allBreakpoints('M');
46
- });
47
- }, _ref6 => {
37
+ return theme.breakpoints2026('M');
38
+ }, _ref5 => {
48
39
  let {
49
- isCarousel
50
- } = _ref6;
51
- return isCarousel && (0, _styledComponents.css)(["@media ", "{padding-inline:2rem;}@media (min-width:", "px){padding-inline:0;}"], _ref7 => {
52
- let {
53
- theme
54
- } = _ref7;
55
- return theme.allBreakpoints('M');
56
- }, _allBreakpoints.breakpointValues.XL);
40
+ theme
41
+ } = _ref5;
42
+ return theme.breakpoints2026('L');
57
43
  });
44
+ const CardsInner = exports.CardsInner = _styledComponents.default.div.withConfig({
45
+ displayName: "CTAMultiCardstyle__CardsInner",
46
+ componentId: "sc-gsdqzv-2"
47
+ })(["width:100%;max-width:1152px;margin:0 auto;"]);
58
48
  const CardsContainer = _styledComponents.default.div.withConfig({
59
49
  displayName: "CTAMultiCardstyle__CardsContainer",
60
50
  componentId: "sc-gsdqzv-3"
61
- })(["display:flex;flex-direction:column;width:100%;gap:1rem;@media ", "{flex-direction:row;flex-wrap:wrap;justify-content:center;align-items:stretch;width:100%;max-width:100%;margin:0 auto;}@media ", "{column-gap:2rem;}", " ", ""], _ref8 => {
51
+ })(["display:flex;flex-direction:column;width:100%;gap:1rem;@media ", "{flex-direction:row;flex-wrap:wrap;justify-content:center;align-items:stretch;}@media ", "{column-gap:2rem;row-gap:2rem;}", " ", ""], _ref6 => {
62
52
  let {
63
53
  theme
64
- } = _ref8;
54
+ } = _ref6;
65
55
  return theme.allBreakpoints('M');
66
- }, _ref9 => {
56
+ }, _ref7 => {
67
57
  let {
68
58
  theme
69
- } = _ref9;
59
+ } = _ref7;
70
60
  return theme.allBreakpoints('L');
71
- }, _ref10 => {
61
+ }, _ref8 => {
72
62
  let {
73
63
  columns,
74
64
  useSplideCarousel
75
- } = _ref10;
65
+ } = _ref8;
76
66
  return !useSplideCarousel && columns === 2 && (0, _styledComponents.css)(["@media (min-width:", "px){display:grid;grid-template-columns:repeat(2,minmax(443px,560px));justify-content:center;align-items:stretch;column-gap:2rem;row-gap:2rem;width:100%;max-width:100%;margin:0;& > *:last-child:nth-child(odd){grid-column:1 / -1;justify-self:center;width:min(100%,560px);}}"], _allBreakpoints.breakpointValues.L);
77
- }, _ref11 => {
67
+ }, _ref9 => {
78
68
  let {
79
69
  isCarousel
80
- } = _ref11;
81
- return isCarousel && (0, _styledComponents.css)(["@media (max-width:", "px){", "}"], _allBreakpoints.breakpointValues.L - 1, _ref12 => {
70
+ } = _ref9;
71
+ return isCarousel && (0, _styledComponents.css)(["@media (max-width:", "px){", "}"], _allBreakpoints.breakpointValues.L - 1, _ref10 => {
82
72
  let {
83
73
  useSplideCarousel
84
- } = _ref12;
85
- return useSplideCarousel ? (0, _styledComponents.css)(["display:block;cursor:grab;width:100%;margin:0;max-width:100%;padding:0.75rem 1rem;gap:0;.splide,.splide__track{overflow:visible}.splide__list{align-items:stretch;}.splide__slide{display:flex;height:auto;}"]) : (0, _styledComponents.css)(["flex-direction:row;flex-wrap:nowrap;justify-content:flex-start;width:100%;margin:0;max-width:100%;overflow-x:auto;overflow-y:visible;-webkit-overflow-scrolling:touch;scroll-snap-type:x mandatory;padding:0.75rem 1rem;scrollbar-width:none;-ms-overflow-style:none;&::-webkit-scrollbar{display:none;}"]);
74
+ } = _ref10;
75
+ return useSplideCarousel ? (0, _styledComponents.css)(["display:block;cursor:grab;width:100%;max-width:100%;gap:0;.splide,.splide__track{overflow:visible;}.splide__list{align-items:stretch;}.splide:not(.is-overflow) .splide__list{justify-content:center;}.splide__slide{display:flex;height:auto;}"]) : (0, _styledComponents.css)(["flex-direction:row;flex-wrap:nowrap;justify-content:flex-start;width:100%;max-width:100%;overflow-x:auto;overflow-y:visible;-webkit-overflow-scrolling:touch;scroll-snap-type:x mandatory;scrollbar-width:none;-ms-overflow-style:none;&::-webkit-scrollbar{display:none;}"]);
86
76
  });
87
77
  });
88
78
  var _default = exports.default = CardsContainer;
@@ -5,13 +5,13 @@ exports[`handles data structure correctly 1`] = `
5
5
  className="CTAMultiCardstyle__CardsQueryWrapper-sc-gsdqzv-0 qqegF"
6
6
  >
7
7
  <div
8
- className="CTAMultiCardstyle__CardsSection-sc-gsdqzv-1 ehBqzi"
8
+ className="CTAMultiCardstyle__CardsSection-sc-gsdqzv-1 kCDlNQ"
9
9
  >
10
10
  <div
11
- className="CTAMultiCardstyle__CardsInner-sc-gsdqzv-2 jdzsUU"
11
+ className="CTAMultiCardstyle__CardsInner-sc-gsdqzv-2 iFzzWM"
12
12
  >
13
13
  <div
14
- className="CTAMultiCardstyle__CardsContainer-sc-gsdqzv-3 icRtAH"
14
+ className="CTAMultiCardstyle__CardsContainer-sc-gsdqzv-3 gAuYUa"
15
15
  >
16
16
  <div
17
17
  className="CTACardstyle__CardWrapper-sc-si8xx1-5 jZEwGJ"
@@ -288,13 +288,13 @@ exports[`renders 2 columns layout correctly 1`] = `
288
288
  className="CTAMultiCardstyle__CardsQueryWrapper-sc-gsdqzv-0 qqegF"
289
289
  >
290
290
  <div
291
- className="CTAMultiCardstyle__CardsSection-sc-gsdqzv-1 eznbgX"
291
+ className="CTAMultiCardstyle__CardsSection-sc-gsdqzv-1 cywbFh"
292
292
  >
293
293
  <div
294
- className="CTAMultiCardstyle__CardsInner-sc-gsdqzv-2 kjrrbi"
294
+ className="CTAMultiCardstyle__CardsInner-sc-gsdqzv-2 iFzzWM"
295
295
  >
296
296
  <div
297
- className="CTAMultiCardstyle__CardsContainer-sc-gsdqzv-3 bWeGAp"
297
+ className="CTAMultiCardstyle__CardsContainer-sc-gsdqzv-3 jrSYsC"
298
298
  >
299
299
  <div
300
300
  className="CTACardstyle__CardWrapper-sc-si8xx1-5 xdzCi"
@@ -571,13 +571,13 @@ exports[`renders carousel mode correctly 1`] = `
571
571
  className="CTAMultiCardstyle__CardsQueryWrapper-sc-gsdqzv-0 qqegF"
572
572
  >
573
573
  <div
574
- className="CTAMultiCardstyle__CardsSection-sc-gsdqzv-1 eznbgX"
574
+ className="CTAMultiCardstyle__CardsSection-sc-gsdqzv-1 cywbFh"
575
575
  >
576
576
  <div
577
- className="CTAMultiCardstyle__CardsInner-sc-gsdqzv-2 kjrrbi"
577
+ className="CTAMultiCardstyle__CardsInner-sc-gsdqzv-2 iFzzWM"
578
578
  >
579
579
  <div
580
- className="CTAMultiCardstyle__CardsContainer-sc-gsdqzv-3 eomXiE"
580
+ className="CTAMultiCardstyle__CardsContainer-sc-gsdqzv-3 fqrDP"
581
581
  >
582
582
  <div
583
583
  className="CTACardstyle__CardWrapper-sc-si8xx1-5 eieQSs"
@@ -854,13 +854,13 @@ exports[`renders correctly with data prop 1`] = `
854
854
  className="CTAMultiCardstyle__CardsQueryWrapper-sc-gsdqzv-0 qqegF"
855
855
  >
856
856
  <div
857
- className="CTAMultiCardstyle__CardsSection-sc-gsdqzv-1 eznbgX"
857
+ className="CTAMultiCardstyle__CardsSection-sc-gsdqzv-1 cywbFh"
858
858
  >
859
859
  <div
860
- className="CTAMultiCardstyle__CardsInner-sc-gsdqzv-2 kjrrbi"
860
+ className="CTAMultiCardstyle__CardsInner-sc-gsdqzv-2 iFzzWM"
861
861
  >
862
862
  <div
863
- className="CTAMultiCardstyle__CardsContainer-sc-gsdqzv-3 eomXiE"
863
+ className="CTAMultiCardstyle__CardsContainer-sc-gsdqzv-3 fqrDP"
864
864
  >
865
865
  <div
866
866
  className="CTACardstyle__CardWrapper-sc-si8xx1-5 eieQSs"
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+
3
+ var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
4
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.default = void 0;
9
+ var _throttle2 = _interopRequireDefault(require("lodash/throttle"));
10
+ var _floor2 = _interopRequireDefault(require("lodash/floor"));
11
+ var _orderBy2 = _interopRequireDefault(require("lodash/orderBy"));
12
+ var _react = _interopRequireWildcard(require("react"));
13
+ var _reactResponsive = require("react-responsive");
14
+ var _breakpoints = require("../../../theme/shared/breakpoints2026");
15
+ var _Button = _interopRequireDefault(require("../../Atoms/Button/Button"));
16
+ var _Lightbox = _interopRequireWildcard(require("./_Lightbox"));
17
+ var _DynamicGallery = require("./DynamicGallery.style");
18
+ var _DynamicGalleryColumn = _interopRequireDefault(require("./_DynamicGalleryColumn"));
19
+ var _types = require("./_types");
20
+ /**
21
+ * the Dynamic Gallery component displays a grid of images,
22
+ * by default using dynamic heights per image to create an more organic look
23
+ */
24
+ const DynamicGallery = _ref => {
25
+ let {
26
+ pageBackgroundColour = 'transparent',
27
+ textColour = 'black',
28
+ gridWidth = 3,
29
+ maxWidth = '1500px',
30
+ loadingBehaviour = '25',
31
+ imageRatio = 'dynamic',
32
+ useLightbox = true,
33
+ nodes = [],
34
+ paddingTop = '0rem',
35
+ paddingBottom = '2rem'
36
+ } = _ref;
37
+ const hasNodes = (nodes === null || nodes === void 0 ? void 0 : nodes.length) > 0;
38
+ const containerRef = (0, _react.useRef)(null);
39
+
40
+ // handle loading behaviour;
41
+ // if we're in chunk mode, display images a chunk at a time
42
+ // (or the total number of images if less than the chunk size)
43
+ // or display all images at once
44
+ const isChunked = loadingBehaviour !== 'all';
45
+ const imageChunkSize = +loadingBehaviour;
46
+ const [imageCount, setImageCount] = (0, _react.useState)(isChunked ? Math.min(imageChunkSize, nodes.length) : nodes.length);
47
+ function handleLoadMore() {
48
+ setImageCount(imageCount + imageChunkSize);
49
+ }
50
+
51
+ // assign a manual tabbing order to gallery images based on their position in the DOM,
52
+ // starting from top-left and working downwards in a natural order
53
+ function updateTabOrder() {
54
+ if (!containerRef.current) return;
55
+ const galleryNodes = containerRef.current.querySelectorAll('.gallery-node');
56
+ const sortedNodes = (0, _orderBy2.default)(galleryNodes, node => {
57
+ const {
58
+ top,
59
+ left
60
+ } = node.getBoundingClientRect();
61
+ return (0, _floor2.default)(top, -2) + Math.floor(left) / 1000;
62
+ }, 'asc');
63
+ sortedNodes.forEach((galleryNode, index) => {
64
+ galleryNode.setAttribute('data-order', String(index));
65
+ });
66
+ }
67
+ // create a throttled version of the updateTabOrder function
68
+ const throttledUpdateTabOrder = (0, _react.useRef)((0, _throttle2.default)(updateTabOrder, 2000));
69
+
70
+ /**
71
+ * handle column counts;
72
+ * column count is based on a combination of the maxColumns prop and the window width
73
+ * - for small screens columns = 1
74
+ * - for medium screens columns = 2
75
+ * - for large and xl screens we use the maxColumns prop which defaults to 3
76
+ * .
77
+ * we need to use JS here rather than CSS because our columns are created dynamically;
78
+ * this is to allow us to assign nodes in the natural "horizontal" order rather than "vertically"
79
+ */
80
+ const [columnCount, setColumnCount] = (0, _react.useState)(gridWidth);
81
+ const isSmall = (0, _reactResponsive.useMediaQuery)({
82
+ maxWidth: _breakpoints.breakpointValues2026.S
83
+ });
84
+ const isMedium = (0, _reactResponsive.useMediaQuery)({
85
+ maxWidth: _breakpoints.breakpointValues2026.M
86
+ });
87
+ (0, _react.useEffect)(() => {
88
+ let newColumnCount;
89
+ switch (true) {
90
+ case isSmall:
91
+ newColumnCount = 1;
92
+ break;
93
+ case isMedium:
94
+ newColumnCount = 2;
95
+ break;
96
+ default:
97
+ newColumnCount = gridWidth;
98
+ break;
99
+ }
100
+ setColumnCount(newColumnCount);
101
+ throttledUpdateTabOrder.current();
102
+ }, [isSmall, isMedium, gridWidth, setColumnCount]);
103
+
104
+ // handle selected gallery node
105
+ const [selectedNode, setSelectedNode] = (0, _react.useState)(null);
106
+
107
+ // handle next/previous node events from the lightbox
108
+ function handleNextNode(node) {
109
+ const nodeIndex = nodes.indexOf(node);
110
+ const nextNodeIndex = (nodeIndex + 1) % imageCount;
111
+ setSelectedNode(nodes[nextNodeIndex]);
112
+ }
113
+ function handlePreviousNode(node) {
114
+ const nodeIndex = nodes.indexOf(node);
115
+ const previousNodeIndex = (nodeIndex - 1 + imageCount) % imageCount;
116
+ setSelectedNode(nodes[previousNodeIndex]);
117
+ }
118
+
119
+ // handle keydown events,
120
+ // including image opening and tabbing
121
+ function handleKeyDown(event) {
122
+ switch (event.key) {
123
+ // if the lightbox is enabled, open the image in the lightbox when the user presses enter
124
+ case 'Enter':
125
+ {
126
+ if (useLightbox) {
127
+ event.preventDefault();
128
+ const nodeIndex = +event.target.dataset.nodeIndex;
129
+ if (Number.isNaN(nodeIndex)) return;
130
+ setSelectedNode(nodes[nodeIndex]);
131
+ }
132
+ break;
133
+ }
134
+ // handle tabbing between images;
135
+ // there doesn't seem to be a great way to handle this!
136
+ // it's tied into the way the grid is structured, and the way the nodes are rendered;
137
+ // ideal scenario would be a tabbable grid with nice ordering and no gaps,
138
+ // but this isn't currently possible without either getting a bit hacky with CSS or JS
139
+ // our options are:
140
+ // - use a standard CSS grid > ordered and tabble but gappy
141
+ // - use absolute positioning > no gaps but complex and weird DOM order (pinterest approach)
142
+ // - flex-column+order > no gaps but complex (https://mui.com/material-ui/react-masonry/)
143
+ // - columns + custom tabbing > what we're doing here
144
+ case 'Tab':
145
+ {
146
+ const nodeIndex = +event.target.dataset.order;
147
+ if (Number.isNaN(nodeIndex)) return;
148
+ const galleryContainer = event.target.closest('.gallery-container');
149
+ if (!galleryContainer) return;
150
+ let newNodeIndex;
151
+ if (event.shiftKey) {
152
+ // shift-tab: move to the previous image
153
+ newNodeIndex = nodeIndex - 1;
154
+ if (newNodeIndex < 0) return;
155
+ event.preventDefault();
156
+ galleryContainer.querySelector(`[data-order="${newNodeIndex}"]`).focus();
157
+ } else {
158
+ // tab: move to the next image
159
+ newNodeIndex = nodeIndex + 1;
160
+ if (newNodeIndex >= imageCount) {
161
+ // if we're on the last image, move to the focus trap
162
+ // before allowing the tab event to continue to the next natural element;
163
+ // this is a bit hacky but is needed for when the "last" image isn't in the last column;
164
+ // eg 10 images divided across 3 columns = [4, 3, 3]
165
+ // when this happens the browser tries to tab into the next column,
166
+ // rather than out of the grid and onwards
167
+ galleryContainer.querySelector('.gallery-focus-trap').focus();
168
+ return;
169
+ }
170
+ event.preventDefault();
171
+ galleryContainer.querySelector(`[data-order="${newNodeIndex}"]`).focus();
172
+ }
173
+ break;
174
+ }
175
+ default:
176
+ break;
177
+ }
178
+ }
179
+ return /*#__PURE__*/_react.default.createElement(_DynamicGallery.Container, {
180
+ className: "gallery-container",
181
+ ref: containerRef,
182
+ maxWidth: maxWidth,
183
+ pageBackgroundColour: pageBackgroundColour,
184
+ textColour: textColour,
185
+ paddingTop: paddingTop,
186
+ paddingBottom: paddingBottom
187
+ }, /*#__PURE__*/_react.default.createElement(_Lightbox.LightboxContext.Provider, {
188
+ value: {
189
+ useLightbox,
190
+ selectedNode,
191
+ setSelectedNode,
192
+ nextNode: handleNextNode,
193
+ previousNode: handlePreviousNode
194
+ }
195
+ }, /*#__PURE__*/_react.default.createElement(_DynamicGallery.ImageGrid, {
196
+ className: "gallery-grid",
197
+ onKeyDown: event => handleKeyDown(event)
198
+ }, hasNodes && Array(columnCount).fill(null).map((column, columnIndex) => /*#__PURE__*/_react.default.createElement(_DynamicGalleryColumn.default
199
+ // disabling the lint rule here
200
+ // as we're chunking an array and have no unique keys
201
+ // eslint-disable-next-line react/no-array-index-key
202
+ , {
203
+ key: columnIndex,
204
+ columnIndex: columnIndex,
205
+ columnCount: columnCount,
206
+ nodes: nodes.slice(0, imageCount),
207
+ imageRatio: imageRatio,
208
+ updateTabOrder: throttledUpdateTabOrder.current
209
+ })), /*#__PURE__*/_react.default.createElement(_DynamicGallery.EmptyMessage, {
210
+ isEmpty: !hasNodes
211
+ }, "No images to display")), /*#__PURE__*/_react.default.createElement(_Lightbox.default, null), /*#__PURE__*/_react.default.createElement("div", {
212
+ className: "gallery-focus-trap",
213
+ tabIndex: 0
214
+ })), imageCount < nodes.length && /*#__PURE__*/_react.default.createElement(_Button.default, {
215
+ onClick: () => handleLoadMore()
216
+ }, "Load more"));
217
+ };
218
+ var _default = exports.default = DynamicGallery;
@@ -0,0 +1,30 @@
1
+ # Dynamic Gallery
2
+
3
+ ### Empty gallery
4
+
5
+ ```js
6
+ <DynamicGallery />
7
+ ```
8
+
9
+ ### Basic gallery
10
+
11
+ ```js
12
+ const defaultData = require('../../../styleguide/data/data').defaultData;
13
+ import createMockGalleryNodes from './_utils';
14
+ <DynamicGallery nodes={createMockGalleryNodes(50)} />;
15
+ ```
16
+
17
+ ### Customised gallery with multiple options
18
+ ```js
19
+ const defaultData = require('../../../styleguide/data/data').defaultData;
20
+ import createMockGalleryNodes from './_utils';
21
+ <DynamicGallery gridWidth={4} nodes={createMockGalleryNodes(4)} loadingBehaviour="all" imageRatio="4:3" pageBackgroundColour="blue" textColour="white" paddingTop="6rem" paddingBottom="6rem" useLightbox={false} />;
22
+ ```
23
+
24
+ ### Gallery with max 5 columns
25
+
26
+ ```js
27
+ const defaultData = require('../../../styleguide/data/data').defaultData;
28
+ import createMockGalleryNodes from './_utils';
29
+ <DynamicGallery gridWidth={5} nodes={createMockGalleryNodes(5)} />;
30
+ ```
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+
3
+ var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.Title = exports.InteractiveGalleryNode = exports.ImageGrid = exports.ImageContainer = exports.GalleryNode = exports.EmptyMessage = exports.Details = exports.Container = exports.Column = exports.Caption = void 0;
8
+ var _styledComponents = _interopRequireWildcard(require("styled-components"));
9
+ const Container = exports.Container = _styledComponents.default.div.withConfig({
10
+ displayName: "DynamicGallerystyle__Container",
11
+ componentId: "sc-1kgt7yr-0"
12
+ })(["display:flex;flex-direction:column;align-items:center;gap:1rem;max-width:", ";background:", ";", " color:", ";"], _ref => {
13
+ let {
14
+ maxWidth
15
+ } = _ref;
16
+ return maxWidth;
17
+ }, _ref2 => {
18
+ let {
19
+ theme,
20
+ pageBackgroundColour
21
+ } = _ref2;
22
+ return theme.color(pageBackgroundColour);
23
+ }, _ref3 => {
24
+ let {
25
+ paddingTop,
26
+ paddingBottom
27
+ } = _ref3;
28
+ return (0, _styledComponents.css)(["padding:", " 2rem ", ";"], paddingTop, paddingBottom);
29
+ }, _ref4 => {
30
+ let {
31
+ theme,
32
+ textColour
33
+ } = _ref4;
34
+ return theme.color(textColour);
35
+ });
36
+ const ImageGrid = exports.ImageGrid = _styledComponents.default.div.withConfig({
37
+ displayName: "DynamicGallerystyle__ImageGrid",
38
+ componentId: "sc-1kgt7yr-1"
39
+ })(["display:flex;gap:1rem;width:100%;@media ", "{gap:2rem;}"], _ref5 => {
40
+ let {
41
+ theme
42
+ } = _ref5;
43
+ return theme.breakpoints2026('M');
44
+ });
45
+ const Column = exports.Column = _styledComponents.default.div.withConfig({
46
+ displayName: "DynamicGallerystyle__Column",
47
+ componentId: "sc-1kgt7yr-2"
48
+ })(["flex:1;display:flex;flex-direction:column;gap:1.1rem;@media ", "{gap:2rem;}"], _ref6 => {
49
+ let {
50
+ theme
51
+ } = _ref6;
52
+ return theme.breakpoints2026('M');
53
+ });
54
+ const EmptyMessage = exports.EmptyMessage = _styledComponents.default.div.withConfig({
55
+ displayName: "DynamicGallerystyle__EmptyMessage",
56
+ componentId: "sc-1kgt7yr-3"
57
+ })(["display:", ";"], _ref7 => {
58
+ let {
59
+ isEmpty
60
+ } = _ref7;
61
+ return isEmpty ? 'block' : 'none';
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;"]);
64
+ const GalleryNode = exports.GalleryNode = _styledComponents.default.div.withConfig({
65
+ displayName: "DynamicGallerystyle__GalleryNode",
66
+ componentId: "sc-1kgt7yr-4"
67
+ })(["", ""], GalleryNodeBase);
68
+ const InteractiveGalleryNode = exports.InteractiveGalleryNode = _styledComponents.default.button.withConfig({
69
+ displayName: "DynamicGallerystyle__InteractiveGalleryNode",
70
+ 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);
72
+ const ImageContainer = exports.ImageContainer = _styledComponents.default.div.withConfig({
73
+ displayName: "DynamicGallerystyle__ImageContainer",
74
+ 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 => {
76
+ let {
77
+ minHeight
78
+ } = _ref8;
79
+ return minHeight;
80
+ }, _ref9 => {
81
+ let {
82
+ maxHeight
83
+ } = _ref9;
84
+ return maxHeight;
85
+ });
86
+ const Details = exports.Details = _styledComponents.default.div.withConfig({
87
+ displayName: "DynamicGallerystyle__Details",
88
+ 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;"]);