[MM-61588]: Fixed the keyboard interactivity of tablist and added appropriate ARIA properties (#29592)

* [MA-9]: Fixed the keyboard interactivity of tablist and added appropriate ARIA properties

* [MA-9]: Review Fixes: Added aria-level attribute to plugin preferences heading

* [MA-9]: Fixed failing e2e test case

* [MA-9]: Fixed styling in mobile view and fixed failing e2e test cases

* [MA-9]: Minor Refactoring

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
ayush-chauhan233
2025-01-21 02:55:11 +05:30
коммит произвёл GitHub
родитель ea3be1a9f2
Коммит 5fbd6e1139
19 изменённых файлов: 232 добавлений и 140 удалений

Просмотреть файл

@@ -212,7 +212,7 @@ describe('Verify Accessibility Support in different sections in Settings and Pro
// * Verify image alt in profile image
cy.get('.profile-img').should('have.attr', 'alt', 'profile image');
cy.get('#generalSettings').then((el) => {
cy.get('#profileSettings').then((el) => {
if (el.find('.profile-img__remove').length > 0) {
cy.findByTestId('removeSettingPicture').click();
cy.uiSave();

Просмотреть файл

@@ -15,7 +15,7 @@ export default class SettingsModal {
this.container = container;
this.notificationsSettingsTab = container.locator('#notificationsButton');
this.notificationsSettings = new NotificationsSettings(container.locator('#notificationSettings'));
this.notificationsSettings = new NotificationsSettings(container.locator('#notificationsSettings'));
}
async toBeVisible() {

Просмотреть файл

@@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import classNames from 'classnames';
import React from 'react';
import type {RefObject} from 'react';
import {FormattedMessage} from 'react-intl';
@@ -26,10 +27,12 @@ export type Props = {
export default class SettingsSidebar extends React.PureComponent<Props> {
buttonRefs: Array<RefObject<HTMLButtonElement>>;
totalTabs: Tab[];
constructor(props: Props) {
super(props);
this.buttonRefs = this.props.tabs.map(() => React.createRef());
this.totalTabs = [...this.props.tabs, ...this.props.pluginTabs || []];
this.buttonRefs = this.totalTabs.map(() => React.createRef());
}
public handleClick = (tab: Tab, e: React.MouseEvent) => {
@@ -41,13 +44,19 @@ export default class SettingsSidebar extends React.PureComponent<Props> {
public handleKeyUp = (index: number, e: React.KeyboardEvent) => {
if (isKeyPressed(e, Constants.KeyCodes.UP)) {
if (index > 0) {
this.props.updateTab(this.props.tabs[index - 1].name);
this.props.updateTab(this.totalTabs[index - 1].name);
a11yFocus(this.buttonRefs[index - 1].current);
} else {
this.props.updateTab(this.totalTabs[this.totalTabs.length - 1].name);
a11yFocus(this.buttonRefs[this.buttonRefs.length - 1].current);
}
} else if (isKeyPressed(e, Constants.KeyCodes.DOWN)) {
if (index < this.props.tabs.length - 1) {
this.props.updateTab(this.props.tabs[index + 1].name);
if (index < this.totalTabs.length - 1) {
this.props.updateTab(this.totalTabs[index + 1].name);
a11yFocus(this.buttonRefs[index + 1].current);
} else {
this.props.updateTab(this.totalTabs[0].name);
a11yFocus(this.buttonRefs[0].current);
}
}
};
@@ -55,10 +64,6 @@ export default class SettingsSidebar extends React.PureComponent<Props> {
private renderTab(tab: Tab, index: number) {
const key = `${tab.name}_li`;
const isActive = this.props.activeTab === tab.name;
let className = '';
if (isActive) {
className = 'active';
}
let icon;
if (typeof tab.icon === 'string') {
@@ -79,27 +84,22 @@ export default class SettingsSidebar extends React.PureComponent<Props> {
}
return (
<li
id={`${tab.name}Li`}
<button
key={key}
className={className}
role='presentation'
ref={this.buttonRefs[index]}
id={`${tab.name}Button`}
className={classNames('cursor--pointer style--none nav-pills__tab', {active: isActive})}
onClick={this.handleClick.bind(null, tab)}
onKeyUp={this.handleKeyUp.bind(null, index)}
aria-label={tab.uiName.toLowerCase()}
role='tab'
aria-selected={isActive}
tabIndex={!isActive && !this.props.isMobileView ? -1 : 0}
aria-controls={`${tab.name}Settings`}
>
<button
ref={this.buttonRefs[index]}
id={`${tab.name}Button`}
className='cursor--pointer style--none'
onClick={this.handleClick.bind(null, tab)}
onKeyUp={this.handleKeyUp.bind(null, index)}
aria-label={tab.uiName.toLowerCase()}
role='tab'
aria-selected={isActive}
tabIndex={!isActive && !this.props.isMobileView ? -1 : 0}
>
{icon}
{tab.uiName}
</button>
</li>
{icon}
{tab.uiName}
</button>
);
}
@@ -110,32 +110,39 @@ export default class SettingsSidebar extends React.PureComponent<Props> {
pluginTabList = (
<>
<hr/>
<li
key={'plugin preferences heading'}
role='heading'
className={'header'}
<div
role='group'
aria-labelledby='userSettingsModal.pluginPreferences.header'
>
<FormattedMessage
id={'userSettingsModal.pluginPreferences.header'}
defaultMessage={'PLUGIN PREFERENCES'}
/>
</li>
{this.props.pluginTabs.map((tab, index) => this.renderTab(tab, index))}
<div
key={'plugin preferences heading'}
role='heading'
className={'header'}
aria-level={3}
id='userSettingsModal_pluginPreferences_header'
>
<FormattedMessage
id={'userSettingsModal.pluginPreferences.header'}
defaultMessage={'PLUGIN PREFERENCES'}
/>
</div>
{this.props.pluginTabs.map((tab, index) => this.renderTab(tab, index + this.props.tabs.length))}
</div>
</>
);
}
return (
<div>
<ul
id='tabList'
className='nav nav-pills nav-stacked'
role='tablist'
aria-orientation='vertical'
>
<div
id='tabList'
className='nav nav-pills nav-stacked'
role='tablist'
aria-orientation='vertical'
>
<div role='group'>
{tabList}
{pluginTabList}
</ul>
</div>
{pluginTabList}
</div>
);
}

Просмотреть файл

@@ -123,7 +123,12 @@ const AccessTab = ({closeModal, collapseModal, hasChangeTabError, hasChanges, se
<span>{formatMessage({id: 'team_settings_modal.title', defaultMessage: 'Team Settings'})}</span>
</h4>
</div>
<div className='modal-access-tab-content user-settings'>
<div
className='modal-access-tab-content user-settings'
id='accessSettings'
aria-labelledby='accessButton'
role='tabpanel'
>
{team.group_constrained ?
undefined :
<AllowedDomainsSelect

Просмотреть файл

@@ -198,7 +198,12 @@ const InfoTab = ({team, hasChanges, maxFileSize, closeModal, collapseModal, hasC
<span>{formatMessage({id: 'team_settings_modal.title', defaultMessage: 'Team Settings'})}</span>
</h4>
</div>
<div className='modal-info-tab-content user-settings' >
<div
className='modal-info-tab-content user-settings'
id='infoSettings'
aria-labelledby='infoButton'
role='tabpanel'
>
<div className='name-description-container' >
<TeamNameSection
name={name}

Просмотреть файл

@@ -738,7 +738,11 @@ export default class AdvancedSettingsDisplay extends React.PureComponent<Props,
}
return (
<div>
<div
id='advancedSettings'
aria-labelledby='advancedButton'
role='tabpanel'
>
<SettingMobileHeader
closeModal={this.props.closeModal}
collapseModal={this.props.collapseModal}

Просмотреть файл

@@ -2,7 +2,9 @@
exports[`components/user_settings/display/UserSettingsDisplay should match snapshot, channel display mode section 1`] = `
<div
aria-labelledby="displayButton"
id="displaySettings"
role="tabpanel"
>
<SettingMobileHeader
closeModal={[MockFunction]}
@@ -355,7 +357,9 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
exports[`components/user_settings/display/UserSettingsDisplay should match snapshot, clickToReply section 1`] = `
<div
aria-labelledby="displayButton"
id="displaySettings"
role="tabpanel"
>
<SettingMobileHeader
closeModal={[MockFunction]}
@@ -708,7 +712,9 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
exports[`components/user_settings/display/UserSettingsDisplay should match snapshot, clock section 1`] = `
<div
aria-labelledby="displayButton"
id="displaySettings"
role="tabpanel"
>
<SettingMobileHeader
closeModal={[MockFunction]}
@@ -1061,7 +1067,9 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
exports[`components/user_settings/display/UserSettingsDisplay should match snapshot, collapse section 1`] = `
<div
aria-labelledby="displayButton"
id="displaySettings"
role="tabpanel"
>
<SettingMobileHeader
closeModal={[MockFunction]}
@@ -1414,7 +1422,9 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
exports[`components/user_settings/display/UserSettingsDisplay should match snapshot, languages section 1`] = `
<div
aria-labelledby="displayButton"
id="displaySettings"
role="tabpanel"
>
<SettingMobileHeader
closeModal={[MockFunction]}
@@ -1694,7 +1704,9 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
exports[`components/user_settings/display/UserSettingsDisplay should match snapshot, link preview section with EnableLinkPreviews is false 1`] = `
<div
aria-labelledby="displayButton"
id="displaySettings"
role="tabpanel"
>
<SettingMobileHeader
closeModal={[MockFunction]}
@@ -1956,7 +1968,9 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
exports[`components/user_settings/display/UserSettingsDisplay should match snapshot, link preview section with EnableLinkPreviews is true 1`] = `
<div
aria-labelledby="displayButton"
id="displaySettings"
role="tabpanel"
>
<SettingMobileHeader
closeModal={[MockFunction]}
@@ -2309,7 +2323,9 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
exports[`components/user_settings/display/UserSettingsDisplay should match snapshot, message display section 1`] = `
<div
aria-labelledby="displayButton"
id="displaySettings"
role="tabpanel"
>
<SettingMobileHeader
closeModal={[MockFunction]}
@@ -2680,7 +2696,9 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
exports[`components/user_settings/display/UserSettingsDisplay should match snapshot, no active section 1`] = `
<div
aria-labelledby="displayButton"
id="displaySettings"
role="tabpanel"
>
<SettingMobileHeader
closeModal={[MockFunction]}
@@ -2960,7 +2978,9 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
exports[`components/user_settings/display/UserSettingsDisplay should match snapshot, teammate name display section 1`] = `
<div
aria-labelledby="displayButton"
id="displaySettings"
role="tabpanel"
>
<SettingMobileHeader
closeModal={[MockFunction]}
@@ -3240,7 +3260,9 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
exports[`components/user_settings/display/UserSettingsDisplay should match snapshot, theme section with EnableThemeSelection is false 1`] = `
<div
aria-labelledby="displayButton"
id="displaySettings"
role="tabpanel"
>
<SettingMobileHeader
closeModal={[MockFunction]}
@@ -3520,7 +3542,9 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
exports[`components/user_settings/display/UserSettingsDisplay should match snapshot, theme section with EnableThemeSelection is true 1`] = `
<div
aria-labelledby="displayButton"
id="displaySettings"
role="tabpanel"
>
<SettingMobileHeader
closeModal={[MockFunction]}
@@ -3812,7 +3836,9 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
exports[`components/user_settings/display/UserSettingsDisplay should match snapshot, timezone section 1`] = `
<div
aria-labelledby="displayButton"
id="displaySettings"
role="tabpanel"
>
<SettingMobileHeader
closeModal={[MockFunction]}
@@ -4092,7 +4118,9 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
exports[`components/user_settings/display/UserSettingsDisplay should not show last active section 1`] = `
<div
aria-labelledby="displayButton"
id="displaySettings"
role="tabpanel"
>
<SettingMobileHeader
closeModal={[MockFunction]}

Просмотреть файл

@@ -1148,7 +1148,11 @@ export default class UserSettingsDisplay extends React.PureComponent<Props, Stat
}
return (
<div id='displaySettings'>
<div
id='displaySettings'
aria-labelledby='displayButton'
role='tabpanel'
>
<SettingMobileHeader
closeModal={this.props.closeModal}
collapseModal={this.props.collapseModal}

Просмотреть файл

@@ -1537,7 +1537,11 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
const pictureSection = this.createPictureSection();
return (
<div id='generalSettings'>
<div
id='profileSettings'
aria-labelledby='profileButton'
role='tabpanel'
>
<SettingMobileHeader
closeModal={this.props.closeModal}
collapseModal={this.props.collapseModal}

Просмотреть файл

@@ -6,7 +6,9 @@ Object {
"baseElement": <body>
<div>
<div
id="notificationSettings"
aria-labelledby="notificationsButton"
id="notificationsSettings"
role="tabpanel"
>
<div
class="modal-header"
@@ -310,7 +312,9 @@ Object {
</body>,
"container": <div>
<div
id="notificationSettings"
aria-labelledby="notificationsButton"
id="notificationsSettings"
role="tabpanel"
>
<div
class="modal-header"
@@ -673,7 +677,9 @@ Object {
"baseElement": <body>
<div>
<div
id="notificationSettings"
aria-labelledby="notificationsButton"
id="notificationsSettings"
role="tabpanel"
>
<div
class="modal-header"
@@ -980,7 +986,9 @@ Object {
</body>,
"container": <div>
<div
id="notificationSettings"
aria-labelledby="notificationsButton"
id="notificationsSettings"
role="tabpanel"
>
<div
class="modal-header"
@@ -1346,7 +1354,9 @@ Object {
"baseElement": <body>
<div>
<div
id="notificationSettings"
aria-labelledby="notificationsButton"
id="notificationsSettings"
role="tabpanel"
>
<div
class="modal-header"
@@ -1615,7 +1625,9 @@ Object {
</body>,
"container": <div>
<div
id="notificationSettings"
aria-labelledby="notificationsButton"
id="notificationsSettings"
role="tabpanel"
>
<div
class="modal-header"

Просмотреть файл

@@ -980,7 +980,11 @@ class NotificationsTab extends React.PureComponent<Props, State> {
const areAllSectionsInactive = this.props.activeSection === '';
return (
<div id='notificationSettings'>
<div
id='notificationsSettings'
aria-labelledby='notificationsButton'
role='tabpanel'
>
<SettingMobileHeader
closeModal={this.props.closeModal}
collapseModal={this.props.collapseModal}

Просмотреть файл

@@ -1,7 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`plugin tab all props are properly passed to the children 1`] = `
<div>
<div
aria-labelledby="pluginAButton"
id="pluginASettings"
role="tabpanel"
>
<SettingMobileHeader
closeModal={[MockFunction]}
collapseModal={[MockFunction]}

Просмотреть файл

@@ -37,7 +37,11 @@ const PluginTab = ({
);
return (
<div>
<div
id={`${settings.id}Settings`}
aria-labelledby={`${settings.id}Button`}
role='tabpanel'
>
<SettingMobileHeader
closeModal={closeModal}
collapseModal={collapseModal}

Просмотреть файл

@@ -1,7 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/user_settings/display/UserSettingsDisplay should match snapshot, enable gitlab 1`] = `
<div>
<div
aria-labelledby="securityButton"
id="securitySettings"
role="tabpanel"
>
<SettingMobileHeader
closeModal={[MockFunction]}
collapseModal={[MockFunction]}
@@ -138,7 +142,11 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
`;
exports[`components/user_settings/display/UserSettingsDisplay should match snapshot, enable google 1`] = `
<div>
<div
aria-labelledby="securityButton"
id="securitySettings"
role="tabpanel"
>
<SettingMobileHeader
closeModal={[MockFunction]}
collapseModal={[MockFunction]}
@@ -275,7 +283,11 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
`;
exports[`components/user_settings/display/UserSettingsDisplay should match snapshot, enable office365 1`] = `
<div>
<div
aria-labelledby="securityButton"
id="securitySettings"
role="tabpanel"
>
<SettingMobileHeader
closeModal={[MockFunction]}
collapseModal={[MockFunction]}
@@ -412,7 +424,11 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
`;
exports[`components/user_settings/display/UserSettingsDisplay should match snapshot, enable openID 1`] = `
<div>
<div
aria-labelledby="securityButton"
id="securitySettings"
role="tabpanel"
>
<SettingMobileHeader
closeModal={[MockFunction]}
collapseModal={[MockFunction]}
@@ -549,7 +565,11 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
`;
exports[`components/user_settings/display/UserSettingsDisplay should match snapshot, to email 1`] = `
<div>
<div
aria-labelledby="securityButton"
id="securitySettings"
role="tabpanel"
>
<SettingMobileHeader
closeModal={[MockFunction]}
collapseModal={[MockFunction]}

Просмотреть файл

@@ -1002,7 +1002,11 @@ export class SecurityTab extends React.PureComponent<Props, State> {
}
return (
<div>
<div
id='securitySettings'
aria-labelledby='securityButton'
role='tabpanel'
>
<SettingMobileHeader
closeModal={this.props.closeModal}
collapseModal={this.props.collapseModal}

Просмотреть файл

@@ -24,7 +24,11 @@ export interface Props {
export default function UserSettingsSidebar(props: Props): JSX.Element {
return (
<div>
<div
id='sidebarSettings'
aria-labelledby='sidebarButton'
role='tabpanel'
>
<SettingMobileHeader
closeModal={props.closeModal}
collapseModal={props.collapseModal}

Просмотреть файл

@@ -21,43 +21,43 @@
// Tabs
.nav-pills {
> li {
&__tab {
display: flex;
width: 100%;
height: 32px;
align-items: center;
justify-content: start;
padding: 0 12px;
border-radius: 4px;
margin-bottom: 8px;
color: rgba(var(--center-channel-color-rgb), 0.75);
font-weight: 600;
&.header {
color: rgba(var(--center-channel-color-rgb), 0.75);
font-weight: 600;
i {
color: rgba(var(--center-channel-color-rgb), 0.64);
}
button {
height: 32px;
padding: 0 12px;
&:hover {
border-radius: 4px;
color: rgba(var(--center-channel-color-rgb), 0.75);
font-weight: 600;
i {
color: rgba(var(--center-channel-color-rgb), 0.64);
}
&:hover {
border-radius: 4px;
background-color: rgba(var(--center-channel-color-rgb), 0.04);
color: rgba(var(--center-channel-color-rgb), 0.8);
}
background-color: rgba(var(--center-channel-color-rgb), 0.04);
color: rgba(var(--center-channel-color-rgb), 0.8);
}
&.active {
button {
background: rgba(var(--button-bg-rgb), 0.08);
color: functions.v(button-bg);
background: rgba(var(--button-bg-rgb), 0.08);
color: functions.v(button-bg);
i {
color: functions.v(button-bg);
}
i {
color: functions.v(button-bg);
}
}
}
& .header {
margin-bottom: 8px;
color: rgba(var(--center-channel-color-rgb), 0.75);
font-weight: 600;
}
}
.settings-table {
@@ -70,10 +70,6 @@
background: rgba(var(--center-channel-color-rgb), 0.04);
}
.nav {
width: 200px;
}
.settings-content {
.divider-dark {
border-color: rgba(var(--center-channel-color-rgb), 0.12);

Просмотреть файл

@@ -167,7 +167,7 @@
}
.nav-pills {
>li {
> &__tab {
&:hover {
a {
background: transparent !important;
@@ -276,7 +276,9 @@
width: 100%;
margin: 0;
>li {
&-pills__tab {
height: 48px;
>a {
font-size: 1.1em;
line-height: 2.7;
@@ -285,10 +287,6 @@
margin: 0 7px;
}
}
> button {
height: 48px;
}
}
}
@@ -319,14 +317,12 @@
}
.nav-pills {
>li {
&__tab {
&.active {
button {
background: transparent;
background: transparent;
&::before {
display: none;
}
&::before {
display: none;
}
}
}

Просмотреть файл

@@ -569,30 +569,29 @@
background-color: rgba(0, 0, 0, 0.1);
}
.nav-pills > li button {
color: rgba(var(--center-channel-color-rgb), 0.75);
.nav-pills {
&__tab {
color: rgba(var(--center-channel-color-rgb), 0.75);
}
}
}
.nav-pills {
> li {
&__tab {
overflow: hidden;
width: 100%;
padding: 6px 15px;
border-radius: 4px;
margin-bottom: 8px;
color: variables.$gray;
font-weight: 600;
text-align: left;
text-overflow: ellipsis;
white-space: nowrap;
button {
overflow: hidden;
width: 100%;
padding: 6px 15px;
border-radius: 4px;
color: variables.$gray;
font-weight: 600;
text-align: left;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
background-color: rgba(var(--center-channel-color-rgb), 0.04);
color: rgba(var(--center-channel-color-rgb), 0.8);
}
&:hover {
background-color: rgba(var(--center-channel-color-rgb), 0.04);
color: rgba(var(--center-channel-color-rgb), 0.8);
}
img {
@@ -604,7 +603,6 @@
.icon {
position: relative;
top: 1px;
width: 16px;
margin-right: 8px;
font-size: 18px;
@@ -612,6 +610,10 @@
}
&.active {
position: relative;
background: rgba(var(--button-bg-rgb), 0.08);
color: functions.v(button-bg);
div {
background-color: #e1e1e1;
color: #111;
@@ -626,17 +628,6 @@
content: '';
}
}
button,
button:hover,
button:focus {
position: relative;
}
button {
background: rgba(var(--button-bg-rgb), 0.08);
color: functions.v(button-bg);
}
}
}
}