Skip to content

Commit

Permalink
feat(intranet-header): implement condensed intranet-header
Browse files Browse the repository at this point in the history
  • Loading branch information
oliverschuerch committed Dec 2, 2024
1 parent 255cd47 commit 54eb1be
Show file tree
Hide file tree
Showing 7 changed files with 809 additions and 2,461 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,9 @@
</li>
</sp-intranet-header>
</section>

<iframe
src="http://localhost:9320/samples-navigation"
frameborder="0"
style="position: fixed; top: 300px; left: 0; width: 100%; height: 500px"
></iframe>
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ <h1 class="m-0 font-weight-bold">{{ siteTitle }}</h1>
</div>

<nav
*ngIf="condenseHeader && hasNavbar"
*ngIf="hasNavbar && condenseHeader && !isMobile"
aria-label="navigation"
class="justify-content-start top-navigation condense m-0 d-md-flex"
[ngClass]="{ 'd-none': !openedMenu }"
Expand Down Expand Up @@ -104,7 +104,7 @@ <h1 class="m-0 font-weight-bold">{{ siteTitle }}</h1>
</div>
</div>
<button
*ngIf="hasNavbar === true || condenseHeader"
*ngIf="hasNavbar"
(click)="toggleMenu()"
id="nav-toggler"
class="btn btn-lg btn-tertiary px-16 border-left rounded-0 flex-shrink-0 align-self-stretch d-md-none"
Expand All @@ -114,7 +114,7 @@ <h1 class="m-0 font-weight-bold">{{ siteTitle }}</h1>
</div>
</div>
<nav
*ngIf="hasNavbar === true && !condenseHeader"
*ngIf="hasNavbar && (!condenseHeader || isMobile)"
class="row justify-content-center top-navigation m-0 d-md-flex"
aria-label="navigation"
[ngClass]="{ 'd-none': !openedMenu }"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export class SwissPostIntranetHeaderComponent implements OnInit, OnChanges, Afte
@ViewChild('optionDropdown', { read: ElementRef })
optionDropdownElement!: ElementRef<HTMLElement>;

isMobile: boolean | null = null;
appLangs!: string[];
avatarUrl = this.createSafeAvatarUrl();
openedMenu = false;
Expand Down Expand Up @@ -77,7 +78,7 @@ export class SwissPostIntranetHeaderComponent implements OnInit, OnChanges, Afte
};

private windowResize$ = new Subject();
private moreElement!: HTMLElement;
private moreElement!: HTMLElement | null;
private navElement!: HTMLElement;
private navItems!: Array<HTMLElement>;
private logoElement!: HTMLElement;
Expand Down Expand Up @@ -106,7 +107,8 @@ export class SwissPostIntranetHeaderComponent implements OnInit, OnChanges, Afte
// use rxjs instead of HostListener to enable usage of debounce operator (see https://stackoverflow.com/a/44055389)
// => drastically improves performance of component!
this.windowResize$.subscribe(() => {
this.navigationResize();
this.computeIsMobile();
setTimeout(this.navigationResize.bind(this));
});

