Skip to content

Commit a18f762

Browse files
[a11y] Fix focus on generate API keys in connectors config (elastic#237522)
## Summary Closes elastic#200028. Resolves an accessibility issue where opening the modal to generate API keys on the content connectors configuration page would incorrectly return focus to the top of the page after the confirmation modal closes. __Before__ https://github.com/user-attachments/assets/81e7bd94-c339-4329-bd2f-6b01ef564f62 __After__ https://github.com/user-attachments/assets/5528ea6a-9e0e-45ee-878b-3a039b54bef2 ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [x] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [x] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [x] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> (cherry picked from commit 8be8570)
1 parent 0406eff commit a18f762

File tree

2 files changed

+57
-6
lines changed
  • x-pack

2 files changed

+57
-6
lines changed

x-pack/platform/plugins/shared/content_connectors/public/components/connector_detail/components/generated_config_fields.tsx

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
* 2.0.
66
*/
77

8-
import React, { useState } from 'react';
8+
import React, { useRef, useState } from 'react';
9+
import type { ComponentProps } from 'react';
910

1011
import {
1112
EuiButtonIcon,
@@ -35,9 +36,10 @@ import { EuiLinkTo } from '../../shared/react_router_helpers';
3536
import { MANAGE_API_KEYS_URL } from '../../../../common/constants';
3637

3738
const ConfirmModal: React.FC<{
39+
focusTrapProps?: ComponentProps<typeof EuiConfirmModal>['focusTrapProps'];
3840
onCancel: () => void;
3941
onConfirm: () => void;
40-
}> = ({ onCancel, onConfirm }) => {
42+
}> = ({ onCancel, onConfirm, focusTrapProps }) => {
4143
const confirmModalTitleId = useGeneratedHtmlId();
4244

4345
return (
@@ -65,6 +67,7 @@ const ConfirmModal: React.FC<{
6567
}
6668
)}
6769
defaultFocusedButton="confirm"
70+
focusTrapProps={focusTrapProps}
6871
>
6972
{i18n.translate(
7073
'xpack.contentConnectors.content.indices.configurationConnector.apiKey.confirmModal.description',
@@ -90,6 +93,8 @@ export const GeneratedConfigFields: React.FC<GeneratedConfigFieldsProps> = ({
9093
generateApiKey,
9194
isGenerateLoading,
9295
}) => {
96+
const generateButtonRef = useRef<HTMLButtonElement>(null);
97+
const refreshButtonRef = useRef<HTMLButtonElement>(null);
9398
const [isModalVisible, setIsModalVisible] = useState(false);
9499
const refreshButtonClick = () => {
95100
setIsModalVisible(true);
@@ -108,7 +113,25 @@ export const GeneratedConfigFields: React.FC<GeneratedConfigFieldsProps> = ({
108113

109114
return (
110115
<>
111-
{isModalVisible && <ConfirmModal onCancel={onCancel} onConfirm={onConfirm} />}
116+
{isModalVisible && (
117+
<ConfirmModal
118+
onCancel={onCancel}
119+
onConfirm={onConfirm}
120+
focusTrapProps={{
121+
returnFocus: () => {
122+
if (generateButtonRef.current) {
123+
generateButtonRef.current.focus();
124+
return false;
125+
}
126+
if (refreshButtonRef.current) {
127+
refreshButtonRef.current.focus();
128+
return false;
129+
}
130+
return true;
131+
},
132+
}}
133+
/>
134+
)}
112135
<>
113136
<EuiFlexGrid columns={3} alignItems="center" gutterSize="s">
114137
<EuiFlexItem>
@@ -241,6 +264,7 @@ export const GeneratedConfigFields: React.FC<GeneratedConfigFieldsProps> = ({
241264
data-test-subj="enterpriseSearchGeneratedConfigFieldsButton"
242265
size="xs"
243266
iconType="refresh"
267+
buttonRef={refreshButtonRef}
244268
isLoading={isGenerateLoading}
245269
onClick={refreshButtonClick}
246270
disabled={!connector.index_name}
@@ -274,6 +298,7 @@ export const GeneratedConfigFields: React.FC<GeneratedConfigFieldsProps> = ({
274298
data-test-subj="enterpriseSearchGeneratedConfigFieldsButton"
275299
size="xs"
276300
iconType="refresh"
301+
buttonRef={generateButtonRef}
277302
isLoading={isGenerateLoading}
278303
onClick={refreshButtonClick}
279304
disabled={!connector.index_name}
@@ -290,6 +315,7 @@ export const GeneratedConfigFields: React.FC<GeneratedConfigFieldsProps> = ({
290315
<>
291316
<EuiSpacer size="m" />
292317
<EuiCallOut
318+
announceOnMount
293319
color="success"
294320
size="s"
295321
title={i18n.translate(

x-pack/solutions/search/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/generated_config_fields.tsx

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
* 2.0.
66
*/
77

8-
import React, { useState } from 'react';
8+
import React, { useRef, useState } from 'react';
9+
import type { ComponentProps } from 'react';
910

1011
import {
1112
EuiButtonIcon,
@@ -43,9 +44,10 @@ export interface GeneratedConfigFieldsProps {
4344
}
4445

4546
const ConfirmModal: React.FC<{
47+
focusTrapProps?: ComponentProps<typeof EuiConfirmModal>['focusTrapProps'];
4648
onCancel: () => void;
4749
onConfirm: () => void;
48-
}> = ({ onCancel, onConfirm }) => {
50+
}> = ({ onCancel, onConfirm, focusTrapProps }) => {
4951
const modalTitleId = useGeneratedHtmlId();
5052

5153
return (
@@ -73,6 +75,7 @@ const ConfirmModal: React.FC<{
7375
}
7476
)}
7577
defaultFocusedButton="confirm"
78+
focusTrapProps={focusTrapProps}
7679
>
7780
{i18n.translate(
7881
'xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.description',
@@ -91,6 +94,8 @@ export const GeneratedConfigFields: React.FC<GeneratedConfigFieldsProps> = ({
9194
generateApiKey,
9295
isGenerateLoading,
9396
}) => {
97+
const generateButtonRef = useRef<HTMLButtonElement>(null);
98+
const refreshButtonRef = useRef<HTMLButtonElement>(null);
9499
const [isModalVisible, setIsModalVisible] = useState(false);
95100
const refreshButtonClick = () => {
96101
setIsModalVisible(true);
@@ -109,7 +114,25 @@ export const GeneratedConfigFields: React.FC<GeneratedConfigFieldsProps> = ({
109114

110115
return (
111116
<>
112-
{isModalVisible && <ConfirmModal onCancel={onCancel} onConfirm={onConfirm} />}
117+
{isModalVisible && (
118+
<ConfirmModal
119+
onCancel={onCancel}
120+
onConfirm={onConfirm}
121+
focusTrapProps={{
122+
returnFocus: () => {
123+
if (generateButtonRef.current) {
124+
generateButtonRef.current.focus();
125+
return false;
126+
}
127+
if (refreshButtonRef.current) {
128+
refreshButtonRef.current.focus();
129+
return false;
130+
}
131+
return true;
132+
},
133+
}}
134+
/>
135+
)}
113136
<>
114137
<EuiFlexGrid columns={3} alignItems="center" gutterSize="s">
115138
<EuiFlexItem>
@@ -242,6 +265,7 @@ export const GeneratedConfigFields: React.FC<GeneratedConfigFieldsProps> = ({
242265
data-test-subj="enterpriseSearchGeneratedConfigFieldsButton"
243266
size="xs"
244267
iconType="refresh"
268+
buttonRef={refreshButtonRef}
245269
isLoading={isGenerateLoading}
246270
onClick={refreshButtonClick}
247271
disabled={!connector.index_name}
@@ -275,6 +299,7 @@ export const GeneratedConfigFields: React.FC<GeneratedConfigFieldsProps> = ({
275299
data-test-subj="enterpriseSearchGeneratedConfigFieldsButton"
276300
size="xs"
277301
iconType="refresh"
302+
buttonRef={generateButtonRef}
278303
isLoading={isGenerateLoading}
279304
onClick={refreshButtonClick}
280305
disabled={!connector.index_name}

0 commit comments

Comments
 (0)