2026-04-28 08:43:57 -04:00
|
|
|
|
import { ChangeDetectionStrategy, Component, signal, HostListener, OnDestroy, OnInit } from '@angular/core';
|
2026-04-26 13:32:59 +00:00
|
|
|
|
import { RouterLink, RouterLinkActive } from '@angular/router';
|
|
|
|
|
|
import { MatIconModule } from '@angular/material/icon';
|
|
|
|
|
|
import { MatButtonModule } from '@angular/material/button';
|
|
|
|
|
|
import { MatChipsModule } from '@angular/material/chips';
|
|
|
|
|
|
import { MatBadgeModule } from '@angular/material/badge';
|
|
|
|
|
|
import { NAV_DESTINATIONS } from '../../models/nav.model';
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Adaptive Navigation Component — switches between desktop sidebar
|
2026-04-28 08:43:57 -04:00
|
|
|
|
* and mobile header layouts using CSS media queries + JS breakpoint sync.
|
2026-04-26 13:32:59 +00:00
|
|
|
|
*
|
2026-04-28 08:43:57 -04:00
|
|
|
|
* Per CUB-27 spec breakpoints:
|
|
|
|
|
|
* Compact (0–599px): Mobile header + hamburger + bottom nav
|
|
|
|
|
|
* Medium (600–1023px): Collapsed sidebar (icon-only)
|
|
|
|
|
|
* Expanded (≥1024px): Expandable sidebar (hover/click)
|
2026-04-26 13:32:59 +00:00
|
|
|
|
*
|
2026-04-28 08:43:57 -04:00
|
|
|
|
* The LIVE status indicator is visible in all layouts.
|
2026-04-26 13:32:59 +00:00
|
|
|
|
*/
|
|
|
|
|
|
@Component({
|
|
|
|
|
|
selector: 'app-adaptive-navigation',
|
|
|
|
|
|
standalone: true,
|
|
|
|
|
|
imports: [
|
|
|
|
|
|
RouterLink,
|
|
|
|
|
|
RouterLinkActive,
|
|
|
|
|
|
MatIconModule,
|
|
|
|
|
|
MatButtonModule,
|
|
|
|
|
|
MatChipsModule,
|
|
|
|
|
|
MatBadgeModule,
|
|
|
|
|
|
],
|
|
|
|
|
|
templateUrl: './adaptive-navigation.component.html',
|
|
|
|
|
|
styleUrl: './adaptive-navigation.component.scss',
|
|
|
|
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
|
|
|
|
})
|
2026-04-28 08:43:57 -04:00
|
|
|
|
export class AdaptiveNavigationComponent implements OnInit, OnDestroy {
|
2026-04-26 13:32:59 +00:00
|
|
|
|
/** Navigation destinations shared with other nav components */
|
|
|
|
|
|
protected readonly destinations = NAV_DESTINATIONS;
|
|
|
|
|
|
|
|
|
|
|
|
/** Whether the mobile drawer is open */
|
|
|
|
|
|
protected readonly mobileMenuOpen = signal(false);
|
|
|
|
|
|
|
|
|
|
|
|
/** Live connection status */
|
|
|
|
|
|
protected readonly isConnected = signal(true);
|
|
|
|
|
|
|
2026-04-28 08:43:57 -04:00
|
|
|
|
/** Responsive breakpoint state */
|
|
|
|
|
|
protected readonly isMedium = signal(false);
|
|
|
|
|
|
protected readonly isExpanded = signal(false);
|
|
|
|
|
|
|
|
|
|
|
|
private readonly COMPACT_MAX = 599;
|
|
|
|
|
|
private readonly MEDIUM_MAX = 1023;
|
|
|
|
|
|
|
|
|
|
|
|
ngOnInit(): void {
|
|
|
|
|
|
this.updateBreakpoint();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@HostListener('window:resize')
|
|
|
|
|
|
onResize(): void {
|
|
|
|
|
|
this.updateBreakpoint();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-26 13:32:59 +00:00
|
|
|
|
/** Toggle mobile menu */
|
|
|
|
|
|
toggleMobileMenu(): void {
|
|
|
|
|
|
this.mobileMenuOpen.update((v) => !v);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Close mobile menu (e.g. on nav) */
|
|
|
|
|
|
closeMobileMenu(): void {
|
|
|
|
|
|
this.mobileMenuOpen.set(false);
|
|
|
|
|
|
}
|
2026-04-28 08:43:57 -04:00
|
|
|
|
|
|
|
|
|
|
private updateBreakpoint(): void {
|
|
|
|
|
|
const w = window.innerWidth;
|
|
|
|
|
|
this.isMedium.set(w >= this.COMPACT_MAX + 1 && w <= this.MEDIUM_MAX);
|
|
|
|
|
|
this.isExpanded.set(w > this.MEDIUM_MAX);
|
|
|
|
|
|
// Close mobile menu when switching to desktop
|
|
|
|
|
|
if (w > this.COMPACT_MAX) {
|
|
|
|
|
|
this.mobileMenuOpen.set(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ngOnDestroy(): void {
|
|
|
|
|
|
// HostListener auto-unsubscribes
|
|
|
|
|
|
}
|
2026-04-26 13:32:59 +00:00
|
|
|
|
}
|