feat(a11y) add headings across the app for easier screen reader nav (#12427)

feat(a11y): added headings across the app for easier screen reader nav
This commit is contained in:
Emmanuel Pelletier 2023-02-28 15:52:06 +01:00 committed by GitHub
parent fed74afffe
commit 72dd609247
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 73 additions and 8 deletions

View File

@ -452,6 +452,11 @@
"veryBad": "Very Bad", "veryBad": "Very Bad",
"veryGood": "Very Good" "veryGood": "Very Good"
}, },
"filmstrip": {
"accessibilityLabel": {
"heading": "Video thumbnails"
}
},
"giphy": { "giphy": {
"noResults": "No results found :(", "noResults": "No results found :(",
"search": "Search GIPHY" "search": "Search GIPHY"
@ -1097,6 +1102,7 @@
"giphy": "Toggle GIPHY menu", "giphy": "Toggle GIPHY menu",
"grantModerator": "Grant Moderator Rights", "grantModerator": "Grant Moderator Rights",
"hangup": "Leave the meeting", "hangup": "Leave the meeting",
"heading": "Toolbar",
"help": "Help", "help": "Help",
"invite": "Invite people", "invite": "Invite people",
"kick": "Kick participant", "kick": "Kick participant",
@ -1393,5 +1399,10 @@
"terms": "Terms", "terms": "Terms",
"title": "Secure, fully featured, and completely free video conferencing", "title": "Secure, fully featured, and completely free video conferencing",
"upcomingMeetings": "Your upcoming meetings" "upcomingMeetings": "Your upcoming meetings"
},
"whiteboard": {
"accessibilityLabel": {
"heading": "Whiteboard"
}
} }
} }

View File

@ -46,9 +46,12 @@ function Header({ onCancel, className, isPollsEnabled, t }: Props) {
return ( return (
<div <div
className = { className || 'chat-dialog-header' } className = { className || 'chat-dialog-header' }>
role = 'heading'> <span
{ t(isPollsEnabled ? 'chat.titleWithPolls' : 'chat.title') } aria-level = { 1 }
role = 'heading'>
{ t(isPollsEnabled ? 'chat.titleWithPolls' : 'chat.title') }
</span>
<Icon <Icon
ariaLabel = { t('toolbar.closeChat') } ariaLabel = { t('toolbar.closeChat') }
onClick = { onCancel } onClick = { onCancel }

View File

@ -210,7 +210,8 @@ class Conference extends AbstractConference<Props, *> {
_notificationsVisible, _notificationsVisible,
_overflowDrawer, _overflowDrawer,
_showLobby, _showLobby,
_showPrejoin _showPrejoin,
t
} = this.props; } = this.props;
return ( return (
@ -240,7 +241,17 @@ class Conference extends AbstractConference<Props, *> {
} }
</div> </div>
{ _showPrejoin || _showLobby || <Toolbox /> } { _showPrejoin || _showLobby || (
<>
<span
aria-level = { 1 }
className = 'sr-only'
role = 'heading'>
{ t('toolbar.accessibilityLabel.heading') }
</span>
<Toolbox />
</>
)}
{_notificationsVisible && !_isAnyOverlayVisible && (_overflowDrawer {_notificationsVisible && !_isAnyOverlayVisible && (_overflowDrawer
? <JitsiPortal className = 'notification-portal'> ? <JitsiPortal className = 'notification-portal'>

View File

@ -343,7 +343,8 @@ class Filmstrip extends PureComponent <IProps, IState> {
_verticalViewGrid, _verticalViewGrid,
_verticalViewMaxWidth, _verticalViewMaxWidth,
classes, classes,
filmstripType filmstripType,
t
} = this.props; } = this.props;
const { isMouseDown } = this.state; const { isMouseDown } = this.state;
const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW; const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW;
@ -434,6 +435,12 @@ class Filmstrip extends PureComponent <IProps, IState> {
_verticalViewGrid && 'no-vertical-padding', _verticalViewGrid && 'no-vertical-padding',
_verticalViewBackground && classes.filmstripBackground) } _verticalViewBackground && classes.filmstripBackground) }
style = { filmstripStyle }> style = { filmstripStyle }>
<span
aria-level = { 1 }
className = 'sr-only'
role = 'heading'>
{ t('filmstrip.accessibilityLabel.heading') }
</span>
{ toolbar } { toolbar }
{_resizableFilmstrip {_resizableFilmstrip
? <div ? <div

View File

@ -112,6 +112,12 @@ function MeetingParticipants({
return ( return (
<> <>
<span
aria-level = { 1 }
className = 'sr-only'
role = 'heading'>
{ t('participantsPane.title') }
</span>
<div className = { cx(styles.heading, styles.headingW) }> <div className = { cx(styles.heading, styles.headingW) }>
{visitorsCount && visitorsCount > 0 {visitorsCount && visitorsCount > 0
&& t('participantsPane.headings.visitors', { count: visitorsCount })} && t('participantsPane.headings.visitors', { count: visitorsCount })}

View File

@ -6,6 +6,7 @@ import { useSelector } from 'react-redux';
// @ts-expect-error // @ts-expect-error
import Filmstrip from '../../../../../modules/UI/videolayout/Filmstrip'; import Filmstrip from '../../../../../modules/UI/videolayout/Filmstrip';
import { IReduxState } from '../../../app/types'; import { IReduxState } from '../../../app/types';
import { translate } from '../../../base/i18n/functions';
import { getLocalParticipant } from '../../../base/participants/functions'; import { getLocalParticipant } from '../../../base/participants/functions';
import { getVerticalViewMaxWidth } from '../../../filmstrip/functions.web'; import { getVerticalViewMaxWidth } from '../../../filmstrip/functions.web';
import { getToolboxHeight } from '../../../toolbox/functions.web'; import { getToolboxHeight } from '../../../toolbox/functions.web';
@ -32,12 +33,24 @@ interface IDimensions {
width: string; width: string;
} }
/**
* The type of the React {@link Component} props of {@link Whiteboard}.
*/
type Props = {
/**
* Invoked to obtain translated strings.
*/
t: Function;
};
/** /**
* The Whiteboard component. * The Whiteboard component.
* *
* @param {Props} props - The React props passed to this component.
* @returns {JSX.Element} - The React component. * @returns {JSX.Element} - The React component.
*/ */
const Whiteboard: () => JSX.Element = () => { const Whiteboard = (props: Props): JSX.Element => {
const excalidrawRef = useRef<any>(null); const excalidrawRef = useRef<any>(null);
const collabAPIRef = useRef<any>(null); const collabAPIRef = useRef<any>(null);
@ -113,6 +126,20 @@ const Whiteboard: () => JSX.Element = () => {
{ {
isOpen && ( isOpen && (
<div className = 'excalidraw-wrapper'> <div className = 'excalidraw-wrapper'>
{/*
* Excalidraw renders a few lvl 2 headings. This is
* quite fortunate, because we actually use lvl 1
* headings to mark the big sections of our app. So make
* sure to mark the Excalidraw context with a lvl 1
* heading before showing the whiteboard.
*/
<span
aria-level = { 1 }
className = 'sr-only'
role = 'heading'>
{ props.t('whiteboard.accessibilityLabel.heading') }
</span>
}
<ExcalidrawApp <ExcalidrawApp
collabDetails = { collabDetails } collabDetails = { collabDetails }
collabServerUrl = { collabServerUrl } collabServerUrl = { collabServerUrl }
@ -132,4 +159,4 @@ const Whiteboard: () => JSX.Element = () => {
); );
}; };
export default Whiteboard; export default translate(Whiteboard);