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}