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}`}} +