From d659447da75bc6518e2d968e5d9ef2a860059464 Mon Sep 17 00:00:00 2001 From: Jeff Largent Date: Tue, 20 Apr 2021 16:09:27 -0400 Subject: [PATCH] Adds K-5 schedule tab jump to navigation button Replaces the hidden duplicate weekly nav toolbar with a separate "Jump to weekly navigation" button that returns focus to the lone navigation toolbar (for better keyboard-only/screenreader usability). fixes LS-2131 flag = canvas_for_elementary Test plan: - As a student enrolled in a K-5 course, go to the dashboard - Switch to the schedule tab - Tab to the bottom of the planner - Expect a hidden button to appear reading "Jump to navigation toolbar" - Press enter, and expect the button to take you back to the "Today" button - Press left or right to switch to one of the next/previous buttons - Tab back to the "Jump to navigation toolbar" button - Press enter, expect focus to jump to whatever button was last active on the toolbar (next/previous) Change-Id: Ic715d64603253412d2b97e961988ff9fcd6e92a5 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/263277 Tested-by: Service Cloud Jenkins Reviewed-by: Robin Kuss QA-Review: Robin Kuss Product-Review: Jeff Largent --- jest/jest-setup.js | 4 ++ .../components/JumpToHeaderButton/index.js | 63 +++++++++++++++++++ .../components/WeeklyPlannerHeader/index.js | 58 ++++++----------- .../components/WeeklyPlannerHeader/styles.css | 4 -- .../canvas-planner/src/components/index.js | 1 + .../react/__tests__/K5Dashboard.test.js | 19 +++++- ui/shared/k5/react/SchedulePage.js | 8 ++- 7 files changed, 110 insertions(+), 47 deletions(-) create mode 100644 packages/canvas-planner/src/components/JumpToHeaderButton/index.js diff --git a/jest/jest-setup.js b/jest/jest-setup.js index a92d4955a25..6545324c49b 100644 --- a/jest/jest-setup.js +++ b/jest/jest-setup.js @@ -135,3 +135,7 @@ if (!('matchMedia' in window)) { }) window.matchMedia._mocked = true } + +if (!('scrollIntoView' in window.HTMLElement.prototype)) { + window.HTMLElement.prototype.scrollIntoView = () => {} +} diff --git a/packages/canvas-planner/src/components/JumpToHeaderButton/index.js b/packages/canvas-planner/src/components/JumpToHeaderButton/index.js new file mode 100644 index 00000000000..2bed4a2a3f1 --- /dev/null +++ b/packages/canvas-planner/src/components/JumpToHeaderButton/index.js @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This file is part of Canvas. + * + * Canvas is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, version 3 of the License. + * + * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License along + * with this program. If not, see . + */ + +import React, {PureComponent} from 'react' +import {Button} from '@instructure/ui-buttons' +import formatMessage from '../../format-message' + +import {WEEKLY_PLANNER_ACTIVE_BTN_ID} from '../WeeklyPlannerHeader' + +export const WEEKLY_PLANNER_JUMP_TO_NAV_BUTTON = 'jump-to-weekly-nav-button' + +export default class JumpToHeaderButton extends PureComponent { + buttonRef = null + + state = {focused: false} + + setFocused = focused => () => { + this.setState({focused}, () => this.buttonRef.scrollIntoView(false)) + } + + focusHeader = () => { + document.getElementById(WEEKLY_PLANNER_ACTIVE_BTN_ID)?.focus() + } + + render = () => ( +
+ +
+ ) +} diff --git a/packages/canvas-planner/src/components/WeeklyPlannerHeader/index.js b/packages/canvas-planner/src/components/WeeklyPlannerHeader/index.js index 8ecd0a7963f..aff8475e2d6 100644 --- a/packages/canvas-planner/src/components/WeeklyPlannerHeader/index.js +++ b/packages/canvas-planner/src/components/WeeklyPlannerHeader/index.js @@ -35,6 +35,8 @@ import {isInMomentRange} from '../../utilities/dateUtils' import theme from './theme' import styles from './styles.css' +export const WEEKLY_PLANNER_ACTIVE_BTN_ID = 'weekly-header-active-button' + // Breaking our encapsulation by reaching outside our dom sub-tree // I suppose we could wire up the event handlers in K5Dashboard.js // and pass the height as a prop to all the pages. Maybe it will be @@ -69,7 +71,6 @@ export class WeeklyPlannerHeader extends Component { loadingError: PropTypes.string }).isRequired, visible: PropTypes.bool, - isFooter: PropTypes.bool, todayMoment: momentObj, weekStartMoment: momentObj, weekEndMoment: momentObj, @@ -88,9 +89,7 @@ export class WeeklyPlannerHeader extends Component { prevEnabled: true, nextEnabled: true, focusedButtonIndex: 1, // start with the today button - buttons: [this.prevButtonRef, this.todayButtonRef, this.nextButtonRef], - focused: false, - activeButton: 0 // -1 for prev, 0 for today, 1 for next + buttons: [this.prevButtonRef, this.todayButtonRef, this.nextButtonRef] } handleStickyOffset = () => { @@ -100,14 +99,14 @@ export class WeeklyPlannerHeader extends Component { handlePrev = () => { this.prevButtonRef.current.focus() this.props.loadPastWeekItems() - this.setState({focusedButtonIndex: 0, activeButton: -1}) + this.setState({focusedButtonIndex: 0}) } handleToday = () => { this.todayButtonRef.current.focus() this.props.loadThisWeekItems() this.setState((state, _props) => { - return {focusedButtonIndex: state.prevEnabled ? 1 : 0, activeButton: 0} + return {focusedButtonIndex: state.prevEnabled ? 1 : 0} }) } @@ -115,7 +114,7 @@ export class WeeklyPlannerHeader extends Component { this.nextButtonRef.current.focus() this.props.loadNextWeekItems({loadMoreButtonClicked: true}) this.setState((state, _props) => { - return {focusedButtonIndex: state.prevEnabled ? 2 : 1, activeButton: 1} + return {focusedButtonIndex: state.prevEnabled ? 2 : 1} }) } @@ -133,14 +132,6 @@ export class WeeklyPlannerHeader extends Component { this.setState({focusedButtonIndex: newFocusedIndex}) } - handleFocus = () => { - this.setState({focused: true}) - } - - handleBlur = () => { - this.setState({focused: false}) - } - updateButtons() { const buttons = [] @@ -187,7 +178,7 @@ export class WeeklyPlannerHeader extends Component { // 2. the window becomes narrow enough for the tabs to wrap. // We need to relocate the WeeklyPlannerHeader so it sticks // to the bottom of the tabs panel. - if (!this.props.isFooter && this.props.visible !== prevProps.visible) { + if (this.props.visible !== prevProps.visible) { if (this.props.visible) { const focusTarget = processFocusTarget() this.handleStickyOffset() @@ -215,7 +206,7 @@ export class WeeklyPlannerHeader extends Component { ) { const buttons = this.updateButtons() - if (!this.props.isFooter && prevState.buttons.length === 3 && buttons.length === 2) { + if (prevState.buttons.length === 3 && buttons.length === 2) { // when prev or next buttons go away, move focus to Today this.todayButtonRef.current.focus() } @@ -237,30 +228,21 @@ export class WeeklyPlannerHeader extends Component { } } + getButtonId(which) { + return this.getButtonTabIndex(which) === 0 ? WEEKLY_PLANNER_ACTIVE_BTN_ID : undefined + } + render() { - let prevButtonId, todayButtonId, nextButtonId - if (!this.props.isFooter) { - prevButtonId = this.state.activeButton === -1 ? 'weekly-header-active-button' : undefined - todayButtonId = this.state.activeButton === 0 ? 'weekly-header-active-button' : undefined - nextButtonId = this.state.activeButton === 1 ? 'weekly-header-active-button' : undefined - } return (