@comicrelief/component-library 8.83.1 → 8.84.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 (44) hide show
  1. package/dist/index.cjs.js +1227 -859
  2. package/dist/index.es.js +10902 -10144
  3. package/package.json +1 -1
  4. package/src/App.styles.js +2 -2
  5. package/src/components/Atoms/Button/Button.js +13 -3
  6. package/src/components/Atoms/Button/Button.style.js +19 -1
  7. package/src/components/Atoms/Button/Button.test.js +1 -1
  8. package/src/components/Atoms/Button/ButtonExample.jsx +24 -0
  9. package/src/components/Atoms/ButtonWithStates/ButtonWithStates.js +1 -1
  10. package/src/components/Atoms/Icons/Cross.js +0 -1
  11. package/src/components/Atoms/Icons/Download.js +4 -4
  12. package/src/components/Atoms/Icons/External.js +4 -4
  13. package/src/components/Atoms/Icons/Filter.js +50 -0
  14. package/src/components/Atoms/Icons/IconsExample.jsx +9 -5
  15. package/src/components/Atoms/Icons/Undo.js +44 -0
  16. package/src/components/Atoms/Icons/index.js +2 -0
  17. package/src/components/Atoms/Link/Link.test.js +1 -1
  18. package/src/components/Atoms/Link/LinkExample.jsx +4 -2
  19. package/src/components/Atoms/Text/TextExample.jsx +338 -130
  20. package/src/components/Molecules/CardDs/__snapshots__/CardDs.test.js.snap +2 -2
  21. package/src/components/Molecules/Promo/__snapshots__/Promo.test.js.snap +2 -2
  22. package/src/components/Molecules/QuoteSlice/__snapshots__/QuoteSlice.test.js.snap +1 -1
  23. package/src/components/Molecules/SingleMessage/__snapshots__/SingleMessage.test.js.snap +2 -2
  24. package/src/components/Molecules/SingleMessageDS/__snapshots__/SingleMessageDs.test.js.snap +1 -3
  25. package/src/components/Organisms/CookieBanner/CookieBanner.test.js +1 -1
  26. package/src/components/Organisms/EmailBanner/__snapshots__/EmailBanner.test.js.snap +12 -12
  27. package/src/components/Organisms/EmailSignUp/__snapshots__/EmailSignUp.test.js.snap +3 -3
  28. package/src/components/Organisms/FilterCard/FilterCard.js +286 -0
  29. package/src/components/Organisms/FilterCard/FilterCard.style.js +425 -0
  30. package/src/components/Organisms/FilterCard/FilterCard.test.js +60 -0
  31. package/src/components/Organisms/FilterCard/FilterCardExample.jsx +48 -0
  32. package/src/components/Organisms/FilterCard/FilterCardNodes.js +155 -0
  33. package/src/components/Organisms/FilterCard/__snapshots__/FilterCard.test.js.snap +1006 -0
  34. package/src/components/Organisms/FilterCard/_utils/_utils.js +57 -0
  35. package/src/components/Organisms/FilterCard/assets/Rectangle.svg +3 -0
  36. package/src/components/Organisms/FooterNew/__snapshots__/FooterNew.test.js.snap +4 -4
  37. package/src/components/Organisms/MarketingPreferencesDS/MarketingPreferencesDS.style.js +1 -1
  38. package/src/data/TestFilterCardNodeData.js +930 -0
  39. package/src/data/data.js +64 -1
  40. package/src/data/test-icons/Children--black.svg +5 -0
  41. package/src/data/test-icons/Children--white.svg +5 -0
  42. package/src/demos/index.jsx +31 -9
  43. package/src/index.js +1 -0
  44. package/src/theme/crTheme/buttonColors.js +3 -4
