@comicrelief/component-library 8.44.3 → 8.45.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 (123) hide show
  1. package/dist/components/Atoms/Checkbox/Checkbox.test.js +0 -4
  2. package/dist/components/Atoms/ErrorText/__snapshots__/ErrorText.test.js.snap +0 -2
  3. package/dist/components/Atoms/Input/input.test.js +0 -2
  4. package/dist/components/Atoms/RadioButton/RadioButton.test.js +0 -4
  5. package/dist/components/Atoms/Select/__snapshots__/Select.test.js.snap +0 -1
  6. package/dist/components/Atoms/SocialIcons/Icon/Icon.js +39 -14
  7. package/dist/components/Atoms/SocialIcons/SocialIcons.js +91 -22
  8. package/dist/components/Atoms/SocialIcons/Utils/Icons.js +26 -7
  9. package/dist/components/Atoms/SocialIcons/Utils/Links.js +10 -0
  10. package/dist/components/Atoms/SocialIcons/__snapshots__/SocialIcons.test.js.snap +63 -48
  11. package/{src/components/Atoms/SocialIcons/assets → dist/components/Atoms/SocialIcons/assets/circled}/facebook.svg +1 -1
  12. package/{src/components/Atoms/SocialIcons/assets → dist/components/Atoms/SocialIcons/assets/circled}/twitter.svg +1 -1
  13. package/dist/components/Atoms/SocialIcons/assets/{youtube.svg → circled/youtube.svg} +1 -1
  14. package/dist/components/Atoms/SocialIcons/assets/standard/facebook.svg +3 -0
  15. package/dist/components/Atoms/SocialIcons/assets/standard/instagram.svg +3 -0
  16. package/dist/components/Atoms/SocialIcons/assets/standard/tiktok.svg +3 -0
  17. package/dist/components/Atoms/SocialIcons/assets/standard/x.svg +3 -0
  18. package/dist/components/Atoms/SocialIcons/assets/standard/youtube.svg +3 -0
  19. package/dist/components/Atoms/Text/Text.js +28 -28
  20. package/dist/components/Atoms/Text/Text.md +8 -8
  21. package/dist/components/Atoms/Text/__snapshots__/Text.test.js.snap +0 -9
  22. package/dist/components/Atoms/TextArea/TextArea.test.js +0 -1
  23. package/dist/components/Atoms/TextInputWithDropdown/__snapshots__/TextInputWithDropdown.test.js.snap +0 -7
  24. package/dist/components/Molecules/Accordion/__snapshots__/Accordion.test.js.snap +0 -4
  25. package/dist/components/Molecules/ArticleTeaser/ArticleTeaser.test.js +0 -10
  26. package/dist/components/Molecules/Banner/__snapshots__/Banner.test.js.snap +0 -2
  27. package/dist/components/Molecules/Descriptor/Descriptor.test.js +0 -13
  28. package/dist/components/Molecules/EmailSignUp/EmailSignUp.js +38 -0
  29. package/dist/components/Molecules/EmailSignUp/EmailSignUp.style.js +113 -0
  30. package/dist/components/Molecules/InfoBanner/__snapshots__/InfoBanner.test.js.snap +0 -24
  31. package/dist/components/Molecules/LogoLinked/LogoLinked.js +6 -6
  32. package/dist/components/Molecules/PartnerLink/PartnerLink.test.js +0 -2
  33. package/dist/components/Molecules/Promo/__snapshots__/Promo.test.js.snap +0 -8
  34. package/dist/components/Molecules/SchoolLookup/__snapshots__/SchoolLookup.test.js.snap +0 -1
  35. package/dist/components/Molecules/SearchInput/SearchInput.test.js +0 -1
  36. package/dist/components/Molecules/SearchResult/__snapshots__/SearchResult.test.js.snap +0 -18
  37. package/dist/components/Molecules/SingleMessage/__snapshots__/SingleMessage.test.js.snap +0 -14
  38. package/dist/components/Molecules/SingleMessageDS/__snapshots__/SingleMessageDs.test.js.snap +0 -4
  39. package/dist/components/Molecules/Typeahead/__snapshots__/Typeahead.test.js.snap +0 -1
  40. package/dist/components/Organisms/CookieBanner/CookieBanner.test.js +0 -2
  41. package/dist/components/Organisms/Donate/__snapshots__/Donate.test.js.snap +0 -35
  42. package/dist/components/Organisms/EmailSignUp/__snapshots__/EmailSignUp.test.js.snap +0 -8
  43. package/dist/components/Organisms/Footer/Footer.md +12 -11
  44. package/dist/components/Organisms/Footer/FundraisingRegulatorLogo/FundraisingRegulatorLogo.js +36 -16
  45. package/dist/components/Organisms/Footer/Nav/Nav.style.js +8 -8
  46. package/dist/components/Organisms/Footer/__snapshots__/Footer.test.js.snap +188 -241
  47. package/dist/components/Organisms/FooterNew/FooterNew.js +136 -0
  48. package/dist/components/Organisms/FooterNew/FooterNew.md +47 -0
  49. package/dist/components/Organisms/FooterNew/FooterNew.style.js +312 -0
  50. package/dist/components/Organisms/FooterNew/FooterNew.test.js +20 -0
  51. package/dist/components/Organisms/FooterNew/Nav/PrimaryNav.js +32 -0
  52. package/dist/components/Organisms/FooterNew/Nav/SecondaryNav.js +32 -0
  53. package/dist/components/Organisms/FooterNew/__snapshots__/FooterNew.test.js.snap +1490 -0
  54. package/dist/components/Organisms/FooterNew/dev-data/data.js +106 -0
  55. package/dist/components/Organisms/Membership/Membership.test.js +0 -12
  56. package/dist/components/Organisms/WYMDCarousel/__snapshots__/WYMDCarousel.test.js.snap +0 -3
  57. package/dist/index.js +20 -0
  58. package/dist/theme/crTheme/colors.js +12 -7
  59. package/dist/theme/shared/animations.js +46 -0
  60. package/package.json +1 -1
  61. package/src/components/Atoms/Checkbox/Checkbox.test.js +0 -4
  62. package/src/components/Atoms/ErrorText/__snapshots__/ErrorText.test.js.snap +0 -2
  63. package/src/components/Atoms/Input/input.test.js +0 -2
  64. package/src/components/Atoms/RadioButton/RadioButton.test.js +0 -4
  65. package/src/components/Atoms/Select/__snapshots__/Select.test.js.snap +0 -1
  66. package/src/components/Atoms/SocialIcons/Icon/Icon.js +47 -11
  67. package/src/components/Atoms/SocialIcons/SocialIcons.js +99 -25
  68. package/src/components/Atoms/SocialIcons/Utils/Icons.js +29 -10
  69. package/src/components/Atoms/SocialIcons/Utils/Links.js +10 -0
  70. package/src/components/Atoms/SocialIcons/__snapshots__/SocialIcons.test.js.snap +63 -48
  71. package/{dist/components/Atoms/SocialIcons/assets → src/components/Atoms/SocialIcons/assets/circled}/facebook.svg +1 -1
  72. package/{dist/components/Atoms/SocialIcons/assets → src/components/Atoms/SocialIcons/assets/circled}/twitter.svg +1 -1
  73. package/src/components/Atoms/SocialIcons/assets/{youtube.svg → circled/youtube.svg} +1 -1
  74. package/src/components/Atoms/SocialIcons/assets/standard/facebook.svg +3 -0
  75. package/src/components/Atoms/SocialIcons/assets/standard/instagram.svg +3 -0
  76. package/src/components/Atoms/SocialIcons/assets/standard/tiktok.svg +3 -0
  77. package/src/components/Atoms/SocialIcons/assets/standard/x.svg +3 -0
  78. package/src/components/Atoms/SocialIcons/assets/standard/youtube.svg +3 -0
  79. package/src/components/Atoms/Text/Text.js +19 -19
  80. package/src/components/Atoms/Text/Text.md +8 -8
  81. package/src/components/Atoms/Text/__snapshots__/Text.test.js.snap +0 -9
  82. package/src/components/Atoms/TextArea/TextArea.test.js +0 -1
  83. package/src/components/Atoms/TextInputWithDropdown/__snapshots__/TextInputWithDropdown.test.js.snap +0 -7
  84. package/src/components/Molecules/Accordion/__snapshots__/Accordion.test.js.snap +0 -4
  85. package/src/components/Molecules/ArticleTeaser/ArticleTeaser.test.js +0 -10
  86. package/src/components/Molecules/Banner/__snapshots__/Banner.test.js.snap +0 -2
  87. package/src/components/Molecules/Descriptor/Descriptor.test.js +0 -13
  88. package/src/components/Molecules/EmailSignUp/EmailSignUp.js +55 -0
  89. package/src/components/Molecules/EmailSignUp/EmailSignUp.style.js +107 -0
  90. package/src/components/Molecules/InfoBanner/__snapshots__/InfoBanner.test.js.snap +0 -24
  91. package/src/components/Molecules/LogoLinked/LogoLinked.js +5 -14
  92. package/src/components/Molecules/PartnerLink/PartnerLink.test.js +0 -2
  93. package/src/components/Molecules/Promo/__snapshots__/Promo.test.js.snap +0 -8
  94. package/src/components/Molecules/SchoolLookup/__snapshots__/SchoolLookup.test.js.snap +0 -1
  95. package/src/components/Molecules/SearchInput/SearchInput.test.js +0 -1
  96. package/src/components/Molecules/SearchResult/__snapshots__/SearchResult.test.js.snap +0 -18
  97. package/src/components/Molecules/SingleMessage/__snapshots__/SingleMessage.test.js.snap +0 -14
  98. package/src/components/Molecules/SingleMessageDS/__snapshots__/SingleMessageDs.test.js.snap +0 -4
  99. package/src/components/Molecules/Typeahead/__snapshots__/Typeahead.test.js.snap +0 -1
  100. package/src/components/Organisms/CookieBanner/CookieBanner.test.js +0 -2
  101. package/src/components/Organisms/Donate/__snapshots__/Donate.test.js.snap +0 -35
  102. package/src/components/Organisms/EmailSignUp/__snapshots__/EmailSignUp.test.js.snap +0 -8
  103. package/src/components/Organisms/Footer/Footer.md +12 -11
  104. package/src/components/Organisms/Footer/FundraisingRegulatorLogo/FundraisingRegulatorLogo.js +14 -3
  105. package/src/components/Organisms/Footer/Nav/Nav.style.js +8 -8
  106. package/src/components/Organisms/Footer/__snapshots__/Footer.test.js.snap +188 -241
  107. package/src/components/Organisms/FooterNew/FooterNew.js +211 -0
  108. package/src/components/Organisms/FooterNew/FooterNew.md +47 -0
  109. package/src/components/Organisms/FooterNew/FooterNew.style.js +294 -0
  110. package/src/components/Organisms/FooterNew/FooterNew.test.js +24 -0
  111. package/src/components/Organisms/FooterNew/Nav/PrimaryNav.js +54 -0
  112. package/src/components/Organisms/FooterNew/Nav/SecondaryNav.js +54 -0
  113. package/src/components/Organisms/FooterNew/__snapshots__/FooterNew.test.js.snap +1490 -0
  114. package/src/components/Organisms/FooterNew/dev-data/data.js +123 -0
  115. package/src/components/Organisms/Membership/Membership.test.js +0 -12
  116. package/src/components/Organisms/WYMDCarousel/__snapshots__/WYMDCarousel.test.js.snap +0 -3
  117. package/src/index.js +2 -0
  118. package/src/theme/crTheme/colors.js +13 -7
  119. package/src/theme/shared/animations.js +60 -0
  120. /package/dist/components/Atoms/SocialIcons/assets/{X-white-Subtract.svg → circled/X-white-Subtract.svg} +0 -0
  121. /package/dist/components/Atoms/SocialIcons/assets/{instagram.svg → circled/instagram.svg} +0 -0
  122. /package/src/components/Atoms/SocialIcons/assets/{X-white-Subtract.svg → circled/X-white-Subtract.svg} +0 -0
  123. /package/src/components/Atoms/SocialIcons/assets/{instagram.svg → circled/instagram.svg} +0 -0
