@nauedu/frontend-component-header 20.1.0 → 20.1.1
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.
- package/dist/Header.js +13 -9
- package/dist/Header.js.map +1 -1
- package/dist/Header.test.jsx +3 -0
- package/dist/Icons.js +1 -1
- package/dist/LanguageSelector/data/api.js +13 -5
- package/dist/LanguageSelector/data/api.js.map +1 -1
- package/dist/LanguageSelector/index.js +12 -4
- package/dist/LanguageSelector/index.js.map +1 -1
- package/dist/Logo.js +10 -23
- package/dist/Logo.js.map +1 -1
- package/dist/Menu/Menu.js +11 -11
- package/dist/Menu/Menu.js.map +1 -1
- package/dist/Menu/menu.scss +6 -6
- package/dist/__snapshots__/Header.test.jsx.snap +2 -0
- package/dist/desktop-header/DesktopHeader.js +172 -0
- package/dist/desktop-header/DesktopHeader.js.map +1 -0
- package/dist/desktop-header/DesktopHeaderMainOrSecondaryMenu.js +51 -0
- package/dist/desktop-header/DesktopHeaderMainOrSecondaryMenu.js.map +1 -0
- package/dist/desktop-header/DesktopHeaderUserMenu.js +49 -0
- package/dist/desktop-header/DesktopHeaderUserMenu.js.map +1 -0
- package/dist/desktop-header/DesktopLoggedOutItems.js +22 -0
- package/dist/desktop-header/DesktopLoggedOutItems.js.map +1 -0
- package/dist/desktop-header/DesktopUserMenuToggle.js +25 -0
- package/dist/desktop-header/DesktopUserMenuToggle.js.map +1 -0
- package/dist/frontend-platform.d.ts +41 -0
- package/dist/index.scss +27 -15
- package/dist/learning-header/AnonymousUserMenu.js +11 -8
- package/dist/learning-header/AnonymousUserMenu.js.map +1 -1
- package/dist/learning-header/AuthenticatedUserDropdown.js +32 -24
- package/dist/learning-header/AuthenticatedUserDropdown.js.map +1 -1
- package/dist/learning-header/LearningHeader.js +17 -15
- package/dist/learning-header/LearningHeader.js.map +1 -1
- package/dist/learning-header/LearningHeaderCourseInfo.js +24 -0
- package/dist/learning-header/LearningHeaderCourseInfo.js.map +1 -0
- package/dist/learning-header/LearningHeaderHelpLink.js +13 -0
- package/dist/learning-header/LearningHeaderHelpLink.js.map +1 -0
- package/dist/learning-header/LearningHeaderUserMenuItems.js +20 -0
- package/dist/learning-header/LearningHeaderUserMenuItems.js.map +1 -0
- package/dist/learning-header/LearningLoggedOutButtons.js +24 -0
- package/dist/learning-header/LearningLoggedOutButtons.js.map +1 -0
- package/dist/learning-header/LearningUserMenuToggle.js +26 -0
- package/dist/learning-header/LearningUserMenuToggle.js.map +1 -0
- package/dist/learning-header/data/api.js +12 -4
- package/dist/learning-header/data/api.js.map +1 -1
- package/dist/learning-header/data/api.test.js +92 -95
- package/dist/learning-header/data/api.test.js.map +1 -1
- package/dist/learning-header/messages.js +5 -0
- package/dist/learning-header/messages.js.map +1 -1
- package/dist/mobile-header/MobileHeader.js +197 -0
- package/dist/mobile-header/MobileHeader.js.map +1 -0
- package/dist/mobile-header/MobileHeaderMainMenu.js +46 -0
- package/dist/mobile-header/MobileHeaderMainMenu.js.map +1 -0
- package/dist/mobile-header/MobileHeaderUserMenu.js +38 -0
- package/dist/mobile-header/MobileHeaderUserMenu.js.map +1 -0
- package/dist/mobile-header/MobileLoggedOutItems.js +27 -0
- package/dist/mobile-header/MobileLoggedOutItems.js.map +1 -0
- package/dist/mobile-header/MobileUserMenuToggle.js +19 -0
- package/dist/mobile-header/MobileUserMenuToggle.js.map +1 -0
- package/dist/plugin-slots/CourseInfoSlot/README.md +128 -0
- package/dist/plugin-slots/CourseInfoSlot/images/add_custom_components_before_and_after_course_info.png +0 -0
- package/dist/plugin-slots/CourseInfoSlot/images/replace_course_info_with_custom_component.png +0 -0
- package/dist/plugin-slots/CourseInfoSlot/images/replace_course_title.png +0 -0
- package/dist/plugin-slots/CourseInfoSlot/index.js +32 -0
- package/dist/plugin-slots/CourseInfoSlot/index.js.map +1 -0
- package/dist/plugin-slots/DesktopHeaderSlot/README.md +44 -0
- package/dist/plugin-slots/DesktopHeaderSlot/images/desktop_header_custom_component.png +0 -0
- package/dist/plugin-slots/DesktopHeaderSlot/index.js +16 -0
- package/dist/plugin-slots/DesktopHeaderSlot/index.js.map +1 -0
- package/dist/plugin-slots/DesktopLoggedOutItemsSlot/README.md +137 -0
- package/dist/plugin-slots/DesktopLoggedOutItemsSlot/images/desktop_logged_out_items_custom_component.png +0 -0
- package/dist/plugin-slots/DesktopLoggedOutItemsSlot/images/desktop_logged_out_items_custom_components_before_after.png +0 -0
- package/dist/plugin-slots/DesktopLoggedOutItemsSlot/images/desktop_logged_out_items_modify_items.png +0 -0
- package/dist/plugin-slots/DesktopLoggedOutItemsSlot/index.js +20 -0
- package/dist/plugin-slots/DesktopLoggedOutItemsSlot/index.js.map +1 -0
- package/dist/plugin-slots/DesktopMainMenuSlot/README.md +137 -0
- package/dist/plugin-slots/DesktopMainMenuSlot/images/desktop_main_menu_custom_component.png +0 -0
- package/dist/plugin-slots/DesktopMainMenuSlot/images/desktop_main_menu_custom_components_before_after.png +0 -0
- package/dist/plugin-slots/DesktopMainMenuSlot/images/desktop_main_menu_modify_items.png +0 -0
- package/dist/plugin-slots/DesktopMainMenuSlot/index.js +20 -0
- package/dist/plugin-slots/DesktopMainMenuSlot/index.js.map +1 -0
- package/dist/plugin-slots/DesktopSecondaryMenuSlot/README.md +132 -0
- package/dist/plugin-slots/DesktopSecondaryMenuSlot/images/desktop_secondary_menu_custom_component.png +0 -0
- package/dist/plugin-slots/DesktopSecondaryMenuSlot/images/desktop_secondary_menu_custom_components_before_after.png +0 -0
- package/dist/plugin-slots/DesktopSecondaryMenuSlot/images/desktop_secondary_menu_modify_items.png +0 -0
- package/dist/plugin-slots/DesktopSecondaryMenuSlot/index.js +20 -0
- package/dist/plugin-slots/DesktopSecondaryMenuSlot/index.js.map +1 -0
- package/dist/plugin-slots/DesktopUserMenuSlot/README.md +144 -0
- package/dist/plugin-slots/DesktopUserMenuSlot/images/desktop_user_menu_custom_component.png +0 -0
- package/dist/plugin-slots/DesktopUserMenuSlot/images/desktop_user_menu_custom_components_before_after.png +0 -0
- package/dist/plugin-slots/DesktopUserMenuSlot/images/desktop_user_menu_modify_items.png +0 -0
- package/dist/plugin-slots/DesktopUserMenuSlot/index.js +20 -0
- package/dist/plugin-slots/DesktopUserMenuSlot/index.js.map +1 -0
- package/dist/plugin-slots/DesktopUserMenuToggleSlot/README.md +74 -0
- package/dist/plugin-slots/DesktopUserMenuToggleSlot/images/desktop_user_menu_custom_component.png +0 -0
- package/dist/plugin-slots/DesktopUserMenuToggleSlot/images/desktop_user_menu_modified_toggle.png +0 -0
- package/dist/plugin-slots/DesktopUserMenuToggleSlot/index.js +19 -0
- package/dist/plugin-slots/DesktopUserMenuToggleSlot/index.js.map +1 -0
- package/dist/plugin-slots/LearningHelpSlot/README.md +44 -0
- package/dist/plugin-slots/LearningHelpSlot/images/learning_help_custom_component.png +0 -0
- package/dist/plugin-slots/LearningHelpSlot/index.js +11 -0
- package/dist/plugin-slots/LearningHelpSlot/index.js.map +1 -0
- package/dist/plugin-slots/LearningLoggedOutItemsSlot/README.md +135 -0
- package/dist/plugin-slots/LearningLoggedOutItemsSlot/images/learning_logged_out_items_custom_component.png +0 -0
- package/dist/plugin-slots/LearningLoggedOutItemsSlot/images/learning_logged_out_items_custom_components_before_after.png +0 -0
- package/dist/plugin-slots/LearningLoggedOutItemsSlot/images/learning_logged_out_items_modified_items.png +0 -0
- package/dist/plugin-slots/LearningLoggedOutItemsSlot/index.js +18 -0
- package/dist/plugin-slots/LearningLoggedOutItemsSlot/index.js.map +1 -0
- package/dist/plugin-slots/LearningUserMenuSlot/README.md +133 -0
- package/dist/plugin-slots/LearningUserMenuSlot/images/learning_user_menu_custom_component.png +0 -0
- package/dist/plugin-slots/LearningUserMenuSlot/images/learning_user_menu_custom_components_before_after.png +0 -0
- package/dist/plugin-slots/LearningUserMenuSlot/images/learning_user_menu_modified_items.png +0 -0
- package/dist/plugin-slots/LearningUserMenuSlot/index.js +18 -0
- package/dist/plugin-slots/LearningUserMenuSlot/index.js.map +1 -0
- package/dist/plugin-slots/LearningUserMenuToggleSlot/README.md +74 -0
- package/dist/plugin-slots/LearningUserMenuToggleSlot/images/learning_user_menu_toggle_custom_component.png +0 -0
- package/dist/plugin-slots/LearningUserMenuToggleSlot/images/learning_user_menu_toggle_modified_items.png +0 -0
- package/dist/plugin-slots/LearningUserMenuToggleSlot/index.js +19 -0
- package/dist/plugin-slots/LearningUserMenuToggleSlot/index.js.map +1 -0
- package/dist/plugin-slots/LogoSlot/README.md +72 -0
- package/dist/plugin-slots/LogoSlot/index.js +27 -0
- package/dist/plugin-slots/LogoSlot/index.js.map +1 -0
- package/dist/plugin-slots/MobileHeaderSlot/README.md +44 -0
- package/dist/plugin-slots/MobileHeaderSlot/images/mobile_header_custom_component.png +0 -0
- package/dist/plugin-slots/MobileHeaderSlot/index.js +16 -0
- package/dist/plugin-slots/MobileHeaderSlot/index.js.map +1 -0
- package/dist/plugin-slots/MobileLoggedOutItemsSlot/README.md +136 -0
- package/dist/plugin-slots/MobileLoggedOutItemsSlot/images/mobile_logged_out_items_custom_component.png +0 -0
- package/dist/plugin-slots/MobileLoggedOutItemsSlot/images/mobile_logged_out_items_custom_components_before_after.png +0 -0
- package/dist/plugin-slots/MobileLoggedOutItemsSlot/images/mobile_logged_out_items_modify_items.png +0 -0
- package/dist/plugin-slots/MobileLoggedOutItemsSlot/index.js +20 -0
- package/dist/plugin-slots/MobileLoggedOutItemsSlot/index.js.map +1 -0
- package/dist/plugin-slots/MobileMainMenuSlot/README.md +136 -0
- package/dist/plugin-slots/MobileMainMenuSlot/images/mobile_main_menu_custom_component.png +0 -0
- package/dist/plugin-slots/MobileMainMenuSlot/images/mobile_main_menu_custom_components_before_after.png +0 -0
- package/dist/plugin-slots/MobileMainMenuSlot/images/mobile_main_menu_modify_items.png +0 -0
- package/dist/plugin-slots/MobileMainMenuSlot/index.js +20 -0
- package/dist/plugin-slots/MobileMainMenuSlot/index.js.map +1 -0
- package/dist/plugin-slots/MobileUserMenuSlot/README.md +144 -0
- package/dist/plugin-slots/MobileUserMenuSlot/images/mobile_user_menu_custom_component.png +0 -0
- package/dist/plugin-slots/MobileUserMenuSlot/images/mobile_user_menu_custom_components_before_after.png +0 -0
- package/dist/plugin-slots/MobileUserMenuSlot/images/mobile_user_menu_modify_items.png +0 -0
- package/dist/plugin-slots/MobileUserMenuSlot/index.js +20 -0
- package/dist/plugin-slots/MobileUserMenuSlot/index.js.map +1 -0
- package/dist/plugin-slots/MobileUserMenuToggleSlot/README.md +74 -0
- package/dist/plugin-slots/MobileUserMenuToggleSlot/images/mobile_user_menu_toggle_custom_component.png +0 -0
- package/dist/plugin-slots/MobileUserMenuToggleSlot/images/mobile_user_menu_toggle_modified_items.png +0 -0
- package/dist/plugin-slots/MobileUserMenuToggleSlot/index.js +19 -0
- package/dist/plugin-slots/MobileUserMenuToggleSlot/index.js.map +1 -0
- package/dist/plugin-slots/README.md +26 -0
- package/dist/setupTest.js +0 -2
- package/dist/studio-header/BrandNav.test.tsx +40 -0
- package/dist/studio-header/BrandNav.tsx +25 -0
- package/dist/studio-header/CourseLockUp.test.tsx +59 -0
- package/dist/studio-header/CourseLockUp.tsx +56 -0
- package/dist/studio-header/HeaderBody.test.tsx +102 -0
- package/dist/studio-header/HeaderBody.tsx +196 -0
- package/dist/studio-header/MobileHeader.tsx +74 -0
- package/dist/studio-header/MobileMenu.test.tsx +81 -0
- package/dist/studio-header/MobileMenu.tsx +50 -0
- package/dist/studio-header/NavDropdownMenu.test.tsx +67 -0
- package/dist/studio-header/NavDropdownMenu.tsx +42 -0
- package/dist/studio-header/StudioHeader.scss +8 -10
- package/dist/studio-header/StudioHeader.test.tsx +220 -0
- package/dist/studio-header/StudioHeader.tsx +84 -0
- package/dist/studio-header/UserMenu.tsx +69 -0
- package/dist/studio-header/{index.js → index.ts} +1 -1
- package/dist/studio-header/{messages.js → messages.ts} +15 -14
- package/dist/studio-header/utils.ts +37 -0
- package/dist/test-utils.js +12 -4
- package/dist/test-utils.js.map +1 -1
- package/package.json +1 -1
- package/dist/DesktopHeader.js +0 -242
- package/dist/DesktopHeader.js.map +0 -1
- package/dist/MobileHeader.js +0 -244
- package/dist/MobileHeader.js.map +0 -1
- package/dist/studio-header/BrandNav.js +0 -21
- package/dist/studio-header/BrandNav.js.map +0 -1
- package/dist/studio-header/CourseLockUp.js +0 -45
- package/dist/studio-header/CourseLockUp.js.map +0 -1
- package/dist/studio-header/HeaderBody.js +0 -131
- package/dist/studio-header/HeaderBody.js.map +0 -1
- package/dist/studio-header/MobileHeader.js +0 -79
- package/dist/studio-header/MobileHeader.js.map +0 -1
- package/dist/studio-header/MobileMenu.js +0 -45
- package/dist/studio-header/MobileMenu.js.map +0 -1
- package/dist/studio-header/NavDropdownMenu.js +0 -30
- package/dist/studio-header/NavDropdownMenu.js.map +0 -1
- package/dist/studio-header/StudioHeader.js +0 -72
- package/dist/studio-header/StudioHeader.js.map +0 -1
- package/dist/studio-header/UserMenu.js +0 -55
- package/dist/studio-header/UserMenu.js.map +0 -1
- package/dist/studio-header/index.js.map +0 -1
- package/dist/studio-header/messages.js.map +0 -1
- package/dist/studio-header/utils.js +0 -29
- package/dist/studio-header/utils.js.map +0 -1
|
@@ -0,0 +1,102 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,196 @@
|
|
|
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;
|
|
@@ -0,0 +1,74 @@
|
|
|
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;
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
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;
|
|
@@ -0,0 +1,67 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
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;
|
|
@@ -4,13 +4,11 @@ $white: #FFFFFF;
|
|
|
4
4
|
.studio-header {
|
|
5
5
|
position: relative;
|
|
6
6
|
z-index: 1000;
|
|
7
|
-
|
|
8
|
-
height: 3.75rem;
|
|
9
7
|
box-shadow: 0 1px 0 0 rgb(0 0 0 / .1);
|
|
10
|
-
background: $white;
|
|
8
|
+
background: var(--pgn-color-white, $white);
|
|
11
9
|
|
|
12
10
|
.btn-outline-primary {
|
|
13
|
-
border-color: $white;
|
|
11
|
+
border-color: var(--pgn-color-white, $white);
|
|
14
12
|
}
|
|
15
13
|
|
|
16
14
|
.logo {
|
|
@@ -19,8 +17,8 @@ $white: #FFFFFF;
|
|
|
19
17
|
position: relative;
|
|
20
18
|
top: -.05em;
|
|
21
19
|
height: 1.75rem;
|
|
22
|
-
padding: $spacer 0;
|
|
23
|
-
margin-right: $spacer;
|
|
20
|
+
padding: var(--pgn-spacing-spacer-base, $spacer) 0;
|
|
21
|
+
margin-right: var(--pgn-spacing-spacer-base, $spacer);
|
|
24
22
|
|
|
25
23
|
img {
|
|
26
24
|
display: block;
|
|
@@ -29,17 +27,17 @@ $white: #FFFFFF;
|
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
.course-title-lockup {
|
|
30
|
+
overflow: hidden;
|
|
31
|
+
|
|
32
32
|
@media only screen and (min-width: 769px) {
|
|
33
33
|
padding: .5rem;
|
|
34
|
-
padding-right: $spacer;
|
|
34
|
+
padding-right: var(--pgn-spacing-spacer-base, $spacer);
|
|
35
35
|
border-right: 1px solid #E5E5E5;
|
|
36
36
|
width: 70%;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
overflow: hidden;
|
|
40
|
-
|
|
41
39
|
span {
|
|
42
|
-
color: #333333;
|
|
40
|
+
color: var(--pgn-color-gray-800, #333333);
|
|
43
41
|
white-space: nowrap;
|
|
44
42
|
overflow: hidden;
|
|
45
43
|
text-overflow: ellipsis;
|