diff --git a/e2e-tests/cypress/tests/integration/channels/enterprise/accessibility/accessibility_modals_dialogs_spec.ts b/e2e-tests/cypress/tests/integration/channels/enterprise/accessibility/accessibility_modals_dialogs_spec.ts index 5f81f65b83..4ca8cd068e 100644 --- a/e2e-tests/cypress/tests/integration/channels/enterprise/accessibility/accessibility_modals_dialogs_spec.ts +++ b/e2e-tests/cypress/tests/integration/channels/enterprise/accessibility/accessibility_modals_dialogs_spec.ts @@ -71,7 +71,7 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => { }); // * Verify if the reader is able to read out the selected row - cy.get('.filtered-user-list .sr-only'). + cy.get('.filtered-user-list div.sr-only:not([role="status"])'). should('have.attr', 'aria-live', 'polite'). and('have.attr', 'aria-atomic', 'true'). invoke('text').then((text) => { @@ -181,7 +181,7 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => { }); // * Verify if the reader is able to read out the selected row - cy.get('.filtered-user-list .sr-only'). + cy.get('.filtered-user-list div.sr-only:not([role="status"])'). should('have.attr', 'aria-live', 'polite'). and('have.attr', 'aria-atomic', 'true'). invoke('text').then((text) => { diff --git a/webapp/channels/src/components/add_groups_to_channel_modal/__snapshots__/add_groups_to_channel_modal.test.tsx.snap b/webapp/channels/src/components/add_groups_to_channel_modal/__snapshots__/add_groups_to_channel_modal.test.tsx.snap index 340ba8e7e1..cb8e6d7cfb 100644 --- a/webapp/channels/src/components/add_groups_to_channel_modal/__snapshots__/add_groups_to_channel_modal.test.tsx.snap +++ b/webapp/channels/src/components/add_groups_to_channel_modal/__snapshots__/add_groups_to_channel_modal.test.tsx.snap @@ -103,6 +103,7 @@ exports[`components/AddGroupsToChannelModal should match snapshot 1`] = ` "id": "multiselect.addGroupsPlaceholder", } } + required={false} saveButtonPosition="top" saving={false} savingEnabled={true} diff --git a/webapp/channels/src/components/add_groups_to_team_modal/__snapshots__/add_groups_to_team_modal.test.tsx.snap b/webapp/channels/src/components/add_groups_to_team_modal/__snapshots__/add_groups_to_team_modal.test.tsx.snap index 4cc13d2c43..53e6f5b94b 100644 --- a/webapp/channels/src/components/add_groups_to_team_modal/__snapshots__/add_groups_to_team_modal.test.tsx.snap +++ b/webapp/channels/src/components/add_groups_to_team_modal/__snapshots__/add_groups_to_team_modal.test.tsx.snap @@ -140,6 +140,7 @@ exports[`components/AddGroupsToTeamModal should match snapshot 1`] = ` "id": "multiselect.addGroupsPlaceholder", } } + required={false} saveButtonPosition="top" saving={false} savingEnabled={true} diff --git a/webapp/channels/src/components/add_user_to_group_multiselect/__snapshots__/add_user_to_group_multiselect.test.tsx.snap b/webapp/channels/src/components/add_user_to_group_multiselect/__snapshots__/add_user_to_group_multiselect.test.tsx.snap index ca6da7b7ee..0b201a6c30 100644 --- a/webapp/channels/src/components/add_user_to_group_multiselect/__snapshots__/add_user_to_group_multiselect.test.tsx.snap +++ b/webapp/channels/src/components/add_user_to_group_multiselect/__snapshots__/add_user_to_group_multiselect.test.tsx.snap @@ -76,6 +76,7 @@ exports[`component/add_user_to_group_multiselect should match snapshot with diff "id": "multiselect.placeholder", } } + required={true} saveButtonPosition="bottom" saving={false} savingEnabled={false} @@ -175,6 +176,7 @@ exports[`component/add_user_to_group_multiselect should match snapshot with prof "id": "multiselect.placeholder", } } + required={true} saveButtonPosition="bottom" saving={false} savingEnabled={false} @@ -259,6 +261,7 @@ exports[`component/add_user_to_group_multiselect should match snapshot without a "id": "multiselect.placeholder", } } + required={true} saveButtonPosition="bottom" saving={false} savingEnabled={false} diff --git a/webapp/channels/src/components/add_user_to_group_multiselect/add_user_to_group_multiselect.tsx b/webapp/channels/src/components/add_user_to_group_multiselect/add_user_to_group_multiselect.tsx index bea2d2a966..7e64a073d2 100644 --- a/webapp/channels/src/components/add_user_to_group_multiselect/add_user_to_group_multiselect.tsx +++ b/webapp/channels/src/components/add_user_to_group_multiselect/add_user_to_group_multiselect.tsx @@ -245,6 +245,7 @@ export class AddUserToGroupMultiSelect extends React.PureComponent backButtonText={this.props.backButtonText} maxValues={maxValues} numRemainingText={numRemainingText} + required={true} /> ); }; diff --git a/webapp/channels/src/components/add_users_to_team_modal/__snapshots__/add_users_to_team_modal.test.tsx.snap b/webapp/channels/src/components/add_users_to_team_modal/__snapshots__/add_users_to_team_modal.test.tsx.snap index 66ea8edec8..6a6669f136 100644 --- a/webapp/channels/src/components/add_users_to_team_modal/__snapshots__/add_users_to_team_modal.test.tsx.snap +++ b/webapp/channels/src/components/add_users_to_team_modal/__snapshots__/add_users_to_team_modal.test.tsx.snap @@ -225,6 +225,7 @@ exports[`components/admin_console/add_users_to_team_modal/AddUsersToTeamModal sh "id": "multiselect.placeholder", } } + required={false} saveButtonPosition="top" saving={false} savingEnabled={true} @@ -466,6 +467,7 @@ exports[`components/admin_console/add_users_to_team_modal/AddUsersToTeamModal sh "id": "multiselect.placeholder", } } + required={false} saveButtonPosition="top" saving={false} savingEnabled={true} diff --git a/webapp/channels/src/components/admin_console/billing/company_info_edit.scss b/webapp/channels/src/components/admin_console/billing/company_info_edit.scss index 8661c17098..eddfdc836f 100644 --- a/webapp/channels/src/components/admin_console/billing/company_info_edit.scss +++ b/webapp/channels/src/components/admin_console/billing/company_info_edit.scss @@ -84,7 +84,7 @@ } &.Input_fieldset___legend { - >legend { + >label { margin-left: 11px; } } diff --git a/webapp/channels/src/components/admin_console/system_roles/system_role/add_users_to_role_modal/__snapshots__/add_users_to_role_modal.test.tsx.snap b/webapp/channels/src/components/admin_console/system_roles/system_role/add_users_to_role_modal/__snapshots__/add_users_to_role_modal.test.tsx.snap index d7d62e2357..cd29c2112b 100644 --- a/webapp/channels/src/components/admin_console/system_roles/system_role/add_users_to_role_modal/__snapshots__/add_users_to_role_modal.test.tsx.snap +++ b/webapp/channels/src/components/admin_console/system_roles/system_role/add_users_to_role_modal/__snapshots__/add_users_to_role_modal.test.tsx.snap @@ -184,6 +184,7 @@ exports[`admin_console/add_users_to_role_modal search should not include bot use "id": "multiselect.placeholder", } } + required={false} saveButtonPosition="top" saving={false} savingEnabled={true} @@ -335,6 +336,7 @@ exports[`admin_console/add_users_to_role_modal should exclude user 1`] = ` "id": "multiselect.placeholder", } } + required={false} saveButtonPosition="top" saving={false} savingEnabled={true} @@ -530,6 +532,7 @@ exports[`admin_console/add_users_to_role_modal should have single passed value 1 "id": "multiselect.placeholder", } } + required={false} saveButtonPosition="top" saving={false} savingEnabled={true} @@ -766,6 +769,7 @@ exports[`admin_console/add_users_to_role_modal should include additional user 1` "id": "multiselect.placeholder", } } + required={false} saveButtonPosition="top" saving={false} savingEnabled={true} @@ -1002,6 +1006,7 @@ exports[`admin_console/add_users_to_role_modal should include additional user 2` "id": "multiselect.placeholder", } } + required={false} saveButtonPosition="top" saving={false} savingEnabled={true} @@ -1197,6 +1202,7 @@ exports[`admin_console/add_users_to_role_modal should not include bot user 1`] = "id": "multiselect.placeholder", } } + required={false} saveButtonPosition="top" saving={false} savingEnabled={true} diff --git a/webapp/channels/src/components/channel_invite_modal/__snapshots__/channel_invite_modal.test.tsx.snap b/webapp/channels/src/components/channel_invite_modal/__snapshots__/channel_invite_modal.test.tsx.snap index c1d705e852..fc9c36106f 100644 --- a/webapp/channels/src/components/channel_invite_modal/__snapshots__/channel_invite_modal.test.tsx.snap +++ b/webapp/channels/src/components/channel_invite_modal/__snapshots__/channel_invite_modal.test.tsx.snap @@ -106,6 +106,7 @@ exports[`components/channel_invite_modal should match snapshot for channel_invit "id": "multiselect.placeholder.peopleOrGroups", } } + required={false} saveButtonPosition="bottom" saving={false} savingEnabled={true} @@ -233,6 +234,7 @@ exports[`components/channel_invite_modal should match snapshot for channel_invit "id": "multiselect.placeholder.peopleOrGroups", } } + required={false} saveButtonPosition="bottom" saving={false} savingEnabled={true} @@ -360,6 +362,7 @@ exports[`components/channel_invite_modal should match snapshot for channel_invit "id": "multiselect.placeholder.peopleOrGroups", } } + required={false} saveButtonPosition="bottom" saving={false} savingEnabled={true} @@ -487,6 +490,7 @@ exports[`components/channel_invite_modal should match snapshot with exclude and "id": "multiselect.placeholder.peopleOrGroups", } } + required={false} saveButtonPosition="bottom" saving={false} savingEnabled={true} diff --git a/webapp/channels/src/components/channel_invite_modal/channel_invite_modal.scss b/webapp/channels/src/components/channel_invite_modal/channel_invite_modal.scss index c3b26839fc..682de30d98 100644 --- a/webapp/channels/src/components/channel_invite_modal/channel_invite_modal.scss +++ b/webapp/channels/src/components/channel_invite_modal/channel_invite_modal.scss @@ -11,7 +11,7 @@ &__content { padding: 0; - .multi-select__container { + .multi-select__container, .multi-select__error { padding: 0 !important; } diff --git a/webapp/channels/src/components/channel_selector_modal/__snapshots__/channel_selector_modal.test.tsx.snap b/webapp/channels/src/components/channel_selector_modal/__snapshots__/channel_selector_modal.test.tsx.snap index 9c2ae99c14..46b3c8628e 100644 --- a/webapp/channels/src/components/channel_selector_modal/__snapshots__/channel_selector_modal.test.tsx.snap +++ b/webapp/channels/src/components/channel_selector_modal/__snapshots__/channel_selector_modal.test.tsx.snap @@ -149,6 +149,7 @@ exports[`components/ChannelSelectorModal exclude already selected 1`] = ` "id": "multiselect.addChannelsPlaceholder", } } + required={false} saveButtonPosition="top" saving={false} savingEnabled={true} @@ -336,6 +337,7 @@ exports[`components/ChannelSelectorModal should match snapshot 1`] = ` "id": "multiselect.addChannelsPlaceholder", } } + required={false} saveButtonPosition="top" saving={false} savingEnabled={true} diff --git a/webapp/channels/src/components/create_user_groups_modal/__snapshots__/create_user_groups_modal.test.tsx.snap b/webapp/channels/src/components/create_user_groups_modal/__snapshots__/create_user_groups_modal.test.tsx.snap index ed8d12601e..b38de72f32 100644 --- a/webapp/channels/src/components/create_user_groups_modal/__snapshots__/create_user_groups_modal.test.tsx.snap +++ b/webapp/channels/src/components/create_user_groups_modal/__snapshots__/create_user_groups_modal.test.tsx.snap @@ -24,13 +24,18 @@ exports[`component/create_user_groups_modal should match snapshot with back butt "remove": [Function], } } - onExited={[MockFunction]} + onExited={[Function]} onHide={[Function]} renderBackdrop={[Function]} restoreFocus={true} role="none" show={true} > +
@@ -111,37 +117,42 @@ exports[`component/create_user_groups_modal should match snapshot with back butt "id": "user_groups_modal.mention", } } + required={true} type="text" value="" />
-

- -

-
- + + +
+ -
+ deleteUserCallback={[Function]} + focusOnLoad={false} + multilSelectKey="addUsersToGroupKey" + onSubmitCallback={[Function]} + saving={false} + savingEnabled={false} + /> +
+ @@ -171,13 +182,18 @@ exports[`component/create_user_groups_modal should match snapshot without back b "remove": [Function], } } - onExited={[MockFunction]} + onExited={[Function]} onHide={[Function]} renderBackdrop={[Function]} restoreFocus={true} role="none" show={true} > +
@@ -244,37 +261,42 @@ exports[`component/create_user_groups_modal should match snapshot without back b "id": "user_groups_modal.mention", } } + required={true} type="text" value="" />
-

- -

-
- + + +
+ -
+ deleteUserCallback={[Function]} + focusOnLoad={false} + multilSelectKey="addUsersToGroupKey" + onSubmitCallback={[Function]} + saving={false} + savingEnabled={false} + /> +
+ diff --git a/webapp/channels/src/components/create_user_groups_modal/create_user_groups_modal.tsx b/webapp/channels/src/components/create_user_groups_modal/create_user_groups_modal.tsx index f32a0b99f2..7bf8720a2b 100644 --- a/webapp/channels/src/components/create_user_groups_modal/create_user_groups_modal.tsx +++ b/webapp/channels/src/components/create_user_groups_modal/create_user_groups_modal.tsx @@ -41,6 +41,7 @@ type State = { nameInputErrorText: React.ReactNode; showUnknownError: boolean; saving: boolean; + saved: boolean; } export class CreateUserGroupsModal extends React.PureComponent { @@ -58,6 +59,7 @@ export class CreateUserGroupsModal extends React.PureComponent { nameInputErrorText: '', showUnknownError: false, saving: false, + saved: false, }; } @@ -92,15 +94,22 @@ export class CreateUserGroupsModal extends React.PureComponent { this.setState({usersToAdd}); }; - goBack = () => { + onExited = () => { if (typeof this.props.backButtonCallback === 'function') { this.props.backButtonCallback(); - this.props.onExited(); } + this.props.onExited(); }; createGroup = async (users?: UserProfile[]) => { - this.setState({showUnknownError: false, mentionInputErrorText: '', nameInputErrorText: '', saving: true}); + this.setState({ + showUnknownError: false, + mentionInputErrorText: '', + nameInputErrorText: '', + saving: true, + saved: false, + }); + let mention = this.state.mention; const displayName = this.state.name; @@ -200,9 +209,8 @@ export class CreateUserGroupsModal extends React.PureComponent { this.setState({showUnknownError: true}); } this.setState({saving: false}); - } else if (typeof this.props.backButtonCallback === 'function') { - this.goBack(); } else { + this.setState({saved: true}); this.doHide(); } }; @@ -213,11 +221,23 @@ export class CreateUserGroupsModal extends React.PureComponent { dialogClassName='a11y__modal user-groups-modal-create' show={this.state.show} onHide={this.doHide} - onExited={this.props.onExited} + onExited={this.onExited} role='none' aria-labelledby='createUserGroupsModalLabel' id='createUserGroupsModal' > +
+ {this.state.saved && + + } +
{ typeof this.props.backButtonCallback === 'function' ? ( @@ -226,9 +246,7 @@ export class CreateUserGroupsModal extends React.PureComponent { type='button' className='modal-header-back-button btn btn-icon' aria-label={this.props.intl.formatMessage({id: 'user_groups_modal.goBackLabel', defaultMessage: 'Back'})} - onClick={() => { - this.goBack(); - }} + onClick={this.doHide} > @@ -267,6 +285,7 @@ export class CreateUserGroupsModal extends React.PureComponent { data-testid='nameInput' maxLength={64} autoFocus={true} + required={true} customMessage={{type: ItemStatus.ERROR, value: this.state.nameInputErrorText}} /> @@ -278,31 +297,32 @@ export class CreateUserGroupsModal extends React.PureComponent { value={this.state.mention} maxLength={64} data-testid='mentionInput' + required={true} customMessage={{type: ItemStatus.ERROR, value: this.state.mentionInputErrorText}} /> -

- -

-
- -
+
+ + + +
+ +
+
{ this.state.showUnknownError &&
diff --git a/webapp/channels/src/components/multiselect/__snapshots__/multiselect.test.tsx.snap b/webapp/channels/src/components/multiselect/__snapshots__/multiselect.test.tsx.snap index babdb28089..284487b248 100644 --- a/webapp/channels/src/components/multiselect/__snapshots__/multiselect.test.tsx.snap +++ b/webapp/channels/src/components/multiselect/__snapshots__/multiselect.test.tsx.snap @@ -12,6 +12,7 @@ exports[`components/multiselect/multiselect MultiSelectList should match snapsho className="multi-select__container react-select" > = { savingEnabled?: boolean; handleCancel?: () => void; customNoOptionsMessage?: React.ReactNode; + required?: boolean; } export type State = { a11yActive: boolean; input: string; page: number; + hasError: boolean; } const KeyCodes = Constants.KeyCodes; @@ -88,6 +90,7 @@ export class MultiSelect extends React.PureComponent, valueWithImage: false, focusOnLoad: true, savingEnabled: true, + required: false, }; public constructor(props: Props) { @@ -97,9 +100,16 @@ export class MultiSelect extends React.PureComponent, a11yActive: false, page: 0, input: '', + hasError: false, }; } + public componentDidUpdate(prevProps: Props) { + if (prevProps.values !== this.props.values) { + this.validateInput(); + } + } + public componentDidMount() { const inputRef: unknown = this.reactSelectRef.current && this.reactSelectRef.current.inputRef; @@ -265,12 +275,6 @@ export class MultiSelect extends React.PureComponent, this.props.handleDelete(values); }; - MultiValueRemove = ({children, innerProps}: any) => ( -
- {children || } -
- ); - formatOptionLabel = (user: any) => { const profileImg = imageURLForUser(user.id, user.last_picture_update); @@ -292,6 +296,33 @@ export class MultiSelect extends React.PureComponent, return this.props.valueWithImage ? : this.props.valueRenderer; }; + private MultiValueRemove = ({children, innerProps, data}: MultiValueRemoveProps) => ( +
{ + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + e.stopPropagation(); + innerProps.onClick?.(e as unknown as React.MouseEvent); + } + }} + > + {children || } +
+ ); + + private validateInput = () => { + this.setState({hasError: Boolean(this.props.required && this.props.values.length === 0)}); + }; + public render() { const options = Object.assign([...this.props.options]); const {totalCount, users, values} = this.props; @@ -463,7 +494,11 @@ export class MultiSelect extends React.PureComponent, <>
-
+
} // type of ref on @types/react-select is outdated @@ -491,6 +526,9 @@ export class MultiSelect extends React.PureComponent, aria-label={formatAsString(this.props.intl.formatMessage, this.props.placeholderText)} className={this.state.a11yActive ? 'multi-select__focused' : ''} classNamePrefix='react-select-auto react-select' + aria-invalid={this.state.hasError} + aria-describedby={this.state.hasError ? 'multiSelectMessageError' : undefined} + onBlur={this.validateInput} /> {this.props.saveButtonPosition === 'top' && extends React.PureComponent, > {noteTextContainer}
+ {this.state.hasError && ( + + )} {this.props.saveButtonPosition === 'top' &&
{previousButton} diff --git a/webapp/channels/src/components/multiselect/multiselect_list.tsx b/webapp/channels/src/components/multiselect/multiselect_list.tsx index e6535eba0c..5865ea235a 100644 --- a/webapp/channels/src/components/multiselect/multiselect_list.tsx +++ b/webapp/channels/src/components/multiselect/multiselect_list.tsx @@ -213,6 +213,17 @@ export default class MultiSelectList extends React.PureComponen renderOutput = (
+
+ +
-
-
-
+
- +
-
-
-
+
`; diff --git a/webapp/channels/src/components/widgets/inputs/input/input.test.tsx b/webapp/channels/src/components/widgets/inputs/input/input.test.tsx index 4b96fb14e5..ddfcb21fab 100644 --- a/webapp/channels/src/components/widgets/inputs/input/input.test.tsx +++ b/webapp/channels/src/components/widgets/inputs/input/input.test.tsx @@ -77,7 +77,7 @@ describe('components/widgets/inputs/Input', () => { }); // Check for error styling - const fieldset = screen.getByRole('group'); + const fieldset = screen.getByTestId('input-wrapper'); expect(fieldset).toHaveClass('Input_fieldset___error'); // Check for error message @@ -108,7 +108,7 @@ describe('components/widgets/inputs/Input', () => { }); // Check for error styling - const fieldset = screen.getByRole('group'); + const fieldset = screen.getByTestId('input-wrapper'); expect(fieldset).toHaveClass('Input_fieldset___error'); // Check for error message @@ -159,7 +159,7 @@ describe('components/widgets/inputs/Input', () => { }); // Check for error styling - const fieldset = screen.getByRole('group'); + const fieldset = screen.getByTestId('input-wrapper'); expect(fieldset).toHaveClass('Input_fieldset___error'); }); @@ -205,7 +205,7 @@ describe('components/widgets/inputs/Input', () => { expect(errorMessage).toBeInTheDocument(); // Check for error styling - const fieldset = screen.getByRole('group'); + const fieldset = screen.getByTestId('input-wrapper'); expect(fieldset).toHaveClass('Input_fieldset___error'); }); diff --git a/webapp/channels/src/components/widgets/inputs/input/input.tsx b/webapp/channels/src/components/widgets/inputs/input/input.tsx index 614e459f01..1e8e854206 100644 --- a/webapp/channels/src/components/widgets/inputs/input/input.tsx +++ b/webapp/channels/src/components/widgets/inputs/input/input.tsx @@ -256,16 +256,17 @@ const Input = React.forwardRef(( return (
-
{useLegend && ( - + + )}
{inputPrefix} @@ -275,7 +276,7 @@ const Input = React.forwardRef(( {clearButton}
{addon} -
+
{/* Display custom or derived error messages */} {customInputLabel && (
{ - test('should match snapshot', () => { - const wrapper = shallow( - , - ); - expect(wrapper).toMatchInlineSnapshot(` - - - - `); - }); -}); diff --git a/webapp/channels/src/components/widgets/inputs/users_emails_input.tsx b/webapp/channels/src/components/widgets/inputs/users_emails_input.tsx index 7c2be22f58..51141183b3 100644 --- a/webapp/channels/src/components/widgets/inputs/users_emails_input.tsx +++ b/webapp/channels/src/components/widgets/inputs/users_emails_input.tsx @@ -4,8 +4,8 @@ import classNames from 'classnames'; import React from 'react'; import type {RefObject} from 'react'; -import type {MessageDescriptor} from 'react-intl'; -import {FormattedMessage, defineMessages} from 'react-intl'; +import type {MessageDescriptor, IntlShape} from 'react-intl'; +import {FormattedMessage, defineMessages, injectIntl} from 'react-intl'; import {components} from 'react-select'; import type {FormatOptionLabelMeta, InputActionMeta, InputProps, Options, StylesConfig, SelectInstance, MultiValue, SingleValue, OptionsOrGroups, GroupBase, MultiValueRemoveProps} from 'react-select'; import AsyncCreatable from 'react-select/async-creatable'; @@ -50,6 +50,7 @@ type Props = { autoFocus?: boolean; suppressNoOptionsMessage?: boolean; onPaste?: (e: ClipboardEvent) => void; + intl: IntlShape; } export type EmailInvite = { @@ -79,7 +80,7 @@ const messages = defineMessages({ }, }); -export default class UsersEmailsInput extends React.PureComponent { +export class UsersEmailsInput extends React.PureComponent { static defaultProps = { noMatchMessage: messages.noMatchDefault, validAddress: messages.validAddressDefault, @@ -259,6 +260,18 @@ export default class UsersEmailsInput extends React.PureComponent ); }; + private renderAriaLabel = (option: UserProfile | EmailInvite): string => { + if (!option) { + return ''; + } + + if (this.isUserProfile(option)) { + return (option as UserProfile).username; + } + + return (option as EmailInvite).value; + }; + MultiValueRemove = (props: MultiValueRemoveProps) => { const {children, innerProps} = props; @@ -267,6 +280,12 @@ export default class UsersEmailsInput extends React.PureComponent {...innerProps} role='button' tabIndex={0} + aria-label={this.props.intl.formatMessage({ + id: 'multiselect.remove', + defaultMessage: 'Remove {label}', + }, { + label: this.renderAriaLabel(props.data), + })} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); @@ -564,3 +583,5 @@ export default class UsersEmailsInput extends React.PureComponent ); } } + +export default injectIntl(UsersEmailsInput); diff --git a/webapp/channels/src/i18n/en.json b/webapp/channels/src/i18n/en.json index 75d9e62ce7..ac6403634f 100644 --- a/webapp/channels/src/i18n/en.json +++ b/webapp/channels/src/i18n/en.json @@ -4736,6 +4736,7 @@ "multiselect.creating": "Creating...", "multiselect.go": "Go", "multiselect.list.notFound": "No results found matching {searchQuery}", + "multiselect.list.resultsAvailable": "{count, plural, one {# result found} other {# results found}} for your search.", "multiselect.loading": "Loading...", "multiselect.maxGroupMembers": "No more than 256 members can be added to a group at once.", "multiselect.maxPeople": "Use ↑↓ to browse, ↵ to select. You can't add more than 7 people. Please create a channel to include more people. ", @@ -4745,6 +4746,8 @@ "multiselect.numRemaining": "Up to {max, number} can be added at a time. You have {num, number} remaining.", "multiselect.placeholder": "Search for people", "multiselect.placeholder.peopleOrGroups": "Search for people or groups", + "multiselect.remove": "Remove {label}", + "multiselect.required": "This field is required", "multiselect.saveDetailsButton": "Save Details", "multiselect.savingDetailsButton": "Saving...", "multiselect.selectChannels": "Use ↑↓ to browse, ↵ to select.", @@ -5764,6 +5767,7 @@ "user_groups_modal.editGroupTitle": "Edit Group Details", "user_groups_modal.filterAriaLabel": "Groups Filter", "user_groups_modal.goBackLabel": "Back", + "user_groups_modal.groupCreatedSuccess": "Group created successfully", "user_groups_modal.joinGroup": "Join Group", "user_groups_modal.leaveGroup": "Leave Group", "user_groups_modal.memberCount": "{member_count} {member_count, plural, one {member} other {members}}", diff --git a/webapp/channels/src/sass/components/_channel-invite-modal.scss b/webapp/channels/src/sass/components/_channel-invite-modal.scss index c4aa66f2b5..c0628ff19c 100644 --- a/webapp/channels/src/sass/components/_channel-invite-modal.scss +++ b/webapp/channels/src/sass/components/_channel-invite-modal.scss @@ -3,7 +3,7 @@ .modal .channel-invite__content, .modal .user-groups-modal__content { - .multi-select__container { + .multi-select__container, .multi-select__error { height: auto; padding: 0 3.2rem; } diff --git a/webapp/channels/src/sass/components/_multi-select.scss b/webapp/channels/src/sass/components/_multi-select.scss index c16acec4e5..a6b64ad3cb 100644 --- a/webapp/channels/src/sass/components/_multi-select.scss +++ b/webapp/channels/src/sass/components/_multi-select.scss @@ -9,6 +9,10 @@ height: 40px; margin-left: 16px; } + + &.has-error.react-select .react-select__control.react-select-auto { + border-color: var(--error-text); + } } .multi-select__focused { @@ -52,3 +56,23 @@ } } } + +.multi-select__error { + display: flex; + margin-top: 5px; + color: var(--error-text); + font-size: 12px; + line-height: 16px; + text-align: left; + + i { + height: 14px; + align-self: baseline; + margin-right: 7px; + font-size: 14px; + + &::before { + margin: 0; + } + } +} diff --git a/webapp/channels/src/sass/responsive/_mobile.scss b/webapp/channels/src/sass/responsive/_mobile.scss index 1044f73a9f..dac0e85d54 100644 --- a/webapp/channels/src/sass/responsive/_mobile.scss +++ b/webapp/channels/src/sass/responsive/_mobile.scss @@ -525,7 +525,7 @@ border: none; } - .multi-select__container { + .multi-select__container, .multi-select__error { .btn { display: block; min-width: 50px;