Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

563-refactor: Widget school menu #659

Merged
merged 30 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
fe40b43
refactor: 634 - resolve sass deprecation warning
Quiddlee Nov 9, 2024
54531c5
refactor: 563 - move school menu to dedicated folder
Quiddlee Nov 10, 2024
7f26f38
refactor: 563 - break down styles to their own files
Quiddlee Nov 10, 2024
b42f8b2
refactor: 563 - school menu scss
Quiddlee Nov 10, 2024
1fbb70f
refactor: 563 - move all anchor links to constant
Quiddlee Nov 11, 2024
a221a99
refactor: 563 - move get menu items to separate file
Quiddlee Nov 11, 2024
611557a
refactor: 563 - move links to dev-data
Quiddlee Nov 11, 2024
5921255
refactor: 563 - create color type
Quiddlee Nov 11, 2024
1aaeb22
refactor: 563 - merge school list with school menu component
Quiddlee Nov 11, 2024
a474ac9
refactor: 563 - menu as compound component
Quiddlee Nov 22, 2024
00334b5
fix: 563 - test issues
Quiddlee Nov 22, 2024
b3f16aa
refactor: 563 - remove type re-export
Quiddlee Nov 22, 2024
c50edcd
refactor: 563 - replace interface with type
Quiddlee Nov 22, 2024
7660258
refactor: 563 - replace tag selectors with classes
Quiddlee Nov 22, 2024
b170679
fix: 563 - stylelint issue
Quiddlee Nov 22, 2024
8b9416a
chore: 563 - resolve merge conflicts
Quiddlee Nov 22, 2024
0af2681
fix: 563 - incorrect course dates
Quiddlee Nov 22, 2024
3f6b668
fix: 563 - css property alignment
Quiddlee Nov 22, 2024
fed9d17
fix: 563 - remove important
Quiddlee Dec 2, 2024
1938b82
refactor: 563 - remove redundant test case
Quiddlee Dec 2, 2024
48df3e2
feat: add desktop menu playwright test
Quiddlee Dec 2, 2024
314e8a3
feat: update desktop menu test
Quiddlee Dec 2, 2024
f4e1ec6
fix: desktop menu test issue
Quiddlee Dec 2, 2024
c1f66c2
fix: menu incorrect width on safari issue
Quiddlee Dec 2, 2024
c9aa295
refactor: remove redundant style
Quiddlee Dec 2, 2024
648830b
fix: overflow issue
Quiddlee Dec 7, 2024
3090685
fix: mobile menu height issue
Quiddlee Dec 7, 2024
eee1a00
fix: 563 - to close menu on nav item click
Quiddlee Dec 13, 2024
1ea946c
fix: 563 - minor issues
Quiddlee Dec 14, 2024
6f80458
fix: 563 - to add aria-hidden to menu icons
Quiddlee Dec 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dev-data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export { awsFundamentals } from './awsFundamentals.data';
export { benefitMentorshipHome, benefitMentorshipMentors } from './benefit-mentorship.data';
export { communicationText } from './widget-communication.data';
export { communityGroups } from './community-media.data';
export { communityMenuStaticLinks, schoolMenuStaticLinks } from './school-menu-links';
export { contentMap } from './training-program.data';
export { contentMapAbout, introLocalizedContent } from './about-course.data';
export { contributeOptions } from './contribute-options.data';
Expand Down
37 changes: 37 additions & 0 deletions dev-data/school-menu-links.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ANCHORS, ROUTES } from '@/core/const';

export const schoolMenuStaticLinks = [
{
title: 'About RS School',
detailsUrl: `/#${ANCHORS.ABOUT_SCHOOL}`,
description: 'Free online education',
},
{
title: 'Upcoming courses',
detailsUrl: `/#${ANCHORS.UPCOMING_COURSES}`,
description: 'Schedule your study',
},
];

export const communityMenuStaticLinks = [
{
title: 'About',
detailsUrl: `/${ROUTES.COMMUNITY}/#${ANCHORS.ABOUT_COMMUNITY}`,
description: 'Who we are',
},
{
title: 'Events',
detailsUrl: `/${ROUTES.COMMUNITY}/#${ANCHORS.EVENTS}`,
description: 'Meet us at events',
},
{
title: 'Merch',
detailsUrl: `/${ROUTES.COMMUNITY}/#${ANCHORS.MERCH}`,
description: 'Sloths for your daily life',
},
{
title: 'Contribute',
detailsUrl: `/${ROUTES.COMMUNITY}/#${ANCHORS.CONTRIBUTE}`,
description: 'Assist us and improve yourself',
},
];
26 changes: 24 additions & 2 deletions src/core/base-layout/components/footer/desktop-view.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AboutList } from './about-list';
import { getCourses } from '@/entities/course/api/course-api';
import { SchoolMenu } from '@/widgets/school-menu';
import { schoolMenuStaticLinks } from 'data';

