Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
120 changes: 104 additions & 16 deletions src/components/Menu/MenuItem.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -19,6 +26,7 @@ type MenuItemProps = {
level: number;
currentPlatform?: Platform;
hideChildren?: boolean;
tabIndex?: number;
};

function getPathname(route, currentPlatform: Platform | undefined) {
Expand All @@ -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 (
Expand All @@ -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);
Expand All @@ -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) {
Expand Down Expand Up @@ -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}
>
<Flex
className={`menu__list-item__link__inner ${listItemLinkInnerStyle}`}
Expand Down Expand Up @@ -188,23 +263,35 @@ export function MenuItem({
onFocus={handleFocus}
key={pageNode.route}
className={`menu__list-item ${listItemStyle}`}
ref={ref}
>
<Link
className={`menu__list-item__link ${listItemLinkStyle} ${currentStyle}`}
aria-current={current ? 'page' : null}
href={href}
onClick={onLinkClick}
passHref
>
<Flex
className={`menu__list-item__link__inner ${listItemLinkInnerStyle}`}
<Flex className={`menu__list-item__inner`}>
<Link
className={`menu__list-item__link ${listItemLinkStyle} ${current ? currentStyle : null}`}
aria-current={current ? 'page' : null}
href={href}
onClick={onLinkClick}
tabIndex={level > Levels.Subcategory ? -1 : 0}
passHref
>
{pageNode.title}
{children && hasVisibleChildren && level !== Levels.Category && (
<Flex
className={`menu__list-item__link__inner ${listItemLinkInnerStyle}`}
>
{pageNode.title}
</Flex>
</Link>
{children && hasVisibleChildren && level !== Levels.Category && (
<Button
className={`${listItemLinkStyle} expand-button`}
onClick={onCheveronClick}
aria-expanded="true"
aria-labelledby="li"
tabIndex={level > Levels.Subcategory ? -1 : 0}
>
<IconChevron className={open ? '' : 'icon-rotate-90-reverse'} />
)}
</Flex>
</Link>
</Button>
)}
</Flex>
{children && (
<ul
className={`menu__list ${
Expand All @@ -218,6 +305,7 @@ export function MenuItem({
parentSetOpen={setOpen}
level={level + 1}
currentPlatform={currentPlatform}
tabIndex={level > Levels.Subcategory ? -1 : 0}
/>
))}
</ul>
Expand Down
24 changes: 20 additions & 4 deletions src/styles/menu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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)
Expand Down Expand Up @@ -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);
}
}
}
}