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;
}
}