Skip to content

Commit c55c466

Browse files
authored
fix: support keyboard navigation for menus (#988)
1 parent a61602e commit c55c466

File tree

9 files changed

+116
-33
lines changed

9 files changed

+116
-33
lines changed

layouts/404.html

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313

1414

1515
<div class="wrapper">
16-
<input type="checkbox" class="hidden" id="menu-header-control" />
17-
1816
{{ partial "site-header" (dict "Root" . "MenuEnabled" false) }}
1917

2018

layouts/_default/baseof.html

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@
3232
<div
3333
class="wrapper {{ if default false .Site.Params.geekdocDarkModeDim }}dark-mode-dim{{ end }}"
3434
>
35-
<input type="checkbox" class="hidden" id="menu-control" />
36-
<input type="checkbox" class="hidden" id="menu-header-control" />
3735
{{ $navEnabled := default true .Page.Params.geekdocNav }}
3836
{{ partial "site-header" (dict "Root" . "MenuEnabled" $navEnabled) }}
3937

layouts/partials/language.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{{ if hugo.IsMultilingual }}
22
<span class="gdoc-language">
3-
<ul class="gdoc-language__selector" role="button" aria-pressed="false" tabindex="0">
3+
<ul class="gdoc-language__selector" tabindex="0" role="button" aria-pressed="false">
44
<li>
55
{{ range .Site.Languages }}
66
{{ if eq . $.Site.Language }}

layouts/partials/site-header.html

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
<header class="gdoc-header">
22
<div class="container flex align-center justify-between">
33
{{ if .MenuEnabled }}
4-
<label for="menu-control" class="gdoc-nav__control" tabindex="0">
5-
<svg class="gdoc-icon gdoc_menu">
6-
<title>{{ i18n "button_nav_open" }}</title>
7-
<use xlink:href="#gdoc_menu"></use>
8-
</svg>
9-
<svg class="gdoc-icon gdoc_arrow_back">
10-
<title>{{ i18n "button_nav_close" }}</title>
11-
<use xlink:href="#gdoc_arrow_back"></use>
12-
</svg>
4+
<label for="menu-control" class="gdoc-nav__control">
5+
<div tabindex="0" role="button" aria-pressed="false">
6+
<input type="checkbox" class="hidden" id="menu-control" />
7+
8+
<svg class="gdoc-icon gdoc_menu">
9+
<title>{{ i18n "button_nav_open" }}</title>
10+
<use xlink:href="#gdoc_menu"></use>
11+
</svg>
12+
<svg class="gdoc-icon gdoc_arrow_back">
13+
<title>{{ i18n "button_nav_close" }}</title>
14+
<use xlink:href="#gdoc_arrow_back"></use>
15+
</svg>
16+
</div>
1317
</label>
1418
{{ end }}
1519
<div>
@@ -24,14 +28,14 @@
2428
</span>
2529
</a>
2630
</div>
27-
<div class="gdoc-menu-header">
31+
<div class="gdoc-menu-header flex gap-16">
2832
<span class="gdoc-menu-header__items">
2933
{{ if .Root.Site.Data.menu.extra.header }}
3034
{{ partial "menu-extra" (dict "current" .Root "source" .Root.Site.Data.menu.extra.header "target" "header") }}
3135
{{ end }}
3236

3337

34-
<span id="gdoc-color-theme">
38+
<span id="gdoc-color-theme" tabindex="0" role="button" aria-pressed="false">
3539
<svg class="gdoc-icon gdoc_brightness_dark">
3640
<title>{{ i18n "button_toggle_dark" }}</title>
3741
<use xlink:href="#gdoc_brightness_dark"></use>
@@ -56,23 +60,24 @@
5660
</span>
5761

5862
{{ partial "language" .Root }}
63+
</span>
64+
<span class="gdoc-menu-header__control">
65+
<label for="menu-header-control">
66+
<div tabindex="0" role="button" aria-pressed="false">
67+
<input type="checkbox" class="hidden" id="menu-header-control" />
5968

60-
61-
<span class="gdoc-menu-header__control">
62-
<label for="menu-header-control">
6369
<svg class="gdoc-icon gdoc_keyboard_arrow_right">
6470
<use xlink:href="#gdoc_keyboard_arrow_right"></use>
6571
<title>{{ i18n "button_menu_close" }}</title>
6672
</svg>
67-
</label>
68-
</span>
73+
74+
<svg class="gdoc-icon gdoc_keyboard_arrow_left">
75+
<use xlink:href="#gdoc_keyboard_arrow_left"></use>
76+
<title>{{ i18n "button_menu_open" }}</title>
77+
</svg>
78+
</div>
79+
</label>
6980
</span>
70-
<label for="menu-header-control" class="gdoc-menu-header__control">
71-
<svg class="gdoc-icon gdoc_keyboard_arrow_left">
72-
<use xlink:href="#gdoc_keyboard_arrow_left"></use>
73-
<title>{{ i18n "button_menu_open" }}</title>
74-
</svg>
75-
</label>
7681
</div>
7782
</div>
7883
</header>

src/js/accessibility.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
document.addEventListener("DOMContentLoaded", function () {
2+
// Find all elements with role="button"
3+
const buttonRoleElements = document.querySelectorAll('[role="button"]')
4+
5+
const gdocNav = document.querySelector(".gdoc-nav")
6+
const gdocPage = document.querySelector(".gdoc-page")
7+
8+
buttonRoleElements.forEach((buttonElement) => {
9+
// Check if this button controls a checkbox
10+
const controlId = buttonElement.parentElement.getAttribute("for")
11+
if (!controlId) return
12+
13+
const controlElement = document.getElementById(controlId)
14+
if (!controlElement || controlElement.type !== "checkbox") return
15+
16+
// Set initial accessibility state
17+
buttonElement.setAttribute("aria-pressed", controlElement.checked)
18+
19+
if (controlId === "menu-control" && gdocNav && gdocPage) {
20+
updateMenuAccessibility(controlElement.checked)
21+
}
22+
23+
buttonElement.addEventListener("click", function () {
24+
this.setAttribute("aria-pressed", controlElement.checked)
25+
26+
if (controlId === "menu-control" && gdocNav && gdocPage) {
27+
updateMenuAccessibility(controlElement.checked)
28+
}
29+
})
30+
31+
buttonElement.addEventListener("keydown", function (event) {
32+
if (event.key === "Enter") {
33+
controlElement.checked = !controlElement.checked
34+
this.setAttribute("aria-pressed", controlElement.checked)
35+
36+
if (controlId === "menu-control" && gdocNav && gdocPage) {
37+
updateMenuAccessibility(controlElement.checked)
38+
}
39+
40+
event.preventDefault()
41+
}
42+
})
43+
})
44+
45+
// Helper function for menu navigation accessibility
46+
function updateMenuAccessibility(isMenuOpen) {
47+
if (!gdocNav || !gdocPage) return
48+
49+
if (isMenuOpen) {
50+
gdocNav.removeAttribute("inert")
51+
gdocNav.setAttribute("aria-hidden", false)
52+
53+
gdocPage.setAttribute("inert", "")
54+
gdocPage.setAttribute("aria-hidden", true)
55+
} else {
56+
gdocNav.setAttribute("inert", "")
57+
gdocNav.setAttribute("aria-hidden", true)
58+
59+
gdocPage.removeAttribute("inert")
60+
gdocPage.setAttribute("aria-hidden", false)
61+
}
62+
}
63+
})

src/js/colorTheme.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,25 @@ import { TOGGLE_COLOR_THEMES, THEME, COLOR_THEME_AUTO } from "./config.js"
77
document.addEventListener("DOMContentLoaded", () => {
88
const colorThemeToggle = document.getElementById("gdoc-color-theme")
99

10-
colorThemeToggle.onclick = function () {
10+
function toggleColorTheme() {
1111
let lstore = Storage.namespace(THEME)
1212
let currentColorTheme = lstore.get("color-theme") || COLOR_THEME_AUTO
1313
let nextColorTheme = toggle(TOGGLE_COLOR_THEMES, currentColorTheme)
1414

1515
lstore.set("color-theme", TOGGLE_COLOR_THEMES[nextColorTheme])
1616
applyTheme(false)
1717
}
18+
19+
colorThemeToggle.onclick = function () {
20+
toggleColorTheme()
21+
}
22+
23+
colorThemeToggle.addEventListener("keydown", function (event) {
24+
if (event.key === "Enter") {
25+
toggleColorTheme()
26+
event.preventDefault()
27+
}
28+
})
1829
})
1930

2031
function applyTheme(init = true) {

src/js/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Clipboard from "clipboard"
2+
import "./accessibility.js"
23

34
document.addEventListener("DOMContentLoaded", function () {
45
let clipboard = new Clipboard(".clip")

src/sass/_mobile.scss

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@
3131
display: none;
3232
}
3333

34+
&__control {
35+
svg.gdoc-icon.gdoc_keyboard_arrow_right {
36+
display: none;
37+
}
38+
}
39+
3440
&__control,
3541
&__home {
3642
display: flex;
@@ -76,7 +82,7 @@
7682
}
7783
}
7884

79-
#menu-control:checked ~ main {
85+
.wrapper:has(#menu-control:checked) {
8086
.gdoc-nav nav,
8187
.gdoc-page {
8288
transform: translateX(defaults.$menu-width);
@@ -85,9 +91,7 @@
8591
.gdoc-page {
8692
opacity: 0.25;
8793
}
88-
}
8994

90-
#menu-control:checked ~ .gdoc-header .gdoc-nav__control {
9195
svg.gdoc-icon.gdoc_menu {
9296
display: none;
9397
}
@@ -97,7 +101,7 @@
97101
}
98102
}
99103

100-
#menu-header-control:checked ~ .gdoc-header {
104+
.wrapper:has(#menu-header-control:checked) {
101105
.gdoc-brand {
102106
display: none;
103107
}
@@ -111,6 +115,9 @@
111115
svg.gdoc-icon.gdoc_keyboard_arrow_left {
112116
display: none;
113117
}
118+
svg.gdoc-icon.gdoc_keyboard_arrow_right {
119+
display: inline-block;
120+
}
114121
}
115122
}
116123
}

webpack.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ var config = {
8787
generate(seed, files) {
8888
let manifest = {}
8989

90-
files.forEach(function (element, index) {
90+
files.forEach(function (element) {
9191
if (element.name.endsWith("VERSION")) return
9292
if (element.name.endsWith(".svg")) return
9393
if (element.name.startsWith("fonts/")) return

0 commit comments

Comments
 (0)