@nauedu/frontend-component-header 20.0.0 → 20.1.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 (195) hide show
  1. package/dist/DesktopHeader.js +242 -0
  2. package/dist/DesktopHeader.js.map +1 -0
  3. package/dist/Header.js +9 -13
  4. package/dist/Header.js.map +1 -1
  5. package/dist/Header.test.jsx +0 -3
  6. package/dist/Icons.js +1 -1
  7. package/dist/LanguageSelector/data/api.js +5 -13
  8. package/dist/LanguageSelector/data/api.js.map +1 -1
  9. package/dist/LanguageSelector/index.js +4 -12
  10. package/dist/LanguageSelector/index.js.map +1 -1
  11. package/dist/Logo.js +23 -10
  12. package/dist/Logo.js.map +1 -1
  13. package/dist/Menu/Menu.js +11 -11
  14. package/dist/Menu/Menu.js.map +1 -1
  15. package/dist/Menu/menu.scss +6 -6
  16. package/dist/MobileHeader.js +244 -0
  17. package/dist/MobileHeader.js.map +1 -0
  18. package/dist/__snapshots__/Header.test.jsx.snap +0 -2
  19. package/dist/index.scss +15 -27
  20. package/dist/learning-header/AnonymousUserMenu.js +8 -11
  21. package/dist/learning-header/AnonymousUserMenu.js.map +1 -1
  22. package/dist/learning-header/AuthenticatedUserDropdown.js +24 -32
  23. package/dist/learning-header/AuthenticatedUserDropdown.js.map +1 -1
  24. package/dist/learning-header/LearningHeader.js +14 -15
  25. package/dist/learning-header/LearningHeader.js.map +1 -1
  26. package/dist/learning-header/data/api.js +4 -12
  27. package/dist/learning-header/data/api.js.map +1 -1
  28. package/dist/learning-header/data/api.test.js +95 -92
  29. package/dist/learning-header/data/api.test.js.map +1 -1
  30. package/dist/learning-header/messages.js +0 -5
  31. package/dist/learning-header/messages.js.map +1 -1
  32. package/dist/setupTest.js +2 -0
  33. package/dist/studio-header/BrandNav.js +21 -0
  34. package/dist/studio-header/BrandNav.js.map +1 -0
  35. package/dist/studio-header/CourseLockUp.js +45 -0
  36. package/dist/studio-header/CourseLockUp.js.map +1 -0
  37. package/dist/studio-header/HeaderBody.js +131 -0
  38. package/dist/studio-header/HeaderBody.js.map +1 -0
  39. package/dist/studio-header/MobileHeader.js +79 -0
  40. package/dist/studio-header/MobileHeader.js.map +1 -0
  41. package/dist/studio-header/MobileMenu.js +45 -0
  42. package/dist/studio-header/MobileMenu.js.map +1 -0
  43. package/dist/studio-header/NavDropdownMenu.js +30 -0
  44. package/dist/studio-header/NavDropdownMenu.js.map +1 -0
  45. package/dist/studio-header/StudioHeader.js +72 -0
  46. package/dist/studio-header/StudioHeader.js.map +1 -0
  47. package/dist/studio-header/StudioHeader.scss +10 -8
  48. package/dist/studio-header/UserMenu.js +55 -0
  49. package/dist/studio-header/UserMenu.js.map +1 -0
  50. package/dist/studio-header/{index.ts → index.js} +1 -1
  51. package/dist/studio-header/index.js.map +1 -0
  52. package/dist/studio-header/{messages.ts → messages.js} +14 -15
  53. package/dist/studio-header/messages.js.map +1 -0
  54. package/dist/studio-header/utils.js +29 -0
  55. package/dist/studio-header/utils.js.map +1 -0
  56. package/dist/test-utils.js +4 -12
  57. package/dist/test-utils.js.map +1 -1
  58. package/package.json +1 -1
  59. package/dist/desktop-header/DesktopHeader.js +0 -172
  60. package/dist/desktop-header/DesktopHeader.js.map +0 -1
  61. package/dist/desktop-header/DesktopHeaderMainOrSecondaryMenu.js +0 -51
  62. package/dist/desktop-header/DesktopHeaderMainOrSecondaryMenu.js.map +0 -1
  63. package/dist/desktop-header/DesktopHeaderUserMenu.js +0 -49
  64. package/dist/desktop-header/DesktopHeaderUserMenu.js.map +0 -1
  65. package/dist/desktop-header/DesktopLoggedOutItems.js +0 -22
  66. package/dist/desktop-header/DesktopLoggedOutItems.js.map +0 -1
  67. package/dist/desktop-header/DesktopUserMenuToggle.js +0 -25
  68. package/dist/desktop-header/DesktopUserMenuToggle.js.map +0 -1
  69. package/dist/frontend-platform.d.ts +0 -41
  70. package/dist/learning-header/LearningHeaderCourseInfo.js +0 -24
  71. package/dist/learning-header/LearningHeaderCourseInfo.js.map +0 -1
  72. package/dist/learning-header/LearningHeaderHelpLink.js +0 -13
  73. package/dist/learning-header/LearningHeaderHelpLink.js.map +0 -1
  74. package/dist/learning-header/LearningHeaderUserMenuItems.js +0 -20
  75. package/dist/learning-header/LearningHeaderUserMenuItems.js.map +0 -1
  76. package/dist/learning-header/LearningLoggedOutButtons.js +0 -24
  77. package/dist/learning-header/LearningLoggedOutButtons.js.map +0 -1
  78. package/dist/learning-header/LearningUserMenuToggle.js +0 -26
  79. package/dist/learning-header/LearningUserMenuToggle.js.map +0 -1
  80. package/dist/mobile-header/MobileHeader.js +0 -197
  81. package/dist/mobile-header/MobileHeader.js.map +0 -1
  82. package/dist/mobile-header/MobileHeaderMainMenu.js +0 -46
  83. package/dist/mobile-header/MobileHeaderMainMenu.js.map +0 -1
  84. package/dist/mobile-header/MobileHeaderUserMenu.js +0 -38
  85. package/dist/mobile-header/MobileHeaderUserMenu.js.map +0 -1
  86. package/dist/mobile-header/MobileLoggedOutItems.js +0 -27
  87. package/dist/mobile-header/MobileLoggedOutItems.js.map +0 -1
  88. package/dist/mobile-header/MobileUserMenuToggle.js +0 -19
  89. package/dist/mobile-header/MobileUserMenuToggle.js.map +0 -1
  90. package/dist/plugin-slots/CourseInfoSlot/README.md +0 -128
  91. package/dist/plugin-slots/CourseInfoSlot/images/add_custom_components_before_and_after_course_info.png +0 -0
  92. package/dist/plugin-slots/CourseInfoSlot/images/replace_course_info_with_custom_component.png +0 -0
  93. package/dist/plugin-slots/CourseInfoSlot/images/replace_course_title.png +0 -0
  94. package/dist/plugin-slots/CourseInfoSlot/index.js +0 -32
  95. package/dist/plugin-slots/CourseInfoSlot/index.js.map +0 -1
  96. package/dist/plugin-slots/DesktopHeaderSlot/README.md +0 -44
  97. package/dist/plugin-slots/DesktopHeaderSlot/images/desktop_header_custom_component.png +0 -0
  98. package/dist/plugin-slots/DesktopHeaderSlot/index.js +0 -16
  99. package/dist/plugin-slots/DesktopHeaderSlot/index.js.map +0 -1
  100. package/dist/plugin-slots/DesktopLoggedOutItemsSlot/README.md +0 -137
  101. package/dist/plugin-slots/DesktopLoggedOutItemsSlot/images/desktop_logged_out_items_custom_component.png +0 -0
  102. package/dist/plugin-slots/DesktopLoggedOutItemsSlot/images/desktop_logged_out_items_custom_components_before_after.png +0 -0
  103. package/dist/plugin-slots/DesktopLoggedOutItemsSlot/images/desktop_logged_out_items_modify_items.png +0 -0
  104. package/dist/plugin-slots/DesktopLoggedOutItemsSlot/index.js +0 -20
  105. package/dist/plugin-slots/DesktopLoggedOutItemsSlot/index.js.map +0 -1
  106. package/dist/plugin-slots/DesktopMainMenuSlot/README.md +0 -137
  107. package/dist/plugin-slots/DesktopMainMenuSlot/images/desktop_main_menu_custom_component.png +0 -0
  108. package/dist/plugin-slots/DesktopMainMenuSlot/images/desktop_main_menu_custom_components_before_after.png +0 -0
  109. package/dist/plugin-slots/DesktopMainMenuSlot/images/desktop_main_menu_modify_items.png +0 -0
  110. package/dist/plugin-slots/DesktopMainMenuSlot/index.js +0 -20
  111. package/dist/plugin-slots/DesktopMainMenuSlot/index.js.map +0 -1
  112. package/dist/plugin-slots/DesktopSecondaryMenuSlot/README.md +0 -132
  113. package/dist/plugin-slots/DesktopSecondaryMenuSlot/images/desktop_secondary_menu_custom_component.png +0 -0
  114. package/dist/plugin-slots/DesktopSecondaryMenuSlot/images/desktop_secondary_menu_custom_components_before_after.png +0 -0
  115. package/dist/plugin-slots/DesktopSecondaryMenuSlot/images/desktop_secondary_menu_modify_items.png +0 -0
  116. package/dist/plugin-slots/DesktopSecondaryMenuSlot/index.js +0 -20
  117. package/dist/plugin-slots/DesktopSecondaryMenuSlot/index.js.map +0 -1
  118. package/dist/plugin-slots/DesktopUserMenuSlot/README.md +0 -144
  119. package/dist/plugin-slots/DesktopUserMenuSlot/images/desktop_user_menu_custom_component.png +0 -0
  120. package/dist/plugin-slots/DesktopUserMenuSlot/images/desktop_user_menu_custom_components_before_after.png +0 -0
  121. package/dist/plugin-slots/DesktopUserMenuSlot/images/desktop_user_menu_modify_items.png +0 -0
  122. package/dist/plugin-slots/DesktopUserMenuSlot/index.js +0 -20
  123. package/dist/plugin-slots/DesktopUserMenuSlot/index.js.map +0 -1
  124. package/dist/plugin-slots/DesktopUserMenuToggleSlot/README.md +0 -74
  125. package/dist/plugin-slots/DesktopUserMenuToggleSlot/images/desktop_user_menu_custom_component.png +0 -0
  126. package/dist/plugin-slots/DesktopUserMenuToggleSlot/images/desktop_user_menu_modified_toggle.png +0 -0
  127. package/dist/plugin-slots/DesktopUserMenuToggleSlot/index.js +0 -19
  128. package/dist/plugin-slots/DesktopUserMenuToggleSlot/index.js.map +0 -1
  129. package/dist/plugin-slots/LearningHelpSlot/README.md +0 -44
  130. package/dist/plugin-slots/LearningHelpSlot/images/learning_help_custom_component.png +0 -0
  131. package/dist/plugin-slots/LearningHelpSlot/index.js +0 -11
  132. package/dist/plugin-slots/LearningHelpSlot/index.js.map +0 -1
  133. package/dist/plugin-slots/LearningLoggedOutItemsSlot/README.md +0 -135
  134. package/dist/plugin-slots/LearningLoggedOutItemsSlot/images/learning_logged_out_items_custom_component.png +0 -0
  135. package/dist/plugin-slots/LearningLoggedOutItemsSlot/images/learning_logged_out_items_custom_components_before_after.png +0 -0
  136. package/dist/plugin-slots/LearningLoggedOutItemsSlot/images/learning_logged_out_items_modified_items.png +0 -0
  137. package/dist/plugin-slots/LearningLoggedOutItemsSlot/index.js +0 -18
  138. package/dist/plugin-slots/LearningLoggedOutItemsSlot/index.js.map +0 -1
  139. package/dist/plugin-slots/LearningUserMenuSlot/README.md +0 -133
  140. package/dist/plugin-slots/LearningUserMenuSlot/images/learning_user_menu_custom_component.png +0 -0
  141. package/dist/plugin-slots/LearningUserMenuSlot/images/learning_user_menu_custom_components_before_after.png +0 -0
  142. package/dist/plugin-slots/LearningUserMenuSlot/images/learning_user_menu_modified_items.png +0 -0
  143. package/dist/plugin-slots/LearningUserMenuSlot/index.js +0 -18
  144. package/dist/plugin-slots/LearningUserMenuSlot/index.js.map +0 -1
  145. package/dist/plugin-slots/LearningUserMenuToggleSlot/README.md +0 -74
  146. package/dist/plugin-slots/LearningUserMenuToggleSlot/images/learning_user_menu_toggle_custom_component.png +0 -0
  147. package/dist/plugin-slots/LearningUserMenuToggleSlot/images/learning_user_menu_toggle_modified_items.png +0 -0
  148. package/dist/plugin-slots/LearningUserMenuToggleSlot/index.js +0 -19
  149. package/dist/plugin-slots/LearningUserMenuToggleSlot/index.js.map +0 -1
  150. package/dist/plugin-slots/LogoSlot/README.md +0 -72
  151. package/dist/plugin-slots/LogoSlot/index.js +0 -27
  152. package/dist/plugin-slots/LogoSlot/index.js.map +0 -1
  153. package/dist/plugin-slots/MobileHeaderSlot/README.md +0 -44
  154. package/dist/plugin-slots/MobileHeaderSlot/images/mobile_header_custom_component.png +0 -0
  155. package/dist/plugin-slots/MobileHeaderSlot/index.js +0 -16
  156. package/dist/plugin-slots/MobileHeaderSlot/index.js.map +0 -1
  157. package/dist/plugin-slots/MobileLoggedOutItemsSlot/README.md +0 -136
  158. package/dist/plugin-slots/MobileLoggedOutItemsSlot/images/mobile_logged_out_items_custom_component.png +0 -0
  159. package/dist/plugin-slots/MobileLoggedOutItemsSlot/images/mobile_logged_out_items_custom_components_before_after.png +0 -0
  160. package/dist/plugin-slots/MobileLoggedOutItemsSlot/images/mobile_logged_out_items_modify_items.png +0 -0
  161. package/dist/plugin-slots/MobileLoggedOutItemsSlot/index.js +0 -20
  162. package/dist/plugin-slots/MobileLoggedOutItemsSlot/index.js.map +0 -1
  163. package/dist/plugin-slots/MobileMainMenuSlot/README.md +0 -136
  164. package/dist/plugin-slots/MobileMainMenuSlot/images/mobile_main_menu_custom_component.png +0 -0
  165. package/dist/plugin-slots/MobileMainMenuSlot/images/mobile_main_menu_custom_components_before_after.png +0 -0
  166. package/dist/plugin-slots/MobileMainMenuSlot/images/mobile_main_menu_modify_items.png +0 -0
  167. package/dist/plugin-slots/MobileMainMenuSlot/index.js +0 -20
  168. package/dist/plugin-slots/MobileMainMenuSlot/index.js.map +0 -1
  169. package/dist/plugin-slots/MobileUserMenuSlot/README.md +0 -144
  170. package/dist/plugin-slots/MobileUserMenuSlot/images/mobile_user_menu_custom_component.png +0 -0
  171. package/dist/plugin-slots/MobileUserMenuSlot/images/mobile_user_menu_custom_components_before_after.png +0 -0
  172. package/dist/plugin-slots/MobileUserMenuSlot/images/mobile_user_menu_modify_items.png +0 -0
  173. package/dist/plugin-slots/MobileUserMenuSlot/index.js +0 -20
  174. package/dist/plugin-slots/MobileUserMenuSlot/index.js.map +0 -1
  175. package/dist/plugin-slots/MobileUserMenuToggleSlot/README.md +0 -74
  176. package/dist/plugin-slots/MobileUserMenuToggleSlot/images/mobile_user_menu_toggle_custom_component.png +0 -0
  177. package/dist/plugin-slots/MobileUserMenuToggleSlot/images/mobile_user_menu_toggle_modified_items.png +0 -0
  178. package/dist/plugin-slots/MobileUserMenuToggleSlot/index.js +0 -19
  179. package/dist/plugin-slots/MobileUserMenuToggleSlot/index.js.map +0 -1
  180. package/dist/plugin-slots/README.md +0 -26
  181. package/dist/studio-header/BrandNav.test.tsx +0 -40
  182. package/dist/studio-header/BrandNav.tsx +0 -25
  183. package/dist/studio-header/CourseLockUp.test.tsx +0 -59
  184. package/dist/studio-header/CourseLockUp.tsx +0 -56
  185. package/dist/studio-header/HeaderBody.test.tsx +0 -102
  186. package/dist/studio-header/HeaderBody.tsx +0 -196
  187. package/dist/studio-header/MobileHeader.tsx +0 -74
  188. package/dist/studio-header/MobileMenu.test.tsx +0 -81
  189. package/dist/studio-header/MobileMenu.tsx +0 -50
  190. package/dist/studio-header/NavDropdownMenu.test.tsx +0 -67
  191. package/dist/studio-header/NavDropdownMenu.tsx +0 -42
  192. package/dist/studio-header/StudioHeader.test.tsx +0 -220
  193. package/dist/studio-header/StudioHeader.tsx +0 -84
  194. package/dist/studio-header/UserMenu.tsx +0 -69
  195. package/dist/studio-header/utils.ts +0 -37
