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
Этот коммит содержится в:
Harrison Healey
2024-12-17 16:50:06 -05:00
коммит произвёл GitHub
родитель 0e4e7ca0b9
Коммит f11ed97a5c
40 изменённых файлов: 1373 добавлений и 1568 удалений

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

@@ -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;
}

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

@@ -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",