From b45697a1ff41bc53695795a900946fd08f044b86 Mon Sep 17 00:00:00 2001 From: Ekaterina Kondareva <ekaterina.kondareva@helsinki.fi> Date: Mon, 30 Nov 2020 10:56:01 +0200 Subject: [PATCH] Nxstage 1001 upper navigation advanced --- .../hy-desktop-menu-links.scss | 28 +- .../hy-desktop-menu-links.tsx | 315 ++++++++++++------ 2 files changed, 240 insertions(+), 103 deletions(-) diff --git a/src/components/site-header/hy-desktop-menu-links/hy-desktop-menu-links.scss b/src/components/site-header/hy-desktop-menu-links/hy-desktop-menu-links.scss index 6c4f9cae..ba2dc9e1 100644 --- a/src/components/site-header/hy-desktop-menu-links/hy-desktop-menu-links.scss +++ b/src/components/site-header/hy-desktop-menu-links/hy-desktop-menu-links.scss @@ -57,7 +57,7 @@ position: relative; .desktop-menu-link__heading__icon { - bottom: 14px; + bottom: 0; display: block; left: 0; position: absolute; @@ -67,7 +67,7 @@ justify-content: center; transform: rotateX(180deg); svg { - padding: 4px 0; + padding: 8px 0; } } } @@ -82,11 +82,11 @@ background-color: var(--grayscale-white); display: flex; flex-direction: row; - justify-content: center; opacity: 1; position: absolute; left: 0; - top: 104px; + padding-left: 300px; + padding-bottom: 28px; width: 100%; z-index: 510; } @@ -115,7 +115,13 @@ &__desktop-menu { display: flex; - position: relative; + //position: relative; + + &__menu-items { + //@todo check max-with in Specs + min-width: 450px; + max-width: 550px; + } // first level link inside panel &__first-level-menu-item { @@ -199,7 +205,7 @@ flex-direction: row; letter-spacing: -0.47px; padding-left: 24px; - padding-right: 24px; + padding-right: 48px; text-transform: none; } @@ -238,8 +244,14 @@ // Shortcuts .shortcuts-panel { + position: absolute; + left: 0; // override in js list-style: none; - margin-left: 48px; + min-width: 350px; + max-width: 400px; + //margin: 0 48px; + padding: 0 48px; + top: 0; &__title { @include font-size(18px, 22px); @@ -264,11 +276,13 @@ display: flex; flex-direction: row; font-family: var(--main-font-family); + justify-content: space-between; letter-spacing: -0.5px; padding: 19px 0; text-decoration: none; .icon { + padding-left: 24px; svg { padding: 4px; } diff --git a/src/components/site-header/hy-desktop-menu-links/hy-desktop-menu-links.tsx b/src/components/site-header/hy-desktop-menu-links/hy-desktop-menu-links.tsx index b4567b98..607d2e24 100644 --- a/src/components/site-header/hy-desktop-menu-links/hy-desktop-menu-links.tsx +++ b/src/components/site-header/hy-desktop-menu-links/hy-desktop-menu-links.tsx @@ -7,10 +7,13 @@ export interface ShortcutLinks { export interface DesktopLinks { label: string; + labelExtra: string; url: string; description: string; menuLinkId: string; isActive: string; + shortcutsTitle: string; + closeButtonTitle: string; items: Array<DesktopLinks>; shortcuts: Array<ShortcutLinks>; } @@ -32,8 +35,14 @@ export class HyDesktopMenuLinks { private _dataDesktopLinks: DesktopLinks[]; @State() firstLevelLinksList: Array<object> = []; @State() menuLinkItems: Array<object> = []; + @State() hasToolbar: boolean = false; + @State() isDesktopMenuOpen: boolean = false; - @Watch('dataDesktopLinks') dataDesktopLinksWatcher(data: DesktopLinks[] | string) { + private _submenuLeftMargin: number = 32; + private _headerBorderOffset: number = 8; + + @Watch('dataDesktopLinks') + dataDesktopLinksWatcher(data: DesktopLinks[] | string) { this._dataDesktopLinks = typeof data === 'string' ? JSON.parse(data) : data; } @@ -41,13 +50,33 @@ export class HyDesktopMenuLinks { this.dataDesktopLinksWatcher(this.dataDesktopLinks); } + showBackdropShadow(state = 'close', top = 0) { + let hyHeader = this.el.closest('.hy-site-header') as HTMLElement; + let hyBackdropDiv = hyHeader.children[0] as HTMLElement; + + if (hyBackdropDiv) { + if (state === 'open') { + hyBackdropDiv.classList.add('is-active'); + hyBackdropDiv.style.top = `${top}px`; + } + + if (state === 'close') { + hyBackdropDiv.classList.remove('is-active'); + hyBackdropDiv.style.top = '0'; + } + } + } + handleDesktopMenuClose() { + this.isDesktopMenuOpen = false; + this.showBackdropShadow(); + const menuItems = this.el.shadowRoot.querySelectorAll(`.desktop-menu-link`); const menuPanelItems = this.el.shadowRoot.querySelectorAll('.hy-desktop-menu-panel'); - const activeMenuItem = this.el.shadowRoot.querySelector(`.desktop-menu-link[aria-expanded="true"]`); + const activeMenuItem = this.el.shadowRoot.querySelector(`.desktop-menu-link[aria-expanded="true"]`) as HTMLElement; // Return focus to the button of the last desktop panel that was active. - if (activeMenuItem !== null) (activeMenuItem as HTMLElement).focus(); + if (activeMenuItem !== null) activeMenuItem.focus(); // Reset elements by removing the active classes. menuItems.forEach((item) => { @@ -61,10 +90,11 @@ export class HyDesktopMenuLinks { } handleDesktopMenuToggle(id) { + this.isDesktopMenuOpen = true; const menuItems = this.el.shadowRoot.querySelectorAll(`.desktop-menu-link`); const menuPanelItems = this.el.shadowRoot.querySelectorAll('.hy-desktop-menu-panel'); // all panels - const activeMenuItem = this.el.shadowRoot.querySelector(`.desktop-menu-link[link-id="${id}"]`); - const activeMenuItemSibling = activeMenuItem.nextElementSibling; // current panel + const activeMenuItem = this.el.shadowRoot.querySelector(`.desktop-menu-link[link-id="${id}"]`) as HTMLElement; + const activeMenuItemSibling = activeMenuItem.nextElementSibling as HTMLElement; // current panel // Reset elements by removing the active classes. menuItems.forEach((item) => { @@ -80,109 +110,202 @@ export class HyDesktopMenuLinks { activeMenuItem.classList.add('desktop-menu-link--is-active'); activeMenuItem.setAttribute('aria-expanded', 'true'); activeMenuItemSibling.classList.add('hy-desktop-menu-panel--is-active'); + if (this.hasToolbar) { + activeMenuItemSibling.classList.add('hy-desktop-menu-panel--is-active--has-toolbar'); + } activeMenuItemSibling.setAttribute('aria-hidden', 'false'); + + // Add panels top value automatically with the correct header height + const headerHeight = `${ + this.el.parentElement.offsetTop + this.el.parentElement.offsetHeight + this._headerBorderOffset + }px`; + activeMenuItemSibling.style.top = headerHeight; + + // Add shadow backdrop + let rect = activeMenuItemSibling.getBoundingClientRect(); + this.showBackdropShadow('open', rect.bottom); + + // Position submenu block under the activated top menu item. + let activeButtonRect = activeMenuItem.getBoundingClientRect(); + const menuPanelLeftPosition = activeButtonRect.left - this._submenuLeftMargin; + activeMenuItemSibling.style.paddingLeft = `${menuPanelLeftPosition}px`; + + // Position shortcuts block. + let shortcutsDiv = activeMenuItemSibling.querySelectorAll('ul.shortcuts-panel')[0] as HTMLElement; // shortcuts block + if (shortcutsDiv) { + let subMenuDiv = activeMenuItemSibling.querySelectorAll( + '.hy-desktop-menu-panel__desktop-menu__menu-items' + )[0] as HTMLElement; // 2nd level menu block + + let spaceLeftAfterSubmenu = subMenuDiv.getBoundingClientRect().right + shortcutsDiv.offsetWidth; + if (spaceLeftAfterSubmenu >= document.body.scrollWidth) { + // Shortcuts should be placed to the left. + let shortcutsLeftPosition = subMenuDiv.getBoundingClientRect().left - shortcutsDiv.offsetWidth; + shortcutsDiv.style.left = shortcutsLeftPosition.toString().concat('px'); + } else { + // Shortcuts should be placed to the right. + let shortcutsLeftPosition = subMenuDiv.getBoundingClientRect().right; + shortcutsDiv.style.left = shortcutsLeftPosition.toString().concat('px'); + } + } + } + + handleDesktopMenuClick(id) { + const activeMenuItem = this.el.shadowRoot.querySelector(`.desktop-menu-link[link-id="${id}"]`); + const activeMenuItemSibling = activeMenuItem.nextElementSibling as HTMLElement; // current panel + + if (!this.isDesktopMenuOpen) { + // Add active classes to the currently active item and its sibling element. + this.isDesktopMenuOpen = true; + activeMenuItem.classList.add('desktop-menu-link--is-active'); + activeMenuItem.setAttribute('aria-expanded', 'true'); + activeMenuItemSibling.classList.add('hy-desktop-menu-panel--is-active'); + + if (this.hasToolbar) { + activeMenuItemSibling.classList.add('hy-desktop-menu-panel--is-active--has-toolbar'); + } + activeMenuItemSibling.setAttribute('aria-hidden', 'false'); + + let rect = activeMenuItemSibling.getBoundingClientRect(); + this.showBackdropShadow('open', rect.bottom); + } else { + // Remove active classes to the currently active item and its sibling element. + this.isDesktopMenuOpen = false; + activeMenuItem.classList.remove('desktop-menu-link--is-active'); + activeMenuItem.setAttribute('aria-expanded', 'false'); + activeMenuItemSibling.classList.remove('hy-desktop-menu-panel--is-active'); + if (this.hasToolbar) { + activeMenuItemSibling.classList.remove('hy-desktop-menu-panel--is-active--has-toolbar'); + } + + activeMenuItemSibling.setAttribute('aria-hidden', 'true'); + + this.showBackdropShadow(); + } } componentDidLoad() { + let hyToolbar = document.querySelectorAll('#toolbar-administration')[0]; + if (hyToolbar) { + this.hasToolbar = true; + } + const links = this._dataDesktopLinks as Array<DesktopLinks>; let menuLinkItems = []; - links.map(({menuLinkId: id, shortcuts, items, url, description, label, isActive}) => { - let classAttributes = ['desktop-menu-link', isActive === 'true' ? 'desktop-menu-link--is-active-trail' : ''].join( - ' ' - ); - - menuLinkItems.push( - <li> - <button - type="button" - class={classAttributes} - link-id={id} - onMouseOver={() => this.handleDesktopMenuToggle(id)} - onFocus={() => this.handleDesktopMenuToggle(id)} - aria-expanded="false" - > - {label} - <span class="desktop-menu-link__heading__icon"> - <hy-icon icon={'hy-icon-caret-down'} size={16} /> - </span> - </button> - <div class="hy-desktop-menu-panel" onMouseLeave={() => this.handleDesktopMenuClose()} aria-hidden="true"> - <div class="hy-desktop-menu-panel__desktop-menu"> - <div class="hy-desktop-menu-panel__desktop-menu__menu-items"> - <a - aria-current={label} - href={url} - class="hy-desktop-menu-panel__desktop-menu__first-level-menu-item" - menu-link-id={id} - > - <span class="heading-icon"> - <hy-icon icon={'hy-icon-arrow-right'} size={40} /> - </span> - <span class="label">{label}</span> - {description && <span class="description">{description}</span>} - </a> - <ul class={'hy-desktop-menu-panel__desktop-menu__second-level-menu'} menu-link-id={id}> - {items.map(({label, url}) => ( - <li> - <a href={url}> - <span class="heading-icon"> - <hy-icon icon={'hy-icon-caret-right'} size={12} /> - </span> - <span class="label">{label}</span> - </a> - </li> - ))} - </ul> - </div> - {shortcuts.length > 0 && ( - <ul class="shortcuts-panel"> - <h2 class="shortcuts-panel__title">{'Shortcuts'}</h2> - {shortcuts.map(({shortcut_title, shortcut_url, shortcut_is_external, shortcut_aria_label}, index) => { - let target = shortcut_is_external ? '_blank' : '_self'; - - let shortcutClass = [ - 'shortcuts-panel__shortcut-item', - index == 0 ? 'shortcuts-panel__shortcut-item__first' : '', - ].join(' '); - - return ( - <li class={shortcutClass}> - <a - aria-current={shortcut_aria_label} - href={shortcut_url} - class="shortcut-item__link" - target={target} - aria-label={shortcut_aria_label} - > - <span class="label">{shortcut_title}</span> - <span class="icon"> - <hy-icon icon={'hy-icon-arrow-right'} size={24} /> - </span> - </a> - </li> - ); - })} - </ul> - )} - </div> + links.map( + ({ + menuLinkId: id, + shortcuts, + items, + url, + description, + label, + labelExtra, + isActive, + shortcutsTitle, + closeButtonTitle, + }) => { + let classAttributes = [ + 'desktop-menu-link', + isActive === 'true' ? 'desktop-menu-link--is-active-trail' : '', + ].join(' '); + + menuLinkItems.push( + <li> <button - onClick={() => this.handleDesktopMenuClose()} - class={{ - 'hy-desktop-menu-panel__panel-toggle': true, - }} - aria-label="Close menu" + type="button" + class={classAttributes} + link-id={id} + onClick={() => this.handleDesktopMenuClick(id)} + onMouseOver={() => this.handleDesktopMenuToggle(id)} + onFocus={() => this.handleDesktopMenuToggle(id)} + aria-expanded="false" > - <span class="hy-desktop-menu-panel__panel-toggle__label"> - <span class="hy-desktop-menu-panel__panel-toggle__label__title">CLOSE</span> - <hy-icon icon={'hy-icon-remove'} size={20} fill={ColorVariant.black} /> + {label} + <span class="desktop-menu-link__heading__icon"> + <hy-icon icon={'hy-icon-caret-down'} size={32} /> </span> </button> - </div> - </li> - ); - }); + <div class="hy-desktop-menu-panel" onMouseLeave={() => this.handleDesktopMenuClose()} aria-hidden="true"> + <div class="hy-desktop-menu-panel__desktop-menu"> + <div class="hy-desktop-menu-panel__desktop-menu__menu-items"> + <a + aria-current={label} + href={url} + class="hy-desktop-menu-panel__desktop-menu__first-level-menu-item" + menu-link-id={id} + > + <span class="heading-icon"> + <hy-icon icon={'hy-icon-arrow-right'} size={40} /> + </span> + {labelExtra ? <span class="label">{labelExtra}</span> : <span class="label">{label}</span>} + {description && <span class="description">{description}</span>} + </a> + <ul class={'hy-desktop-menu-panel__desktop-menu__second-level-menu'} menu-link-id={id}> + {items.map(({label, url}) => ( + <li> + <a href={url}> + <span class="heading-icon"> + <hy-icon icon={'hy-icon-caret-right'} size={12} /> + </span> + <span class="label">{label}</span> + </a> + </li> + ))} + </ul> + </div> + {shortcuts.length > 0 && ( + <ul class="shortcuts-panel"> + <h2 class="shortcuts-panel__title">{shortcutsTitle}</h2> + {shortcuts.map( + ({shortcut_title, shortcut_url, shortcut_is_external, shortcut_aria_label}, index) => { + let target = shortcut_is_external ? '_blank' : '_self'; + + let shortcutClass = [ + 'shortcuts-panel__shortcut-item', + index == 0 ? 'shortcuts-panel__shortcut-item__first' : '', + ].join(' '); + + return ( + <li class={shortcutClass}> + <a + aria-current={shortcut_aria_label} + href={shortcut_url} + class="shortcut-item__link" + target={target} + aria-label={shortcut_aria_label} + > + <span class="label">{shortcut_title}</span> + <span class="icon"> + <hy-icon icon={'hy-icon-arrow-right'} size={24} /> + </span> + </a> + </li> + ); + } + )} + </ul> + )} + </div> + <button + onClick={() => this.handleDesktopMenuClose()} + class={{ + 'hy-desktop-menu-panel__panel-toggle': true, + }} + aria-label="Close menu" + > + <span class="hy-desktop-menu-panel__panel-toggle__label"> + <span class="hy-desktop-menu-panel__panel-toggle__label__title">{closeButtonTitle}</span> + <hy-icon icon={'hy-icon-remove'} size={20} fill={ColorVariant.black} /> + </span> + </button> + </div> + </li> + ); + } + ); this.menuLinkItems = menuLinkItems; } -- GitLab