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:
parent
889a63e382
commit
fc050c64cf
|
@ -1,3 +1,3 @@
|
|||
#polls-panel {
|
||||
.polls-panel {
|
||||
height: calc(100% - 119px);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
] } />
|
||||
|
|
Loading…
Reference in New Issue