@@ -1,102 +0,0 @@
1
- import React from 'react';
2
- import { render, screen, fireEvent } from '@testing-library/react';
3
- import '@testing-library/jest-dom/extend-expect';
4
- import { IntlProvider } from '@edx/frontend-platform/i18n';
5
- import { MemoryRouter } from 'react-router-dom';
6
-
7
- import HeaderBody from './HeaderBody';
8
- import messages from './messages';
9
-
10
- const mockOnNavigate = jest.fn();
11
- const mockSearchButtonAction = jest.fn();
12
- const mockToggleModalPopup = jest.fn();
13
- const mockSetModalPopupTarget = jest.fn();
14
-
15
- const defaultProps = {
16
- studioBaseUrl: 'https://example.com',
17
- logoutUrl: 'https://example.com/logout',
18
- onNavigate: mockOnNavigate,
19
- setModalPopupTarget: mockSetModalPopupTarget,
20
- toggleModalPopup: mockToggleModalPopup,
21
- searchButtonAction: mockSearchButtonAction,
22
- username: 'testuser',
23
- authenticatedUserAvatar: 'avatar.png',
24
- isAdmin: true,
25
- isMobile: false,
26
- isHiddenMainMenu: false,
27
- mainMenuDropdowns: [],
28
- logo: 'logo.png',
29
- logoAltText: 'Test Logo',
30
- number: '101',
31
- org: 'EDX',
32
- title: 'Test Course',
33
- outlineLink: '/courses/edx/course-101',
34
- };
35
-
36
- const RootWrapper = (props) => (
37
- <MemoryRouter>
38
- <IntlProvider locale="en" messages={{}}>
39
- <HeaderBody {...props} />
40
- </IntlProvider>
41
- </MemoryRouter>
42
- );
43
-
44
- describe('HeaderBody Component', () => {
45
- afterEach(() => {
46
- jest.clearAllMocks();
47
- });
48
-
49
- it('renders the logo and brand navigation', () => {
50
- render(<RootWrapper {...defaultProps} />);
51
-
52
- const logoImage = screen.getByAltText(defaultProps.logoAltText);
53
- expect(logoImage).toBeInTheDocument();
54
- expect(logoImage).toHaveAttribute('src', defaultProps.logo);
55
- });
56
-
57
- it('renders course lockup information', () => {
58
- render(<RootWrapper {...defaultProps} />);
59
-
60
- const courseTitle = screen.getByText(defaultProps.title);
61
- const courseOrgNumber = screen.getByText(`${defaultProps.org} ${defaultProps.number}`);
62
-
63
- expect(courseTitle).toBeInTheDocument();
64
- expect(courseOrgNumber).toBeInTheDocument();
65
- });
66
-
67
- it('renders a course lock-up link with the correct outline URL', () => {
68
- render(<RootWrapper {...defaultProps} />);
69
-
70
- const courseLockUpLink = screen.getByTestId('course-lock-up-block');
71
- expect(courseLockUpLink.getAttribute('href')).toBe(defaultProps.outlineLink);
72
- });
73
-
74
- it('displays search button and triggers searchButtonAction on click', () => {
75
- render(<RootWrapper {...defaultProps} />);
76
-
77
- const searchButton = screen.getByLabelText(messages['header.label.search.nav'].defaultMessage);
78
- expect(searchButton).toBeInTheDocument();
79
-
80
- fireEvent.click(searchButton);
81
- expect(mockSearchButtonAction).toHaveBeenCalled();
82
- });
83
-
84
- it('displays user menu with username and avatar', () => {
85
- render(<RootWrapper {...defaultProps} />);
86
-
87
- const userMenu = screen.getByText(defaultProps.username);
88
- const avatarImage = screen.getByAltText(defaultProps.username);
89
-
90
- expect(userMenu).toBeInTheDocument();
91
- expect(avatarImage).toHaveAttribute('src', defaultProps.authenticatedUserAvatar);
92
- });
93
-
94
- it('toggles mobile menu popup when button is clicked in mobile view', () => {
95
- render(<RootWrapper {...defaultProps} isMobile isModalPopupOpen={false} />);
96
-
97
- const menuButton = screen.getByTestId('mobile-menu-button');
98
- fireEvent.click(menuButton);
99
-
100
- expect(mockToggleModalPopup).toHaveBeenCalled();
101
- });
102
- });
@@ -1,196 +0,0 @@
1
- import React from 'react';
2
- import PropTypes from 'prop-types';
3
- import { useIntl } from '@edx/frontend-platform/i18n';
4
- import classNames from 'classnames';
5
- import {
6
- ActionRow,
7
- Button,
8
- Container,
9
- Icon,
10
- IconButton,
11
- Nav,
12
- Row,
13
- } from '@openedx/paragon';
14
- import { Close, MenuIcon, Search } from '@openedx/paragon/icons';
15
-
16
- import CourseLockUp from './CourseLockUp';
17
- import UserMenu from './UserMenu';
18
- import BrandNav from './BrandNav';
19
- import NavDropdownMenu from './NavDropdownMenu';
20
- import messages from './messages';
21
-
22
- const HeaderBody = ({
23
- logo,
24
- logoAltText,
25
- number,
26
- org,
27
- title,
28
- username,
29
- isAdmin,
30
- studioBaseUrl,
31
- logoutUrl,
32
- authenticatedUserAvatar,
33
- isMobile,
34
- setModalPopupTarget,
35
- toggleModalPopup,
36
- isModalPopupOpen,
37
- isHiddenMainMenu,
38
- mainMenuDropdowns,
39
- outlineLink,
40
- searchButtonAction,
41
- containerProps,
42
- }) => {
43
- const intl = useIntl();
44
-
45
- const renderBrandNav = (
46
- <BrandNav
47
- {...{
48
- studioBaseUrl,
49
- logo,
50
- logoAltText,
51
- }}
52
- />
53
- );
54
-
55
- const { className: containerClassName, ...restContainerProps } = containerProps || {};
56
-
57
- return (
58
- <Container
59
- size="xl"
60
- className={classNames('px-2.5', containerClassName)}
61
- {...restContainerProps}
62
- >
63
- <ActionRow as="header">
64
- {isHiddenMainMenu ? (
65
- <Row className="flex-nowrap ml-4">
66
- {renderBrandNav}
67
- </Row>
68
- ) : (
69
- <>
70
- {isMobile ? (
71
- <Button
72
- ref={setModalPopupTarget}
73
- className="d-inline-flex align-items-center"
74
- variant="tertiary"
75
- onClick={toggleModalPopup}
76
- iconBefore={isModalPopupOpen ? Close : MenuIcon}
77
- data-testid="mobile-menu-button"
78
- >
79
- Menu
80
- </Button>
81
- ) : (
82
- <div className="w-25">
83
- <Row className="m-0 flex-nowrap align-items-center">
84
- {renderBrandNav}
85
- <CourseLockUp
86
- {...{
87
- outlineLink,
88
- number,
89
- org,
90
- title,
91
- }}
92
- />
93
- </Row>
94
- </div>
95
- )}
96
- {isMobile ? (
97
- <>
98
- <ActionRow.Spacer />
99
- {renderBrandNav}
100
- </>
101
- ) : (
102
- <Nav data-testid="desktop-menu" className="ml-2">
103
- {mainMenuDropdowns.map(dropdown => {
104
- const { id, buttonTitle, items } = dropdown;
105
- return (
106
- <NavDropdownMenu
107
- key={id}
108
- {...{
109
- id, buttonTitle, items,
110
- }}
111
- />
112
- );
113
- })}
114
- </Nav>
115
- )}
116
- </>
117
- )}
118
- <ActionRow.Spacer />
119
- {searchButtonAction && (
120
- <Nav>
121
- <IconButton
122
- src={Search}
123
- iconAs={Icon}
124
- onClick={searchButtonAction}
125
- aria-label={intl.formatMessage(messages['header.label.search.nav'])}
126
- alt={intl.formatMessage(messages['header.label.search.nav'])}
127
- />
128
- </Nav>
129
- )}
130
- <Nav>
131
- <UserMenu
132
- {...{
133
- username,
134
- studioBaseUrl,
135
- logoutUrl,
136
- authenticatedUserAvatar,
137
- isAdmin,
138
- isMobile,
139
- }}
140
- />
141
- </Nav>
142
- </ActionRow>
143
- </Container>
144
- );
145
- };
146
-
147
- HeaderBody.propTypes = {
148
- studioBaseUrl: PropTypes.string.isRequired,
149
- logoutUrl: PropTypes.string.isRequired,
150
- setModalPopupTarget: PropTypes.func,
151
- toggleModalPopup: PropTypes.func,
152
- isModalPopupOpen: PropTypes.bool,
153
- number: PropTypes.string,
154
- org: PropTypes.string,
155
- title: PropTypes.string,
156
- logo: PropTypes.string,
157
- logoAltText: PropTypes.string,
158
- authenticatedUserAvatar: PropTypes.string,
159
- username: PropTypes.string,
160
- isAdmin: PropTypes.bool,
161
- isMobile: PropTypes.bool,
162
- isHiddenMainMenu: PropTypes.bool,
163
- mainMenuDropdowns: PropTypes.arrayOf(PropTypes.shape({
164
- id: PropTypes.string,
165
- buttonTitle: PropTypes.node,
166
- items: PropTypes.arrayOf(PropTypes.shape({
167
- href: PropTypes.string,
168
- title: PropTypes.node,
169
- })),
170
- })),
171
- outlineLink: PropTypes.string,
172
- searchButtonAction: PropTypes.func,
173
- containerProps: PropTypes.shape(Container.propTypes),
174
- };
175
-
176
- HeaderBody.defaultProps = {
177
- setModalPopupTarget: null,
178
- toggleModalPopup: null,
179
- isModalPopupOpen: false,
180
- logo: null,
181
- logoAltText: null,
182
- number: '',
183
- org: '',
184
- title: '',
185
- authenticatedUserAvatar: null,
186
- username: null,
187
- isAdmin: false,
188
- isMobile: false,
189
- isHiddenMainMenu: false,
190
- mainMenuDropdowns: [],
191
- outlineLink: null,
192
- searchButtonAction: null,
193
- containerProps: {},
194
- };
195
-
196
- export default HeaderBody;
@@ -1,74 +0,0 @@
1
- import React, { useState } from 'react';
2
- import PropTypes from 'prop-types';
3
- import { useToggle, ModalPopup } from '@openedx/paragon';
4
- import HeaderBody from './HeaderBody';
5
- import MobileMenu from './MobileMenu';
6
-
7
- const MobileHeader = ({
8
- mainMenuDropdowns,
9
- ...props
10
- }) => {
11
- const [isOpen, , close, toggle] = useToggle(false);
12
- const [target, setTarget] = useState(null);
13
-
14
- return (
15
- <>
16
- {/* @ts-expect-error The type of 'props' is any until we convert from propTypes to TypeScript interface/types */}
17
- <HeaderBody
18
- {...props}
19
- isMobile
20
- setModalPopupTarget={setTarget}
21
- toggleModalPopup={toggle}
22
- isModalPopupOpen={isOpen}
23
- />
24
- <ModalPopup
25
- hasArrow
26
- placement="bottom"
27
- positionRef={target}
28
- isOpen={isOpen}
29
- onClose={close}
30
- onEscapeKey={close}
31
- className="mobile-menu-container"
32
- >
33
- <MobileMenu {...{ mainMenuDropdowns }} />
34
- </ModalPopup>
35
- </>
36
- );
37
- };
38
-
39
- MobileHeader.propTypes = {
40
- studioBaseUrl: PropTypes.string.isRequired, // eslint-disable-line react/no-unused-prop-types
41
- logoutUrl: PropTypes.string.isRequired, // eslint-disable-line react/no-unused-prop-types
42
- number: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
43
- org: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
44
- title: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
45
- logo: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
46
- logoAltText: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
47
- authenticatedUserAvatar: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
48
- username: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
49
- isAdmin: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types
50
- mainMenuDropdowns: PropTypes.arrayOf(PropTypes.shape({
51
- id: PropTypes.string,
52
- buttonTitle: PropTypes.node,
53
- items: PropTypes.arrayOf(PropTypes.shape({
54
- href: PropTypes.string,
55
- title: PropTypes.node,
56
- })),
57
- })),
58
- outlineLink: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
59
- };
60
-
61
- MobileHeader.defaultProps = {
62
- logo: null,
63
- logoAltText: null,
64
- number: null,
65
- org: null,
66
- title: null,
67
- authenticatedUserAvatar: null,
68
- username: null,
69
- isAdmin: false,
70
- mainMenuDropdowns: [],
71
- outlineLink: null,
72
- };
73
-
74
- export default MobileHeader;
@@ -1,81 +0,0 @@
1
- import React from 'react';
2
- import { render, screen, fireEvent } from '@testing-library/react';
3
- import { MemoryRouter } from 'react-router-dom';
4
-
5
- import '@testing-library/jest-dom/extend-expect';
6
- import MobileMenu from './MobileMenu';
7
-
8
- const mockOnNavigate = jest.fn();
9
-
10
- const defaultProps = {
11
- mainMenuDropdowns: [
12
- {
13
- id: 'menu1',
14
- buttonTitle: 'Menu 1',
15
- items: [
16
- { href: '/menu1/item1', title: 'Item 1' },
17
- { href: '/menu1/item2', title: 'Item 2' },
18
- ],
19
- },
20
- {
21
- id: 'menu2',
22
- buttonTitle: 'Menu 2',
23
- items: [
24
- { href: 'https://external-link.com', title: 'External Link' },
25
- ],
26
- },
27
- ],
28
- onNavigate: mockOnNavigate,
29
- };
30
-
31
- const RootWrapper = (props) => (
32
- <MemoryRouter>
33
- <MobileMenu {...props} />
34
- </MemoryRouter>
35
- );
36
-
37
- describe('MobileMenu Component', () => {
38
- afterEach(() => {
39
- jest.clearAllMocks();
40
- });
41
-
42
- test('renders the mobile menu with dropdowns and items', () => {
43
- render(<RootWrapper {...defaultProps} />);
44
-
45
- const menu1Title = screen.getByText('Menu 1');
46
- const menu2Title = screen.getByText('Menu 2');
47
-
48
- expect(menu1Title).toBeInTheDocument();
49
- expect(menu2Title).toBeInTheDocument();
50
- });
51
-
52
- test('navigates to internal URL when item is clicked', () => {
53
- render(<RootWrapper {...defaultProps} />);
54
-
55
- const menu1Title = screen.getByText(defaultProps.mainMenuDropdowns[0].buttonTitle);
56
- fireEvent.click(menu1Title);
57
-
58
- const menuItem = screen.getByText(defaultProps.mainMenuDropdowns[0].items[0].title);
59
- expect(menuItem.getAttribute('href')).toBe(defaultProps.mainMenuDropdowns[0].items[0].href);
60
- });
61
-
62
- test('navigates to an external URL when external link is clicked', () => {
63
- render(<RootWrapper {...defaultProps} />);
64
-
65
- const menu2Title = screen.getByText(defaultProps.mainMenuDropdowns[1].buttonTitle);
66
- fireEvent.click(menu2Title);
67
-
68
- const externalLink = screen.getByText(defaultProps.mainMenuDropdowns[1].items[0].title);
69
- expect(externalLink.getAttribute('href')).toBe(defaultProps.mainMenuDropdowns[1].items[0].href);
70
- });
71
-
72
- test('renders empty state when there are no dropdowns', () => {
73
- render(<RootWrapper mainMenuDropdowns={[]} onNavigate={mockOnNavigate} />);
74
-
75
- const mobileMenu = screen.getByTestId('mobile-menu');
76
- expect(mobileMenu).toBeInTheDocument();
77
-
78
- const menuItems = screen.queryAllByRole('listitem');
79
- expect(menuItems.length).toBe(0);
80
- });
81
- });
@@ -1,50 +0,0 @@
1
- import React from 'react';
2
- import PropTypes from 'prop-types';
3
- import { Collapsible } from '@openedx/paragon';
4
- import { Link } from 'react-router-dom';
5
-
6
- const MobileMenu = ({ mainMenuDropdowns }) => (
7
- <div
8
- className="ml-4 p-2 bg-light-100 border border-gray-200 small rounded"
9
- data-testid="mobile-menu"
10
- >
11
- <div>
12
- {mainMenuDropdowns.map(dropdown => {
13
- const { id, buttonTitle, items } = dropdown;
14
- return (
15
- <Collapsible
16
- className="border-light-100"
17
- title={buttonTitle}
18
- key={id}
19
- >
20
- <ul className="p-0" style={{ listStyleType: 'none' }}>
21
- {items.map(item => (
22
- <li className="mobile-menu-item">
23
- <Link to={item.href}>
24
- {item.title}
25
- </Link>
26
- </li>
27
- ))}
28
- </ul>
29
- </Collapsible>
30
- );
31
- })}
32
- </div>
33
- </div>
34
- );
35
-
36
- MobileMenu.propTypes = {
37
- mainMenuDropdowns: PropTypes.arrayOf(PropTypes.shape({
38
- id: PropTypes.string,
39
- buttonTitle: PropTypes.node,
40
- items: PropTypes.arrayOf(PropTypes.shape({
41
- href: PropTypes.string,
42
- title: PropTypes.node,
43
- })),
44
- })),
45
- };
46
- MobileMenu.defaultProps = {
47
- mainMenuDropdowns: [],
48
- };
49
-
50
- export default MobileMenu;
@@ -1,67 +0,0 @@
1
- import React from 'react';
2
- import { render, screen, fireEvent } from '@testing-library/react';
3
- import '@testing-library/jest-dom/extend-expect';
4
- import { MemoryRouter } from 'react-router-dom';
5
-
6
- import NavDropdownMenu from './NavDropdownMenu';
7
-
8
- const defaultProps = {
9
- id: 'menu-id',
10
- buttonTitle: 'Menu',
11
- items: [
12
- { href: '/item1', title: 'Item 1' },
13
- { href: 'https://external.com', title: 'External Link' },
14
- ],
15
- };
16
-
17
- const RootWrapper = (props) => (
18
- <MemoryRouter>
19
- <NavDropdownMenu {...props} />
20
- </MemoryRouter>
21
- );
22
-
23
- describe('NavDropdownMenu Component', () => {
24
- afterEach(() => {
25
- jest.clearAllMocks();
26
- });
27
-
28
- test('renders the dropdown button with correct title', () => {
29
- render(<NavDropdownMenu {...defaultProps} />);
30
-
31
- const dropdownButton = screen.getByRole('button', { name: defaultProps.buttonTitle });
32
- expect(dropdownButton).toBeInTheDocument();
33
- });
34
-
35
- test('renders all dropdown items', () => {
36
- render(<RootWrapper {...defaultProps} />);
37
-
38
- const dropdownButton = screen.getByRole('button', { name: defaultProps.buttonTitle });
39
- fireEvent.click(dropdownButton);
40
-
41
- const item1 = screen.getByText(defaultProps.items[0].title);
42
- const externalLink = screen.getByText(defaultProps.items[1].title);
43
-
44
- expect(item1).toBeInTheDocument();
45
- expect(externalLink).toBeInTheDocument();
46
- });
47
-
48
- test('calls onNavigate with the correct URL for internal link', () => {
49
- render(<RootWrapper {...defaultProps} />);
50
-
51
- const dropdownButton = screen.getByRole('button', { name: defaultProps.buttonTitle });
52
- fireEvent.click(dropdownButton);
53
-
54
- const item1 = screen.getByText(defaultProps.items[0].title);
55
- expect(item1.getAttribute('href')).toBe(defaultProps.items[0].href);
56
- });
57
-
58
- test('navigates to external URL when external link is clicked', () => {
59
- render(<RootWrapper {...defaultProps} />);
60
-
61
- const dropdownButton = screen.getByRole('button', { name: defaultProps.buttonTitle });
62
- fireEvent.click(dropdownButton);
63
-
64
- const externalLink = screen.getByText(defaultProps.items[1].title);
65
- expect(externalLink.getAttribute('href')).toBe(defaultProps.items[1].href);
66
- });
67
- });
@@ -1,42 +0,0 @@
1
- import React from 'react';
2
- import PropTypes from 'prop-types';
3
- import {
4
- Dropdown,
5
- DropdownButton,
6
- } from '@openedx/paragon';
7
- import { Link } from 'react-router-dom';
8
-
9
- const NavDropdownMenu = ({
10
- id,
11
- buttonTitle,
12
- items,
13
- }) => (
14
- <DropdownButton
15
- id={id}
16
- title={buttonTitle}
17
- variant="outline-primary"
18
- className="mr-2"
19
- >
20
- {items.map(item => (
21
- <Dropdown.Item
22
- as={Link}
23
- key={`${item.title}-dropdown-item`}
24
- to={item.href}
25
- className="small"
26
- >
27
- {item.title}
28
- </Dropdown.Item>
29
- ))}
30
- </DropdownButton>
31
- );
32
-
33
- NavDropdownMenu.propTypes = {
34
- id: PropTypes.string.isRequired,
35
- buttonTitle: PropTypes.node.isRequired,
36
- items: PropTypes.arrayOf(PropTypes.shape({
37
- href: PropTypes.string.isRequired,
38
- title: PropTypes.node.isRequired,
39
- })).isRequired,
40
- };
41
-
42
- export default NavDropdownMenu;