[MM-62973][MM-63028][MM-63034][MM-63027] Various accessibility fixes around Browse Channels modal (#30922)
* [MM-62973] Fix order of Close button and other buttons in the header * [MM-63028] Add aria-live status area when searching for channels * [MM-63027] Add announcement that channel is joined * [MM-63034] Add role=checkbox and reformat HTML to have correct custom checkbox code * Update snapshots from generic_modal change * PR feedback * PR feedback * PR feedback
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
c82a24f396
Коммит
883711c72d
@@ -20,12 +20,12 @@ function verifyNoChannelToJoinMessage(isVisible) {
|
||||
function ensureHideJoinedCheckboxEnabled(shouldBeChecked) {
|
||||
cy.get('#hideJoinedPreferenceCheckbox').then(($checkbox) => {
|
||||
cy.wrap($checkbox).findByText('Hide Joined').should('be.visible');
|
||||
cy.wrap($checkbox).find('button').invoke('attr', 'class').then(($classList) => {
|
||||
cy.wrap($checkbox).find('div.get-app__checkbox').invoke('attr', 'class').then(($classList) => {
|
||||
if ($classList.split(' ').includes('checked') ^ shouldBeChecked) {
|
||||
// We click on the button only when the XOR operands do not match
|
||||
// e.g. checkbox is checked, but should not be checked; and vice-versa
|
||||
cy.wrap($checkbox).click();
|
||||
cy.wrap($checkbox).find('button').should(`${shouldBeChecked ? '' : 'not.'}have.class`, 'checked');
|
||||
cy.wrap($checkbox).find('div.get-app__checkbox').should(`${shouldBeChecked ? '' : 'not.'}have.class`, 'checked');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -120,8 +120,8 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
|
||||
// # Hide already joined channels
|
||||
cy.findByText('Hide Joined').click();
|
||||
|
||||
// # Focus on the Create Channel button and TAB four time
|
||||
cy.get('#createNewChannelButton').focus().tab().tab().tab().tab();
|
||||
// # Focus on the Create Channel button and TAB five time
|
||||
cy.get('#createNewChannelButton').focus().tab().tab().tab().tab().tab();
|
||||
|
||||
// * Verify channel name is highlighted and reader reads the channel name and channel description
|
||||
cy.get('#moreChannelsList').within(() => {
|
||||
|
||||
@@ -121,11 +121,15 @@ exports[`components/SearchableChannelList should match init snapshot 1`] = `
|
||||
/>
|
||||
</Menu>
|
||||
<div
|
||||
aria-checked={false}
|
||||
aria-label="Hide joined channels"
|
||||
id="hideJoinedPreferenceCheckbox"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
role="checkbox"
|
||||
tabIndex={0}
|
||||
>
|
||||
<button
|
||||
aria-label="Hide joined channels checkbox, not checked"
|
||||
<div
|
||||
className="get-app__checkbox"
|
||||
/>
|
||||
<MemoizedFormattedMessage
|
||||
|
||||
@@ -40,22 +40,6 @@ Object {
|
||||
<div
|
||||
class="modal-header"
|
||||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="close"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
>
|
||||
×
|
||||
</span>
|
||||
<span
|
||||
class="sr-only"
|
||||
>
|
||||
Close
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
class="GenericModal__header__text_container"
|
||||
>
|
||||
@@ -87,6 +71,22 @@ Object {
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="close"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
>
|
||||
×
|
||||
</span>
|
||||
<span
|
||||
class="sr-only"
|
||||
>
|
||||
Close
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="modal-body"
|
||||
@@ -522,22 +522,6 @@ Object {
|
||||
<div
|
||||
class="modal-header"
|
||||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="close"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
>
|
||||
×
|
||||
</span>
|
||||
<span
|
||||
class="sr-only"
|
||||
>
|
||||
Close
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
class="GenericModal__header__text_container"
|
||||
>
|
||||
@@ -569,6 +553,22 @@ Object {
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="close"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
>
|
||||
×
|
||||
</span>
|
||||
<span
|
||||
class="sr-only"
|
||||
>
|
||||
Close
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="modal-body"
|
||||
@@ -1164,22 +1164,6 @@ Object {
|
||||
<div
|
||||
class="modal-header"
|
||||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="close"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
>
|
||||
×
|
||||
</span>
|
||||
<span
|
||||
class="sr-only"
|
||||
>
|
||||
Close
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
class="GenericModal__header__text_container"
|
||||
>
|
||||
@@ -1211,6 +1195,22 @@ Object {
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="close"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
>
|
||||
×
|
||||
</span>
|
||||
<span
|
||||
class="sr-only"
|
||||
>
|
||||
Close
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="modal-body"
|
||||
@@ -1525,22 +1525,6 @@ Object {
|
||||
<div
|
||||
class="modal-header"
|
||||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="close"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
>
|
||||
×
|
||||
</span>
|
||||
<span
|
||||
class="sr-only"
|
||||
>
|
||||
Close
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
class="GenericModal__header__text_container"
|
||||
>
|
||||
@@ -1572,6 +1556,22 @@ Object {
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="close"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
>
|
||||
×
|
||||
</span>
|
||||
<span
|
||||
class="sr-only"
|
||||
>
|
||||
Close
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="modal-body"
|
||||
@@ -2105,22 +2105,6 @@ Object {
|
||||
<div
|
||||
class="modal-header"
|
||||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="close"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
>
|
||||
×
|
||||
</span>
|
||||
<span
|
||||
class="sr-only"
|
||||
>
|
||||
Close
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
class="GenericModal__header__text_container"
|
||||
>
|
||||
@@ -2152,6 +2136,22 @@ Object {
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="close"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
>
|
||||
×
|
||||
</span>
|
||||
<span
|
||||
class="sr-only"
|
||||
>
|
||||
Close
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="modal-body"
|
||||
@@ -2685,22 +2685,6 @@ Object {
|
||||
<div
|
||||
class="modal-header"
|
||||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="close"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
>
|
||||
×
|
||||
</span>
|
||||
<span
|
||||
class="sr-only"
|
||||
>
|
||||
Close
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
class="GenericModal__header__text_container"
|
||||
>
|
||||
@@ -2732,6 +2716,22 @@ Object {
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="close"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
>
|
||||
×
|
||||
</span>
|
||||
<span
|
||||
class="sr-only"
|
||||
>
|
||||
Close
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="modal-body"
|
||||
|
||||
@@ -40,7 +40,7 @@ exports[`components/marketplace/ doesn't show web marketplace banner in FeatureF
|
||||
>
|
||||
<ModalHeader
|
||||
bsClass="modal-header"
|
||||
closeButton={true}
|
||||
closeButton={false}
|
||||
closeLabel="Close"
|
||||
>
|
||||
<div
|
||||
@@ -57,6 +57,26 @@ exports[`components/marketplace/ doesn't show web marketplace banner in FeatureF
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Close"
|
||||
className="close"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
>
|
||||
×
|
||||
</span>
|
||||
<span
|
||||
className="sr-only"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Close"
|
||||
id="generic_modal.close"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</ModalHeader>
|
||||
<ModalBody
|
||||
bsClass="modal-body"
|
||||
@@ -132,7 +152,7 @@ exports[`components/marketplace/ hides search, shows web marketplace banner in F
|
||||
>
|
||||
<ModalHeader
|
||||
bsClass="modal-header"
|
||||
closeButton={true}
|
||||
closeButton={false}
|
||||
closeLabel="Close"
|
||||
>
|
||||
<div
|
||||
@@ -149,6 +169,26 @@ exports[`components/marketplace/ hides search, shows web marketplace banner in F
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Close"
|
||||
className="close"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
>
|
||||
×
|
||||
</span>
|
||||
<span
|
||||
className="sr-only"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Close"
|
||||
id="generic_modal.close"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</ModalHeader>
|
||||
<ModalBody
|
||||
bsClass="modal-body"
|
||||
@@ -225,7 +265,7 @@ exports[`components/marketplace/ should render default 1`] = `
|
||||
>
|
||||
<ModalHeader
|
||||
bsClass="modal-header"
|
||||
closeButton={true}
|
||||
closeButton={false}
|
||||
closeLabel="Close"
|
||||
>
|
||||
<div
|
||||
@@ -262,6 +302,26 @@ exports[`components/marketplace/ should render default 1`] = `
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Close"
|
||||
className="close"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
>
|
||||
×
|
||||
</span>
|
||||
<span
|
||||
className="sr-only"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Close"
|
||||
id="generic_modal.close"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</ModalHeader>
|
||||
<ModalBody
|
||||
bsClass="modal-body"
|
||||
@@ -369,7 +429,7 @@ exports[`components/marketplace/ should render with error banner 1`] = `
|
||||
>
|
||||
<ModalHeader
|
||||
bsClass="modal-header"
|
||||
closeButton={true}
|
||||
closeButton={false}
|
||||
closeLabel="Close"
|
||||
>
|
||||
<div
|
||||
@@ -406,6 +466,26 @@ exports[`components/marketplace/ should render with error banner 1`] = `
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Close"
|
||||
className="close"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
>
|
||||
×
|
||||
</span>
|
||||
<span
|
||||
className="sr-only"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Close"
|
||||
id="generic_modal.close"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</ModalHeader>
|
||||
<ModalBody
|
||||
bsClass="modal-body"
|
||||
@@ -530,7 +610,7 @@ exports[`components/marketplace/ should render with no plugins available 1`] = `
|
||||
>
|
||||
<ModalHeader
|
||||
bsClass="modal-header"
|
||||
closeButton={true}
|
||||
closeButton={false}
|
||||
closeLabel="Close"
|
||||
>
|
||||
<div
|
||||
@@ -567,6 +647,26 @@ exports[`components/marketplace/ should render with no plugins available 1`] = `
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Close"
|
||||
className="close"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
>
|
||||
×
|
||||
</span>
|
||||
<span
|
||||
className="sr-only"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Close"
|
||||
id="generic_modal.close"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</ModalHeader>
|
||||
<ModalBody
|
||||
bsClass="modal-body"
|
||||
@@ -682,7 +782,7 @@ exports[`components/marketplace/ should render with plugins available 1`] = `
|
||||
>
|
||||
<ModalHeader
|
||||
bsClass="modal-header"
|
||||
closeButton={true}
|
||||
closeButton={false}
|
||||
closeLabel="Close"
|
||||
>
|
||||
<div
|
||||
@@ -719,6 +819,26 @@ exports[`components/marketplace/ should render with plugins available 1`] = `
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Close"
|
||||
className="close"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
>
|
||||
×
|
||||
</span>
|
||||
<span
|
||||
className="sr-only"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Close"
|
||||
id="generic_modal.close"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</ModalHeader>
|
||||
<ModalBody
|
||||
bsClass="modal-body"
|
||||
@@ -852,7 +972,7 @@ exports[`components/marketplace/ should render with plugins installed 1`] = `
|
||||
>
|
||||
<ModalHeader
|
||||
bsClass="modal-header"
|
||||
closeButton={true}
|
||||
closeButton={false}
|
||||
closeLabel="Close"
|
||||
>
|
||||
<div
|
||||
@@ -889,6 +1009,26 @@ exports[`components/marketplace/ should render with plugins installed 1`] = `
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Close"
|
||||
className="close"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
>
|
||||
×
|
||||
</span>
|
||||
<span
|
||||
className="sr-only"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Close"
|
||||
id="generic_modal.close"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</ModalHeader>
|
||||
<ModalBody
|
||||
bsClass="modal-body"
|
||||
|
||||
@@ -223,6 +223,20 @@ export class SearchableChannelList extends React.PureComponent<Props, State> {
|
||||
</div>
|
||||
<div className='more-modal__actions'>
|
||||
{joinViewChannelButton}
|
||||
{this.state.joiningChannel === channel.id && !this.isMemberOfChannel(channel.id) && (
|
||||
<span
|
||||
className='sr-only'
|
||||
role='alert'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='more_channels.joinedChannel'
|
||||
defaultMessage='Joined channel {channelName}'
|
||||
values={{
|
||||
channelName: channel.display_name,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -530,13 +544,20 @@ export class SearchableChannelList extends React.PureComponent<Props, State> {
|
||||
<div
|
||||
id={'hideJoinedPreferenceCheckbox'}
|
||||
onClick={this.handleChecked}
|
||||
onKeyDown={(e) => {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
this.handleChecked();
|
||||
}
|
||||
}}
|
||||
role='checkbox'
|
||||
aria-checked={this.props.rememberHideJoinedChannelsChecked}
|
||||
aria-label={this.props.intl.formatMessage({id: 'more_channels.hide_joined_channels', defaultMessage: 'Hide joined channels'})}
|
||||
tabIndex={0}
|
||||
>
|
||||
<button
|
||||
className={hideJoinedButtonClass}
|
||||
aria-label={this.props.rememberHideJoinedChannelsChecked ? this.props.intl.formatMessage({id: 'more_channels.hide_joined_checked', defaultMessage: 'Hide joined channels checkbox, checked'}) : this.props.intl.formatMessage({id: 'more_channels.hide_joined_not_checked', defaultMessage: 'Hide joined channels checkbox, not checked'})}
|
||||
>
|
||||
<div className={hideJoinedButtonClass}>
|
||||
{this.props.rememberHideJoinedChannelsChecked ? <CheckboxCheckedIcon/> : null}
|
||||
</button>
|
||||
</div>
|
||||
<FormattedMessage
|
||||
id='more_channels.hide_joined'
|
||||
defaultMessage='Hide Joined'
|
||||
@@ -558,6 +579,15 @@ export class SearchableChannelList extends React.PureComponent<Props, State> {
|
||||
const dropDownContainer = (
|
||||
<div className='more-modal__dropdown'>
|
||||
<span id='channelCountLabel'>{channelCountLabel}</span>
|
||||
{this.props.isSearch &&
|
||||
<span
|
||||
className='sr-only'
|
||||
role='status'
|
||||
aria-live='polite'
|
||||
>
|
||||
{channelCountLabel}
|
||||
</span>
|
||||
}
|
||||
<div id='modalPreferenceContainer'>
|
||||
{channelDropdown}
|
||||
{hideJoinedPreferenceCheckbox}
|
||||
|
||||
@@ -4499,9 +4499,9 @@
|
||||
"more_channels.count_zero": "0 Results",
|
||||
"more_channels.create": "Create New Channel",
|
||||
"more_channels.hide_joined": "Hide Joined",
|
||||
"more_channels.hide_joined_checked": "Hide joined channels checkbox, checked",
|
||||
"more_channels.hide_joined_not_checked": "Hide joined channels checkbox, not checked",
|
||||
"more_channels.hide_joined_channels": "Hide joined channels",
|
||||
"more_channels.joined": "Joined",
|
||||
"more_channels.joinedChannel": "Joined channel {channelName}",
|
||||
"more_channels.membership_indicator": "Membership Indicator: Joined",
|
||||
"more_channels.next": "Next",
|
||||
"more_channels.noArchived": "No archived channels",
|
||||
|
||||
@@ -39,6 +39,9 @@ exports[`GenericModal should match snapshot for base case 1`] = `
|
||||
<div
|
||||
class="modal-header"
|
||||
>
|
||||
<div
|
||||
class="GenericModal__header__text_container"
|
||||
/>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="close"
|
||||
@@ -55,9 +58,6 @@ exports[`GenericModal should match snapshot for base case 1`] = `
|
||||
Close
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
class="GenericModal__header__text_container"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="modal-body"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import classNames from 'classnames';
|
||||
import React, {useState, useEffect, useCallback, useRef} from 'react';
|
||||
import {Modal} from 'react-bootstrap';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {FormattedMessage, useIntl} from 'react-intl';
|
||||
|
||||
import {useFocusTrap} from '../hooks/useFocusTrap';
|
||||
import './generic_modal.scss';
|
||||
@@ -128,6 +128,8 @@ export const GenericModal: React.FC<Props> = ({
|
||||
dataTestId,
|
||||
delayFocusTrap,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
// Create a ref for the modal container
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -290,7 +292,7 @@ export const GenericModal: React.FC<Props> = ({
|
||||
className='GenericModal__wrapper GenericModal__wrapper-enter-key-press-catcher'
|
||||
>
|
||||
{showHeader && (
|
||||
<Modal.Header closeButton={showCloseButton}>
|
||||
<Modal.Header closeButton={false}>
|
||||
<div className='GenericModal__header__text_container'>
|
||||
{compassDesign && (
|
||||
<>
|
||||
@@ -306,6 +308,19 @@ export const GenericModal: React.FC<Props> = ({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{showCloseButton && (
|
||||
<button
|
||||
type='button'
|
||||
className='close'
|
||||
onClick={onHideCallback}
|
||||
aria-label={intl.formatMessage({id: 'generic_modal.close', defaultMessage: 'Close'})}
|
||||
>
|
||||
<span aria-hidden='true'>{'×'}</span>
|
||||
<span className='sr-only'>
|
||||
<FormattedMessage id='generic_modal.close' defaultMessage='Close' />
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</Modal.Header>
|
||||
)}
|
||||
<Modal.Body className={classNames({divider: bodyDivider, 'overflow-visible': bodyOverflowVisible})}>
|
||||
|
||||
Ссылка в новой задаче
Block a user