diff options
Diffstat (limited to 'app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue')
10 files changed, 208 insertions, 120 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 new file mode 100644 index 00000000000..ab652c9356a --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/constants.js @@ -0,0 +1,5 @@ +// eslint-disable-next-line import/prefer-default-export +export const DropdownVariant = { + Sidebar: 'sidebar', + Standalone: 'standalone', +}; diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue index 55fa1e4ef9c..f45c14f8344 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue @@ -1,21 +1,35 @@ <script> -import { mapGetters } from 'vuex'; -import { GlDeprecatedButton, GlIcon } from '@gitlab/ui'; +import { mapActions, mapGetters } from 'vuex'; +import { GlButton, GlIcon } from '@gitlab/ui'; export default { components: { - GlDeprecatedButton, + GlButton, GlIcon, }, computed: { - ...mapGetters(['dropdownButtonText']), + ...mapGetters(['dropdownButtonText', 'isDropdownVariantStandalone']), + }, + methods: { + ...mapActions(['toggleDropdownContents']), + handleButtonClick(e) { + if (this.isDropdownVariantStandalone) { + this.toggleDropdownContents(); + e.stopPropagation(); + } + }, }, }; </script> <template> - <gl-deprecated-button class="labels-select-dropdown-button w-100 text-left"> - <span class="dropdown-toggle-text">{{ dropdownButtonText }}</span> + <gl-button + class="labels-select-dropdown-button js-dropdown-button w-100 text-left" + @click="handleButtonClick" + > + <span class="dropdown-toggle-text flex-fill"> + {{ dropdownButtonText }} + </span> <gl-icon name="chevron-down" class="pull-right" /> - </gl-deprecated-button> + </gl-button> </template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue index 6bb77f6b6f3..ba8d8391952 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue @@ -1,18 +1,10 @@ <script> import { mapState, mapActions } from 'vuex'; -import { - GlTooltipDirective, - GlDeprecatedButton, - GlIcon, - GlFormInput, - GlLink, - GlLoadingIcon, -} from '@gitlab/ui'; +import { GlTooltipDirective, GlButton, GlFormInput, GlLink, GlLoadingIcon } from '@gitlab/ui'; export default { components: { - GlDeprecatedButton, - GlIcon, + GlButton, GlFormInput, GlLink, GlLoadingIcon, @@ -60,25 +52,23 @@ export default { <template> <div class="labels-select-contents-create js-labels-create"> <div class="dropdown-title d-flex align-items-center pt-0 pb-2"> - <gl-deprecated-button + <gl-button :aria-label="__('Go back')" variant="link" - size="sm" + size="small" class="js-btn-back dropdown-header-button p-0" + icon="arrow-left" @click="toggleDropdownContentsCreateView" - > - <gl-icon name="arrow-left" /> - </gl-deprecated-button> + /> <span class="flex-grow-1">{{ labelsCreateTitle }}</span> - <gl-deprecated-button + <gl-button :aria-label="__('Close')" variant="link" - size="sm" + size="small" class="dropdown-header-button p-0" + icon="close" @click="toggleDropdownContents" - > - <gl-icon name="close" /> - </gl-deprecated-button> + /> </div> <div class="dropdown-input"> <gl-form-input @@ -107,21 +97,19 @@ export default { </div> </div> <div class="dropdown-actions clearfix pt-2 px-2"> - <gl-deprecated-button + <gl-button :disabled="disableCreate" - variant="primary" + category="primary" + variant="success" class="pull-left d-flex align-items-center" @click="handleCreateClick" > <gl-loading-icon v-show="labelCreateInProgress" :inline="true" class="mr-1" /> {{ __('Create') }} - </gl-deprecated-button> - <gl-deprecated-button - class="pull-right js-btn-cancel-create" - @click="toggleDropdownContentsCreateView" - > + </gl-button> + <gl-button class="pull-right js-btn-cancel-create" @click="toggleDropdownContentsCreateView"> {{ __('Cancel') }} - </gl-deprecated-button> + </gl-button> </div> </div> </template> 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 a8e48bfe1a1..1ef2e8b3bed 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,16 +1,18 @@ <script> import { mapState, mapGetters, mapActions } from 'vuex'; -import { GlLoadingIcon, GlDeprecatedButton, GlIcon, GlSearchBoxByType, GlLink } from '@gitlab/ui'; +import { GlLoadingIcon, GlButton, GlSearchBoxByType, GlLink } from '@gitlab/ui'; import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes'; +import LabelItem from './label_item.vue'; + export default { components: { GlLoadingIcon, - GlDeprecatedButton, - GlIcon, + GlButton, GlSearchBoxByType, GlLink, + LabelItem, }, data() { return { @@ -20,6 +22,8 @@ export default { }, computed: { ...mapState([ + 'allowLabelCreate', + 'allowMultiselect', 'labelsManagePath', 'labels', 'labelsFetchInProgress', @@ -27,7 +31,7 @@ export default { 'footerCreateLabelTitle', 'footerManageLabelTitle', ]), - ...mapGetters(['selectedLabelsList']), + ...mapGetters(['selectedLabelsList', 'isDropdownVariantSidebar']), visibleLabels() { if (this.searchKey) { return this.labels.filter(label => @@ -56,12 +60,8 @@ export default { 'toggleDropdownContentsCreateView', 'fetchLabels', 'updateSelectedLabels', + 'toggleDropdownContents', ]), - getDropdownLabelBoxStyle(label) { - return { - backgroundColor: label.color, - }; - }, isLabelSelected(label) { return this.selectedLabelsList.includes(label.id); }, @@ -111,6 +111,7 @@ export default { }, handleLabelClick(label) { this.updateSelectedLabels([label]); + if (!this.allowMultiselect) this.toggleDropdownContents(); }, }, }; @@ -123,54 +124,47 @@ export default { 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"> + <div v-if="isDropdownVariantSidebar" class="dropdown-title d-flex align-items-center pt-0 pb-2"> <span class="flex-grow-1">{{ labelsListTitle }}</span> - <gl-deprecated-button + <gl-button :aria-label="__('Close')" variant="link" - size="sm" + size="small" class="dropdown-header-button p-0" + icon="close" @click="toggleDropdownContents" - > - <gl-icon name="close" /> - </gl-deprecated-button> + /> </div> - <div class="dropdown-input"> + <div class="dropdown-input" @click.stop="() => {}"> <gl-search-box-by-type v-model="searchKey" :autofocus="true" /> </div> - <div v-if="!labelsFetchInProgress" ref="labelsListContainer" class="dropdown-content"> + <div v-show="!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> + <label-item + :label="label" + :highlight="index === currentHighlightItem" + @clickLabel="handleLabelClick(label)" + /> </li> - <li v-if="!visibleLabels.length" class="p-2 text-center"> + <li v-show="!visibleLabels.length" class="p-2 text-center"> {{ __('No matching results') }} </li> </ul> </div> - <div class="dropdown-footer"> + <div v-if="isDropdownVariantSidebar" class="dropdown-footer"> <ul class="list-unstyled"> - <li> - <gl-deprecated-button - variant="link" + <li v-if="allowLabelCreate"> + <gl-link class="d-flex w-100 flex-row text-break-word label-item" @click="toggleDropdownContentsCreateView" - >{{ footerCreateLabelTitle }}</gl-deprecated-button + >{{ footerCreateLabelTitle }}</gl-link > </li> <li> - <gl-link :href="labelsManagePath" class="d-flex flex-row text-break-word label-item"> - {{ footerManageLabelTitle }} - </gl-link> + <gl-link :href="labelsManagePath" class="d-flex flex-row text-break-word label-item">{{ + footerManageLabelTitle + }}</gl-link> </li> </ul> </div> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_value.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_value.vue index 695af775750..12ad2acf308 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_value.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_value.vue @@ -9,12 +9,7 @@ export default { GlLabel, }, computed: { - ...mapState([ - 'selectedLabels', - 'allowScopedLabels', - 'labelsFilterBasePath', - 'scopedLabelsDocumentationPath', - ]), + ...mapState(['selectedLabels', 'allowScopedLabels', 'labelsFilterBasePath']), }, methods: { labelFilterUrl(label) { @@ -45,7 +40,6 @@ export default { :background-color="label.color" :target="labelFilterUrl(label)" :scoped="scopedLabel(label)" - :scoped-labels-documentation-link="scopedLabelsDocumentationPath" tooltip-placement="top" /> </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 new file mode 100644 index 00000000000..c95221d71b5 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/label_item.vue @@ -0,0 +1,52 @@ +<script> +import { GlIcon, GlLink } from '@gitlab/ui'; + +export default { + components: { + GlIcon, + GlLink, + }, + props: { + label: { + type: Object, + required: true, + }, + highlight: { + type: Boolean, + required: false, + default: false, + }, + }, + data() { + return { + isSet: this.label.set, + }; + }, + computed: { + labelBoxStyle() { + return { + backgroundColor: this.label.color, + }; + }, + }, + methods: { + handleClick() { + this.isSet = !this.isSet; + this.$emit('clickLabel', this.label); + }, + }, +}; +</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 78102caacf5..f38b66fdfdf 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 @@ -1,7 +1,7 @@ <script> import $ from 'jquery'; import Vue from 'vue'; -import Vuex, { mapState, mapActions } from 'vuex'; +import Vuex, { mapState, mapActions, mapGetters } from 'vuex'; import { __ } from '~/locale'; import DropdownValueCollapsed from '~/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue'; @@ -13,6 +13,8 @@ import DropdownValue from './dropdown_value.vue'; import DropdownButton from './dropdown_button.vue'; import DropdownContents from './dropdown_contents.vue'; +import { DropdownVariant } from './constants'; + Vue.use(Vuex); export default { @@ -33,14 +35,19 @@ export default { type: Boolean, required: true, }, + allowMultiselect: { + type: Boolean, + required: false, + default: false, + }, allowScopedLabels: { type: Boolean, required: true, }, - dropdownOnly: { - type: Boolean, + variant: { + type: String, required: false, - default: false, + default: DropdownVariant.Sidebar, }, selectedLabels: { type: Array, @@ -67,11 +74,6 @@ export default { required: false, default: '', }, - scopedLabelsDocumentationPath: { - type: String, - required: false, - default: '', - }, labelsListTitle: { type: String, required: false, @@ -95,6 +97,10 @@ export default { }, computed: { ...mapState(['showDropdownButton', 'showDropdownContents']), + ...mapGetters(['isDropdownVariantSidebar', 'isDropdownVariantStandalone']), + dropdownButtonVisible() { + return this.isDropdownVariantSidebar ? this.showDropdownButton : true; + }, }, watch: { selectedLabels(selectedLabels) { @@ -105,15 +111,15 @@ export default { }, mounted() { this.setInitialState({ - dropdownOnly: this.dropdownOnly, + variant: this.variant, allowLabelEdit: this.allowLabelEdit, allowLabelCreate: this.allowLabelCreate, + allowMultiselect: this.allowMultiselect, allowScopedLabels: this.allowScopedLabels, selectedLabels: this.selectedLabels, labelsFetchPath: this.labelsFetchPath, labelsManagePath: this.labelsManagePath, labelsFilterBasePath: this.labelsFilterBasePath, - scopedLabelsDocumentationPath: this.scopedLabelsDocumentationPath, labelsListTitle: this.labelsListTitle, labelsCreateTitle: this.labelsCreateTitle, footerCreateLabelTitle: this.footerCreateLabelTitle, @@ -154,13 +160,24 @@ export default { // as the dropdown wrapper is not using `GlDropdown` as // it will also require us to use `BDropdownForm` // which is yet to be implemented in GitLab UI. + const hasExceptionClass = [ + 'js-dropdown-button', + 'js-btn-cancel-create', + 'js-sidebar-dropdown-toggle', + ].some( + className => + target?.classList.contains(className) || + target?.parentElement.classList.contains(className), + ); + + const hadExceptionParent = ['.js-btn-back', '.js-labels-list'].some( + className => $(target).parents(className).length, + ); + if ( - this.showDropdownButton && this.showDropdownContents && - !$(target).parents('.js-btn-back').length && - !$(target).parents('.js-labels-list').length && - !target?.classList.contains('js-btn-cancel-create') && - !target?.classList.contains('js-sidebar-dropdown-toggle') && + !hadExceptionParent && + !hasExceptionClass && !this.$refs.dropdownButtonCollapsed?.$el.contains(target) && !this.$refs.dropdownContents?.$el.contains(target) ) { @@ -181,10 +198,12 @@ export default { </script> <template> - <div class="labels-select-wrapper position-relative"> - <div v-if="!dropdownOnly"> + <div + class="labels-select-wrapper position-relative" + :class="{ 'is-standalone': isDropdownVariantStandalone }" + > + <template v-if="isDropdownVariantSidebar"> <dropdown-value-collapsed - v-if="allowLabelCreate" ref="dropdownButtonCollapsed" :labels="selectedLabels" @onValueClick="handleCollapsedValueClick" @@ -196,8 +215,18 @@ export default { <dropdown-value v-show="!showDropdownButton"> <slot></slot> </dropdown-value> - <dropdown-button v-show="showDropdownButton" /> - <dropdown-contents v-if="showDropdownButton && showDropdownContents" ref="dropdownContents" /> - </div> + <dropdown-button v-show="dropdownButtonVisible" /> + <dropdown-contents + v-if="dropdownButtonVisible && showDropdownContents" + ref="dropdownContents" + /> + </template> + <template v-if="isDropdownVariantStandalone"> + <dropdown-button v-show="dropdownButtonVisible" /> + <dropdown-contents + v-if="dropdownButtonVisible && showDropdownContents" + ref="dropdownContents" + /> + </template> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/getters.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/getters.js index c08a8a8ea58..c39222959a9 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/getters.js +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/getters.js @@ -1,4 +1,5 @@ import { __, s__, sprintf } from '~/locale'; +import { DropdownVariant } from '../constants'; /** * Returns string representing current labels @@ -6,8 +7,11 @@ import { __, s__, sprintf } from '~/locale'; * * @param {object} state */ -export const dropdownButtonText = state => { - const selectedLabels = state.labels.filter(label => label.set); +export const dropdownButtonText = (state, getters) => { + const selectedLabels = getters.isDropdownVariantSidebar + ? state.labels.filter(label => label.set) + : state.selectedLabels; + if (!selectedLabels.length) { return __('Label'); } else if (selectedLabels.length > 1) { @@ -26,5 +30,19 @@ export const dropdownButtonText = state => { */ export const selectedLabelsList = state => state.selectedLabels.map(label => label.id); +/** + * Returns boolean representing whether dropdown variant + * is `sidebar` + * @param {object} state + */ +export const isDropdownVariantSidebar = state => state.variant === DropdownVariant.Sidebar; + +/** + * Returns boolean representing whether dropdown variant + * is `standalone` + * @param {object} state + */ +export const isDropdownVariantStandalone = state => state.variant === DropdownVariant.Standalone; + // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js index 32a78507e88..54f8c78b4e1 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js @@ -1,4 +1,5 @@ import * as types from './mutation_types'; +import { DropdownVariant } from '../constants'; export default { [types.SET_INITIAL_STATE](state, props) { @@ -10,7 +11,7 @@ export default { }, [types.TOGGLE_DROPDOWN_CONTENTS](state) { - if (!state.dropdownOnly) { + if (state.variant === DropdownVariant.Sidebar) { state.showDropdownButton = !state.showDropdownButton; } state.showDropdownContents = !state.showDropdownContents; @@ -57,20 +58,13 @@ export default { }, [types.UPDATE_SELECTED_LABELS](state, { labels }) { - // Iterate over all the labels and update - // `set` prop value to represent their current state. - const labelIds = labels.map(label => label.id); - state.labels = state.labels.reduce((allLabels, label) => { - if (labelIds.includes(label.id)) { - allLabels.push({ - ...label, - touched: true, - set: !label.set, - }); - } else { - allLabels.push(label); - } - return allLabels; - }, []); + // Find the label to update from all the labels + // and change `set` prop value to represent their current state. + const labelId = labels.pop()?.id; + const candidateLabel = state.labels.find(label => labelId === label.id); + if (candidateLabel) { + candidateLabel.touched = true; + candidateLabel.set = !candidateLabel.set; + } }, }; diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/state.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/state.js index ceabc696693..6a6c0b4c0ee 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/state.js +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/state.js @@ -11,13 +11,13 @@ export default () => ({ namespace: '', labelsFetchPath: '', labelsFilterBasePath: '', - scopedLabelsDocumentationPath: '#', // UI Flags + variant: '', allowLabelCreate: false, allowLabelEdit: false, allowScopedLabels: false, - dropdownOnly: false, + allowMultiselect: false, showDropdownButton: false, showDropdownContents: false, showDropdownContentsCreateView: false, |