Skip to content

Commit

Permalink
Announce current project nav in screen readers (#5682)
Browse files Browse the repository at this point in the history
* Announce current project nav in screen readers
- Add `aria-current=page` to the project navigation and the project About section navigation menus.
- Use the `[aria-current=page]` attribute selector to style the current page link.
- Compare the current project section, not the whole URL path, to find the current page in the project navigation menu.

* refactor active link in DropdownNav components too

* Accessible project navigation: fix server-side page paths (#5755)

* Fix About Nav highlighting

* Account for panoptesEnv in server-side page paths

* Clarify server-side vs. client-side app paths

* check for router.isReady

---------

Co-authored-by: Delilah C <[email protected]>
  • Loading branch information
eatyourgreens and goplayoutside3 authored Dec 20, 2023
1 parent 995a00e commit b84229d
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { bool, object, oneOfType, string } from 'prop-types'
import { useState } from 'react'
import styled, { css } from 'styled-components'
import { useTranslation } from 'next-i18next'
import { useRouter } from 'next/router'

import { useProjectNavigation } from '../../hooks'

Expand Down Expand Up @@ -47,7 +48,7 @@ function DropdownNav({
className,
margin = defaultMargin,
organizationSlug = '',
organizationTitle = '',
organizationTitle = ''
}) {
const { t } = useTranslation('components')
const navLinks = useProjectNavigation(adminMode)
Expand All @@ -61,6 +62,32 @@ function DropdownNav({
setIsOpen(true)
}

function NavItem({ navLink }) {
const router = useRouter()
let isCurrentPage
if (router?.asPath) {
const routerPath = router.asPath.split('/')
const hrefPath = navLink.href.split('/')
/*
The path arrays will be ['', owner, project, section, ...rest].
The section is always the fourth item.
*/
isCurrentPage = routerPath[3] === hrefPath[3]
}

return (
<Box as='li' key={navLink.href}>
<NavLink
aria-current={isCurrentPage ? 'page' : undefined}
color='white'
link={navLink}
StyledAnchor={StyledAnchor}
weight='bold'
/>
</Box>
)
}

const dropContent = (
<Box
aria-label={t('ProjectHeader.ProjectNav.ariaLabel')}
Expand All @@ -69,48 +96,24 @@ function DropdownNav({
elevation='medium'
margin={{ top: 'medium ' }}
>
<Box
as='ul'
pad='0px'
>
<>
{navLinks.map(navLink => (
<Box
as='li'
key={navLink.href}
>
<NavLink
color='white'
link={navLink}
StyledAnchor={StyledAnchor}
weight='bold'
/>
</Box>
))}
</>
<Box as='ul' pad='0px'>
{navLinks.map(navLink => (
<NavItem key={navLink.href} navLink={navLink} />
))}
{organizationTitle ? (
<Box
as='li'
key={organizationSlug}
>
<Box as='li' key={organizationSlug}>
<StyledAnchor
href={`/organizations/${organizationSlug}`}
label={(
label={
<Box pad='none'>
<SpacedText
color='white'
size='xsmall'
>
<SpacedText color='white' size='xsmall'>
{t('ProjectHeader.organization')}
</SpacedText>
<SpacedText
color='white'
weight='bold'
>
<SpacedText color='white' weight='bold'>
{organizationTitle}
</SpacedText>
</Box>
)}
}
/>
</Box>
) : null}
Expand Down Expand Up @@ -160,7 +163,4 @@ DropdownNav.propTypes = {
}

export default DropdownNav
export {
DropdownNav,
StyledDropButton
}
// export { DropdownNav, StyledDropButton }
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { SpacedText } from '@zooniverse/react-components'
import { Anchor, Box } from 'grommet'
import { useRouter } from 'next/router'
import { bool } from 'prop-types'
import styled, { css } from 'styled-components'
import { useTranslation } from 'next-i18next'
Expand Down Expand Up @@ -28,31 +29,50 @@ const StyledAnchor = styled(Anchor)`
&[href]:hover {
border-bottom-color: ${props.color};
}
&:not([href]) {
&[aria-current='page'] {
cursor: default;
border-bottom-color: ${props.color};
}
`}
`

function Nav({
adminMode = false,
}) {
function NavItem({ navLink }) {
const router = useRouter()

let isCurrentPage
if (router?.isReady) {
const routerPath = router.asPath.split('/')
const hrefPath = navLink.href.split('/')
/*
Client-side routerPath will be ['', owner, project, section, ...rest].
The link hrefPath will be ['', owner, project, section, ...rest].
The section is always the fourth item in the array.
*/
isCurrentPage = routerPath[3] === hrefPath[3]
}

return (
<Box as='li' key={navLink.href} flex='grow' pad={{ left: 'small' }}>
<NavLink
aria-current={ isCurrentPage ? 'page' : undefined }
color='white'
link={navLink}
StyledAnchor={StyledAnchor}
StyledSpacedText={StyledSpacedText}
weight='bold'
/>
</Box>
)
}

function Nav({ adminMode = false }) {
const navLinks = useProjectNavigation(adminMode)
const { t } = useTranslation('components')
return (
<Box aria-label={t('ProjectHeader.ProjectNav.ariaLabel')} as='nav'>
<Box as='ul' direction='row' style={{ paddingInlineStart: 0 }}>
{navLinks.map(navLink => (
<Box as='li' key={navLink.href} flex='grow' pad={{ left: 'small' }}>
<NavLink
color='white'
link={navLink}
StyledAnchor={StyledAnchor}
StyledSpacedText={StyledSpacedText}
weight='bold'
/>
</Box>
<NavItem key={navLink.href} navLink={navLink} />
))}
</Box>
</Box>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from 'react'
import { arrayOf, object, string } from 'prop-types'
import { arrayOf, string } from 'prop-types'
import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next'

Expand All @@ -9,8 +9,7 @@ import { FormDown } from 'grommet-icons'
import { SpacedText } from '@zooniverse/react-components'
import AboutNavLink from '../AboutNavLink'

// this is a separate componenet specifically for testing with enzyme
export const AboutDropContent = ({ aboutNavLinks }) => {
const AboutDropContent = ({ aboutNavLinks }) => {
const router = useRouter()
const { owner, project } = router.query
const baseUrl = `/${owner}/${project}/about`
Expand All @@ -24,22 +23,19 @@ export const AboutDropContent = ({ aboutNavLinks }) => {
href: `${baseUrl}/research`,
text: t('About.PageHeading.title.research')
}}
router={router}
/>
<AboutNavLink
link={{
href: `${baseUrl}/team`,
text: t('About.PageHeading.title.team')
}}
router={router}
/>
{aboutNavLinks.includes('results') && (
<AboutNavLink
link={{
href: `${baseUrl}/results`,
text: t('About.PageHeading.title.results')
}}
router={router}
/>
)}
{aboutNavLinks.includes('education') && (
Expand All @@ -48,7 +44,6 @@ export const AboutDropContent = ({ aboutNavLinks }) => {
href: `${baseUrl}/education`,
text: t('About.PageHeading.title.education')
}}
router={router}
/>
)}
{aboutNavLinks.includes('faq') && (
Expand All @@ -57,14 +52,13 @@ export const AboutDropContent = ({ aboutNavLinks }) => {
href: `${baseUrl}/faq`,
text: t('About.PageHeading.title.faq')
}}
router={router}
/>
)}
</Nav>
)
}

const AboutDropdownNav = ({ aboutNavLinks, router }) => {
const AboutDropdownNav = ({ aboutNavLinks }) => {
const [isOpen, setIsOpen] = useState(false)

const handleOpen = () => setIsOpen(!isOpen)
Expand All @@ -77,7 +71,7 @@ const AboutDropdownNav = ({ aboutNavLinks, router }) => {
isOpen={isOpen}
alignSelf='center'
dropContent={
<AboutDropContent aboutNavLinks={aboutNavLinks} router={router} />
<AboutDropContent aboutNavLinks={aboutNavLinks} />
}
onClose={handleOpen}
onOpen={handleOpen}
Expand All @@ -94,7 +88,6 @@ const AboutDropdownNav = ({ aboutNavLinks, router }) => {

AboutDropdownNav.propTypes = {
aboutNavLinks: arrayOf(string),
router: object
}

export default AboutDropdownNav
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
import { string, object, shape } from 'prop-types'
import { Anchor, Box } from 'grommet'
import { useRouter } from 'next/router'
import { string, shape } from 'prop-types'
import styled from 'styled-components'
import addQueryParams from '@helpers/addQueryParams'

/** Components */
import addQueryParams from '@helpers/addQueryParams'
import NavLink from '@shared/components/NavLink'
import { Anchor, Box } from 'grommet'

const StyledAnchor = styled(Anchor)`
&:hover {
text-decoration: none;
}
`

const AboutNavLink = ({ router, link }) => {
const AboutNavLink = ({ link }) => {
const { href } = link
const isCurrentPage = router?.asPath === addQueryParams(href)
const router = useRouter()
const isCurrentPage = router?.isReady && router?.asPath === addQueryParams(href)

return (
<Box
background={isCurrentPage ? 'accent-1' : { light: 'neutral-6', dark: '' }}
pad={{ horizontal: '20px', vertical: '5px' }}
>
<NavLink
aria-current={ isCurrentPage ? 'page' : undefined }
link={link}
color={{ dark: 'neutral-6', light: 'dark-3' }}
weight={isCurrentPage ? 'bold' : 'normal'}
Expand All @@ -35,8 +37,7 @@ AboutNavLink.propTypes = {
link: shape({
href: string,
text: string
}),
router: object
})
}

export default AboutNavLink
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import AboutNavLink from '../AboutNavLink'
import { Nav } from 'grommet'
import { useRouter } from 'next/router'
import { arrayOf, object, string } from 'prop-types'
import { arrayOf, string } from 'prop-types'
import { useTranslation } from 'next-i18next'

const AboutSidebar = ({ aboutNavLinks }) => {
Expand All @@ -18,22 +18,19 @@ const AboutSidebar = ({ aboutNavLinks }) => {
href: `${baseUrl}/research`,
text: t('About.PageHeading.title.research')
}}
router={router}
/>
<AboutNavLink
link={{
href: `${baseUrl}/team`,
text: t('About.PageHeading.title.team')
}}
router={router}
/>
{aboutNavLinks.includes('results') && (
<AboutNavLink
link={{
href: `${baseUrl}/results`,
text: t('About.PageHeading.title.results')
}}
router={router}
/>
)}
{aboutNavLinks.includes('education') && (
Expand All @@ -42,7 +39,6 @@ const AboutSidebar = ({ aboutNavLinks }) => {
href: `${baseUrl}/education`,
text: t('About.PageHeading.title.education')
}}
router={router}
/>
)}
{aboutNavLinks.includes('faq') && (
Expand All @@ -51,7 +47,6 @@ const AboutSidebar = ({ aboutNavLinks }) => {
href: `${baseUrl}/faq`,
text: t('About.PageHeading.title.faq')
}}
router={router}
/>
)}
</Nav>
Expand All @@ -60,7 +55,6 @@ const AboutSidebar = ({ aboutNavLinks }) => {

AboutSidebar.propTypes = {
aboutNavLinks: arrayOf(string),
router: object
}

export default AboutSidebar
Loading

0 comments on commit b84229d

Please sign in to comment.