Skip to content

Commit

Permalink
[website] Fix a11y issues
Browse files Browse the repository at this point in the history
  • Loading branch information
oliviertassinari committed Jul 16, 2023
1 parent 4036eb6 commit 9f16247
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 111 deletions.
165 changes: 59 additions & 106 deletions docs/src/components/header/HeaderNavBar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
import * as React from 'react';
import { styled, alpha } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Chip from '@mui/material/Chip';
import ButtonBase from '@mui/material/ButtonBase';
import Popper from '@mui/material/Popper';
import Paper from '@mui/material/Paper';
import { unstable_debounce as debounce } from '@mui/utils';
Expand All @@ -24,9 +26,10 @@ const Navigation = styled('nav')(({ theme }) => [
color: (theme.vars || theme).palette.text.primary,
...theme.typography.body2,
fontWeight: theme.typography.fontWeightBold,
'& > a, & > div': {
'& > a, & > button': {
display: 'inline-block',
color: 'inherit',
font: 'inherit',
textDecoration: 'none',
padding: theme.spacing('8px', 1),
borderRadius: (theme.vars || theme).shape.borderRadius,
Expand Down Expand Up @@ -144,67 +147,31 @@ const ProductSubMenu = React.forwardRef<HTMLAnchorElement, ProductSubMenuProps>(
},
);

function getNextIndex(eventKey: KeyboardEvent['key'], currentIndex: number, length: number) {
if (eventKey === 'ArrowLeft') {
return currentIndex === 0 ? length - 1 : currentIndex - 1;
}
if (eventKey === 'ArrowRight') {
return currentIndex === length - 1 ? 0 : currentIndex + 1;
}
return currentIndex;
}

export default function HeaderNavBar() {
const [subMenuOpen, setSubMenuOpen] = React.useState<null | 'products' | 'docs'>(null);
const [subMenuIndex, setSubMenuIndex] = React.useState<number | null>(null);
const navRef = React.useRef<HTMLUListElement | null>(null);
const productsMenuRef = React.useRef<HTMLDivElement | null>(null);
const docsMenuRef = React.useRef<HTMLDivElement | null>(null);
const productsMenuRef = React.useRef<HTMLButtonElement | null>(null);
const docsMenuRef = React.useRef<HTMLButtonElement | null>(null);
React.useEffect(() => {
if (typeof subMenuIndex === 'number') {
document.getElementById(PRODUCT_IDS[subMenuIndex])?.focus();
}
}, [subMenuIndex]);
function handleLeftRightArrow(
event: React.KeyboardEvent,
target: EventTarget | HTMLElement | null = event.target,
) {
if (navRef.current) {
if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
let i = 0;
while (i < navRef.current.children.length) {
const child = navRef.current.children.item(i);
if (child && (target === child || child.contains(target as Node))) {
const prevSibling = navRef.current.children.item(
getNextIndex(event.key, i, navRef.current.children.length),
);
const htmlElement = prevSibling ? (prevSibling.firstChild as HTMLElement) : null;
if (htmlElement) {
htmlElement.focus();
}
}
i += 1;
}
}
}
}

function handleKeyDown(event: React.KeyboardEvent) {
if (event.key === 'Tab' && !event.shiftKey) {
event.preventDefault();
handleLeftRightArrow(
new KeyboardEvent('keydown', { key: 'ArrowRight' }) as unknown as React.KeyboardEvent,
productsMenuRef.current?.parentElement,
);
}
if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
event.preventDefault();
handleLeftRightArrow(event, productsMenuRef.current?.parentElement);
let menuItem;

if (subMenuOpen === 'products') {
menuItem = productsMenuRef.current!;
} else if (subMenuOpen === 'docs') {
menuItem = docsMenuRef.current!;
} else {
return;
}
if (event.key === 'ArrowDown') {

if (event.key === 'ArrowDown' && subMenuOpen === 'products') {
event.preventDefault();
if (event.target === productsMenuRef.current) {
setSubMenuOpen('products');
}
setSubMenuIndex((prevValue) => {
if (prevValue === null) {
return 0;
Expand All @@ -215,7 +182,7 @@ export default function HeaderNavBar() {
return prevValue + 1;
});
}
if (event.key === 'ArrowUp') {
if (event.key === 'ArrowUp' && subMenuOpen === 'products') {
event.preventDefault();
setSubMenuIndex((prevValue) => {
if (prevValue === null) {
Expand All @@ -227,7 +194,8 @@ export default function HeaderNavBar() {
return prevValue - 1;
});
}
if (event.key === 'Escape') {
if (event.key === 'Escape' || event.key === 'Tab') {
menuItem.focus();
setSubMenuOpen(null);
setSubMenuIndex(null);
}
Expand All @@ -238,13 +206,15 @@ export default function HeaderNavBar() {
[setSubMenuOpen],
);

const setSubMenuOpenUndebounce = React.useMemo(
() => (value: typeof subMenuOpen) => {
setSubMenuOpenDebounced.clear();
setSubMenuOpen(value);
},
[setSubMenuOpen, setSubMenuOpenDebounced],
);
const setSubMenuOpenUndebounce = (value: typeof subMenuOpen) => () => {
setSubMenuOpenDebounced.clear();
setSubMenuOpen(value);
};

const handleClickMenu = (value: typeof subMenuOpen) => () => {
setSubMenuOpenDebounced.clear();
setSubMenuOpen(subMenuOpen ? null : value);
};

React.useEffect(() => {
return () => {
Expand All @@ -254,25 +224,24 @@ export default function HeaderNavBar() {

return (
<Navigation>
<ul ref={navRef} role="menubar" onKeyDown={handleLeftRightArrow}>
<ul ref={navRef} onKeyDown={handleKeyDown}>
<li
role="none"
onMouseEnter={() => setSubMenuOpenUndebounce('products')}
onFocus={() => setSubMenuOpenUndebounce('products')}
onMouseEnter={setSubMenuOpenUndebounce('products')}
onFocus={setSubMenuOpenUndebounce('products')}
onMouseLeave={() => setSubMenuOpenDebounced(null)}
onBlur={() => setSubMenuOpenUndebounce(null)}
onBlur={setSubMenuOpenUndebounce(null)}
>
<div
role="menuitem"
tabIndex={0}
<ButtonBase
ref={productsMenuRef}
aria-haspopup
aria-expanded={subMenuOpen === 'products' ? 'true' : 'false'}
onKeyDown={handleKeyDown}
onClick={handleClickMenu('products')}
aria-controls={subMenuOpen === 'products' ? 'products-popper' : undefined}
>
Products
</div>
</ButtonBase>
<Popper
id="products-popper"
open={subMenuOpen === 'products'}
anchorEl={productsMenuRef.current}
transition
Expand Down Expand Up @@ -317,61 +286,51 @@ export default function HeaderNavBar() {
}),
]}
>
<ul role="menu">
<li role="none">
<ul>
<li>
<ProductSubMenu
id={PRODUCT_IDS[0]}
role="menuitem"
href={ROUTES.productCore}
icon={<IconImage name="product-core" />}
name="MUI Core"
description="Ready-to-use foundational React components, free forever."
onKeyDown={handleKeyDown}
/>
</li>
<li role="none">
<li>
<ProductSubMenu
id={PRODUCT_IDS[1]}
role="menuitem"
href={ROUTES.productAdvanced}
icon={<IconImage name="product-advanced" />}
name="MUI X"
description="Advanced and powerful components for complex use cases."
onKeyDown={handleKeyDown}
/>
</li>
<li role="none">
<li>
<ProductSubMenu
id={PRODUCT_IDS[2]}
role="menuitem"
href={ROUTES.productTemplates}
icon={<IconImage name="product-templates" />}
name="Templates"
description="Fully built, out-of-the-box, templates for your application."
onKeyDown={handleKeyDown}
/>
</li>
<li role="none">
<li>
<ProductSubMenu
id={PRODUCT_IDS[3]}
role="menuitem"
href={ROUTES.productDesignKits}
icon={<IconImage name="product-designkits" />}
name="Design kits"
description="Our components available in your favorite design tool."
onKeyDown={handleKeyDown}
/>
</li>
<li role="none">
<li>
<ProductSubMenu
id={PRODUCT_IDS[4]}
role="menuitem"
href={ROUTES.productToolpad}
icon={<IconImage name="product-toolpad" />}
name="MUI Toolpad"
chip={<Chip label="Beta" size="small" color="primary" variant="outlined" />}
description="Low-code admin builder."
onKeyDown={handleKeyDown}
/>
</li>
</ul>
Expand All @@ -381,22 +340,22 @@ export default function HeaderNavBar() {
</Popper>
</li>
<li
role="none"
onMouseEnter={() => setSubMenuOpenUndebounce('docs')}
onFocus={() => setSubMenuOpenUndebounce('docs')}
onMouseEnter={setSubMenuOpenUndebounce('docs')}
onFocus={setSubMenuOpenUndebounce('docs')}
onMouseLeave={() => setSubMenuOpenDebounced(null)}
onBlur={() => setSubMenuOpenUndebounce(null)}
onBlur={setSubMenuOpenUndebounce(null)}
>
<div
role="menuitem"
tabIndex={0}
<ButtonBase
ref={docsMenuRef}
aria-haspopup
aria-expanded={subMenuOpen === 'docs' ? 'true' : 'false'}
onClick={handleClickMenu('docs')}
aria-controls={subMenuOpen === 'docs' ? 'docs-popper' : undefined}
>
Docs
</div>
</ButtonBase>
<Popper
id="docs-popper"
open={subMenuOpen === 'docs'}
anchorEl={docsMenuRef.current}
transition
Expand Down Expand Up @@ -425,28 +384,22 @@ export default function HeaderNavBar() {
},
})}
>
<ul role="menu">
<ul>
<MuiProductSelector />
</ul>
</Paper>
</Fade>
)}
</Popper>
</li>
<li role="none">
<Link role="menuitem" href={ROUTES.pricing}>
Pricing
</Link>
<li>
<Link href={ROUTES.pricing}>Pricing</Link>
</li>
<li role="none">
<Link role="menuitem" href={ROUTES.about}>
About us
</Link>
<li>
<Link href={ROUTES.about}>About us</Link>
</li>
<li role="none">
<Link role="menuitem" href={ROUTES.blog}>
Blog
</Link>
<li>
<Link href={ROUTES.blog}>Blog</Link>
</li>
</ul>
</Navigation>
Expand Down
12 changes: 10 additions & 2 deletions docs/src/components/home/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,11 @@ export default function Hero() {
right={
<React.Fragment>
{isMdUp && (
<Stack spacing={4} sx={{ '& > .MuiPaper-root': { maxWidth: 'none' } }}>
<Stack
aria-hidden="true"
spacing={4}
sx={{ '& > .MuiPaper-root': { maxWidth: 'none' } }}
>
<TaskCard />
<PlayerCard />
<ThemeToggleButton />
Expand All @@ -163,7 +167,11 @@ export default function Hero() {
</Stack>
)}
{isMdUp && (
<Stack spacing={4} sx={{ ml: 4, '& > .MuiPaper-root': { maxWidth: 'none' } }}>
<Stack
aria-hidden="true"
spacing={4}
sx={{ ml: 4, '& > .MuiPaper-root': { maxWidth: 'none' } }}
>
<ThemeDatePicker />
<ThemeTabs />
<Box sx={{ display: 'flex' }}>
Expand Down
2 changes: 0 additions & 2 deletions docs/src/layouts/HeroContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ export default function HeroContainer({
>
<Box
ref={rightRef}
id="hero-container-right-area"
aria-hidden="true"
sx={[
{
bgcolor: 'grey.50',
Expand Down
2 changes: 1 addition & 1 deletion docs/src/modules/components/AppNavDrawer.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ function ProductDrawerButton(props) {
<React.Fragment>
<Button
id="mui-product-selector"
aria-controls="drawer-open-button"
aria-haspopup="true"
aria-controls={open ? 'drawer-open-button' : undefined}
aria-expanded={open ? 'true' : undefined}
onClick={handleClick}
endIcon={<ArrowDropDownRoundedIcon fontSize="small" sx={{ ml: -0.5 }} />}
Expand Down

0 comments on commit 9f16247

Please sign in to comment.