managarten/packages/shared-ui/src/toast/toast.svelte.ts
Till-JS 14ce457c7b 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>
2026-01-29 14:03:29 +01:00

146 lines
3.3 KiB
TypeScript

/**
* Toast Store - Centralized notification system using Svelte 5 runes
*
* Usage:
* ```ts
* import { toastStore } from '@manacore/shared-ui';
*
* // Show notifications
* toastStore.success('Saved successfully');
* toastStore.error('Something went wrong');
* toastStore.warning('Please check your input');
* toastStore.info('New update available');
*
* // Manual control
* const id = toastStore.show('Custom message', 'info', 5000);
* toastStore.dismiss(id);
* toastStore.dismissAll();
* ```
*/
export type ToastType = 'success' | 'error' | 'warning' | 'info';
export interface Toast {
id: string;
type: ToastType;
message: string;
duration: number;
}
// State
let toasts = $state<Toast[]>([]);
// Auto-incrementing ID with timestamp for uniqueness
let nextId = 0;
function generateId(): string {
return `toast-${++nextId}-${Date.now()}`;
}
export const toastStore = {
/**
* Get all active toasts (reactive)
*/
get toasts() {
return toasts;
},
/**
* Show a toast notification
* @param message - The message to display
* @param type - Toast type: 'success' | 'error' | 'warning' | 'info'
* @param duration - Duration in ms (0 = permanent, default: 4000)
* @returns The toast ID for manual dismissal
*/
show(message: string, type: ToastType = 'info', duration = 4000): string {
const id = generateId();
const toast: Toast = { id, type, message, duration };
toasts = [...toasts, toast];
// Auto-remove after duration (unless permanent)
if (duration > 0) {
setTimeout(() => {
this.dismiss(id);
}, duration);
}
return id;
},
/**
* Show a success toast (green)
* @param message - The message to display
* @param duration - Duration in ms (default: 4000)
*/
success(message: string, duration?: number): string {
return this.show(message, 'success', duration);
},
/**
* Show an error toast (red) - longer default duration
* @param message - The message to display
* @param duration - Duration in ms (default: 6000)
*/
error(message: string, duration = 6000): string {
return this.show(message, 'error', duration);
},
/**
* Show a warning toast (amber)
* @param message - The message to display
* @param duration - Duration in ms (default: 4000)
*/
warning(message: string, duration?: number): string {
return this.show(message, 'warning', duration);
},
/**
* Show an info toast (blue)
* @param message - The message to display
* @param duration - Duration in ms (default: 4000)
*/
info(message: string, duration?: number): string {
return this.show(message, 'info', duration);
},
/**
* Dismiss a specific toast by ID
* @param id - The toast ID to dismiss
*/
dismiss(id: string): void {
toasts = toasts.filter((t) => t.id !== id);
},
/**
* Dismiss all active toasts
*/
dismissAll(): void {
toasts = [];
},
};
/**
* Helper function for API error handling
* Shows an error toast and returns the error message
*
* @example
* ```ts
* try {
* await api.save(data);
* } catch (error) {
* handleApiError(error, 'Could not save data');
* }
* ```
*/
export function handleApiError(
error: unknown,
fallbackMessage = 'Ein Fehler ist aufgetreten'
): string {
const message = error instanceof Error ? error.message : fallbackMessage;
toastStore.error(message);
return message;
}
// Backwards compatible alias
export const toast = toastStore;