Files
crewli/apps/portal/src/components/public-form/FieldRadio.vue
bert.hausmans 4074dce402 feat(portal): public-form component architecture
Replace monolithic register/[eventSlug].vue with composable field
renderer, conditional-logic engine, stepper, and per-field components
driven by Form Builder schema. Adds flatpickr for date fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 17:20:59 +02:00

86 lines
2.0 KiB
Vue

<script setup lang="ts">
import type { FieldOption, PublicFormField } from '@/types/formBuilder'
import { getValidatorsForField } from '@/utils/formValidation'
const props = defineProps<{
field: PublicFormField
modelValue: unknown
errorMessages?: string[]
}>()
const emit = defineEmits<{
(e: 'update:modelValue', v: string): void
(e: 'blur'): void
}>()
interface NormalizedOption {
label: string
description?: string | null
value: string
}
function normalize(opt: FieldOption): NormalizedOption {
if (typeof opt === 'string') return { label: opt, value: opt }
const base = { label: opt.label, description: opt.description ?? null }
const value = opt.value != null ? String(opt.value) : opt.label
return { ...base, value }
}
const options = computed<NormalizedOption[]>(() =>
(props.field.options ?? []).map(normalize),
)
const rules = computed(() => getValidatorsForField(props.field))
const model = computed({
get: () => (props.modelValue ?? '') as string,
set: (v: string) => {
emit('update:modelValue', v)
emit('blur')
},
})
</script>
<template>
<div>
<div class="text-body-2 mb-1 text-high-emphasis">
{{ field.label }}<span
v-if="field.is_required"
class="text-error"
> *</span>
</div>
<p
v-if="field.help_text"
class="text-caption text-medium-emphasis mb-2"
>
{{ field.help_text }}
</p>
<VRadioGroup
v-model="model"
density="comfortable"
:rules="rules"
:error-messages="errorMessages"
hide-details="auto"
>
<VRadio
v-for="opt in options"
:key="opt.value"
:value="opt.value"
>
<template #label>
<div>
<span class="text-body-1">{{ opt.label }}</span>
<p
v-if="opt.description"
class="text-disabled text-caption mt-1 mb-0"
>
{{ opt.description }}
</p>
</div>
</template>
</VRadio>
</VRadioGroup>
</div>
</template>