@comicrelief/component-library 8.54.0 → 8.55.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 (76) hide show
  1. package/dist/components/Atoms/Logo/Logo.js +0 -5
  2. package/dist/components/Atoms/Logo/Logo.test.js +1 -1
  3. package/dist/components/Atoms/LogoNav2026/LogoNav2026.test.js +94 -0
  4. package/dist/components/Atoms/LogoNav2026/_LogoNav2026.js +63 -0
  5. package/dist/components/Atoms/LogoNav2026/assets/cr-logo-mob.svg +14 -0
  6. package/dist/components/Atoms/LogoNav2026/assets/cr-logo.svg +14 -0
  7. package/dist/components/Molecules/CTA/CTAMultiCard/__snapshots__/CTAMultiCard.test.js.snap +12 -12
  8. package/dist/components/Molecules/CTA/CTASingleCard/CTASingleCard.md +2 -2
  9. package/dist/components/Molecules/CTA/CTASingleCard/__snapshots__/CTASingleCard.test.js.snap +2 -2
  10. package/dist/components/Molecules/CTA/shared/CTACard.style.js +1 -1
  11. package/dist/components/Molecules/LogoLinked/LogoLinked.md +6 -1
  12. package/dist/components/Organisms/Header/HeaderNav/HeaderNav.style.js +3 -3
  13. package/dist/components/Organisms/Header2025/Header2025.md +1 -1
  14. package/dist/components/Organisms/Header2025/HeaderNav2025/HeaderNav2025.js +1 -1
  15. package/dist/components/Organisms/Header2025/HeaderNav2025/HeaderNav2025.style.js +3 -3
  16. package/dist/components/Organisms/Header2026/Burger/BurgerMenu.js +25 -0
  17. package/dist/components/Organisms/Header2026/Burger/BurgerMenu.style.js +58 -0
  18. package/dist/components/Organisms/Header2026/Header2026.js +148 -0
  19. package/dist/components/Organisms/Header2026/Header2026.md +14 -0
  20. package/dist/components/Organisms/Header2026/Header2026.style.js +129 -0
  21. package/dist/components/Organisms/Header2026/Navs/Navs.js +209 -0
  22. package/dist/components/Organisms/Header2026/Navs/Navs.style.js +104 -0
  23. package/dist/components/Organisms/Header2026/Navs/PrimaryNavItem.js +227 -0
  24. package/dist/components/Organisms/Header2026/Navs/PrimaryNavItem.style.js +401 -0
  25. package/dist/components/Organisms/Header2026/Navs/arrow-right.png +0 -0
  26. package/dist/components/Organisms/Header2026/Navs/arrow.svg +6 -0
  27. package/dist/components/Organisms/Header2026/Navs/chevron-down.svg +3 -0
  28. package/dist/components/Organisms/Header2026/assets/arrow-icon.svg +3 -0
  29. package/dist/components/Organisms/Header2026/assets/chevron-icon.svg +3 -0
  30. package/dist/components/Organisms/Header2026/assets/search-icon.svg +10 -0
  31. package/dist/components/Organisms/Header2026/header2026.test.js +24 -0
  32. package/dist/components/Organisms/Header2026/mockData/mockData.json +569 -0
  33. package/dist/components/Organisms/Header2026/mockData/query.graphql +64 -0
  34. package/dist/theme/shared/animations.js +6 -1
  35. package/dist/utils/navHelper.js +75 -3
  36. package/dist/utils/remove-extra-styles-in-preview.css +14 -0
  37. package/dist/utils/urlHelper.js +30 -0
  38. package/package.json +1 -1
  39. package/src/components/Atoms/Logo/Logo.js +0 -4
  40. package/src/components/Atoms/Logo/Logo.test.js +5 -5
  41. package/src/components/Atoms/LogoNav2026/LogoNav2026.test.js +91 -0
  42. package/src/components/Atoms/LogoNav2026/_LogoNav2026.js +75 -0
  43. package/src/components/Atoms/LogoNav2026/assets/cr-logo-mob.svg +14 -0
  44. package/src/components/Atoms/LogoNav2026/assets/cr-logo.svg +14 -0
  45. package/src/components/Molecules/CTA/CTAMultiCard/__snapshots__/CTAMultiCard.test.js.snap +12 -12
  46. package/src/components/Molecules/CTA/CTASingleCard/CTASingleCard.md +2 -2
  47. package/src/components/Molecules/CTA/CTASingleCard/__snapshots__/CTASingleCard.test.js.snap +2 -2
  48. package/src/components/Molecules/CTA/shared/CTACard.style.js +1 -1
  49. package/src/components/Molecules/LogoLinked/LogoLinked.md +6 -1
  50. package/src/components/Organisms/Header/HeaderNav/HeaderNav.style.js +2 -2
  51. package/src/components/Organisms/Header2025/Header2025.md +1 -1
  52. package/src/components/Organisms/Header2025/HeaderNav2025/HeaderNav2025.js +1 -1
  53. package/src/components/Organisms/Header2025/HeaderNav2025/HeaderNav2025.style.js +2 -2
  54. package/src/components/Organisms/Header2026/Burger/BurgerMenu.js +26 -0
  55. package/src/components/Organisms/Header2026/Burger/BurgerMenu.style.js +104 -0
  56. package/src/components/Organisms/Header2026/Header2026.js +215 -0
  57. package/src/components/Organisms/Header2026/Header2026.md +14 -0
  58. package/src/components/Organisms/Header2026/Header2026.style.js +195 -0
  59. package/src/components/Organisms/Header2026/Navs/Navs.js +251 -0
  60. package/src/components/Organisms/Header2026/Navs/Navs.style.js +168 -0
  61. package/src/components/Organisms/Header2026/Navs/PrimaryNavItem.js +354 -0
  62. package/src/components/Organisms/Header2026/Navs/PrimaryNavItem.style.js +658 -0
  63. package/src/components/Organisms/Header2026/Navs/arrow-right.png +0 -0
  64. package/src/components/Organisms/Header2026/Navs/arrow.svg +6 -0
  65. package/src/components/Organisms/Header2026/Navs/chevron-down.svg +3 -0
  66. package/src/components/Organisms/Header2026/assets/arrow-icon.svg +3 -0
  67. package/src/components/Organisms/Header2026/assets/chevron-icon.svg +3 -0
  68. package/src/components/Organisms/Header2026/assets/search-icon.svg +10 -0
  69. package/src/components/Organisms/Header2026/header2026.test.js +22 -0
  70. package/src/components/Organisms/Header2026/mockData/mockData.json +569 -0
  71. package/src/components/Organisms/Header2026/mockData/query.graphql +64 -0
  72. package/src/theme/crTheme/theme.js +0 -1
  73. package/src/theme/shared/animations.js +43 -2
  74. package/src/utils/navHelper.js +82 -2
  75. package/src/utils/remove-extra-styles-in-preview.css +14 -0
  76. package/src/utils/urlHelper.js +27 -0
