diff --git a/src/app/app.component.html b/src/app/app.component.html index 2f70899..332550e 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,3 +1,3 @@ - + diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 9be37f5..625ed47 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -2,9 +2,11 @@ import {Component} from '@angular/core'; import { RouterOutlet } from '@angular/router'; import {HeaderComponent} from '@app/header/header.component'; +import { NotificationBoxComponent } from './notification-box/notification-box.component'; + @Component({ selector: 'app-root', - imports: [RouterOutlet, HeaderComponent], + imports: [RouterOutlet, HeaderComponent, NotificationBoxComponent], templateUrl: './app.component.html', styleUrl: './app.component.scss' }) diff --git a/src/app/core/notification/notification.service.ts b/src/app/core/notification/notification.service.ts new file mode 100644 index 0000000..995919a --- /dev/null +++ b/src/app/core/notification/notification.service.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@angular/core'; +import { Subject, Subscription } from 'rxjs'; + +export interface Notification { + msg: string; + type: NotificationType; +} + +export enum NotificationType { + Information = 'info', + Error = 'error', +} + +export type NotificationCallback = (notification: Notification) => void; + +@Injectable({ providedIn: 'root' }) +export class NotificationService { + private readonly bus: Subject = new Subject(); + private readonly subscribers: Map = new Map(); + + subscribe(subscriberId: string, callback: NotificationCallback): void { + this.subscribers.set( + subscriberId, + this.bus.subscribe({ + next: callback + }) + ); + } + + publish(msg: string, type: NotificationType = NotificationType.Information) { + this.bus.next({ msg, type }); + } + + unsubscribe(subscriberId: string) { + this.subscribers.get(subscriberId)?.unsubscribe(); + } +} diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html index 9f4a656..bd828b2 100644 --- a/src/app/header/header.component.html +++ b/src/app/header/header.component.html @@ -9,4 +9,4 @@ login Login - \ No newline at end of file + diff --git a/src/app/header/header.component.scss b/src/app/header/header.component.scss index 4cb99ae..c9252ad 100644 --- a/src/app/header/header.component.scss +++ b/src/app/header/header.component.scss @@ -1,6 +1,4 @@ .header { - position: sticky; - top: 0; margin: 0; padding: 0.5rem 1rem; z-index: 100; @@ -31,3 +29,4 @@ min-width: fit-content; } } + diff --git a/src/app/notification-box/notification-box.component.html b/src/app/notification-box/notification-box.component.html new file mode 100644 index 0000000..5dcc820 --- /dev/null +++ b/src/app/notification-box/notification-box.component.html @@ -0,0 +1,7 @@ +
+ @for (notification of notifications.values(); track notification){ + + {{notification.msg}} + + } +
diff --git a/src/app/notification-box/notification-box.component.scss b/src/app/notification-box/notification-box.component.scss new file mode 100644 index 0000000..002d31e --- /dev/null +++ b/src/app/notification-box/notification-box.component.scss @@ -0,0 +1,31 @@ +.notification-box { + position: absolute; + z-index: 100; + right: 0; + top: 3.5rem; // Header Height + + width: 15%; + min-width: 20rem; + + display: flex; + flex-direction: column; + padding: 0.5rem; + gap: 0.5rem; + + overflow: hidden; + + &__card { + width: 100%; + box-sizing: border-box; + + &.info { + color: var(--mat-sys-tertiary); + background-color: var(--mat-sys-tertiary-container); + } + + &.error { + color: var(--mat-sys-error); + background-color: var(--mat-sys-error-container); + } + } +} diff --git a/src/app/notification-box/notification-box.component.ts b/src/app/notification-box/notification-box.component.ts new file mode 100644 index 0000000..693cc2b --- /dev/null +++ b/src/app/notification-box/notification-box.component.ts @@ -0,0 +1,41 @@ +import { animate, style, transition, trigger } from '@angular/animations'; +import { Component, OnInit } from '@angular/core'; +import { MatCard, MatCardContent } from '@angular/material/card'; +import { Notification, NotificationService } from '@core/notification/notification.service'; + +const NOTIFICATION_TTL = 3000; + +@Component({ + selector: 'app-notification-box', + imports: [MatCard, MatCardContent], + animations: [ + trigger('slideInOut', [ + transition(':enter', [ + style({ transform: 'translateX(100%)' }), + animate('200ms ease-in-out', style({ transform: 'translateX(0)' })), + ]), + transition(':leave', [animate('200ms ease-in-out', style({ transform: 'translateX(100%)' })),]) + ]), + ], + templateUrl: './notification-box.component.html', + styleUrl: './notification-box.component.scss' +}) +export class NotificationBoxComponent implements OnInit { + notifications: Map = new Map(); + + constructor(private notificationService: NotificationService) { + } + + ngOnInit(): void { + this.notificationService.subscribe('notification-box', this.onNotification.bind(this)); + } + + onNotification(notification: Notification): void { + const now = Date.now(); + this.notifications.set(now, notification); + + setTimeout(() => { + this.notifications.delete(now); + }, NOTIFICATION_TTL); + } +} diff --git a/src/app/views/dashboard/dashboard.component.html b/src/app/views/dashboard/dashboard.component.html index 9c5fce9..2e49c07 100644 --- a/src/app/views/dashboard/dashboard.component.html +++ b/src/app/views/dashboard/dashboard.component.html @@ -1 +1,4 @@

dashboard works!

+ + + diff --git a/src/app/views/dashboard/dashboard.component.ts b/src/app/views/dashboard/dashboard.component.ts index 96e3d35..9455e38 100644 --- a/src/app/views/dashboard/dashboard.component.ts +++ b/src/app/views/dashboard/dashboard.component.ts @@ -1,11 +1,20 @@ import { Component } from '@angular/core'; +import { MatButton } from '@angular/material/button'; + +import { NotificationService, NotificationType } from '../../core/notification/notification.service'; @Component({ selector: 'app-dashboard', - imports: [], + imports: [MatButton], templateUrl: './dashboard.component.html', styleUrl: './dashboard.component.scss' }) export class DashboardComponent { + constructor(private notifications: NotificationService) { } + testInfo() { + this.notifications.publish('Cake', NotificationType.Information); + } -} + testError() { + this.notifications.publish('Cake', NotificationType.Error); + }} diff --git a/src/styles.scss b/src/styles.scss index 6cdd52a..e040c88 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1,21 +1,21 @@ @use '@angular/material' as mat; -html { - color-scheme: light dark; - - @include mat.theme((color: mat.$azure-palette, - typography: Roboto, - density: 0)); - --mat-sys-primary-overlay: color-mix(in srgb, var(--mat-sys-primary) 10%, transparent); -} html, body { height: 100%; -} - -body { margin: 0; + background: var(--mat-sys-surface); color: var(--mat-sys-on-surface); + + --mat-sys-primary-overlay: color-mix(in srgb, var(--mat-sys-primary) 10%, transparent); + + color-scheme: light dark; + font-family: var(--mat-sys-label-medium-font); + + @include mat.theme((color: mat.$azure-palette, + typography: Roboto, + density: 0)); + }