diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue b/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue index 834c39a5ee023a2863264792327d8448961fff3b..4e5dfbf3bf8cfac0af6753751c7f22f23e300293 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue @@ -1,15 +1,21 @@ <script> import $ from 'jquery'; +import { GlButton } from '@gitlab/ui'; +import { __ } from '~/locale'; 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. + * + * When there are no results and `showCreateMode` is true + * it renders a create button with the value typed. */ export default { name: 'FilteredSearchDropdown', components: { Icon, + GlButton, }, props: { title: { @@ -43,6 +49,16 @@ export default { type: String, required: true, }, + showCreateMode: { + type: Boolean, + required: false, + default: false, + }, + createButtonText: { + type: String, + required: false, + default: __('Create'), + }, }, data() { return { @@ -64,6 +80,12 @@ export default { return this.items.slice(0, this.visibleItems); }, + computedCreateButtonText() { + return `${this.createButtonText} ${this.filter}`; + }, + shouldRenderCreateButton() { + return this.showCreateMode && this.filteredResults.length === 0 && this.filter !== ''; + }, }, mounted() { /** @@ -112,10 +134,20 @@ export default { <div class="dropdown-content"> <ul> <li v-for="(result, i) in filteredResults" :key="i" class="js-filtered-dropdown-result"> - <slot name="result" :result="result"> {{ result[filterKey] }} </slot> + <slot name="result" :result="result">{{ result[filterKey] }}</slot> </li> </ul> </div> + + <div v-if="shouldRenderCreateButton" class="dropdown-footer"> + <slot name="footer" :filter="filter"> + <gl-button + class="js-dropdown-create-button btn-transparent" + @click="$emit('createItem', filter)" + >{{ computedCreateButtonText }}</gl-button + > + </slot> + </div> </div> </div> </div> diff --git a/spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js b/spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js index b84b5ae67a835374ccb2434cb87263c9dfc73dc7..3d251426b5abac8c0982bdf02e27250fa526f7f2 100644 --- a/spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js +++ b/spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js @@ -88,4 +88,103 @@ describe('Filtered search dropdown', () => { }); }); }); + + describe('with create mode enabled', () => { + describe('when there are no matches', () => { + beforeEach(() => { + vm = mountComponent(Component, { + items: [ + { title: 'One' }, + { title: 'Two/three' }, + { title: 'Three four' }, + { title: 'Five' }, + ], + filterKey: 'title', + showCreateMode: true, + }); + + vm.$el.querySelector('.js-filtered-dropdown-input').value = 'eleven'; + vm.$el.querySelector('.js-filtered-dropdown-input').dispatchEvent(new Event('input')); + }); + + it('renders a create button', done => { + vm.$nextTick(() => { + expect(vm.$el.querySelector('.js-dropdown-create-button')).not.toBeNull(); + done(); + }); + }); + + it('renders computed button text', done => { + vm.$nextTick(() => { + expect(vm.$el.querySelector('.js-dropdown-create-button').textContent.trim()).toEqual( + 'Create eleven', + ); + done(); + }); + }); + + describe('on click create button', () => { + it('emits createItem event with the filter', done => { + spyOn(vm, '$emit'); + vm.$nextTick(() => { + vm.$el.querySelector('.js-dropdown-create-button').click(); + + expect(vm.$emit).toHaveBeenCalledWith('createItem', 'eleven'); + done(); + }); + }); + }); + }); + + describe('when there are matches', () => { + beforeEach(() => { + vm = mountComponent(Component, { + items: [ + { title: 'One' }, + { title: 'Two/three' }, + { title: 'Three four' }, + { title: 'Five' }, + ], + filterKey: 'title', + showCreateMode: true, + }); + + vm.$el.querySelector('.js-filtered-dropdown-input').value = 'one'; + vm.$el.querySelector('.js-filtered-dropdown-input').dispatchEvent(new Event('input')); + }); + + it('does not render a create button', done => { + vm.$nextTick(() => { + expect(vm.$el.querySelector('.js-dropdown-create-button')).toBeNull(); + done(); + }); + }); + }); + }); + + describe('with create mode disabled', () => { + describe('when there are no matches', () => { + beforeEach(() => { + vm = mountComponent(Component, { + items: [ + { title: 'One' }, + { title: 'Two/three' }, + { title: 'Three four' }, + { title: 'Five' }, + ], + filterKey: 'title', + }); + + vm.$el.querySelector('.js-filtered-dropdown-input').value = 'eleven'; + vm.$el.querySelector('.js-filtered-dropdown-input').dispatchEvent(new Event('input')); + }); + + it('does not render a create button', done => { + vm.$nextTick(() => { + expect(vm.$el.querySelector('.js-dropdown-create-button')).toBeNull(); + done(); + }); + }); + }); + }); });