129 lines
3 KiB
Vue
129 lines
3 KiB
Vue
<script>
|
|
import { GlAvatarLabeled, GlCollapsibleListbox } from '@gitlab/ui';
|
|
import { uniqueId } from 'lodash';
|
|
import { s__, n__ } from '~/locale';
|
|
import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
|
|
import searchProjectTopics from '~/graphql_shared/queries/project_topics_search.query.graphql';
|
|
|
|
export default {
|
|
components: {
|
|
GlAvatarLabeled,
|
|
GlCollapsibleListbox,
|
|
},
|
|
props: {
|
|
selectedTopic: {
|
|
type: Object,
|
|
required: false,
|
|
default: () => ({}),
|
|
},
|
|
labelText: {
|
|
type: String,
|
|
required: false,
|
|
default: null,
|
|
},
|
|
},
|
|
apollo: {
|
|
topics: {
|
|
query: searchProjectTopics,
|
|
variables() {
|
|
return {
|
|
search: this.search,
|
|
};
|
|
},
|
|
update(data) {
|
|
return data.topics?.nodes || [];
|
|
},
|
|
debounce: 250,
|
|
},
|
|
},
|
|
data() {
|
|
return {
|
|
topics: [],
|
|
search: '',
|
|
selected: null,
|
|
};
|
|
},
|
|
computed: {
|
|
loading() {
|
|
return this.$apollo.queries.topics.loading;
|
|
},
|
|
dropdownText() {
|
|
if (Object.keys(this.selectedTopic).length) {
|
|
return this.selectedTopic.name;
|
|
}
|
|
|
|
return this.$options.i18n.dropdownText;
|
|
},
|
|
items() {
|
|
return this.topics.map(({ id, title, name, avatarUrl }) => ({
|
|
value: id,
|
|
text: title,
|
|
secondaryText: name,
|
|
icon: avatarUrl,
|
|
}));
|
|
},
|
|
searchSummary() {
|
|
return n__('TopicSelect|%d topic found', 'TopicSelect|%d topics found', this.topics.length);
|
|
},
|
|
labelId() {
|
|
if (!this.labelText) {
|
|
return null;
|
|
}
|
|
|
|
return uniqueId('topic-listbox-label-');
|
|
},
|
|
},
|
|
methods: {
|
|
onSelect(topicId) {
|
|
const topicObj = this.topics.find((topic) => topic.id === topicId);
|
|
|
|
if (!topicObj) return;
|
|
|
|
this.$emit('click', topicObj);
|
|
},
|
|
onSearch(query) {
|
|
this.search = query;
|
|
},
|
|
},
|
|
i18n: {
|
|
dropdownText: s__('TopicSelect|Select a topic'),
|
|
searchPlaceholder: s__('TopicSelect|Search topics'),
|
|
emptySearchResult: s__('TopicSelect|No matching results'),
|
|
},
|
|
AVATAR_SHAPE_OPTION_RECT,
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<label v-if="labelText" :id="labelId">{{ labelText }}</label>
|
|
<gl-collapsible-listbox
|
|
v-model="selected"
|
|
block
|
|
searchable
|
|
is-check-centered
|
|
:items="items"
|
|
:toggle-text="dropdownText"
|
|
:searching="loading"
|
|
:search-placeholder="$options.i18n.searchPlaceholder"
|
|
:no-results-text="$options.i18n.emptySearchResult"
|
|
:toggle-aria-labelled-by="labelId"
|
|
@select="onSelect"
|
|
@search="onSearch"
|
|
>
|
|
<template #list-item="{ item: { text, secondaryText, icon } }">
|
|
<gl-avatar-labeled
|
|
:label="text"
|
|
:sub-label="secondaryText"
|
|
:src="icon"
|
|
:entity-name="secondaryText"
|
|
:size="32"
|
|
:shape="$options.AVATAR_SHAPE_OPTION_RECT"
|
|
/>
|
|
</template>
|
|
<template #search-summary-sr-only>
|
|
{{ searchSummary }}
|
|
</template>
|
|
</gl-collapsible-listbox>
|
|
</div>
|
|
</template>
|