68 lines
2.0 KiB
Vue
68 lines
2.0 KiB
Vue
<script setup lang="ts">
|
|
import { computed, nextTick, useTemplateRef, watch } from 'vue';
|
|
import FeedbackState from '@/components/FeedbackState.vue';
|
|
import { useLocalization } from '@/composables/useLocalization';
|
|
import type { FormFeedbackState } from '@/lib/form-feedback';
|
|
|
|
const props = defineProps<{ state: FormFeedbackState }>();
|
|
defineEmits<{ retry: [] }>();
|
|
const { t } = useLocalization();
|
|
const feedback = useTemplateRef<HTMLDivElement>('feedback');
|
|
watch(
|
|
() => props.state,
|
|
async (state) => {
|
|
if (['validation', 'network', 'server', 'cancelled'].includes(state)) {
|
|
await nextTick();
|
|
if (props.state === state) {
|
|
feedback.value?.focus();
|
|
}
|
|
}
|
|
},
|
|
);
|
|
const titles = {
|
|
idle: 'feedback.saved',
|
|
loading: 'forms.saving',
|
|
success: 'feedback.saved',
|
|
validation: 'forms.validationTitle',
|
|
network: 'forms.networkTitle',
|
|
server: 'forms.serverTitle',
|
|
cancelled: 'forms.cancelledTitle',
|
|
} as const;
|
|
const descriptions = {
|
|
validation: 'forms.validationDescription',
|
|
network: 'forms.networkDescription',
|
|
server: 'forms.serverDescription',
|
|
cancelled: 'forms.cancelledDescription',
|
|
} as const;
|
|
const description = computed(() =>
|
|
props.state in descriptions
|
|
? t(descriptions[props.state as keyof typeof descriptions])
|
|
: undefined,
|
|
);
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
ref="feedback"
|
|
v-show="state !== 'idle'"
|
|
:data-form-state="state"
|
|
tabindex="-1"
|
|
class="focus-visible:ring-ring/35 rounded-md outline-none focus-visible:ring-2"
|
|
>
|
|
<FeedbackState
|
|
v-if="state !== 'idle'"
|
|
:state="
|
|
state === 'loading' || state === 'success' ? state : 'error'
|
|
"
|
|
:title="t(titles[state])"
|
|
:description="description"
|
|
:retryable="
|
|
state === 'network' ||
|
|
state === 'server' ||
|
|
state === 'cancelled'
|
|
"
|
|
@retry="$emit('retry')"
|
|
/>
|
|
</div>
|
|
</template>
|