this.zone.runOutsideAngular(() => {
Expand Down Expand Up @@ -140,37 +142,13 @@ export class SwissPostIntranetHeaderComponent implements OnInit, OnChanges, Afte
}

ngAfterViewInit(): void {
this.computeIsMobile();

// this check is needed, because it would add another element when a link from within "#overflow" is clicked
if (this.dom.nativeElement.querySelector('#more') == null) {
const extensionElement = `<li tabindex="0" class="nav-item${
this.openedMenuOverflow ? '' : ' hidden'
}" id="more">
<span class="nav-link col-auto py-16 px-24"></span>
</li>`;
this.logoElement = this.dom.nativeElement.querySelector('#logo');
this.titleElement = this.dom.nativeElement.querySelector('#title');
this.optionHeaderContentElement =
this.dom.nativeElement.querySelector('#optionHeaderContent');
this.profileMenuElement = this.dom.nativeElement.querySelector('#profileMenu');
this.intranetSearchElement = this.dom.nativeElement.querySelector('#intranetSearch');
this.navElement = this.dom.nativeElement.querySelector('#nav');
if (this.navElement == null) {
return;
}
this.navItems = Array.from(this.navElement.querySelectorAll('.nav-item'));
this.navElement.insertAdjacentHTML('beforeend', extensionElement);
this.moreElement = this.dom.nativeElement.querySelector('#more');
const toggleElement = this.moreElement.getElementsByTagName('span')[0];
toggleElement.addEventListener('click', () => this.toggleMenuOverflow());
this.moreElement.addEventListener('keydown', (e: KeyboardEvent) =>
this.handleOverflowKeyEvent(e),
);
this.updateMoreElementText();
this.navigationResize();

if (!MutationObserver) {
return;
}
if (!MutationObserver) return;

this.navChanges = new MutationObserver(m => this.navMutationCallback(m));
this.navChanges.observe(this.navElement, {
Expand Down Expand Up @@ -249,11 +227,10 @@ export class SwissPostIntranetHeaderComponent implements OnInit, OnChanges, Afte
}

this.openedMenuOverflow = doOpenMenu;

if (this.openedMenuOverflow) {
this.moreElement.classList.remove('hidden');
this.overflowItems.forEach((el: HTMLElement) => (el.style.display = ''));
} else {
this.moreElement.classList.add('hidden');
this.overflowItems.forEach((el: HTMLElement) => (el.style.display = 'none'));
}
}
Expand All @@ -268,12 +245,47 @@ export class SwissPostIntranetHeaderComponent implements OnInit, OnChanges, Afte
}
}

public computeIsMobile() {
this.isMobile = this.dom.nativeElement.querySelector('#nav-toggler')?.offsetParent !== null;
}

public setUpRefs() {
this.logoElement = this.dom.nativeElement.querySelector('#logo');
this.titleElement = this.dom.nativeElement.querySelector('#title');
this.optionHeaderContentElement = this.dom.nativeElement.querySelector('#optionHeaderContent');
this.profileMenuElement = this.dom.nativeElement.querySelector('#profileMenu');
this.intranetSearchElement = this.dom.nativeElement.querySelector('#intranetSearch');
this.navElement = this.dom.nativeElement.querySelector('#nav');

if (this.navElement == null) return;

this.navItems = Array.from(this.navElement.querySelectorAll('.nav-item:not(#more)'));

this.moreElement = this.navElement.querySelector('#more');

if (!this.moreElement) {
this.navElement.insertAdjacentHTML(
'beforeend',
`<li tabindex="0" class="nav-item" id="more">
<span class="nav-link col-auto py-16 px-24"></span>
</li>`,
);
this.moreElement = this.navElement.querySelector('#more');

if (this.moreElement) {
this.moreElement.addEventListener('click', () => this.toggleMenuOverflow());
this.moreElement.addEventListener('keydown', this.handleOverflowKeyEvent);
this.updateMoreElementText();
}
}
}

public navigationResize() {
this.setUpRefs();

const navItems: Array<HTMLElement> = [];

if (this.moreElement == null) {
return;
}
if (this.moreElement == null) return;

// Hide overflow items
this.toggleMenuOverflow(false);
Expand All @@ -284,40 +296,37 @@ export class SwissPostIntranetHeaderComponent implements OnInit, OnChanges, Afte
el.style.transform = '';
el.style.width = '';
el.style.display = '';

navItems[index] = el;
});

// Get the total width of the nav items
const listMargin = 40;
const navItemWidth = navItems.reduce((acc, el) => acc + el.scrollWidth, listMargin);
if (this.isMobile) return;

// Get available width for the navigation bar
const availableNavWidth = this.getAvailableNavWidth();

// Get the total width of the nav items
const listMargin = 40;
const navItemWidth = navItems.reduce((acc, el) => acc + el.scrollWidth, 0);
const showMoreElement = navItemWidth > availableNavWidth;

// Display the more element if necessary, hide it otherwise
this.moreElement.style.display = navItemWidth > availableNavWidth ? '' : 'none';
this.moreElement.style.display = showMoreElement ? '' : 'none';

