Skip to content

Commit a0df38d

Browse files
authored
chore: Shift focus to active ref by clicking / pressing on the recovery CTA (#4115)
1 parent 36ebfc0 commit a0df38d

File tree

5 files changed

+42
-8
lines changed

5 files changed

+42
-8
lines changed

src/multiselect/__tests__/multiselect.test.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { warnOnce } from '@cloudscape-design/component-toolkit/internal';
77
import { KeyCode } from '@cloudscape-design/test-utils-core/utils';
88

99
import '../../__a11y__/to-validate-a11y';
10+
import TestI18nProvider from '../../../lib/components/i18n/testing';
1011
import Multiselect, { MultiselectProps } from '../../../lib/components/multiselect';
1112
import createWrapper from '../../../lib/components/test-utils/dom';
1213

@@ -534,6 +535,25 @@ describe.each([true, false])('footer live announcements [expandToViewport=%s]',
534535
});
535536
});
536537

538+
test('Shifts focus to the filter on retry failed request', () => {
539+
const { wrapper } = renderMultiselect(
540+
<TestI18nProvider messages={{ select: { recoveryText: 'Custom recovery text' } }}>
541+
<Multiselect
542+
selectedOptions={[]}
543+
options={defaultOptions}
544+
onChange={() => {}}
545+
onLoadItems={() => {}}
546+
errorText="Error fetching items"
547+
statusType="error"
548+
filteringType="auto"
549+
/>
550+
</TestI18nProvider>
551+
);
552+
wrapper.openDropdown();
553+
wrapper.findErrorRecoveryButton()!.click();
554+
expect(wrapper.findFilteringInput()!.findNativeInput()!.getElement()).toHaveFocus();
555+
});
556+
537557
test('fires a change event when user selects a group option from the dropdown', () => {
538558
const onChange = jest.fn();
539559
const { wrapper } = renderMultiselect(

src/multiselect/use-multiselect.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ export function useMultiselect({
190190
getOptionProps,
191191
highlightOption,
192192
announceSelected,
193+
focusActiveRef,
193194
} = useSelect({
194195
selectedOptions,
195196
updateSelectedOption,
@@ -233,7 +234,10 @@ export function useMultiselect({
233234
isNoMatch,
234235
noMatch,
235236
filteringResultsText: filteredText,
236-
onRecoveryClick: handleRecoveryClick,
237+
onRecoveryClick: () => {
238+
handleRecoveryClick();
239+
focusActiveRef();
240+
},
237241
errorIconAriaLabel: errorIconAriaLabel,
238242
hasRecoveryCallback: !!onLoadItems,
239243
});

src/select/__tests__/select.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ describe.each([false, true])('expandToViewport=%s', expandToViewport => {
210210
wrapper.openDropdown();
211211
onLoadItems.mockClear();
212212
wrapper.findErrorRecoveryButton({ expandToViewport })!.click();
213+
expect(wrapper.findFilteringInput()!.findNativeInput()!.getElement()).toHaveFocus();
213214
expect(onLoadItems).toHaveBeenCalledWith({ filteringText: '', firstPage: false, samePage: true });
214215
});
215216

src/select/internal.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ const InternalSelect = React.forwardRef(
122122
highlightOption,
123123
selectOption,
124124
announceSelected,
125+
focusActiveRef,
125126
} = useSelect({
126127
selectedOptions: selectedOption ? [selectedOption] : [],
127128
updateSelectedOption: option => fireNonCancelableEvent(onChange, { selectedOption: option }),
@@ -197,7 +198,10 @@ const InternalSelect = React.forwardRef(
197198
noMatch,
198199
filteringResultsText: filteredText,
199200
errorIconAriaLabel,
200-
onRecoveryClick: handleRecoveryClick,
201+
onRecoveryClick: () => {
202+
handleRecoveryClick();
203+
focusActiveRef();
204+
},
201205
hasRecoveryCallback: !!onLoadItems,
202206
});
203207

src/select/utils/use-select.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
3-
import React, { RefObject } from 'react';
3+
import React, { RefObject, useCallback } from 'react';
44
import { useEffect, useRef } from 'react';
55

66
import { useUniqueId } from '@cloudscape-design/component-toolkit/internal';
@@ -337,14 +337,18 @@ export function useSelect({
337337
hasFilter,
338338
]);
339339

340+
const focusActiveRef = useCallback(() => {
341+
// dropdown-fit calculations ensure that the dropdown will fit inside the current
342+
// viewport, so prevent the browser from trying to scroll it into view (e.g. if
343+
// scroll-padding-top is set on a parent)
344+
activeRef.current?.focus({ preventScroll: true });
345+
}, [activeRef]);
346+
340347
useEffect(() => {
341348
if (isOpen && !embedded) {
342-
// dropdown-fit calculations ensure that the dropdown will fit inside the current
343-
// viewport, so prevent the browser from trying to scroll it into view (e.g. if
344-
// scroll-padding-top is set on a parent)
345-
activeRef.current?.focus({ preventScroll: true });
349+
focusActiveRef();
346350
}
347-
}, [isOpen, activeRef, embedded]);
351+
}, [isOpen, activeRef, embedded, focusActiveRef]);
348352

349353
useForwardFocus(externalRef, triggerRef as React.RefObject<HTMLElement>);
350354
const highlightedGroupSelected =
@@ -366,5 +370,6 @@ export function useSelect({
366370
selectOption,
367371
announceSelected,
368372
dialogId,
373+
focusActiveRef,
369374
};
370375
}

0 commit comments

Comments
 (0)