Skip to content

Commit 7e20763

Browse files
authored
Improve settings tab accessibility (#19)
* Improve settings tab accessibility * Polish responsive tab accessibility --------- Co-authored-by: jonmartin721 <jonmartin721@users.noreply.github.com>
1 parent 6ba6662 commit 7e20763

4 files changed

Lines changed: 228 additions & 366 deletions

File tree

options/options.css

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,6 @@ h1 {
196196
.tab-navigation {
197197
display: flex;
198198
flex-direction: column;
199-
gap: 4px;
200199
padding: 8px;
201200
background: var(--bg-secondary);
202201
border-right: 1px solid var(--border-color);
@@ -208,6 +207,12 @@ h1 {
208207
scrollbar-width: thin;
209208
}
210209

210+
.tab-list {
211+
display: flex;
212+
flex-direction: column;
213+
gap: 4px;
214+
}
215+
211216
.sidebar-header {
212217
padding: 16px 16px 12px;
213218
margin-bottom: 8px;
@@ -284,11 +289,14 @@ h1 {
284289

285290
/* Tab Panels */
286291
.tab-panel {
287-
display: none;
288292
animation: fadeIn 0.2s ease-in;
289293
}
290294

291-
.tab-panel.active {
295+
.tab-panel[hidden] {
296+
display: none;
297+
}
298+
299+
.tab-panel:not([hidden]) {
292300
display: block;
293301
}
294302

@@ -321,6 +329,11 @@ h1 {
321329
padding: 4px;
322330
}
323331

332+
.tab-list {
333+
flex-direction: row;
334+
min-width: max-content;
335+
}
336+
324337
.tab-button {
325338
flex-direction: column;
326339
gap: 4px;
@@ -862,6 +875,8 @@ body.dark-mode .notification-toggle input:checked + .toggle-slider {
862875

863876
.setup-step.clickable {
864877
cursor: pointer;
878+
color: inherit;
879+
text-decoration: none;
865880
}
866881

867882
.setup-step:hover {
@@ -910,6 +925,11 @@ body.dark-mode .notification-toggle input:checked + .toggle-slider {
910925
transform: none;
911926
}
912927

928+
.setup-step.clickable:focus-visible {
929+
outline: 2px solid var(--link-color);
930+
outline-offset: 2px;
931+
}
932+
913933
.step-all-content {
914934
flex: 1;
915935
}
@@ -2921,4 +2941,3 @@ body.dark-mode .setup-btn:hover {
29212941
padding: 20px 0 15px;
29222942
}
29232943
}
2924-

options/options.html

Lines changed: 52 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -8,52 +8,54 @@
88
</head>
99
<body>
1010
<div class="container">
11-
<nav class="tab-navigation" role="navigation" aria-label="Settings tabs">
11+
<nav class="tab-navigation" aria-label="Settings">
1212
<div class="sidebar-header">
1313
<h2>Settings</h2>
1414
</div>
15-
<button class="tab-button active" data-tab="setup" aria-selected="true">
16-
<svg class="tab-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
17-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"/>
18-
</svg>
19-
<span>Setup</span>
20-
</button>
21-
<button class="tab-button" data-tab="repositories" aria-selected="false">
22-
<svg class="tab-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
23-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"/>
24-
</svg>
25-
<span>Repositories</span>
26-
</button>
27-
<button class="tab-button" data-tab="filters" aria-selected="false">
28-
<svg class="tab-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
29-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/>
30-
</svg>
31-
<span>Activity Filters</span>
32-
</button>
33-
<button class="tab-button" data-tab="preferences" aria-selected="false">
34-
<svg class="tab-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
35-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"/>
36-
</svg>
37-
<span>Appearance & Behavior</span>
38-
</button>
39-
<button class="tab-button" data-tab="advanced" aria-selected="false">
40-
<svg class="tab-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
41-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
42-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
43-
</svg>
44-
<span>Advanced</span>
45-
</button>
46-
<button class="tab-button" data-tab="help" aria-selected="false">
47-
<svg class="tab-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
48-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
49-
</svg>
50-
<span>Help</span>
51-
</button>
15+
<div class="tab-list" role="tablist" aria-label="Settings sections" aria-orientation="vertical">
16+
<button class="tab-button active" type="button" id="tab-setup" role="tab" data-tab="setup" aria-selected="true" aria-controls="panel-setup" tabindex="0">
17+
<svg class="tab-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
18+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"/>
19+
</svg>
20+
<span>Setup</span>
21+
</button>
22+
<button class="tab-button" type="button" id="tab-repositories" role="tab" data-tab="repositories" aria-selected="false" aria-controls="panel-repositories" tabindex="-1">
23+
<svg class="tab-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
24+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"/>
25+
</svg>
26+
<span>Repositories</span>
27+
</button>
28+
<button class="tab-button" type="button" id="tab-filters" role="tab" data-tab="filters" aria-selected="false" aria-controls="panel-filters" tabindex="-1">
29+
<svg class="tab-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
30+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/>
31+
</svg>
32+
<span>Activity Filters</span>
33+
</button>
34+
<button class="tab-button" type="button" id="tab-preferences" role="tab" data-tab="preferences" aria-selected="false" aria-controls="panel-preferences" tabindex="-1">
35+
<svg class="tab-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
36+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"/>
37+
</svg>
38+
<span>Appearance & Behavior</span>
39+
</button>
40+
<button class="tab-button" type="button" id="tab-advanced" role="tab" data-tab="advanced" aria-selected="false" aria-controls="panel-advanced" tabindex="-1">
41+
<svg class="tab-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
42+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
43+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
44+
</svg>
45+
<span>Advanced</span>
46+
</button>
47+
<button class="tab-button" type="button" id="tab-help" role="tab" data-tab="help" aria-selected="false" aria-controls="panel-help" tabindex="-1">
48+
<svg class="tab-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
49+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
50+
</svg>
51+
<span>Help</span>
52+
</button>
53+
</div>
5254
</nav>
5355

5456
<div class="content-wrapper">
5557
<!-- Tab Panel 1: Setup -->
56-
<div class="tab-panel active" data-tab="setup">
58+
<div class="tab-panel" id="panel-setup" role="tabpanel" data-tab="setup" aria-labelledby="tab-setup">
5759
<section class="section">
5860
<h2>Getting Started</h2>
5961
<p class="help-text">Follow these steps to set up GitHub DevWatch</p>
@@ -104,29 +106,29 @@ <h3>Create a GitHub Token</h3>
104106
</div>
105107
</div>
106108

107-
<div class="setup-step clickable" data-tab="repositories">
109+
<a class="setup-step clickable" href="#repositories" data-tab="repositories">
108110
<div class="step-number">2</div>
109111
<div class="step-content">
110112
<h3>Add Repositories</h3>
111113
<p>Go to the <strong>Repositories</strong> tab to add repositories you want to monitor. You can add them manually or import from your GitHub account.</p>
112114
</div>
113-
</div>
115+
</a>
114116

115-
<div class="setup-step clickable" data-tab="filters">
117+
<a class="setup-step clickable" href="#filters" data-tab="filters">
116118
<div class="step-number">3</div>
117119
<div class="step-content">
118120
<h3>Configure Filters</h3>
119121
<p>Visit the <strong>Activity Filters</strong> tab to choose which types of activity you want to see (Pull Requests, Issues, Releases) and enable notifications.</p>
120122
</div>
121-
</div>
123+
</a>
122124

123-
<div class="setup-step clickable" data-tab="help">
125+
<a class="setup-step clickable" href="#help" data-tab="help">
124126
<div class="step-number">4</div>
125127
<div class="step-content">
126128
<h3>View Help & Changelog</h3>
127129
<p>Visit the <strong>Help</strong> tab to find documentation, support resources, and the changelog to see what's new in the latest version.</p>
128130
</div>
129-
</div>
131+
</a>
130132
</div>
131133

132134
<div class="info-box info-box-compact">
@@ -141,7 +143,7 @@ <h3>View Help & Changelog</h3>
141143
</div>
142144

143145
<!-- Tab Panel 2: Repositories -->
144-
<div class="tab-panel" data-tab="repositories">
146+
<div class="tab-panel" id="panel-repositories" role="tabpanel" data-tab="repositories" aria-labelledby="tab-repositories" hidden>
145147
<section class="section" id="repositories">
146148
<!-- Add New Repository Card -->
147149
<div class="repo-section-card add-repo-card">
@@ -263,7 +265,7 @@ <h2>Your Watched Repositories <span class="repo-count-badge" id="repoCountBadge"
263265
</div>
264266

265267
<!-- Tab Panel 3: Activity Filters -->
266-
<div class="tab-panel" data-tab="filters">
268+
<div class="tab-panel" id="panel-filters" role="tabpanel" data-tab="filters" aria-labelledby="tab-filters" hidden>
267269
<section class="section" id="filters">
268270
<h2>Activity Filters & Notifications</h2>
269271
<p class="help-text">Choose which types of activity to show and get notified about</p>
@@ -372,7 +374,7 @@ <h3>Releases</h3>
372374
</div>
373375

374376
<!-- Tab Panel 4: Appearance & Behavior -->
375-
<div class="tab-panel" data-tab="preferences">
377+
<div class="tab-panel" id="panel-preferences" role="tabpanel" data-tab="preferences" aria-labelledby="tab-preferences" hidden>
376378
<section class="section" id="feed-management">
377379
<h2>Feed Management</h2>
378380
<p class="help-text">Control how long activity items are stored in your feed and archive</p>
@@ -542,7 +544,7 @@ <h3>Currently Snoozed</h3>
542544
</div>
543545

544546
<!-- Tab Panel 5: Advanced -->
545-
<div class="tab-panel" data-tab="advanced">
547+
<div class="tab-panel" id="panel-advanced" role="tabpanel" data-tab="advanced" aria-labelledby="tab-advanced" hidden>
546548
<section class="section" id="data-management">
547549
<h2>Data Management</h2>
548550
<p class="help-text">Manage your extension data, cache, and settings</p>
@@ -661,7 +663,7 @@ <h2>Import/Export Settings</h2>
661663
</div>
662664

663665
<!-- Tab Panel 6: Help -->
664-
<div class="tab-panel" data-tab="help">
666+
<div class="tab-panel" id="panel-help" role="tabpanel" data-tab="help" aria-labelledby="tab-help" hidden>
665667
<section class="section">
666668
<div class="help-hero">
667669
<svg class="help-logo" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">

options/options.js

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,44 +51,92 @@ if (typeof document !== 'undefined') {
5151
// Theme listener imported from controllers/theme-controller.js
5252

5353
function setupTabNavigation() {
54-
const tabButtons = document.querySelectorAll('.tab-button');
55-
const tabPanels = document.querySelectorAll('.tab-panel');
54+
const tabButtons = Array.from(document.querySelectorAll('.tab-button'));
55+
const tabPanels = Array.from(document.querySelectorAll('.tab-panel'));
56+
57+
if (tabButtons.length === 0 || tabPanels.length === 0) {
58+
return;
59+
}
60+
61+
const validTabs = tabButtons
62+
.map(button => button.dataset.tab)
63+
.filter(Boolean);
64+
65+
function getTabButton(tabName) {
66+
return tabButtons.find(button => button.dataset.tab === tabName);
67+
}
68+
69+
function switchTab(tabName, { focusTab = false } = {}) {
70+
if (!validTabs.includes(tabName)) {
71+
return;
72+
}
5673

57-
// Function to switch tabs
58-
function switchTab(tabName) {
5974
// Update buttons
6075
tabButtons.forEach(btn => {
6176
const isActive = btn.dataset.tab === tabName;
6277
btn.classList.toggle('active', isActive);
63-
btn.setAttribute('aria-selected', isActive);
78+
btn.setAttribute('aria-selected', isActive ? 'true' : 'false');
79+
btn.tabIndex = isActive ? 0 : -1;
6480
});
6581

6682
// Update panels
6783
tabPanels.forEach(panel => {
68-
panel.classList.toggle('active', panel.dataset.tab === tabName);
84+
const isActive = panel.dataset.tab === tabName;
85+
panel.hidden = !isActive;
6986
});
7087

7188
// Save to localStorage
7289
localStorage.setItem('activeTab', tabName);
7390

7491
// Update URL hash without scrolling
7592
history.replaceState(null, null, `#${tabName}`);
93+
94+
if (focusTab) {
95+
getTabButton(tabName)?.focus();
96+
}
7697
}
7798

7899
// Add click listeners to tab buttons
79-
tabButtons.forEach(button => {
100+
tabButtons.forEach((button, index) => {
80101
button.addEventListener('click', () => {
81102
switchTab(button.dataset.tab);
82103
});
104+
105+
button.addEventListener('keydown', (event) => {
106+
let targetIndex = null;
107+
108+
switch (event.key) {
109+
case 'ArrowUp':
110+
case 'ArrowLeft':
111+
targetIndex = (index - 1 + tabButtons.length) % tabButtons.length;
112+
break;
113+
case 'ArrowDown':
114+
case 'ArrowRight':
115+
targetIndex = (index + 1) % tabButtons.length;
116+
break;
117+
case 'Home':
118+
targetIndex = 0;
119+
break;
120+
case 'End':
121+
targetIndex = tabButtons.length - 1;
122+
break;
123+
default:
124+
return;
125+
}
126+
127+
event.preventDefault();
128+
switchTab(tabButtons[targetIndex].dataset.tab, { focusTab: true });
129+
});
83130
});
84131

85132
// Add click listeners to clickable setup steps
86133
const clickableSetupSteps = document.querySelectorAll('.setup-step.clickable');
87134
clickableSetupSteps.forEach(step => {
88-
step.addEventListener('click', () => {
135+
step.addEventListener('click', (event) => {
136+
event.preventDefault();
89137
const tabName = step.dataset.tab;
90138
if (tabName) {
91-
switchTab(tabName);
139+
switchTab(tabName, { focusTab: true });
92140
}
93141
});
94142
});
@@ -98,8 +146,6 @@ function setupTabNavigation() {
98146
const savedTab = localStorage.getItem('activeTab');
99147
const initialTab = hash || savedTab || 'setup';
100148

101-
// Check if the hash/saved tab is valid
102-
const validTabs = ['setup', 'repositories', 'filters', 'preferences', 'advanced', 'help'];
103149
const tabToActivate = validTabs.includes(initialTab) ? initialTab : 'setup';
104150

105151
switchTab(tabToActivate);
@@ -1193,6 +1239,7 @@ setInterval(async () => {
11931239
// ES6 exports for tests
11941240
export {
11951241
state,
1242+
setupTabNavigation,
11961243
validateToken,
11971244
loadSettings,
11981245
setupEventListeners,

0 commit comments

Comments
 (0)