felt/frontend/src/lib/stores/toast.svelte.ts
Mikkel Georgsen 7f91301efa feat(01-13): layout shell with header, tabs, FAB, toast, data table
- Persistent header: clock countdown, level, blinds, player count (red pulse <10s, PAUSED/BREAK badges)
- Bottom tab bar (mobile): Overview, Players, Tables, Financials, More with 48px touch targets
- Desktop sidebar (>=768px): vertical nav replacing bottom tabs
- FAB: expandable quick actions (Bust, Buy In, Rebuy, Add-On, Pause/Resume) with backdrop
- Toast notification system: success/info/warning/error with auto-dismiss and stacking
- DataTable: sortable columns, sticky header, search/filter, mobile swipe actions, skeleton loading
- Multi-tournament tabs: horizontal scrollable selector when 2+ tournaments active
- Loading components: spinner (sm/md/lg), skeleton rows, full-page overlay
- Root layout: auth guard, responsive shell (mobile bottom tabs / desktop sidebar)
- Route pages: overview, players, tables, financials, more with placeholder content

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 04:13:17 +01:00

92 lines
2.1 KiB
TypeScript

/**
* Toast notification state using Svelte 5 runes.
*
* Provides success/info/warning/error notifications with auto-dismiss.
* Toasts stack vertically and animate in/out.
*/
export type ToastType = 'success' | 'info' | 'warning' | 'error';
export interface Toast {
id: string;
type: ToastType;
message: string;
duration: number;
dismissible: boolean;
timer?: ReturnType<typeof setTimeout>;
}
/** Default auto-dismiss durations by type (milliseconds). */
const DEFAULT_DURATIONS: Record<ToastType, number> = {
success: 3000,
info: 4000,
warning: 5000,
error: 8000
};
let nextId = 0;
class ToastState {
toasts = $state<Toast[]>([]);
/** Add a success toast (green, 3s auto-dismiss). */
success(message: string, duration?: number): string {
return this.add('success', message, duration);
}
/** Add an info toast (blue, 4s auto-dismiss). */
info(message: string, duration?: number): string {
return this.add('info', message, duration);
}
/** Add a warning toast (yellow, 5s auto-dismiss). */
warning(message: string, duration?: number): string {
return this.add('warning', message, duration);
}
/** Add an error toast (red, 8s auto-dismiss). */
error(message: string, duration?: number): string {
return this.add('error', message, duration);
}
/** Dismiss a toast by ID. */
dismiss(id: string): void {
const toast = this.toasts.find((t) => t.id === id);
if (toast?.timer) {
clearTimeout(toast.timer);
}
this.toasts = this.toasts.filter((t) => t.id !== id);
}
/** Dismiss all toasts. */
dismissAll(): void {
for (const t of this.toasts) {
if (t.timer) clearTimeout(t.timer);
}
this.toasts = [];
}
private add(type: ToastType, message: string, duration?: number): string {
const id = `toast-${++nextId}`;
const dur = duration ?? DEFAULT_DURATIONS[type];
const toast: Toast = {
id,
type,
message,
duration: dur,
dismissible: type === 'error'
};
// Auto-dismiss after duration
toast.timer = setTimeout(() => {
this.dismiss(id);
}, dur);
this.toasts = [...this.toasts, toast];
return id;
}
}
/** Singleton toast state instance. */
export const toast = new ToastState();