summaryrefslogtreecommitdiff
path: root/frontend/src/components/Status.svelte
blob: 7f06d95abcc4e9f856e3fb9e374d90f656ce000a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<script lang="ts">
  import type { Snippet } from 'svelte';
  import { Alert, AlertTitle, AlertDescription } from '$lib/components/ui/alert';
  import { Skeleton } from '$lib/components/ui/skeleton';
  import * as Card from '$lib/components/ui/card';

  interface Props {
    error?: string | null;
    busy?: boolean;
    busyText?: string;
    empty?: string | boolean | null;
    emptyHint?: string;
    emptyCta?: Snippet;
    errorCta?: Snippet;
    warnings?: string[];
    skeletonRows?: number;
  }
  let { error, busy, busyText = '加载中…', empty, emptyHint, emptyCta, errorCta, warnings, skeletonRows = 3 }: Props = $props();
</script>

{#if busy}
  <div class="flex flex-col gap-1" data-testid="busy-skeleton" aria-label={busyText} role="status">
    {#each Array.from({ length: skeletonRows }) as _, i (i)}
      <Skeleton class="h-10 w-full" />
    {/each}
  </div>
{:else if error}
  <Alert variant="destructive" data-testid="load-error">
    <span aria-hidden="true">⚠</span>
    <AlertTitle>加载失败</AlertTitle>
    <AlertDescription>{error ?? busyText}</AlertDescription>
    {#if errorCta}
      <div class="mt-2">
        {@render errorCta()}
      </div>
    {/if}
  </Alert>
{:else if empty !== null && empty !== undefined && empty !== false}
  <Card.Root class="border-dashed text-center py-10 px-6" data-testid="empty-state">
    <p class="text-lg font-semibold mb-1">{empty}</p>
    {#if emptyHint}
      <p class="text-xs text-muted-foreground mb-4">{emptyHint}</p>
    {/if}
    {#if emptyCta}{@render emptyCta()}{/if}
  </Card.Root>
{/if}
{#if warnings && warnings.length > 0}
  {#each warnings as w, i (i)}
    <Alert variant="default"><span aria-hidden="true">△</span><span>{w}</span></Alert>
  {/each}
{/if}