Skip to content
This repository was archived by the owner on Oct 1, 2024. It is now read-only.

Commit e48e43e

Browse files
authored
Merge pull request #157 from geotrev/app/accessibility
[155] [156] Fix skip to main, jump to top, and Documentation global nav buttons.
2 parents bfa9019 + da1c0db commit e48e43e

File tree

11 files changed

+190
-74
lines changed

11 files changed

+190
-74
lines changed

app/components/Article/Article.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,5 @@ export default function Article(props) {
3636
}
3737

3838
Article.propTypes = {
39-
children: PropTypes.any,
39+
children: PropTypes.string.isRequired,
4040
}

app/components/Footer/Footer.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ import PropTypes from "prop-types"
66
import "./styles.scss"
77

88
export default function Footer(props) {
9+
const handleClick = event => {
10+
event.preventDefault()
11+
props.handleRefocusClick(props.headerRef)
12+
}
13+
914
return (
1015
<div className="footer-wrapper small-section fluid grid">
1116
<div className="row">
@@ -50,8 +55,9 @@ export default function Footer(props) {
5055
<li role="none">
5156
<button
5257
className="is-visually-hidden-focusable"
53-
onClick={props.handleHeaderFocusClick}
58+
onClick={handleClick}
5459
role="listitem"
60+
type="button"
5561
>
5662
Return to top of page
5763
</button>
@@ -64,5 +70,8 @@ export default function Footer(props) {
6470
}
6571

6672
Footer.propTypes = {
67-
handleHeaderFocusClick: PropTypes.func.isRequired,
73+
handleRefocusClick: PropTypes.func.isRequired,
74+
headerRef: PropTypes.shape({
75+
current: PropTypes.instanceOf(Element),
76+
}),
6877
}

app/components/Footer/__tests__/Footer.spec.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ jest.mock("react-feather/dist/icons/github", () => global.simpleMock("Github"))
66

77
describe("<Footer />", () => {
88
const baseProps = {
9-
handleHeaderFocusClick: jest.fn(),
9+
handleRefocusClick: jest.fn(),
10+
headerRef: {
11+
current: document.createElement("header"),
12+
},
1013
}
1114

1215
const mountComponent = () => {
@@ -20,12 +23,12 @@ describe("<Footer />", () => {
2023
expect(wrapper).toMatchSnapshot()
2124
})
2225

23-
it("calls handleHeaderFocusClick prop when hidden button is clicked", () => {
26+
it("calls handleRefocusClick prop when hidden button is clicked", () => {
2427
// Given
2528
const wrapper = mountComponent()
2629
// When
2730
wrapper.find(".is-visually-hidden-focusable").simulate("click")
2831
// Then
29-
expect(baseProps.handleHeaderFocusClick).toHaveBeenCalled()
32+
expect(baseProps.handleRefocusClick).toHaveBeenCalledWith(baseProps.headerRef)
3033
})
3134
})

app/components/Footer/__tests__/__snapshots__/Footer.spec.js.snap

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22

33
exports[`<Footer /> renders 1`] = `
44
<Footer
5-
handleHeaderFocusClick={[MockFunction]}
5+
handleRefocusClick={[MockFunction]}
6+
headerRef={
7+
Object {
8+
"current": <header />,
9+
}
10+
}
611
>
712
<div
813
className="footer-wrapper small-section fluid grid"
@@ -85,8 +90,9 @@ exports[`<Footer /> renders 1`] = `
8590
>
8691
<button
8792
className="is-visually-hidden-focusable"
88-
onClick={[MockFunction]}
93+
onClick={[Function]}
8994
role="listitem"
95+
type="button"
9096
>
9197
Return to top of page
9298
</button>

app/components/GlobalNav/GlobalNav.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,27 @@ import Logo from "app/assets/images/un-logo.png"
99
import "./styles.scss"
1010

1111
export default function GlobalNav(props) {
12+
const handleClick = event => {
13+
event.preventDefault()
14+
props.handleRefocusClick(props.mainRef)
15+
}
16+
1217
return (
1318
<nav id="global-nav" className="fluid grid">
1419
<ul className="nav-list row has-no-padding">
1520
<li className="small-5 xsmall-12 columns" role="none">
16-
<Link to="/" className="logo">
21+
<Link to="/" className="logo" role="listitem">
1722
<img src={Logo} alt="Undernet" />
1823
</Link>
1924
</li>
20-
<li className="small-7 xsmall-12 columns" role="none">
25+
<li className="small-7 xsmall-12 columns">
2126
<ul className="row">
2227
<li role="none">
2328
<button
2429
role="listitem"
2530
className="is-visually-hidden-focusable"
26-
onClick={props.handleMainFocusClick}
31+
onClick={handleClick}
32+
type="button"
2733
>
2834
Skip to main content
2935
</button>
@@ -47,9 +53,9 @@ export default function GlobalNav(props) {
4753
</a>
4854
</li>
4955
<li role="none">
50-
<Link to={introductionPath} role="listitem">
56+
<a href={introductionPath} role="listitem">
5157
Documentation
52-
</Link>
58+
</a>
5359
</li>
5460
</ul>
5561
</li>
@@ -59,5 +65,8 @@ export default function GlobalNav(props) {
5965
}
6066

6167
GlobalNav.propTypes = {
62-
handleMainFocusClick: PropTypes.func.isRequired,
68+
handleRefocusClick: PropTypes.func.isRequired,
69+
mainRef: PropTypes.shape({
70+
current: PropTypes.instanceOf(Element),
71+
}),
6372
}

app/components/GlobalNav/__tests__/GlobalNav.spec.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import { BrowserRouter as Router } from "react-router-dom"
44

55
describe("<Nav />", () => {
66
const baseProps = {
7-
handleMainFocusClick: jest.fn(),
7+
handleRefocusClick: jest.fn(),
8+
mainRef: {
9+
current: document.createElement("main"),
10+
},
811
}
912

1013
const mountComponent = () => {
@@ -22,12 +25,12 @@ describe("<Nav />", () => {
2225
expect(wrapper).toMatchSnapshot()
2326
})
2427

25-
it("calls handleMainFocusClick prop when hidden button is clicked", () => {
28+
it("calls handleRefocusClick prop when hidden button is clicked", () => {
2629
// Given
2730
const wrapper = mountComponent()
2831
// When
2932
wrapper.find(".is-visually-hidden-focusable").simulate("click")
3033
// Then
31-
expect(baseProps.handleMainFocusClick).toHaveBeenCalled()
34+
expect(baseProps.handleRefocusClick).toHaveBeenCalledWith(baseProps.mainRef)
3235
})
3336
})

app/components/GlobalNav/__tests__/__snapshots__/GlobalNav.spec.js.snap

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@ exports[`<Nav /> renders 1`] = `
2525
}
2626
>
2727
<GlobalNav
28-
handleMainFocusClick={[MockFunction]}
28+
handleRefocusClick={[MockFunction]}
29+
mainRef={
30+
Object {
31+
"current": <main />,
32+
}
33+
}
2934
>
3035
<nav
3136
className="fluid grid"
@@ -40,12 +45,14 @@ exports[`<Nav /> renders 1`] = `
4045
>
4146
<Link
4247
className="logo"
48+
role="listitem"
4349
to="/"
4450
>
4551
<a
4652
className="logo"
4753
href="/"
4854
onClick={[Function]}
55+
role="listitem"
4956
>
5057
<img
5158
alt="Undernet"
@@ -56,7 +63,6 @@ exports[`<Nav /> renders 1`] = `
5663
</li>
5764
<li
5865
className="small-7 xsmall-12 columns"
59-
role="none"
6066
>
6167
<ul
6268
className="row"
@@ -66,8 +72,9 @@ exports[`<Nav /> renders 1`] = `
6672
>
6773
<button
6874
className="is-visually-hidden-focusable"
69-
onClick={[MockFunction]}
75+
onClick={[Function]}
7076
role="listitem"
77+
type="button"
7178
>
7279
Skip to main content
7380
</button>
@@ -153,18 +160,12 @@ exports[`<Nav /> renders 1`] = `
153160
<li
154161
role="none"
155162
>
156-
<Link
163+
<a
164+
href="/docs/overview/introduction"
157165
role="listitem"
158-
to="/docs/overview/introduction"
159166
>
160-
<a
161-
href="/docs/overview/introduction"
162-
onClick={[Function]}
163-
role="listitem"
164-
>
165-
Documentation
166-
</a>
167-
</Link>
167+
Documentation
168+
</a>
168169
</li>
169170
</ul>
170171
</li>

app/layouts/Main/Main.js

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect } from "react"
1+
import React, { useEffect } from "react"
22
import { Switch, Route } from "react-router-dom"
33
import { ContextUtil } from "undernet"
44

@@ -14,8 +14,10 @@ import "./styles.scss"
1414
import { FOCUSABLE_TABINDEX, UNFOCUSABLE_TABINDEX } from "./constants"
1515

1616
export default function Main() {
17-
const [headerTabIndex, setHeaderTabIndex] = useState(UNFOCUSABLE_TABINDEX)
18-
const [mainTabIndex, setMainTabIndex] = useState(UNFOCUSABLE_TABINDEX)
17+
const Attributes = {
18+
TABINDEX: "tabindex",
19+
}
20+
1921
const headerRef = React.createRef()
2022
const mainRef = React.createRef()
2123
const componentUnmountFunction = () => {
@@ -27,41 +29,31 @@ export default function Main() {
2729
return componentUnmountFunction
2830
}, [])
2931

30-
useEffect(() => {
31-
if (headerTabIndex === FOCUSABLE_TABINDEX) {
32-
headerRef.current.focus()
33-
} else if (mainTabIndex === FOCUSABLE_TABINDEX) {
34-
mainRef.current.focus()
35-
}
36-
}, [headerTabIndex, mainTabIndex])
37-
38-
const handleHeaderFocusClick = event => {
39-
event.preventDefault()
40-
setHeaderTabIndex(FOCUSABLE_TABINDEX)
32+
const handleRefocusClick = ref => {
33+
ref.current.setAttribute(Attributes.TABINDEX, FOCUSABLE_TABINDEX)
34+
ref.current.focus()
4135
}
4236

43-
const handleMainFocusClick = event => {
44-
event.preventDefault()
45-
setMainTabIndex(FOCUSABLE_TABINDEX)
46-
}
37+
const getHeaderTabIndex = () => headerRef.current.getAttribute(Attributes.TABINDEX)
38+
const getMainTabIndex = () => mainRef.current.getAttribute(Attributes.TABINDEX)
4739

4840
const handleHeaderBlur = () => {
49-
if (headerTabIndex === UNFOCUSABLE_TABINDEX) return
50-
setHeaderTabIndex(UNFOCUSABLE_TABINDEX)
41+
if (getHeaderTabIndex() === UNFOCUSABLE_TABINDEX) return
42+
headerRef.current.removeAttribute(Attributes.TABINDEX)
5143
}
5244

5345
const handleMainBlur = () => {
54-
if (mainTabIndex === UNFOCUSABLE_TABINDEX) return
55-
setMainTabIndex(UNFOCUSABLE_TABINDEX)
46+
if (getMainTabIndex() === UNFOCUSABLE_TABINDEX) return
47+
mainRef.current.removeAttribute(Attributes.TABINDEX)
5648
}
5749

5850
return (
5951
<React.Fragment>
60-
<header tabIndex={headerTabIndex} ref={headerRef} onBlur={handleHeaderBlur} role="banner">
61-
<GlobalNav handleMainFocusClick={handleMainFocusClick} />
52+
<header ref={headerRef} onBlur={handleHeaderBlur} role="banner">
53+
<GlobalNav handleRefocusClick={handleRefocusClick} mainRef={mainRef} />
6254
</header>
6355

64-
<main tabIndex={mainTabIndex} ref={mainRef} onBlur={handleMainBlur} role="main">
56+
<main ref={mainRef} onBlur={handleMainBlur} role="main">
6557
<Switch>
6658
<Route exact path={rootPath} component={Home} />
6759
<Route path={docsPath} component={Docs} />
@@ -70,7 +62,7 @@ export default function Main() {
7062
</main>
7163

7264
<footer>
73-
<Footer handleHeaderFocusClick={handleHeaderFocusClick} />
65+
<Footer handleRefocusClick={handleRefocusClick} headerRef={headerRef} />
7466
</footer>
7567
</React.Fragment>
7668
)

app/layouts/Main/__tests__/Main.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ describe("<Main />", () => {
3737
expect(header.is(":focus")).toBe(true)
3838
})
3939

40-
it("sets [tabindex] of header tag to null when blurred", () => {
40+
it("removes [tabindex] of header tag when blurred", () => {
4141
// Given
4242
const wrapper = mountComponent()
4343
const header = wrapper.find("header")
@@ -60,7 +60,7 @@ describe("<Main />", () => {
6060
expect(main.is(":focus")).toBe(true)
6161
})
6262

63-
it("sets [tabindex] of main tag to null when blurred", () => {
63+
it("removes [tabindex] of main tag when blurred", () => {
6464
// Given
6565
const wrapper = mountComponent()
6666
const main = wrapper.find("main")

0 commit comments

Comments
 (0)