From d5488ea37d49f974ef9adc62f2d790d158eb0f34 Mon Sep 17 00:00:00 2001 From: Aaron Moore Date: Tue, 24 Mar 2026 11:16:04 +0000 Subject: [PATCH 01/14] Copilot test - Migrate legacy nav --- .../DropdownNavigation/index.styles.ts | 111 + .../Navigation/DropdownNavigation/index.tsx | 198 ++ .../Navigation/DropdownNavigation/types.d.ts | 22 + .../Navigation/NavigationList/index.styles.ts | 80 + .../Navigation/NavigationList/index.tsx | 93 + .../NavigationWrapper/index.styles.ts | 41 + .../Navigation/NavigationWrapper/index.tsx | 35 + .../ScrollableNavigation/index.styles.ts | 59 + .../Navigation/ScrollableNavigation/index.tsx | 30 + .../__snapshots__/index.amp.test.tsx.snap | 226 +- .../index.canonical.test.tsx.snap | 232 +- .../__snapshots__/index.test.tsx.snap | 2252 +++++++---------- src/app/components/Navigation/index.amp.tsx | 25 +- .../Navigation/index.canonical.test.tsx | 2 +- .../components/Navigation/index.canonical.tsx | 14 +- src/app/components/Navigation/index.styles.ts | 33 +- src/app/components/Navigation/index.tsx | 25 +- 17 files changed, 1779 insertions(+), 1699 deletions(-) create mode 100644 src/app/components/Navigation/DropdownNavigation/index.styles.ts create mode 100644 src/app/components/Navigation/DropdownNavigation/index.tsx create mode 100644 src/app/components/Navigation/DropdownNavigation/types.d.ts create mode 100644 src/app/components/Navigation/NavigationList/index.styles.ts create mode 100644 src/app/components/Navigation/NavigationList/index.tsx create mode 100644 src/app/components/Navigation/NavigationWrapper/index.styles.ts create mode 100644 src/app/components/Navigation/NavigationWrapper/index.tsx create mode 100644 src/app/components/Navigation/ScrollableNavigation/index.styles.ts create mode 100644 src/app/components/Navigation/ScrollableNavigation/index.tsx diff --git a/src/app/components/Navigation/DropdownNavigation/index.styles.ts b/src/app/components/Navigation/DropdownNavigation/index.styles.ts new file mode 100644 index 00000000000..62f32b440de --- /dev/null +++ b/src/app/components/Navigation/DropdownNavigation/index.styles.ts @@ -0,0 +1,111 @@ +import { css, Theme } from '@emotion/react'; +import pixelsToRem from '#app/utilities/pixelsToRem'; +import { GROUP_B_MIN_WIDTH } from '#app/components/ThemeProvider/fontMediaQueries'; +import { MAX_NAV_ITEM_HEIGHT } from '../index.styles'; + +export default { + dropdown: ({ palette, mq }: Theme) => + css({ + backgroundColor: palette.WHITE, + clear: 'both', + overflow: 'hidden', + height: 0, + transition: 'all 0.2s ease-out', + transitionTimingFunction: 'cubic-bezier(0, 0, 0.58, 1)', + visibility: 'hidden', + [mq.GROUP_3_MIN_WIDTH]: { + display: 'none', + visibility: 'hidden', + }, + '@media (prefers-reduced-motion: reduce)': { + transition: 'none', + }, + }), + + dropdownOpen: css({ + visibility: 'visible', + }), + + ampDropdown: ({ palette, mq }: Theme) => + css({ + backgroundColor: palette.WHITE, + clear: 'both', + [mq.GROUP_3_MIN_WIDTH]: { + display: 'none', + visibility: 'hidden', + }, + }), + + dropdownList: css({ + listStyleType: 'none', + margin: 0, + padding: 0, + }), + + dropdownListItem: ({ palette }: Theme) => + css({ + padding: 0, + borderBottom: `${pixelsToRem(1)}rem solid ${palette.GREY_3}`, + '&:last-child': { + border: 0, + }, + }), + + // Visual defaults for legacy nav services. + // The new-nav container's `dropdown` descendant styles override display, padding and hover. + dropdownLink: ({ palette, fontSizes, fontVariants }: Theme) => + css({ + ...fontSizes.pica, + ...fontVariants.sansRegular, + color: palette.GREY_10, + textDecoration: 'none', + padding: '0.75rem 0', + display: 'inline-block', + '&:hover, &:focus': { + textDecoration: 'underline', + textDecorationColor: palette.POSTBOX, + }, + }), + + // Active link indicator: inline-start border + padding for the current page item + currentLink: ({ palette, spacings }: Theme) => + css({ + borderInlineStart: `${pixelsToRem(4)}rem solid ${palette.POSTBOX}`, + paddingInlineStart: `${spacings.FULL}rem`, + }), + + menuButton: ({ palette, mq }: Theme) => + css({ + position: 'relative', + padding: 0, + margin: 0, + border: 0, + float: 'inline-start', + backgroundColor: palette.POSTBOX, + color: palette.WHITE, + width: `${pixelsToRem(MAX_NAV_ITEM_HEIGHT)}rem`, + height: `${pixelsToRem(MAX_NAV_ITEM_HEIGHT)}rem`, + [mq.GROUP_3_MIN_WIDTH]: { + display: 'none', + visibility: 'hidden', + }, + [GROUP_B_MIN_WIDTH]: { + width: `${pixelsToRem(MAX_NAV_ITEM_HEIGHT)}rem`, + height: `${pixelsToRem(MAX_NAV_ITEM_HEIGHT)}rem`, + }, + svg: { + verticalAlign: 'middle', + fill: palette.WHITE, + }, + '&:hover, &:focus': { + cursor: 'pointer', + boxShadow: `inset 0 0 0 ${pixelsToRem(4)}rem ${palette.WHITE}`, + '&::after': { + content: "''", + position: 'absolute', + inset: 0, + border: `${pixelsToRem(4)}rem solid ${palette.BLACK}`, + }, + }, + }), +}; diff --git a/src/app/components/Navigation/DropdownNavigation/index.tsx b/src/app/components/Navigation/DropdownNavigation/index.tsx new file mode 100644 index 00000000000..23d20ca0a5c --- /dev/null +++ b/src/app/components/Navigation/DropdownNavigation/index.tsx @@ -0,0 +1,198 @@ +import { PropsWithChildren, useRef, cloneElement } from 'react'; +import { Helmet } from 'react-helmet'; +import { navigationIcons } from '#psammead/psammead-assets/src/svgs'; +import VisuallyHiddenText from '#app/components/VisuallyHiddenText'; +import { Direction } from '#app/models/types/global'; +import styles from './index.styles'; + +type CanonicalDropdownProps = { + isOpen: boolean; + className?: string; +}; + +export const CanonicalDropdown = ({ + isOpen, + children, + className, +}: PropsWithChildren) => { + const heightRef = useRef(null); + return ( +
+ {children} +
+ ); +}; + +type AmpDropdownProps = { + id?: string; + className?: string; + hidden?: boolean; +}; + +export const AmpDropdown = ({ + children, + id, + className, + hidden, +}: PropsWithChildren) => ( + +); + +export const DropdownList = ({ + children, + ...props +}: PropsWithChildren>) => ( +
    + {children} +
+); + +type DropdownListItemProps = { + url: string; + active?: boolean; + currentPageText?: string; + clickTracker?: Record | null; + viewTracker?: Record | null; +}; + +export const DropdownListItem = ({ + children, + clickTracker = null, + currentPageText, + active = false, + url, + viewTracker = null, +}: PropsWithChildren) => { + const ariaId = `dropdownNavigation-${(children as string) + .replace(/\s+/g, '-') + .toLowerCase()}`; + return ( + // aria-labelledby is a temporary fix for the a11y nested span bug in TalkBack: https://github.com/bbc/simorgh/issues/9652 +
  • + + {active && currentPageText ? ( + // ID is a temporary fix for the a11y nested span bug in TalkBack: https://github.com/bbc/simorgh/issues/9652 + // eslint-disable-next-line jsx-a11y/aria-role + + {`${currentPageText}, `} + {children} + + ) : ( + // ID is a temporary fix for the a11y nested span bug in TalkBack: https://github.com/bbc/simorgh/issues/9652 + {children} + )} + +
  • + ); +}; + +type CanonicalMenuButtonProps = { + announcedText: string; + isOpen: boolean; + onClick: () => void; + dir?: Direction; +}; + +export const CanonicalMenuButton = ({ + announcedText, + isOpen, + onClick, + dir = 'ltr', +}: CanonicalMenuButtonProps) => ( + +); + +const AmpHead = () => ( + +