diff --git a/dev-data/index.ts b/dev-data/index.ts index 8e3fb3bcd..e9cfd3b86 100644 --- a/dev-data/index.ts +++ b/dev-data/index.ts @@ -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'; diff --git a/dev-data/school-menu-links.ts b/dev-data/school-menu-links.ts new file mode 100644 index 000000000..7f8801f97 --- /dev/null +++ b/dev-data/school-menu-links.ts @@ -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', + }, +]; diff --git a/src/core/base-layout/components/footer/desktop-view.tsx b/src/core/base-layout/components/footer/desktop-view.tsx index 4dd2b94c2..38b7cea05 100644 --- a/src/core/base-layout/components/footer/desktop-view.tsx +++ b/src/core/base-layout/components/footer/desktop-view.tsx @@ -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(); @@ -9,11 +10,32 @@ export const DesktopView = async () => {
- + + {schoolMenuStaticLinks.map((link, i) => ( + + ))} +
- + + {courses.map((course) => ( + + ))} +
); diff --git a/src/core/base-layout/components/header/header.module.scss b/src/core/base-layout/components/header/header.module.scss index 0033401ca..d0d608f8c 100644 --- a/src/core/base-layout/components/header/header.module.scss +++ b/src/core/base-layout/components/header/header.module.scss @@ -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; diff --git a/src/core/base-layout/components/header/header.tsx b/src/core/base-layout/components/header/header.tsx index da8e42f20..6ce4881e0 100644 --- a/src/core/base-layout/components/header/header.tsx +++ b/src/core/base-layout/components/header/header.tsx @@ -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'; @@ -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'; @@ -60,6 +36,10 @@ export const Header = ({ courses }: HeaderProps) => { setMenuOpen((prev) => !prev); }; + const handleMenuClose = () => { + setMenuOpen(false); + }; + useEffect(() => { const listenScrollEvent = () => { const scrollY = window.scrollY; @@ -81,18 +61,8 @@ 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 ( diff --git a/src/core/base-layout/components/header/nav-item/nav-item.tsx b/src/core/base-layout/components/header/nav-item/nav-item.tsx index 599e5019a..c6de06c85 100644 --- a/src/core/base-layout/components/header/nav-item/nav-item.tsx +++ b/src/core/base-layout/components/header/nav-item/nav-item.tsx @@ -1,7 +1,7 @@ import { FocusEvent, KeyboardEvent, - ReactNode, + PropsWithChildren, useEffect, useRef, useState, @@ -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(null); @@ -58,20 +57,25 @@ export const NavItem = ({ label, href, dropdownInner }: NavItemProps) => { }, [pathname]); return ( -
+
{label} - {dropdownInner && ( + {children && ( )} - {dropdownInner && ( + {children && ( - {dropdownInner} + {children} )}
diff --git a/src/core/const/index.ts b/src/core/const/index.ts index abd1b986c..09f5f1474 100644 --- a/src/core/const/index.ts +++ b/src/core/const/index.ts @@ -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; diff --git a/src/shared/__tests__/visual/main.spec.ts b/src/shared/__tests__/visual/main.spec.ts index b7e545fcb..a98a197ac 100644 --- a/src/shared/__tests__/visual/main.spec.ts +++ b/src/shared/__tests__/visual/main.spec.ts @@ -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}`); + } +}); diff --git a/src/widgets/mobile-view/ui/mobile-view.tsx b/src/widgets/mobile-view/ui/mobile-view.tsx index 6c578c409..3ae5807a1 100644 --- a/src/widgets/mobile-view/ui/mobile-view.tsx +++ b/src/widgets/mobile-view/ui/mobile-view.tsx @@ -4,6 +4,7 @@ import { ROUTES } from '@/core/const'; import { Course } from '@/entities/course'; import { Logo } from '@/shared/ui/logo'; import { SchoolMenu } from '@/widgets/school-menu'; +import { communityMenuStaticLinks, mentorshipCourses, schoolMenuStaticLinks } from 'data'; import styles from './mobile-view.module.scss'; @@ -18,9 +19,10 @@ const Divider = ({ color }: DividerProps) =>
void; }; -export const MobileView = ({ type, courses }: MobileViewProps) => { +export const MobileView = ({ type, courses, onClose }: MobileViewProps) => { const color = type === 'header' ? 'dark' : 'light'; const logoView = type === 'header' ? null : 'with-border'; @@ -30,35 +32,80 @@ export const MobileView = ({ type, courses }: MobileViewProps) => { - + RS School - + + {schoolMenuStaticLinks.map((link, i) => ( + + ))} + - + Courses - + + {courses.map((course) => ( + + ))} + - + Community - + + {communityMenuStaticLinks.map((link, i) => ( + + ))} + - + Mentorship - + + {mentorshipCourses.map((course) => ( + + ))} + ); }; diff --git a/src/widgets/school-menu/index.ts b/src/widgets/school-menu/index.ts index d88ff088d..5a81493ba 100644 --- a/src/widgets/school-menu/index.ts +++ b/src/widgets/school-menu/index.ts @@ -1 +1 @@ -export { SchoolMenu } from './ui/school-menu'; +export { SchoolMenu } from './ui/school-menu/school-menu'; diff --git a/src/widgets/school-menu/school-menu.test.tsx b/src/widgets/school-menu/school-menu.test.tsx deleted file mode 100644 index 867c9570d..000000000 --- a/src/widgets/school-menu/school-menu.test.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { screen } from '@testing-library/react'; -import { SchoolMenu } from './ui/school-menu'; -import { Course } from '@/entities/course'; -import { MOCKED_IMAGE_PATH, mockedCourses } from '@/shared/__tests__/constants'; -import { renderWithRouter } from '@/shared/__tests__/utils'; -import { COURSE_TITLES } from 'data'; - -describe('SchoolMenu', () => { - const aws = mockedCourses.find( - (course) => course.title === COURSE_TITLES.AWS_FUNDAMENTALS, - ) as Course; - const react = mockedCourses.find((course) => course.title === COURSE_TITLES.REACT) as Course; - - it('renders without crashing and displays "rs school" heading', () => { - renderWithRouter(); - - const headingElement = screen.getByRole('heading', { name: /rs school/i }); - - expect(headingElement).toBeInTheDocument(); - }); - - it('displays correct links and descriptions with "rs school" props', () => { - const { container } = renderWithRouter( - , - ); - - expect(screen.getAllByRole('link')).toHaveLength(2); - - const links = screen.getAllByRole('link'); - - links.forEach((link) => { - expect(link).toBeInTheDocument(); - }); - - const descriptions = container.getElementsByTagName('small'); - - for (const description of descriptions) { - expect(description).toBeInTheDocument(); - } - }); - - it('renders without crashing and displays "all courses" heading', () => { - renderWithRouter(); - - const headingElement = screen.getByRole('heading', { name: /all courses/i }); - - expect(headingElement).toBeInTheDocument(); - }); - - it('renders [mentorshipId] correct when "all courses" heading is passed', () => { - renderWithRouter(); - - const imageAWS = screen.getByRole('img', { name: aws.title }); - - expect(imageAWS).toHaveAttribute('src', MOCKED_IMAGE_PATH.src); - const imageReact = screen.getByRole('img', { name: react.title }); - - expect(imageReact).toHaveAttribute('src', MOCKED_IMAGE_PATH.src); - }); - - it('renders correct link description when date is passed', () => { - const { container } = renderWithRouter( - , - ); - - const descriptions = container.getElementsByClassName('description'); - - expect(descriptions).toHaveLength(6); - expect(descriptions[0]).toHaveTextContent(/tbd/i); - expect(descriptions[3]).toHaveTextContent(/tbd/i); - }); - - it('renders correct link for "AWS Fundamentals" and "React JS [mentorshipId]"', () => { - renderWithRouter(); - - const links = screen.getAllByRole('link'); - const linkReact = links.at(3); - const linkAWS = links.at(-1); - - expect(linkAWS).toHaveAttribute('href', aws.detailsUrl); - expect(linkReact).toHaveAttribute('href', react.detailsUrl); - }); -}); diff --git a/src/widgets/school-menu/types.ts b/src/widgets/school-menu/types.ts new file mode 100644 index 000000000..f00162b20 --- /dev/null +++ b/src/widgets/school-menu/types.ts @@ -0,0 +1 @@ +export type Color = 'dark' | 'light'; diff --git a/src/widgets/school-menu/ui/school-item/school-item.module.scss b/src/widgets/school-menu/ui/school-item/school-item.module.scss new file mode 100644 index 000000000..fdfca1147 --- /dev/null +++ b/src/widgets/school-menu/ui/school-item/school-item.module.scss @@ -0,0 +1,60 @@ +.school-item { + display: flex; + gap: 5px; + column-gap: 15px; + align-items: center; + + .title { + font-weight: $font-weight-medium; + line-height: 20px; + text-align: start; + + &.dark { + color: $color-gray-600; + } + + &.light { + color: $color-gray-200; + } + } + + &:hover { + .title { + &.dark { + color: $color-black; + } + + &.light { + color: $color-gray-400; + } + } + } + + &.with-icon { + display: flex; + flex-direction: row; + gap: 15px; + align-items: center; + justify-content: flex-start; + + .details { + display: flex; + flex-direction: column; + gap: 5px; + align-items: flex-start; + justify-content: flex-start; + } + } + + .description-wrapper { + display: flex; + flex-direction: column; + gap: 5px; + align-items: flex-start; + + .description { + font-size: 12px; + color: $color-gray-500; + } + } +} diff --git a/src/widgets/school-menu/ui/school-item/school-item.tsx b/src/widgets/school-menu/ui/school-item/school-item.tsx index 4189d6220..cb63fff0b 100644 --- a/src/widgets/school-menu/ui/school-item/school-item.tsx +++ b/src/widgets/school-menu/ui/school-item/school-item.tsx @@ -1,63 +1,49 @@ -import Image from 'next/image'; +/* eslint-disable @stylistic/jsx-closing-bracket-location */ +import { HTMLProps } from 'react'; +import classNames from 'classnames/bind'; +import Image, { StaticImageData } from 'next/image'; import Link from 'next/link'; -import { GenericItemProps } from '../school-list/school-list'; -import type { Course } from '@/entities/course'; -import { DateStart } from '@/shared/ui/date-start'; -import { MentorshipCourse } from 'data'; +import { Color } from '@/widgets/school-menu/types'; -interface SchoolItemProps { - item: MentorshipCourse | Course | GenericItemProps; - color: 'dark' | 'light'; -} +import styles from './school-item.module.scss'; -export const SchoolItem = ({ item, color }: SchoolItemProps) => { - const courseDate = 'startDate' in item && item.startDate; - const registrationEndDate = 'registrationEndDate' in item && item.registrationEndDate; - const descriptionText = 'description' in item ? item.description : courseDate; +const cx = classNames.bind(styles); - const descriptionContent = ( - <> - {item.title} - {courseDate && registrationEndDate - ? ( - - - ) - : ( - {descriptionText} - )} - - ); - - const descriptionBlock = - 'description' in item - ? ( - descriptionContent - ) - : ( -
{descriptionContent}
- ); +type SchoolItemProps = HTMLProps & { + title: string; + url: string; + description?: string; + icon?: StaticImageData; + color?: Color; +}; +export const SchoolItem = ({ + icon, + description, + title, + color = 'dark', + url, + ...props +}: SchoolItemProps) => { return ( -
  • - - {'iconSmall' in item && ( - {item.title} - )} - {descriptionBlock} +
  • + + {icon && } +
    + {title} + {description && ( + + {description} + + )} +
  • ); diff --git a/src/widgets/school-menu/ui/school-list/school-list.tsx b/src/widgets/school-menu/ui/school-list/school-list.tsx deleted file mode 100644 index d14bcaf8f..000000000 --- a/src/widgets/school-menu/ui/school-list/school-list.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { SchoolItem } from '../school-item/school-item'; -import type { Course } from '@/entities/course'; -import { MentorshipCourse } from 'data'; - -export interface GenericItemProps { - title: string; - detailsUrl: string; - description: string; -} - -interface SchoolListProps { - list: MentorshipCourse[] | Course[] | GenericItemProps[]; - color: 'dark' | 'light'; -} - -export const SchoolList = ({ list, color }: SchoolListProps) => { - const className = - !!list && !!list[0] && 'description' in list[0] - ? 'school-list' - : 'school-list school-list_width'; - - return ( -
      - {list?.map((item) => )} -
    - ); -}; diff --git a/src/widgets/school-menu/ui/school-menu.scss b/src/widgets/school-menu/ui/school-menu.scss deleted file mode 100644 index e2fd0012f..000000000 --- a/src/widgets/school-menu/ui/school-menu.scss +++ /dev/null @@ -1,98 +0,0 @@ -.school-menu { - display: flex; - flex-direction: column; - gap: 16px; - align-items: baseline; - justify-content: flex-start; - - color: $color-gray-100; - - & .heading { - margin: 0; - font-size: 12px; - font-weight: $font-weight-medium; - text-transform: uppercase; - - &.dark { - color: $color-black; - } - - &.light { - color: $color-gray-400; - } - } - - .school-list { - display: flex; - flex-flow: column wrap; - gap: 19px; - column-gap: 40px; - align-items: baseline; - - max-height: 280px; - - list-style-type: none; - - &_width { - width: 512px; - - @media (width <= 795px) { - width: auto; - } - } - - & .school-item { - display: flex; - flex-direction: column; - gap: 5px; - align-items: baseline; - justify-content: flex-start; - - &.with-icon { - display: flex; - flex-direction: row; - gap: 15px; - align-items: center; - justify-content: flex-start; - - .details { - display: flex; - flex-direction: column; - gap: 5px; - align-items: flex-start; - justify-content: flex-start; - } - } - - span { - @extend %transition-all; - - font-weight: $font-weight-medium; - line-height: 20px; - text-align: start; - - &.dark { - color: $color-gray-600; - } - - &.light { - color: $color-gray-200; - } - - &:hover { - color: $color-gray-400; - } - } - - .description { - font-size: 12px; - color: $color-gray-500; - } - } - - @include media-mobile-landscape { - column-gap: 10px; - max-height: 600px; - } - } -} diff --git a/src/widgets/school-menu/ui/school-menu.tsx b/src/widgets/school-menu/ui/school-menu.tsx deleted file mode 100644 index 171e048b0..000000000 --- a/src/widgets/school-menu/ui/school-menu.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { GenericItemProps, SchoolList } from './school-list/school-list'; -import { ANCHORS } from '@/core/const'; -import type { Course } from '@/entities/course'; -import { MentorshipCourse, MentorshipDefaultRouteKeys, mentorshipCourses } from 'data'; - -import './school-menu.scss'; - -const schoolMenuStaticLinks = [ - { - title: 'About RS School', - detailsUrl: `/#${ANCHORS.ABOUT_SCHOOL}`, - description: 'Free online education', - }, - { - title: 'Upcoming courses', - detailsUrl: '/#upcoming-courses', - description: 'Schedule your study', - }, -]; - -const communityMenuStaticLinks = [ - { - title: 'About', - detailsUrl: `/community/#${ANCHORS.ABOUT_COMMUNITY}`, - description: 'Who we are', - }, - { - title: 'Events', - detailsUrl: '/community/#events', - description: 'Meet us at events', - }, - { - title: 'Merch', - detailsUrl: '/community/#merch', - description: 'Sloths for your daily life', - }, - { - title: 'Contribute', - detailsUrl: '/community/#contribute', - description: 'Assist us and improve yourself', - }, -]; - -interface SchoolMenuProps { - heading: 'rs school' | 'all courses' | 'community' | MentorshipDefaultRouteKeys; - courses: Course[]; - hasTitle?: boolean; - color?: 'dark' | 'light'; -} - -function getMenuItems( - heading: SchoolMenuProps['heading'], - courses: Course[], - mentorshipCourses: MentorshipCourse[], -): GenericItemProps[] | Course[] | MentorshipCourse[] { - switch (heading) { - case 'all courses': - return courses; - case 'rs school': - return schoolMenuStaticLinks; - case 'community': - return communityMenuStaticLinks; - case 'mentorship': - return mentorshipCourses; - default: - return []; - } -} - -export const SchoolMenu = ({ - heading, - courses, - hasTitle = true, - color = 'light', -}: SchoolMenuProps) => { - const menuItems = getMenuItems(heading, courses, mentorshipCourses); - - return ( -
    - {hasTitle &&

    {heading}

    } - -
    - ); -}; diff --git a/src/widgets/school-menu/ui/school-menu/school-menu.module.scss b/src/widgets/school-menu/ui/school-menu/school-menu.module.scss new file mode 100644 index 000000000..1bd26433e --- /dev/null +++ b/src/widgets/school-menu/ui/school-menu/school-menu.module.scss @@ -0,0 +1,44 @@ +.school-menu { + display: flex; + flex-direction: column; + gap: 16px; + color: $color-gray-100; + + .heading { + margin: 0; + font-size: 12px; + font-weight: $font-weight-medium; + text-transform: uppercase; + + &.dark { + color: $color-black; + } + + &.light { + color: $color-gray-400; + } + } + + .school-list { + display: flex; + flex-flow: column wrap; + gap: 19px 40px; + + max-height: 280px; + + list-style-type: none; + + &:has(:nth-child(5)) { + width: 512px; + + @include media-tablet { + width: unset; + } + } + + @include media-mobile-landscape { + column-gap: 10px; + max-height: 600px; + } + } +} diff --git a/src/widgets/school-menu/ui/school-menu/school-menu.test.tsx b/src/widgets/school-menu/ui/school-menu/school-menu.test.tsx new file mode 100644 index 000000000..c760222f1 --- /dev/null +++ b/src/widgets/school-menu/ui/school-menu/school-menu.test.tsx @@ -0,0 +1,134 @@ +import { screen } from '@testing-library/react'; +import { SchoolMenu } from '../school-menu/school-menu'; +import { Course } from '@/entities/course'; +import { mockedCourses } from '@/shared/__tests__/constants'; +import { renderWithRouter } from '@/shared/__tests__/utils'; +import { COURSE_TITLES, schoolMenuStaticLinks } from 'data'; + +describe('SchoolMenu', () => { + const aws = mockedCourses.find( + (course) => course.title === COURSE_TITLES.AWS_FUNDAMENTALS, + ) as Course; + const react = mockedCourses.find((course) => course.title === COURSE_TITLES.REACT) as Course; + + it('renders without crashing and displays "rs school" heading', () => { + renderWithRouter( + + {schoolMenuStaticLinks.map((link) => ( + + ))} + , + ); + + const headingElement = screen.getByRole('heading', { name: /rs school/i }); + + expect(headingElement).toBeInTheDocument(); + }); + + it('displays correct links and descriptions with "rs school" props', () => { + const { container } = renderWithRouter( + + {schoolMenuStaticLinks.map((link) => ( + + ))} + , + ); + + expect(screen.getAllByRole('link')).toHaveLength(2); + + expect(container.getElementsByTagName('small')).toHaveLength(2); + }); + + it('renders without crashing and displays "all courses" heading', () => { + renderWithRouter( + + {mockedCourses.map((course) => ( + + ))} + , + ); + + const headingElement = screen.getByRole('heading', { name: /all courses/i }); + + expect(headingElement).toBeInTheDocument(); + }); + + it('renders [mentorshipId] correct when "all courses" heading is passed', () => { + renderWithRouter( + + {mockedCourses.map((course) => ( + + ))} + , + ); + + const images = screen.getAllByTestId('school-item-icon'); + + expect(images).toHaveLength(6); + images.forEach((img) => expect(img).toHaveAttribute('aria-hidden', 'true')); + }); + + it('renders correct link description when date is passed', () => { + renderWithRouter( + + {mockedCourses.map((course) => ( + + ))} + , + ); + + const descriptions = screen.getAllByTestId('school-item-description'); + + expect(descriptions).toHaveLength(6); + expect(descriptions[0]).toHaveTextContent(/Jun 24, 2024/i); + expect(descriptions[3]).toHaveTextContent(/Jul 1, 2024/i); + }); + + it('renders correct link for "AWS Fundamentals" and "React JS [mentorshipId]"', () => { + renderWithRouter( + + {mockedCourses.map((course) => ( + + ))} + , + ); + + const links = screen.getAllByRole('link'); + const linkReact = links.at(3); + const linkAWS = links.at(-1); + + expect(linkAWS).toHaveAttribute('href', aws.detailsUrl); + expect(linkReact).toHaveAttribute('href', react.detailsUrl); + }); +}); diff --git a/src/widgets/school-menu/ui/school-menu/school-menu.tsx b/src/widgets/school-menu/ui/school-menu/school-menu.tsx new file mode 100644 index 000000000..5bfedefcc --- /dev/null +++ b/src/widgets/school-menu/ui/school-menu/school-menu.tsx @@ -0,0 +1,25 @@ +import { HTMLProps, PropsWithChildren } from 'react'; +import classNames from 'classnames/bind'; +import { Color } from '@/widgets/school-menu/types'; +import { SchoolItem } from '@/widgets/school-menu/ui/school-item/school-item'; + +import styles from './school-menu.module.scss'; + +const cx = classNames.bind(styles); + +type SchoolMenuProps = PropsWithChildren & + HTMLProps & { + heading?: string; + color?: Color; + }; + +export const SchoolMenu = ({ heading, color = 'light', children, className }: SchoolMenuProps) => { + return ( +
    + {heading &&

    {heading}

    } +
      {children}
    +
    + ); +}; + +SchoolMenu.Item = SchoolItem;