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.
This commit is contained in:
Emmanuel Pelletier 2023-03-02 18:27:06 +01:00
parent 889a63e382
commit fc050c64cf
3 changed files with 58 additions and 26 deletions

View File

@ -1,3 +1,3 @@
#polls-panel {
.polls-panel {
height: calc(100% - 119px);
}

View File

@ -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<HTMLButtonElement>) => {
onChange(e.currentTarget.id);
const onClick = useCallback(id => () => {
onChange(id);
}, []);
const onKeyDown = useCallback((index: number) => (event: React.KeyboardEvent<HTMLButtonElement>) => {
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<HTMLButtonElement>(`#${selected}`)?.focus();
}
}, [ selected ]);
return (
<div
aria-label = { accessibilityLabel }
className = { cx(classes.container, className) }
role = 'tablist'>
{tabs.map(tab => (
{tabs.map((tab, index) => (
<button
aria-controls = { tab.controlsId }
aria-label = { tab.accessibilityLabel }
aria-selected = { selected === tab.id }
className = { cx(classes.tab, selected === tab.id && 'selected', isMobile && 'is-mobile') }
disabled = { tab.disabled }
id = { tab.id }
key = { tab.id }
onClick = { handleChange }
role = 'tab'>
onClick = { onClick(tab.id) }
onKeyDown = { onKeyDown(index) }
role = 'tab'
tabIndex = { selected === tab.id ? undefined : -1 }>
{tab.label}
{tab.countBadge && <span className = { classes.badge }>{tab.countBadge}</span>}
</button>

View File

@ -134,35 +134,38 @@ class Chat extends AbstractChat<Props> {
_renderChat() {
const { _isPollsEnabled, _isPollsTabFocused } = this.props;
if (_isPollsTabFocused) {
return (
<>
{ _isPollsEnabled && this._renderTabs() }
<div
aria-labelledby = { CHAT_TABS.POLLS }
id = 'polls-panel'
role = 'tabpanel'>
<PollsPane />
</div>
<KeyboardAvoider />
</>
);
}
return (
<>
{ _isPollsEnabled && this._renderTabs() }
<div
aria-labelledby = { CHAT_TABS.CHAT }
className = { clsx('chat-panel', !_isPollsEnabled && 'chat-panel-no-tabs') }
id = 'chat-panel'
role = 'tabpanel'>
className = { clsx(
'chat-panel',
!_isPollsEnabled && 'chat-panel-no-tabs',
_isPollsTabFocused && 'hide'
) }
id = { `${CHAT_TABS.CHAT}-panel` }
role = 'tabpanel'
tabIndex = { 0 }>
<MessageContainer
messages = { this.props._messages } />
<MessageRecipient />
<ChatInput
onSend = { this._onSendMessage } />
</div>
{ _isPollsEnabled && (
<>
<div
aria-labelledby = { CHAT_TABS.POLLS }
className = { clsx('polls-panel', !_isPollsTabFocused && 'hide') }
id = { `${CHAT_TABS.POLLS}-panel` }
role = 'tabpanel'
tabIndex = { 0 }>
<PollsPane />
</div>
<KeyboardAvoider />
</>
)}
</>
);
}
@ -185,11 +188,13 @@ class Chat extends AbstractChat<Props> {
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')
}
] } />