189 lines
4.7 KiB
Vue
189 lines
4.7 KiB
Vue
<script>
|
|
import {
|
|
GlLoadingIcon,
|
|
GlDropdown,
|
|
GlDropdownForm,
|
|
GlDropdownDivider,
|
|
GlDropdownItem,
|
|
GlSearchBoxByType,
|
|
} from '@gitlab/ui';
|
|
import { __ } from '~/locale';
|
|
|
|
export default {
|
|
components: {
|
|
GlLoadingIcon,
|
|
GlDropdown,
|
|
GlDropdownForm,
|
|
GlDropdownDivider,
|
|
GlDropdownItem,
|
|
GlSearchBoxByType,
|
|
},
|
|
props: {
|
|
selectText: {
|
|
type: String,
|
|
required: false,
|
|
default: __('Select'),
|
|
},
|
|
searchText: {
|
|
type: String,
|
|
required: false,
|
|
default: __('Search'),
|
|
},
|
|
presetOptions: {
|
|
type: Array,
|
|
required: false,
|
|
default: () => [],
|
|
},
|
|
options: {
|
|
type: Array,
|
|
required: false,
|
|
default: () => [],
|
|
},
|
|
isLoading: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: false,
|
|
},
|
|
selected: {
|
|
type: [Object, Array],
|
|
required: false,
|
|
default: () => {},
|
|
},
|
|
searchTerm: {
|
|
type: String,
|
|
required: false,
|
|
default: '',
|
|
},
|
|
allowMultiselect: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: false,
|
|
},
|
|
customIsSelectedOption: {
|
|
type: Function,
|
|
required: false,
|
|
default: undefined,
|
|
},
|
|
},
|
|
computed: {
|
|
isSearchEmpty() {
|
|
return this.searchTerm === '' && !this.isLoading;
|
|
},
|
|
noOptionsFound() {
|
|
return !this.isSearchEmpty && this.options.length === 0;
|
|
},
|
|
},
|
|
methods: {
|
|
selectOption(option) {
|
|
this.$emit('set-option', option || null);
|
|
if (!this.allowMultiselect) {
|
|
this.$refs.dropdown.hide();
|
|
}
|
|
},
|
|
isSelected(option) {
|
|
if (this.customIsSelectedOption !== undefined) {
|
|
return this.customIsSelectedOption(option);
|
|
}
|
|
if (Array.isArray(this.selected)) {
|
|
return this.selected.some((label) => label.title === option.title);
|
|
}
|
|
return this.selected && option.id && this.selected.id === option.id;
|
|
},
|
|
showDropdown() {
|
|
this.$refs.dropdown.show();
|
|
},
|
|
setFocus() {
|
|
this.$refs.search?.focusInput();
|
|
},
|
|
setSearchTerm(search) {
|
|
this.$emit('set-search', search);
|
|
},
|
|
avatarUrl(option) {
|
|
return option.avatar_url || option.avatarUrl || null;
|
|
},
|
|
secondaryText(option) {
|
|
// TODO: this has some knowledge of the context where the component is used. We could later rework it.
|
|
return option.username || null;
|
|
},
|
|
optionKey(option) {
|
|
return option.key ? option.key : option.id;
|
|
},
|
|
},
|
|
i18n: {
|
|
noMatchingResults: __('No matching results'),
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<gl-dropdown
|
|
ref="dropdown"
|
|
:text="selectText"
|
|
lazy
|
|
menu-class="gl-w-full!"
|
|
class="gl-w-full"
|
|
v-on="$listeners"
|
|
@shown="setFocus"
|
|
>
|
|
<template #header>
|
|
<slot name="header">
|
|
<gl-search-box-by-type
|
|
ref="search"
|
|
:value="searchTerm"
|
|
:placeholder="searchText"
|
|
class="js-dropdown-input-field"
|
|
@input="setSearchTerm"
|
|
/>
|
|
</slot>
|
|
</template>
|
|
<slot name="default">
|
|
<gl-dropdown-form class="gl-relative gl-min-h-7" data-qa-selector="labels_dropdown_content">
|
|
<gl-loading-icon
|
|
v-if="isLoading"
|
|
size="lg"
|
|
class="gl-absolute gl-left-0 gl-top-0 gl-right-0"
|
|
/>
|
|
<template v-else>
|
|
<template v-if="isSearchEmpty && presetOptions.length > 0">
|
|
<gl-dropdown-item
|
|
v-for="option in presetOptions"
|
|
:key="option.id"
|
|
:is-checked="isSelected(option)"
|
|
is-check-centered
|
|
is-check-item
|
|
@click.native.capture.stop="selectOption(option)"
|
|
>
|
|
<slot name="preset-item" :item="option">
|
|
{{ option.title }}
|
|
</slot>
|
|
</gl-dropdown-item>
|
|
<gl-dropdown-divider />
|
|
</template>
|
|
<gl-dropdown-item
|
|
v-for="option in options"
|
|
:key="optionKey(option)"
|
|
:is-checked="isSelected(option)"
|
|
is-check-centered
|
|
is-check-item
|
|
:avatar-url="avatarUrl(option)"
|
|
:secondary-text="secondaryText(option)"
|
|
data-testid="unselected-option"
|
|
@click.native.capture.stop="selectOption(option)"
|
|
>
|
|
<slot name="item" :item="option">
|
|
{{ option.title }}
|
|
</slot>
|
|
</gl-dropdown-item>
|
|
<slot v-bind="{ isSelected }" name="grouped-options"></slot>
|
|
<gl-dropdown-item v-if="noOptionsFound" class="gl-pl-6!">
|
|
{{ $options.i18n.noMatchingResults }}
|
|
</gl-dropdown-item>
|
|
</template>
|
|
</gl-dropdown-form>
|
|
</slot>
|
|
<template #footer>
|
|
<slot name="footer"></slot>
|
|
</template>
|
|
</gl-dropdown>
|
|
</template>
|