2018-10-22 08:31:24 +00:00
|
|
|
<script>
|
|
|
|
import $ from 'jquery';
|
2020-04-03 09:09:31 +00:00
|
|
|
import { GlDeprecatedButton } from '@gitlab/ui';
|
2019-01-23 16:27:37 +00:00
|
|
|
import { __ } from '~/locale';
|
2018-10-22 08:31:24 +00:00
|
|
|
import Icon from '~/vue_shared/components/icon.vue';
|
|
|
|
/**
|
|
|
|
* Renders a split dropdown with
|
|
|
|
* an input that allows to search through the given
|
|
|
|
* array of options.
|
2019-01-23 16:27:37 +00:00
|
|
|
*
|
|
|
|
* When there are no results and `showCreateMode` is true
|
|
|
|
* it renders a create button with the value typed.
|
2018-10-22 08:31:24 +00:00
|
|
|
*/
|
|
|
|
export default {
|
|
|
|
name: 'FilteredSearchDropdown',
|
|
|
|
components: {
|
|
|
|
Icon,
|
2020-04-03 09:09:31 +00:00
|
|
|
GlDeprecatedButton,
|
2018-10-22 08:31:24 +00:00
|
|
|
},
|
|
|
|
props: {
|
|
|
|
title: {
|
|
|
|
type: String,
|
|
|
|
required: false,
|
|
|
|
default: '',
|
|
|
|
},
|
|
|
|
buttonType: {
|
|
|
|
required: false,
|
|
|
|
validator: value =>
|
|
|
|
['primary', 'default', 'secondary', 'success', 'info', 'warning', 'danger'].indexOf(
|
|
|
|
value,
|
|
|
|
) !== -1,
|
|
|
|
default: 'default',
|
|
|
|
},
|
|
|
|
size: {
|
|
|
|
required: false,
|
|
|
|
type: String,
|
|
|
|
default: 'sm',
|
|
|
|
},
|
|
|
|
items: {
|
|
|
|
type: Array,
|
|
|
|
required: true,
|
|
|
|
},
|
|
|
|
visibleItems: {
|
|
|
|
type: Number,
|
|
|
|
required: false,
|
|
|
|
default: 5,
|
|
|
|
},
|
|
|
|
filterKey: {
|
|
|
|
type: String,
|
|
|
|
required: true,
|
|
|
|
},
|
2019-01-23 16:27:37 +00:00
|
|
|
showCreateMode: {
|
|
|
|
type: Boolean,
|
|
|
|
required: false,
|
|
|
|
default: false,
|
|
|
|
},
|
|
|
|
createButtonText: {
|
|
|
|
type: String,
|
|
|
|
required: false,
|
|
|
|
default: __('Create'),
|
|
|
|
},
|
2018-10-22 08:31:24 +00:00
|
|
|
},
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
filter: '',
|
|
|
|
};
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
className() {
|
|
|
|
return `btn btn-${this.buttonType} btn-${this.size}`;
|
|
|
|
},
|
|
|
|
filteredResults() {
|
|
|
|
if (this.filter !== '') {
|
|
|
|
return this.items.filter(
|
2018-10-30 20:28:31 +00:00
|
|
|
item =>
|
|
|
|
item[this.filterKey] &&
|
|
|
|
item[this.filterKey].toLowerCase().includes(this.filter.toLowerCase()),
|
2018-10-22 08:31:24 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.items.slice(0, this.visibleItems);
|
2018-10-30 20:28:31 +00:00
|
|
|
},
|
2019-01-23 16:27:37 +00:00
|
|
|
computedCreateButtonText() {
|
|
|
|
return `${this.createButtonText} ${this.filter}`;
|
|
|
|
},
|
|
|
|
shouldRenderCreateButton() {
|
|
|
|
return this.showCreateMode && this.filteredResults.length === 0 && this.filter !== '';
|
|
|
|
},
|
2018-10-22 08:31:24 +00:00
|
|
|
},
|
|
|
|
mounted() {
|
|
|
|
/**
|
|
|
|
* Resets the filter every time the user closes the dropdown
|
|
|
|
*/
|
|
|
|
$(this.$el)
|
|
|
|
.on('shown.bs.dropdown', () => {
|
|
|
|
this.$nextTick(() => this.$refs.searchInput.focus());
|
|
|
|
})
|
|
|
|
.on('hidden.bs.dropdown', () => {
|
|
|
|
this.filter = '';
|
|
|
|
});
|
|
|
|
},
|
|
|
|
};
|
|
|
|
</script>
|
|
|
|
<template>
|
|
|
|
<div class="dropdown">
|
|
|
|
<div class="btn-group">
|
2018-11-16 20:07:38 +00:00
|
|
|
<slot name="mainAction" :class-name="className">
|
|
|
|
<button type="button" :class="className">{{ title }}</button>
|
2018-10-22 08:31:24 +00:00
|
|
|
</slot>
|
|
|
|
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
:class="className"
|
|
|
|
class="dropdown-toggle dropdown-toggle-split"
|
|
|
|
data-toggle="dropdown"
|
|
|
|
aria-haspopup="true"
|
|
|
|
aria-expanded="false"
|
2019-07-08 08:26:37 +00:00
|
|
|
:aria-label="__('Expand dropdown')"
|
2018-10-22 08:31:24 +00:00
|
|
|
>
|
2018-11-16 20:07:38 +00:00
|
|
|
<icon name="angle-down" :size="12" />
|
2018-10-22 08:31:24 +00:00
|
|
|
</button>
|
|
|
|
<div class="dropdown-menu dropdown-menu-right">
|
|
|
|
<div class="dropdown-input">
|
|
|
|
<input
|
|
|
|
ref="searchInput"
|
|
|
|
v-model="filter"
|
|
|
|
type="search"
|
2019-07-08 08:26:37 +00:00
|
|
|
:placeholder="__('Filter')"
|
2018-10-22 08:31:24 +00:00
|
|
|
class="js-filtered-dropdown-input dropdown-input-field"
|
|
|
|
/>
|
2018-11-16 20:07:38 +00:00
|
|
|
<icon class="dropdown-input-search" name="search" />
|
2018-10-22 08:31:24 +00:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="dropdown-content">
|
|
|
|
<ul>
|
2018-11-16 20:07:38 +00:00
|
|
|
<li v-for="(result, i) in filteredResults" :key="i" class="js-filtered-dropdown-result">
|
2019-01-23 16:27:37 +00:00
|
|
|
<slot name="result" :result="result">{{ result[filterKey] }}</slot>
|
2018-10-22 08:31:24 +00:00
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</div>
|
2019-01-23 16:27:37 +00:00
|
|
|
|
|
|
|
<div v-if="shouldRenderCreateButton" class="dropdown-footer">
|
|
|
|
<slot name="footer" :filter="filter">
|
2020-04-03 09:09:31 +00:00
|
|
|
<gl-deprecated-button
|
2019-01-23 16:27:37 +00:00
|
|
|
class="js-dropdown-create-button btn-transparent"
|
|
|
|
@click="$emit('createItem', filter)"
|
2020-04-03 09:09:31 +00:00
|
|
|
>{{ computedCreateButtonText }}</gl-deprecated-button
|
2019-01-23 16:27:37 +00:00
|
|
|
>
|
|
|
|
</slot>
|
|
|
|
</div>
|
2018-10-22 08:31:24 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|