summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-04 15:08:09 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-04 15:08:09 +0000
commitd3fc3be040a4fed2328e23ef28696dd8bd8238b4 (patch)
treef1874ea5e6e3c50c6a3c2ca2900af4ae73a53119 /app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue
parentc6c7437861bff9572747674095c4dfbdfbea4988 (diff)
downloadgitlab-ce-d3fc3be040a4fed2328e23ef28696dd8bd8238b4.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue')
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue178
1 files changed, 178 insertions, 0 deletions
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
new file mode 100644
index 00000000000..7ec420fa908
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue
@@ -0,0 +1,178 @@
+<script>
+import { mapState, mapGetters, mapActions } from 'vuex';
+import { GlLoadingIcon, GlButton, GlIcon, GlSearchBoxByType, GlLink } from '@gitlab/ui';
+
+import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
+
+export default {
+ components: {
+ GlLoadingIcon,
+ GlButton,
+ GlIcon,
+ GlSearchBoxByType,
+ GlLink,
+ },
+ data() {
+ return {
+ searchKey: '',
+ currentHighlightItem: -1,
+ };
+ },
+ computed: {
+ ...mapState([
+ 'labelsManagePath',
+ 'labels',
+ 'labelsFetchInProgress',
+ 'labelsListTitle',
+ 'footerCreateLabelTitle',
+ 'footerManageLabelTitle',
+ ]),
+ ...mapGetters(['selectedLabelsList']),
+ visibleLabels() {
+ if (this.searchKey) {
+ return this.labels.filter(label =>
+ label.title.toLowerCase().includes(this.searchKey.toLowerCase()),
+ );
+ }
+ return this.labels;
+ },
+ },
+ watch: {
+ searchKey(value) {
+ // When there is search string present
+ // and there are matching results,
+ // highlight first item by default.
+ if (value && this.visibleLabels.length) {
+ this.currentHighlightItem = 0;
+ }
+ },
+ },
+ mounted() {
+ this.fetchLabels();
+ },
+ methods: {
+ ...mapActions([
+ 'toggleDropdownContents',
+ 'toggleDropdownContentsCreateView',
+ 'fetchLabels',
+ 'updateSelectedLabels',
+ ]),
+ getDropdownLabelBoxStyle(label) {
+ return {
+ backgroundColor: label.color,
+ };
+ },
+ isLabelSelected(label) {
+ return this.selectedLabelsList.includes(label.id);
+ },
+ /**
+ * This method scrolls item from dropdown into
+ * the view if it is off the viewable area of the
+ * container.
+ */
+ scrollIntoViewIfNeeded() {
+ const highlightedLabel = this.$refs.labelsListContainer.querySelector('.is-focused');
+
+ if (highlightedLabel) {
+ const rect = highlightedLabel.getBoundingClientRect();
+ if (rect.bottom > this.$refs.labelsListContainer.clientHeight) {
+ highlightedLabel.scrollIntoView(false);
+ }
+ if (rect.top < 0) {
+ highlightedLabel.scrollIntoView();
+ }
+ }
+ },
+ /**
+ * This method enables keyboard navigation support for
+ * the dropdown.
+ */
+ handleKeyDown(e) {
+ if (e.keyCode === UP_KEY_CODE && this.currentHighlightItem > 0) {
+ this.currentHighlightItem -= 1;
+ } else if (
+ e.keyCode === DOWN_KEY_CODE &&
+ this.currentHighlightItem < this.visibleLabels.length - 1
+ ) {
+ this.currentHighlightItem += 1;
+ } else if (e.keyCode === ENTER_KEY_CODE && this.currentHighlightItem > -1) {
+ this.updateSelectedLabels([this.visibleLabels[this.currentHighlightItem]]);
+ } else if (e.keyCode === ESC_KEY_CODE) {
+ this.toggleDropdownContents();
+ }
+
+ if (e.keyCode !== ESC_KEY_CODE) {
+ // Scroll the list only after highlighting
+ // styles are rendered completely.
+ this.$nextTick(() => {
+ this.scrollIntoViewIfNeeded();
+ });
+ }
+ },
+ handleLabelClick(label) {
+ this.updateSelectedLabels([label]);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="labels-select-contents-list" @keydown="handleKeyDown">
+ <gl-loading-icon
+ v-if="labelsFetchInProgress"
+ class="labels-fetch-loading position-absolute d-flex align-items-center w-100 h-100"
+ size="md"
+ />
+ <div class="dropdown-title d-flex align-items-center pt-0 pb-2">
+ <span class="flex-grow-1">{{ labelsListTitle }}</span>
+ <gl-button
+ :aria-label="__('Close')"
+ variant="link"
+ size="sm"
+ class="dropdown-header-button p-0"
+ @click="toggleDropdownContents"
+ >
+ <gl-icon name="close" />
+ </gl-button>
+ </div>
+ <div class="dropdown-input">
+ <gl-search-box-by-type v-model="searchKey" :autofocus="true" />
+ </div>
+ <div v-if="!labelsFetchInProgress" ref="labelsListContainer" class="dropdown-content">
+ <ul class="list-unstyled mb-0">
+ <li v-for="(label, index) in visibleLabels" :key="label.id" class="d-block text-left">
+ <gl-link
+ class="d-flex align-items-baseline text-break-word label-item"
+ :class="{ 'is-focused': index === currentHighlightItem }"
+ @click="handleLabelClick(label)"
+ >
+ <gl-icon v-show="label.set" name="mobile-issue-close" class="mr-2 align-self-center" />
+ <span v-show="!label.set" class="mr-3 pr-2"></span>
+ <span class="dropdown-label-box" :style="getDropdownLabelBoxStyle(label)"></span>
+ <span>{{ label.title }}</span>
+ </gl-link>
+ </li>
+ <li v-if="!visibleLabels.length" class="p-2 text-center">
+ {{ __('No matching results') }}
+ </li>
+ </ul>
+ </div>
+ <div class="dropdown-footer">
+ <ul class="list-unstyled">
+ <li>
+ <gl-button
+ variant="link"
+ class="d-flex w-100 flex-row text-break-word label-item"
+ @click="toggleDropdownContentsCreateView"
+ >{{ footerCreateLabelTitle }}</gl-button
+ >
+ </li>
+ <li>
+ <gl-link :href="labelsManagePath" class="d-flex flex-row text-break-word label-item">
+ {{ footerManageLabelTitle }}
+ </gl-link>
+ </li>
+ </ul>
+ </div>
+ </div>
+</template>