diff --git a/pages/wizard/simple.page.tsx b/pages/wizard/simple.page.tsx
index c2204d641e..8a76a9f274 100644
--- a/pages/wizard/simple.page.tsx
+++ b/pages/wizard/simple.page.tsx
@@ -4,6 +4,7 @@ import React, { useState } from 'react';
import Wizard, { WizardProps } from '~components/wizard';
import Toggle from '~components/toggle';
import Button from '~components/button';
+import Link from '~components/link';
import styles from './styles.scss';
import { i18nStrings } from './common';
@@ -27,9 +28,14 @@ const steps: WizardProps.Step[] = [
},
{
title: 'Step 3',
+ info: Info,
content: (
-
Content 3
+ {Array.from(Array(15).keys()).map(key => (
+
+ Item {key}
+
+ ))}
),
},
diff --git a/pages/wizard/styles.scss b/pages/wizard/styles.scss
index 9a6c55e931..3816653911 100644
--- a/pages/wizard/styles.scss
+++ b/pages/wizard/styles.scss
@@ -14,3 +14,7 @@
height: 200px;
overflow: scroll;
}
+
+.content-item {
+ height: 100px;
+}
diff --git a/src/wizard/__integ__/wizard.test.ts b/src/wizard/__integ__/wizard.test.ts
index a0b661e615..6df52d2872 100644
--- a/src/wizard/__integ__/wizard.test.ts
+++ b/src/wizard/__integ__/wizard.test.ts
@@ -63,6 +63,35 @@ describe('Wizard keyboard navigation', () => {
await expect(page.getText('#content-text')).resolves.toBe('Content 1');
})
);
+
+ test(
+ 'should focus on header after navigation to the next step',
+ setupTest(async page => {
+ await page.resetFocus();
+ await page.keys(['Tab', 'Tab', 'Space']);
+ await expect(page.getFocusedElementText()).resolves.toBe('Step 2');
+ })
+ );
+
+ test(
+ 'should focus on header after navigation to the previous step',
+ setupTest(async page => {
+ await page.clickPrimaryButton();
+ await page.keys(['Shift', 'Tab', 'Space']);
+ await expect(page.getFocusedElementText()).resolves.toBe('Step 1');
+ })
+ );
+
+ test(
+ 'header should receive focus only programmatically',
+ setupTest(async page => {
+ await page.resetFocus();
+ await page.keys(['Tab', 'Tab', 'Space']);
+ await expect(page.getFocusedElementText()).resolves.toBe('Step 2');
+ await page.keys(['Tab', 'Shift', 'Tab']);
+ await expect(page.getFocusedElementText()).resolves.not.toBe('Step 2');
+ })
+ );
});
});
diff --git a/src/wizard/__tests__/wizard.test.tsx b/src/wizard/__tests__/wizard.test.tsx
index 9e12939417..14a9ab68e8 100644
--- a/src/wizard/__tests__/wizard.test.tsx
+++ b/src/wizard/__tests__/wizard.test.tsx
@@ -402,23 +402,6 @@ describe('Form', () => {
});
});
-describe('Focus delegation', () => {
- test('when previous button is not focused and unmounted no focus delegation occurs', () => {
- const [wrapper] = renderDefaultWizard();
- wrapper.findPrimaryButton()!.click();
- wrapper.findPreviousButton()!.click();
- expect(wrapper.findPrimaryButton()!.getElement()).not.toBe(document.activeElement);
- });
-
- test('when previous button is focused and unmounted the focus is delegated to the next button', () => {
- const [wrapper] = renderDefaultWizard();
- wrapper.findPrimaryButton()!.click();
- wrapper.findPreviousButton()!.focus();
- wrapper.findPreviousButton()!.click();
- expect(wrapper.findPrimaryButton()!.getElement()).toBe(document.activeElement);
- });
-});
-
test('sets last step as active when activeStepIndex is out of bound, and raises warning', () => {
consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
expect(consoleWarnSpy).not.toHaveBeenCalled();
diff --git a/src/wizard/index.tsx b/src/wizard/index.tsx
index e3b6fe611d..370e83065c 100644
--- a/src/wizard/index.tsx
+++ b/src/wizard/index.tsx
@@ -1,6 +1,6 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-import React, { useEffect, useRef } from 'react';
+import React, { useRef } from 'react';
import clsx from 'clsx';
import { getBaseProps } from '../internal/base-component';
import { fireNonCancelableEvent } from '../internal/events';
@@ -18,19 +18,6 @@ import { useVisualRefresh } from '../internal/hooks/use-visual-mode';
export { WizardProps };
-const scrollToTop = (ref: React.RefObject) => {
- const overflowRegex = /(auto|scroll)/;
- let parent = ref?.current?.parentElement;
- while (parent && !overflowRegex.test(getComputedStyle(parent).overflow)) {
- parent = parent.parentElement;
- }
- if (parent) {
- parent.scrollTop = 0;
- } else {
- window.scrollTo(window.pageXOffset, 0);
- }
-};
-
export default function Wizard({
steps,
activeStepIndex: controlledActiveStepIndex,
@@ -61,11 +48,6 @@ export default function Wizard({
const farthestStepIndex = useRef(actualActiveStepIndex);
farthestStepIndex.current = Math.max(farthestStepIndex.current, actualActiveStepIndex);
- const internalRef = useRef(null);
- useEffect(() => {
- scrollToTop(internalRef);
- }, [actualActiveStepIndex]);
-
const isVisualRefresh = useVisualRefresh();
const isLastStep = actualActiveStepIndex >= steps.length - 1;
@@ -101,7 +83,6 @@ export default function Wizard({
;
@@ -43,6 +45,15 @@ export default function WizardForm({
const isLastStep = activeStepIndex >= steps.length - 1;
const skipToTargetIndex = findSkipToTargetIndex(steps, activeStepIndex);
const isMobile = useMobile();
+ const stepHeaderRef = useRef(null);
+
+ useEffectOnUpdate(() => {
+ if (stepHeaderRef && stepHeaderRef.current) {
+ stepHeaderRef.current?.focus();
+ }
+ }, [activeStepIndex]);
+
+ const focusVisible = useFocusVisible();
const showSkipTo = allowSkipTo && skipToTargetIndex !== -1;
const skipToButtonText =
@@ -63,8 +74,10 @@ export default function WizardForm({
{i18nStrings.collapsedStepsLabel(activeStepIndex + 1, steps.length)}
- {title}
- {isOptional && {` - ${i18nStrings.optional}`}}
+
+ {title}
+ {isOptional && {` - ${i18nStrings.optional}`}}
+