fix(a11y): make breadcrumb nav more accessible (#45021)

* fix: make breadcrumb nav more accessible

* refactor: cypress test and jest snapshot

* slight adjustment to left breadcrumb focus outline

* feat: translate aria for nav

* chore(tools): update snapshot

Co-authored-by: ahmad abdolsaheb <ahmad.abdolsaheb@gmail.com>
Co-authored-by: Naomi Carrigan <nhcarrigan@gmail.com>
This commit is contained in:
Bruce B
2022-02-15 10:44:18 -08:00
committed by GitHub
parent 1f8df61b1e
commit 7abb89326b
6 changed files with 117 additions and 78 deletions

View File

@ -466,7 +466,9 @@
"first-page": "Go to first page",
"previous-page": "Go to previous page",
"next-page": "Go to next page",
"last-page": "Go to last page"
"last-page": "Go to last page",
"primary-nav": "primary",
"breadcrumb-nav": "breadcrumb"
},
"flash": {
"honest-first": "To claim a certification, you must first accept our academic honesty policy",

View File

@ -5,6 +5,7 @@
// @ts-nocheck
import Loadable from '@loadable/component';
import React, { Ref } from 'react';
import { useTranslation } from 'react-i18next';
import { isLanding } from '../../../utils/path-parsers';
import { Link, SkeletonSprite } from '../../helpers';
import MenuButton from './menu-button';
@ -34,6 +35,7 @@ export const UniversalNav = ({
fetchState
}: UniversalNavProps): JSX.Element => {
const { pending } = fetchState;
const { t } = useTranslation();
const search =
typeof window !== `undefined` && isLanding(window.location.pathname) ? (
@ -44,6 +46,7 @@ export const UniversalNav = ({
return (
<nav
aria-label={t('aria.primary-nav')}
className={'universal-nav' + (displayMenu ? ' expand-nav' : '')}
id='universal-nav'
>

View File

@ -4,35 +4,41 @@ exports[`<ChallengeTitle/> renders correctly 1`] = `
<div
className="challenge-title-wrap"
>
<div
<nav
aria-label="aria.breadcrumb-nav"
className="challenge-title-breadcrumbs"
>
<a
className="breadcrumb-left"
href="/learn/fake-superblock"
state={
Object {
"breadcrumbBlockClick": "fake-block",
}
}
>
<span
className="ellipsis"
/>
</a>
<div
className="breadcrumb-center"
/>
<a
className="breadcrumb-right"
href="/learn/fake-superblock/#fake-block"
state={
Object {
"breadcrumbBlockClick": "fake-block",
}
}
/>
</div>
<ol>
<li
className="breadcrumb-left"
>
<a
href="/learn/fake-superblock"
state={
Object {
"breadcrumbBlockClick": "fake-block",
}
}
>
<span
className="ellipsis"
/>
</a>
</li>
<li
className="breadcrumb-right"
>
<a
href="/learn/fake-superblock/#fake-block"
state={
Object {
"breadcrumbBlockClick": "fake-block",
}
}
/>
</li>
</ol>
</nav>
<div
className="challenge-title"
>

View File

@ -1,5 +1,6 @@
import i18next from 'i18next';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from '../../../components/helpers/index';
import './challenge-title.css';
@ -10,26 +11,33 @@ interface BreadCrumbProps {
}
function BreadCrumb({ block, superBlock }: BreadCrumbProps): JSX.Element {
const { t } = useTranslation();
return (
<div className='challenge-title-breadcrumbs'>
<Link
className='breadcrumb-left'
state={{ breadcrumbBlockClick: block }}
to={`/learn/${superBlock}`}
>
<span className='ellipsis'>
{i18next.t(`intro:${superBlock}.title`)}
</span>
</Link>
<div className='breadcrumb-center' />
<Link
className='breadcrumb-right'
state={{ breadcrumbBlockClick: block }}
to={`/learn/${superBlock}/#${block}`}
>
{i18next.t(`intro:${superBlock}.blocks.${block}.title`)}
</Link>
</div>
<nav
className='challenge-title-breadcrumbs'
aria-label={t('aria.breadcrumb-nav')}
>
<ol>
<li className='breadcrumb-left'>
<Link
state={{ breadcrumbBlockClick: block }}
to={`/learn/${superBlock}`}
>
<span className='ellipsis'>
{i18next.t(`intro:${superBlock}.title`)}
</span>
</Link>
</li>
<li className='breadcrumb-right'>
<Link
state={{ breadcrumbBlockClick: block }}
to={`/learn/${superBlock}/#${block}`}
>
{i18next.t(`intro:${superBlock}.blocks.${block}.title`)}
</Link>
</li>
</ol>
</nav>
);
}

View File

@ -13,39 +13,69 @@
}
.challenge-title-breadcrumbs {
display: flex;
flex-direction: row;
justify-content: space-around;
font-size: 16px;
border: 1px solid var(--quaternary-background);
height: 25px;
text-align: center;
}
.challenge-title-breadcrumbs ol {
display: flex;
justify-content: space-around;
list-style-type: none;
margin-bottom: 0;
padding-left: 0;
width: 100%;
}
.challenge-title-breadcrumbs ol a {
width: 100%;
text-decoration: none;
overflow: hidden;
text-overflow: ellipsis;
display: block;
padding: 0 3px;
}
.challenge-title-breadcrumbs ol a:focus {
outline: 2px solid var(--secondary-color);
background-color: inherit;
}
.challenge-title-breadcrumbs ol a:focus:not(:focus-visible) {
outline: none;
}
.challenge-title-breadcrumbs ol a:hover {
background-color: inherit;
text-decoration: underline;
}
.breadcrumb-left {
text-decoration: none;
min-width: 25px;
min-width: 3rem;
display: inline-block;
align-items: center;
justify-content: center;
overflow: hidden;
text-overflow: ellipsis;
max-height: 25px;
white-space: nowrap;
flex-grow: 1;
flex-shrink: 2;
background-color: var(--quaternary-background);
padding: 0px 3px;
padding: 0 0.57rem 0 0;
position: relative;
}
.breadcrumb-center {
width: 0;
height: 0;
border-top: calc(25px / 2) solid transparent;
border-bottom: calc(23px / 2) solid transparent;
border-left: calc(23px / 2) solid var(--quaternary-background);
flex-grow: 0;
flex-shrink: 0;
.breadcrumb-left:after {
background-color: var(--secondary-background);
content: '';
border-top: calc(1.375rem / 2) solid transparent;
border-bottom: calc(1.2rem / 2) solid transparent;
border-left: calc(1.1rem / 2) solid var(--quaternary-background);
position: absolute;
top: 0;
right: 0;
height: 100%;
}
.breadcrumb-right {
@ -53,29 +83,19 @@
display: inline-block;
align-items: center;
justify-content: center;
max-height: 25px;
min-width: 50px;
flex-grow: 2;
flex-shrink: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0px 3px;
padding: 0;
}
.breadcrumb-rule {
margin: 5px -10px;
}
.breadcrumb-left:hover {
text-decoration: underline;
background-color: var(--quaternary-background);
}
.breadcrumb-right:hover {
text-decoration: underline;
background-color: var(--secondary-background);
}
.title-text {
text-decoration: none;
min-width: 25px;

View File

@ -8,23 +8,23 @@ describe('The breadcumbs should work corectly', () => {
it('It should have a superblock and a course', () => {
cy.visit(challengeUrl);
cy.get('.ellipsis').contains('Responsive Web Design').and('be.visible');
cy.get('.breadcrumb-left')
cy.get('.breadcrumb-left > a')
.should('have.attr', 'href')
.and('include', superBlockUrl);
cy.get('.breadcrumb-right')
cy.get('.breadcrumb-right > a')
.contains('Basic HTML and HTML5')
.and('be.visible');
cy.get('.breadcrumb-right')
cy.get('.breadcrumb-right > a')
.should('have.attr', 'href')
.and('include', courseUrl);
});
it('Should redirect to the right url', () => {
cy.visit(challengeUrl);
cy.get('.breadcrumb-left').click();
cy.get('.breadcrumb-left > a').click();
cy.url().should('include', '/responsive-web-design');
cy.visit(challengeUrl);
cy.get('.breadcrumb-right').click();
cy.get('.breadcrumb-right > a').click();
cy.url().should('include', '/responsive-web-design/#basic-html-and-html5');
});
});