fix(gallery): add accessible keyboard navigation (#56)

Co-authored-by: Cathryn Lavery <cathryn@bestself.co>
This commit is contained in:
Ngo Quoc Viet
2026-08-17 15:03:34 -07:00
committed by GitHub
co-authored by Cathryn Lavery
parent 4a769fe3eb
commit e48df3fb1e
3 changed files with 55 additions and 12 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "diagram-design", "name": "diagram-design",
"description": "Create branded architecture, IT current-state, flowchart, sequence, state machine, ER/data model, timeline, swimlane, quadrant, radar/spider, loop/flywheel, nested, tree, org chart, layer stack, Venn, pyramid/funnel, bar, line, Gantt and scatter charts, high-level, process, medallion, data flow, DP integration, or DP security matrix diagrams as standalone HTML/SVG/PNG. Redraw .drawio/.drawio.png/.drawio.svg or Mermaid .mmd sources at a chosen size/detail; onboard brand tokens from a website; add semantic patterns, callouts, accessible motion, or sketchy/hand-drawn styling.", "description": "Create branded architecture, IT current-state, flowchart, sequence, state machine, ER/data model, timeline, swimlane, quadrant, radar/spider, loop/flywheel, nested, tree, org chart, layer stack, Venn, pyramid/funnel, bar, line, Gantt and scatter charts, high-level, process, medallion, data flow, DP integration, or DP security matrix diagrams as standalone HTML/SVG/PNG. Redraw .drawio/.drawio.png/.drawio.svg or Mermaid .mmd sources at a chosen size/detail; onboard brand tokens from a website; add semantic patterns, callouts, accessible motion, or sketchy/hand-drawn styling.",
"version": "2.4.2", "version": "2.4.3",
"author": { "author": {
"name": "Cathryn Lavery", "name": "Cathryn Lavery",
"url": "https://github.com/cathrynlavery" "url": "https://github.com/cathrynlavery"
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "diagram-design", "name": "diagram-design",
"description": "Create branded architecture, IT current-state, flowchart, sequence, state machine, ER/data model, timeline, swimlane, quadrant, radar/spider, loop/flywheel, nested, tree, org chart, layer stack, Venn, pyramid/funnel, bar, line, Gantt and scatter charts, high-level, process, medallion, data flow, DP integration, or DP security matrix diagrams as standalone HTML/SVG/PNG. Redraw .drawio/.drawio.png/.drawio.svg or Mermaid .mmd sources at a chosen size/detail; onboard brand tokens from a website; add semantic patterns, callouts, accessible motion, or sketchy/hand-drawn styling.", "description": "Create branded architecture, IT current-state, flowchart, sequence, state machine, ER/data model, timeline, swimlane, quadrant, radar/spider, loop/flywheel, nested, tree, org chart, layer stack, Venn, pyramid/funnel, bar, line, Gantt and scatter charts, high-level, process, medallion, data flow, DP integration, or DP security matrix diagrams as standalone HTML/SVG/PNG. Redraw .drawio/.drawio.png/.drawio.svg or Mermaid .mmd sources at a chosen size/detail; onboard brand tokens from a website; add semantic patterns, callouts, accessible motion, or sketchy/hand-drawn styling.",
"version": "2.4.2", "version": "2.4.3",
"author": { "author": {
"name": "Cathryn Lavery", "name": "Cathryn Lavery",
"url": "https://github.com/cathrynlavery" "url": "https://github.com/cathrynlavery"
+53 -10
View File
@@ -194,7 +194,7 @@
</div> </div>
</div> </div>
<div class="tabs" id="type-tabs" role="tablist" aria-label="Diagram type"> <div class="tabs" id="type-tabs" role="radiogroup" aria-label="Diagram type">
<button class="tab active" data-type="architecture"> <button class="tab active" data-type="architecture">
<span class="eyebrow">01</span>Architecture <span class="eyebrow">01</span>Architecture
</button> </button>
@@ -311,7 +311,7 @@
<div <div
class="tabs variants" class="tabs variants"
id="variant-tabs" id="variant-tabs"
role="tablist" role="radiogroup"
aria-label="Variant" aria-label="Variant"
> >
<button class="tab active" data-variant="">Minimal light</button> <button class="tab active" data-variant="">Minimal light</button>
@@ -334,30 +334,73 @@
const openRaw = document.getElementById("open-raw"); const openRaw = document.getElementById("open-raw");
function update() { function update() {
const activeType = document.querySelector("#type-tabs .tab.active"); const activeType = [...document.querySelectorAll("#type-tabs .tab")].find(
(tab) => tab.dataset.type === state.type,
);
const single = activeType && activeType.hasAttribute("data-single"); const single = activeType && activeType.hasAttribute("data-single");
const variantTabs = document.querySelectorAll("#variant-tabs .tab"); const variantTabs = document.querySelectorAll("#variant-tabs .tab");
if (single) state.variant = ""; if (single) state.variant = "";
variantTabs.forEach((tab) => { variantTabs.forEach((tab) => {
tab.disabled = single && tab.dataset.variant !== ""; tab.disabled = single && tab.dataset.variant !== "";
tab.classList.toggle("active", tab.dataset.variant === state.variant);
}); });
for (const [containerId, key] of [
["type-tabs", "type"],
["variant-tabs", "variant"],
]) {
document.querySelectorAll(`#${containerId} .tab`).forEach((tab) => {
const active = tab.dataset[key] === state[key];
tab.classList.toggle("active", active);
tab.setAttribute("aria-checked", String(active));
tab.tabIndex = active ? 0 : -1;
});
}
const src = `example-${state.type}${state.variant}.html`; const src = `example-${state.type}${state.variant}.html`;
iframe.src = src; iframe.src = src;
openRaw.href = src; openRaw.href = src;
const selectedTabs = document.querySelectorAll('[role="radio"][aria-checked="true"]');
iframe.setAttribute(
"aria-labelledby",
[...selectedTabs].map((tab) => tab.id).join(" "),
);
} }
function bindTabs(containerId, key) { function bindTabs(containerId, key) {
const container = document.getElementById(containerId); const container = document.getElementById(containerId);
const tabs = [...container.querySelectorAll(".tab")];
tabs.forEach((tab, index) => {
tab.id = `${containerId}-${index}`;
tab.setAttribute("role", "radio");
tab.setAttribute("aria-controls", "preview");
});
function activate(tab, focus = false) {
state[key] = tab.dataset[key];
update();
if (focus) tab.focus();
}
container.addEventListener("click", (e) => { container.addEventListener("click", (e) => {
const btn = e.target.closest(".tab"); const btn = e.target.closest(".tab");
if (!btn) return; if (!btn) return;
container activate(btn);
.querySelectorAll(".tab") });
.forEach((t) => t.classList.remove("active")); container.addEventListener("keydown", (e) => {
btn.classList.add("active"); const current = e.target.closest('[role="radio"]');
state[key] = btn.dataset[key]; if (!current || !container.contains(current)) return;
update(); const enabledTabs = tabs.filter((tab) => !tab.disabled);
const index = enabledTabs.indexOf(current);
let next;
if (e.key === "ArrowRight" || e.key === "ArrowDown") {
next = enabledTabs[(index + 1) % enabledTabs.length];
}
if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
next = enabledTabs[(index - 1 + enabledTabs.length) % enabledTabs.length];
}
if (e.key === "Home") next = enabledTabs[0];
if (e.key === "End") next = enabledTabs.at(-1);
if (!next) return;
e.preventDefault();
activate(next, true);
}); });
} }