diff options
Diffstat (limited to 'app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue')
-rw-r--r-- | app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue | 217 |
1 files changed, 217 insertions, 0 deletions
diff --git a/app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue b/app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue new file mode 100644 index 00000000000..02323e5a0c6 --- /dev/null +++ b/app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue @@ -0,0 +1,217 @@ +<script> +import { + GlIcon, + GlLoadingIcon, + GlDropdown, + GlDropdownForm, + GlDropdownItem, + GlSearchBoxByType, + GlButton, + GlTooltipDirective as GlTooltip, +} from '@gitlab/ui'; + +import axios from '~/lib/utils/axios_utils'; + +export default { + components: { + GlIcon, + GlLoadingIcon, + GlDropdown, + GlDropdownForm, + GlDropdownItem, + GlSearchBoxByType, + GlButton, + }, + directives: { + GlTooltip, + }, + props: { + projectsFetchPath: { + type: String, + required: true, + }, + dropdownButtonTitle: { + type: String, + required: true, + }, + dropdownHeaderTitle: { + type: String, + required: true, + }, + moveInProgress: { + type: Boolean, + required: false, + default: false, + }, + disabled: { + type: Boolean, + required: false, + default: false, + }, + }, + data() { + return { + projectsListLoading: false, + projectsListLoadFailed: false, + searchKey: '', + projects: [], + selectedProject: null, + projectItemClick: false, + }; + }, + computed: { + hasNoSearchResults() { + return Boolean( + !this.projectsListLoading && + !this.projectsListLoadFailed && + this.searchKey && + !this.projects.length, + ); + }, + failedToLoadResults() { + return !this.projectsListLoading && this.projectsListLoadFailed; + }, + }, + watch: { + searchKey(value = '') { + this.fetchProjects(value); + }, + }, + methods: { + fetchProjects(search = '') { + this.projectsListLoading = true; + this.projectsListLoadFailed = false; + return axios + .get(this.projectsFetchPath, { + params: { + search, + }, + }) + .then(({ data }) => { + this.projects = data; + this.$refs.searchInput.focusInput(); + }) + .catch(() => { + this.projectsListLoadFailed = true; + }) + .finally(() => { + this.projectsListLoading = false; + }); + }, + isSelectedProject(project) { + if (this.selectedProject) { + return this.selectedProject.id === project.id; + } + return false; + }, + /** + * This handler is to prevent dropdown + * from closing when an item is selected + * and emit an event only when dropdown closes. + */ + handleDropdownHide(e) { + if (this.projectItemClick) { + e.preventDefault(); + this.projectItemClick = false; + } else { + this.$emit('dropdown-close'); + } + }, + handleDropdownCloseClick() { + this.$refs.dropdown.hide(); + }, + handleProjectSelect(project) { + this.selectedProject = project.id === this.selectedProject?.id ? null : project; + this.projectItemClick = true; + }, + handleMoveClick() { + this.$refs.dropdown.hide(); + this.$emit('move-issuable', this.selectedProject); + }, + }, +}; +</script> + +<template> + <div class="js-issuable-move-block issuable-move-dropdown sidebar-move-issue-dropdown"> + <div + v-gl-tooltip.left.viewport + data-testid="move-collapsed" + :title="dropdownButtonTitle" + class="sidebar-collapsed-icon" + @click="$emit('toggle-collapse')" + > + <gl-icon name="arrow-right" /> + </div> + <gl-dropdown + ref="dropdown" + :block="true" + :disabled="moveInProgress || disabled" + class="hide-collapsed" + toggle-class="js-sidebar-dropdown-toggle" + @shown="fetchProjects" + @hide="handleDropdownHide" + > + <template #button-content + ><gl-loading-icon v-if="moveInProgress" size="sm" class="gl-mr-3" />{{ + dropdownButtonTitle + }}</template + > + <gl-dropdown-form class="gl-pt-0"> + <div + data-testid="header" + class="gl-display-flex gl-pb-3 gl-border-1 gl-border-b-solid gl-border-gray-100" + > + <span class="gl-flex-grow-1 gl-text-center gl-font-weight-bold gl-py-1">{{ + dropdownHeaderTitle + }}</span> + <gl-button + variant="link" + icon="close" + class="gl-mr-2 gl-w-auto! gl-p-2!" + :aria-label="__('Close')" + @click.prevent="handleDropdownCloseClick" + /> + </div> + <gl-search-box-by-type + ref="searchInput" + v-model.trim="searchKey" + :placeholder="__('Search project')" + :debounce="300" + /> + <div data-testid="content" class="dropdown-content"> + <gl-loading-icon v-if="projectsListLoading" size="lg" class="gl-p-5" /> + <ul v-else> + <gl-dropdown-item + v-for="project in projects" + :key="project.id" + is-check-item + :is-checked="isSelectedProject(project)" + @click.stop.prevent="handleProjectSelect(project)" + >{{ project.name_with_namespace }}</gl-dropdown-item + > + </ul> + <div v-if="hasNoSearchResults" class="gl-text-center gl-p-3"> + {{ __('No matching results') }} + </div> + <div v-if="failedToLoadResults" class="gl-text-center gl-p-3"> + {{ __('Failed to load projects') }} + </div> + </div> + <div + data-testid="footer" + class="gl-pt-3 gl-px-3 gl-border-1 gl-border-t-solid gl-border-gray-100" + > + <gl-button + category="primary" + variant="confirm" + :disabled="!Boolean(selectedProject)" + class="gl-text-center! issuable-move-button" + @click="handleMoveClick" + >{{ __('Move') }}</gl-button + > + </div> + </gl-dropdown-form> + </gl-dropdown> + </div> +</template> |