fix(a11y): improve keyboard accessibility in tablist (#45866)

* chore: resolve conflicts

* fix: focus outline on console pane

* refactor: focus indicator on console pane

* chore: remove commented code

* chore: resolve conflicts

* chore: add newline to end of file

* chore: fixed for prettier's sake
This commit is contained in:
Bruce Blaser
2022-09-23 09:30:20 -07:00
committed by GitHub
parent 608583bfbc
commit 3394c8aad2
5 changed files with 66 additions and 9 deletions

View File

@@ -110,3 +110,13 @@
flex: 1;
max-width: 50%;
}
/* Focus indicator for tab panel */
.nav-tabs [role='tab']:focus {
outline: 2px solid var(--blue-mid);
outline-offset: -2px;
}
.nav-tabs [role='tab']:focus:not(:focus-visible) {
outline: none;
}

View File

@@ -89,8 +89,10 @@ interface EditorProps {
initialExt: string;
initTests: (tests: Test[]) => void;
initialTests: Test[];
isMobileLayout: boolean;
isResetting: boolean;
isSignedIn: boolean;
isUsingKeyboardInTablist: boolean;
openHelpModal: () => void;
openResetModal: () => void;
output: string[];
@@ -373,6 +375,7 @@ const Editor = (props: EditorProps): JSX.Element => {
editor: editor.IStandaloneCodeEditor,
monaco: typeof monacoEditor
) => {
const { isMobileLayout, isUsingKeyboardInTablist } = props;
// TODO this should *probably* be set on focus
editorRef.current = editor;
dataRef.current.editor = editor;
@@ -405,11 +408,16 @@ const Editor = (props: EditorProps): JSX.Element => {
editor.updateOptions({
accessibilitySupport: accessibilityMode ? 'on' : 'auto'
});
// Users who are using screen readers should not have to move focus from
// the editor to the description every time they open a challenge.
if (props.canFocus && !accessibilityMode) {
focusIfTargetEditor();
} else focusOnHotkeys();
// Focus should not automatically leave the 'Code' tab when using a keyboard
// to navigate the tablist.
if (!isMobileLayout || !isUsingKeyboardInTablist) {
// Users who are using screen readers should not have to move focus from
// the editor to the description every time they open a challenge.
if (props.canFocus && !accessibilityMode) {
focusIfTargetEditor();
} else focusOnHotkeys();
}
// Removes keybind for intellisense
// Private method - hopefully changes with future version
// ref: https://github.com/microsoft/monaco-editor/issues/102

View File

@@ -14,6 +14,7 @@ interface MobileLayoutProps {
instructions: JSX.Element;
notes: ReactElement;
preview: JSX.Element;
updateUsingKeyboardInTablist: (arg0: boolean) => void;
testOutput: JSX.Element;
videoUrl: string;
usesMultifileEditor: boolean;
@@ -44,6 +45,10 @@ class MobileLayout extends Component<MobileLayoutProps, MobileLayoutState> {
});
};
handleKeyDown = () => this.props.updateUsingKeyboardInTablist(true);
handleClick = () => this.props.updateUsingKeyboardInTablist(false);
render() {
const { currentTab } = this.state;
const {
@@ -74,7 +79,10 @@ class MobileLayout extends Component<MobileLayoutProps, MobileLayoutState> {
activeKey={currentTab}
defaultActiveKey={currentTab}
id='mobile-layout'
onKeyDown={this.handleKeyDown}
onMouseDown={this.handleClick}
onSelect={this.switchTab}
onTouchStart={this.handleClick}
>
{!hasEditableBoundaries && (
<TabPane
@@ -86,6 +94,7 @@ class MobileLayout extends Component<MobileLayoutProps, MobileLayoutState> {
)}
<TabPane
eventKey={Tab.Editor}
tabIndex='0'
title={i18next.t('learn.editor-tabs.code')}
{...editorTabPaneProps}
>

View File

@@ -38,6 +38,8 @@ interface MultifileEditorProps {
initialEditorContent?: string;
initialExt?: string;
initialTests: Test[];
isMobileLayout: boolean;
isUsingKeyboardInTablist: boolean;
output?: string[];
resizeProps: ResizeProps;
title: string;
@@ -77,6 +79,8 @@ const MultifileEditor = (props: MultifileEditorProps) => {
description,
editorRef,
initialTests,
isMobileLayout,
isUsingKeyboardInTablist,
resizeProps,
title,
visibleEditors: { stylescss, indexhtml, scriptjs, indexjsx },
@@ -143,6 +147,8 @@ const MultifileEditor = (props: MultifileEditorProps) => {
editorRef={editorRef}
fileKey={key as FileKey}
initialTests={initialTests}
isMobileLayout={isMobileLayout}
isUsingKeyboardInTablist={isUsingKeyboardInTablist}
resizeProps={resizeProps}
contents={props.contents ?? ''}
dimensions={props.dimensions ?? { height: 0, width: 0 }}

View File

@@ -118,6 +118,7 @@ interface ShowClassicProps {
interface ShowClassicState {
layout: ReflexLayout;
resizing: boolean;
usingKeyboardInTablist: boolean;
}
interface ReflexLayout {
@@ -129,6 +130,11 @@ interface ReflexLayout {
testsPane: { flex: number };
}
interface RenderEditorArgs {
isMobileLayout: boolean;
isUsingKeyboardInTablist: boolean;
}
const REFLEX_LAYOUT = 'challenge-layout';
const BASE_LAYOUT = {
codePane: { flex: 1 },
@@ -167,12 +173,20 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
// layout: Holds the information of the panes sizes for desktop view
this.state = {
layout: this.getLayoutState(),
resizing: false
resizing: false,
usingKeyboardInTablist: false
};
this.containerRef = React.createRef();
this.editorRef = React.createRef();
this.instructionsPanelRef = React.createRef();
this.updateUsingKeyboardInTablist =
this.updateUsingKeyboardInTablist.bind(this);
}
updateUsingKeyboardInTablist(usingKeyboardInTablist: boolean): void {
this.setState({ usingKeyboardInTablist });
}
getLayoutState(): ReflexLayout {
@@ -389,7 +403,7 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
);
}
renderEditor() {
renderEditor({ isMobileLayout, isUsingKeyboardInTablist }: RenderEditorArgs) {
const {
pageContext: {
projectPreview: { showProjectPreview }
@@ -416,6 +430,8 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
this.editorRef as MutableRefObject<editor.IStandaloneCodeEditor>
}
initialTests={tests}
isMobileLayout={isMobileLayout}
isUsingKeyboardInTablist={isUsingKeyboardInTablist}
resizeProps={this.resizeProps}
title={title}
usesMultifileEditor={usesMultifileEditor}
@@ -494,7 +510,10 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
<Helmet title={windowTitle} />
<Media maxWidth={MAX_MOBILE_WIDTH}>
<MobileLayout
editor={this.renderEditor()}
editor={this.renderEditor({
isMobileLayout: true,
isUsingKeyboardInTablist: this.state.usingKeyboardInTablist
})}
guideUrl={getGuideUrl({ forumTopicId, title })}
hasEditableBoundaries={hasEditableBoundaries}
hasNotes={!!notes}
@@ -505,6 +524,8 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
notes={this.renderNotes(notes)}
preview={this.renderPreview()}
testOutput={this.renderTestOutput()}
// eslint-disable-next-line @typescript-eslint/unbound-method
updateUsingKeyboardInTablist={this.updateUsingKeyboardInTablist}
usesMultifileEditor={usesMultifileEditor}
videoUrl={this.getVideoUrl()}
/>
@@ -514,7 +535,10 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
block={block}
challengeFiles={challengeFiles}
challengeType={challengeType}
editor={this.renderEditor()}
editor={this.renderEditor({
isMobileLayout: false,
isUsingKeyboardInTablist: this.state.usingKeyboardInTablist
})}
hasEditableBoundaries={hasEditableBoundaries}
hasNotes={!!notes}
hasPreview={this.hasPreview()}