This is an automated email from the ASF dual-hosted git repository.

guoqqqi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-dashboard.git


The following commit(s) were added to refs/heads/master by this push:
     new 30d4b9f0d fix: accessible names for header icon buttons, Monaco 
contrast, drawer Escape (#3442)
30d4b9f0d is described below

commit 30d4b9f0db1e3926fb109307a50dc4f573fda019
Author: Yuhan <[email protected]>
AuthorDate: Thu Jul 23 09:52:05 2026 +0800

    fix: accessible names for header icon buttons, Monaco contrast, drawer 
Escape (#3442)
---
 .../a11y.icon-buttons-and-escape.spec.ts           | 51 ++++++++++++++++++++++
 src/components/Header/LanguageMenu.tsx             |  6 ++-
 src/components/Header/SettingModalBtn.tsx          |  3 ++
 src/components/Header/index.tsx                    |  8 +++-
 .../FormItemPlugins/SelectPluginsDrawer.tsx        |  3 +-
 src/locales/en/common.json                         |  5 +++
 src/locales/zh/common.json                         |  5 +++
 src/styles/global.css                              |  4 +-
 8 files changed, 81 insertions(+), 4 deletions(-)

diff --git a/e2e/tests/regression/a11y.icon-buttons-and-escape.spec.ts 
b/e2e/tests/regression/a11y.icon-buttons-and-escape.spec.ts
new file mode 100644
index 000000000..429189c19
--- /dev/null
+++ b/e2e/tests/regression/a11y.icon-buttons-and-escape.spec.ts
@@ -0,0 +1,51 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Regression for a11y items of apache/apisix-dashboard#3417: the header's
+// icon-only buttons (settings, language) had no accessible name, and the
+// Select Plugins drawer disabled Escape-to-close despite having no unsaved
+// state to protect.
+
+import { routesPom } from '@e2e/pom/routes';
+import { test } from '@e2e/utils/test';
+import { expect } from '@playwright/test';
+
+test('header icon buttons expose accessible names', async ({ page }) => {
+  await routesPom.toIndex(page);
+  await routesPom.isIndexPage(page);
+
+  await expect(
+    page.getByRole('button', { name: 'Open settings' })
+  ).toBeVisible();
+  await expect(
+    page.getByRole('button', { name: 'Select language' })
+  ).toBeVisible();
+});
+
+test('the Select Plugins drawer closes on Escape', async ({ page }) => {
+  await routesPom.toIndex(page);
+  await routesPom.isIndexPage(page);
+  await routesPom.getAddRouteBtn(page).click();
+  await routesPom.isAddPage(page);
+
+  await page.getByRole('button', { name: 'Select Plugins' }).click();
+  const drawer = page.getByRole('dialog', { name: 'Select Plugins' });
+  await expect(drawer).toBeVisible();
+
+  await page.keyboard.press('Escape');
+  await expect(drawer).toBeHidden();
+});
diff --git a/src/components/Header/LanguageMenu.tsx 
b/src/components/Header/LanguageMenu.tsx
index 316daf05e..73ff7caf9 100644
--- a/src/components/Header/LanguageMenu.tsx
+++ b/src/components/Header/LanguageMenu.tsx
@@ -50,7 +50,11 @@ export const LanguageMenu = () => {
   return (
     <Menu shadow="md" width={200}>
       <Menu.Target>
-        <ActionIcon variant="light" size="sm">
+        <ActionIcon
+          variant="light"
+          size="sm"
+          aria-label={t('a11y.selectLanguage')}
+        >
           <IconLanguage />
         </ActionIcon>
       </Menu.Target>
diff --git a/src/components/Header/SettingModalBtn.tsx 
b/src/components/Header/SettingModalBtn.tsx
index 1003d60fc..0ebb566f6 100644
--- a/src/components/Header/SettingModalBtn.tsx
+++ b/src/components/Header/SettingModalBtn.tsx
@@ -16,18 +16,21 @@
  */
 import { ActionIcon } from '@mantine/core';
 import { useSetAtom } from 'jotai';
+import { useTranslation } from 'react-i18next';
 
 import { isSettingsOpenAtom } from '@/stores/global';
 import IconSettings from '~icons/material-symbols/settings';
 
 export const SettingModalBtn = () => {
   const setIsSettingsOpen = useSetAtom(isSettingsOpenAtom);
+  const { t } = useTranslation();
 
   return (
     <ActionIcon
       onClick={() => setIsSettingsOpen(true)}
       variant="light"
       size="sm"
+      aria-label={t('a11y.openSettings')}
     >
       <IconSettings />
     </ActionIcon>
diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx
index 75e8bc9da..964008931 100644
--- a/src/components/Header/index.tsx
+++ b/src/components/Header/index.tsx
@@ -41,7 +41,13 @@ export const Header: FC<HeaderProps> = (props) => {
     <AppShell.Header>
       <Group h="100%" px="md" justify="space-between">
         <Group h="100%" gap="sm">
-          <Burger opened={opened} onClick={toggle} hiddenFrom="sm" size="sm" />
+          <Burger
+            opened={opened}
+            onClick={toggle}
+            hiddenFrom="sm"
+            size="sm"
+            aria-label={t('a11y.toggleNavigation')}
+          />
           <Logo />
           <div>{t('apisix.dashboard')}</div>
         </Group>
diff --git a/src/components/form-slice/FormItemPlugins/SelectPluginsDrawer.tsx 
b/src/components/form-slice/FormItemPlugins/SelectPluginsDrawer.tsx
index 6f4a9eb2f..1b459e840 100644
--- a/src/components/form-slice/FormItemPlugins/SelectPluginsDrawer.tsx
+++ b/src/components/form-slice/FormItemPlugins/SelectPluginsDrawer.tsx
@@ -47,7 +47,8 @@ export const SelectPluginsDrawer = (props: 
SelectPluginsDrawerProps) => {
         radius="md"
         position="right"
         size="xl"
-        closeOnEscape={false}
+        // this drawer only picks a plugin to add — no unsaved edits to
+        // protect, so Escape should close it like any other dialog (#3417)
         opened={opened}
         onClose={() => setOpened(false)}
         title={t('form.plugins.selectPlugins.title')}
diff --git a/src/locales/en/common.json b/src/locales/en/common.json
index 5209795a4..2ebfb685e 100644
--- a/src/locales/en/common.json
+++ b/src/locales/en/common.json
@@ -374,5 +374,10 @@
   "error": {
     "title": "Something went wrong",
     "retry": "Retry"
+  },
+  "a11y": {
+    "openSettings": "Open settings",
+    "selectLanguage": "Select language",
+    "toggleNavigation": "Toggle navigation"
   }
 }
diff --git a/src/locales/zh/common.json b/src/locales/zh/common.json
index 6a82d155a..1499a2151 100644
--- a/src/locales/zh/common.json
+++ b/src/locales/zh/common.json
@@ -374,5 +374,10 @@
   "error": {
     "title": "出错了",
     "retry": "重试"
+  },
+  "a11y": {
+    "openSettings": "打开设置",
+    "selectLanguage": "选择语言",
+    "toggleNavigation": "切换导航"
   }
 }
diff --git a/src/styles/global.css b/src/styles/global.css
index 4be68eb64..c78ef5dc4 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -52,8 +52,10 @@
     pointer-events: auto !important;
   }
 
+  /* gray-7 on the gray-0 background clears WCAG AA (~7:1); gray-5 was
+     ~2:1 and unreadable on the primary read path (#3417) */
   .monaco-editor .view-lines,
   .monaco-editor .view-line * {
-    color: var(--mantine-color-gray-5) !important;
+    color: var(--mantine-color-gray-7) !important;
   }
 }

Reply via email to