diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-12-02 06:07:57 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-12-02 06:07:57 +0300 |
commit | 0409a31740fa29fa1131c0496da16677cb3debe4 (patch) | |
tree | 4cbcd702a01145e8acd723f21b6bd52f3c6639d0 /app/assets/javascripts/invite_members | |
parent | b26eec8cbcf32085079eee0e196456eccefc993f (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/invite_members')
4 files changed, 138 insertions, 126 deletions
diff --git a/app/assets/javascripts/invite_members/components/invite_members_modal.vue b/app/assets/javascripts/invite_members/components/invite_members_modal.vue index 887dad7465b..4c5e4fb1021 100644 --- a/app/assets/javascripts/invite_members/components/invite_members_modal.vue +++ b/app/assets/javascripts/invite_members/components/invite_members_modal.vue @@ -123,7 +123,7 @@ export default { selectedAccessLevel: undefined, errorsLimit: 2, isErrorsSectionExpanded: false, - emptyInvitesError: false, + shouldShowEmptyInvitesAlert: false, }; }, computed: { @@ -208,12 +208,15 @@ export default { count: this.errorsExpanded.length, }); }, + formGroupDescription() { + return this.invalidFeedbackMessage ? null : this.$options.labels.placeHolder; + }, }, watch: { isEmptyInvites: { handler(updatedValue) { // nothing to do if the invites are **still** empty and the emptyInvites were never set from submit - if (!updatedValue && !this.emptyInvitesError) { + if (!updatedValue && !this.shouldShowEmptyInvitesAlert) { return; } @@ -262,16 +265,17 @@ export default { const tracking = new ExperimentTracking(experimentName); tracking.event(eventName); }, - showEmptyInvitesError() { - this.invalidFeedbackMessage = this.$options.labels.emptyInvitesErrorText; - this.emptyInvitesError = true; + showEmptyInvitesAlert() { + this.invalidFeedbackMessage = this.$options.labels.placeHolder; + this.shouldShowEmptyInvitesAlert = true; + this.$refs.alerts.focus(); }, sendInvite({ accessLevel, expiresAt }) { this.isLoading = true; this.clearValidation(); if (!this.isEmptyInvites) { - this.showEmptyInvitesError(); + this.showEmptyInvitesAlert(); return; } @@ -312,6 +316,7 @@ export default { }, showMemberErrors(message) { this.invalidMembers = message; + this.$refs.alerts.focus(); }, tokenName(username) { // initial token creation hits this and nothing is found... so safe navigation @@ -326,6 +331,7 @@ export default { resetFields() { this.clearValidation(); this.isLoading = false; + this.shouldShowEmptyInvitesAlert = false; this.newUsersToInvite = []; this.selectedTasksToBeDone = []; [this.selectedTaskProject] = this.projects; @@ -351,7 +357,7 @@ export default { }, clearEmptyInviteError() { this.invalidFeedbackMessage = ''; - this.emptyInvitesError = false; + this.shouldShowEmptyInvitesAlert = false; }, removeToken(token) { delete this.invalidMembers[memberName(token)]; @@ -374,7 +380,7 @@ export default { :help-link="helpLink" :label-intro-text="labelIntroText" :label-search-field="$options.labels.searchField" - :form-group-description="$options.labels.placeHolder" + :form-group-description="formGroupDescription" :invalid-feedback-message="invalidFeedbackMessage" :is-loading="isLoading" :new-users-to-invite="newUsersToInvite" @@ -395,59 +401,77 @@ export default { </template> <template #alert> - <gl-alert - v-if="hasInvalidMembers" - variant="danger" - :dismissible="false" - :title="memberErrorTitle" - data-testid="alert-member-error" - > - {{ $options.labels.memberErrorListText }} - <ul class="gl-pl-5 gl-mb-0"> - <li v-for="error in errorsLimited" :key="error.member" data-testid="errors-limited-item"> - <strong>{{ error.displayedMemberName }}:</strong> {{ error.message }} - </li> - </ul> - <template v-if="shouldErrorsSectionExpand"> - <gl-collapse v-model="isErrorsSectionExpanded"> - <ul class="gl-pl-5 gl-mb-0"> - <li - v-for="error in errorsExpanded" - :key="error.member" - data-testid="errors-expanded-item" - > - <strong>{{ error.displayedMemberName }}:</strong> {{ error.message }} - </li> - </ul> - </gl-collapse> - <gl-button - class="gl-text-decoration-none! gl-shadow-none! gl-mt-3" - data-testid="accordion-button" - variant="link" - @click="toggleErrorExpansion" - > - {{ errorCollapseText }} - <gl-icon - name="chevron-down" - class="gl-transition-medium" - :class="{ 'gl-rotate-180': isErrorsSectionExpanded }" - /> - </gl-button> - </template> - </gl-alert> - <user-limit-notification - v-else-if="showUserLimitNotification" - :limit-variant="limitVariant" - :users-limit-dataset="usersLimitDataset" - /> + <div ref="alerts" tabindex="-1"> + <gl-alert + v-if="shouldShowEmptyInvitesAlert" + id="empty-invites-alert" + class="gl-mb-4" + variant="danger" + :dismissible="false" + data-testid="empty-invites-alert" + > + {{ $options.labels.emptyInvitesAlertText }} + </gl-alert> + <gl-alert + v-if="hasInvalidMembers" + class="gl-mb-4" + variant="danger" + :dismissible="false" + :title="memberErrorTitle" + data-testid="alert-member-error" + > + {{ $options.labels.memberErrorListText }} + <ul class="gl-pl-5 gl-mb-0"> + <li + v-for="error in errorsLimited" + :key="error.member" + data-testid="errors-limited-item" + > + <strong>{{ error.displayedMemberName }}:</strong> {{ error.message }} + </li> + </ul> + <template v-if="shouldErrorsSectionExpand"> + <gl-collapse v-model="isErrorsSectionExpanded"> + <ul class="gl-pl-5 gl-mb-0"> + <li + v-for="error in errorsExpanded" + :key="error.member" + data-testid="errors-expanded-item" + > + <strong>{{ error.displayedMemberName }}:</strong> {{ error.message }} + </li> + </ul> + </gl-collapse> + <gl-button + class="gl-text-decoration-none! gl-shadow-none! gl-mt-3" + data-testid="accordion-button" + variant="link" + @click="toggleErrorExpansion" + > + {{ errorCollapseText }} + <gl-icon + name="chevron-down" + class="gl-transition-medium" + :class="{ 'gl-rotate-180': isErrorsSectionExpanded }" + /> + </gl-button> + </template> + </gl-alert> + <user-limit-notification + v-else-if="showUserLimitNotification" + :limit-variant="limitVariant" + :users-limit-dataset="usersLimitDataset" + /> + </div> </template> - <template #select="{ exceptionState, labelId }"> + <template #select="{ exceptionState, inputId }"> <members-token-select v-model="newUsersToInvite" class="gl-mb-2" + aria-labelledby="empty-invites-alert" + :input-id="inputId" :exception-state="exceptionState" - :aria-labelledby="labelId" :users-filter="usersFilter" :filter-id="filterId" :invalid-members="invalidMembers" diff --git a/app/assets/javascripts/invite_members/components/invite_modal_base.vue b/app/assets/javascripts/invite_members/components/invite_modal_base.vue index e3511a49fc5..2cbd681c67d 100644 --- a/app/assets/javascripts/invite_members/components/invite_modal_base.vue +++ b/app/assets/javascripts/invite_members/components/invite_modal_base.vue @@ -1,14 +1,5 @@ <script> -import { - GlFormGroup, - GlModal, - GlDropdown, - GlDropdownItem, - GlDatepicker, - GlLink, - GlSprintf, - GlFormInput, -} from '@gitlab/ui'; +import { GlFormGroup, GlFormSelect, GlModal, GlDatepicker, GlLink, GlSprintf } from '@gitlab/ui'; import Tracking from '~/tracking'; import { sprintf } from '~/locale'; import ContentTransition from '~/vue_shared/components/content_transition.vue'; @@ -37,13 +28,11 @@ const DEFAULT_SLOTS = [ export default { components: { GlFormGroup, + GlFormSelect, GlDatepicker, GlLink, GlModal, - GlDropdown, - GlDropdownItem, GlSprintf, - GlFormInput, ContentTransition, }, mixins: [Tracking.mixin()], @@ -141,14 +130,23 @@ export default { }; }, computed: { + accessLevelsOptions() { + return Object.entries(this.accessLevels).map(([text, value]) => ({ text, value })); + }, introText() { return sprintf(this.labelIntroText, { name: this.name }); }, exceptionState() { return this.invalidFeedbackMessage ? false : null; }, - selectLabelId() { - return `${this.modalId}_select`; + selectId() { + return `${this.modalId}_search`; + }, + dropdownId() { + return `${this.modalId}_dropdown`; + }, + datepickerId() { + return `${this.modalId}_expires_at`; }, selectedRoleName() { return Object.keys(this.accessLevels).find( @@ -218,9 +216,6 @@ export default { this.$emit('cancel'); }, - changeSelectedItem(item) { - this.selectedAccessLevel = item; - }, onSubmit(e) { // We never want to hide when submitting e.preventDefault(); @@ -279,64 +274,50 @@ export default { <slot name="alert"></slot> <gl-form-group + :label="labelSearchField" + :label-for="selectId" :invalid-feedback="invalidFeedbackMessage" :state="exceptionState" :description="formGroupDescription" data-testid="members-form-group" > - <label :id="selectLabelId" class="col-form-label">{{ labelSearchField }}</label> - <slot name="select" v-bind="{ exceptionState, labelId: selectLabelId }"></slot> + <slot name="select" v-bind="{ exceptionState, inputId: selectId }"></slot> </gl-form-group> - <label class="gl-font-weight-bold">{{ $options.ACCESS_LEVEL }}</label> - <div class="gl-mt-2 gl-w-half gl-xs-w-full"> - <gl-dropdown - class="gl-shadow-none gl-w-full" + <gl-form-group + class="gl-w-half gl-xs-w-full" + :label="$options.ACCESS_LEVEL" + :label-for="dropdownId" + > + <template #description> + <gl-sprintf :message="$options.READ_MORE_TEXT"> + <template #link="{ content }"> + <gl-link :href="helpLink" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + </template> + <gl-form-select + :id="dropdownId" + v-model="selectedAccessLevel" data-qa-selector="access_level_dropdown" - v-bind="$attrs" - :text="selectedRoleName" - > - <template v-for="(key, item) in accessLevels"> - <gl-dropdown-item - :key="key" - active-class="is-active" - is-check-item - :is-checked="key === selectedAccessLevel" - @click="changeSelectedItem(key)" - > - <div>{{ item }}</div> - </gl-dropdown-item> - </template> - </gl-dropdown> - </div> - - <div class="gl-mt-2 gl-w-half gl-xs-w-full"> - <gl-sprintf :message="$options.READ_MORE_TEXT"> - <template #link="{ content }"> - <gl-link :href="helpLink" target="_blank">{{ content }}</gl-link> - </template> - </gl-sprintf> - </div> + :options="accessLevelsOptions" + /> + </gl-form-group> - <label class="gl-mt-5 gl-display-block" for="expires_at">{{ - $options.ACCESS_EXPIRE_DATE - }}</label> - <div class="gl-mt-2 gl-w-half gl-xs-w-full gl-display-inline-block"> + <gl-form-group + class="gl-w-half gl-xs-w-full" + :label="$options.ACCESS_EXPIRE_DATE" + :label-for="datepickerId" + > <gl-datepicker v-model="selectedDate" - class="gl-display-inline!" + :input-id="datepickerId" + class="gl-display-block!" :min-date="minDate" :target="null" - > - <template #default="{ formattedDate }"> - <gl-form-input - class="gl-w-full" - :value="formattedDate" - :placeholder="__(`YYYY-MM-DD`)" - /> - </template> - </gl-datepicker> - </div> + /> + </gl-form-group> + <slot name="form-after"></slot> </template> diff --git a/app/assets/javascripts/invite_members/components/members_token_select.vue b/app/assets/javascripts/invite_members/components/members_token_select.vue index 2ddb04e1eeb..68602068699 100644 --- a/app/assets/javascripts/invite_members/components/members_token_select.vue +++ b/app/assets/javascripts/invite_members/components/members_token_select.vue @@ -49,6 +49,11 @@ export default { type: Object, required: true, }, + inputId: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -84,6 +89,13 @@ export default { hasInvalidMembers() { return !isEmpty(this.invalidMembers); }, + textInputAttrs() { + return { + 'data-testid': 'members-token-select-input', + 'data-qa-selector': 'members_token_select_input', + id: this.inputId, + }; + }, }, watch: { // We might not really want this to be *reactive* since we want the "class" state to be @@ -183,10 +195,7 @@ export default { :hide-dropdown-with-no-items="hideDropdownWithNoItems" :placeholder="placeholderText" :aria-labelledby="ariaLabelledby" - :text-input-attrs="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ { - 'data-testid': 'members-token-select-input', - 'data-qa-selector': 'members_token_select_input', - } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */" + :text-input-attrs="textInputAttrs" @blur="handleBlur" @text-input="handleTextInput" @input="handleInput" diff --git a/app/assets/javascripts/invite_members/constants.js b/app/assets/javascripts/invite_members/constants.js index 29889f47fe1..3b522f9a7d9 100644 --- a/app/assets/javascripts/invite_members/constants.js +++ b/app/assets/javascripts/invite_members/constants.js @@ -81,9 +81,7 @@ export const MEMBER_ERROR_LIST_TEXT = s__( ); export const COLLAPSED_ERRORS = s__('InviteMembersModal|Show more (%{count})'); export const EXPANDED_ERRORS = s__('InviteMembersModal|Show less'); -export const EMPTY_INVITES_ERROR_TEXT = s__( - 'InviteMembersModal|Please select members or type email addresses to invite', -); +export const EMPTY_INVITES_ALERT_TEXT = s__('InviteMembersModal|Please add members to invite'); export const MEMBER_MODAL_LABELS = { modal: { @@ -121,7 +119,7 @@ export const MEMBER_MODAL_LABELS = { memberErrorListText: MEMBER_ERROR_LIST_TEXT, collapsedErrors: COLLAPSED_ERRORS, expandedErrors: EXPANDED_ERRORS, - emptyInvitesErrorText: EMPTY_INVITES_ERROR_TEXT, + emptyInvitesAlertText: EMPTY_INVITES_ALERT_TEXT, }; export const GROUP_MODAL_LABELS = { |