refactor(shared-ui): centralize toast system across all web apps

- Add toast module to @manacore/shared-ui (toastStore, ToastContainer)
- Remove local toast implementations from:
  - calendar/web
  - chat/web
  - clock/web
  - contacts/web
  - picture/web
  - storage/web
- Update all apps to import toast from shared-ui
- Consistent toast API: toast.success(), toast.error(), toast.info()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2026-01-29 14:03:29 +01:00
parent 6d0d9d4f67
commit 14ce457c7b
56 changed files with 487 additions and 1249 deletions

View file

@ -1,72 +0,0 @@
<script lang="ts">
import { toasts } from '$lib/stores/toast';
import type { Toast } from '$lib/stores/toast';
function getIcon(type: Toast['type']) {
switch (type) {
case 'success':
return '✓';
case 'error':
return '✕';
case 'warning':
return '⚠';
case 'info':
default:
return '';
}
}
function getColorClass(type: Toast['type']) {
switch (type) {
case 'success':
return 'bg-green-500';
case 'error':
return 'bg-red-500';
case 'warning':
return 'bg-yellow-500';
case 'info':
default:
return 'bg-blue-500';
}
}
</script>
<div class="fixed bottom-4 right-4 z-50 flex flex-col gap-2">
{#each $toasts as toast (toast.id)}
<div
class="flex items-center gap-3 rounded-lg bg-card px-4 py-3 shadow-lg border border-border animate-in slide-in-from-right duration-200"
>
<span
class="{getColorClass(
toast.type
)} flex h-6 w-6 items-center justify-center rounded-full text-white text-sm"
>
{getIcon(toast.type)}
</span>
<span class="text-foreground">{toast.message}</span>
<button
onclick={() => toasts.remove(toast.id)}
class="ml-2 text-muted-foreground hover:text-foreground"
>
</button>
</div>
{/each}
</div>
<style>
@keyframes slide-in-from-right {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.animate-in {
animation: slide-in-from-right 0.2s ease-out;
}
</style>

View file

@ -1,47 +0,0 @@
import { writable } from 'svelte/store';
export interface Toast {
id: string;
type: 'success' | 'error' | 'info' | 'warning';
message: string;
duration?: number;
}
function createToastStore() {
const { subscribe, update } = writable<Toast[]>([]);
function addToast(toast: Omit<Toast, 'id'>) {
const id = crypto.randomUUID();
const newToast = { ...toast, id };
update((toasts) => [...toasts, newToast]);
// Auto-remove after duration
const duration = toast.duration || 5000;
setTimeout(() => {
removeToast(id);
}, duration);
return id;
}
function removeToast(id: string) {
update((toasts) => toasts.filter((t) => t.id !== id));
}
return {
subscribe,
success: (message: string, duration?: number) =>
addToast({ type: 'success', message, duration }),
error: (message: string, duration?: number) => addToast({ type: 'error', message, duration }),
info: (message: string, duration?: number) => addToast({ type: 'info', message, duration }),
warning: (message: string, duration?: number) =>
addToast({ type: 'warning', message, duration }),
remove: removeToast,
};
}
export const toasts = createToastStore();
// Alias for compatibility with different import styles
export const toast = toasts;

View file

@ -1,10 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
import { _ } from 'svelte-i18n';
import { PageHeader } from '@manacore/shared-ui';
import { PageHeader, toast } from '@manacore/shared-ui';
import { alarmsStore } from '$lib/stores/alarms.svelte';
import { authStore } from '$lib/stores/auth.svelte';
import { toast } from '$lib/stores/toast';
import type { CreateAlarmInput, Alarm } from '@clock/shared';
import { ALARM_SOUNDS, DEFAULT_ALARM_PRESETS } from '@clock/shared';
import { AlarmsSkeleton } from '$lib/components/skeletons';

View file

@ -2,10 +2,9 @@
import { onMount, onDestroy } from 'svelte';
import { _ } from 'svelte-i18n';
import { browser } from '$app/environment';
import { PageHeader } from '@manacore/shared-ui';
import { PageHeader, toast } from '@manacore/shared-ui';
import { timersStore } from '$lib/stores/timers.svelte';
import { authStore } from '$lib/stores/auth.svelte';
import { toast } from '$lib/stores/toast';
import { QUICK_TIMER_PRESETS, formatDuration } from '@clock/shared';
import { TimersSkeleton } from '$lib/components/skeletons';

View file

@ -1,10 +1,9 @@
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { _ } from 'svelte-i18n';
import { PageHeader } from '@manacore/shared-ui';
import { PageHeader, toast } from '@manacore/shared-ui';
import { worldClocksStore } from '$lib/stores/world-clocks.svelte';
import { authStore } from '$lib/stores/auth.svelte';
import { toast } from '$lib/stores/toast';
import { POPULAR_TIMEZONES } from '@clock/shared';
import WorldMap from '$lib/components/WorldMap.svelte';
import { WorldClockSkeleton } from '$lib/components/skeletons';

View file

@ -5,7 +5,7 @@
import { theme } from '$lib/stores/theme.svelte';
import { authStore } from '$lib/stores/auth.svelte';
import { waitLocale } from '$lib/i18n';
import ToastContainer from '$lib/components/ToastContainer.svelte';
import { ToastContainer } from '@manacore/shared-ui';
import { AppLoadingSkeleton } from '$lib/components/skeletons';
let { children } = $props();