diff --git a/src/components/Menu/MenuItem.tsx b/src/components/Menu/MenuItem.tsx index e49cbe2510f..eecc724a5d6 100644 --- a/src/components/Menu/MenuItem.tsx +++ b/src/components/Menu/MenuItem.tsx @@ -1,6 +1,13 @@ import { usePathWithoutHash } from '@/utils/usePathWithoutHash'; -import { ReactElement, useContext, useEffect, useState, useMemo } from 'react'; -import { Link as AmplifyUILink, Flex } from '@aws-amplify/ui-react'; +import { + ReactElement, + useContext, + useEffect, + useState, + useMemo, + useRef +} from 'react'; +import { Link as AmplifyUILink, Button, Flex } from '@aws-amplify/ui-react'; import { IconExternalLink, IconChevron } from '@/components/Icons'; import Link from 'next/link'; import { JS_PLATFORMS, Platform, JSPlatform } from '@/data/platforms'; @@ -19,6 +26,7 @@ type MenuItemProps = { level: number; currentPlatform?: Platform; hideChildren?: boolean; + tabIndex?: number; }; function getPathname(route, currentPlatform: Platform | undefined) { @@ -43,10 +51,45 @@ export function MenuItem({ const { menuOpen, toggleMenuOpen } = useContext(LayoutContext); const asPathWithoutHash = usePathWithoutHash(); const [open, setOpen] = useState(false); + const ref = useRef(null); const children = useMemo( () => (hideChildren ? [] : pageNode.children), [hideChildren, pageNode.children] ); + + const makeSubsKeyboardAccessible = () => { + const pages = ref.current?.children[1]?.children; + for (const page of pages) { + const links = page.children[0].children; + + for (const link of links) { + link.setAttribute('tabIndex', 0); + } + + if (!page.children[1]?.classList.contains('menu__list--hide')) { + const subs = page.children[1]?.children; + if (subs) { + for (const sub of subs) { + sub.children[0].children[0].setAttribute('tabIndex', 0); + } + } + } + } + }; + + const makeSubsKeyboardInaccessible = () => { + const subItems = ref.current?.children[1]; + const links = subItems?.getElementsByTagName('a'); + const buttons = subItems?.getElementsByTagName('button'); + + for (const link of links) { + link.setAttribute('tabIndex', -1); + } + for (const button of buttons) { + button.setAttribute('tabIndex', -1); + } + }; + const onLinkClick = () => { // Category shouldn't be collapsible if ( @@ -62,6 +105,17 @@ export function MenuItem({ } }; + const onCheveronClick = () => { + setOpen((prevOpen) => !prevOpen); + + if (menuOpen) { + // Close the menu after clicking a link (applies to the mobile menu) + toggleMenuOpen(false); + } else { + toggleMenuOpen(true); + } + }; + const handleFocus = () => { if (parentSetOpen) { parentSetOpen(true); @@ -76,6 +130,26 @@ export function MenuItem({ let hideAPIResources = false; + useEffect(() => { + const current = ref.current; + if ( + current?.children.length > 1 && + open && + !current?.classList.contains('menu__list-item--category') + ) { + makeSubsKeyboardAccessible(); + } else if ( + current?.children.length > 1 && + !open && + !current?.classList.contains('menu__list-item--category') + ) { + const pages = current?.children[1]?.children; + if (pages) { + makeSubsKeyboardInaccessible(); + } + } + }, [open]); + useEffect(() => { if (current) { if (children && children.length > 0) { @@ -158,6 +232,7 @@ export function MenuItem({ isExternal={true} aria-label={pageNode.title + ' (opens in new tab)'} onClick={onLinkClick} + tabIndex={level > Levels.Subcategory ? -1 : 0} > - - + Levels.Subcategory ? -1 : 0} + passHref > - {pageNode.title} - {children && hasVisibleChildren && level !== Levels.Category && ( + + {pageNode.title} + + + {children && hasVisibleChildren && level !== Levels.Category && ( + + )} + {children && ( diff --git a/src/styles/menu.scss b/src/styles/menu.scss index a1cea161304..86f89ebbfd1 100644 --- a/src/styles/menu.scss +++ b/src/styles/menu.scss @@ -18,6 +18,14 @@ margin: 0; padding: 0; + &__inner { + gap: 0; + + &:hover { + background-color: var(--amplify-colors-overlay-5); + } + } + &--category { &:not(:first-child) { margin-top: 25px; @@ -27,6 +35,7 @@ &__link { color: var(--amplify-colors-font-primary); text-decoration: none; + width: 100%; &:focus { outline: 2px auto var(--amplify-colors-border-focus); @@ -48,10 +57,6 @@ justify-content: space-between; align-items: center; - &:hover { - background-color: var(--amplify-colors-overlay-5); - } - &--subpage { padding-left: calc( var(--amplify-space-large) + var(--amplify-space-small) @@ -88,5 +93,16 @@ } } } + + .expand-button { + border: none; + margin-right: 8px; + background-color: transparent; + border-radius: 0; + + &:hover { + background-color: var(--amplify-colors-overlay-5); + } + } } }