- 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>
92 lines
2.1 KiB
TypeScript
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();
|