Skip to content
Snippets Groups Projects
FormGroup.vue 2.93 KiB
Newer Older
  • Learn to ignore specific revisions
  • <template>
      <div class="form-group tw-block" :class="formGroupClass">
    
        <div class="tw-flex tw-items-baseline tw-gap-2">
          <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 tw-grow"
          >
            <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>
    
          <Transition
            enter-active-class="tw-transition tw-duration-200 tw-ease-out"
            enter-from-class="tw-opacity-0"
            enter-to-class="tw-opacity-100"
            leave-active-class="tw-transition tw-duration-150 tw-ease-in"
            leave-from-class="tw-opacity-100"
            leave-to-class="tw-opacity-0"
          >
            <SaveIndicator v-if="isSavingDebounced" />
          </Transition>
        </div>
    
        <div class="tw-flex tw-flex-col">
          <slot v-bind="controlAttributes" />
    
          <div
            v-if="hasErrors"
            :id="errorsId"
            class="invalid-feedback"
            :class="{ 'tw-block': hasErrors }"
          >
    
            <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, ref, watchEffect } from 'vue'
    import { computedDebounced, useId } from '@/util'
    
    import { useI18n } from '@/i18n'
    
    import SaveIndicator from '@/components/generic/SaveIndicator.vue'
    
    defineOptions({ compatConfig: { MODE: 3 } })
    
    
    type Error = {
    
    }
    
    const props = defineProps<{
    
      customControl?: boolean
    
      errors?: undefined | (Error | undefined | null)[]
    
      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 ?? []).filter((e): e is Error => !!e))
    
    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,
    }))
    
    
    let isSavingStart = new Date()
    const isSavingDebounced = computedDebounced(
      () => props.isSaving,
      (isSaving) => {
        if (isSaving) return 0
        const now = new Date()
        const timePassed = (now.getTime() - isSavingStart.getTime()) / 1000
        return timePassed < 1 ? 1 - timePassed : 0
      },
    )
    watchEffect(() => {
      if (props.isSaving) isSavingStart = new Date()
    })