diff options
Diffstat (limited to 'app/assets/javascripts/vue_shared/components/listbox_input')
-rw-r--r-- | app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.stories.js | 26 | ||||
-rw-r--r-- | app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue | 110 |
2 files changed, 136 insertions, 0 deletions
diff --git a/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.stories.js b/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.stories.js new file mode 100644 index 00000000000..4106de371cb --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.stories.js @@ -0,0 +1,26 @@ +import ListboxInput from './listbox_input.vue'; + +export default { + component: ListboxInput, + title: 'vue_shared/listbox_input', +}; + +const Template = (args, { argTypes }) => ({ + components: { ListboxInput }, + data() { + return { selected: null }; + }, + props: Object.keys(argTypes), + template: '<listbox-input v-model="selected" v-bind="$props" />', +}); + +export const Default = Template.bind({}); +Default.args = { + name: 'input_name', + defaultToggleText: 'Select an option', + items: [ + { text: 'Option 1', value: '1' }, + { text: 'Option 2', value: '2' }, + { text: 'Option 3', value: '3' }, + ], +}; diff --git a/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue b/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue new file mode 100644 index 00000000000..b1809e6a9f3 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue @@ -0,0 +1,110 @@ +<script> +import { GlListbox } from '@gitlab/ui'; +import { __ } from '~/locale'; + +const MIN_ITEMS_COUNT_FOR_SEARCHING = 20; + +export default { + i18n: { + noResultsText: __('No results found'), + }, + components: { + GlListbox, + }, + model: GlListbox.model, + props: { + name: { + type: String, + required: true, + }, + defaultToggleText: { + type: String, + required: true, + }, + selected: { + type: String, + required: false, + default: null, + }, + items: { + type: GlListbox.props.items.type, + required: true, + }, + }, + data() { + return { + searchString: '', + }; + }, + computed: { + allOptions() { + const allOptions = []; + + const getOptions = (options) => { + for (let i = 0; i < options.length; i += 1) { + const option = options[i]; + if (option.options) { + getOptions(option.options); + } else { + allOptions.push(option); + } + } + }; + getOptions(this.items); + + return allOptions; + }, + isGrouped() { + return this.items.some((item) => item.options !== undefined); + }, + isSearchable() { + return this.allOptions.length > MIN_ITEMS_COUNT_FOR_SEARCHING; + }, + filteredItems() { + const searchString = this.searchString.toLowerCase(); + + if (!searchString) { + return this.items; + } + + if (this.isGrouped) { + return this.items + .map(({ text, options }) => { + return { + text, + options: options.filter((option) => option.text.toLowerCase().includes(searchString)), + }; + }) + .filter(({ options }) => options.length); + } + + return this.items.filter((item) => item.text.toLowerCase().includes(searchString)); + }, + toggleText() { + return this.selected + ? this.allOptions.find((option) => option.value === this.selected).text + : this.defaultToggleText; + }, + }, + methods: { + search(searchString) { + this.searchString = searchString; + }, + }, +}; +</script> + +<template> + <div> + <gl-listbox + :selected="selected" + :toggle-text="toggleText" + :items="filteredItems" + :searchable="isSearchable" + :no-results-text="$options.i18n.noResultsText" + @search="search" + @select="$emit($options.model.event, $event)" + /> + <input ref="input" type="hidden" :name="name" :value="selected" /> + </div> +</template> |