gitlab-ce/app/assets/javascripts/invite_members/components/invite_modal_base.vue

304 lines
7.3 KiB
Vue

<script>
import {
GlFormGroup,
GlModal,
GlDropdown,
GlDropdownItem,
GlDatepicker,
GlLink,
GlSprintf,
GlButton,
GlFormInput,
} from '@gitlab/ui';
import { sprintf } from '~/locale';
import ContentTransition from '~/vue_shared/components/content_transition.vue';
import {
ACCESS_LEVEL,
ACCESS_EXPIRE_DATE,
READ_MORE_TEXT,
INVITE_BUTTON_TEXT,
CANCEL_BUTTON_TEXT,
HEADER_CLOSE_LABEL,
} from '../constants';
const DEFAULT_SLOT = 'default';
const DEFAULT_SLOTS = [
{
key: DEFAULT_SLOT,
attributes: {
class: 'invite-modal-content',
'data-testid': 'invite-modal-initial-content',
},
},
];
export default {
components: {
GlFormGroup,
GlDatepicker,
GlLink,
GlModal,
GlDropdown,
GlDropdownItem,
GlSprintf,
GlButton,
GlFormInput,
ContentTransition,
},
inheritAttrs: false,
props: {
modalTitle: {
type: String,
required: true,
},
modalId: {
type: String,
required: true,
},
name: {
type: String,
required: true,
},
accessLevels: {
type: Object,
required: true,
},
defaultAccessLevel: {
type: Number,
required: true,
},
helpLink: {
type: String,
required: true,
},
labelIntroText: {
type: String,
required: true,
},
labelSearchField: {
type: String,
required: true,
},
formGroupDescription: {
type: String,
required: false,
default: '',
},
submitDisabled: {
type: Boolean,
required: false,
default: false,
},
isLoading: {
type: Boolean,
required: false,
default: false,
},
invalidFeedbackMessage: {
type: String,
required: false,
default: '',
},
submitButtonText: {
type: String,
required: false,
default: INVITE_BUTTON_TEXT,
},
currentSlot: {
type: String,
required: false,
default: DEFAULT_SLOT,
},
extraSlots: {
type: Array,
required: false,
default: () => [],
},
},
data() {
// Be sure to check out reset!
return {
selectedAccessLevel: this.defaultAccessLevel,
selectedDate: undefined,
minDate: new Date(),
};
},
computed: {
introText() {
return sprintf(this.labelIntroText, { name: this.name });
},
validationState() {
return this.invalidFeedbackMessage ? false : null;
},
selectLabelId() {
return `${this.modalId}_select`;
},
selectedRoleName() {
return Object.keys(this.accessLevels).find(
(key) => this.accessLevels[key] === Number(this.selectedAccessLevel),
);
},
contentSlots() {
return [...DEFAULT_SLOTS, ...(this.extraSlots || [])];
},
},
watch: {
selectedAccessLevel: {
immediate: true,
handler(val) {
this.$emit('access-level', val);
},
},
},
methods: {
reset() {
// This component isn't necessarily disposed,
// so we might need to reset it's state.
this.selectedAccessLevel = this.defaultAccessLevel;
this.selectedDate = undefined;
this.$emit('reset');
},
closeModal() {
this.reset();
this.$refs.modal.hide();
},
changeSelectedItem(item) {
this.selectedAccessLevel = item;
},
submit() {
this.$emit('submit', {
accessLevel: this.selectedAccessLevel,
expiresAt: this.selectedDate,
});
},
},
HEADER_CLOSE_LABEL,
ACCESS_EXPIRE_DATE,
ACCESS_LEVEL,
READ_MORE_TEXT,
INVITE_BUTTON_TEXT,
CANCEL_BUTTON_TEXT,
DEFAULT_SLOT,
};
</script>
<template>
<gl-modal
ref="modal"
:modal-id="modalId"
data-qa-selector="invite_members_modal_content"
data-testid="invite-modal"
size="sm"
:title="modalTitle"
:header-close-label="$options.HEADER_CLOSE_LABEL"
@hidden="reset"
@close="reset"
@hide="reset"
>
<content-transition
class="gl-display-grid"
transition-name="invite-modal-transition"
:slots="contentSlots"
:current-slot="currentSlot"
>
<template #[$options.DEFAULT_SLOT]>
<div class="gl-display-flex" data-testid="modal-base-intro-text">
<slot name="intro-text-before"></slot>
<p>
<gl-sprintf :message="introText">
<template #strong="{ content }">
<strong>{{ content }}</strong>
</template>
</gl-sprintf>
</p>
<slot name="intro-text-after"></slot>
</div>
<slot name="user-limit-notification"></slot>
<gl-form-group
:invalid-feedback="invalidFeedbackMessage"
:state="validationState"
:description="formGroupDescription"
data-testid="members-form-group"
>
<label :id="selectLabelId" class="col-form-label">{{ labelSearchField }}</label>
<slot name="select" v-bind="{ validationState, labelId: selectLabelId }"></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"
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>
<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-datepicker
v-model="selectedDate"
class="gl-display-inline!"
: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>
<slot name="form-after"></slot>
</template>
<template v-for="{ key } in extraSlots" #[key]>
<slot :name="key"></slot>
</template>
</content-transition>
<template #modal-footer>
<slot name="cancel-button">
<gl-button data-testid="cancel-button" @click="closeModal">
{{ $options.CANCEL_BUTTON_TEXT }}
</gl-button>
</slot>
<gl-button
:disabled="submitDisabled"
:loading="isLoading"
variant="confirm"
data-qa-selector="invite_button"
data-testid="invite-button"
@click="submit"
>
{{ submitButtonText }}
</gl-button>
</template>
</gl-modal>
</template>