diff options
Diffstat (limited to 'app/assets/javascripts/vue_shared/components')
4 files changed, 146 insertions, 137 deletions
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/constants.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/constants.js index 746e38e98e8..00c54313292 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/constants.js +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/constants.js @@ -3,5 +3,3 @@ export const DropdownVariant = { Standalone: 'standalone', Embedded: 'embedded', }; - -export const LIST_BUFFER_SIZE = 5; diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue index c8dee81d746..353dee862d0 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue @@ -1,23 +1,25 @@ <script> import { mapState, mapGetters, mapActions } from 'vuex'; -import { GlLoadingIcon, GlButton, GlSearchBoxByType, GlLink } from '@gitlab/ui'; +import { + GlIntersectionObserver, + GlLoadingIcon, + GlButton, + GlSearchBoxByType, + GlLink, +} from '@gitlab/ui'; import fuzzaldrinPlus from 'fuzzaldrin-plus'; import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes'; -import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue'; import LabelItem from './label_item.vue'; -import { LIST_BUFFER_SIZE } from './constants'; - export default { - LIST_BUFFER_SIZE, components: { + GlIntersectionObserver, GlLoadingIcon, GlButton, GlSearchBoxByType, GlLink, - SmartVirtualList, LabelItem, }, data() { @@ -46,15 +48,8 @@ export default { } return this.labels; }, - showListContainer() { - if (this.isDropdownVariantSidebar) { - return !this.labelsFetchInProgress; - } - - return true; - }, showNoMatchingResultsMessage() { - return !this.labelsFetchInProgress && !this.visibleLabels.length; + return Boolean(this.searchKey) && this.visibleLabels.length === 0; }, }, watch: { @@ -67,14 +62,12 @@ export default { } }, }, - mounted() { - this.fetchLabels(); - }, methods: { ...mapActions([ 'toggleDropdownContents', 'toggleDropdownContentsCreateView', 'fetchLabels', + 'receiveLabelsSuccess', 'updateSelectedLabels', 'toggleDropdownContents', ]), @@ -100,6 +93,17 @@ export default { } }, /** + * We want to remove loaded labels to ensure component + * fetches fresh set of labels every time when shown. + */ + handleComponentDisappear() { + this.receiveLabelsSuccess([]); + }, + handleCreateLabelClick() { + this.receiveLabelsSuccess([]); + this.toggleDropdownContentsCreateView(); + }, + /** * This method enables keyboard navigation support for * the dropdown. */ @@ -135,84 +139,75 @@ export default { </script> <template> - <div class="labels-select-contents-list js-labels-list" @keydown="handleKeyDown"> - <gl-loading-icon - v-if="labelsFetchInProgress" - class="labels-fetch-loading position-absolute gl-display-flex gl-align-items-center w-100 h-100" - size="md" - /> - <div - v-if="isDropdownVariantSidebar || isDropdownVariantEmbedded" - class="dropdown-title gl-display-flex gl-align-items-center gl-pt-0 gl-pb-3!" - data-testid="dropdown-title" - > - <span class="flex-grow-1">{{ labelsListTitle }}</span> - <gl-button - :aria-label="__('Close')" - variant="link" - size="small" - class="dropdown-header-button gl-p-0!" - icon="close" - @click="toggleDropdownContents" - /> - </div> - <div class="dropdown-input" @click.stop="() => {}"> - <gl-search-box-by-type - v-model="searchKey" - :autofocus="true" - data-qa-selector="dropdown_input_field" - /> - </div> - <div - v-show="showListContainer" - ref="labelsListContainer" - class="dropdown-content" - data-testid="dropdown-content" - > - <smart-virtual-list - :length="visibleLabels.length" - :remain="$options.LIST_BUFFER_SIZE" - :size="$options.LIST_BUFFER_SIZE" - wclass="list-unstyled mb-0" - wtag="ul" - class="h-100" + <gl-intersection-observer @appear="fetchLabels" @disappear="handleComponentDisappear"> + <div class="labels-select-contents-list js-labels-list" @keydown="handleKeyDown"> + <div + v-if="isDropdownVariantSidebar || isDropdownVariantEmbedded" + class="dropdown-title gl-display-flex gl-align-items-center gl-pt-0 gl-pb-3!" + data-testid="dropdown-title" > - <li v-for="(label, index) in visibleLabels" :key="label.id" class="d-block text-left"> + <span class="flex-grow-1">{{ labelsListTitle }}</span> + <gl-button + :aria-label="__('Close')" + variant="link" + size="small" + class="dropdown-header-button gl-p-0!" + icon="close" + @click="toggleDropdownContents" + /> + </div> + <div class="dropdown-input" @click.stop="() => {}"> + <gl-search-box-by-type + v-model="searchKey" + :autofocus="true" + :disabled="labelsFetchInProgress" + data-qa-selector="dropdown_input_field" + /> + </div> + <div ref="labelsListContainer" class="dropdown-content" data-testid="dropdown-content"> + <gl-loading-icon + v-if="labelsFetchInProgress" + class="labels-fetch-loading gl-align-items-center w-100 h-100" + size="md" + /> + <ul v-else class="list-unstyled mb-0"> <label-item + v-for="(label, index) in visibleLabels" + :key="label.id" :label="label" :is-label-set="label.set" :highlight="index === currentHighlightItem" @clickLabel="handleLabelClick(label)" /> - </li> - <li v-show="showNoMatchingResultsMessage" class="gl-p-3 gl-text-center"> - {{ __('No matching results') }} - </li> - </smart-virtual-list> - </div> - <div - v-if="isDropdownVariantSidebar || isDropdownVariantEmbedded" - class="dropdown-footer" - data-testid="dropdown-footer" - > - <ul class="list-unstyled"> - <li v-if="allowLabelCreate"> - <gl-link - class="gl-display-flex w-100 flex-row text-break-word label-item" - @click="toggleDropdownContentsCreateView" - > - {{ footerCreateLabelTitle }} - </gl-link> - </li> - <li> - <gl-link - :href="labelsManagePath" - class="gl-display-flex flex-row text-break-word label-item" - > - {{ footerManageLabelTitle }} - </gl-link> - </li> - </ul> + <li v-show="showNoMatchingResultsMessage" class="gl-p-3 gl-text-center"> + {{ __('No matching results') }} + </li> + </ul> + </div> + <div + v-if="isDropdownVariantSidebar || isDropdownVariantEmbedded" + class="dropdown-footer" + data-testid="dropdown-footer" + > + <ul class="list-unstyled"> + <li v-if="allowLabelCreate"> + <gl-link + class="gl-display-flex w-100 flex-row text-break-word label-item" + @click="handleCreateLabelClick" + > + {{ footerCreateLabelTitle }} + </gl-link> + </li> + <li> + <gl-link + :href="labelsManagePath" + class="gl-display-flex flex-row text-break-word label-item" + > + {{ footerManageLabelTitle }} + </gl-link> + </li> + </ul> + </div> </div> - </div> + </gl-intersection-observer> </template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/label_item.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/label_item.vue index 002e741ab96..e431fd000a6 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/label_item.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/label_item.vue @@ -1,11 +1,8 @@ <script> -import { GlIcon, GlLink } from '@gitlab/ui'; +import { GlLink, GlIcon } from '@gitlab/ui'; export default { - components: { - GlIcon, - GlLink, - }, + functional: true, props: { label: { type: Object, @@ -21,46 +18,65 @@ export default { default: false, }, }, - data() { - return { - isSet: this.isLabelSet, - }; - }, - computed: { - labelBoxStyle() { - return { - backgroundColor: this.label.color, - }; - }, - }, - watch: { - /** - * This watcher assures that if user used - * `Enter` key to set/unset label, changes - * are reflected here too. - */ - isLabelSet(value) { - this.isSet = value; - }, - }, - methods: { - handleClick() { - this.isSet = !this.isSet; - this.$emit('clickLabel', this.label); - }, + render(h, { props, listeners }) { + const { label, highlight, isLabelSet } = props; + + const labelColorBox = h('span', { + class: 'dropdown-label-box', + style: { + backgroundColor: label.color, + }, + attrs: { + 'data-testid': 'label-color-box', + }, + }); + + const checkedIcon = h(GlIcon, { + class: { + 'mr-2 align-self-center': true, + hidden: !isLabelSet, + }, + props: { + name: 'mobile-issue-close', + }, + }); + + const noIcon = h('span', { + class: { + 'mr-3 pr-2': true, + hidden: isLabelSet, + }, + attrs: { + 'data-testid': 'no-icon', + }, + }); + + const labelTitle = h('span', label.title); + + const labelLink = h( + GlLink, + { + class: 'd-flex align-items-baseline text-break-word label-item', + on: { + click: () => { + listeners.clickLabel(label); + }, + }, + }, + [noIcon, checkedIcon, labelColorBox, labelTitle], + ); + + return h( + 'li', + { + class: { + 'd-block': true, + 'text-left': true, + 'is-focused': highlight, + }, + }, + [labelLink], + ); }, }; </script> - -<template> - <gl-link - class="d-flex align-items-baseline text-break-word label-item" - :class="{ 'is-focused': highlight }" - @click="handleClick" - > - <gl-icon v-show="isSet" name="mobile-issue-close" class="mr-2 align-self-center" /> - <span v-show="!isSet" data-testid="no-icon" class="mr-3 pr-2"></span> - <span class="dropdown-label-box" data-testid="label-color-box" :style="labelBoxStyle"></span> - <span>{{ label.title }}</span> - </gl-link> -</template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue index c651013c5f5..2f71907f772 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue @@ -266,7 +266,7 @@ export default { </dropdown-value> <dropdown-button v-show="dropdownButtonVisible" class="gl-mt-2" /> <dropdown-contents - v-if="dropdownButtonVisible && showDropdownContents" + v-show="dropdownButtonVisible && showDropdownContents" ref="dropdownContents" /> </template> |