fix: support keyboard navigation for menus (#988)

This commit is contained in:
Robert Kaussow 2025-03-03 09:10:22 +01:00 committed by GitHub
parent a61602e94a
commit c55c466e17
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 116 additions and 33 deletions

View File

@ -13,8 +13,6 @@
<div class="wrapper">
<input type="checkbox" class="hidden" id="menu-header-control" />
{{ partial "site-header" (dict "Root" . "MenuEnabled" false) }}

View File

@ -32,8 +32,6 @@
<div
class="wrapper {{ if default false .Site.Params.geekdocDarkModeDim }}dark-mode-dim{{ end }}"
>
<input type="checkbox" class="hidden" id="menu-control" />
<input type="checkbox" class="hidden" id="menu-header-control" />
{{ $navEnabled := default true .Page.Params.geekdocNav }}
{{ partial "site-header" (dict "Root" . "MenuEnabled" $navEnabled) }}

View File

@ -1,6 +1,6 @@
{{ if hugo.IsMultilingual }}
<span class="gdoc-language">
<ul class="gdoc-language__selector" role="button" aria-pressed="false" tabindex="0">
<ul class="gdoc-language__selector" tabindex="0" role="button" aria-pressed="false">
<li>
{{ range .Site.Languages }}
{{ if eq . $.Site.Language }}

View File

@ -1,15 +1,19 @@
<header class="gdoc-header">
<div class="container flex align-center justify-between">
{{ if .MenuEnabled }}
<label for="menu-control" class="gdoc-nav__control" tabindex="0">
<svg class="gdoc-icon gdoc_menu">
<title>{{ i18n "button_nav_open" }}</title>
<use xlink:href="#gdoc_menu"></use>
</svg>
<svg class="gdoc-icon gdoc_arrow_back">
<title>{{ i18n "button_nav_close" }}</title>
<use xlink:href="#gdoc_arrow_back"></use>
</svg>
<label for="menu-control" class="gdoc-nav__control">
<div tabindex="0" role="button" aria-pressed="false">
<input type="checkbox" class="hidden" id="menu-control" />
<svg class="gdoc-icon gdoc_menu">
<title>{{ i18n "button_nav_open" }}</title>
<use xlink:href="#gdoc_menu"></use>
</svg>
<svg class="gdoc-icon gdoc_arrow_back">
<title>{{ i18n "button_nav_close" }}</title>
<use xlink:href="#gdoc_arrow_back"></use>
</svg>
</div>
</label>
{{ end }}
<div>
@ -24,14 +28,14 @@
</span>
</a>
</div>
<div class="gdoc-menu-header">
<div class="gdoc-menu-header flex gap-16">
<span class="gdoc-menu-header__items">
{{ if .Root.Site.Data.menu.extra.header }}
{{ partial "menu-extra" (dict "current" .Root "source" .Root.Site.Data.menu.extra.header "target" "header") }}
{{ end }}
<span id="gdoc-color-theme">
<span id="gdoc-color-theme" tabindex="0" role="button" aria-pressed="false">
<svg class="gdoc-icon gdoc_brightness_dark">
<title>{{ i18n "button_toggle_dark" }}</title>
<use xlink:href="#gdoc_brightness_dark"></use>
@ -56,23 +60,24 @@
</span>
{{ partial "language" .Root }}
</span>
<span class="gdoc-menu-header__control">
<label for="menu-header-control">
<div tabindex="0" role="button" aria-pressed="false">
<input type="checkbox" class="hidden" id="menu-header-control" />
<span class="gdoc-menu-header__control">
<label for="menu-header-control">
<svg class="gdoc-icon gdoc_keyboard_arrow_right">
<use xlink:href="#gdoc_keyboard_arrow_right"></use>
<title>{{ i18n "button_menu_close" }}</title>
</svg>
</label>
</span>
<svg class="gdoc-icon gdoc_keyboard_arrow_left">
<use xlink:href="#gdoc_keyboard_arrow_left"></use>
<title>{{ i18n "button_menu_open" }}</title>
</svg>
</div>
</label>
</span>
<label for="menu-header-control" class="gdoc-menu-header__control">
<svg class="gdoc-icon gdoc_keyboard_arrow_left">
<use xlink:href="#gdoc_keyboard_arrow_left"></use>
<title>{{ i18n "button_menu_open" }}</title>
</svg>
</label>
</div>
</div>
</header>

63
src/js/accessibility.js Normal file
View File

@ -0,0 +1,63 @@
document.addEventListener("DOMContentLoaded", function () {
// Find all elements with role="button"
const buttonRoleElements = document.querySelectorAll('[role="button"]')
const gdocNav = document.querySelector(".gdoc-nav")
const gdocPage = document.querySelector(".gdoc-page")
buttonRoleElements.forEach((buttonElement) => {
// Check if this button controls a checkbox
const controlId = buttonElement.parentElement.getAttribute("for")
if (!controlId) return
const controlElement = document.getElementById(controlId)
if (!controlElement || controlElement.type !== "checkbox") return
// Set initial accessibility state
buttonElement.setAttribute("aria-pressed", controlElement.checked)
if (controlId === "menu-control" && gdocNav && gdocPage) {
updateMenuAccessibility(controlElement.checked)
}
buttonElement.addEventListener("click", function () {
this.setAttribute("aria-pressed", controlElement.checked)
if (controlId === "menu-control" && gdocNav && gdocPage) {
updateMenuAccessibility(controlElement.checked)
}
})
buttonElement.addEventListener("keydown", function (event) {
if (event.key === "Enter") {
controlElement.checked = !controlElement.checked
this.setAttribute("aria-pressed", controlElement.checked)
if (controlId === "menu-control" && gdocNav && gdocPage) {
updateMenuAccessibility(controlElement.checked)
}
event.preventDefault()
}
})
})
// Helper function for menu navigation accessibility
function updateMenuAccessibility(isMenuOpen) {
if (!gdocNav || !gdocPage) return
if (isMenuOpen) {
gdocNav.removeAttribute("inert")
gdocNav.setAttribute("aria-hidden", false)
gdocPage.setAttribute("inert", "")
gdocPage.setAttribute("aria-hidden", true)
} else {
gdocNav.setAttribute("inert", "")
gdocNav.setAttribute("aria-hidden", true)
gdocPage.removeAttribute("inert")
gdocPage.setAttribute("aria-hidden", false)
}
}
})

View File

@ -7,7 +7,7 @@ import { TOGGLE_COLOR_THEMES, THEME, COLOR_THEME_AUTO } from "./config.js"
document.addEventListener("DOMContentLoaded", () => {
const colorThemeToggle = document.getElementById("gdoc-color-theme")
colorThemeToggle.onclick = function () {
function toggleColorTheme() {
let lstore = Storage.namespace(THEME)
let currentColorTheme = lstore.get("color-theme") || COLOR_THEME_AUTO
let nextColorTheme = toggle(TOGGLE_COLOR_THEMES, currentColorTheme)
@ -15,6 +15,17 @@ document.addEventListener("DOMContentLoaded", () => {
lstore.set("color-theme", TOGGLE_COLOR_THEMES[nextColorTheme])
applyTheme(false)
}
colorThemeToggle.onclick = function () {
toggleColorTheme()
}
colorThemeToggle.addEventListener("keydown", function (event) {
if (event.key === "Enter") {
toggleColorTheme()
event.preventDefault()
}
})
})
function applyTheme(init = true) {

View File

@ -1,4 +1,5 @@
import Clipboard from "clipboard"
import "./accessibility.js"
document.addEventListener("DOMContentLoaded", function () {
let clipboard = new Clipboard(".clip")

View File

@ -31,6 +31,12 @@
display: none;
}
&__control {
svg.gdoc-icon.gdoc_keyboard_arrow_right {
display: none;
}
}
&__control,
&__home {
display: flex;
@ -76,7 +82,7 @@
}
}
#menu-control:checked ~ main {
.wrapper:has(#menu-control:checked) {
.gdoc-nav nav,
.gdoc-page {
transform: translateX(defaults.$menu-width);
@ -85,9 +91,7 @@
.gdoc-page {
opacity: 0.25;
}
}
#menu-control:checked ~ .gdoc-header .gdoc-nav__control {
svg.gdoc-icon.gdoc_menu {
display: none;
}
@ -97,7 +101,7 @@
}
}
#menu-header-control:checked ~ .gdoc-header {
.wrapper:has(#menu-header-control:checked) {
.gdoc-brand {
display: none;
}
@ -111,6 +115,9 @@
svg.gdoc-icon.gdoc_keyboard_arrow_left {
display: none;
}
svg.gdoc-icon.gdoc_keyboard_arrow_right {
display: inline-block;
}
}
}
}

View File

@ -87,7 +87,7 @@ var config = {
generate(seed, files) {
let manifest = {}
files.forEach(function (element, index) {
files.forEach(function (element) {
if (element.name.endsWith("VERSION")) return
if (element.name.endsWith(".svg")) return
if (element.name.startsWith("fonts/")) return