Commit cac8eab9 authored by Phil Hughes's avatar Phil Hughes

Merge branch '36326-auto-focus-search-bar' into 'master'

Automatically focus cluster dropdown search box

Closes #36326

See merge request gitlab-org/gitlab!21440
parents 0f1cdc8b 3ded0aa3
<script> <script>
import $ from 'jquery';
import { GlIcon } from '@gitlab/ui'; import { GlIcon } from '@gitlab/ui';
import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue'; import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue'; import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
...@@ -106,6 +107,7 @@ export default { ...@@ -106,6 +107,7 @@ export default {
data() { data() {
return { return {
searchQuery: '', searchQuery: '',
focusOnSearch: false,
}; };
}, },
computed: { computed: {
...@@ -141,6 +143,18 @@ export default { ...@@ -141,6 +143,18 @@ export default {
return itemsProp(this.selectedItems, this.valueProperty).join(', '); return itemsProp(this.selectedItems, this.valueProperty).join(', ');
}, },
}, },
mounted() {
$(this.$refs.dropdown)
.on('shown.bs.dropdown', () => {
this.focusOnSearch = true;
})
.on('hidden.bs.dropdown', () => {
this.focusOnSearch = false;
});
},
beforeDestroy() {
$(this.$refs.dropdown).off();
},
methods: { methods: {
getItemsOrEmptyList() { getItemsOrEmptyList() {
return this.items || []; return this.items || [];
...@@ -170,7 +184,7 @@ export default { ...@@ -170,7 +184,7 @@ export default {
<template> <template>
<div> <div>
<div class="js-gcp-machine-type-dropdown dropdown"> <div ref="dropdown" class="dropdown">
<dropdown-hidden-input :name="fieldName" :value="selectedItemsValues" /> <dropdown-hidden-input :name="fieldName" :value="selectedItemsValues" />
<dropdown-button <dropdown-button
:class="{ 'border-danger': hasErrors }" :class="{ 'border-danger': hasErrors }"
...@@ -179,7 +193,11 @@ export default { ...@@ -179,7 +193,11 @@ export default {
:toggle-text="toggleText" :toggle-text="toggleText"
/> />
<div class="dropdown-menu dropdown-select"> <div class="dropdown-menu dropdown-select">
<dropdown-search-input v-model="searchQuery" :placeholder-text="searchFieldPlaceholder" /> <dropdown-search-input
v-model="searchQuery"
:focused="focusOnSearch"
:placeholder-text="searchFieldPlaceholder"
/>
<div class="dropdown-content"> <div class="dropdown-content">
<ul> <ul>
<li v-if="!results.length"> <li v-if="!results.length">
......
...@@ -8,6 +8,11 @@ export default { ...@@ -8,6 +8,11 @@ export default {
required: true, required: true,
default: __('Search'), default: __('Search'),
}, },
focused: {
type: Boolean,
required: false,
default: false,
},
}, },
data() { data() {
return { searchQuery: this.value }; return { searchQuery: this.value };
...@@ -16,6 +21,11 @@ export default { ...@@ -16,6 +21,11 @@ export default {
searchQuery(query) { searchQuery(query) {
this.$emit('input', query); this.$emit('input', query);
}, },
focused(val) {
if (val) {
this.$refs.searchInput.focus();
}
},
}, },
}; };
</script> </script>
...@@ -23,6 +33,7 @@ export default { ...@@ -23,6 +33,7 @@ export default {
<template> <template>
<div class="dropdown-input"> <div class="dropdown-input">
<input <input
ref="searchInput"
v-model="searchQuery" v-model="searchQuery"
:placeholder="placeholderText" :placeholder="placeholderText"
class="dropdown-input-field" class="dropdown-input-field"
......
---
title: Autofocus cluster dropdown search input
merge_request: 21440
author:
type: changed
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import $ from 'jquery';
import { GlIcon } from '@gitlab/ui'; import { GlIcon } from '@gitlab/ui';
import ClusterFormDropdown from '~/create_cluster/eks_cluster/components/cluster_form_dropdown.vue'; import ClusterFormDropdown from '~/create_cluster/eks_cluster/components/cluster_form_dropdown.vue';
...@@ -169,4 +170,14 @@ describe('ClusterFormDropdown', () => { ...@@ -169,4 +170,14 @@ describe('ClusterFormDropdown', () => {
expect(vm.findAll('.js-dropdown-item').length).toEqual(1); expect(vm.findAll('.js-dropdown-item').length).toEqual(1);
expect(vm.find('.js-dropdown-item').text()).toEqual(secondItem.name); expect(vm.find('.js-dropdown-item').text()).toEqual(secondItem.name);
}); });
it('focuses dropdown search input when dropdown is displayed', () => {
const dropdownEl = vm.find('.dropdown').element;
expect(vm.find(DropdownSearchInput).props('focused')).toBe(false);
$(dropdownEl).trigger('shown.bs.dropdown');
expect(vm.find(DropdownSearchInput).props('focused')).toBe(true);
});
}); });
import { mount } from '@vue/test-utils';
import DropdownSearchInputComponent from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
describe('DropdownSearchInputComponent', () => {
let wrapper;
const defaultProps = {
placeholderText: 'Search something',
};
const buildVM = (propsData = defaultProps) => {
wrapper = mount(DropdownSearchInputComponent, {
propsData,
});
};
const findInputEl = () => wrapper.find('.dropdown-input-field');
beforeEach(() => {
buildVM();
});
afterEach(() => {
wrapper.destroy();
});
describe('template', () => {
it('renders input element with type `search`', () => {
expect(findInputEl().exists()).toBe(true);
expect(findInputEl().attributes('type')).toBe('search');
});
it('renders search icon element', () => {
expect(wrapper.find('.fa-search.dropdown-input-search').exists()).toBe(true);
});
it('renders clear search icon element', () => {
expect(wrapper.find('.fa-times.dropdown-input-clear.js-dropdown-input-clear').exists()).toBe(
true,
);
});
it('displays custom placeholder text', () => {
expect(findInputEl().attributes('placeholder')).toBe(defaultProps.placeholderText);
});
it('focuses input element when focused property equals true', () => {
const inputEl = findInputEl().element;
jest.spyOn(inputEl, 'focus');
wrapper.setProps({ focused: true });
expect(inputEl.focus).toHaveBeenCalled();
});
});
});
import Vue from 'vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import dropdownSearchInputComponent from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
const componentConfig = {
placeholderText: 'Search something',
};
const createComponent = (config = componentConfig) => {
const Component = Vue.extend(dropdownSearchInputComponent);
return mountComponent(Component, config);
};
describe('DropdownSearchInputComponent', () => {
let vm;
beforeEach(() => {
vm = createComponent();
});
afterEach(() => {
vm.$destroy();
});
describe('template', () => {
it('renders input element with type `search`', () => {
const inputEl = vm.$el.querySelector('input.dropdown-input-field');
expect(inputEl).not.toBeNull();
expect(inputEl.getAttribute('type')).toBe('search');
});
it('renders search icon element', () => {
expect(vm.$el.querySelector('.fa-search.dropdown-input-search')).not.toBeNull();
});
it('renders clear search icon element', () => {
expect(
vm.$el.querySelector('.fa-times.dropdown-input-clear.js-dropdown-input-clear'),
).not.toBeNull();
});
it('displays custom placeholder text', () => {
const inputEl = vm.$el.querySelector('input.dropdown-input-field');
expect(inputEl.getAttribute('placeholder')).toBe(componentConfig.placeholderText);
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment