this repo has no description
1<script lang="ts">
2 import { getToasts, dismissToast, type Toast } from '../lib/toast.svelte'
3
4 const toasts = $derived(getToasts())
5
6 function handleDismiss(id: number) {
7 dismissToast(id)
8 }
9
10 function getIcon(type: Toast['type']): string {
11 switch (type) {
12 case 'success':
13 return '✓'
14 case 'error':
15 return '!'
16 case 'warning':
17 return '⚠'
18 case 'info':
19 return 'i'
20 }
21 }
22</script>
23
24{#if toasts.length > 0}
25 <div class="toast-container" role="region" aria-label="Notifications">
26 {#each toasts as toast (toast.id)}
27 <div
28 class="toast toast-{toast.type}"
29 class:dismissing={toast.dismissing}
30 role="alert"
31 aria-live="polite"
32 >
33 <span class="toast-icon">{getIcon(toast.type)}</span>
34 <span class="toast-message">{toast.message}</span>
35 <button
36 type="button"
37 class="toast-dismiss"
38 onclick={() => handleDismiss(toast.id)}
39 aria-label="Dismiss notification"
40 >
41 x
42 </button>
43 </div>
44 {/each}
45 </div>
46{/if}
47
48<style>
49 .toast-container {
50 position: fixed;
51 top: var(--space-6);
52 right: var(--space-6);
53 z-index: 9999;
54 display: flex;
55 flex-direction: column;
56 gap: var(--space-3);
57 max-width: min(400px, calc(100vw - var(--space-12)));
58 pointer-events: none;
59 }
60
61 .toast {
62 display: flex;
63 align-items: flex-start;
64 gap: var(--space-3);
65 padding: var(--space-4);
66 border-radius: var(--radius-lg);
67 box-shadow: var(--shadow-lg);
68 pointer-events: auto;
69 animation: toast-in 0.1s ease-out;
70 }
71
72 .toast.dismissing {
73 animation: toast-out 0.15s ease-in forwards;
74 }
75
76 @keyframes toast-in {
77 from {
78 opacity: 0;
79 transform: scale(0.95);
80 }
81 to {
82 opacity: 1;
83 transform: scale(1);
84 }
85 }
86
87 @keyframes toast-out {
88 from {
89 opacity: 1;
90 transform: scale(1);
91 }
92 to {
93 opacity: 0;
94 transform: scale(0.95);
95 }
96 }
97
98 .toast-success {
99 background: var(--success-bg);
100 border: 1px solid var(--success-border);
101 color: var(--success-text);
102 }
103
104 .toast-error {
105 background: var(--error-bg);
106 border: 1px solid var(--error-border);
107 color: var(--error-text);
108 }
109
110 .toast-warning {
111 background: var(--warning-bg);
112 border: 1px solid var(--warning-border);
113 color: var(--warning-text);
114 }
115
116 .toast-info {
117 background: var(--accent-muted);
118 border: 1px solid var(--accent);
119 color: var(--text-primary);
120 }
121
122 .toast-icon {
123 flex-shrink: 0;
124 width: 20px;
125 height: 20px;
126 display: flex;
127 align-items: center;
128 justify-content: center;
129 border-radius: 50%;
130 font-size: var(--text-xs);
131 font-weight: var(--font-bold);
132 }
133
134 .toast-success .toast-icon {
135 background: var(--success-text);
136 color: var(--success-bg);
137 }
138
139 .toast-error .toast-icon {
140 background: var(--error-text);
141 color: var(--error-bg);
142 }
143
144 .toast-warning .toast-icon {
145 background: var(--warning-text);
146 color: var(--warning-bg);
147 }
148
149 .toast-info .toast-icon {
150 background: var(--accent);
151 color: var(--bg-card);
152 }
153
154 .toast-message {
155 flex: 1;
156 font-size: var(--text-sm);
157 line-height: 1.4;
158 }
159
160 .toast-dismiss {
161 flex-shrink: 0;
162 width: 20px;
163 height: 20px;
164 padding: 0;
165 border: none;
166 background: transparent;
167 cursor: pointer;
168 opacity: 0.6;
169 font-size: var(--text-sm);
170 line-height: 1;
171 color: inherit;
172 border-radius: var(--radius-sm);
173 }
174
175 .toast-dismiss:hover {
176 opacity: 1;
177 background: rgba(0, 0, 0, 0.1);
178 }
179
180 @media (max-width: 480px) {
181 .toast-container {
182 top: var(--space-4);
183 right: var(--space-4);
184 left: var(--space-4);
185 max-width: none;
186 }
187 }
188</style>