diff --git a/libs/shell/header/src/index.ts b/libs/shell/header/src/index.ts index 7278cb2cc..5dfa2dc65 100644 --- a/libs/shell/header/src/index.ts +++ b/libs/shell/header/src/index.ts @@ -1 +1 @@ -export * from './lib/shell-header/shell-header.component'; +export * from './lib/shell-header.component'; diff --git a/libs/shell/header/src/lib/components/font-size-selector/font-size-selector.component.css b/libs/shell/header/src/lib/components/font-size-selector/font-size-selector.component.css new file mode 100644 index 000000000..9a6e2bb7c --- /dev/null +++ b/libs/shell/header/src/lib/components/font-size-selector/font-size-selector.component.css @@ -0,0 +1,15 @@ +.selector { + @apply relative flex rounded-full bg-isa-neutral-300; +} + +.indicator { + @apply absolute top-0 size-12 rounded-full bg-isa-neutral-700 transition-transform duration-200 ease-out; +} + +.option { + @apply relative flex h-12 w-12 cursor-pointer items-center justify-center transition-colors duration-200; +} + +.option.selected { + @apply text-isa-neutral-300; +} diff --git a/libs/shell/header/src/lib/components/font-size-selector/font-size-selector.component.html b/libs/shell/header/src/lib/components/font-size-selector/font-size-selector.component.html new file mode 100644 index 000000000..de9bad7ea --- /dev/null +++ b/libs/shell/header/src/lib/components/font-size-selector/font-size-selector.component.html @@ -0,0 +1,33 @@ +
+
+ + @for (option of fontSizeOptions; track option.value) { + + + } +
diff --git a/libs/shell/header/src/lib/components/font-size-selector/font-size-selector.component.ts b/libs/shell/header/src/lib/components/font-size-selector/font-size-selector.component.ts new file mode 100644 index 000000000..73cf1f44a --- /dev/null +++ b/libs/shell/header/src/lib/components/font-size-selector/font-size-selector.component.ts @@ -0,0 +1,56 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + inject, +} from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { logger } from '@isa/core/logging'; +import { NgIcon, provideIcons } from '@ng-icons/core'; +import { isaNavigationFontsize } from '@isa/icons'; +import { FontSize, FontSizeService } from '@isa/shell/common'; + +interface FontSizeOption { + value: FontSize; + label: string; + iconSize: string; +} + +@Component({ + selector: 'shell-font-size-selector', + templateUrl: './font-size-selector.component.html', + styleUrl: './font-size-selector.component.css', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [FormsModule, NgIcon], + providers: [ + provideIcons({ + isaNavigationFontsize, + }), + ], +}) +export class ShellFontSizeSelectorComponent { + #logger = logger({ component: 'ShellFontSizeSelectorComponent' }); + + readonly fontSizeService = inject(FontSizeService); + + readonly fontSizeOptions: FontSizeOption[] = [ + { value: 'small', label: 'Kleine Schriftgröße', iconSize: '0.63rem' }, + { value: 'medium', label: 'Mittlere Schriftgröße', iconSize: '1rem' }, + { value: 'large', label: 'Große Schriftgröße', iconSize: '1.3rem' }, + ]; + + readonly #offsetMap: Record = { + small: 0, + medium: 3, + large: 6, + }; + + readonly indicatorOffset = computed( + () => this.#offsetMap[this.fontSizeService.get()], + ); + + onFontSizeChange(size: FontSize): void { + this.#logger.debug('Font size changed', () => ({ size })); + this.fontSizeService.set(size); + } +} diff --git a/libs/shell/header/src/lib/components/logout-button.component.ts b/libs/shell/header/src/lib/components/logout-button.component.ts new file mode 100644 index 000000000..b7171ae6d --- /dev/null +++ b/libs/shell/header/src/lib/components/logout-button.component.ts @@ -0,0 +1,31 @@ +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { logger } from '@isa/core/logging'; +import { InfoButtonComponent } from '@isa/ui/buttons'; +import { AuthService } from '@isa/core/auth'; +import { NgIcon, provideIcons } from '@ng-icons/core'; +import { isaNavigationLogout } from '@isa/icons'; + +@Component({ + selector: 'shell-logout-button', + template: ` + + NEU + `, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [InfoButtonComponent, NgIcon], + providers: [provideIcons({ isaNavigationLogout })], +}) +export class ShellLogoutButtonComponent { + #logger = logger({ component: 'ShellLogoutButtonComponent' }); + #authService = inject(AuthService); + + logout(): void { + this.#logger.info('User logging out'); + this.#authService.logout(); + } +} diff --git a/libs/shell/header/src/lib/components/navigation-toggle.component.ts b/libs/shell/header/src/lib/components/navigation-toggle.component.ts new file mode 100644 index 000000000..c460621e9 --- /dev/null +++ b/libs/shell/header/src/lib/components/navigation-toggle.component.ts @@ -0,0 +1,56 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + inject, +} from '@angular/core'; +import { logger } from '@isa/core/logging'; +import { isaActionClose, isaNavigationSidemenu } from '@isa/icons'; +import { NavigationService } from '@isa/shell/common'; +import { + IconButtonComponent, + IconButtonColor, + IconButtonSize, +} from '@isa/ui/buttons'; +import { provideIcons } from '@ng-icons/core'; + +@Component({ + selector: 'shell-navigation-toggle', + template: ``, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [IconButtonComponent], + providers: [provideIcons({ isaNavigationSidemenu, isaActionClose })], +}) +export class ShellNavigationToggleComponent { + #logger = logger({ component: 'ShellNavigationToggleComponent' }); + + readonly navigationService = inject(NavigationService); + + readonly IconButtonColor = IconButtonColor; + readonly IconButtonSize = IconButtonSize; + + iconName = computed(() => { + const open = this.navigationService.get(); + return open ? 'isaActionClose' : 'isaNavigationSidemenu'; + }); + + ariaLabel = computed(() => + this.navigationService.get() ? 'Menü schließen' : 'Menü öffnen', + ); + + toggle(): void { + this.navigationService.toggle(); + this.#logger.debug('Navigation toggled', () => ({ + isOpen: this.navigationService.get(), + })); + } +} diff --git a/libs/shell/header/src/lib/components/notifications-toggle/notifications-toggle.component.css b/libs/shell/header/src/lib/components/notifications-toggle/notifications-toggle.component.css new file mode 100644 index 000000000..7e9537025 --- /dev/null +++ b/libs/shell/header/src/lib/components/notifications-toggle/notifications-toggle.component.css @@ -0,0 +1,3 @@ +:host { + display: block; +} diff --git a/libs/shell/header/src/lib/components/notifications-toggle/notifications-toggle.component.html b/libs/shell/header/src/lib/components/notifications-toggle/notifications-toggle.component.html new file mode 100644 index 000000000..a48535c40 --- /dev/null +++ b/libs/shell/header/src/lib/components/notifications-toggle/notifications-toggle.component.html @@ -0,0 +1,36 @@ + + + + + diff --git a/libs/shell/header/src/lib/components/notifications-toggle/notifications-toggle.component.ts b/libs/shell/header/src/lib/components/notifications-toggle/notifications-toggle.component.ts new file mode 100644 index 000000000..ccda25104 --- /dev/null +++ b/libs/shell/header/src/lib/components/notifications-toggle/notifications-toggle.component.ts @@ -0,0 +1,79 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + inject, + signal, +} from '@angular/core'; +import { CdkConnectedOverlay, CdkOverlayOrigin, ConnectedPosition } from '@angular/cdk/overlay'; +import { logger } from '@isa/core/logging'; +import { isaNavigationMessage, isaNavigationMessageUnread } from '@isa/icons'; +import { provideIcons } from '@ng-icons/core'; +import { + IconButtonComponent, + IconButtonColor, + IconButtonSize, +} from '@isa/ui/buttons'; +import { NotificationsService } from '@isa/shell/common'; +import { ShellNotificationsComponent } from '@isa/shell/notifications'; + +@Component({ + selector: 'shell-notifications-toggle', + templateUrl: './notifications-toggle.component.html', + styleUrl: './notifications-toggle.component.css', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [IconButtonComponent, ShellNotificationsComponent, CdkOverlayOrigin, CdkConnectedOverlay], + providers: [ + provideIcons({ isaNavigationMessage, isaNavigationMessageUnread }), + ], +}) +export class ShellNotificationsToggleComponent { + #logger = logger({ component: 'ShellNotificationsToggleComponent' }); + + readonly IconButtonColor = IconButtonColor; + readonly IconButtonSize = IconButtonSize; + + readonly notificationsService = inject(NotificationsService); + + isOpen = signal(false); + + readonly positions: ConnectedPosition[] = [ + // Bottom right + { originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top' }, + // Bottom left + { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' }, + // Left top + { originX: 'start', originY: 'top', overlayX: 'end', overlayY: 'top' }, + // Left bottom + { originX: 'start', originY: 'bottom', overlayX: 'end', overlayY: 'bottom' }, + ]; + + toggle(): void { + this.isOpen.update((open) => !open); + this.#logger.debug('Notifications panel toggled', () => ({ + isOpen: this.isOpen(), + })); + } + + close(): void { + this.isOpen.set(false); + this.#logger.debug('Notifications panel closed'); + } + + hasNotifications = computed(() => this.notificationsService.get().length > 0); + + unreadNotifications = computed( + () => this.notificationsService.unreadCount() > 0, + ); + + icon = computed(() => + this.unreadNotifications() + ? 'isaNavigationMessageUnread' + : 'isaNavigationMessage', + ); + + ariaLabel = computed(() => { + if (!this.hasNotifications()) return 'Keine Benachrichtigungen'; + return this.isOpen() ? 'Benachrichtigungen schließen' : 'Benachrichtigungen öffnen'; + }); +} diff --git a/libs/shell/header/src/lib/shell-header/shell-header.component.css b/libs/shell/header/src/lib/shell-header.component.css similarity index 100% rename from libs/shell/header/src/lib/shell-header/shell-header.component.css rename to libs/shell/header/src/lib/shell-header.component.css diff --git a/libs/shell/header/src/lib/shell-header.component.html b/libs/shell/header/src/lib/shell-header.component.html new file mode 100644 index 000000000..28f352c5e --- /dev/null +++ b/libs/shell/header/src/lib/shell-header.component.html @@ -0,0 +1,14 @@ + diff --git a/libs/shell/header/src/lib/shell-header.component.ts b/libs/shell/header/src/lib/shell-header.component.ts new file mode 100644 index 000000000..5b601b4bb --- /dev/null +++ b/libs/shell/header/src/lib/shell-header.component.ts @@ -0,0 +1,22 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ShellNavigationToggleComponent } from './components/navigation-toggle.component'; +import { Breakpoint, breakpoint } from '@isa/ui/layout'; +import { ShellFontSizeSelectorComponent } from './components/font-size-selector/font-size-selector.component'; +import { ShellLogoutButtonComponent } from './components/logout-button.component'; +import { ShellNotificationsToggleComponent } from './components/notifications-toggle/notifications-toggle.component'; + +@Component({ + selector: 'shell-header', + templateUrl: './shell-header.component.html', + styleUrls: ['./shell-header.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + ShellNavigationToggleComponent, + ShellFontSizeSelectorComponent, + ShellLogoutButtonComponent, + ShellNotificationsToggleComponent, + ], +}) +export class ShellHeaderComponent { + readonly isTablet = breakpoint(Breakpoint.Tablet); +} diff --git a/libs/shell/header/src/lib/shell-header/shell-header.component.html b/libs/shell/header/src/lib/shell-header/shell-header.component.html deleted file mode 100644 index b1709d9f4..000000000 --- a/libs/shell/header/src/lib/shell-header/shell-header.component.html +++ /dev/null @@ -1 +0,0 @@ -

ShellHeader works!

diff --git a/libs/shell/header/src/lib/shell-header/shell-header.component.spec.ts b/libs/shell/header/src/lib/shell-header/shell-header.component.spec.ts deleted file mode 100644 index 9c67c3779..000000000 --- a/libs/shell/header/src/lib/shell-header/shell-header.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ShellHeaderComponent } from './shell-header.component'; - -describe('ShellHeaderComponent', () => { - let component: ShellHeaderComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ShellHeaderComponent], - }).compileComponents(); - - fixture = TestBed.createComponent(ShellHeaderComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/libs/shell/header/src/lib/shell-header/shell-header.component.ts b/libs/shell/header/src/lib/shell-header/shell-header.component.ts deleted file mode 100644 index b77b03d1e..000000000 --- a/libs/shell/header/src/lib/shell-header/shell-header.component.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Component } from '@angular/core'; -import { CommonModule } from '@angular/common'; - -@Component({ - selector: 'shell-shell-header', - imports: [CommonModule], - templateUrl: './shell-header.component.html', - styleUrl: './shell-header.component.css', -}) -export class ShellHeaderComponent {}