Browse Source

Refs #31034 -- Improved accessibility of admin navigation sidebar.

Tom Carrick 4 years ago
parent
commit
780473d756

+ 1 - 0
django/contrib/admin/static/admin/css/nav_sidebar.css

@@ -17,6 +17,7 @@
     cursor: pointer;
     font-size: 20px;
     color: #447e9b;
+    padding: 0;
 }
 
 [dir="rtl"] .toggle-nav-sidebar {

+ 17 - 0
django/contrib/admin/static/admin/js/nav_sidebar.js

@@ -2,18 +2,35 @@
 {
     const toggleNavSidebar = document.getElementById('toggle-nav-sidebar');
     if (toggleNavSidebar !== null) {
+        const navLinks = document.querySelectorAll('#nav-sidebar a');
+        function disableNavLinkTabbing() {
+            for (const navLink of navLinks) {
+                navLink.tabIndex = -1;
+            }
+        }
+        function enableNavLinkTabbing() {
+            for (const navLink of navLinks) {
+                navLink.tabIndex = 0;
+            }
+        }
+
         const main = document.getElementById('main');
         let navSidebarIsOpen = localStorage.getItem('django.admin.navSidebarIsOpen');
         if (navSidebarIsOpen === null) {
             navSidebarIsOpen = 'true';
         }
+        if (navSidebarIsOpen === 'false') {
+            disableNavLinkTabbing();
+        }
         main.classList.toggle('shifted', navSidebarIsOpen === 'true');
 
         toggleNavSidebar.addEventListener('click', function() {
             if (navSidebarIsOpen === 'true') {
                 navSidebarIsOpen = 'false';
+                disableNavLinkTabbing();
             } else {
                 navSidebarIsOpen = 'true';
+                enableNavLinkTabbing();
             }
             localStorage.setItem('django.admin.navSidebarIsOpen', navSidebarIsOpen);
             main.classList.toggle('shifted');

+ 2 - 1
django/contrib/admin/templates/admin/nav_sidebar.html

@@ -1,4 +1,5 @@
-<div class="sticky toggle-nav-sidebar" id="toggle-nav-sidebar"></div>
+{% load i18n %}
+<button class="sticky toggle-nav-sidebar" id="toggle-nav-sidebar" aria-label="{% translate 'Toggle navigation' %}"></button>
 <nav class="sticky" id="nav-sidebar">
   {% include 'admin/app_list.html' with app_list=available_apps %}
 </nav>

+ 12 - 0
tests/admin_views/test_nav_sidebar.py

@@ -97,7 +97,14 @@ class SeleniumTests(AdminSeleniumTestCase):
     def test_sidebar_can_be_closed(self):
         self.selenium.get(self.live_server_url + reverse('test_with_sidebar:auth_user_changelist'))
         toggle_button = self.selenium.find_element_by_css_selector('#toggle-nav-sidebar')
+        self.assertEqual(toggle_button.tag_name, 'button')
+        self.assertEqual(toggle_button.get_attribute('aria-label'), 'Toggle navigation')
+        for link in self.selenium.find_elements_by_css_selector('#nav-sidebar a'):
+            self.assertEqual(link.get_attribute('tabIndex'), '0')
         toggle_button.click()
+        # Hidden sidebar is not reachable via keyboard navigation.
+        for link in self.selenium.find_elements_by_css_selector('#nav-sidebar a'):
+            self.assertEqual(link.get_attribute('tabIndex'), '-1')
         main_element = self.selenium.find_element_by_css_selector('#main')
         self.assertNotIn('shifted', main_element.get_attribute('class').split())
 
@@ -115,7 +122,12 @@ class SeleniumTests(AdminSeleniumTestCase):
         self.assertNotIn('shifted', main_element.get_attribute('class').split())
 
         toggle_button = self.selenium.find_element_by_css_selector('#toggle-nav-sidebar')
+        # Hidden sidebar is not reachable via keyboard navigation.
+        for link in self.selenium.find_elements_by_css_selector('#nav-sidebar a'):
+            self.assertEqual(link.get_attribute('tabIndex'), '-1')
         toggle_button.click()
+        for link in self.selenium.find_elements_by_css_selector('#nav-sidebar a'):
+            self.assertEqual(link.get_attribute('tabIndex'), '0')
         self.assertEqual(
             self.selenium.execute_script("return localStorage.getItem('django.admin.navSidebarIsOpen')"),
             'true',