@@ -0,0 +1,251 @@
1
+ import React, {
2
+ useState, useEffect, useCallback, useMemo
3
+ } from 'react';
4
+ import PropTypes from 'prop-types';
5
+
6
+ import Link from '../../../Atoms/Link/Link';
7
+ import Text from '../../../Atoms/Text/Text';
8
+ import BurgerMenu from '../Burger/BurgerMenu';
9
+ import Icon from '../../../Atoms/SocialIcons/Icon/Icon';
10
+ import { breakpointValues2026 } from '../../../../theme/shared/breakpoints2026';
11
+ import {
12
+ NavHelperNew, NavHelperPrimary, MoreNavPreProcessNew, getColumnLinks
13
+ } from '../../../../utils/navHelper';
14
+ import { InternalLinkHelper } from '../../../../utils/internalLinkHelper';
15
+ import allowListed from '../../../../utils/allowListed';
16
+ import PrimaryNavItem from './PrimaryNavItem';
17
+ import searchIcon from '../assets/search-icon.svg';
18
+ import prependBaseUrl from '../../../../utils/urlHelper';
19
+
20
+ import {
21
+ Navigation,
22
+ PrimaryMenuWrapper,
23
+ PrimaryMenu,
24
+ DonateButtonMobileModalWrapper,
25
+ SearchWrapperMobile,
26
+ SearchLinkMobile,
27
+ SearchIconWrapperMobile
28
+ } from './Navs.style';
29
+
30
+ const Navs = ({
31
+ navItems = {},
32
+ characterLimit,
33
+ isMenuOpen,
34
+ setIsMenuOpen,
35
+ devMode = false,
36
+ onSubMenuChange = () => {},
37
+ onTertiaryMenuChange = () => {}
38
+ }) => {
39
+ const { headerPageGroups } = navItems;
40
+ const [openedSubMenu, setOpenedSubMenu] = useState({});
41
+ const [isNotDesktop, setIsNotDesktop] = useState(() => {
42
+ if (typeof window !== 'undefined') {
43
+ return window.innerWidth < breakpointValues2026.L;
44
+ }
45
+ return false;
46
+ });
47
+ const [processedItems, setProcessedItems] = useState(null);
48
+ const [isTertiaryOpen, setIsTertiaryOpen] = useState(false);
49
+ let theseGroups = null;
50
+
51
+ // Check if any submenu is currently open
52
+ const isSubMenuOpen = Object.values(openedSubMenu).some(v => v);
53
+
54
+ // Close all submenus (used by back button in header)
55
+ const closeSubMenus = useCallback(() => {
56
+ setOpenedSubMenu({});
57
+ }, []);
58
+
59
+ // Handle tertiary menu changes from PrimaryNavItem
60
+ const handleTertiaryMenuChange = useCallback((isOpen, parentName, closeFunction) => {
61
+ setIsTertiaryOpen(isOpen);
62
+ onTertiaryMenuChange(isOpen && isNotDesktop, parentName, closeFunction);
63
+ }, [isNotDesktop, onTertiaryMenuChange]);
64
+
65
+ // Notify parent when submenu state changes, passing close function
66
+ useEffect(() => {
67
+ onSubMenuChange(isSubMenuOpen && isNotDesktop, closeSubMenus);
68
+ }, [isSubMenuOpen, isNotDesktop, onSubMenuChange, closeSubMenus]);
69
+
70
+ const toggleBurgerMenu = e => {
71
+ e.preventDefault();
72
+ setIsMenuOpen(!isMenuOpen);
73
+
74
+ // If we've just closed the nav, collapse any open submenus and reset tertiary state:
75
+ if (isMenuOpen) {
76
+ setOpenedSubMenu({});
77
+ setIsTertiaryOpen(false);
78
+ onTertiaryMenuChange(false, null, null);
79
+ }
80
+ };
81
+
82
+ // Toggle the open/not-open value of the specific submenu passed
83
+ const toggleSubMenu = (e, item) => {
84
+ e.preventDefault();
85
+ setOpenedSubMenu({ [item]: !openedSubMenu[item] });
86
+ };
87
+
88
+ // Called by eventHandler to reset the nav on a specific mouse interaction
89
+ const resetMoreNavMouse = () => {
90
+ // Remove active 'opened' state for any open More Nav submenus
91
+ setOpenedSubMenu({});
92
+ // And also remove the focus state so the 'focus-within' nav rules don't apply:
93
+ document.activeElement.blur();
94
+ };
95
+
96
+ // Process the nav items on initial mount:
97
+ useMemo(() => {
98
+ if (!headerPageGroups) return;
99
+ // Divide up nav items accordingly
100
+ const theseItems = MoreNavPreProcessNew(headerPageGroups, characterLimit);
101
+ setProcessedItems(theseItems);
102
+ }, [headerPageGroups, characterLimit]);
103
+
104
+ // Custom function to let us update the nav dynamically:
105
+ const screenResizeNav = useCallback(() => {
106
+ // Grab the current width:
107
+ const currentScreenWidth = typeof window !== 'undefined' ? window.innerWidth : null;
108
+
109
+ // Compare to our breakpoint:
110
+ const isCurrentlyNotDesktop = currentScreenWidth < breakpointValues2026.L;
111
+
112
+ // Only if the screen size has *changed*, update the state:
113
+ if (currentScreenWidth !== null && (isNotDesktop !== isCurrentlyNotDesktop)) {
114
+ // listeners, BEFORE we update the flag that'd remove the elements from the DOM:
115
+ if (isCurrentlyNotDesktop && processedItems.moreNavGroups.length) {
116
+ document.getElementById('more-nav-ul').removeEventListener('mouseleave', resetMoreNavMouse);
117
+ }
118
+
119
+ // Update our desktop flag to prevent any further calls:
120
+ setIsNotDesktop(isCurrentlyNotDesktop);
121
+ }
122
+ }, [isNotDesktop, processedItems]);
123
+
124
+ // Hook into browser's own onresize event to call our custom wrapper function:
125
+ useEffect(() => {
126
+ if (typeof window !== 'undefined') window.onresize = screenResizeNav;
127
+ }, [screenResizeNav]);
128
+
129
+ // Once we've processed the items, assign according to breakpoint; sub-desktop 'Nav'
130
+ // breakpoints use 'raw' unprocessed header page groups, Desktop ('Nav' breakpoint and up)
131
+ // uses the divided-up versions:
132
+ if (processedItems) theseGroups = isNotDesktop ? headerPageGroups : processedItems.standardGroups;
133
+
134
+ return (
135
+ <>
136
+ <Navigation
137
+ data-testid="Nav"
138
+ aria-label="main-menu"
139
+ isMenuOpen={isMenuOpen}
140
+ role="navigation"
141
+ id="main-nav"
142
+ >
143
+ {/* Unseen accessibility aid */}
144
+ <Text id="main-menu" tag="h2">Main navigation</Text>
145
+
146
+ <PrimaryMenuWrapper data-testid="PrimaryMenuWrapper">
147
+
148
+ {/* Only render once we've processed the menu items: */}
149
+ {processedItems && (
150
+ // First level of the navigation (ul tag): Parent
151
+ <PrimaryMenu
152
+ data-testid="PrimaryMenu"
153
+ role="menubar"
154
+ >
155
+ {theseGroups.map((group, index) => {
156
+ /* Generate an ID from the primary page name */
157
+ const thisID = group.primaryPageName.toLowerCase().replace(/\s+/g, '-');
158
+ /* Determine which field represents our url path */
159
+ let thisUrl = NavHelperPrimary(group);
160
+ const relNoopener = (!allowListed(thisUrl) && 'noopener') || undefined;
161
+ /* Get all column links for submenu */
162
+ const columnLinks = getColumnLinks(group);
163
+ const hasSubMenu = columnLinks.length > 0;
164
+ const hasPopUp = hasSubMenu ? 'true' : null;
165
+ thisUrl = InternalLinkHelper(thisUrl);
166
+
167
+ // Renders the primary page as the parent; a button for the dropdown
168
+ // on mobile, a clickable LINK on desktop but hover to reveal the submenu:
169
+ return (
170
+ // Secondary Menu is nested inside PrimaryNavItem
171
+ <PrimaryNavItem
172
+ thisID={thisID}
173
+ key={group.id}
174
+ index={index}
175
+ hasSubMenu={hasSubMenu}
176
+ openedSubMenu={openedSubMenu}
177
+ toggleSubMenu={toggleSubMenu}
178
+ hasPopUp={hasPopUp}
179
+ isNotDesktop={isNotDesktop}
180
+ thisUrl={thisUrl}
181
+ group={group}
182
+ navHelperNew={NavHelperNew}
183
+ internalLinkHelper={InternalLinkHelper}
184
+ relNoopener={relNoopener}
185
+ devMode={devMode}
186
+ onTertiaryMenuChange={handleTertiaryMenuChange}
187
+ isTertiaryOpenGlobal={isTertiaryOpen}
188
+ isSubMenuOpenGlobal={isSubMenuOpen}
189
+ />
190
+ );
191
+ })}
192
+
193
+ {/* Hide search on mobile when secondary or tertiary modals are open */}
194
+ {!isSubMenuOpen && !isTertiaryOpen && (
195
+ <SearchWrapperMobile>
196
+ <SearchLinkMobile href={prependBaseUrl('/search', devMode)}>
197
+ Search
198
+ <SearchIconWrapperMobile data-testid="SearchIconWrapperMobile">
199
+ <Icon
200
+ icon={searchIcon}
201
+ title="Search"
202
+ target="self"
203
+ role="button"
204
+ href={prependBaseUrl('/search', devMode)}
205
+ brand="comicrelief"
206
+ tabIndex="0"
207
+ id="search"
208
+ isHeader
209
+ />
210
+ </SearchIconWrapperMobile>
211
+ </SearchLinkMobile>
212
+ </SearchWrapperMobile>
213
+ )}
214
+
215
+ </PrimaryMenu>
216
+ )}
217
+ </PrimaryMenuWrapper>
218
+
219
+ {isMenuOpen && !isTertiaryOpen && (
220
+ <DonateButtonMobileModalWrapper data-testid="donate-button--mobile">
221
+ <Link
222
+ color="red"
223
+ type="button"
224
+ href="https://donation.comicrelief.com/"
225
+ >
226
+ Donate
227
+ </Link>
228
+ </DonateButtonMobileModalWrapper>
229
+ )}
230
+ </Navigation>
231
+
232
+ <BurgerMenu
233
+ data-testid="BurgerMenu"
234
+ toggle={toggleBurgerMenu}
235
+ isMenuOpen={isMenuOpen}
236
+ />
237
+ </>
238
+ );
239
+ };
240
+
241
+ Navs.propTypes = {
242
+ navItems: PropTypes.objectOf(PropTypes.shape),
243
+ characterLimit: PropTypes.number,
244
+ isMenuOpen: PropTypes.bool,
245
+ setIsMenuOpen: PropTypes.func,
246
+ devMode: PropTypes.bool,
247
+ onSubMenuChange: PropTypes.func,
248
+ onTertiaryMenuChange: PropTypes.func
249
+ };
250
+
251
+ export default Navs;
@@ -0,0 +1,168 @@
1
+ import styled from 'styled-components';
2
+
3
+ import Link from '../../../Atoms/Link/Link';
4
+ import hideVisually from '../../../../theme/shared/hideVisually';
5
+ import zIndex from '../../../../theme/shared/zIndex';
6
+
7
+ const Navigation = styled.nav`
8
+ display: ${({ isMenuOpen }) => (isMenuOpen ? 'block' : 'none')};
9
+ width: 100%;
10
+ position: absolute;
11
+ top: 75px;
12
+ left: 0;
13
+ ${zIndex('higher')};
14
+
15
+ @media ${({ theme }) => theme.breakpoints2026('L')} {
16
+ ${zIndex('medium')};
17
+ position: static;
18
+ top: 0;
19
+ display: block;
20
+ margin: 0;
21
+ width: 100%;
22
+ height: 100%;
23
+ box-shadow: none;
24
+ }
25
+
26
+ // Accessibility aid
27
+ > h2 {
28
+ ${hideVisually};
29
+ }
30
+ `;
31
+
32
+ const PrimaryMenuWrapper = styled.div`
33
+ background-color: ${({ theme }) => theme.color('white')};
34
+ box-shadow: 0 5px 20px 0 rgba(0, 0, 0, 0.05);
35
+ border-radius: 16px;
36
+ overflow: hidden;
37
+ position: static;
38
+
39
+ @media ${({ theme }) => theme.breakpoints2026('L')} {
40
+ box-shadow: none;
41
+ border-radius: 0;
42
+ height: 100%;
43
+ display: flex;
44
+ justify-content: center;
45
+ align-items: center;
46
+ overflow: visible;
47
+ }
48
+ `;
49
+
50
+ const PrimaryMenu = styled.ul`
51
+ background-color: transparent;
52
+ list-style: none outside;
53
+ padding: 0;
54
+ margin: 0;
55
+ width: 100%;
56
+ height: 100%;
57
+ position: relative
58
+ border: 1px solid green;
59
+
60
+ @media ${({ theme }) => theme.breakpoints2026('L')} {
61
+ position: static;
62
+ display: flex;
63
+ justify-content: center;
64
+ align-items: center;
65
+ gap: 24px;
66
+ width: 100%;
67
+ height: 100%;
68
+ background-color: ${({ theme }) => theme.color('white')};
69
+ }
70
+
71
+ `;
72
+
73
+ const DonateButtonMobileModalWrapper = styled.div`
74
+ display: block;
75
+ background-color: inherit;
76
+ margin-top: 48px;
77
+ width: 100%;
78
+
79
+ // Donate button
80
+ a {
81
+ box-sizing: border-box;
82
+ display: block;
83
+ text-align: center;
84
+ border-radius: 16px;
85
+ width: 100%;
86
+ margin: 0;
87
+ font-family: Montserrat;
88
+ height: auto;
89
+ padding: 18px 0;
90
+
91
+ &:hover,
92
+ &:focus {
93
+ width: 100%;
94
+ box-shadow: 0 5px 20px 0 rgba(0, 0, 0, 0.05);
95
+ }
96
+ }
97
+
98
+ // Hide the 'Nav'-embedded version of the button when the nav
99
+ // goes FULL DESKTOP, leaving just the 'Header'-embedded example
100
+ @media ${({ theme }) => theme.breakpoints2026('L')} {
101
+ display: none;
102
+ }
103
+ `;
104
+
105
+ const SearchWrapperMobile = styled.div`
106
+ display: block;
107
+ width: 100%;
108
+ border-top: 1px solid ${({ theme }) => theme.color('grey_medium')};
109
+
110
+ @media ${({ theme }) => theme.breakpoints2026('L')} {
111
+ display: none;
112
+ }
113
+ `;
114
+
115
+ const SearchLinkMobile = styled(Link)`
116
+ display: flex;
117
+ align-items: center;
118
+ justify-content: space-between;
119
+ padding: 25px;
120
+ width: 100%;
121
+ text-decoration: none;
122
+ font-family: Montserrat;
123
+ font-weight: 600;
124
+ color: ${({ theme }) => theme.color('black')};
125
+ transition: color 0.2s ease;
126
+ outline-offset: -3px;
127
+
128
+ &:hover,
129
+ &:focus {
130
+ color: ${({ theme }) => theme.color('red')};
131
+ background-color: ${({ theme }) => theme.color('grey_extra_light')};
132
+ }
133
+ `;
134
+
135
+ const SearchIconWrapperMobile = styled.div`
136
+ overflow: visible;
137
+
138
+ img {
139
+ width: 18px;
140
+ height: 18px;
141
+ transition: transform 0.2s ease;
142
+ transform-origin: center;
143
+ }
144
+
145
+ a {
146
+ overflow: visible;
147
+ display: block;
148
+ }
149
+
150
+ &:hover img,
151
+ &:focus-within img {
152
+ transform: scale(1.33);
153
+ }
154
+
155
+ @media ${({ theme }) => theme.breakpoints2026('L')} {
156
+ display: none;
157
+ }
158
+ `;
159
+
160
+ export {
161
+ Navigation,
162
+ PrimaryMenuWrapper,
163
+ PrimaryMenu,
164
+ DonateButtonMobileModalWrapper,
165
+ SearchWrapperMobile,
166
+ SearchLinkMobile,
167
+ SearchIconWrapperMobile
168
+ };