@@ -41,12 +41,12 @@ exports[`Image banner left orientation renders correctly 1`] = `
41
41
 
42
42
  .c21:disabled {
43
43
  cursor: not-allowed;
44
- opacity: 0.5;
44
+ opacity: 0.35;
45
45
  }
46
46
 
47
47
  .c22:disabled {
48
48
  cursor: not-allowed;
49
- opacity: 0.5;
49
+ opacity: 0.35;
50
50
  }
51
51
 
52
52
  .c6 {
@@ -621,7 +621,7 @@ exports[`Image banner left orientation renders correctly 1`] = `
621
621
  <button
622
622
  className="c21 c22"
623
623
  data-test="subscribe-button"
624
- data-testid="Button"
624
+ data-testid="ButtonWithStates"
625
625
  disabled={false}
626
626
  type="submit"
627
627
  >
@@ -681,12 +681,12 @@ exports[`Image banner renders correctly 1`] = `
681
681
 
682
682
  .c21:disabled {
683
683
  cursor: not-allowed;
684
- opacity: 0.5;
684
+ opacity: 0.35;
685
685
  }
686
686
 
687
687
  .c22:disabled {
688
688
  cursor: not-allowed;
689
- opacity: 0.5;
689
+ opacity: 0.35;
690
690
  }
691
691
 
692
692
  .c6 {
@@ -1261,7 +1261,7 @@ exports[`Image banner renders correctly 1`] = `
1261
1261
  <button
1262
1262
  className="c21 c22"
1263
1263
  data-test="subscribe-button"
1264
- data-testid="Button"
1264
+ data-testid="ButtonWithStates"
1265
1265
  disabled={false}
1266
1266
  type="submit"
1267
1267
  >
@@ -1321,12 +1321,12 @@ exports[`Text-only email widget renders correctly 1`] = `
1321
1321
 
1322
1322
  .c21:disabled {
1323
1323
  cursor: not-allowed;
1324
- opacity: 0.5;
1324
+ opacity: 0.35;
1325
1325
  }
1326
1326
 
1327
1327
  .c22:disabled {
1328
1328
  cursor: not-allowed;
1329
- opacity: 0.5;
1329
+ opacity: 0.35;
1330
1330
  }
1331
1331
 
1332
1332
  .c6 {
@@ -1901,7 +1901,7 @@ exports[`Text-only email widget renders correctly 1`] = `
1901
1901
  <button
1902
1902
  className="c21 c22"
1903
1903
  data-test="subscribe-button"
1904
- data-testid="Button"
1904
+ data-testid="ButtonWithStates"
1905
1905
  disabled={false}
1906
1906
  type="submit"
1907
1907
  >
@@ -1961,12 +1961,12 @@ exports[`Text-only email widget with copyColor renders correctly 1`] = `
1961
1961
 
1962
1962
  .c21:disabled {
1963
1963
  cursor: not-allowed;
1964
- opacity: 0.5;
1964
+ opacity: 0.35;
1965
1965
  }
1966
1966
 
1967
1967
  .c22:disabled {
1968
1968
  cursor: not-allowed;
1969
- opacity: 0.5;
1969
+ opacity: 0.35;
1970
1970
  }
1971
1971
 
1972
1972
  .c6 {
@@ -2541,7 +2541,7 @@ exports[`Text-only email widget with copyColor renders correctly 1`] = `
2541
2541
  <button
2542
2542
  className="c21 c22"
2543
2543
  data-test="subscribe-button"
2544
- data-testid="Button"
2544
+ data-testid="ButtonWithStates"
2545
2545
  disabled={false}
2546
2546
  type="submit"
2547
2547
  >
@@ -41,12 +41,12 @@ exports[`renders correctly 1`] = `
41
41
 
42
42
  .c15:disabled {
43
43
  cursor: not-allowed;
44
- opacity: 0.5;
44
+ opacity: 0.35;
45
45
  }
46
46
 
47
47
  .c16:disabled {
48
48
  cursor: not-allowed;
49
- opacity: 0.5;
49
+ opacity: 0.35;
50
50
  }
51
51
 
52
52
  .c1 {
@@ -442,7 +442,7 @@ exports[`renders correctly 1`] = `
442
442
  <button
