[MM-63035][MM-63970][MM-63978][MM-64028][MM-64019][MM-64022][MM-64026][MM-64023][MM-61625] Create User Group modal accessibility fixes (#31047)
* [MM-63970] Add aria-live for creating a group, always use modal animation * [MM-63978] Move my fix for multiselect remove button focus to the multiselect component * [MM-63035][MM-64028] Change legend to label, fieldset to div in the Input component * [MM-64019] Add fieldset and legend to Add people * [MM-64026] Add required to Name and Mention boxes * [MM-64023] Add results available aria-live area to multiselect * [MM-64022] Restore original multivalueremove for users_email_input, use ariaLabelRenderer for Multiselect * Use noteText for required for now * Fix * Fix e2e * Only show required prompt when there are no users picked * Fix cancel button submitting form * PR feedback * Do the required field for multiselect properly * Other PR feedback * Fix checks --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
3d2aa70b7b
Коммит
26613fb4c6
@@ -71,7 +71,7 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// * Verify if the reader is able to read out the selected row
|
// * 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').
|
should('have.attr', 'aria-live', 'polite').
|
||||||
and('have.attr', 'aria-atomic', 'true').
|
and('have.attr', 'aria-atomic', 'true').
|
||||||
invoke('text').then((text) => {
|
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
|
// * 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').
|
should('have.attr', 'aria-live', 'polite').
|
||||||
and('have.attr', 'aria-atomic', 'true').
|
and('have.attr', 'aria-atomic', 'true').
|
||||||
invoke('text').then((text) => {
|
invoke('text').then((text) => {
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ exports[`components/AddGroupsToChannelModal should match snapshot 1`] = `
|
|||||||
"id": "multiselect.addGroupsPlaceholder",
|
"id": "multiselect.addGroupsPlaceholder",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={false}
|
||||||
saveButtonPosition="top"
|
saveButtonPosition="top"
|
||||||
saving={false}
|
saving={false}
|
||||||
savingEnabled={true}
|
savingEnabled={true}
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ exports[`components/AddGroupsToTeamModal should match snapshot 1`] = `
|
|||||||
"id": "multiselect.addGroupsPlaceholder",
|
"id": "multiselect.addGroupsPlaceholder",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={false}
|
||||||
saveButtonPosition="top"
|
saveButtonPosition="top"
|
||||||
saving={false}
|
saving={false}
|
||||||
savingEnabled={true}
|
savingEnabled={true}
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ exports[`component/add_user_to_group_multiselect should match snapshot with diff
|
|||||||
"id": "multiselect.placeholder",
|
"id": "multiselect.placeholder",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={true}
|
||||||
saveButtonPosition="bottom"
|
saveButtonPosition="bottom"
|
||||||
saving={false}
|
saving={false}
|
||||||
savingEnabled={false}
|
savingEnabled={false}
|
||||||
@@ -175,6 +176,7 @@ exports[`component/add_user_to_group_multiselect should match snapshot with prof
|
|||||||
"id": "multiselect.placeholder",
|
"id": "multiselect.placeholder",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={true}
|
||||||
saveButtonPosition="bottom"
|
saveButtonPosition="bottom"
|
||||||
saving={false}
|
saving={false}
|
||||||
savingEnabled={false}
|
savingEnabled={false}
|
||||||
@@ -259,6 +261,7 @@ exports[`component/add_user_to_group_multiselect should match snapshot without a
|
|||||||
"id": "multiselect.placeholder",
|
"id": "multiselect.placeholder",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={true}
|
||||||
saveButtonPosition="bottom"
|
saveButtonPosition="bottom"
|
||||||
saving={false}
|
saving={false}
|
||||||
savingEnabled={false}
|
savingEnabled={false}
|
||||||
|
|||||||
@@ -245,6 +245,7 @@ export class AddUserToGroupMultiSelect extends React.PureComponent<Props, State>
|
|||||||
backButtonText={this.props.backButtonText}
|
backButtonText={this.props.backButtonText}
|
||||||
maxValues={maxValues}
|
maxValues={maxValues}
|
||||||
numRemainingText={numRemainingText}
|
numRemainingText={numRemainingText}
|
||||||
|
required={true}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -225,6 +225,7 @@ exports[`components/admin_console/add_users_to_team_modal/AddUsersToTeamModal sh
|
|||||||
"id": "multiselect.placeholder",
|
"id": "multiselect.placeholder",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={false}
|
||||||
saveButtonPosition="top"
|
saveButtonPosition="top"
|
||||||
saving={false}
|
saving={false}
|
||||||
savingEnabled={true}
|
savingEnabled={true}
|
||||||
@@ -466,6 +467,7 @@ exports[`components/admin_console/add_users_to_team_modal/AddUsersToTeamModal sh
|
|||||||
"id": "multiselect.placeholder",
|
"id": "multiselect.placeholder",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={false}
|
||||||
saveButtonPosition="top"
|
saveButtonPosition="top"
|
||||||
saving={false}
|
saving={false}
|
||||||
savingEnabled={true}
|
savingEnabled={true}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.Input_fieldset___legend {
|
&.Input_fieldset___legend {
|
||||||
>legend {
|
>label {
|
||||||
margin-left: 11px;
|
margin-left: 11px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ exports[`admin_console/add_users_to_role_modal search should not include bot use
|
|||||||
"id": "multiselect.placeholder",
|
"id": "multiselect.placeholder",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={false}
|
||||||
saveButtonPosition="top"
|
saveButtonPosition="top"
|
||||||
saving={false}
|
saving={false}
|
||||||
savingEnabled={true}
|
savingEnabled={true}
|
||||||
@@ -335,6 +336,7 @@ exports[`admin_console/add_users_to_role_modal should exclude user 1`] = `
|
|||||||
"id": "multiselect.placeholder",
|
"id": "multiselect.placeholder",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={false}
|
||||||
saveButtonPosition="top"
|
saveButtonPosition="top"
|
||||||
saving={false}
|
saving={false}
|
||||||
savingEnabled={true}
|
savingEnabled={true}
|
||||||
@@ -530,6 +532,7 @@ exports[`admin_console/add_users_to_role_modal should have single passed value 1
|
|||||||
"id": "multiselect.placeholder",
|
"id": "multiselect.placeholder",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={false}
|
||||||
saveButtonPosition="top"
|
saveButtonPosition="top"
|
||||||
saving={false}
|
saving={false}
|
||||||
savingEnabled={true}
|
savingEnabled={true}
|
||||||
@@ -766,6 +769,7 @@ exports[`admin_console/add_users_to_role_modal should include additional user 1`
|
|||||||
"id": "multiselect.placeholder",
|
"id": "multiselect.placeholder",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={false}
|
||||||
saveButtonPosition="top"
|
saveButtonPosition="top"
|
||||||
saving={false}
|
saving={false}
|
||||||
savingEnabled={true}
|
savingEnabled={true}
|
||||||
@@ -1002,6 +1006,7 @@ exports[`admin_console/add_users_to_role_modal should include additional user 2`
|
|||||||
"id": "multiselect.placeholder",
|
"id": "multiselect.placeholder",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={false}
|
||||||
saveButtonPosition="top"
|
saveButtonPosition="top"
|
||||||
saving={false}
|
saving={false}
|
||||||
savingEnabled={true}
|
savingEnabled={true}
|
||||||
@@ -1197,6 +1202,7 @@ exports[`admin_console/add_users_to_role_modal should not include bot user 1`] =
|
|||||||
"id": "multiselect.placeholder",
|
"id": "multiselect.placeholder",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={false}
|
||||||
saveButtonPosition="top"
|
saveButtonPosition="top"
|
||||||
saving={false}
|
saving={false}
|
||||||
savingEnabled={true}
|
savingEnabled={true}
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ exports[`components/channel_invite_modal should match snapshot for channel_invit
|
|||||||
"id": "multiselect.placeholder.peopleOrGroups",
|
"id": "multiselect.placeholder.peopleOrGroups",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={false}
|
||||||
saveButtonPosition="bottom"
|
saveButtonPosition="bottom"
|
||||||
saving={false}
|
saving={false}
|
||||||
savingEnabled={true}
|
savingEnabled={true}
|
||||||
@@ -233,6 +234,7 @@ exports[`components/channel_invite_modal should match snapshot for channel_invit
|
|||||||
"id": "multiselect.placeholder.peopleOrGroups",
|
"id": "multiselect.placeholder.peopleOrGroups",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={false}
|
||||||
saveButtonPosition="bottom"
|
saveButtonPosition="bottom"
|
||||||
saving={false}
|
saving={false}
|
||||||
savingEnabled={true}
|
savingEnabled={true}
|
||||||
@@ -360,6 +362,7 @@ exports[`components/channel_invite_modal should match snapshot for channel_invit
|
|||||||
"id": "multiselect.placeholder.peopleOrGroups",
|
"id": "multiselect.placeholder.peopleOrGroups",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={false}
|
||||||
saveButtonPosition="bottom"
|
saveButtonPosition="bottom"
|
||||||
saving={false}
|
saving={false}
|
||||||
savingEnabled={true}
|
savingEnabled={true}
|
||||||
@@ -487,6 +490,7 @@ exports[`components/channel_invite_modal should match snapshot with exclude and
|
|||||||
"id": "multiselect.placeholder.peopleOrGroups",
|
"id": "multiselect.placeholder.peopleOrGroups",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={false}
|
||||||
saveButtonPosition="bottom"
|
saveButtonPosition="bottom"
|
||||||
saving={false}
|
saving={false}
|
||||||
savingEnabled={true}
|
savingEnabled={true}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
&__content {
|
&__content {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
.multi-select__container {
|
.multi-select__container, .multi-select__error {
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ exports[`components/ChannelSelectorModal exclude already selected 1`] = `
|
|||||||
"id": "multiselect.addChannelsPlaceholder",
|
"id": "multiselect.addChannelsPlaceholder",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={false}
|
||||||
saveButtonPosition="top"
|
saveButtonPosition="top"
|
||||||
saving={false}
|
saving={false}
|
||||||
savingEnabled={true}
|
savingEnabled={true}
|
||||||
@@ -336,6 +337,7 @@ exports[`components/ChannelSelectorModal should match snapshot 1`] = `
|
|||||||
"id": "multiselect.addChannelsPlaceholder",
|
"id": "multiselect.addChannelsPlaceholder",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={false}
|
||||||
saveButtonPosition="top"
|
saveButtonPosition="top"
|
||||||
saving={false}
|
saving={false}
|
||||||
savingEnabled={true}
|
savingEnabled={true}
|
||||||
|
|||||||
@@ -24,13 +24,18 @@ exports[`component/create_user_groups_modal should match snapshot with back butt
|
|||||||
"remove": [Function],
|
"remove": [Function],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onExited={[MockFunction]}
|
onExited={[Function]}
|
||||||
onHide={[Function]}
|
onHide={[Function]}
|
||||||
renderBackdrop={[Function]}
|
renderBackdrop={[Function]}
|
||||||
restoreFocus={true}
|
restoreFocus={true}
|
||||||
role="none"
|
role="none"
|
||||||
show={true}
|
show={true}
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
aria-live="polite"
|
||||||
|
className="sr-only"
|
||||||
|
role="alert"
|
||||||
|
/>
|
||||||
<ModalHeader
|
<ModalHeader
|
||||||
bsClass="modal-header"
|
bsClass="modal-header"
|
||||||
closeButton={true}
|
closeButton={true}
|
||||||
@@ -88,6 +93,7 @@ exports[`component/create_user_groups_modal should match snapshot with back butt
|
|||||||
"id": "user_groups_modal.name",
|
"id": "user_groups_modal.name",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={true}
|
||||||
type="text"
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
@@ -111,37 +117,42 @@ exports[`component/create_user_groups_modal should match snapshot with back butt
|
|||||||
"id": "user_groups_modal.mention",
|
"id": "user_groups_modal.mention",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={true}
|
||||||
type="text"
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h2>
|
<fieldset
|
||||||
<MemoizedFormattedMessage
|
className="group-add-people-fieldset"
|
||||||
defaultMessage="Add People"
|
|
||||||
id="user_groups_modal.addPeople"
|
|
||||||
/>
|
|
||||||
</h2>
|
|
||||||
<div
|
|
||||||
className="group-add-user"
|
|
||||||
>
|
>
|
||||||
<Connect(injectIntl(AddUserToGroupMultiSelect))
|
<legend>
|
||||||
addUserCallback={[Function]}
|
<MemoizedFormattedMessage
|
||||||
backButtonClass="multiselect-back"
|
defaultMessage="Add People"
|
||||||
backButtonClick={[Function]}
|
id="user_groups_modal.addPeople"
|
||||||
backButtonText={
|
/>
|
||||||
Object {
|
</legend>
|
||||||
"defaultMessage": "Cancel",
|
<div
|
||||||
"id": "multiselect.cancelButton",
|
className="group-add-user"
|
||||||
|
>
|
||||||
|
<Connect(injectIntl(AddUserToGroupMultiSelect))
|
||||||
|
addUserCallback={[Function]}
|
||||||
|
backButtonClass="multiselect-back"
|
||||||
|
backButtonClick={[Function]}
|
||||||
|
backButtonText={
|
||||||
|
Object {
|
||||||
|
"defaultMessage": "Cancel",
|
||||||
|
"id": "multiselect.cancelButton",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
deleteUserCallback={[Function]}
|
||||||
deleteUserCallback={[Function]}
|
focusOnLoad={false}
|
||||||
focusOnLoad={false}
|
multilSelectKey="addUsersToGroupKey"
|
||||||
multilSelectKey="addUsersToGroupKey"
|
onSubmitCallback={[Function]}
|
||||||
onSubmitCallback={[Function]}
|
saving={false}
|
||||||
saving={false}
|
savingEnabled={false}
|
||||||
savingEnabled={false}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
</Modal>
|
</Modal>
|
||||||
@@ -171,13 +182,18 @@ exports[`component/create_user_groups_modal should match snapshot without back b
|
|||||||
"remove": [Function],
|
"remove": [Function],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onExited={[MockFunction]}
|
onExited={[Function]}
|
||||||
onHide={[Function]}
|
onHide={[Function]}
|
||||||
renderBackdrop={[Function]}
|
renderBackdrop={[Function]}
|
||||||
restoreFocus={true}
|
restoreFocus={true}
|
||||||
role="none"
|
role="none"
|
||||||
show={true}
|
show={true}
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
aria-live="polite"
|
||||||
|
className="sr-only"
|
||||||
|
role="alert"
|
||||||
|
/>
|
||||||
<ModalHeader
|
<ModalHeader
|
||||||
bsClass="modal-header"
|
bsClass="modal-header"
|
||||||
closeButton={true}
|
closeButton={true}
|
||||||
@@ -221,6 +237,7 @@ exports[`component/create_user_groups_modal should match snapshot without back b
|
|||||||
"id": "user_groups_modal.name",
|
"id": "user_groups_modal.name",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={true}
|
||||||
type="text"
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
@@ -244,37 +261,42 @@ exports[`component/create_user_groups_modal should match snapshot without back b
|
|||||||
"id": "user_groups_modal.mention",
|
"id": "user_groups_modal.mention",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
required={true}
|
||||||
type="text"
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h2>
|
<fieldset
|
||||||
<MemoizedFormattedMessage
|
className="group-add-people-fieldset"
|
||||||
defaultMessage="Add People"
|
|
||||||
id="user_groups_modal.addPeople"
|
|
||||||
/>
|
|
||||||
</h2>
|
|
||||||
<div
|
|
||||||
className="group-add-user"
|
|
||||||
>
|
>
|
||||||
<Connect(injectIntl(AddUserToGroupMultiSelect))
|
<legend>
|
||||||
addUserCallback={[Function]}
|
<MemoizedFormattedMessage
|
||||||
backButtonClass="multiselect-back"
|
defaultMessage="Add People"
|
||||||
backButtonClick={[Function]}
|
id="user_groups_modal.addPeople"
|
||||||
backButtonText={
|
/>
|
||||||
Object {
|
</legend>
|
||||||
"defaultMessage": "Cancel",
|
<div
|
||||||
"id": "multiselect.cancelButton",
|
className="group-add-user"
|
||||||
|
>
|
||||||
|
<Connect(injectIntl(AddUserToGroupMultiSelect))
|
||||||
|
addUserCallback={[Function]}
|
||||||
|
backButtonClass="multiselect-back"
|
||||||
|
backButtonClick={[Function]}
|
||||||
|
backButtonText={
|
||||||
|
Object {
|
||||||
|
"defaultMessage": "Cancel",
|
||||||
|
"id": "multiselect.cancelButton",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
deleteUserCallback={[Function]}
|
||||||
deleteUserCallback={[Function]}
|
focusOnLoad={false}
|
||||||
focusOnLoad={false}
|
multilSelectKey="addUsersToGroupKey"
|
||||||
multilSelectKey="addUsersToGroupKey"
|
onSubmitCallback={[Function]}
|
||||||
onSubmitCallback={[Function]}
|
saving={false}
|
||||||
saving={false}
|
savingEnabled={false}
|
||||||
savingEnabled={false}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ type State = {
|
|||||||
nameInputErrorText: React.ReactNode;
|
nameInputErrorText: React.ReactNode;
|
||||||
showUnknownError: boolean;
|
showUnknownError: boolean;
|
||||||
saving: boolean;
|
saving: boolean;
|
||||||
|
saved: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CreateUserGroupsModal extends React.PureComponent<Props, State> {
|
export class CreateUserGroupsModal extends React.PureComponent<Props, State> {
|
||||||
@@ -58,6 +59,7 @@ export class CreateUserGroupsModal extends React.PureComponent<Props, State> {
|
|||||||
nameInputErrorText: '',
|
nameInputErrorText: '',
|
||||||
showUnknownError: false,
|
showUnknownError: false,
|
||||||
saving: false,
|
saving: false,
|
||||||
|
saved: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,15 +94,22 @@ export class CreateUserGroupsModal extends React.PureComponent<Props, State> {
|
|||||||
this.setState({usersToAdd});
|
this.setState({usersToAdd});
|
||||||
};
|
};
|
||||||
|
|
||||||
goBack = () => {
|
onExited = () => {
|
||||||
if (typeof this.props.backButtonCallback === 'function') {
|
if (typeof this.props.backButtonCallback === 'function') {
|
||||||
this.props.backButtonCallback();
|
this.props.backButtonCallback();
|
||||||
this.props.onExited();
|
|
||||||
}
|
}
|
||||||
|
this.props.onExited();
|
||||||
};
|
};
|
||||||
|
|
||||||
createGroup = async (users?: UserProfile[]) => {
|
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;
|
let mention = this.state.mention;
|
||||||
const displayName = this.state.name;
|
const displayName = this.state.name;
|
||||||
|
|
||||||
@@ -200,9 +209,8 @@ export class CreateUserGroupsModal extends React.PureComponent<Props, State> {
|
|||||||
this.setState({showUnknownError: true});
|
this.setState({showUnknownError: true});
|
||||||
}
|
}
|
||||||
this.setState({saving: false});
|
this.setState({saving: false});
|
||||||
} else if (typeof this.props.backButtonCallback === 'function') {
|
|
||||||
this.goBack();
|
|
||||||
} else {
|
} else {
|
||||||
|
this.setState({saved: true});
|
||||||
this.doHide();
|
this.doHide();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -213,11 +221,23 @@ export class CreateUserGroupsModal extends React.PureComponent<Props, State> {
|
|||||||
dialogClassName='a11y__modal user-groups-modal-create'
|
dialogClassName='a11y__modal user-groups-modal-create'
|
||||||
show={this.state.show}
|
show={this.state.show}
|
||||||
onHide={this.doHide}
|
onHide={this.doHide}
|
||||||
onExited={this.props.onExited}
|
onExited={this.onExited}
|
||||||
role='none'
|
role='none'
|
||||||
aria-labelledby='createUserGroupsModalLabel'
|
aria-labelledby='createUserGroupsModalLabel'
|
||||||
id='createUserGroupsModal'
|
id='createUserGroupsModal'
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
role='alert'
|
||||||
|
aria-live='polite'
|
||||||
|
className='sr-only'
|
||||||
|
>
|
||||||
|
{this.state.saved &&
|
||||||
|
<FormattedMessage
|
||||||
|
id='user_groups_modal.groupCreatedSuccess'
|
||||||
|
defaultMessage='Group created successfully'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
<Modal.Header closeButton={true}>
|
<Modal.Header closeButton={true}>
|
||||||
{
|
{
|
||||||
typeof this.props.backButtonCallback === 'function' ? (
|
typeof this.props.backButtonCallback === 'function' ? (
|
||||||
@@ -226,9 +246,7 @@ export class CreateUserGroupsModal extends React.PureComponent<Props, State> {
|
|||||||
type='button'
|
type='button'
|
||||||
className='modal-header-back-button btn btn-icon'
|
className='modal-header-back-button btn btn-icon'
|
||||||
aria-label={this.props.intl.formatMessage({id: 'user_groups_modal.goBackLabel', defaultMessage: 'Back'})}
|
aria-label={this.props.intl.formatMessage({id: 'user_groups_modal.goBackLabel', defaultMessage: 'Back'})}
|
||||||
onClick={() => {
|
onClick={this.doHide}
|
||||||
this.goBack();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<i className='icon icon-arrow-left'/>
|
<i className='icon icon-arrow-left'/>
|
||||||
</button>
|
</button>
|
||||||
@@ -267,6 +285,7 @@ export class CreateUserGroupsModal extends React.PureComponent<Props, State> {
|
|||||||
data-testid='nameInput'
|
data-testid='nameInput'
|
||||||
maxLength={64}
|
maxLength={64}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
|
required={true}
|
||||||
customMessage={{type: ItemStatus.ERROR, value: this.state.nameInputErrorText}}
|
customMessage={{type: ItemStatus.ERROR, value: this.state.nameInputErrorText}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -278,31 +297,32 @@ export class CreateUserGroupsModal extends React.PureComponent<Props, State> {
|
|||||||
value={this.state.mention}
|
value={this.state.mention}
|
||||||
maxLength={64}
|
maxLength={64}
|
||||||
data-testid='mentionInput'
|
data-testid='mentionInput'
|
||||||
|
required={true}
|
||||||
customMessage={{type: ItemStatus.ERROR, value: this.state.mentionInputErrorText}}
|
customMessage={{type: ItemStatus.ERROR, value: this.state.mentionInputErrorText}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h2>
|
<fieldset className='group-add-people-fieldset'>
|
||||||
<FormattedMessage
|
<legend>
|
||||||
id='user_groups_modal.addPeople'
|
<FormattedMessage
|
||||||
defaultMessage='Add People'
|
id='user_groups_modal.addPeople'
|
||||||
/>
|
defaultMessage='Add People'
|
||||||
</h2>
|
/>
|
||||||
<div className='group-add-user'>
|
</legend>
|
||||||
<AddUserToGroupMultiSelect
|
<div className='group-add-user'>
|
||||||
multilSelectKey={'addUsersToGroupKey'}
|
<AddUserToGroupMultiSelect
|
||||||
onSubmitCallback={this.createGroup}
|
multilSelectKey={'addUsersToGroupKey'}
|
||||||
focusOnLoad={false}
|
onSubmitCallback={this.createGroup}
|
||||||
savingEnabled={this.isSaveEnabled()}
|
focusOnLoad={false}
|
||||||
addUserCallback={this.addUserCallback}
|
savingEnabled={this.isSaveEnabled()}
|
||||||
deleteUserCallback={this.deleteUserCallback}
|
addUserCallback={this.addUserCallback}
|
||||||
backButtonText={defineMessage({id: 'multiselect.cancelButton', defaultMessage: 'Cancel'})}
|
deleteUserCallback={this.deleteUserCallback}
|
||||||
backButtonClick={
|
backButtonText={defineMessage({id: 'multiselect.cancelButton', defaultMessage: 'Cancel'})}
|
||||||
typeof this.props.backButtonCallback === 'function' ? this.goBack : this.doHide
|
backButtonClick={this.doHide}
|
||||||
}
|
backButtonClass={'multiselect-back'}
|
||||||
backButtonClass={'multiselect-back'}
|
saving={this.state.saving}
|
||||||
saving={this.state.saving}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</fieldset>
|
||||||
{
|
{
|
||||||
this.state.showUnknownError &&
|
this.state.showUnknownError &&
|
||||||
<div className='Input___error group-error'>
|
<div className='Input___error group-error'>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ exports[`components/multiselect/multiselect MultiSelectList should match snapsho
|
|||||||
className="multi-select__container react-select"
|
className="multi-select__container react-select"
|
||||||
>
|
>
|
||||||
<ForwardRef
|
<ForwardRef
|
||||||
|
aria-invalid={false}
|
||||||
className=""
|
className=""
|
||||||
classNamePrefix="react-select-auto react-select"
|
classNamePrefix="react-select-auto react-select"
|
||||||
components={
|
components={
|
||||||
@@ -29,6 +30,7 @@ exports[`components/multiselect/multiselect MultiSelectList should match snapsho
|
|||||||
isClearable={false}
|
isClearable={false}
|
||||||
isMulti={true}
|
isMulti={true}
|
||||||
menuIsOpen={false}
|
menuIsOpen={false}
|
||||||
|
onBlur={[Function]}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onInputChange={[Function]}
|
onInputChange={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
@@ -196,6 +198,7 @@ exports[`components/multiselect/multiselect should match snapshot 1`] = `
|
|||||||
className="multi-select__container react-select"
|
className="multi-select__container react-select"
|
||||||
>
|
>
|
||||||
<ForwardRef
|
<ForwardRef
|
||||||
|
aria-invalid={false}
|
||||||
className=""
|
className=""
|
||||||
classNamePrefix="react-select-auto react-select"
|
classNamePrefix="react-select-auto react-select"
|
||||||
components={
|
components={
|
||||||
@@ -213,6 +216,7 @@ exports[`components/multiselect/multiselect should match snapshot 1`] = `
|
|||||||
isClearable={false}
|
isClearable={false}
|
||||||
isMulti={true}
|
isMulti={true}
|
||||||
menuIsOpen={false}
|
menuIsOpen={false}
|
||||||
|
onBlur={[Function]}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onInputChange={[Function]}
|
onInputChange={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
@@ -371,6 +375,7 @@ exports[`components/multiselect/multiselect should match snapshot for page 2 1`]
|
|||||||
className="multi-select__container react-select"
|
className="multi-select__container react-select"
|
||||||
>
|
>
|
||||||
<ForwardRef
|
<ForwardRef
|
||||||
|
aria-invalid={false}
|
||||||
className=""
|
className=""
|
||||||
classNamePrefix="react-select-auto react-select"
|
classNamePrefix="react-select-auto react-select"
|
||||||
components={
|
components={
|
||||||
@@ -388,6 +393,7 @@ exports[`components/multiselect/multiselect should match snapshot for page 2 1`]
|
|||||||
isClearable={false}
|
isClearable={false}
|
||||||
isMulti={true}
|
isMulti={true}
|
||||||
menuIsOpen={false}
|
menuIsOpen={false}
|
||||||
|
onBlur={[Function]}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onInputChange={[Function]}
|
onInputChange={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type {ComponentProps, ReactNode} from 'react';
|
|||||||
import type {IntlShape, MessageDescriptor} from 'react-intl';
|
import type {IntlShape, MessageDescriptor} from 'react-intl';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
import ReactSelect, {components} from 'react-select';
|
import ReactSelect, {components} from 'react-select';
|
||||||
import type {GetOptionValue, InputActionMeta, SelectInstance} from 'react-select';
|
import type {GetOptionValue, InputActionMeta, MultiValueRemoveProps, SelectInstance} from 'react-select';
|
||||||
|
|
||||||
import SaveButton from 'components/save_button';
|
import SaveButton from 'components/save_button';
|
||||||
import CloseCircleSolidIcon from 'components/widgets/icons/close_circle_solid_icon';
|
import CloseCircleSolidIcon from 'components/widgets/icons/close_circle_solid_icon';
|
||||||
@@ -67,12 +67,14 @@ export type Props<T extends Value> = {
|
|||||||
savingEnabled?: boolean;
|
savingEnabled?: boolean;
|
||||||
handleCancel?: () => void;
|
handleCancel?: () => void;
|
||||||
customNoOptionsMessage?: React.ReactNode;
|
customNoOptionsMessage?: React.ReactNode;
|
||||||
|
required?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
a11yActive: boolean;
|
a11yActive: boolean;
|
||||||
input: string;
|
input: string;
|
||||||
page: number;
|
page: number;
|
||||||
|
hasError: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const KeyCodes = Constants.KeyCodes;
|
const KeyCodes = Constants.KeyCodes;
|
||||||
@@ -88,6 +90,7 @@ export class MultiSelect<T extends Value> extends React.PureComponent<Props<T>,
|
|||||||
valueWithImage: false,
|
valueWithImage: false,
|
||||||
focusOnLoad: true,
|
focusOnLoad: true,
|
||||||
savingEnabled: true,
|
savingEnabled: true,
|
||||||
|
required: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
public constructor(props: Props<T>) {
|
public constructor(props: Props<T>) {
|
||||||
@@ -97,9 +100,16 @@ export class MultiSelect<T extends Value> extends React.PureComponent<Props<T>,
|
|||||||
a11yActive: false,
|
a11yActive: false,
|
||||||
page: 0,
|
page: 0,
|
||||||
input: '',
|
input: '',
|
||||||
|
hasError: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public componentDidUpdate(prevProps: Props<T>) {
|
||||||
|
if (prevProps.values !== this.props.values) {
|
||||||
|
this.validateInput();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public componentDidMount() {
|
public componentDidMount() {
|
||||||
const inputRef: unknown = this.reactSelectRef.current && this.reactSelectRef.current.inputRef;
|
const inputRef: unknown = this.reactSelectRef.current && this.reactSelectRef.current.inputRef;
|
||||||
|
|
||||||
@@ -265,12 +275,6 @@ export class MultiSelect<T extends Value> extends React.PureComponent<Props<T>,
|
|||||||
this.props.handleDelete(values);
|
this.props.handleDelete(values);
|
||||||
};
|
};
|
||||||
|
|
||||||
MultiValueRemove = ({children, innerProps}: any) => (
|
|
||||||
<div {...innerProps}>
|
|
||||||
{children || <CloseCircleSolidIcon/>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
formatOptionLabel = (user: any) => {
|
formatOptionLabel = (user: any) => {
|
||||||
const profileImg = imageURLForUser(user.id, user.last_picture_update);
|
const profileImg = imageURLForUser(user.id, user.last_picture_update);
|
||||||
|
|
||||||
@@ -292,6 +296,33 @@ export class MultiSelect<T extends Value> extends React.PureComponent<Props<T>,
|
|||||||
return this.props.valueWithImage ? <components.MultiValueLabel {...props}/> : this.props.valueRenderer;
|
return this.props.valueWithImage ? <components.MultiValueLabel {...props}/> : this.props.valueRenderer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private MultiValueRemove = ({children, innerProps, data}: MultiValueRemoveProps<T>) => (
|
||||||
|
<div
|
||||||
|
{...innerProps}
|
||||||
|
role='button'
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label={this.props.intl.formatMessage({
|
||||||
|
id: 'multiselect.remove',
|
||||||
|
defaultMessage: 'Remove {label}',
|
||||||
|
}, {
|
||||||
|
label: this.props.ariaLabelRenderer(data),
|
||||||
|
})}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
innerProps.onClick?.(e as unknown as React.MouseEvent<HTMLDivElement, MouseEvent>);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children || <CloseCircleSolidIcon/>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
private validateInput = () => {
|
||||||
|
this.setState({hasError: Boolean(this.props.required && this.props.values.length === 0)});
|
||||||
|
};
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const options = Object.assign([...this.props.options]);
|
const options = Object.assign([...this.props.options]);
|
||||||
const {totalCount, users, values} = this.props;
|
const {totalCount, users, values} = this.props;
|
||||||
@@ -463,7 +494,11 @@ export class MultiSelect<T extends Value> extends React.PureComponent<Props<T>,
|
|||||||
<>
|
<>
|
||||||
<div className='filtered-user-list'>
|
<div className='filtered-user-list'>
|
||||||
<div className='filter-row filter-row--full'>
|
<div className='filter-row filter-row--full'>
|
||||||
<div className='multi-select__container react-select'>
|
<div
|
||||||
|
className={classNames('multi-select__container react-select', {
|
||||||
|
'has-error': this.state.hasError,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<ReactSelect
|
<ReactSelect
|
||||||
id='selectItems'
|
id='selectItems'
|
||||||
ref={this.reactSelectRef as React.RefObject<any>} // type of ref on @types/react-select is outdated
|
ref={this.reactSelectRef as React.RefObject<any>} // type of ref on @types/react-select is outdated
|
||||||
@@ -491,6 +526,9 @@ export class MultiSelect<T extends Value> extends React.PureComponent<Props<T>,
|
|||||||
aria-label={formatAsString(this.props.intl.formatMessage, this.props.placeholderText)}
|
aria-label={formatAsString(this.props.intl.formatMessage, this.props.placeholderText)}
|
||||||
className={this.state.a11yActive ? 'multi-select__focused' : ''}
|
className={this.state.a11yActive ? 'multi-select__focused' : ''}
|
||||||
classNamePrefix='react-select-auto react-select'
|
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' &&
|
{this.props.saveButtonPosition === 'top' &&
|
||||||
<SaveButton
|
<SaveButton
|
||||||
@@ -518,6 +556,18 @@ export class MultiSelect<T extends Value> extends React.PureComponent<Props<T>,
|
|||||||
>
|
>
|
||||||
{noteTextContainer}
|
{noteTextContainer}
|
||||||
</div>
|
</div>
|
||||||
|
{this.state.hasError && (
|
||||||
|
<div
|
||||||
|
className='multi-select__error'
|
||||||
|
role='alert'
|
||||||
|
id='multiSelectMessageError'
|
||||||
|
>
|
||||||
|
<i className='icon icon-alert-circle-outline'/>
|
||||||
|
<span>
|
||||||
|
{this.props.intl.formatMessage({id: 'multiselect.required', defaultMessage: 'This field is required'})}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{this.props.saveButtonPosition === 'top' &&
|
{this.props.saveButtonPosition === 'top' &&
|
||||||
<div className='filter-controls'>
|
<div className='filter-controls'>
|
||||||
{previousButton}
|
{previousButton}
|
||||||
|
|||||||
@@ -213,6 +213,17 @@ export default class MultiSelectList<T extends Value> extends React.PureComponen
|
|||||||
|
|
||||||
renderOutput = (
|
renderOutput = (
|
||||||
<div className='more-modal__list'>
|
<div className='more-modal__list'>
|
||||||
|
<div
|
||||||
|
className='sr-only'
|
||||||
|
aria-live='polite'
|
||||||
|
role='status'
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='multiselect.list.resultsAvailable'
|
||||||
|
defaultMessage='{count, plural, one {# result found} other {# results found}} for your search.'
|
||||||
|
values={{count: options.length}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className='sr-only'
|
className='sr-only'
|
||||||
aria-live='polite'
|
aria-live='polite'
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
padding-bottom: 12px;
|
padding-bottom: 12px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
|
||||||
fieldset {
|
.Input_fieldset {
|
||||||
max-width: 110px;
|
max-width: 110px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,10 +66,11 @@ Object {
|
|||||||
<div
|
<div
|
||||||
class="Input_container"
|
class="Input_container"
|
||||||
>
|
>
|
||||||
<fieldset
|
<div
|
||||||
class="Input_fieldset name_input"
|
class="Input_fieldset name_input"
|
||||||
|
data-testid="input-wrapper"
|
||||||
>
|
>
|
||||||
<legend
|
<label
|
||||||
class="Input_legend"
|
class="Input_legend"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
@@ -85,19 +86,20 @@ Object {
|
|||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="Input_container"
|
class="Input_container"
|
||||||
>
|
>
|
||||||
<fieldset
|
<div
|
||||||
class="Input_fieldset email_input Input_fieldset___error Input_fieldset___legend"
|
class="Input_fieldset email_input Input_fieldset___error Input_fieldset___legend"
|
||||||
|
data-testid="input-wrapper"
|
||||||
>
|
>
|
||||||
<legend
|
<label
|
||||||
class="Input_legend Input_legend___focus"
|
class="Input_legend Input_legend___focus"
|
||||||
>
|
>
|
||||||
Business Email
|
Business Email
|
||||||
</legend>
|
</label>
|
||||||
<div
|
<div
|
||||||
class="Input_wrapper"
|
class="Input_wrapper"
|
||||||
>
|
>
|
||||||
@@ -113,7 +115,7 @@ Object {
|
|||||||
value="test@mattermost.com"
|
value="test@mattermost.com"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="Input___customMessage Input___error"
|
class="Input___customMessage Input___error"
|
||||||
id="error_email"
|
id="error_email"
|
||||||
@@ -132,10 +134,11 @@ Object {
|
|||||||
<div
|
<div
|
||||||
class="Input_container"
|
class="Input_container"
|
||||||
>
|
>
|
||||||
<fieldset
|
<div
|
||||||
class="Input_fieldset company_name_input"
|
class="Input_fieldset company_name_input"
|
||||||
|
data-testid="input-wrapper"
|
||||||
>
|
>
|
||||||
<legend
|
<label
|
||||||
class="Input_legend"
|
class="Input_legend"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
@@ -151,7 +154,7 @@ Object {
|
|||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="DropdownInput Input_container"
|
class="DropdownInput Input_container"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.description-section-input {
|
.description-section-input {
|
||||||
fieldset {
|
.Input_fieldset {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -389,12 +389,19 @@
|
|||||||
padding: 12px 32px 24px;
|
padding: 12px 32px 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
.group-add-people-fieldset {
|
||||||
padding: 0 32px;
|
margin-top: 20px;
|
||||||
color: inherit;
|
|
||||||
font-size: 18px;
|
legend {
|
||||||
font-weight: 600;
|
padding: 0 32px;
|
||||||
line-height: 24px;
|
border: none;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: inherit;
|
||||||
|
font-family: Metropolis, sans-serif;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-add-user {
|
.group-add-user {
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ exports[`components/widgets/inputs/Input should match snapshot 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="Input_container"
|
class="Input_container"
|
||||||
>
|
>
|
||||||
<fieldset
|
<div
|
||||||
class="Input_fieldset"
|
class="Input_fieldset"
|
||||||
|
data-testid="input-wrapper"
|
||||||
>
|
>
|
||||||
<legend
|
<label
|
||||||
class="Input_legend"
|
class="Input_legend"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
@@ -20,7 +21,7 @@ exports[`components/widgets/inputs/Input should match snapshot 1`] = `
|
|||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ describe('components/widgets/inputs/Input', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Check for error styling
|
// Check for error styling
|
||||||
const fieldset = screen.getByRole('group');
|
const fieldset = screen.getByTestId('input-wrapper');
|
||||||
expect(fieldset).toHaveClass('Input_fieldset___error');
|
expect(fieldset).toHaveClass('Input_fieldset___error');
|
||||||
|
|
||||||
// Check for error message
|
// Check for error message
|
||||||
@@ -108,7 +108,7 @@ describe('components/widgets/inputs/Input', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Check for error styling
|
// Check for error styling
|
||||||
const fieldset = screen.getByRole('group');
|
const fieldset = screen.getByTestId('input-wrapper');
|
||||||
expect(fieldset).toHaveClass('Input_fieldset___error');
|
expect(fieldset).toHaveClass('Input_fieldset___error');
|
||||||
|
|
||||||
// Check for error message
|
// Check for error message
|
||||||
@@ -159,7 +159,7 @@ describe('components/widgets/inputs/Input', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Check for error styling
|
// Check for error styling
|
||||||
const fieldset = screen.getByRole('group');
|
const fieldset = screen.getByTestId('input-wrapper');
|
||||||
expect(fieldset).toHaveClass('Input_fieldset___error');
|
expect(fieldset).toHaveClass('Input_fieldset___error');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -205,7 +205,7 @@ describe('components/widgets/inputs/Input', () => {
|
|||||||
expect(errorMessage).toBeInTheDocument();
|
expect(errorMessage).toBeInTheDocument();
|
||||||
|
|
||||||
// Check for error styling
|
// Check for error styling
|
||||||
const fieldset = screen.getByRole('group');
|
const fieldset = screen.getByTestId('input-wrapper');
|
||||||
expect(fieldset).toHaveClass('Input_fieldset___error');
|
expect(fieldset).toHaveClass('Input_fieldset___error');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -256,16 +256,17 @@ const Input = React.forwardRef((
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames('Input_container', containerClassName, {disabled})}>
|
<div className={classNames('Input_container', containerClassName, {disabled})}>
|
||||||
<fieldset
|
<div
|
||||||
className={classNames('Input_fieldset', className, {
|
className={classNames('Input_fieldset', className, {
|
||||||
Input_fieldset___error: hasError || customInputLabel?.type === 'error',
|
Input_fieldset___error: hasError || customInputLabel?.type === 'error',
|
||||||
Input_fieldset___legend: showLegend,
|
Input_fieldset___legend: showLegend,
|
||||||
})}
|
})}
|
||||||
|
data-testid='input-wrapper'
|
||||||
>
|
>
|
||||||
{useLegend && (
|
{useLegend && (
|
||||||
<legend className={classNames('Input_legend', {Input_legend___focus: showLegend})}>
|
<label className={classNames('Input_legend', {Input_legend___focus: showLegend})}>
|
||||||
{showLegend ? formatAsString(formatMessage, label || placeholder) : null}
|
{showLegend ? formatAsString(formatMessage, label || placeholder) : null}
|
||||||
</legend>
|
</label>
|
||||||
)}
|
)}
|
||||||
<div className={classNames('Input_wrapper', wrapperClassName)}>
|
<div className={classNames('Input_wrapper', wrapperClassName)}>
|
||||||
{inputPrefix}
|
{inputPrefix}
|
||||||
@@ -275,7 +276,7 @@ const Input = React.forwardRef((
|
|||||||
{clearButton}
|
{clearButton}
|
||||||
</div>
|
</div>
|
||||||
{addon}
|
{addon}
|
||||||
</fieldset>
|
</div>
|
||||||
{/* Display custom or derived error messages */}
|
{/* Display custom or derived error messages */}
|
||||||
{customInputLabel && (
|
{customInputLabel && (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import {shallow} from 'enzyme';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import type {UserProfile} from '@mattermost/types/users';
|
|
||||||
|
|
||||||
import UsersEmailsInput from './users_emails_input';
|
|
||||||
|
|
||||||
describe('components/widgets/inputs/UsersEmailsInput', () => {
|
|
||||||
test('should match snapshot', () => {
|
|
||||||
const wrapper = shallow(
|
|
||||||
<UsersEmailsInput
|
|
||||||
placeholder='test'
|
|
||||||
ariaLabel='test'
|
|
||||||
usersLoader={jest.fn()}
|
|
||||||
onChange={jest.fn()}
|
|
||||||
value={[
|
|
||||||
'test@email.com',
|
|
||||||
{
|
|
||||||
id: 'test-user-id',
|
|
||||||
username: 'test-username',
|
|
||||||
first_name: 'test',
|
|
||||||
last_name: 'user',
|
|
||||||
} as UserProfile,
|
|
||||||
]}
|
|
||||||
errorMessage={{
|
|
||||||
id: 'errorMessageId',
|
|
||||||
defaultMessage: 'errorMessageDefault',
|
|
||||||
}}
|
|
||||||
onInputChange={jest.fn()}
|
|
||||||
inputValue=''
|
|
||||||
emailInvitationsEnabled={false}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
expect(wrapper).toMatchInlineSnapshot(`
|
|
||||||
<Fragment>
|
|
||||||
<ForwardRef
|
|
||||||
aria-label="test"
|
|
||||||
className="UsersEmailsInput empty"
|
|
||||||
classNamePrefix="users-emails-input"
|
|
||||||
components={
|
|
||||||
Object {
|
|
||||||
"IndicatorsContainer": [Function],
|
|
||||||
"Input": [Function],
|
|
||||||
"MultiValueRemove": [Function],
|
|
||||||
"NoOptionsMessage": [Function],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defaultMenuIsOpen={false}
|
|
||||||
defaultOptions={false}
|
|
||||||
formatOptionLabel={[Function]}
|
|
||||||
getOptionValue={[Function]}
|
|
||||||
inputValue=""
|
|
||||||
isClearable={false}
|
|
||||||
isMulti={true}
|
|
||||||
isValidNewOption={[Function]}
|
|
||||||
loadOptions={[Function]}
|
|
||||||
loadingMessage={[Function]}
|
|
||||||
onBlur={[Function]}
|
|
||||||
onChange={[Function]}
|
|
||||||
onFocus={[Function]}
|
|
||||||
onInputChange={[Function]}
|
|
||||||
openMenuOnClick={false}
|
|
||||||
openMenuOnFocus={true}
|
|
||||||
placeholder="test"
|
|
||||||
styles={
|
|
||||||
Object {
|
|
||||||
"input": [Function],
|
|
||||||
"placeholder": [Function],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tabSelectsValue={true}
|
|
||||||
value={
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"label": "test@email.com",
|
|
||||||
"value": "test@email.com",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"first_name": "test",
|
|
||||||
"id": "test-user-id",
|
|
||||||
"last_name": "user",
|
|
||||||
"username": "test-username",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Fragment>
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -4,8 +4,8 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type {RefObject} from 'react';
|
import type {RefObject} from 'react';
|
||||||
import type {MessageDescriptor} from 'react-intl';
|
import type {MessageDescriptor, IntlShape} from 'react-intl';
|
||||||
import {FormattedMessage, defineMessages} from 'react-intl';
|
import {FormattedMessage, defineMessages, injectIntl} from 'react-intl';
|
||||||
import {components} from 'react-select';
|
import {components} from 'react-select';
|
||||||
import type {FormatOptionLabelMeta, InputActionMeta, InputProps, Options, StylesConfig, SelectInstance, MultiValue, SingleValue, OptionsOrGroups, GroupBase, MultiValueRemoveProps} 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';
|
import AsyncCreatable from 'react-select/async-creatable';
|
||||||
@@ -50,6 +50,7 @@ type Props = {
|
|||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
suppressNoOptionsMessage?: boolean;
|
suppressNoOptionsMessage?: boolean;
|
||||||
onPaste?: (e: ClipboardEvent) => void;
|
onPaste?: (e: ClipboardEvent) => void;
|
||||||
|
intl: IntlShape;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EmailInvite = {
|
export type EmailInvite = {
|
||||||
@@ -79,7 +80,7 @@ const messages = defineMessages({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default class UsersEmailsInput extends React.PureComponent<Props, State> {
|
export class UsersEmailsInput extends React.PureComponent<Props, State> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
noMatchMessage: messages.noMatchDefault,
|
noMatchMessage: messages.noMatchDefault,
|
||||||
validAddress: messages.validAddressDefault,
|
validAddress: messages.validAddressDefault,
|
||||||
@@ -259,6 +260,18 @@ export default class UsersEmailsInput extends React.PureComponent<Props, State>
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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<EmailInvite | UserProfile, true>) => {
|
MultiValueRemove = (props: MultiValueRemoveProps<EmailInvite | UserProfile, true>) => {
|
||||||
const {children, innerProps} = props;
|
const {children, innerProps} = props;
|
||||||
|
|
||||||
@@ -267,6 +280,12 @@ export default class UsersEmailsInput extends React.PureComponent<Props, State>
|
|||||||
{...innerProps}
|
{...innerProps}
|
||||||
role='button'
|
role='button'
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
aria-label={this.props.intl.formatMessage({
|
||||||
|
id: 'multiselect.remove',
|
||||||
|
defaultMessage: 'Remove {label}',
|
||||||
|
}, {
|
||||||
|
label: this.renderAriaLabel(props.data),
|
||||||
|
})}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -564,3 +583,5 @@ export default class UsersEmailsInput extends React.PureComponent<Props, State>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default injectIntl(UsersEmailsInput);
|
||||||
|
|||||||
@@ -4736,6 +4736,7 @@
|
|||||||
"multiselect.creating": "Creating...",
|
"multiselect.creating": "Creating...",
|
||||||
"multiselect.go": "Go",
|
"multiselect.go": "Go",
|
||||||
"multiselect.list.notFound": "No results found matching <b>{searchQuery}</b>",
|
"multiselect.list.notFound": "No results found matching <b>{searchQuery}</b>",
|
||||||
|
"multiselect.list.resultsAvailable": "{count, plural, one {# result found} other {# results found}} for your search.",
|
||||||
"multiselect.loading": "Loading...",
|
"multiselect.loading": "Loading...",
|
||||||
"multiselect.maxGroupMembers": "No more than 256 members can be added to a group at once.",
|
"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 <a>create a channel</a> to include more people. ",
|
"multiselect.maxPeople": "Use ↑↓ to browse, ↵ to select. You can't add more than 7 people. Please <a>create a channel</a> 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.numRemaining": "Up to {max, number} can be added at a time. You have {num, number} remaining.",
|
||||||
"multiselect.placeholder": "Search for people",
|
"multiselect.placeholder": "Search for people",
|
||||||
"multiselect.placeholder.peopleOrGroups": "Search for people or groups",
|
"multiselect.placeholder.peopleOrGroups": "Search for people or groups",
|
||||||
|
"multiselect.remove": "Remove {label}",
|
||||||
|
"multiselect.required": "This field is required",
|
||||||
"multiselect.saveDetailsButton": "Save Details",
|
"multiselect.saveDetailsButton": "Save Details",
|
||||||
"multiselect.savingDetailsButton": "Saving...",
|
"multiselect.savingDetailsButton": "Saving...",
|
||||||
"multiselect.selectChannels": "Use ↑↓ to browse, ↵ to select.",
|
"multiselect.selectChannels": "Use ↑↓ to browse, ↵ to select.",
|
||||||
@@ -5764,6 +5767,7 @@
|
|||||||
"user_groups_modal.editGroupTitle": "Edit Group Details",
|
"user_groups_modal.editGroupTitle": "Edit Group Details",
|
||||||
"user_groups_modal.filterAriaLabel": "Groups Filter",
|
"user_groups_modal.filterAriaLabel": "Groups Filter",
|
||||||
"user_groups_modal.goBackLabel": "Back",
|
"user_groups_modal.goBackLabel": "Back",
|
||||||
|
"user_groups_modal.groupCreatedSuccess": "Group created successfully",
|
||||||
"user_groups_modal.joinGroup": "Join Group",
|
"user_groups_modal.joinGroup": "Join Group",
|
||||||
"user_groups_modal.leaveGroup": "Leave Group",
|
"user_groups_modal.leaveGroup": "Leave Group",
|
||||||
"user_groups_modal.memberCount": "{member_count} {member_count, plural, one {member} other {members}}",
|
"user_groups_modal.memberCount": "{member_count} {member_count, plural, one {member} other {members}}",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
.modal .channel-invite__content,
|
.modal .channel-invite__content,
|
||||||
.modal .user-groups-modal__content {
|
.modal .user-groups-modal__content {
|
||||||
.multi-select__container {
|
.multi-select__container, .multi-select__error {
|
||||||
height: auto;
|
height: auto;
|
||||||
padding: 0 3.2rem;
|
padding: 0 3.2rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
height: 40px;
|
height: 40px;
|
||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.has-error.react-select .react-select__control.react-select-auto {
|
||||||
|
border-color: var(--error-text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.multi-select__focused {
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -525,7 +525,7 @@
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.multi-select__container {
|
.multi-select__container, .multi-select__error {
|
||||||
.btn {
|
.btn {
|
||||||
display: block;
|
display: block;
|
||||||
min-width: 50px;
|
min-width: 50px;
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user