Files
ui/src/runtime/components/forms/RadioGroup.vue
Gerben Mulder 474accbefb feat(forms): allow null as initial value (#2275)
Co-authored-by: Romain Hamel <rom.hml@gmail.com>
2024-10-01 19:55:03 +02:00

161 lines
4.4 KiB
Vue

<template>
<div :class="ui.wrapper">
<fieldset v-bind="attrs" :class="ui.fieldset">
<legend v-if="legend || $slots.legend" :class="ui.legend">
<slot name="legend">
{{ legend }}
</slot>
</legend>
<URadio
v-for="option in normalizedOptions"
:key="option.value"
:label="option.label"
:model-value="modelValue"
:value="option.value"
:help="option.help"
:disabled="option.disabled || disabled"
:ui="uiRadio"
@change="onUpdate(option.value)"
>
<template #label>
<slot name="label" v-bind="{ option, selected: option.selected }" />
</template>
<template #help>
<slot name="help" v-bind="{ option, selected: option.selected }" />
</template>
</URadio>
</fieldset>
</div>
</template>
<script lang="ts">
import URadio from './Radio.vue'
import { computed, defineComponent, provide, toRef } from 'vue'
import type { PropType } from 'vue'
import { useUI } from '../../composables/useUI'
import { useFormGroup } from '../../composables/useFormGroup'
import { mergeConfig, get } from '../../utils'
import type { DeepPartial, Strategy } from '../../types/index'
// @ts-expect-error
import appConfig from '#build/app.config'
import { radioGroup, radio } from '#ui/ui.config'
import colors from '#ui-colors'
const config = mergeConfig<typeof radioGroup>(appConfig.ui.strategy, appConfig.ui.radioGroup, radioGroup)
const configRadio = mergeConfig<typeof radio>(appConfig.ui.strategy, appConfig.ui.radio, radio)
export default defineComponent({
components: {
URadio
},
inheritAttrs: false,
props: {
modelValue: {
type: [String, Number, Object, Boolean] as PropType<string | number | boolean | object | null>,
default: ''
},
name: {
type: String,
default: null
},
legend: {
type: String,
default: null
},
options: {
type: Array,
default: () => []
},
optionAttribute: {
type: String,
default: 'label'
},
valueAttribute: {
type: String,
default: 'value'
},
disabled: {
type: Boolean,
default: false
},
color: {
type: String as PropType<typeof colors[number]>,
default: () => config.default.color,
validator (value: string) {
return appConfig.ui.colors.includes(value)
}
},
class: {
type: [String, Object, Array] as PropType<any>,
default: () => ''
},
ui: {
type: Object as PropType<DeepPartial<typeof config> & { strategy?: Strategy }>,
default: () => ({})
},
uiRadio: {
type: Object as PropType<DeepPartial<typeof configRadio> & { strategy?: Strategy }>,
default: () => ({})
}
},
emits: ['update:modelValue', 'change'],
setup (props, { emit }) {
const { ui, attrs } = useUI('radioGroup', toRef(props, 'ui'), config, toRef(props, 'class'))
const { ui: uiRadio } = useUI('radio', toRef(props, 'uiRadio'), configRadio)
const { emitFormChange, color, name } = useFormGroup(props, config, false)
provide('radio-group', { color, name })
const onUpdate = (value: any) => {
emit('update:modelValue', value)
emit('change', value)
emitFormChange()
}
const guessOptionValue = (option: any) => {
return get(option, props.valueAttribute, get(option, props.optionAttribute))
}
const guessOptionText = (option: any) => {
return get(option, props.optionAttribute, get(option, props.valueAttribute))
}
const guessOptionSelected = (option: any) => {
return props.modelValue === guessOptionValue(option)
}
const normalizeOption = (option: any) => {
if (['string', 'number', 'boolean'].includes(typeof option)) {
return {
value: option,
label: option
}
}
return {
...option,
value: guessOptionValue(option),
label: guessOptionText(option),
selected: guessOptionSelected(option)
}
}
const normalizedOptions = computed(() => {
return props.options.map(option => normalizeOption(option))
})
return {
// eslint-disable-next-line vue/no-dupe-keys
ui,
// eslint-disable-next-line vue/no-dupe-keys
uiRadio,
attrs,
normalizedOptions,
// eslint-disable-next-line vue/no-dupe-keys
onUpdate
}
}
})
</script>