Files
ui/src/runtime/components/forms/Select.vue
2023-02-17 12:58:19 +01:00

205 lines
4.7 KiB
Vue

<template>
<div :class="wrapperClass">
<select
:id="name"
:name="name"
:value="modelValue"
:required="required"
:disabled="disabled"
:class="selectClass"
@input="onInput(($event.target as any).value)"
>
<template v-for="(option, index) in normalizedOptionsWithPlaceholder">
<optgroup
v-if="option.children"
:key="`${option[valueAttribute]}-optgroup-${index}`"
:value="option[valueAttribute]"
:label="option[textAttribute]"
>
<option
v-for="(childOption, index2) in option.children"
:key="`${childOption[valueAttribute]}-${index}-${index2}`"
:value="childOption[valueAttribute]"
:selected="childOption[valueAttribute] === normalizedValue"
:disabled="childOption.disabled"
v-text="childOption[textAttribute]"
/>
</optgroup>
<option
v-else
:key="`${option[valueAttribute]}-${index}`"
:value="option[valueAttribute]"
:selected="option[valueAttribute] === normalizedValue"
:disabled="option.disabled"
v-text="option[textAttribute]"
/>
</template>
</select>
<div v-if="icon" :class="iconWrapperClass">
<Icon :name="icon" :class="iconClass" />
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { get } from 'lodash-es'
import { classNames } from '../../utils'
import Icon from '../elements/Icon.vue'
import $ui from '#build/ui'
const props = defineProps({
modelValue: {
type: [String, Number, Object],
default: ''
},
name: {
type: String,
required: true
},
placeholder: {
type: String,
default: null
},
required: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
options: {
type: Array,
default: () => []
},
size: {
type: String,
default: 'md',
validator (value: string) {
return Object.keys($ui.select.size).includes(value)
}
},
wrapperClass: {
type: String,
default: () => $ui.select.wrapper
},
baseClass: {
type: String,
default: () => $ui.select.base
},
iconBaseClass: {
type: String,
default: () => $ui.select.icon.base
},
customClass: {
type: String,
default: null
},
appearance: {
type: String,
default: 'default',
validator (value: string) {
return Object.keys($ui.select.appearance).includes(value)
}
},
textAttribute: {
type: String,
default: 'text'
},
valueAttribute: {
type: String,
default: 'value'
},
icon: {
type: String,
default: null
}
})
const emit = defineEmits(['update:modelValue'])
const onInput = (value: string) => {
emit('update:modelValue', value)
}
const guessOptionValue = (option: any) => {
return get(option, props.valueAttribute, get(option, props.textAttribute))
}
const guessOptionText = (option: any) => {
return get(option, props.textAttribute, get(option, props.valueAttribute))
}
const normalizeOption = (option: any) => {
if (['string', 'number', 'boolean'].includes(typeof option)) {
return {
[props.valueAttribute]: option,
[props.textAttribute]: option
}
}
return {
...option,
[props.valueAttribute]: guessOptionValue(option),
[props.textAttribute]: guessOptionText(option)
}
}
const normalizedOptions = computed(() => {
return props.options.map(option => normalizeOption(option))
})
const normalizedOptionsWithPlaceholder = computed(() => {
if (!props.placeholder) {
return normalizedOptions.value
}
return [
{
[props.valueAttribute]: '',
[props.textAttribute]: props.placeholder,
disabled: true
},
...normalizedOptions.value
]
})
const normalizedValue = computed(() => {
const normalizeModelValue = normalizeOption(props.modelValue)
const foundOption = normalizedOptionsWithPlaceholder.value.find(option => option[props.valueAttribute] === normalizeModelValue[props.valueAttribute])
if (!foundOption) {
return ''
}
return foundOption[props.valueAttribute]
})
const selectClass = computed(() => {
return classNames(
props.baseClass,
$ui.select.size[props.size],
$ui.select.spacing[props.size],
$ui.select.appearance[props.appearance],
!!props.icon && $ui.select.leading.spacing[props.size],
$ui.select.trailing.spacing[props.size],
props.customClass
)
})
const iconClass = computed(() => {
return classNames(
props.iconBaseClass,
$ui.select.icon.size[props.size],
!!props.icon && $ui.select.icon.leading.spacing[props.size]
)
})
const iconWrapperClass = $ui.select.icon.leading.wrapper
</script>
<script lang="ts">
export default { name: 'USelect' }
</script>