import {LitElement, html, css, unsafeCSS} from 'lit'; import { customElement, property, queryAssignedElements } from 'lit/decorators.js'; import { breakpoints } from '../../breakpoints'; @customElement('iu-header-navbar') export class SiteHeaderNavbar extends LitElement { static override styles = css` @media ${unsafeCSS(breakpoints.xxl)} { :host{ display: block; } } :host a{ color: var(--theme-color-black); text-decoration: none; } :host iu-container{ display: flex; justify-content: space-between; align-items: center; height: 3.125rem; background: var(--theme-color-white); grid-template-columns: repeat(4, minmax(0, 1fr)); margin-bottom: 0; } @media ${unsafeCSS(breakpoints.xl)} { :host iu-container{ display: grid; height: 3.75rem; } } .logo{ font-weight: 700; grid-column: span 1; } .nav{ grid-column: 2 / 5; display: none; justify-content: space-between; align-items: center; } @media ${unsafeCSS(breakpoints.xl)} { .nav{ display: flex; } } .nav ul{ margin: 0; padding: 0; list-style-type: none; display: flex; } :host nav ul ::slotted(*:not(:last-child)){ margin-right: var(--iu-spacing-4); } .actions button{ width: 50px; height: 50px; background: transparent; border: 0; padding: 0; cursor: pointer; display: none; margin-right: calc((50px - 16px) / 2 * -1) } .actions button svg{ height: 16px; width: 16px; } .mobile-actions{ display: flex; align-items: center; transform: translateX(var(--iu-grid-gutter)); & a{ margin-right: var(--iu-spacing-0); } } @media ${unsafeCSS(breakpoints.xl)} { .mobile-actions{ display: none; } } .hamburger{ background: transparent; border: 0; cursor: pointer; width: 50px; height: 50px; padding: 0; svg{ width: 100%; height: 100%; } } `; @property({ type: Boolean }) mobileMenuOpen = false; @property({type: Boolean}) i18n = false; // Query all `iu-header-navbar-item` elements @queryAssignedElements({ slot: '' }) navbarItems!: HTMLElement[]; private isAnySubmenuOpen(): boolean { return !this.navbarItems.some((item: any) => item.isActive); } private updateActionsVisibility() { const actionsButton = this.shadowRoot?.querySelector('.actions button') as HTMLElement; if (actionsButton) { if (this.isAnySubmenuOpen()) { actionsButton.style.display = 'block'; // Show the button } else { actionsButton.style.display = 'none'; // Hide the button } } } private handleSubmenuToggle(event: CustomEvent) { const { source } = event.detail; this.navbarItems.forEach((item: any) => { // Close all other submenus if (item !== source) { item.isActive = false; item.submenuElements.forEach((submenu: HTMLElement) => submenu.classList.remove('is-active') ); } // Handle closing animation if same element toggled if (event.detail.closing && item === source) { item.submenuElements.forEach((submenu: HTMLElement) => { submenu.classList.add('is-closing') const container = submenu.container; const containerHeight = container.scrollHeight; container.animate( [ { height: `${containerHeight}px` }, { height: 0 } ], { duration: 250, delay: 250, easing: 'ease-out' } ).onfinish = () => { submenu.classList.remove('is-closing'), submenu.classList.remove('is-active') } }); } }); // Check if any submenu is open and toggle button visibility this.updateActionsVisibility(); } private closeAllSubmenu(){ const actionsButton = this.shadowRoot?.querySelector('.actions button') as HTMLElement; if (actionsButton) { actionsButton.style.display = 'none'; // Hide the button } document.body.classList.remove('is-locked'); this.navbarItems.forEach((item: any) => { // Close all submenus item.isActive = false; item.submenuElements.forEach((submenu: HTMLElement) => { // Animate open submenu if (submenu.classList.contains('is-active')){ const overlay = document.querySelector('iu-overlay') as Overlay; overlay.visible = false; submenu.classList.add('is-closing'); const container = submenu.container; const containerHeight = container.scrollHeight; container.animate( [ { height: `${containerHeight}px` }, { height: 0 } ], { duration: 250, delay: 250, easing: 'ease-out' } ).onfinish = () => { submenu.classList.remove('is-closing'), submenu.classList.remove('is-active') } } else { submenu.classList.remove('is-active') } } ); }); } // Mobile menu private toggleMobileMenu(){ // Show overlay const overlay = document.querySelector('iu-overlay') as Overlay; overlay.visible = !overlay.visible; // Change internal var this.mobileMenuOpen = !this.mobileMenuOpen; if (this.mobileMenuOpen) { // Get scrolled height document.body.dataset.scrollY = window.scrollY.toString(); // Lock body document.body.style.top = `-${window.scrollY}px`; document.body.style.position = 'fixed'; } else { // Remove and restore previous scroll position document.body.style.top = ''; document.body.style.position = ''; // Restore scroll position window.scrollTo(0, Number(document.body.dataset.scrollY || '0')); } // Dispatch event this.dispatchEvent(new CustomEvent('toggle-mobile-menu', { bubbles: true, composed: true })); } // Toggle mobile menu off is xl breakpoint private handleResize = () => { if (window.innerWidth > 1280 && this.mobileMenuOpen) { this.mobileMenuOpen = false; this.dispatchEvent(new CustomEvent('toggle-mobile-menu', { detail: false, bubbles: true, composed: true })); // Reset body document.body.style.top = ''; document.body.style.position = ''; // Ensure the overlay is also hidden const overlay = document.querySelector('iu-overlay') as Overlay; if (overlay) overlay.visible = false; this.requestUpdate(); } } override connectedCallback() { super.connectedCallback(); this.addEventListener('iu-header-toggle-submenu', this.handleSubmenuToggle as EventListener); // Update the button visibility on initial load this.updateActionsVisibility(); this.addEventListener('close-mobile-menu', () => { this.mobileMenuOpen = false; this.requestUpdate(); }); // Manage resizing window window.addEventListener('resize', this.handleResize); } override disconnectedCallback() { this.removeEventListener('iu-header-toggle-submenu', this.handleSubmenuToggle as EventListener); // Remove event listener when component is disconnected window.removeEventListener('resize', this.handleResize); super.disconnectedCallback(); } override render() { return html` <iu-container> <div class="logo"> <a href="#" class="logo">Università Iuav di Venezia</a> </div> <div class="nav"> <nav role="navigation"> <ul> <slot></slot> </ul> </nav> <div class="actions"> <button @click="${this.closeAllSubmenu}" role="button"><svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M18.838 1.161 1.16 18.84M18.838 18.839 1.16 1.16" stroke="#000" stroke-width="2"/></svg></button> </div> </div> <!-- Toggle mobile menu --> <div class="mobile-actions"> ${this.i18n ? html` <a href="">EN</a> ` : ``} <button class="hamburger" @click="${this.toggleMobileMenu}"> ${this.mobileMenuOpen ? html` <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><path fill-rule="evenodd" clip-rule="evenodd" d="M23.132 24.546 15 16.414 16.414 15l8.132 8.132L32.678 15l1.414 1.414-8.132 8.132 8.132 8.132-1.414 1.414-8.132-8.132-8.132 8.132L15 32.678l8.132-8.132Z" fill="currentColor"/></svg> ` : html` <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><path fill-rule="evenodd" clip-rule="evenodd" d="M13 24h25v2H13v-2Zm0-7h25v2H13v-2Zm0 14h25v2H13v-2Z" fill="currentColor"/></svg> `} </button> </div> </iu-container> `; } } declare global { interface HTMLElementTagNameMap { 'iu-header-navbar': SiteHeaderNavbar; } }