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:
@ -466,7 +466,9 @@
|
|||||||
"first-page": "Go to first page",
|
"first-page": "Go to first page",
|
||||||
"previous-page": "Go to previous page",
|
"previous-page": "Go to previous page",
|
||||||
"next-page": "Go to next 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": {
|
"flash": {
|
||||||
"honest-first": "To claim a certification, you must first accept our academic honesty policy",
|
"honest-first": "To claim a certification, you must first accept our academic honesty policy",
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import Loadable from '@loadable/component';
|
import Loadable from '@loadable/component';
|
||||||
import React, { Ref } from 'react';
|
import React, { Ref } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { isLanding } from '../../../utils/path-parsers';
|
import { isLanding } from '../../../utils/path-parsers';
|
||||||
import { Link, SkeletonSprite } from '../../helpers';
|
import { Link, SkeletonSprite } from '../../helpers';
|
||||||
import MenuButton from './menu-button';
|
import MenuButton from './menu-button';
|
||||||
@ -34,6 +35,7 @@ export const UniversalNav = ({
|
|||||||
fetchState
|
fetchState
|
||||||
}: UniversalNavProps): JSX.Element => {
|
}: UniversalNavProps): JSX.Element => {
|
||||||
const { pending } = fetchState;
|
const { pending } = fetchState;
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const search =
|
const search =
|
||||||
typeof window !== `undefined` && isLanding(window.location.pathname) ? (
|
typeof window !== `undefined` && isLanding(window.location.pathname) ? (
|
||||||
@ -44,6 +46,7 @@ export const UniversalNav = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<nav
|
<nav
|
||||||
|
aria-label={t('aria.primary-nav')}
|
||||||
className={'universal-nav' + (displayMenu ? ' expand-nav' : '')}
|
className={'universal-nav' + (displayMenu ? ' expand-nav' : '')}
|
||||||
id='universal-nav'
|
id='universal-nav'
|
||||||
>
|
>
|
||||||
|
@ -4,11 +4,15 @@ exports[`<ChallengeTitle/> renders correctly 1`] = `
|
|||||||
<div
|
<div
|
||||||
className="challenge-title-wrap"
|
className="challenge-title-wrap"
|
||||||
>
|
>
|
||||||
<div
|
<nav
|
||||||
|
aria-label="aria.breadcrumb-nav"
|
||||||
className="challenge-title-breadcrumbs"
|
className="challenge-title-breadcrumbs"
|
||||||
>
|
>
|
||||||
<a
|
<ol>
|
||||||
|
<li
|
||||||
className="breadcrumb-left"
|
className="breadcrumb-left"
|
||||||
|
>
|
||||||
|
<a
|
||||||
href="/learn/fake-superblock"
|
href="/learn/fake-superblock"
|
||||||
state={
|
state={
|
||||||
Object {
|
Object {
|
||||||
@ -20,11 +24,11 @@ exports[`<ChallengeTitle/> renders correctly 1`] = `
|
|||||||
className="ellipsis"
|
className="ellipsis"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<div
|
</li>
|
||||||
className="breadcrumb-center"
|
<li
|
||||||
/>
|
|
||||||
<a
|
|
||||||
className="breadcrumb-right"
|
className="breadcrumb-right"
|
||||||
|
>
|
||||||
|
<a
|
||||||
href="/learn/fake-superblock/#fake-block"
|
href="/learn/fake-superblock/#fake-block"
|
||||||
state={
|
state={
|
||||||
Object {
|
Object {
|
||||||
@ -32,7 +36,9 @@ exports[`<ChallengeTitle/> renders correctly 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
<div
|
<div
|
||||||
className="challenge-title"
|
className="challenge-title"
|
||||||
>
|
>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Link } from '../../../components/helpers/index';
|
import { Link } from '../../../components/helpers/index';
|
||||||
|
|
||||||
import './challenge-title.css';
|
import './challenge-title.css';
|
||||||
@ -10,10 +11,15 @@ interface BreadCrumbProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function BreadCrumb({ block, superBlock }: BreadCrumbProps): JSX.Element {
|
function BreadCrumb({ block, superBlock }: BreadCrumbProps): JSX.Element {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div className='challenge-title-breadcrumbs'>
|
<nav
|
||||||
|
className='challenge-title-breadcrumbs'
|
||||||
|
aria-label={t('aria.breadcrumb-nav')}
|
||||||
|
>
|
||||||
|
<ol>
|
||||||
|
<li className='breadcrumb-left'>
|
||||||
<Link
|
<Link
|
||||||
className='breadcrumb-left'
|
|
||||||
state={{ breadcrumbBlockClick: block }}
|
state={{ breadcrumbBlockClick: block }}
|
||||||
to={`/learn/${superBlock}`}
|
to={`/learn/${superBlock}`}
|
||||||
>
|
>
|
||||||
@ -21,15 +27,17 @@ function BreadCrumb({ block, superBlock }: BreadCrumbProps): JSX.Element {
|
|||||||
{i18next.t(`intro:${superBlock}.title`)}
|
{i18next.t(`intro:${superBlock}.title`)}
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
<div className='breadcrumb-center' />
|
</li>
|
||||||
|
<li className='breadcrumb-right'>
|
||||||
<Link
|
<Link
|
||||||
className='breadcrumb-right'
|
|
||||||
state={{ breadcrumbBlockClick: block }}
|
state={{ breadcrumbBlockClick: block }}
|
||||||
to={`/learn/${superBlock}/#${block}`}
|
to={`/learn/${superBlock}/#${block}`}
|
||||||
>
|
>
|
||||||
{i18next.t(`intro:${superBlock}.blocks.${block}.title`)}
|
{i18next.t(`intro:${superBlock}.blocks.${block}.title`)}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,39 +13,69 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.challenge-title-breadcrumbs {
|
.challenge-title-breadcrumbs {
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-around;
|
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
border: 1px solid var(--quaternary-background);
|
border: 1px solid var(--quaternary-background);
|
||||||
height: 25px;
|
|
||||||
text-align: center;
|
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 {
|
.breadcrumb-left {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
min-width: 25px;
|
min-width: 3rem;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
max-height: 25px;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
flex-shrink: 2;
|
flex-shrink: 2;
|
||||||
background-color: var(--quaternary-background);
|
background-color: var(--quaternary-background);
|
||||||
padding: 0px 3px;
|
padding: 0 0.57rem 0 0;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-center {
|
.breadcrumb-left:after {
|
||||||
width: 0;
|
background-color: var(--secondary-background);
|
||||||
height: 0;
|
content: '';
|
||||||
border-top: calc(25px / 2) solid transparent;
|
border-top: calc(1.375rem / 2) solid transparent;
|
||||||
border-bottom: calc(23px / 2) solid transparent;
|
border-bottom: calc(1.2rem / 2) solid transparent;
|
||||||
border-left: calc(23px / 2) solid var(--quaternary-background);
|
border-left: calc(1.1rem / 2) solid var(--quaternary-background);
|
||||||
flex-grow: 0;
|
position: absolute;
|
||||||
flex-shrink: 0;
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-right {
|
.breadcrumb-right {
|
||||||
@ -53,29 +83,19 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
max-height: 25px;
|
|
||||||
min-width: 50px;
|
min-width: 50px;
|
||||||
flex-grow: 2;
|
flex-grow: 2;
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
padding: 0px 3px;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-rule {
|
.breadcrumb-rule {
|
||||||
margin: 5px -10px;
|
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 {
|
.title-text {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
min-width: 25px;
|
min-width: 25px;
|
||||||
|
@ -8,23 +8,23 @@ describe('The breadcumbs should work corectly', () => {
|
|||||||
it('It should have a superblock and a course', () => {
|
it('It should have a superblock and a course', () => {
|
||||||
cy.visit(challengeUrl);
|
cy.visit(challengeUrl);
|
||||||
cy.get('.ellipsis').contains('Responsive Web Design').and('be.visible');
|
cy.get('.ellipsis').contains('Responsive Web Design').and('be.visible');
|
||||||
cy.get('.breadcrumb-left')
|
cy.get('.breadcrumb-left > a')
|
||||||
.should('have.attr', 'href')
|
.should('have.attr', 'href')
|
||||||
.and('include', superBlockUrl);
|
.and('include', superBlockUrl);
|
||||||
cy.get('.breadcrumb-right')
|
cy.get('.breadcrumb-right > a')
|
||||||
.contains('Basic HTML and HTML5')
|
.contains('Basic HTML and HTML5')
|
||||||
.and('be.visible');
|
.and('be.visible');
|
||||||
cy.get('.breadcrumb-right')
|
cy.get('.breadcrumb-right > a')
|
||||||
.should('have.attr', 'href')
|
.should('have.attr', 'href')
|
||||||
.and('include', courseUrl);
|
.and('include', courseUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should redirect to the right url', () => {
|
it('Should redirect to the right url', () => {
|
||||||
cy.visit(challengeUrl);
|
cy.visit(challengeUrl);
|
||||||
cy.get('.breadcrumb-left').click();
|
cy.get('.breadcrumb-left > a').click();
|
||||||
cy.url().should('include', '/responsive-web-design');
|
cy.url().should('include', '/responsive-web-design');
|
||||||
cy.visit(challengeUrl);
|
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');
|
cy.url().should('include', '/responsive-web-design/#basic-html-and-html5');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user