From fc050c64cf65b7c9b69d474e8496acc05709b653 Mon Sep 17 00:00:00 2001 From: Emmanuel Pelletier Date: Thu, 2 Mar 2023 18:27:06 +0100 Subject: [PATCH] fix(a11y/chat sidebar): finish implementation of tabs aria pattern DOM elements in the chat correctly had tabs ARIA pattern attributes, but keyboard behavior wasn't the correct one. Now we fully follow the aria pattern. --- css/_polls.scss | 2 +- .../features/base/ui/components/web/Tabs.tsx | 41 +++++++++++++++---- react/features/chat/components/web/Chat.js | 41 +++++++++++-------- 3 files changed, 58 insertions(+), 26 deletions(-) diff --git a/css/_polls.scss b/css/_polls.scss index 1ee433269..966f7452e 100644 --- a/css/_polls.scss +++ b/css/_polls.scss @@ -1,3 +1,3 @@ -#polls-panel { +.polls-panel { height: calc(100% - 119px); } diff --git a/react/features/base/ui/components/web/Tabs.tsx b/react/features/base/ui/components/web/Tabs.tsx index e198d5cda..323168c22 100644 --- a/react/features/base/ui/components/web/Tabs.tsx +++ b/react/features/base/ui/components/web/Tabs.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { makeStyles } from 'tss-react/mui'; import { isMobileBrowser } from '../../../environment/utils'; @@ -11,6 +11,7 @@ interface ITabProps { selected: string; tabs: Array<{ accessibilityLabel: string; + controlsId: string; countBadge?: number; disabled?: boolean; id: string; @@ -87,26 +88,52 @@ const Tabs = ({ }: ITabProps) => { const { classes, cx } = useStyles(); const isMobile = isMobileBrowser(); - - const handleChange = useCallback((e: React.MouseEvent) => { - onChange(e.currentTarget.id); + const onClick = useCallback(id => () => { + onChange(id); }, []); + const onKeyDown = useCallback((index: number) => (event: React.KeyboardEvent) => { + let newIndex: number | null = null; + + if (event.key === 'ArrowLeft') { + event.preventDefault(); + newIndex = index === 0 ? tabs.length - 1 : index - 1; + } + + if (event.key === 'ArrowRight') { + event.preventDefault(); + newIndex = index === tabs.length - 1 ? 0 : index + 1; + } + + if (newIndex !== null) { + onChange(tabs[newIndex].id); + } + }, [ tabs ]); + + useEffect(() => { + // this test is needed to make sure the effect is triggered because of user actually changing tab + if (document.activeElement?.getAttribute('role') === 'tab') { + document.querySelector(`#${selected}`)?.focus(); + } + }, [ selected ]); return (
- {tabs.map(tab => ( + {tabs.map((tab, index) => ( diff --git a/react/features/chat/components/web/Chat.js b/react/features/chat/components/web/Chat.js index aab8f3365..486e8e227 100644 --- a/react/features/chat/components/web/Chat.js +++ b/react/features/chat/components/web/Chat.js @@ -134,35 +134,38 @@ class Chat extends AbstractChat { _renderChat() { const { _isPollsEnabled, _isPollsTabFocused } = this.props; - if (_isPollsTabFocused) { - return ( - <> - { _isPollsEnabled && this._renderTabs() } -
- -
- - - ); - } - return ( <> { _isPollsEnabled && this._renderTabs() }
+ className = { clsx( + 'chat-panel', + !_isPollsEnabled && 'chat-panel-no-tabs', + _isPollsTabFocused && 'hide' + ) } + id = { `${CHAT_TABS.CHAT}-panel` } + role = 'tabpanel' + tabIndex = { 0 }>
+ { _isPollsEnabled && ( + <> +
+ +
+ + + )} ); } @@ -185,11 +188,13 @@ class Chat extends AbstractChat { accessibilityLabel: t('chat.tabs.chat'), countBadge: _isPollsTabFocused && _nbUnreadMessages > 0 ? _nbUnreadMessages : undefined, id: CHAT_TABS.CHAT, + controlsId: `${CHAT_TABS.CHAT}-panel`, label: t('chat.tabs.chat') }, { accessibilityLabel: t('chat.tabs.polls'), countBadge: !_isPollsTabFocused && _nbUnreadPolls > 0 ? _nbUnreadPolls : undefined, id: CHAT_TABS.POLLS, + controlsId: `${CHAT_TABS.POLLS}-panel`, label: t('chat.tabs.polls') } ] } />