// If the navbar is too wide, turn the nav items into overflow items one by one
if (navItemWidth > availableNavWidth) {
let navWidth = navItemWidth + this.moreElement.scrollWidth;
if (showMoreElement) {
let navWidth = navItemWidth + this.moreElement.scrollWidth + listMargin;
let lastNavItem: HTMLElement;
let greaterWidth = 0;
let maxWidth = 0;

while (navWidth > availableNavWidth) {
lastNavItem = navItems[navItems.length - 1];
if (lastNavItem === undefined) break;

if (lastNavItem === undefined) {
break;
}

navWidth -= lastNavItem.scrollWidth;
lastNavItem.classList.add('nav-overflow');
const lastNavItemWidth = lastNavItem.getBoundingClientRect().width;

if (lastNavItemWidth > greaterWidth) {
greaterWidth = Math.ceil(lastNavItemWidth);
}
maxWidth = Math.max(maxWidth, Math.ceil(lastNavItem.scrollWidth));

navWidth -= lastNavItemWidth;
navItems.pop();
}

Expand All @@ -327,7 +336,7 @@ export class SwissPostIntranetHeaderComponent implements OnInit, OnChanges, Afte
// Adjust the overflow items
this.overflowItems.forEach((el: HTMLElement, index) => {
el.style.transform = `translateY(${index * 100}%)`;
el.style.width = `${greaterWidth}px`;
el.style.width = `${maxWidth}px`;
el.style.display = 'none';
});
}
Expand All @@ -350,16 +359,11 @@ export class SwissPostIntranetHeaderComponent implements OnInit, OnChanges, Afte
return document.documentElement.scrollWidth;
}

const elements = [
this.logoElement,
this.titleElement,
this.optionHeaderContentElement,
this.profileMenuElement,
this.intranetSearchElement,
];
const totalWidth = elements.reduce((sum, element) => sum + (element?.scrollWidth || 0), 0);
this.navElement.setAttribute('style', 'display: none !important');
const availableNavWidth = this.navElement.closest('nav.top-navigation')?.scrollWidth ?? 0;
this.navElement.removeAttribute('style');

return document.documentElement.scrollWidth - totalWidth;
return availableNavWidth;
}

private createSafeAvatarUrl(): SafeUrl {
Expand All @@ -372,15 +376,9 @@ export class SwissPostIntranetHeaderComponent implements OnInit, OnChanges, Afte
const languageField = this.dom.nativeElement.querySelector('#more > span');

// when no navigation bar is displayed theres no 'more' element
if (!languageField) {
return;
}

languageField.innerText = this.getTextForMoreElement();
}
if (!languageField) return;

private getTextForMoreElement() {
return this.localization['moreLabel'][this.lang.toLowerCase()];
languageField.innerText = this.localization['moreLabel'][this.lang.toLowerCase()];
}

// Close dropdown on link clicks https://github.com/swisspost/design-system/issues/1300
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,6 @@
}
}

@include media-breakpoint-up(md) {
.navbar-expand-md .navbar-nav {
flex-wrap: wrap;
}
}

.intranet-header .title {
overflow: hidden;

Expand Down
4 changes: 4 additions & 0 deletions packages/styles/src/components/intranet-header/_settings.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
cursor: pointer;
}

.dropdown-toggle {
padding-inline: 1rem !important;
}

img.profile-image {
width: intranet-header.$profile-image-size;
height: intranet-header.$profile-image-size;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
}

.nav-item > .nav-link {
padding-block: 1rem !important;
border-top: 4px solid transparent; // Border-Width according to PostWeb CSS
border-bottom: 4px solid transparent; // Border-Width according to PostWeb CSS
background-color: transparent;
Expand Down Expand Up @@ -70,6 +71,36 @@
text-decoration: none;
}
}

// condensed styles
.intranet-header & {
flex: 0 1 100%;
border-top: 0 none;

.navbar-collapse {
width: 100%;

.navbar-nav {
flex-wrap: nowrap;
}
}

.nav-item {
> .nav-link {
padding-block: calc(1rem - 3px) !important;
}
}

.nav-overflow {
white-space: nowrap;
}
}
}

@include media-breakpoint-down(md) {
#more {
display: none;
}
}

@include media-breakpoint-down(rg) {
Expand Down
Loading

0 comments on commit 54eb1be

Please sign in to comment.