export const DesktopView = async () => {
const courses = await getCourses();
Expand All @@ -9,11 +10,32 @@ export const DesktopView = async () => {
<div className="desktop-view" data-testid="desktop-view">
<div className="left">
<AboutList />
<SchoolMenu courses={courses} heading="rs school" />
<SchoolMenu heading="rs school" color="light">
{schoolMenuStaticLinks.map((link, i) => (
<SchoolMenu.Item
key={i}
title={link.title}
description={link.description}
url={link.detailsUrl}
color="light"
/>
))}
Quiddlee marked this conversation as resolved.
Show resolved Hide resolved
</SchoolMenu>
</div>

<div className="right">
<SchoolMenu courses={courses} heading="all courses" />
<SchoolMenu heading="all courses" color="light">
{courses.map((course) => (
<SchoolMenu.Item
key={course.id}
icon={course.iconSmall}
title={course.title}
description={course.startDate}
url={course.detailsUrl}
color="light"
/>
))}
</SchoolMenu>
</div>
</div>
);
Expand Down
4 changes: 2 additions & 2 deletions src/core/base-layout/components/header/header.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@

width: 100%;
height: min-content;
min-height: 100vh;
max-height: 100vh;
min-height: 100dvh;
max-height: 100dvh;
margin-top: 0;
padding: 4px 24px 28px 16px;

Expand Down
112 changes: 57 additions & 55 deletions src/core/base-layout/components/header/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Course } from '@/entities/course';
import { Logo } from '@/shared/ui/logo';
import { MobileView } from '@/widgets/mobile-view';
import { SchoolMenu } from '@/widgets/school-menu';
import { communityMenuStaticLinks, mentorshipCourses, schoolMenuStaticLinks } from 'data';

import styles from './header.module.scss';

Expand All @@ -19,34 +20,9 @@ type HeaderProps = {
courses: Course[];
};

const navLinks = [
{
label: 'RS School',
href: ROUTES.HOME,
heading: 'rs school',
},
{
label: 'Courses',
href: `/${ROUTES.COURSES}`,
heading: 'all courses',
},
{
label: 'Community',
href: `/${ROUTES.COMMUNITY}`,
heading: 'community',
},
{
label: 'Mentorship',
href: `/${ROUTES.MENTORSHIP}`,
heading: 'mentorship',
},
] as const;

