Toast fixes
This commit is contained in:
+11
-1
@@ -45,7 +45,9 @@
|
||||
<div class="pointer-events-none fixed right-3 bottom-8 z-50 flex w-80 flex-col gap-2">
|
||||
@for (t of toast.toasts(); track t.id) {
|
||||
<div
|
||||
class="pointer-events-auto flex items-start gap-2 rounded-lg border border-border bg-card p-3 text-xs text-card-foreground shadow-lg"
|
||||
(mouseenter)="toast.pause(t.id)"
|
||||
(mouseleave)="toast.resume(t.id)"
|
||||
class="group pointer-events-auto relative flex items-start gap-2 overflow-hidden rounded-lg border border-border bg-card p-3 text-xs text-card-foreground shadow-lg"
|
||||
>
|
||||
<ng-icon
|
||||
[name]="toastIcon(t.kind)"
|
||||
@@ -59,6 +61,14 @@
|
||||
>
|
||||
<ng-icon name="mynaX" size="0.9rem" />
|
||||
</button>
|
||||
<!-- auto-dismiss countdown; freezes with the timer while hovered -->
|
||||
<div
|
||||
class="toast-countdown absolute inset-x-0 bottom-0 h-0.5 origin-left"
|
||||
[class.bg-destructive]="t.kind === 'error'"
|
||||
[class.bg-success]="t.kind === 'success'"
|
||||
[class.bg-primary]="t.kind === 'info'"
|
||||
[style.animation-duration.ms]="t.ttlMs"
|
||||
></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -4,12 +4,17 @@ export interface Toast {
|
||||
id: number;
|
||||
kind: 'error' | 'info' | 'success';
|
||||
text: string;
|
||||
ttlMs: number;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ToastService {
|
||||
readonly toasts = signal<Toast[]>([]);
|
||||
private nextId = 1;
|
||||
private readonly timers = new Map<
|
||||
number,
|
||||
{ timer: ReturnType<typeof setTimeout>; expiresAt: number; remaining: number }
|
||||
>();
|
||||
|
||||
error(text: string) {
|
||||
this.show('error', text, 6000);
|
||||
@@ -24,12 +29,35 @@ export class ToastService {
|
||||
}
|
||||
|
||||
dismiss(id: number) {
|
||||
const entry = this.timers.get(id);
|
||||
if (entry) clearTimeout(entry.timer);
|
||||
this.timers.delete(id);
|
||||
this.toasts.update((list) => list.filter((t) => t.id !== id));
|
||||
}
|
||||
|
||||
/** Freeze the auto-dismiss countdown (hover). */
|
||||
pause(id: number) {
|
||||
const entry = this.timers.get(id);
|
||||
if (!entry) return;
|
||||
clearTimeout(entry.timer);
|
||||
entry.remaining = Math.max(0, entry.expiresAt - Date.now());
|
||||
}
|
||||
|
||||
/** Resume the countdown with whatever time was left when paused. */
|
||||
resume(id: number) {
|
||||
const entry = this.timers.get(id);
|
||||
if (!entry) return;
|
||||
entry.expiresAt = Date.now() + entry.remaining;
|
||||
entry.timer = setTimeout(() => this.dismiss(id), entry.remaining);
|
||||
}
|
||||
|
||||
private show(kind: Toast['kind'], text: string, ttlMs: number) {
|
||||
const toast = { id: this.nextId++, kind, text };
|
||||
const toast = { id: this.nextId++, kind, text, ttlMs };
|
||||
this.toasts.update((list) => [...list, toast]);
|
||||
setTimeout(() => this.dismiss(toast.id), ttlMs);
|
||||
this.timers.set(toast.id, {
|
||||
timer: setTimeout(() => this.dismiss(toast.id), ttlMs),
|
||||
expiresAt: Date.now() + ttlMs,
|
||||
remaining: ttlMs,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user