MM-62046 Associate most labels with controls and enable label-has-associated-control ESLint rule (#29510)

* Remove label from add_user_to_channel_modal.tsx

The label isn't needed here because it was incorrectly used for error
text. I had to add the display: block to the .modal__error class to
ensure the top margin was the same as before, but I could remove the
font-weight from that class since it was only needed to counteract
the weight that Bootstrap adds for labels.
:

* Remove label from bleve_settings.tsx and replace with SettingSet

It doesn't seem correct to use a label here because it isn't tied to a
single form field. Since this component is a set of fields (a fieldset,
if you will) that looks like a setting, it seemed like a good place to
use SettingSet.

* Remove label from brand_image_setting.tsx, replace with SettingSet, and improve a11y of upload button

Like the last commit for BleveSettings, this setting is multiple inputs,
so it makes more sense as a fieldset/SettingSet.

While doing that, I also changed how the file upload button in
BrandImageSetting works because the new HTML broke how it worked before.
Previously, we overlayed an transparent file input over the button for it,
but the sizing for that got messed up, and that causes some weird
accessibility and breaks some things like hover effects. Instead, the
file input is now fully hidden, and clicking on the button triggers the
file input programatically. The behaviour is otherwise the same, but the
button now shows the hover style and there's only one keyboard focus for
the button.

* Remove some more labels from PluginManagement and use SettingSet there

* Add for attribute to labels in compliance_reports.tsx

* Use SettingSet and add the for attribute to another label in DatabaseSettings

* Use SettingSet in ElasticsearchSettings

* Add for attribute to label in GroupProfile

* Don't use label for help text in TeamEditionRightPanel

For this, I had to add a new CSS class to make the element appear as if
it was a label element as affected by Bootstrap. There's likely some
cases that this won't work, but it applies the same margin and
font-weight.

* Don't use label for help text in TrialBanner

* Don't use label for a heading in PasswordSettings

* Don't use label element in table in ChannelModeration

This text is more like a legend element in SettingSet, but I didn't use
that because it's in a table. This could possibly be given its own
CSS class.

* Don't use label element for error text in AdvancedTextEditor and MessageSubmitError

* Don't use label for help text and add for attribute in RenameChannelModal

* Don't use label for error text in SelectTeam

* Add for attribute to labels in various components

* Turn jsx-a11y/label-has-associated-control to an error

* Fix E2E test
This commit is contained in:
Harrison Healey
2024-12-17 16:50:06 -05:00
committed by GitHub
parent 0e4e7ca0b9
commit f11ed97a5c
40 changed files with 1373 additions and 1568 deletions
@@ -36,7 +36,7 @@ describe('Customization', () => {
cy.findByTestId('CustomBrandImage').should('be.visible').within(() => {
// * Verify that setting is visible and matches text content
cy.get('label').should('be.visible').and('have.text', 'Custom Brand Image:');
cy.get('legend').should('be.visible').and('have.text', 'Custom Brand Image:');
// * Verify that help setting is visible and matches text content
const contents = 'Customize your user experience by adding a custom image to your login screen. Recommended maximum image size is less than 2 MB.';
@@ -95,9 +95,7 @@ exports[`components/AddUserToChannelModal should match snapshot 1`] = `
value=""
/>
</div>
<div>
<br />
</div>
<div />
</ModalBody>
<ModalFooter
bsClass="modal-footer"
@@ -224,16 +224,16 @@ export default class AddUserToChannelModal extends React.PureComponent<Props, St
if (!this.state.saving) {
if (this.state.submitError) {
errorMsg = (
<label
<span
id='add-user-to-channel-modal__invite-error'
className='modal__error has-error control-label'
>
{this.state.submitError}
</label>
</span>
);
} else if (targetUserIsMemberOfSelectedChannel) {
errorMsg = (
<label
<span
id='add-user-to-channel-modal__user-is-member'
className='modal__error has-error control-label'
>
@@ -244,7 +244,7 @@ export default class AddUserToChannelModal extends React.PureComponent<Props, St
name,
}}
/>
</label>
</span>
);
}
}
@@ -317,7 +317,6 @@ export default class AddUserToChannelModal extends React.PureComponent<Props, St
</div>
<div>
{errorMsg}
<br/>
</div>
</Modal.Body>
<Modal.Footer>
@@ -57,43 +57,36 @@ exports[`components/BleveSettings should match snapshot, disabled 1`] = `
setByEnv={false}
value=""
/>
<div
className="form-group"
>
<label
className="control-label col-sm-4"
>
<MemoizedFormattedMessage
<SettingSet
label={
<Memo(MemoizedFormattedMessage)
defaultMessage="Bulk Indexing:"
id="admin.bleve.bulkIndexingTitle"
/>
</label>
}
>
<div
className="col-sm-8"
className="job-table-setting"
>
<div
className="job-table-setting"
>
<Connect(JobTable)
createJobButtonText={
<Memo(MemoizedFormattedMessage)
defaultMessage="Index Now"
id="admin.bleve.createJob.title"
/>
}
createJobHelpText={
<Memo(MemoizedFormattedMessage)
defaultMessage="All users, channels and posts in the database will be indexed from oldest to newest. Bleve is available during indexing but search results may be incomplete until the indexing job is complete."
id="admin.bleve.createJob.help"
/>
}
disabled={true}
getExtraInfoText={[Function]}
jobType="bleve_post_indexing"
/>
</div>
<Connect(JobTable)
createJobButtonText={
<Memo(MemoizedFormattedMessage)
defaultMessage="Index Now"
id="admin.bleve.createJob.title"
/>
}
createJobHelpText={
<Memo(MemoizedFormattedMessage)
defaultMessage="All users, channels and posts in the database will be indexed from oldest to newest. Bleve is available during indexing but search results may be incomplete until the indexing job is complete."
id="admin.bleve.createJob.help"
/>
}
disabled={true}
getExtraInfoText={[Function]}
jobType="bleve_post_indexing"
/>
</div>
</div>
</SettingSet>
<RequestButton
buttonText={
<Memo(MemoizedFormattedMessage)
@@ -260,43 +253,36 @@ exports[`components/BleveSettings should match snapshot, enabled 1`] = `
setByEnv={false}
value="bleve.idx"
/>
<div
className="form-group"
>
<label
className="control-label col-sm-4"
>
<MemoizedFormattedMessage
<SettingSet
label={
<Memo(MemoizedFormattedMessage)
defaultMessage="Bulk Indexing:"
id="admin.bleve.bulkIndexingTitle"
/>
</label>
}
>
<div
className="col-sm-8"
className="job-table-setting"
>
<div
className="job-table-setting"
>
<Connect(JobTable)
createJobButtonText={
<Memo(MemoizedFormattedMessage)
defaultMessage="Index Now"
id="admin.bleve.createJob.title"
/>
}
createJobHelpText={
<Memo(MemoizedFormattedMessage)
defaultMessage="All users, channels and posts in the database will be indexed from oldest to newest. Bleve is available during indexing but search results may be incomplete until the indexing job is complete."
id="admin.bleve.createJob.help"
/>
}
disabled={false}
getExtraInfoText={[Function]}
jobType="bleve_post_indexing"
/>
</div>
<Connect(JobTable)
createJobButtonText={
<Memo(MemoizedFormattedMessage)
defaultMessage="Index Now"
id="admin.bleve.createJob.title"
/>
}
createJobHelpText={
<Memo(MemoizedFormattedMessage)
defaultMessage="All users, channels and posts in the database will be indexed from oldest to newest. Bleve is available during indexing but search results may be incomplete until the indexing job is complete."
id="admin.bleve.createJob.help"
/>
}
disabled={false}
getExtraInfoText={[Function]}
jobType="bleve_post_indexing"
/>
</div>
</div>
</SettingSet>
<RequestButton
buttonText={
<Memo(MemoizedFormattedMessage)
@@ -340,39 +340,33 @@ exports[`components/DatabaseSettings should match snapshot 1`] = `
setByEnv={false}
value={true}
/>
<div
className="form-group"
>
<label
className="control-label col-sm-4"
>
<MemoizedFormattedMessage
<SettingSet
label={
<Memo(MemoizedFormattedMessage)
defaultMessage="Schema Migrations:"
id="admin.database.migrations_table.title"
/>
</label>
}
>
<div
className="col-sm-8"
className="migrations-table-setting"
>
<div
className="migrations-table-setting"
>
<Connect(Component)
createHelpText={
<Memo(MemoizedFormattedMessage)
defaultMessage="All applied migrations."
id="admin.database.migrations_table.help_text"
/>
}
/>
</div>
<Connect(Component)
createHelpText={
<Memo(MemoizedFormattedMessage)
defaultMessage="All applied migrations."
id="admin.database.migrations_table.help_text"
/>
}
/>
</div>
</div>
</SettingSet>
<div
className="form-group"
>
<label
className="control-label col-sm-4"
htmlFor="activeSearchBackend"
>
<MemoizedFormattedMessage
defaultMessage="Active Search Backend:"
@@ -385,6 +379,7 @@ exports[`components/DatabaseSettings should match snapshot 1`] = `
<input
className="form-control"
disabled={true}
id="activeSearchBackend"
type="text"
value=""
/>
@@ -288,43 +288,36 @@ exports[`components/ElasticSearchSettings should match snapshot, disabled 1`] =
}
}
/>
<div
className="form-group"
>
<label
className="control-label col-sm-4"
>
<MemoizedFormattedMessage
<SettingSet
label={
<Memo(MemoizedFormattedMessage)
defaultMessage="Bulk Indexing:"
id="admin.elasticsearch.bulkIndexingTitle"
/>
</label>
}
>
<div
className="col-sm-8"
className="job-table-setting"
>
<div
className="job-table-setting"
>
<Connect(JobTable)
createJobButtonText={
<Memo(MemoizedFormattedMessage)
defaultMessage="Index Now"
id="admin.elasticsearch.createJob.title"
/>
}
createJobHelpText={
<Memo(MemoizedFormattedMessage)
defaultMessage="All users, channels and posts in the database will be indexed from oldest to newest. Elasticsearch is available during indexing but search results may be incomplete until the indexing job is complete."
id="admin.elasticsearch.createJob.help"
/>
}
disabled={true}
getExtraInfoText={[Function]}
jobType="elasticsearch_post_indexing"
/>
</div>
<Connect(JobTable)
createJobButtonText={
<Memo(MemoizedFormattedMessage)
defaultMessage="Index Now"
id="admin.elasticsearch.createJob.title"
/>
}
createJobHelpText={
<Memo(MemoizedFormattedMessage)
defaultMessage="All users, channels and posts in the database will be indexed from oldest to newest. Elasticsearch is available during indexing but search results may be incomplete until the indexing job is complete."
id="admin.elasticsearch.createJob.help"
/>
}
disabled={true}
getExtraInfoText={[Function]}
jobType="elasticsearch_post_indexing"
/>
</div>
</div>
</SettingSet>
<RequestButton
buttonText={
<Memo(MemoizedFormattedMessage)
@@ -790,42 +783,35 @@ exports[`components/ElasticSearchSettings should match snapshot, enabled 1`] = `
}
}
/>
<div
className="form-group"
>
<label
className="control-label col-sm-4"
>
<MemoizedFormattedMessage
<SettingSet
label={
<Memo(MemoizedFormattedMessage)
defaultMessage="Bulk Indexing:"
id="admin.elasticsearch.bulkIndexingTitle"
/>
</label>
}
>
<div
className="col-sm-8"
className="job-table-setting"
>
<div
className="job-table-setting"
>
<Connect(JobTable)
createJobButtonText={
<Memo(MemoizedFormattedMessage)
defaultMessage="Index Now"
id="admin.elasticsearch.createJob.title"
/>
}
createJobHelpText={
<Memo(MemoizedFormattedMessage)
defaultMessage="All users, channels and posts in the database will be indexed from oldest to newest. Elasticsearch is available during indexing but search results may be incomplete until the indexing job is complete."
id="admin.elasticsearch.createJob.help"
/>
}
getExtraInfoText={[Function]}
jobType="elasticsearch_post_indexing"
/>
</div>
<Connect(JobTable)
createJobButtonText={
<Memo(MemoizedFormattedMessage)
defaultMessage="Index Now"
id="admin.elasticsearch.createJob.title"
/>
}
createJobHelpText={
<Memo(MemoizedFormattedMessage)
defaultMessage="All users, channels and posts in the database will be indexed from oldest to newest. Elasticsearch is available during indexing but search results may be incomplete until the indexing job is complete."
id="admin.elasticsearch.createJob.help"
/>
}
getExtraInfoText={[Function]}
jobType="elasticsearch_post_indexing"
/>
</div>
</div>
</SettingSet>
<RequestButton
buttonText={
<Memo(MemoizedFormattedMessage)
@@ -18,6 +18,7 @@ import JobsTable from './jobs';
import OLDAdminSettings from './old_admin_settings';
import type {BaseProps, BaseState} from './old_admin_settings';
import RequestButton from './request_button/request_button';
import SettingSet from './setting_set';
import SettingsGroup from './settings_group';
import TextSetting from './text_setting';
@@ -174,27 +175,24 @@ export default class BleveSettings extends OLDAdminSettings<Props, State> {
setByEnv={this.isSetByEnv('BleveSettings.IndexDir')}
disabled={this.props.isDisabled}
/>
<div className='form-group'>
<label className='control-label col-sm-4'>
<FormattedMessage {...messages.bulkIndexingTitle}/>
</label>
<div className='col-sm-8'>
<div className='job-table-setting'>
<JobsTable
jobType={JobTypes.BLEVE_POST_INDEXING}
disabled={!this.state.canPurgeAndIndex || Boolean(this.props.isDisabled)}
createJobButtonText={
<FormattedMessage
id='admin.bleve.createJob.title'
defaultMessage='Index Now'
/>
}
createJobHelpText={<FormattedMessage {...messages.createJob_help}/>}
getExtraInfoText={this.getExtraInfo}
/>
</div>
<SettingSet
label={<FormattedMessage {...messages.bulkIndexingTitle}/>}
>
<div className='job-table-setting'>
<JobsTable
jobType={JobTypes.BLEVE_POST_INDEXING}
disabled={!this.state.canPurgeAndIndex || Boolean(this.props.isDisabled)}
createJobButtonText={
<FormattedMessage
id='admin.bleve.createJob.title'
defaultMessage='Index Now'
/>
}
createJobHelpText={<FormattedMessage {...messages.createJob_help}/>}
getExtraInfoText={this.getExtraInfo}
/>
</div>
</div>
</SettingSet>
<RequestButton
id='purgeIndexesSection'
requestAction={blevePurgeIndexes}
@@ -8,6 +8,7 @@ import {Client4} from 'mattermost-redux/client';
import {uploadBrandImage, deleteBrandImage} from 'actions/admin_actions.jsx';
import SettingSet from 'components/admin_console/setting_set';
import FormError from 'components/form_error';
import WithTooltip from 'components/with_tooltip';
@@ -110,6 +111,10 @@ export default class BrandImageSetting extends React.PureComponent<Props, State>
}
}
handleSelectClick = () => {
this.fileInputRef.current?.click();
};
handleImageChange = () => {
if (!this.fileInputRef.current) {
return;
@@ -234,50 +239,47 @@ export default class BrandImageSetting extends React.PureComponent<Props, State>
}
return (
<div
data-testid={this.props.id}
className='form-group'
>
<label className='control-label col-sm-4'>
<SettingSet
inputId={this.props.id}
helpText={
<FormattedMessage
id='admin.team.uploadDesc'
defaultMessage='Customize your user experience by adding a custom image to your login screen. Recommended maximum image size is less than 2 MB.'
/>
}
label={
<FormattedMessage
id='admin.team.brandImageTitle'
defaultMessage='Custom Brand Image:'
/>
</label>
<div className='col-sm-8'>
}
setByEnv={false}
>
<div>
<div className='remove-image'>{img}</div>
</div>
<div className='col-sm-4'/>
<div className='col-sm-8'>
<div className='file__upload mt-5'>
<button
type='button'
className='btn btn-tertiary'
disabled={this.props.disabled}
>
<FormattedMessage
id='admin.team.chooseImage'
defaultMessage='Select Image'
/>
</button>
<input
ref={this.fileInputRef}
type='file'
accept={Constants.ACCEPT_STATIC_IMAGE}
disabled={this.props.disabled}
onChange={this.handleImageChange}
/>
</div>
<br/>
<FormError error={this.state.error}/>
<p className='help-text m-0'>
<div className='file__upload mt-5'>
<button
type='button'
className='btn btn-tertiary'
disabled={this.props.disabled}
onClick={this.handleSelectClick}
>
<FormattedMessage
id='admin.team.uploadDesc'
defaultMessage='Customize your user experience by adding a custom image to your login screen. Recommended maximum image size is less than 2 MB.'
id='admin.team.chooseImage'
defaultMessage='Select Image'
/>
</p>
</button>
<input
ref={this.fileInputRef}
type='file'
accept={Constants.ACCEPT_STATIC_IMAGE}
disabled={this.props.disabled}
onChange={this.handleImageChange}
/>
</div>
</div>
<FormError error={this.state.error}/>
</SettingSet>
);
}
}
@@ -338,7 +338,7 @@ export default class ComplianceReports extends React.PureComponent<Props, State>
</h4>
<div className='row'>
<div className='col-sm-6 col-md-4 form-group'>
<label>
<label htmlFor='desc'>
<FormattedMessage
id='admin.compliance_reports.desc'
defaultMessage='Job Name:'
@@ -354,7 +354,7 @@ export default class ComplianceReports extends React.PureComponent<Props, State>
/>
</div>
<div className='col-sm-3 col-md-2 form-group'>
<label>
<label htmlFor='from'>
<FormattedMessage
id='admin.compliance_reports.from'
defaultMessage='From:'
@@ -370,7 +370,7 @@ export default class ComplianceReports extends React.PureComponent<Props, State>
/>
</div>
<div className='col-sm-3 col-md-2 form-group'>
<label>
<label htmlFor='to'>
<FormattedMessage
id='admin.compliance_reports.to'
defaultMessage='To:'
@@ -388,7 +388,7 @@ export default class ComplianceReports extends React.PureComponent<Props, State>
</div>
<div className='row'>
<div className='col-sm-6 col-md-4 form-group'>
<label>
<label htmlFor='emails'>
<FormattedMessage
id='admin.compliance_reports.emails'
defaultMessage='Emails:'
@@ -404,7 +404,7 @@ export default class ComplianceReports extends React.PureComponent<Props, State>
/>
</div>
<div className='col-sm-6 col-md-4 form-group'>
<label>
<label htmlFor='keywords'>
<FormattedMessage
id='admin.compliance_reports.keywords'
defaultMessage='Keywords:'
@@ -18,6 +18,7 @@ import MigrationsTable from './database';
import type {BaseState} from './old_admin_settings';
import OLDAdminSettings from './old_admin_settings';
import RequestButton from './request_button/request_button';
import SettingSet from './setting_set';
import SettingsGroup from './settings_group';
import TextSetting from './text_setting';
@@ -378,31 +379,29 @@ export default class DatabaseSettings extends OLDAdminSettings<Props, State> {
setByEnv={this.isSetByEnv('SqlSettings.DisableDatabaseSearch')}
disabled={this.props.isDisabled}
/>
<div className='form-group'>
<label
className='control-label col-sm-4'
>
<SettingSet
label={
<FormattedMessage
id='admin.database.migrations_table.title'
defaultMessage='Schema Migrations:'
/>
</label>
<div className='col-sm-8'>
<div className='migrations-table-setting'>
<MigrationsTable
createHelpText={
<FormattedMessage
id='admin.database.migrations_table.help_text'
defaultMessage='All applied migrations.'
/>
}
/>
</div>
}
>
<div className='migrations-table-setting'>
<MigrationsTable
createHelpText={
<FormattedMessage
id='admin.database.migrations_table.help_text'
defaultMessage='All applied migrations.'
/>
}
/>
</div>
</div>
</SettingSet>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='activeSearchBackend'
>
<FormattedMessage
id='admin.database.search_backend.title'
@@ -411,6 +410,7 @@ export default class DatabaseSettings extends OLDAdminSettings<Props, State> {
</label>
<div className='col-sm-8'>
<input
id='activeSearchBackend'
type='text'
className='form-control'
value={this.state.searchBackend}
@@ -19,6 +19,7 @@ import JobsTable from './jobs';
import OLDAdminSettings from './old_admin_settings';
import type {BaseProps, BaseState} from './old_admin_settings';
import RequestButton from './request_button/request_button';
import SettingSet from './setting_set';
import SettingsGroup from './settings_group';
import TextSetting from './text_setting';
@@ -419,27 +420,24 @@ export default class ElasticsearchSettings extends OLDAdminSettings<Props, State
})}
disabled={!this.state.enableIndexing}
/>
<div className='form-group'>
<label className='control-label col-sm-4'>
<FormattedMessage {...messages.bulkIndexingTitle}/>
</label>
<div className='col-sm-8'>
<div className='job-table-setting'>
<JobsTable
jobType={JobTypes.ELASTICSEARCH_POST_INDEXING as JobType}
disabled={!this.state.canPurgeAndIndex || this.props.isDisabled!}
createJobButtonText={
<FormattedMessage
id='admin.elasticsearch.createJob.title'
defaultMessage='Index Now'
/>
}
createJobHelpText={<FormattedMessage {...messages.help}/>}
getExtraInfoText={this.getExtraInfo}
/>
</div>
<SettingSet
label={<FormattedMessage {...messages.bulkIndexingTitle}/>}
>
<div className='job-table-setting'>
<JobsTable
jobType={JobTypes.ELASTICSEARCH_POST_INDEXING as JobType}
disabled={!this.state.canPurgeAndIndex || this.props.isDisabled!}
createJobButtonText={
<FormattedMessage
id='admin.elasticsearch.createJob.title'
defaultMessage='Index Now'
/>
}
createJobHelpText={<FormattedMessage {...messages.help}/>}
getExtraInfoText={this.getExtraInfo}
/>
</div>
</div>
</SettingSet>
<RequestButton
id='rebuildChannelsIndexButton'
requestAction={rebuildChannelsIndex}
@@ -3,12 +3,14 @@
exports[`components/admin_console/group_settings/group_details/GroupProfile should match snapshot 1`] = `
<div
className="group-profile form-horizontal"
id="test"
>
<div
className="group-profile-field form-group mb-0"
>
<label
className="control-label col-sm-4"
htmlFor="testInput"
>
<MemoizedFormattedMessage
defaultMessage="Name:"
@@ -29,6 +31,7 @@ exports[`components/admin_console/group_settings/group_details/GroupProfile shou
<input
className="form-control group-at-mention-input"
disabled={false}
id="testInput"
type="text"
value="Test"
/>
@@ -10,6 +10,7 @@ describe('components/admin_console/group_settings/group_details/GroupProfile', (
test('should match snapshot', () => {
const wrapper = shallow(
<GroupProfile
customID='test'
isDisabled={false}
name='Test'
showAtMention={true}
@@ -10,7 +10,7 @@ import MentionsIcon from 'components/widgets/icons/mentions_icon';
type Props = {
name?: string;
title: MessageDescriptor;
customID?: string;
customID: string;
isDisabled?: boolean;
showAtMention: boolean;
onChange?: React.ChangeEventHandler<HTMLInputElement>;
@@ -29,7 +29,10 @@ const GroupProfile = ({
id={customID}
>
<div className='group-profile-field form-group mb-0'>
<label className='control-label col-sm-4'>
<label
className='control-label col-sm-4'
htmlFor={customID + 'Input'}
>
<FormattedMessage {...title}/>
</label>
<div className='col-sm-8'>
@@ -42,6 +45,7 @@ const GroupProfile = ({
)}
</div>
<input
id={customID + 'Input'}
type='text'
className='form-control group-at-mention-input'
value={name}
@@ -101,13 +101,12 @@ const TeamEditionRightPanel: React.FC<TeamEditionRightPanelProps> = ({
{upgradeError && (
<div className='upgrade-error'>
<div className='form-group has-error'>
<label className='control-label'>
<span
dangerouslySetInnerHTML={{
__html: format(upgradeError),
}}
/>
</label>
<div
className='as-bs-label control-label'
dangerouslySetInnerHTML={{
__html: format(upgradeError),
}}
/>
</div>
</div>
)}
@@ -140,17 +139,17 @@ const TeamEditionRightPanel: React.FC<TeamEditionRightPanelProps> = ({
defaultMessage='Restart Server'
/>
</LoadingWrapper>
{restartError && (
<div className='col-sm-12'>
<div className='form-group has-error'>
<label className='control-label'>
{restartError}
</label>
</div>
</div>
)}
</button>
</p>
{restartError && (
<div className='upgrade-error'>
<div className='form-group has-error'>
<div className='as-bs-label control-label'>
{restartError}
</div>
</div>
</div>
)}
</div>
);
}
@@ -340,22 +340,21 @@ const TrialBanner = ({
{upgradeError && (
<div className='upgrade-error'>
<div className='form-group has-error'>
<label className='control-label'>
<span
dangerouslySetInnerHTML={{
__html: format(upgradeError),
}}
/>
</label>
<div
className='as-bs-label control-label'
dangerouslySetInnerHTML={{
__html: format(upgradeError),
}}
/>
</div>
</div>
)}
{restartError && (
<div className='col-sm-12'>
<div className='upgrade-error'>
<div className='form-group has-error'>
<label className='control-label'>
<div className='as-bs-label control-label'>
{restartError}
</label>
</div>
</div>
</div>
)}
@@ -254,10 +254,9 @@ export default class PasswordSettings extends OLDAdminSettings<Props, State> {
</ul>
<div>
<br/>
<label>
<div className='password-settings__preview-heading'>
<FormattedMessage {...messages.preview}/>
</label>
<br/>
</div>
{this.getSampleErrorMsg()}
</div>
</SettingSet>
@@ -24,6 +24,7 @@ import * as Utils from 'utils/utils';
import BooleanSetting from '../boolean_setting';
import OLDAdminSettings from '../old_admin_settings';
import type {BaseProps, BaseState} from '../old_admin_settings';
import SettingSet from '../setting_set';
import SettingsGroup from '../settings_group';
import TextSetting from '../text_setting';
@@ -556,6 +557,10 @@ class PluginManagement extends OLDAdminSettings<Props, State> {
}
}
handleChooseFileClick = () => {
this.fileInput.current?.click();
};
handleUpload = () => {
this.setState({lastMessage: null, serverError: null});
const element = this.fileInput.current as HTMLInputElement;
@@ -1018,18 +1023,15 @@ class PluginManagement extends OLDAdminSettings<Props, State> {
if (enable) {
pluginsContainer = (
<div className='form-group'>
<label className='control-label col-sm-4'>
<FormattedMessage {...messages.installedTitle}/>
</label>
<div className='col-sm-8'>
<p className='help-text'>
<FormattedMessage {...messages.installedDesc}/>
</p>
<br/>
{pluginsListContainer}
</div>
</div>
<SettingSet
label={<FormattedMessage {...messages.installedTitle}/>}
>
<p className='help-text'>
<FormattedMessage {...messages.installedDesc}/>
</p>
<br/>
{pluginsListContainer}
</SettingSet>
);
}
@@ -1147,48 +1149,44 @@ class PluginManagement extends OLDAdminSettings<Props, State> {
onChange={this.handleChange}
setByEnv={this.isSetByEnv('PluginSettings.AutomaticPrepackagedPlugins')}
/>
<div className='form-group'>
<label className='control-label col-sm-4'>
<FormattedMessage {...messages.uploadTitle}/>
</label>
<div className='col-sm-8'>
<div className='file__upload'>
<button
type='button'
className={classNames(['btn', {'btn-tertiary': enableUploads}])}
disabled={!enableUploadButton || this.props.isDisabled}
>
<FormattedMessage
id='admin.plugin.choose'
defaultMessage='Choose File'
/>
</button>
<input
ref={this.fileInput}
type='file'
accept='.gz'
onChange={this.handleUpload}
disabled={!enableUploadButton || this.props.isDisabled}
/>
</div>
<SettingSet
helpText={uploadHelpText}
label={<FormattedMessage {...messages.uploadTitle}/>}
>
<div className='file__upload'>
<button
className={btnClass}
id='uploadPlugin'
disabled={!this.state.fileSelected}
onClick={this.handleSubmitUpload}
type='button'
className={classNames(['btn', {'btn-tertiary': enableUploads}])}
onClick={this.handleChooseFileClick}
disabled={!enableUploadButton || this.props.isDisabled}
>
{uploadButtonText}
<FormattedMessage
id='admin.plugin.choose'
defaultMessage='Choose File'
/>
</button>
<div className='help-text m-0'>
{fileName}
</div>
{serverError}
{lastMessage}
<p className='help-text'>
{uploadHelpText}
</p>
<input
ref={this.fileInput}
type='file'
accept='.gz'
onChange={this.handleUpload}
disabled={!enableUploadButton || this.props.isDisabled}
/>
</div>
</div>
<button
className={btnClass}
id='uploadPlugin'
disabled={!this.state.fileSelected}
onClick={this.handleSubmitUpload}
>
{uploadButtonText}
</button>
<div className='help-text m-0'>
{fileName}
</div>
{serverError}
{lastMessage}
</SettingSet>
<BooleanSetting
id='enableMarketplace'
label={<FormattedMessage {...messages.enableMarketplace}/>}
@@ -259,14 +259,15 @@ exports[`admin_console/team_channel_settings/channel/ChannelModeration Should ma
exports[`admin_console/team_channel_settings/channel/ChannelModeration Should match second Snapshot 1`] = `
<tr>
<td>
<label
<div
className="as-bs-label"
data-testid="admin-channel_settings-channel_moderation-createPosts"
>
<MemoizedFormattedMessage
defaultMessage="Create Posts"
id="admin.channel_settings.channel_moderation.createPosts"
/>
</label>
</div>
<div
data-testid="admin-channel_settings-channel_moderation-createPostsDesc"
>
@@ -303,14 +304,15 @@ exports[`admin_console/team_channel_settings/channel/ChannelModeration Should ma
exports[`admin_console/team_channel_settings/channel/ChannelModeration Should match seventh Snapshot 1`] = `
<tr>
<td>
<label
<div
className="as-bs-label"
data-testid="admin-channel_settings-channel_moderation-createPosts"
>
<MemoizedFormattedMessage
defaultMessage="Create Posts"
id="admin.channel_settings.channel_moderation.createPosts"
/>
</label>
</div>
<div
data-testid="admin-channel_settings-channel_moderation-createPostsDesc"
>
@@ -275,14 +275,15 @@ export const ChannelModerationTableRow = (props: ChannelModerationTableRow) => {
return (
<tr>
<td>
<label
<div
className='as-bs-label'
data-testid={channelModerationPermissionMessages?.title?.id?.replace(PERIOD_TO_SLASH_REGEX, '-')}
>
<FormattedMessage
id={channelModerationPermissionMessages?.title?.id}
defaultMessage={channelModerationPermissionMessages?.title?.defaultMessage}
/>
</label>
</div>
<div
data-testid={channelModerationPermissionMessages?.description?.id?.replace(PERIOD_TO_SLASH_REGEX, '-')}
>
@@ -196,11 +196,6 @@
.post-error {
margin: 4px 0 0 0;
color: var(--error-text);
label {
margin: 0;
color: var(--error-text);
}
}
}
@@ -699,9 +699,9 @@ const AdvancedTextEditor = ({
className='AdvancedTextEditor__footer'
>
{postError && (
<label className={classNames('post-error', {errorClass})}>
<div className={classNames('post-error', {errorClass})}>
{postError}
</label>
</div>
)}
{serverError && (
<MessageSubmitError
@@ -366,7 +366,10 @@ export default class Bot extends React.PureComponent<Props, State> {
onSubmit={this.handleCreateToken}
>
<div className='row'>
<label className='col-sm-auto control-label'>
<label
className='col-sm-auto control-label'
htmlFor='botToken'
>
<FormattedMessage
id='user.settings.tokens.name'
defaultMessage='Token Description: '
@@ -374,6 +377,7 @@ export default class Bot extends React.PureComponent<Props, State> {
</label>
<div className='col-sm-4'>
<input
id='botToken'
autoFocus={true}
className='form-control form-sm'
type='text'
@@ -21,7 +21,7 @@ function MessageSubmitError(props: Props) {
return (
<div className='has-error'>
<label className='control-label'>
<div className='control-label'>
<FormattedMessage
id='message_submit_error.invalidCommand'
defaultMessage="Command with a trigger of ''{slashCommand}'' not found. "
@@ -31,6 +31,7 @@ function MessageSubmitError(props: Props) {
/>
<a
href='#'
role='button'
onClick={props.handleSubmit}
>
<FormattedMessage
@@ -38,7 +39,7 @@ function MessageSubmitError(props: Props) {
defaultMessage='Click here to send as a message.'
/>
</a>
</label>
</div>
</div>
);
}
@@ -59,6 +59,7 @@ exports[`components/RenameChannelModal should match snapshot 1`] = `
>
<label
className="control-label"
htmlFor="display_name"
>
<MemoizedFormattedMessage
defaultMessage="Display Name"
@@ -81,6 +82,7 @@ exports[`components/RenameChannelModal should match snapshot 1`] = `
>
<label
className="control-label"
htmlFor="channel_name"
>
URL
</label>
@@ -241,7 +241,7 @@ export class RenameChannelModal extends React.PureComponent<Props, State> {
let serverError = null;
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
serverError = <div className='form-group has-error'><div className='as-bs-label control-label'>{this.state.serverError}</div></div>;
}
const {formatMessage} = this.props.intl;
@@ -280,7 +280,10 @@ export class RenameChannelModal extends React.PureComponent<Props, State> {
<form role='form'>
<Modal.Body>
<div className='form-group'>
<label className='control-label'>
<label
className='control-label'
htmlFor='display_name'
>
<FormattedMessage
id='rename_channel.displayName'
defaultMessage='Display Name'
@@ -303,7 +306,12 @@ export class RenameChannelModal extends React.PureComponent<Props, State> {
{displayNameError}
</div>
<div className='form-group'>
<label className='control-label'>{urlInputLabel}</label>
<label
className='control-label'
htmlFor='channel_name'
>
{urlInputLabel}
</label>
<div className={urlInputClass}>
<WithTooltip
@@ -233,11 +233,11 @@ exports[`components/select_team/SelectTeam should match snapshot, on error 1`] =
<div
className="form-group has-error"
>
<label
<div
className="control-label"
>
<Component />
</label>
</div>
</div>
</div>
<Memo(SystemPermissionGate)
@@ -535,14 +535,14 @@ exports[`components/select_team/SelectTeam should match snapshot, on no joinable
<div
className="form-group has-error"
>
<label
<div
className="control-label"
>
<MemoizedFormattedMessage
defaultMessage="Your guest account has no channels assigned. Please contact an administrator."
id="signup_team.guest_without_channels"
/>
</label>
</div>
</div>
</div>
<Memo(SystemPermissionGate)
@@ -201,7 +201,7 @@ export default class SelectTeam extends React.PureComponent<Props, State> {
openContent = (
<div className='signup__content'>
<div className={'form-group has-error'}>
<label className='control-label'>{this.state.error}</label>
<div className='control-label'>{this.state.error}</div>
</div>
</div>
);
@@ -209,12 +209,12 @@ export default class SelectTeam extends React.PureComponent<Props, State> {
openContent = (
<div className='signup__content'>
<div className={'form-group has-error'}>
<label className='control-label'>
<div className='control-label'>
<FormattedMessage
id='signup_team.guest_without_channels'
defaultMessage='Your guest account has no channels assigned. Please contact an administrator.'
/>
</label>
</div>
</div>
</div>
);
@@ -198,6 +198,7 @@ export class ManageLanguage extends React.PureComponent<Props, State> {
aria-label={interfaceLanguageLabelAria}
className='control-label'
id='changeInterfaceLanguageLabel'
htmlFor='displayLanguage'
>
<FormattedMessage
id='user.settings.languages.change'
@@ -385,6 +385,7 @@ exports[`components/user_settings/display/CustomThemeChooser should match, init
>
<label
className="custom-label"
htmlFor="codeThemeSelect"
>
<MemoizedFormattedMessage
defaultMessage="Code Theme"
@@ -538,6 +539,7 @@ exports[`components/user_settings/display/CustomThemeChooser should match, init
>
<label
className="custom-label"
htmlFor="pasteBox"
>
<MemoizedFormattedMessage
defaultMessage="Copy to share or paste theme colors here:"
@@ -312,7 +312,10 @@ export class CustomThemeChooser extends React.PureComponent<Props, State> {
className='col-sm-6 form-group'
key={'custom-theme-key' + index}
>
<label className='custom-label'>
<label
className='custom-label'
htmlFor='codeThemeSelect'
>
<FormattedMessage {...messages[element.id]}/>
</label>
<div
@@ -403,7 +406,10 @@ export class CustomThemeChooser extends React.PureComponent<Props, State> {
const pasteBox = (
<div className='col-sm-12'>
<label className='custom-label'>
<label
className='custom-label'
htmlFor='pasteBox'
>
<FormattedMessage
id='user.settings.custom_theme.copyPaste'
defaultMessage='Copy to share or paste theme colors here:'
@@ -494,14 +494,14 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
inputs.push(
<div key='currentEmailSetting'>
<div className='form-group'>
<label className='col-sm-5 control-label'>
<span className='as-bs-label col-sm-5 control-label'>
<FormattedMessage
id='user.settings.general.currentEmail'
defaultMessage='Current Email'
/>
</label>
</span>
<div className='col-sm-7'>
<label className='control-label word-break--all text-left'>{this.state.originalEmail}</label>
<span className='as-bs-label control-label word-break--all text-left'>{this.state.originalEmail}</span>
</div>
</div>
</div>,
@@ -510,7 +510,10 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
inputs.push(
<div key='emailSetting'>
<div className='form-group'>
<label className='col-sm-5 control-label'>
<label
className='col-sm-5 control-label'
htmlFor='primaryEmail'
>
<FormattedMessage
id='user.settings.general.newEmail'
defaultMessage='New Email'
@@ -535,7 +538,10 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
inputs.push(
<div key='confirmEmailSetting'>
<div className='form-group'>
<label className='col-sm-5 control-label'>
<label
className='col-sm-5 control-label'
htmlFor='confirmEmail'
>
<FormattedMessage
id='user.settings.general.confirmEmail'
defaultMessage='Confirm Email'
@@ -559,7 +565,10 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
inputs.push(
<div key='currentPassword'>
<div className='form-group'>
<label className='col-sm-5 control-label'>
<label
className='col-sm-5 control-label'
htmlFor='currentPassword'
>
<FormattedMessage
id='user.settings.general.currentPassword'
defaultMessage='Current Password'
@@ -813,7 +822,10 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
key='firstNameSetting'
className='form-group'
>
<label className='col-sm-5 control-label'>
<label
className='col-sm-5 control-label'
htmlFor='firstName'
>
<FormattedMessage
id='user.settings.general.firstName'
defaultMessage='First Name'
@@ -840,7 +852,10 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
key='lastNameSetting'
className='form-group'
>
<label className='col-sm-5 control-label'>
<label
className='col-sm-5 control-label'
htmlFor='lastName'
>
<FormattedMessage
id='user.settings.general.lastName'
defaultMessage='Last Name'
@@ -518,7 +518,10 @@ export default class UserAccessTokenSection extends React.PureComponent<Props, S
newTokenSection = (
<div className='pl-3'>
<div className='row'>
<label className='col-sm-auto control-label pr-3'>
<label
className='col-sm-auto control-label pr-3'
htmlFor='newTokenDescription'
>
<FormattedMessage
id='user.settings.tokens.name'
defaultMessage='Token Description: '
@@ -526,6 +529,7 @@ export default class UserAccessTokenSection extends React.PureComponent<Props, S
</label>
<div className='col-sm-5'>
<input
id='newTokenDescription'
autoFocus={true}
ref={this.newtokendescriptionRef}
className='form-control'
@@ -258,7 +258,10 @@ export class SecurityTab extends React.PureComponent<Props, State> {
key='currentPasswordUpdateForm'
className='form-group'
>
<label className='col-sm-5 control-label'>
<label
className='col-sm-5 control-label'
htmlFor='currentPassword'
>
<FormattedMessage
id='user.settings.security.currentPassword'
defaultMessage='Current Password'
@@ -285,7 +288,10 @@ export class SecurityTab extends React.PureComponent<Props, State> {
key='newPasswordUpdateForm'
className='form-group'
>
<label className='col-sm-5 control-label'>
<label
className='col-sm-5 control-label'
htmlFor='newPassword'
>
<FormattedMessage
id='user.settings.security.newPassword'
defaultMessage='New Password'
@@ -311,7 +317,10 @@ export class SecurityTab extends React.PureComponent<Props, State> {
key='retypeNewPasswordUpdateForm'
className='form-group'
>
<label className='col-sm-5 control-label'>
<label
className='col-sm-5 control-label'
htmlFor='confirmPassword'
>
<FormattedMessage
id='user.settings.security.retypePassword'
defaultMessage='Retype New Password'
@@ -117,11 +117,10 @@
}
.modal__error {
display: inline-block;
margin-top: 6px;
color: variables.$red;
float: left;
font-size: 0.95em;
font-weight: normal;
}
.more-table {
@@ -239,24 +239,12 @@
}
.file__upload {
position: relative;
display: inline-block;
width: fit-content;
margin: 0 10px 10px 0;
input {
position: absolute;
z-index: 5;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding-left: 100%;
cursor: pointer;
opacity: 0;
&[disabled] {
cursor: not-allowed;
}
display: none;
}
}
@@ -1089,6 +1077,12 @@
margin-bottom: 20px;
}
.password-settings__preview-heading {
display: block;
margin-bottom: 5px;
font-weight: 600;
}
#error-tooltip {
.tooltip-inner {
max-width: none;
@@ -813,3 +813,10 @@
color: rgba(var(--center-channel-color-rgb), 0.4);
}
}
.as-bs-label {
display: inline-block;
max-width: 100%;
margin-bottom: 5px;
font-weight: 700;
}
+1 -1
View File
@@ -283,6 +283,7 @@ body {
}
.control-label {
display: block;
width: 100%;
padding: 0.7em 1em;
border-radius: 3px;
@@ -290,7 +291,6 @@ body {
background: #f2f2f2;
color: #999;
font-size: 14px;
font-weight: normal;
&::before {
@extend %font-awesome;
@@ -29,7 +29,7 @@
"jsx-a11y/iframe-has-title": "warn",
"jsx-a11y/img-redundant-alt": "warn",
"jsx-a11y/interactive-supports-focus": "warn",
"jsx-a11y/label-has-associated-control": "warn",
"jsx-a11y/label-has-associated-control": "error",
"jsx-a11y/media-has-caption": "warn",
"jsx-a11y/mouse-events-have-key-events": "warn",
"jsx-a11y/no-access-key": "error",