export const Header = ({ courses }: HeaderProps) => {
const [isMenuOpen, setMenuOpen] = useState(false);
const [color, setColor] = useState('gray');
const [hash, setHash] = useState('');
const [key, setKey] = useState('');
const pathname = usePathname();

// const headerAccentColor = pathname.includes(ROUTES.MENTORSHIP) ? 'blue' : 'gray';
Expand All @@ -60,6 +36,10 @@ export const Header = ({ courses }: HeaderProps) => {
setMenuOpen((prev) => !prev);
};

const handleMenuClose = () => {
setMenuOpen(false);
};

useEffect(() => {
const listenScrollEvent = () => {
const scrollY = window.scrollY;
Expand All @@ -81,47 +61,69 @@ export const Header = ({ courses }: HeaderProps) => {
}, [headerAccentColor]);

useEffect(() => {
if (typeof window !== 'undefined') {
setHash(window.location.hash);
setKey(window.location.href);
}
}, [pathname]);

useEffect(() => {
if (location.pathname) {
setMenuOpen(false);
setColor(headerAccentColor);
}
}, [key, hash, pathname, headerAccentColor]);
setColor(headerAccentColor);
}, [pathname, headerAccentColor]);

return (
<nav className={cx('navbar', color)} data-testid="navigation">
<section className={cx('navbar-content')}>
<Logo />

<menu className={cx('mobile-menu', { open: isMenuOpen })} data-testid="mobile-menu">
<MobileView courses={courses} type="header" />
<MobileView onClose={handleMenuClose} courses={courses} type="header" />
</menu>
<BurgerMenu isMenuOpen={isMenuOpen} toggleMenu={toggleMenu} />

<menu className={cx('menu')}>
{navLinks.map((link) => {
return (
<NavItem
key={link.label}
label={link.label}
href={link.href}
dropdownInner={(
<SchoolMenu
courses={courses}
heading={link.heading}
color="dark"
hasTitle={false}
/>
)}
/>
);
})}
<NavItem label="RS School" href={ROUTES.HOME}>
<SchoolMenu>
{schoolMenuStaticLinks.map((link, i) => (
<SchoolMenu.Item
key={i}
title={link.title}
description={link.description}
url={link.detailsUrl}
/>
))}
</SchoolMenu>
</NavItem>
<NavItem label="Courses" href={ROUTES.COURSES}>
<SchoolMenu>
{courses.map((course) => (
<SchoolMenu.Item
key={course.id}
icon={course.iconSmall}
title={course.title}
description={course.startDate}
url={course.detailsUrl}
/>
))}
</SchoolMenu>
</NavItem>
<NavItem label="Community" href={ROUTES.COMMUNITY}>
<SchoolMenu>
{communityMenuStaticLinks.map((link, i) => (
<SchoolMenu.Item
key={i}
title={link.title}
description={link.description}
url={link.detailsUrl}
/>
))}
</SchoolMenu>
</NavItem>
<NavItem label="Mentorship" href={ROUTES.MENTORSHIP}>
<SchoolMenu>
{mentorshipCourses.map((course) => (
<SchoolMenu.Item
key={course.id}
icon={course.iconSmall}
title={course.title}
url={course.detailsUrl}
/>
))}
</SchoolMenu>
</NavItem>
</menu>
</section>
</nav>
Expand Down
24 changes: 14 additions & 10 deletions src/core/base-layout/components/header/nav-item/nav-item.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
FocusEvent,
KeyboardEvent,
ReactNode,
PropsWithChildren,
useEffect,
useRef,
useState,
Expand All @@ -16,13 +16,12 @@ import styles from './nav-item.module.scss';

const cx = classNames.bind(styles);

type NavItemProps = {
type NavItemProps = PropsWithChildren & {
label: string;
href: string;
dropdownInner?: ReactNode;
};

export const NavItem = ({ label, href, dropdownInner }: NavItemProps) => {
export const NavItem = ({ label, href, children }: NavItemProps) => {
const [isDropdownOpen, setDropdownOpen] = useState(false);

const dropdownToggleRef = useRef<HTMLButtonElement>(null);
Expand Down Expand Up @@ -58,20 +57,25 @@ export const NavItem = ({ label, href, dropdownInner }: NavItemProps) => {
}, [pathname]);

return (
<div className={cx('menu-item-wrapper')} onBlur={handleBlur} onKeyDown={handleEscKeyPress}>
<div
className={cx('menu-item-wrapper')}
onBlur={handleBlur}
onKeyDown={handleEscKeyPress}
data-testid="menu-item"
>
<Link
href={href}
href={`/${href}`}
className={cx(
'menu-item',
{ active: isActive },
{ 'dropdown-toggle': !!dropdownInner },
{ 'dropdown-toggle': Boolean(children) },
{ rotate: isDropdownOpen },
)}
onMouseLeave={onClose}
onMouseEnter={onOpen}
>
<span className={cx('label')}>{label}</span>
{dropdownInner && (
{children && (
<button
onKeyDown={handleConfirmKeyPress}
ref={dropdownToggleRef}
Expand All @@ -82,9 +86,9 @@ export const NavItem = ({ label, href, dropdownInner }: NavItemProps) => {
</button>
)}
</Link>
{dropdownInner && (
{children && (
<DropdownWrapper onMouseLeave={onClose} onMouseEnter={onOpen} isOpen={isDropdownOpen}>
{dropdownInner}
{children}
</DropdownWrapper>
)}
</div>
Expand Down
4 changes: 4 additions & 0 deletions src/core/const/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ export const ANCHORS = {
ABOUT_COMMUNITY: 'about-community',
ABOUT_SCHOOL: 'about-school',
MENTORS_WANTED: 'mentors-wanted',
UPCOMING_COURSES: 'upcoming-courses',
EVENTS: 'events',
MERCH: 'merch',
CONTRIBUTE: 'contribute',
};

export const COURSE_STALE_AFTER_DAYS = 14;
Expand Down
12 changes: 12 additions & 0 deletions src/shared/__tests__/visual/main.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,15 @@ test('Main page mobile', async ({ page }) => {
await page.getByTestId('burger').click();
await expect(mobileMenu).not.toBeInViewport();
});

test('Main page desktop menu', async ({ page }) => {
await page.goto(ROUTES.HOME);

const elements = page.getByTestId('menu-item');
const elementsCount = await elements.count();

for (let i = 0; i < elementsCount; i++) {
await elements.nth(i).hover();
await takeScreenshot(page, `Main page desktop - menu open ${i + 1}`);
}
});
Loading
Loading