Skip to content

Commit dc920e9

Browse files
committed
fix: Fix screen reader A11y issues and improve focus logic reliability
1 parent 1949260 commit dc920e9

File tree

3 files changed

+52
-10
lines changed

3 files changed

+52
-10
lines changed

src/tutorial-panel/components/tutorial-list/index.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ interface TutorialListProps {
2828
onStartTutorial: HotspotContext['onStartTutorial'];
2929
i18nStrings: TutorialPanelProps['i18nStrings'];
3030
downloadUrl: TutorialPanelProps['downloadUrl'];
31-
headerRef?: React.MutableRefObject<HTMLHeadingElement | null>;
31+
headerRef?: (node: HTMLDivElement | null) => void;
32+
headerId?: string;
3233
}
3334

3435
export default function TutorialList({
@@ -38,6 +39,7 @@ export default function TutorialList({
3839
onStartTutorial,
3940
downloadUrl,
4041
headerRef,
42+
headerId,
4143
}: TutorialListProps) {
4244
checkSafeUrl('TutorialPanel', downloadUrl);
4345

@@ -47,8 +49,19 @@ export default function TutorialList({
4749
<>
4850
<InternalSpaceBetween size="s">
4951
<InternalSpaceBetween size="m">
50-
<div ref={headerRef} tabIndex={-1}>
51-
<InternalBox variant="h2" fontSize={isRefresh ? 'heading-m' : 'heading-l'} padding={{ bottom: 'n' }}>
52+
<div
53+
ref={headerRef}
54+
tabIndex={-1}
55+
role="region"
56+
aria-labelledby={headerId}
57+
className={styles['tutorial-header-region']}
58+
>
59+
<InternalBox
60+
variant="h2"
61+
fontSize={isRefresh ? 'heading-m' : 'heading-l'}
62+
padding={{ bottom: 'n' }}
63+
id={headerId}
64+
>
5265
{i18nStrings.tutorialListTitle}
5366
</InternalBox>
5467
</div>

src/tutorial-panel/components/tutorial-list/styles.scss

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@
1717
margin-inline: 0;
1818
}
1919

20+
.tutorial-header-region {
21+
@include focus-visible.when-visible {
22+
@include styles.focus-highlight(0px);
23+
}
24+
25+
&:focus {
26+
outline: none;
27+
}
28+
}
29+
2030
.tutorial-box {
2131
@include styles.styles-reset;
2232
list-style: none;

src/tutorial-panel/index.tsx

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33
'use client';
4-
import React, { useContext, useRef } from 'react';
4+
import React, { useCallback, useContext, useRef } from 'react';
55
import clsx from 'clsx';
66

7+
import { useUniqueId } from '@cloudscape-design/component-toolkit/internal';
8+
79
import { hotspotContext } from '../annotation-context/context';
810
import { getBaseProps } from '../internal/base-component';
911
import useBaseComponent from '../internal/hooks/use-base-component';
@@ -28,7 +30,26 @@ export default function TutorialPanel({
2830

2931
const baseProps = getBaseProps(restProps);
3032
const context = useContext(hotspotContext);
31-
const headerRef = useRef<HTMLHeadingElement>(null);
33+
34+
// should focus on the header (on exiting tutorial, we have to know and
35+
// focus on header for accessiblity reasons)
36+
const shouldFocusRef = useRef(false);
37+
const headerId = useUniqueId('tutorial-header-');
38+
39+
const headerCallbackRef = useCallback((node: HTMLDivElement | null) => {
40+
if (node && shouldFocusRef.current) {
41+
node.focus({ preventScroll: true });
42+
shouldFocusRef.current = false;
43+
}
44+
}, []);
45+
46+
const handleExitTutorial = useCallback(
47+
(e: any) => {
48+
shouldFocusRef.current = true;
49+
context.onExitTutorial(e);
50+
},
51+
[context]
52+
);
3253

3354
return (
3455
<>
@@ -37,10 +58,7 @@ export default function TutorialPanel({
3758
<TutorialDetailView
3859
i18nStrings={i18nStrings}
3960
tutorial={context.currentTutorial}
40-
onExitTutorial={e => {
41-
context.onExitTutorial(e);
42-
requestAnimationFrame(() => headerRef.current?.focus({ preventScroll: true }));
43-
}}
61+
onExitTutorial={handleExitTutorial}
4462
currentStepIndex={context.currentStepIndex}
4563
onFeedbackClick={onFeedbackClick}
4664
/>
@@ -51,7 +69,8 @@ export default function TutorialPanel({
5169
loading={loading}
5270
onStartTutorial={context.onStartTutorial}
5371
downloadUrl={downloadUrl}
54-
headerRef={headerRef}
72+
headerRef={headerCallbackRef}
73+
headerId={headerId}
5574
/>
5675
)}
5776
</div>

0 commit comments

Comments
 (0)