/ src / components / common / Toast.svelte
Toast.svelte
 1  <script lang="ts">
 2    interface Props {
 3      message: string;
 4      type?: 'success' | 'error' | 'warning' | 'info';
 5      visible?: boolean;
 6      duration?: number;
 7      onclose?: () => void;
 8    }
 9  
10    let {
11      message,
12      type = 'info',
13      visible = $bindable(true),
14      duration = 3000,
15      onclose,
16    }: Props = $props();
17  
18    const typeStyles = {
19      success: 'border-status-success bg-status-success/10 text-status-success',
20      error: 'border-status-error bg-status-error/10 text-status-error',
21      warning: 'border-status-warning bg-status-warning/10 text-status-warning',
22      info: 'border-phosphor bg-phosphor/10 text-phosphor',
23    };
24  
25    const icons = {
26      success: 'M5 13l4 4L19 7',
27      error: 'M6 18L18 6M6 6l12 12',
28      warning: 'M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z',
29      info: 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
30    };
31  
32    $effect(() => {
33      if (visible && duration > 0) {
34        const timer = setTimeout(() => {
35          visible = false;
36          onclose?.();
37        }, duration);
38        return () => clearTimeout(timer);
39      }
40    });
41  
42    function close() {
43      visible = false;
44      onclose?.();
45    }
46  </script>
47  
48  {#if visible}
49    <div class="fixed bottom-4 right-4 z-50 animate-in">
50      <div class="flex items-center gap-3 px-4 py-3 rounded-lg border {typeStyles[type]} shadow-lg backdrop-blur-sm">
51        <svg class="w-5 h-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
52          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d={icons[type]} />
53        </svg>
54        <span class="text-sm font-mono">{message}</span>
55        <button
56          class="ml-2 opacity-60 hover:opacity-100 transition-opacity"
57          onclick={close}
58          aria-label="Dismiss"
59        >
60          <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
61            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
62          </svg>
63        </button>
64      </div>
65    </div>
66  {/if}