gitlab-org--gitlab-foss/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.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>