78 lines
2.0 KiB
Vue
78 lines
2.0 KiB
Vue
<script setup lang="ts">
|
||
/**
|
||
* StateBlock — the CLAUDE.md mandatory three-state wrapper (loading /
|
||
* error / empty) + success passthrough. Built fresh (no crewli-starter
|
||
* source). Per Plan 3 constraint #5 this component intentionally has
|
||
* NO @visual baseline yet (self-baseline is tautological); correctness
|
||
* is locked by the exhaustive Vitest spec. Visual baseline is a
|
||
* Plans 4–5 follow-up after first real page usage.
|
||
*/
|
||
import Skeleton from 'primevue/skeleton'
|
||
import Message from 'primevue/message'
|
||
import Button from 'primevue/button'
|
||
import Card from 'primevue/card'
|
||
|
||
defineProps<{
|
||
state: 'loading' | 'error' | 'empty' | 'success'
|
||
errorMessage?: string
|
||
emptyMessage?: string
|
||
actionLabel?: string
|
||
retryLabel?: string
|
||
}>()
|
||
|
||
const emit = defineEmits<{ retry: []; action: [] }>()
|
||
</script>
|
||
|
||
<template>
|
||
<div
|
||
v-if="state === 'loading'"
|
||
class="flex flex-col gap-3"
|
||
data-state="loading"
|
||
>
|
||
<Skeleton height="2rem" />
|
||
<Skeleton
|
||
height="2rem"
|
||
width="80%"
|
||
/>
|
||
<Skeleton
|
||
height="2rem"
|
||
width="60%"
|
||
/>
|
||
</div>
|
||
|
||
<Message
|
||
v-else-if="state === 'error'"
|
||
severity="error"
|
||
:closable="false"
|
||
data-state="error"
|
||
>
|
||
<div class="flex items-center justify-between gap-4 w-full">
|
||
<span>{{ errorMessage ?? 'Er ging iets mis.' }}</span>
|
||
<Button
|
||
:label="retryLabel ?? 'Opnieuw proberen'"
|
||
size="small"
|
||
@click="emit('retry')"
|
||
/>
|
||
</div>
|
||
</Message>
|
||
|
||
<Card
|
||
v-else-if="state === 'empty'"
|
||
data-state="empty"
|
||
:pt="{ root: 'border border-dashed border-[var(--p-content-border-color)] shadow-none', content: 'flex flex-col items-center gap-3 py-10 text-center' }"
|
||
>
|
||
<template #content>
|
||
<p class="m-0 text-[var(--p-text-muted-color)]">
|
||
{{ emptyMessage ?? 'Nog niets om te tonen.' }}
|
||
</p>
|
||
<Button
|
||
v-if="actionLabel"
|
||
:label="actionLabel"
|
||
@click="emit('action')"
|
||
/>
|
||
</template>
|
||
</Card>
|
||
|
||
<slot v-else />
|
||
</template>
|