<template> <div class="form-group tw-block" :class="formGroupClass"> <label v-if="label" :for="id" class="tw-text-gray-500 tw-font-medium tw-pt-1 tw-flex tw-gap-2 tw-items-center" > <span>{{ label }}</span> <button v-if="withEditButton" type="button" class="btn btn-sm tw-p-0" @click="emit('edit')"> <icon-system-uicons-pen /> </button> </label> <div class="tw-flex tw-flex-col"> <slot v-bind="controlAttributes" /> <div v-if="hasErrors" :id="errorsId" class="invalid-feedback tw-order-first"> <template v-for="(error, index) in errorList" :key="index"> <p class="last:tw-mb-0"> {{ error.code ? t(`error.${error.code}`) : error.message ? error.message : t('error.unknown') }} </p> </template> </div> </div> </div> </template> <script lang="ts" setup> import { computed, inject } from 'vue' import { useId } from '@/util' import { useI18n } from '@/i18n' defineOptions({ compatConfig: { MODE: 3 } }) type Error = { code: string message?: string } const props = defineProps<{ label?: string customControl?: boolean errors?: undefined | Error[] withEditButton?: boolean }>() const emit = defineEmits<{ edit: [] }>() const { t } = useI18n() const formGroupClass = inject('formGroupClass', undefined) const id = useId('form-group-control') const errorsId = useId('form-group-errors') const errorList = computed<Error[]>(() => props.errors ?? []) const hasErrors = computed(() => errorList.value.length > 0) const controlAttributes = computed(() => ({ class: [ 'tw-order-first', { 'is-invalid': hasErrors.value, 'form-control': !props.customControl }, ], 'aria-describedby': hasErrors.value ? errorsId.value : '', id: id.value, })) </script>