@@ -0,0 +1,211 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { useForm, FormProvider } from 'react-hook-form';
4
+ import { yupResolver } from '@hookform/resolvers/yup';
5
+ import * as yup from 'yup';
6
+ import Logo from '../../Atoms/Logo/Logo';
7
+ import SocialIcons from '../../Atoms/SocialIcons/SocialIcons';
8
+ import EmailSignUp from '../../Molecules/EmailSignUp/EmailSignUp';
9
+ import PrimaryNav from './Nav/PrimaryNav';
10
+ import SecondaryNav from './Nav/SecondaryNav';
11
+ import FundraisingRegulatorLogo from '../Footer/FundraisingRegulatorLogo/FundraisingRegulatorLogo';
12
+
13
+ import {
14
+ FooterWrapper,
15
+ InnerWrapper,
16
+ FooterLegalLine,
17
+ TopSection,
18
+ TopSectionLeft,
19
+ NewsletterSignUpWrapper,
20
+ SocialIconWrapper,
21
+ LogosContainer,
22
+ FooterCopyright,
23
+ FooterCopyrightText,
24
+ Brand
25
+ } from './FooterNew.style';
26
+
27
+ const FooterNew = ({
28
+ legalText,
29
+ copyrightText,
30
+ showFacebookSocialIcon,
31
+ showInstagramSocialIcon,
32
+ showXSocialIcon,
33
+ showTikTokSocialIcon,
34
+ showYouTubeSocialIcon,
35
+ onNewsletterSubmit,
36
+ primaryLinksList = [],
37
+ secondaryLinksList = [],
38
+ campaign = 'Comic Relief',
39
+ additionalLegalLine = '',
40
+ showFundraisingRegulatorLogo = false,
41
+ showEmailSignup = true,
42
+ animateRotate = true,
43
+ ...rest
44
+ }) => {
45
+ // Remove white space between words
46
+ const campaignName = campaign.replace(/\s/g, '').toLowerCase();
47
+
48
+ const currentYear = new Date().getFullYear();
49
+ const defaultCopyrightText = `© ${currentYear} Comic Relief`;
50
+ const displayCopyrightText = copyrightText !== undefined ? copyrightText : defaultCopyrightText;
51
+
52
+ const validationSchema = yup.object({
53
+ email: yup
54
+ .string()
55
+ .nullable()
56
+ .transform(value => (value === '' ? null : value))
57
+ .email('Please provide a valid email address')
58
+ });
59
+
60
+ const formMethods = useForm({
61
+ mode: 'onBlur',
62
+ resolver: yupResolver(validationSchema)
63
+ });
64
+
65
+ const { handleSubmit } = formMethods;
66
+
67
+ const handleNewsletterSubmit = data => {
68
+ if (onNewsletterSubmit) {
69
+ onNewsletterSubmit(data.email);
70
+ }
71
+ };
72
+
73
+ return (
74
+ <div>
75
+ <FooterWrapper {...rest}>
76
+ <InnerWrapper>
77
+ {additionalLegalLine && (
78
+ <FooterLegalLine>
79
+ {additionalLegalLine}
80
+ </FooterLegalLine>
81
+ )}
82
+
83
+ <LogosContainer $mobileOnly>
84
+ <Brand href="/" title={`Go to ${campaign} homepage`} animateRotate={animateRotate}>
85
+ <Logo sizeSm="48px" sizeMd="72px" rotate={false} campaign={campaign} />
86
+ </Brand>
87
+ </LogosContainer>
88
+
89
+ <TopSection $hasEmailSignup={showEmailSignup}>
90
+ <TopSectionLeft>
91
+ {showEmailSignup && (
92
+ <NewsletterSignUpWrapper>
93
+ <FormProvider {...formMethods}>
94
+ <form onSubmit={handleSubmit(handleNewsletterSubmit)} noValidate>
95
+ <EmailSignUp formContext={formMethods} />
96
+ </form>
97
+ </FormProvider>
98
+ </NewsletterSignUpWrapper>
99
+ )}
100
+ <SocialIconWrapper $desktopOnly $inTopSection>
101
+ <SocialIcons
102
+ campaign={campaignName}
103
+ newStyle
104
+ invertColor
105
+ showFacebookSocialIcon={showFacebookSocialIcon}
106
+ showInstagramSocialIcon={showInstagramSocialIcon}
107
+ showXSocialIcon={showXSocialIcon}
108
+ showTikTokSocialIcon={showTikTokSocialIcon}
109
+ showYouTubeSocialIcon={showYouTubeSocialIcon}
110
+ />
111
+ </SocialIconWrapper>
112
+ </TopSectionLeft>
113
+ <LogosContainer $desktopOnly $showCRLogoOnly>
114
+ <Brand href="/" title={`Go to ${campaign} homepage`} animateRotate={animateRotate}>
115
+ <Logo sizeSm="59px" sizeMd="59px" rotate={false} campaign={campaign} />
116
+ </Brand>
117
+ </LogosContainer>
118
+ <LogosContainer $desktopOnly $showPairedLogos>
119
+ <Brand href="/" title={`Go to ${campaign} homepage`} animateRotate={animateRotate}>
120
+ <Logo sizeSm="59px" sizeMd="59px" rotate={false} campaign={campaign} />
121
+ </Brand>
122
+ {showFundraisingRegulatorLogo && <FundraisingRegulatorLogo animateOnHover noMargin />}
123
+ </LogosContainer>
124
+ </TopSection>
125
+
126
+ <PrimaryNav navItems={primaryLinksList} {...rest} />
127
+
128
+ <SocialIconWrapper $mobileOnly>
129
+ <SocialIcons
130
+ campaign={campaignName}
131
+ newStyle
132
+ invertColor
133
+ showFacebookSocialIcon={showFacebookSocialIcon}
134
+ showInstagramSocialIcon={showInstagramSocialIcon}
135
+ showXSocialIcon={showXSocialIcon}
136
+ showTikTokSocialIcon={showTikTokSocialIcon}
137
+ showYouTubeSocialIcon={showYouTubeSocialIcon}
138
+ />
139
+ </SocialIconWrapper>
140
+
141
+ <SecondaryNav navItems={secondaryLinksList} {...rest} />
142
+
143
+ <FooterCopyright>
144
+ {displayCopyrightText && (
145
+ <FooterCopyrightText>
146
+ {displayCopyrightText}
147
+ </FooterCopyrightText>
148
+ )}
149
+ {legalText && (
150
+ <FooterCopyrightText>
151
+ {legalText}
152
+ </FooterCopyrightText>
153
+ )}
154
+ </FooterCopyright>
155
+
156
+ <LogosContainer $mobileOnly $showFundraiserAtBottom>
157
+ {showFundraisingRegulatorLogo && <FundraisingRegulatorLogo />}
158
+ </LogosContainer>
159
+ </InnerWrapper>
160
+ </FooterWrapper>
161
+ </div>
162
+ );
163
+ };
164
+
165
+ FooterNew.propTypes = {
166
+ /** Array of primary navigation link objects */
167
+ primaryLinksList: PropTypes.arrayOf(PropTypes.shape({
168
+ title: PropTypes.string,
169
+ path: PropTypes.string,
170
+ url: PropTypes.string,
171
+ reference: PropTypes.shape({
172
+ path: PropTypes.string
173
+ }),
174
+ internal: PropTypes.shape({
175
+ type: PropTypes.string
176
+ })
177
+ })),
178
+ /** Array of secondary navigation link objects */
179
+ secondaryLinksList: PropTypes.arrayOf(PropTypes.shape({
180
+ title: PropTypes.string,
181
+ path: PropTypes.string,
182
+ url: PropTypes.string,
183
+ reference: PropTypes.shape({
184
+ path: PropTypes.string
185
+ }),
186
+ internal: PropTypes.shape({
187
+ type: PropTypes.string
188
+ })
189
+ })),
190
+ /** Legal text displayed at bottom of footer */
191
+ legalText: PropTypes.string,
192
+ /** Optional copyright text displayed at bottom of footer */
193
+ copyrightText: PropTypes.string,
194
+ /** Campaign name, used for logo and social links */
195
+ campaign: PropTypes.string,
196
+ /** Optional legal line displayed at top of footer */
197
+ additionalLegalLine: PropTypes.string,
198
+ showFundraisingRegulatorLogo: PropTypes.bool,
199
+ showEmailSignup: PropTypes.bool,
200
+ showFacebookSocialIcon: PropTypes.bool,
201
+ showInstagramSocialIcon: PropTypes.bool,
202
+ showXSocialIcon: PropTypes.bool,
203
+ showTikTokSocialIcon: PropTypes.bool,
204
+ showYouTubeSocialIcon: PropTypes.bool,
205
+ /** Animate logo rotation on hover */
206
+ animateRotate: PropTypes.bool,
207
+ /** Optional function to handle newsletter signup form submission */
208
+ onNewsletterSubmit: PropTypes.func
209
+ };
210
+
211
+ export default FooterNew;
@@ -0,0 +1,47 @@
1
+ # FooterNew
2
+
3
+ ## FooterNew with email signup
4
+
5
+ ```js
6
+ import FooterNew from './FooterNew';
7
+ import footerCopy from '../Footer/data/footerCopy';
8
+ import { primaryLinksList, secondaryLinksList } from './dev-data/data';
9
+
10
+ <FooterNew
11
+ primaryLinksList={primaryLinksList}
12
+ secondaryLinksList={secondaryLinksList}
13
+ legalText={footerCopy.copy}
14
+ campaign="Comic Relief"
15
+ showFundraisingRegulatorLogo
16
+ showFacebookSocialIcon
17
+ showInstagramSocialIcon
18
+ showXSocialIcon
19
+ showTikTokSocialIcon
20
+ showYouTubeSocialIcon
21
+ animateRotate
22
+ onNewsletterSubmit={(email) => console.log('Newsletter submitted. Normally at this point, the frontend would run its own function to send.', email)}
23
+ />
24
+ ```
25
+
26
+ ## FooterNew without email signup
27
+
28
+ ```js
29
+ import FooterNew from './FooterNew';
30
+ import footerCopy from '../Footer/data/footerCopy';
31
+ import { primaryLinksList, secondaryLinksList } from './dev-data/data';
32
+
33
+ <FooterNew
34
+ primaryLinksList={primaryLinksList}
35
+ secondaryLinksList={secondaryLinksList}
36
+ legalText={footerCopy.copy}
37
+ campaign="Comic Relief"
38
+ showEmailSignup={false}
39
+ showFundraisingRegulatorLogo
40
+ showFacebookSocialIcon
41
+ showInstagramSocialIcon
42
+ showXSocialIcon
43
+ showTikTokSocialIcon
44
+ showYouTubeSocialIcon
45
+ animateRotate
46
+ />
47
+ ```
@@ -0,0 +1,294 @@
1
+ import styled from 'styled-components';
2
+ import spacing from '../../../theme/shared/spacing';
3
+ import Link from '../../Atoms/Link/Link';
4
+ import Text from '../../Atoms/Text/Text';
5
+ import { logoRotateAnimation } from '../../../theme/shared/animations';
6
+
7
+ const FooterWrapper = styled.footer.attrs(() => ({
8
+ role: 'banner'
9
+ }))`
10
+ text-align: left;
11
+ background: ${({ theme }) => theme.color('grey_5')};
12
+ padding-top: 6rem;
13
+ @media ${({ theme }) => theme.breakpoints2026('M')} {
14
+ padding-bottom: ${spacing('lg')};
15
+ }
16
+ `;
17
+
18
+ const InnerWrapper = styled.div`
19
+ position: relative;
20
+ display: block;
21
+ width: 100%;
22
+ height: 100%;
23
+ max-width: 1200px;
24
+ margin: 0 auto;
25
+ padding: 0 ${spacing('md')};
26
+ @media ${({ theme }) => theme.breakpoints2026('L')} {
27
+ padding: 0 ${spacing('md')}};
28
+ }
29
+ `;
30
+
31
+ const FooterLegalLine = styled(Text).attrs({
32
+ tag: 'p'
33
+ })`
34
+ text-align: left;
35
+ margin-top: 1rem;
36
+ margin-bottom: ${spacing('md')};
37
+ line-height: 1.5rem;
38
+ color: ${({ theme }) => theme.color('grey')};
39
+ `;
40
+
41
+ const TopSection = styled.div`
42
+ display: flex;
43
+ flex-direction: column;
44
+ margin-bottom: ${spacing('md')};
45
+ gap: ${spacing('md')};
46
+
47
+ @media ${({ theme }) => theme.breakpoints2026('M')} {
48
+ flex-direction: row;
49
+ justify-content: space-between;
50
+ align-items: ${({ $hasEmailSignup }) => ($hasEmailSignup ? 'flex-start' : 'center')};
51
+ }
52
+ `;
53
+
54
+ const TopSectionLeft = styled.div`
55
+ display: flex;
56
+ flex-direction: column;
57
+ gap: ${spacing('md')};
58
+
59
+ @media ${({ theme }) => theme.breakpoints2026('M')} {
60
+ flex-direction: column;
61
+ flex: 0 0 auto;
62
+ }
63
+ `;
64
+
65
+ const NewsletterSignUpWrapper = styled.div`
66
+ max-width: 100%;
67
+
68
+ @media ${({ theme }) => theme.breakpoints2026('M')} {
69
+ max-width: 100%;
70
+ }
71
+ `;
72
+
73
+ const SocialIconWrapper = styled.div`
74
+ margin-bottom: ${spacing('md')};
75
+ box-sizing: content-box;
76
+ gap: ${spacing('md')};
77
+ display: ${({ $desktopOnly }) => {
78
+ if ($desktopOnly) return 'none';
79
+ return 'block';
80
+ }};
81
+
82
+ @media ${({ theme }) => theme.breakpoints2026('M')} {
83
+ box-sizing: border-box;
84
+ display: ${({ $mobileOnly, $inTopSection }) => {
85
+ if ($mobileOnly) return 'none';
86
+ if ($inTopSection) return 'flex';
87
+ return 'block';
88
+ }};
89
+ margin-bottom: ${({ $inTopSection }) => ($inTopSection ? '0' : spacing('md'))};
90
+ align-items: center;
91
+ }
92
+ `;
93
+
94
+ const PrimaryNav = styled.nav`
95
+ display: flex;
96
+ flex-direction: column;
97
+ list-style: none;
98
+ padding: ${spacing('md')} 0;
99
+ margin: 0 0 ${spacing('md')} 0;
100
+ gap: ${spacing('l')};
101
+
102
+ @media ${({ theme }) => theme.breakpoints2026('M')} {
103
+ flex-direction: row;
104
+ flex-wrap: wrap;
105
+ gap: ${spacing('m')};
106
+ }
107
+ `;
108
+
109
+ const PrimaryNavItem = styled.li`
110
+ margin: 0;
111
+ `;
112
+
113
+ const PrimaryNavLink = styled(Link)`
114
+ color: ${({ theme }) => theme.color('white')};
115
+ text-decoration: none;
116
+ font-weight: bold;
117
+
118
+ &:hover,
119
+ &:focus {
120
+ text-decoration: underline;
121
+ text-decoration-color: ${({ theme }) => theme.color('white')};
122
+ }
123
+ `;
124
+
125
+ const PrimaryNavText = styled(Text)`
126
+ color: ${({ theme }) => theme.color('white')};
127
+ font-weight: bold;
128
+ `;
129
+
130
+ const SecondaryNav = styled.nav`
131
+ display: flex;
132
+ flex-direction: row;
133
+ flex-wrap: wrap;
134
+ list-style: none;
135
+ padding: 0;
136
+ margin: 0 0 ${spacing('md')} 0;
137
+ gap: ${spacing('sm')};
138
+ align-items: center;
139
+ `;
140
+
141
+ const SecondaryNavItem = styled.li`
142
+ margin: 0;
143
+ display: flex;
144
+ align-items: center;
145
+
146
+ @media ${({ theme }) => theme.breakpoints2026('M')} {
147
+ display: inline;
148
+ }
149
+
150
+ &:not(:last-child)::after {
151
+ content: '|';
152
+ margin-left: ${spacing('sm')};
153
+ color: ${({ theme }) => theme.color('grey')};
154
+ }
155
+ `;
156
+
157
+ const SecondaryNavLink = styled(Link)`
158
+ text-decoration: none;
159
+
160
+ > span {
161
+ color: ${({ theme }) => theme.color('grey')};
162
+ text-decoration: underline;
163
+ text-decoration-color: ${({ theme }) => theme.color('grey')};
164
+ font-weight: normal;
165
+ font-size: ${({ theme }) => theme.fontSize('xs')};
166
+ }
167
+
168
+ @media ${({ theme }) => theme.breakpoints2026('M')} {
169
+ > span {
170
+ text-decoration: none;
171
+ }
172
+ }
173
+
174
+ &:hover,
175
+ &:focus {
176
+ text-decoration: underline;
177
+ text-decoration-color: ${({ theme }) => theme.color('grey')};
178
+ }
179
+
180
+ `;
181
+
182
+ const SecondaryNavText = styled(Text)`
183
+ color: ${({ theme }) => theme.color('white')};
184
+ font-weight: normal;
185
+ `;
186
+
187
+ const LogosContainer = styled.div`
188
+ display: flex;
189
+ flex-direction: row;
190
+ align-items: center;
191
+ gap: ${spacing('l')};
192
+ justify-content: ${({ $mobileOnly }) => ($mobileOnly ? 'flex-start' : 'center')};
193
+ margin-top: ${({ $mobileOnly }) => ($mobileOnly ? spacing('md') : '0')};
194
+ margin-bottom: ${({ $mobileOnly }) => ($mobileOnly ? spacing('l') : '0')};
195
+
196
+ /* Hide desktop containers on mobile */
197
+ ${({ $desktopOnly }) => $desktopOnly && 'display: none;'}
198
+
199
+ @media ${({ theme }) => theme.breakpoints2026('M')} {
200
+ flex: 0 0 auto;
201
+ margin-top: 0;
202
+ margin-bottom: 0;
203
+ padding-top: ${({ $desktopOnly }) => ($desktopOnly ? spacing('l') : '0')};
204
+
205
+ display: ${({
206
+ $desktopOnly, $mobileOnly, $showCRLogoOnly, $showPairedLogos, $showFundraiserAtBottom
207
+ }) => {
208
+ // Mobile containers
209
+ if ($mobileOnly && $showFundraiserAtBottom) return 'flex';
210
+ if ($mobileOnly) return 'none';
211
+
212
+ // Desktop containers
213
+ if ($desktopOnly && $showCRLogoOnly) return 'flex';
214
+ if ($desktopOnly && $showPairedLogos) return 'none';
215
+ if ($desktopOnly) return 'none';
216
+
217
+ return 'flex';
218
+ }};
219
+
220
+ justify-content: ${({ $desktopOnly, $showCRLogoOnly, $showFundraiserAtBottom }) => {
221
+ if ($showFundraiserAtBottom) return 'flex-start';
222
+ if ($desktopOnly && $showCRLogoOnly) return 'flex-end';
223
+ if ($desktopOnly) return 'flex-end';
224
+ return 'center';
225
+ }};
226
+ }
227
+
228
+
229
+ }
230
+
231
+ @media ${({ theme }) => theme.breakpoints2026('L')} {
232
+ display: ${({
233
+ $desktopOnly, $mobileOnly, $showCRLogoOnly, $showPairedLogos, $showFundraiserAtBottom
234
+ }) => {
235
+ // Hide fundraiser at bottom for L+ (it's in TopSection)
236
+ if ($mobileOnly && $showFundraiserAtBottom) return 'none';
237
+ if ($mobileOnly) return 'none';
238
+
239
+ // Desktop containers
240
+ if ($desktopOnly && $showCRLogoOnly) return 'none';
241
+ if ($desktopOnly && $showPairedLogos) return 'flex';
242
+ if ($desktopOnly) return 'none';
243
+
244
+ return 'flex';
245
+ }};
246
+ }
247
+ `;
248
+
249
+ const Brand = styled(Link)`
250
+ color: transparent;
251
+ border: 0;
252
+ :hover {
253
+ border: 0;
254
+ }
255
+
256
+ ${({ animateRotate }) => logoRotateAnimation(animateRotate)}
257
+ `;
258
+
259
+ const FooterCopyright = styled.div`
260
+ display: block;
261
+ width: 100%;
262
+ height: 100%;
263
+ text-align: left;
264
+ `;
265
+
266
+ const FooterCopyrightText = styled(Text).attrs({
267
+ tag: 'p'
268
+ })`
269
+ color: ${({ theme }) => theme.color('grey')};
270
+ font-size: ${({ theme }) => theme.fontSize('xs')};
271
+ margin-bottom: ${spacing('sm')};
272
+ `;
273
+
274
+ export {
275
+ FooterWrapper,
276
+ InnerWrapper,
277
+ FooterLegalLine,
278
+ TopSection,
279
+ TopSectionLeft,
280
+ NewsletterSignUpWrapper,
281
+ SocialIconWrapper,
282
+ PrimaryNav,
283
+ PrimaryNavItem,
284
+ PrimaryNavLink,
285
+ PrimaryNavText,
286
+ SecondaryNav,
287
+ SecondaryNavItem,
288
+ SecondaryNavLink,
289
+ SecondaryNavText,
290
+ LogosContainer,
291
+ FooterCopyright,
292
+ FooterCopyrightText,
293
+ Brand
294
+ };
@@ -0,0 +1,24 @@
1
+ /* eslint-disable react/jsx-wrap-multilines */
2
+ import React from 'react';
3
+ import 'jest-styled-components';
4
+ import renderWithTheme from '../../../../tests/hoc/shallowWithTheme';
5
+ import FooterNew from './FooterNew';
6
+
7
+ import footerCopy from '../Footer/data/footerCopy';
8
+ import {
9
+ testPrimaryLinksList,
10
+ testSecondaryLinksList
11
+ } from './dev-data/data';
12
+
13
+ it('renders correctly', () => {
14
+ const tree = renderWithTheme(
15
+ <FooterNew
16
+ primaryLinksList={testPrimaryLinksList}
17
+ secondaryLinksList={testSecondaryLinksList}
18
+ legalText={footerCopy.copy}
19
+ copyrightText="© 2026 Comic Relief"
20
+ />
21
+ ).toJSON();
22
+
23
+ expect(tree).toMatchSnapshot();
24
+ });
@@ -0,0 +1,54 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { NavHelper } from '../../../../utils/navHelper';
4
+ import { InternalLinkHelper } from '../../../../utils/internalLinkHelper';
5
+ import {
6
+ PrimaryNav as StyledPrimaryNav,
7
+ PrimaryNavItem,
8
+ PrimaryNavLink,
9
+ PrimaryNavText
10
+ } from '../FooterNew.style';
11
+
12
+ const PrimaryNav = ({ navItems = [], ...rest }) => {
13
+ if (!navItems || navItems.length === 0) {
14
+ return null;
15
+ }
16
+
17
+ return (
18
+ <StyledPrimaryNav aria-label="Primary footer navigation" role="navigation">
19
+ {navItems.map(item => {
20
+ const thisUrl = InternalLinkHelper(NavHelper(item));
21
+
22
+ return (
23
+ <PrimaryNavItem key={`primary-${thisUrl}-${item.title}`}>
24
+ <PrimaryNavLink
25
+ href={thisUrl}
26
+ {...rest}
27
+ >
28
+ <PrimaryNavText>
29
+ {item.title}
30
+ </PrimaryNavText>
31
+ </PrimaryNavLink>
32
+ </PrimaryNavItem>
33
+ );
34
+ })}
35
+ </StyledPrimaryNav>
36
+ );
37
+ };
38
+
39
+ PrimaryNav.propTypes = {
40
+ /** Array of primary navigation link objects */
41
+ navItems: PropTypes.arrayOf(PropTypes.shape({
42
+ title: PropTypes.string.isRequired,
43
+ path: PropTypes.string,
44
+ url: PropTypes.string,
45
+ reference: PropTypes.shape({
46
+ path: PropTypes.string
47
+ }),
48
+ internal: PropTypes.shape({
49
+ type: PropTypes.string
50
+ })
51
+ }))
52
+ };
53
+
54
+ export default PrimaryNav;
@@ -0,0 +1,54 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { NavHelper } from '../../../../utils/navHelper';
4
+ import { InternalLinkHelper } from '../../../../utils/internalLinkHelper';
5
+ import {
6
+ SecondaryNav as StyledSecondaryNav,
7
+ SecondaryNavItem,
8
+ SecondaryNavLink,
9
+ SecondaryNavText
10
+ } from '../FooterNew.style';
11
+
12
+ const SecondaryNav = ({ navItems = [], ...rest }) => {
13
+ if (!navItems || navItems.length === 0) {
14
+ return null;
15
+ }
16
+
17
+ return (
18
+ <StyledSecondaryNav aria-label="Secondary footer navigation" role="navigation">
19
+ {navItems.map(item => {
20
+ const thisUrl = InternalLinkHelper(NavHelper(item));
21
+
22
+ return (
23
+ <SecondaryNavItem key={`secondary-${thisUrl}-${item.title}`}>
24
+ <SecondaryNavLink
25
+ href={thisUrl}
26
+ {...rest}
27
+ >
28
+ <SecondaryNavText>
29
+ {item.title}
30
+ </SecondaryNavText>
31
+ </SecondaryNavLink>
32
+ </SecondaryNavItem>
33
+ );
34
+ })}
35
+ </StyledSecondaryNav>
36
+ );
37
+ };
38
+
39
+ SecondaryNav.propTypes = {
40
+ /** Array of secondary navigation link objects */
41
+ navItems: PropTypes.arrayOf(PropTypes.shape({
42
+ title: PropTypes.string.isRequired,
43
+ path: PropTypes.string,
44
+ url: PropTypes.string,
45
+ reference: PropTypes.shape({
46
+ path: PropTypes.string
47
+ }),
48
+ internal: PropTypes.shape({
49
+ type: PropTypes.string
50
+ })
51
+ }))
52
+ };
53
+
54
+ export default SecondaryNav;