Add Header Menu Brand Text Hidden Mode
This commit is contained in:
1 parent
be48e8ef89
commit
4605fd7ee4
3 files changed
+115
-7
No files matched your search
@@ -1,7 +1,9 @@
|
||||
<div class="menu-left">
|
||||
<a class="brand" [routerLink]="['/']">
|
||||
<img [src]="settings.darkMode() ? '/logo.svg' : '/logo_inverted.svg'" alt="logo" height="20" width="59.52" />
|
||||
<span><strong>ADOFAI Editor Forum</strong></span>
|
||||
<span #brandText [class.hidden]="!hasExtraSpace()">
|
||||
<strong>ADOFAI Editor Forum</strong>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<span class="menu-item dummy-link">{{ translation.menu.levels }}</span>
|
||||
@@ -10,7 +12,7 @@
|
||||
<span class="menu-item dummy-link">{{ translation.menu.about }}</span>
|
||||
</div>
|
||||
|
||||
<div class="menu-right">
|
||||
<div class="menu-right" #menuRight>
|
||||
<a class="menu-item nav-link" [routerLink]="['/known-issues']">{{ translation.menu.knownIssues }}</a>
|
||||
<span class="menu-item dummy-link"><strong>{{ translation.menu.login }}</strong></span>
|
||||
<span class="menu-item dummy-link">{{ translation.menu.signup }}</span>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
gap: 30px;
|
||||
gap: 0;
|
||||
|
||||
width: fit-content;
|
||||
min-width: 100%;
|
||||
@@ -112,8 +112,12 @@
|
||||
}
|
||||
|
||||
span {
|
||||
/*text-box-trim: trim-both;
|
||||
text-box-edge: cap alphabetic;*/
|
||||
display: inline-block;
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import {
|
||||
afterNextRender,
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ElementRef,
|
||||
inject,
|
||||
NgZone,
|
||||
OnDestroy,
|
||||
signal,
|
||||
viewChild,
|
||||
} from '@angular/core';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { PopupService } from '../../services/popup.service';
|
||||
import { SettingsPopup } from '../settings/settings';
|
||||
@@ -10,12 +20,104 @@ import { SettingsService } from '../../services/settings.service';
|
||||
imports: [RouterLink],
|
||||
templateUrl: './header-menu.html',
|
||||
styleUrl: './header-menu.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class HeaderMenu {
|
||||
export class HeaderMenu implements OnDestroy {
|
||||
private popupService = inject(PopupService);
|
||||
protected translation = inject(TranslationService).translation;
|
||||
protected settings = inject(SettingsService);
|
||||
|
||||
menuRight = viewChild<ElementRef<HTMLElement>>('menuRight');
|
||||
brandText = viewChild<ElementRef<HTMLElement>>('brandText');
|
||||
|
||||
hasExtraSpace = signal(true);
|
||||
|
||||
private menuResizeObserver: ResizeObserver | null = null;
|
||||
private textResizeObserver: ResizeObserver | null = null;
|
||||
|
||||
private childWidths = new Map<Element, number>();
|
||||
private cachedTextWidth = 0;
|
||||
private cachedRightMenuContentWidth = 0;
|
||||
|
||||
constructor(private ngZone: NgZone) {
|
||||
afterNextRender(() => {
|
||||
const rightElement = this.menuRight()?.nativeElement;
|
||||
const textElement = this.brandText()?.nativeElement;
|
||||
if (!rightElement || !textElement) return;
|
||||
|
||||
this.ngZone.runOutsideAngular(() => {
|
||||
// 1. Right Menu & Children Observer
|
||||
this.menuResizeObserver = new ResizeObserver((entries) => {
|
||||
let containerWidth = -1;
|
||||
let contentChanged = false;
|
||||
|
||||
for (const entry of entries) {
|
||||
if (entry.target === rightElement) {
|
||||
containerWidth = entry.borderBoxSize[0].inlineSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
const width = entry.borderBoxSize[0].inlineSize;
|
||||
if (this.childWidths.get(entry.target) !== width) {
|
||||
this.childWidths.set(entry.target, width);
|
||||
contentChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (contentChanged) this.updateCachedRightMenuContentWidth();
|
||||
if (containerWidth !== -1) this.resize(containerWidth);
|
||||
});
|
||||
|
||||
this.menuResizeObserver.observe(rightElement);
|
||||
Array.from(rightElement.children).forEach((child) => {
|
||||
this.childWidths.set(child, (child as HTMLElement).offsetWidth);
|
||||
this.menuResizeObserver?.observe(child);
|
||||
});
|
||||
|
||||
// 2. Brand Text Observer
|
||||
this.textResizeObserver = new ResizeObserver((entries) => {
|
||||
const width = entries[0].borderBoxSize[0].inlineSize;
|
||||
if (width > 0) this.cachedTextWidth = width;
|
||||
});
|
||||
|
||||
this.textResizeObserver.observe(textElement);
|
||||
|
||||
// Initial calculation
|
||||
this.cachedTextWidth = textElement.offsetWidth;
|
||||
this.updateCachedRightMenuContentWidth();
|
||||
this.resize(rightElement.offsetWidth);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.menuResizeObserver?.disconnect();
|
||||
this.textResizeObserver?.disconnect();
|
||||
this.childWidths.clear();
|
||||
}
|
||||
|
||||
private updateCachedRightMenuContentWidth() {
|
||||
const gap = 48;
|
||||
const widths = Array.from(this.childWidths.values());
|
||||
if (widths.length === 0) {
|
||||
this.cachedRightMenuContentWidth = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
this.cachedRightMenuContentWidth =
|
||||
widths.reduce((acc, w) => acc + w, 0) + (widths.length - 1) * gap;
|
||||
}
|
||||
|
||||
private resize(availableWidth: number) {
|
||||
const hadSpace = this.hasExtraSpace();
|
||||
|
||||
const buffer = 20 * 2 + 24; // Internal Padding + Logical Gap
|
||||
const threshold = hadSpace ? 0 : this.cachedTextWidth;
|
||||
const hasSpace = availableWidth > this.cachedRightMenuContentWidth + threshold + buffer;
|
||||
|
||||
if (hadSpace !== hasSpace) this.ngZone.run(() => this.hasExtraSpace.set(hasSpace));
|
||||
}
|
||||
|
||||
onSettingsClick() {
|
||||
this.popupService.open(SettingsPopup, { position: { top: '84px', right: '20px' } });
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user