feat: UserNotificationService supports click-to-navigate toasts
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
This commit is contained in:
@@ -1,9 +1,14 @@
|
||||
@if (notifications().length > 0) {
|
||||
<aside class="floating-notifications" aria-live="polite" aria-atomic="true">
|
||||
@for (note of notifications(); track note.id) {
|
||||
<article class="floating-note" [class]="'floating-note floating-note-' + note.type">
|
||||
<article
|
||||
class="floating-note"
|
||||
[class]="'floating-note floating-note-' + note.type"
|
||||
[class.floating-note-clickable]="!!note.route"
|
||||
(click)="note.route && navigate(note)"
|
||||
>
|
||||
<p>{{ note.message }}</p>
|
||||
<button type="button" (click)="dismiss(note.id)" [attr.aria-label]="'common.dismiss' | translate">×</button>
|
||||
<button type="button" (click)="$event.stopPropagation(); dismiss(note.id)" [attr.aria-label]="'common.dismiss' | translate">×</button>
|
||||
</article>
|
||||
}
|
||||
</aside>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||
import { UserNotificationService } from '../../services/user-notification.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { UserNotification, UserNotificationService } from '../../services/user-notification.service';
|
||||
import { TranslatePipe } from '../../../../../i18n/translate.pipe';
|
||||
|
||||
@Component({
|
||||
@@ -12,10 +13,18 @@ import { TranslatePipe } from '../../../../../i18n/translate.pipe';
|
||||
})
|
||||
export class FloatingNotificationsComponent {
|
||||
private readonly notificationsService = inject(UserNotificationService);
|
||||
private readonly router = inject(Router);
|
||||
|
||||
readonly notifications = this.notificationsService.notifications;
|
||||
|
||||
dismiss(id: string): void {
|
||||
this.notificationsService.dismiss(id);
|
||||
}
|
||||
|
||||
navigate(note: UserNotification): void {
|
||||
if (note.route) {
|
||||
void this.router.navigate(note.route);
|
||||
}
|
||||
this.dismiss(note.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { UserNotificationService } from './user-notification.service';
|
||||
|
||||
describe('UserNotificationService', () => {
|
||||
let service: UserNotificationService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(UserNotificationService);
|
||||
});
|
||||
|
||||
it('stores the route on the notification when provided', () => {
|
||||
service.show('New order #1042', 'info', 4000, ['en', 'backoffice', 'orders', 'ord_1']);
|
||||
|
||||
const [note] = service.notifications();
|
||||
expect(note.message).toBe('New order #1042');
|
||||
expect(note.route).toEqual(['en', 'backoffice', 'orders', 'ord_1']);
|
||||
});
|
||||
|
||||
it('leaves route undefined when not provided', () => {
|
||||
service.show('Saved');
|
||||
|
||||
const [note] = service.notifications();
|
||||
expect(note.route).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -6,6 +6,8 @@ export interface UserNotification {
|
||||
id: string;
|
||||
message: string;
|
||||
type: UserNotificationType;
|
||||
/** Route to navigate to when the notification is clicked. Absent means not clickable. */
|
||||
route?: string[];
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
@@ -14,11 +16,12 @@ export class UserNotificationService {
|
||||
|
||||
readonly notifications = this.state.asReadonly();
|
||||
|
||||
show(message: string, type: UserNotificationType = 'info', durationMs: number = 2500): void {
|
||||
show(message: string, type: UserNotificationType = 'info', durationMs: number = 2500, route?: string[]): void {
|
||||
const next: UserNotification = {
|
||||
id: `note-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||||
message,
|
||||
type
|
||||
type,
|
||||
...(route ? { route } : {}),
|
||||
};
|
||||
|
||||
this.state.update(items => [next, ...items].slice(0, 4));
|
||||
|
||||
Reference in New Issue
Block a user