Skip to content

Commit 1de9874

Browse files
committed
[B] Fix Reader Header Options menu a11y
- Adds a11y props to options button - Adds separate mobile version of options menu with inert state - Adds focus trap to separate mobile options menu
1 parent 08b005e commit 1de9874

File tree

2 files changed

+90
-52
lines changed

2 files changed

+90
-52
lines changed

client/src/reader/components/Header/index.js

Lines changed: 78 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import Utility from "global/components/utility";
1818
import DisclosureNavigationMenu from "global/components/atomic/DisclosureNavigationMenu";
1919
import { useTranslation } from "react-i18next";
2020
import Authorize from "hoc/Authorize";
21+
import { FocusTrap } from "focus-trap-react";
2122

2223
export default function Header(props) {
2324
const {
@@ -88,8 +89,9 @@ export default function Header(props) {
8889
return (
8990
<button
9091
onClick={handleOptionsToggleClick}
91-
aria-hidden
92-
tabIndex={-1}
92+
id="options-menu-button"
93+
aria-controls="options-menu"
94+
aria-expanded={mobileOptionsExpanded}
9395
className="reader-header__button reader-header__options-button"
9496
>
9597
<span>
@@ -151,6 +153,61 @@ export default function Header(props) {
151153
);
152154
};
153155

156+
const renderOptionsNav = () => {
157+
return (
158+
<ul
159+
aria-label={t("reader.header.reader_settings_search")}
160+
className="reader-header__nav-list"
161+
>
162+
<Authorize kind={"any"}>
163+
<li className="reader-header__nav-item">
164+
<ControlMenu.Button
165+
onClick={panelToggleHandler("notes")}
166+
icon="notes24"
167+
label={t("glossary.note_title_case_other")}
168+
active={visibility.uiPanels.notes}
169+
/>
170+
</li>
171+
</Authorize>
172+
<li className="reader-header__nav-item">
173+
<ControlMenu.Button
174+
onClick={panelToggleHandler("visibility")}
175+
icon="eyeball24"
176+
label={t("common.visibility_title_case")}
177+
active={visibility.uiPanels.visibility}
178+
/>
179+
</li>
180+
<li className="reader-header__nav-item">
181+
<ControlMenu.Button
182+
onClick={panelToggleHandler("appearance")}
183+
icon="text24"
184+
label={t("reader.header.reader_appearance")}
185+
active={visibility.uiPanels.appearance}
186+
/>
187+
</li>
188+
<li className="reader-header__nav-item">
189+
<SearchMenu.Button
190+
toggleSearchMenu={panelToggleHandler("search")}
191+
active={visibility.uiPanels.search}
192+
className="reader-header__button reader-header__button--pad-narrow"
193+
iconSize={32}
194+
/>
195+
</li>
196+
<li className="reader-header__nav-item">
197+
<DisclosureNavigationMenu
198+
visible={visibility.uiPanels.user}
199+
disclosure={<UserMenuButton />}
200+
callbacks={commonActions}
201+
onBlur={commonActions.hideUserPanel}
202+
context="reader"
203+
>
204+
<UserMenuBody />
205+
</DisclosureNavigationMenu>
206+
</li>
207+
</ul>
208+
);
209+
};
210+
154211
const innerClassName = classNames({
155212
"reader-header__inner": true,
156213
"reader-header__inner--shifted": mobileOptionsExpanded
@@ -173,58 +230,27 @@ export default function Header(props) {
173230
showSection={!scrollAware.top}
174231
/>
175232
)}
233+
{/* Options menu */}
176234
<div className="reader-header__menu-group reader-header__menu-group--right">
177-
<ul
178-
aria-label={t("reader.header.reader_settings_search")}
179-
className="reader-header__nav-list"
180-
>
181-
<Authorize kind={"any"}>
182-
<li className="reader-header__nav-item">
183-
<ControlMenu.Button
184-
onClick={panelToggleHandler("notes")}
185-
icon="notes24"
186-
label={t("glossary.note_title_case_other")}
187-
active={visibility.uiPanels.notes}
188-
/>
189-
</li>
190-
</Authorize>
191-
<li className="reader-header__nav-item">
192-
<ControlMenu.Button
193-
onClick={panelToggleHandler("visibility")}
194-
icon="eyeball24"
195-
label={t("common.visibility_title_case")}
196-
active={visibility.uiPanels.visibility}
197-
/>
198-
</li>
199-
<li className="reader-header__nav-item">
200-
<ControlMenu.Button
201-
onClick={panelToggleHandler("appearance")}
202-
icon="text24"
203-
label={t("reader.header.reader_appearance")}
204-
active={visibility.uiPanels.appearance}
205-
/>
206-
</li>
207-
<li className="reader-header__nav-item">
208-
<SearchMenu.Button
209-
toggleSearchMenu={panelToggleHandler("search")}
210-
active={visibility.uiPanels.search}
211-
className="reader-header__button reader-header__button--pad-narrow"
212-
iconSize={32}
213-
/>
214-
</li>
215-
<li className="reader-header__nav-item">
216-
<DisclosureNavigationMenu
217-
visible={visibility.uiPanels.user}
218-
disclosure={<UserMenuButton />}
219-
callbacks={commonActions}
220-
onBlur={commonActions.hideUserPanel}
221-
context="reader"
222-
>
223-
<UserMenuBody />
224-
</DisclosureNavigationMenu>
225-
</li>
226-
</ul>
235+
{renderOptionsNav()}
227236
</div>
237+
{/* Options menu, mobile */}
238+
<FocusTrap
239+
active={mobileOptionsExpanded}
240+
focusTrapOptions={{
241+
allowOutsideClick: true,
242+
escapeDeactivates: handleOptionsToggleClick
243+
}}
244+
>
245+
<div
246+
className="reader-header__menu-group reader-header__menu-group--dialog"
247+
id="options-menu"
248+
aria-labelledby="options-menu-button"
249+
{...(mobileOptionsExpanded ? {} : { inert: "" })}
250+
>
251+
{renderOptionsNav()}
252+
</div>
253+
</FocusTrap>
228254
</nav>
229255
{!!text && (
230256
<>

client/src/theme/styles/components/reader/readerHeader.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,20 @@ export default `
5555
&--right {
5656
grid-area: menu-group-right;
5757
width: 100vw;
58+
display: block;
5859
5960
${respond(`width: auto;`, 50)}
61+
${respond(`display: none;`, 50, "max")}
62+
${respond(`display: block;`, 20, "max")}
63+
}
64+
65+
&--dialog {
66+
grid-area: menu-group-right;
67+
width: 100vw;
68+
display: none;
69+
70+
${respond(`display: block;`, 50, "max")}
71+
${respond(`display: none;`, 20, "max")}
6072
}
6173
}
6274

0 commit comments

Comments
 (0)