Skip to content
Snippets Groups Projects
FormGroup.vue 3.16 KiB
Newer Older
  • Learn to ignore specific revisions
  • <template>
    
      <div
        class="form-group last:tw-mb-0"
        :class="[
          formGroupClass,
          {
            'tw-block': !inline,
            'tw-flex tw-gap-2 tw-items-center': inline,
          },
        ]"
      >
    
          class="form-group-description tw-flex tw-gap-x-2 tw-gap-y-1 empty:tw-hidden"
          :class="[
            center ? 'tw-items-center tw-self-center' : 'tw-items-baseline',
            { 'tw-mb-2': !inline },
          ]"
    
            class="tw-font-medium tw-flex tw-gap-2 tw-items-center tw-grow tw-m-0"
    
          >
            <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>
    
    
            v-if="isSaving !== undefined"
            :state="isSaving ? 'pending' : hasErrors ? 'failure' : 'success'"
    
        <div class="tw-flex tw-flex-col">
    
          <div class="tw-grid tw-order-first">
            <slot v-bind="controlAttributes" />
            <slot name="iconLeft" :class="[iconClasses, 'tw-ml-2']" role="presentation" />
            <slot name="iconRight" :class="[iconClasses, 'tw-mr-2']" role="presentation" />
          </div>
    
          <div v-if="errors !== undefined" :id="errorsId" class="invalid-feedback tw-block">
    
            <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, useSlots } from 'vue'
    
    import { useI18n } from '@/i18n'
    
    import { useId } from '@/util'
    
    import SaveIndicator from '@/components/generic/SaveIndicator.vue'
    
    defineOptions({ compatConfig: { MODE: 3 } })
    
    
    type Error = {
    
    const props = withDefaults(
      defineProps<{
        label?: string
        customControl?: boolean
        errors?: undefined | (Error | undefined | null)[]
    
        withEditButton?: boolean
        isSaving?: boolean | undefined
    
      }>(),
      {
        isSaving: undefined,
        label: '',
        errors: undefined,
    
    const emit = defineEmits<{
      edit: []
    
    }>()
    
    const { t } = useI18n()
    
    const slots = useSlots()
    
    const formGroupClass = inject('formGroupClass', undefined)
    const id = useId('form-group-control')
    const errorsId = useId('form-group-errors')
    
    const errorList = computed<Error[]>(() => (props.errors ?? []).filter((e): e is Error => !!e))
    
    const hasErrors = computed(() => props.hasError || errorList.value.length > 0)
    
    const iconClasses = 'tw-pointer-events-none tw-grid-area-cover tw-self-center'
    
    const controlAttributes = computed(() => ({
      class: [
    
        'tw-grid-area-cover',
    
        {
          'is-invalid': hasErrors.value,
          'form-control': !props.customControl,
          'tw-pl-8': slots.iconLeft,
          'tw-pr-8': slots.iconRight,
        },
    
      ],
      'aria-describedby': hasErrors.value ? errorsId.value : '',
      id: id.value,
    }))
    </script>