Implement basic select/search-select components in vue [SCI-8249]

This commit is contained in:
Martin Artnik 2023-05-03 12:37:14 +02:00
parent 1457d09da8
commit 863054b044
4 changed files with 261 additions and 0 deletions

View file

@ -0,0 +1,101 @@
.sn-select {
border: 1px solid;
border-color: var(--sn-grey);
border-radius: 4px;
cursor: pointer;
padding: .5em;
position: relative;
&.sn-select--blank {
.sn-select__value {
color: var(--sn-grey);
font-style: italic;
}
}
&.sn-select--search.sn-select--open {
.sn-select__value {
display: none;
}
}
.sn-select__value {
all: unset;
overflow: hidden;
padding-right: 1em;
text-overflow: ellipsis;
white-space: nowrap;
width: calc(100% - 1em);
}
&:hover {
background: var(--sn-sleepy-grey);
}
&.disabled {
background: var(--sn-sleepy-grey);
cursor: default;
pointer-events: none;
.caret {
border-top-color: var(--sn-grey);
}
}
.sn-select__options {
display: none;
}
.sn-select__caret {
position: absolute;
right: 1em;
top: 1.1em;
}
.sn-select__search-input {
background: transparent;
border: 0;
display: none;
outline: 0;
}
&.sn-select--open {
background: $color-white;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
border-color: var(--sn-science-blue);
.sn-select__search-input {
display: block;
}
.sn-select__caret {
transform: rotate(180deg);
}
.sn-select__options {
background: var(--sn-white);
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
box-shadow: $flyout-shadow;
display: block;
left: 0;
max-height: 300px;
overflow: hidden;
overflow-y: scroll;
position: absolute;
top: 2.5em;
width: 100%;
z-index: 9999;
}
.sn-select__option {
padding: .5em;
}
.sn-select__option:hover {
background: var(--sn-blue);
color: var(--sn-white);
}
}
}

View file

@ -0,0 +1,82 @@
<template>
<div @click="toggle" ref="container" class="sn-select" :class="{ 'sn-select--open': isOpen, 'sn-select--blank': !valueLabel, 'disabled': disabled }">
<slot>
<button ref="focusElement" class="sn-select__value">
<span>{{ valueLabel || (placeholder || i18n.t('general.select')) }}</span>
</button>
<span class="sn-select__caret caret"></span>
</slot>
<div ref="optionsContainer" class="sn-select__options" :style="optionPositionStyle">
<div v-for="option in options" :key="option[0]" @click="setValue(option[0])" class="sn-select__option">
{{ option[1] }}
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Select',
props: {
options: { type: Array, default: () => [] },
initialValue: { type: [String, Number] },
placeholder: { type: String },
disabled: { type: Boolean, default: false }
},
data() {
return {
value: null,
isOpen: false,
optionPositionStyle: ''
}
},
computed: {
valueLabel() {
let option = this.options.find((o) => o[0] === this.value);
return option && option[1];
},
focusElement() {
return this.$refs.focusElement || this.$scopedSlots.default()[0].context.$refs.focusElement;
}
},
mounted() {
this.focusElement.onblur = this.blur;
document.addEventListener("scroll", this.updateOptionPosition);
},
methods: {
blur() {
setTimeout(() => {
this.isOpen = false;
this.$emit('blur');
}, 100)
},
toggle() {
this.isOpen = !this.isOpen;
if (this.isOpen) {
this.$emit('open');
this.$nextTick(() => {
this.focusElement.focus();
});
this.$refs.optionsContainer.scrollTop = 0;
this.updateOptionPosition();
} else {
this.optionPositionStyle = '';
this.$emit('close');
}
},
setValue(value) {
this.value = value;
this.$emit('change', this.value);
},
updateOptionPosition() {
let rect = this.$refs.container.getBoundingClientRect();
let top =rect.top + rect.height;
let left = rect.left;
let width = rect.width;
this.optionPositionStyle = `position: fixed; top: ${top}px; left: ${left}px; width: ${width}px`
}
}
}
</script>

View file

@ -0,0 +1,77 @@
<template>
<Select class="sn-select--search" :options="currentOptions" :placeholder="placeholder" @change="change" @blur="blur" @open="open" @close="close">
<input ref="focusElement" v-model="query" type="text" class="sn-select__search-input" :placeholder="searchPlaceholder" />
<span class="sn-select__value">{{ valueLabel || (placeholder || i18n.t('general.select')) }}</span>
<span class="sn-select__caret caret"></span>
</Select>
</template>
<script>
import Select from './select.vue'
export default {
name: 'SelectSearch',
props: {
options: { type: Array, default: () => [] },
optionsUrl: { type: String },
placeholder: { type: String },
searchPlaceholder: { type: String }
},
components: { Select },
data() {
return {
value: null,
query: null,
currentOptions: null,
isOpen: false
}
},
created() {
this.currentOptions = this.options;
},
watch: {
query() {
if(!this.query) {
this.currentOptions = this.options;
return;
}
if (this.optionsUrl) {
this.fetchOptions();
} else {
this.currentOptions = this.options.filter((o) => o[1].toLowerCase().includes(this.query.toLowerCase()));
}
}
},
computed: {
valueLabel() {
let option = this.options.find((o) => o[0] === this.value);
return option && option[1];
}
},
methods: {
blur() {
this.$emit('blur');
},
change(value) {
this.value = value;
this.$emit('change', this.value);
},
open() {
this.isOpen = true;
this.$emit('open');
},
close() {
this.isOpen = false;
this.$emit('close');
},
fetchOptions() {
$.get(`${this.optionsUrl}?query=${this.query}`,
(data) => {
this.currentOptions = data;
}
);
}
}
}
</script>

View file

@ -3367,6 +3367,7 @@ en:
remove: "Remove"
clone_label: "Clone"
download: "Download"
select: "Select"
# In order to use the strings 'yes' and 'no' as keys, you need to wrap them with quotes
'yes': "Yes"
'no': "No"