443
443
  className="c15 c16"
444
444
  data-test="subscribe-button"
445
- data-testid="Button"
445
+ data-testid="ButtonWithStates"
446
446
  disabled={true}
447
447
  type="submit"
448
448
  >
@@ -0,0 +1,286 @@
1
+ import React, {
2
+ useState, useEffect, useCallback, useRef
3
+ } from 'react';
4
+ import PropTypes from 'prop-types';
5
+ import PulseLoader from 'react-spinners/PulseLoader';
6
+ import Cross from '../../Atoms/Icons/Cross';
7
+ import Filter from '../../Atoms/Icons/Filter';
8
+ import Undo from '../../Atoms/Icons/Undo';
9
+ import Text from '../../Atoms/Text/Text';
10
+ import FilterCardNodes from './FilterCardNodes';
11
+ import { preprocessNodes, renderNodeLimit } from './_utils/_utils';
12
+ import Button from '../../Atoms/Button/Button';
13
+
14
+ import {
15
+ Container,
16
+ OuterWrapper,
17
+ HeaderWrapper,
18
+ Title,
19
+ Body,
20
+ ControlsWrapper,
21
+ ShowHideFiltersButton,
22
+ ClearSelectionButton,
23
+ ResultsWrapper,
24
+ FilterButtonsWrapper,
25
+ FilterButton,
26
+ ShowMoreButtonWrapper
27
+ } from './FilterCard.style';
28
+
29
+ const FilterCard = ({ data }) => {
30
+ const {
31
+ body,
32
+ id,
33
+ filterCardNodes,
34
+ title,
35
+ firstFilterCardNodeAsHero = false,
36
+ loadingBehaviour = null,
37
+ paddingAbove = '0rem',
38
+ paddingBelow = '1rem',
39
+ pageBackgroundColour = 'transparent'
40
+ } = data;
41
+
42
+ // Not currently a CMS option, but keeping it extrapolated for good measure for now:
43
+ const showMoreIncrement = 6;
44
+
45
+ // Load state and content:
46
+ const [processedNodes, setProcessedNodes] = useState(false);
47
+ const [processedTags, setProcessedTags] = useState(false);
48
+ const [hideShowMoreButton, setHideShowMoreButton] = useState(false);
49
+ const initialNodeLimit = useRef(renderNodeLimit(loadingBehaviour, firstFilterCardNodeAsHero));
50
+
51
+ // Keep track of user interactions:
52
+ const [showFilters, setShowFilters] = useState(false);
53
+ const [currentFilters, setCurrentFilters] = useState([]);
54
+ const [totalNodes, setTotalNodes] = useState(0);
55
+
56
+ // Determine our display behaviour based on the CMS option
57
+ const [nodeRenderLimit, setNodeRenderLimit] = useState(initialNodeLimit.current);
58
+
59
+ // Transform the Contentful output for our use case on initialisation:
60
+ useEffect(() => {
61
+ let preprocessTimer;
62
+
63
+ const { outputNodes, outputTags } = preprocessNodes(filterCardNodes);
64
+
65
+ // Only go ahead once we've got the goods:
66
+ if (outputNodes && outputTags) {
67
+ // Pause for a second before going ahead, to prevent nasty Loader
68
+ // flashes, and let the user register that something has happened:
69
+ preprocessTimer = setTimeout(() => {
70
+ setProcessedNodes(outputNodes);
71
+ setProcessedTags(outputTags);
72
+ }, 1000);
73
+ }
74
+ return () => clearTimeout(preprocessTimer);
75
+ }, [filterCardNodes]);
76
+
77
+ // Adds/removes a clicked filter tag button from the state array accordingly:
78
+ const updateFilters = useCallback(thisTag => {
79
+ // When updating filters, reset render and 'Show More' displays config back to defaults:
80
+ setNodeRenderLimit(initialNodeLimit.current);
81
+ setHideShowMoreButton(true);
82
+
83
+ // Cache current state:
84
+ let updatedFilters = currentFilters;
85
+
86
+ if (updatedFilters.includes(thisTag)) {
87
+ // Create 2 slices either side of the tag we're removing, and combine into a new array
88
+ updatedFilters = [
89
+ ...updatedFilters.slice(0, updatedFilters.indexOf(thisTag)),
90
+ ...updatedFilters.slice(updatedFilters.indexOf(thisTag) + 1)
91
+ ];
92
+ } else {
93
+ updatedFilters.push(thisTag);
94
+ }
95
+
96
+ // Spread as a new array to trigger re-render:
97
+ setCurrentFilters([...updatedFilters]);
98
+ }, [currentFilters]);
99
+
100
+ const showHideFilterActive = !(!showFilters && currentFilters.length === 0);
101
+
102
+ const currentTotal = `${totalNodes} ${totalNodes === 1 ? 'result' : 'results'}`;
103
+
104
+ return (
105
+ <Container
106
+ $paddingAbove={paddingAbove}
107
+ $paddingBelow={paddingBelow}
108
+ $pageBackgroundColour={pageBackgroundColour}
109
+ data-testid="FilterCard"
110
+ id={id}
111
+ >
112
+ <OuterWrapper>
113
+ <HeaderWrapper>
114
+
115
+ <Title tag="h1">
116
+ {title}
117
+ </Title>
118
+
119
+ <Body>
120
+ {body}
121
+ </Body>
122
+
123
+ {/* Only render the controls once all content has been fully processed: */}
124
+ {(processedTags && processedNodes) ? (
125
+ <>
126
+ <ControlsWrapper>
127
+ <ShowHideFiltersButton
128
+ color="white"
129
+ $isActive={showHideFilterActive}
130
+ // As this button is acting as a checkbox (toggle-style),
131
+ // let assistive technologies know its role and state:
132
+ role="checkbox"
133
+ aria-checked={showFilters}
134
+ onClick={() => { setShowFilters(!showFilters); }}
135
+ icon={showFilters ? <Cross /> : <Filter />}
136
+ data-testid="FilterCard-ShowHideButton"
137
+ >
138
+ {showFilters ? 'Hide Filters' : 'Show Filters'}
139
+ </ShowHideFiltersButton>
140
+
141
+ <ClearSelectionButton
142
+ color="white"
143
+ icon={<Undo />}
144
+ $show={showHideFilterActive}
145
+ disabled={currentFilters.length === 0}
146
+ onClick={() => {
147
+ // Reset everything:
148
+ setCurrentFilters([]);
149
+ setNodeRenderLimit(initialNodeLimit.current);
150
+ setHideShowMoreButton(true);
151
+ }}
152
+ data-testid="FilterCard-ClearSelectionButton"
153
+ >
154
+ Clear selection
155
+ </ClearSelectionButton>
156
+
157
+ <ResultsWrapper
158
+ data-testid="FilterCard-Results"
159
+ role="status"
160
+ >
161
+ <Text tag="span">
162
+ { currentTotal }
163
+ </Text>
164
+ </ResultsWrapper>
165
+
166
+ </ControlsWrapper>
167
+
168
+ <FilterButtonsWrapper
169
+ $show={showFilters}
170
+ data-testid="FilterCard-FilterButtonsWrapper"
171
+ >
172
+ {Object.keys(processedTags).map(key => {
173
+ const { tag, selectedIcon, unselectedIcon } = processedTags[key];
174
+ const isSelected = currentFilters.includes(tag);
175
+
176
+ return (
177
+ <FilterButton
178
+ key={tag}
179
+ type="button"
180
+ color="grey_light"
181
+ value={tag}
182
+ name={tag}
183
+ iconLeft
184
+ $isSelected={isSelected}
185
+ // Also acting as a checkbox, so let assistive technologies know too:
186
+ role="checkbox"
187
+ aria-checked={isSelected}
188
+ icon={isSelected ? selectedIcon : unselectedIcon}
189
+ onClick={() => { updateFilters(tag); }}
190
+ // Force a re-render to trigger our flash-reducing, fade-in CSS animation:
191
+ iconKey={`${isSelected}`}
192
+ data-testid="FilterCard-FilterButton"
193
+ >
194
+ {tag}
195
+ </FilterButton>
196
+ );
197
+ })}
198
+ </FilterButtonsWrapper>
199
+ </>
200
+ )
201
+ // If content is still loading, shown the nifty loader:
202
+ : <PulseLoader color="black" style={{ textAlign: 'center', display: 'block' }} />
203
+ }
204
+ </HeaderWrapper>
205
+
206
+ {/* Render the content itself once it's fully processed */}
207
+ {(processedTags && processedNodes) && (
208
+ <FilterCardNodes
209
+ processedNodes={processedNodes}
210
+ firstFilterCardNodeAsHero={firstFilterCardNodeAsHero}
211
+ nodeRenderLimit={nodeRenderLimit}
212
+ currentFilters={currentFilters}
213
+ updateTotalResult={setTotalNodes}
214
+ hideShowMoreButton={setHideShowMoreButton}
215
+ data-testid="FilterCard-Nodes"
216
+ />
217
+ )}
218
+
219
+ {/* Only show the Show More button when appropriate,
220
+ hideShowMoreButton set within FilterCardNodes */}
221
+ {(!hideShowMoreButton && processedTags && processedNodes) && (
222
+ <ShowMoreButtonWrapper>
223
+ <Button
224
+ onClick={() => setNodeRenderLimit(nodeRenderLimit + showMoreIncrement)}
225
+ data-testid="FilterCard-ShowMoreButton"
226
+ >
227
+ Show more
228
+ </Button>
229
+ </ShowMoreButtonWrapper>
230
+ )}
231
+
232
+ </OuterWrapper>
233
+ </Container>
234
+ );
235
+ };
236
+
237
+ FilterCard.propTypes = {
238
+ // CMS content:
239
+ data: PropTypes.shape({
240
+ id: PropTypes.string.isRequired,
241
+ title: PropTypes.string.isRequired,
242
+ body: PropTypes.oneOfType([
243
+ PropTypes.node,
244
+ PropTypes.string
245
+ ]).isRequired,
246
+ paddingAbove: PropTypes.string,
247
+ paddingBelow: PropTypes.string,
248
+ pageBackgroundColour: PropTypes.string,
249
+ loadingBehaviour: PropTypes.string,
250
+ firstFilterCardNodeAsHero: PropTypes.bool,
251
+ filterCardNodes: PropTypes.arrayOf(
252
+ PropTypes.shape({
253
+ id: PropTypes.string.isRequired,
254
+ title: PropTypes.string.isRequired,
255
+ label: PropTypes.string.isRequired,
256
+ heading: PropTypes.string.isRequired,
257
+ ctaText: PropTypes.string.isRequired,
258
+ url: PropTypes.string.isRequired,
259
+ description: PropTypes.string,
260
+ // GatsbyImageData deconstruction to happen within CRcom repo to feed these:
261
+ image: PropTypes.string.isRequired,
262
+ imageLow: PropTypes.string.isRequired,
263
+ imageSet: PropTypes.string.isRequired,
264
+ imageAltText: PropTypes.string.isRequired,
265
+ filterTags: PropTypes.arrayOf(
266
+ PropTypes.shape({
267
+ tag: PropTypes.string.isRequired,
268
+ title: PropTypes.string.isRequired,
269
+ filterIconSelected: PropTypes.shape({
270
+ file: PropTypes.shape({
271
+ url: PropTypes.string.isRequired
272
+ })
273
+ }),
274
+ filterIconUnselected: PropTypes.shape({
275
+ file: PropTypes.shape({
276
+ url: PropTypes.string.isRequired
277
+ })
278
+ })
279
+ })
280
+ )
281
+ })
282
+ ).isRequired
283
+ }).isRequired
284
+ };
285
+
286
+ export default FilterCard;