73 lines
2.3 KiB
Vue
73 lines
2.3 KiB
Vue
<script setup lang="ts">
|
|
import { CircleAlert, CircleCheck, Inbox, LoaderCircle } from 'lucide-vue-next';
|
|
import { computed } from 'vue';
|
|
import { Button } from '@/components/ui/button';
|
|
import { useLocalization } from '@/composables/useLocalization';
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
state: 'empty' | 'loading' | 'error' | 'success';
|
|
title?: string;
|
|
description?: string;
|
|
retryable?: boolean;
|
|
}>(),
|
|
{ retryable: false },
|
|
);
|
|
defineEmits<{ retry: [] }>();
|
|
const { t } = useLocalization();
|
|
const icons = {
|
|
empty: Inbox,
|
|
loading: LoaderCircle,
|
|
error: CircleAlert,
|
|
success: CircleCheck,
|
|
};
|
|
const defaultTitles = {
|
|
empty: 'feedback.empty',
|
|
loading: 'common.loading',
|
|
error: 'common.error',
|
|
success: 'feedback.saved',
|
|
} as const;
|
|
const heading = computed(() => props.title ?? t(defaultTitles[props.state]));
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
:role="state === 'error' ? 'alert' : 'status'"
|
|
:aria-busy="state === 'loading' || undefined"
|
|
:data-feedback="state"
|
|
class="gap-card bg-card p-card text-body text-card-foreground flex min-w-0 items-start rounded-md border"
|
|
:class="{
|
|
'border-destructive/40': state === 'error',
|
|
'border-success/40': state === 'success',
|
|
}"
|
|
>
|
|
<component
|
|
:is="icons[state]"
|
|
aria-hidden="true"
|
|
class="mt-0.5 size-5 shrink-0"
|
|
:class="{
|
|
'text-primary animate-spin motion-reduce:animate-none':
|
|
state === 'loading',
|
|
'text-destructive': state === 'error',
|
|
'text-success-foreground': state === 'success',
|
|
'text-muted-foreground': state === 'empty',
|
|
}"
|
|
/>
|
|
<div class="gap-stack grid min-w-0 flex-1 break-words">
|
|
<p class="font-semibold">{{ heading }}</p>
|
|
<p v-if="description" class="text-muted-foreground">
|
|
{{ description }}
|
|
</p>
|
|
<div v-if="$slots.default"><slot /></div>
|
|
<Button
|
|
v-if="retryable && state === 'error'"
|
|
type="button"
|
|
variant="outline"
|
|
class="justify-self-start"
|
|
@click="$emit('retry')"
|
|
>{{ t('common.retry') }}</Button
|
|
>
|
|
</div>
|
|
</div>
|
|
</template>
|