Newer
Older
import {Component, Element, Event, EventEmitter, h, Host, Listen, Prop} from '@stencil/core';
import {MenuType} from '../../../utils/utils';
// import {MenuItemVariants} from '../../utils/utils';
Markus Kalijärvi
committed
@Component({
tag: 'hy-menu-item',
styleUrl: 'menu-item.scss',
Markus Kalijärvi
committed
})
export class MenuItem {
@Element() el: HTMLElement;
@Event() addBreadcrumb: EventEmitter;
@Event() menuContainerActiveTrail: EventEmitter;
@Event() menuContainerToggled: EventEmitter;
@Event() routeClicked: EventEmitter;
Markus Kalijärvi
committed
@Prop() inActiveTrail: boolean = false;
@Prop() isActive: boolean = false;
@Prop() isActiveChild?: boolean = false;
Markus Kalijärvi
committed
@Prop() isHeading: boolean = false;
@Prop() isParent: boolean = false;
@Prop() isDemo: boolean = false;
Markus Kalijärvi
committed
@Prop() menuItemAlternative: boolean = false;
@Prop() menuLinkId: string = '';
@Prop() menuType: MenuType = MenuType.desktop;
@Prop() menuButtonSubmenuExpand?: string = '';
Markus Kalijärvi
committed
@Prop({mutable: true}) ariaExpanded: boolean = false;
@Prop({mutable: true, reflect: true}) depth: number = 0;
@Prop({mutable: true}) hasChildren: boolean = null;
@Prop({mutable: true}) label: string = '';
Markus Kalijärvi
committed
@Prop({mutable: true}) parentAsHeading: string = '';
@Prop({mutable: true, reflect: true}) parentExpanded: boolean = false;
@Prop({mutable: true}) url: string = '';
Markus Kalijärvi
committed
// Listener for submenu expand button. Listener will toggle menu level
// container and add its parent to breadcrumbs.
Markus Kalijärvi
committed
@Listen('handleClick', {capture: true}) handleClick() {
this.menuContainerToggled.emit({
triggerItem: this.menuLinkId,
triggerType: 'add',
});
Markus Kalijärvi
committed
const currentParent = this.el.parentNode;
this.addBreadcrumb.emit({
url: this.url,
label: currentParent.parentElement.getAttribute('label'),
Markus Kalijärvi
committed
});
}
componentWillLoad() {
Markus Kalijärvi
committed
// If is-active class is added by system, add it to menu component as well.
if (this.el.classList.contains('is-active')) {
this.isActive = true;
}
Markus Kalijärvi
committed
// Notify breadcrumbs if item is in active trail.
if (this.inActiveTrail && !this.isActive) {
const currentParent = this.el.parentNode;
this.addBreadcrumb.emit({
url: this.url,
label: currentParent.parentElement.getAttribute('label'),
Markus Kalijärvi
committed
});
if (this.menuType === MenuType.sidenav) {
this.el.children[0].setAttribute('class', 'is-open');
}
Markus Kalijärvi
committed
}
// If current menu item is active, trigger all parent menuLevelContainer
// elements in the same active-trail to open the menu.
Markus Kalijärvi
committed
if (this.isActive) {
const getParents = (elem) => {
let parents = [];
while (elem.parentNode && elem.parentNode.nodeName.toLowerCase() != 'hy-menu') {
Markus Kalijärvi
committed
elem = elem.parentNode;
parents.push(elem);
}
return parents;
};
const parents = getParents(this.el);
parents.forEach((element) => {
if (element.tagName.toLowerCase() === 'hy-menu-item') {
this.menuContainerActiveTrail.emit({
triggerItem: element.getAttribute('menu-link-id'),
});
Markus Kalijärvi
committed
}
});
// If side navigation menu item has is-active state, prepare the menu items
// for the last children.
if (this.menuType === MenuType.sidenav) {
parents.forEach((element) => {
if (element.tagName.toLowerCase() === 'hy-menu-item') {
element.classList.add('is-hidden--child');
}
});
// If current active menu item have children, set the child
// menu-level-container open.
if (this.el.children.length > 0) {
this.el.children[0].setAttribute('class', 'is-open');
}
// If current active menu item does not have any children, mock the
// current menu item and parent menu item to behave like there would
// be child menu items available.
else {
const parentMenuItem = this.el.parentElement.closest('hy-menu-item');
const parentMenuItemAnchor = parentMenuItem.shadowRoot.querySelector('a.hy-menu-item--sidenav');
parentMenuItem.classList.remove('is-hidden--child');
parentMenuItemAnchor.classList.add('is-active--heading');
const menuItemSiblings = Array.prototype.slice.call(this.el.closest('hy-menu-level-container').children);
menuItemSiblings.forEach((element) => {
if (element.tagName.toLowerCase() === 'hy-menu-item') {
element.classList.add('is-active--child');
}
});
Markus Kalijärvi
committed
}
}
componentWillRender() {
// Assign depth value to current menu item instance; 1st level, 2nd level, etc.
Markus Kalijärvi
committed
this.hasChildren = this.el.getElementsByTagName('hy-menu-level-container').length >= 1;
let parentMenuItem = this.el.closest('hy-menu-item');
let nextParentMenuItem;
Markus Kalijärvi
committed
this.depth = 0;
while (parentMenuItem) {
nextParentMenuItem = parentMenuItem.parentElement.closest('hy-menu-item');
if (nextParentMenuItem === parentMenuItem) {
Markus Kalijärvi
committed
break;
} else {
if (nextParentMenuItem !== null) {
this.parentAsHeading = nextParentMenuItem;
Markus Kalijärvi
committed
}
parentMenuItem = nextParentMenuItem;
Markus Kalijärvi
committed
this.depth = this.depth + 1;
}
}
}
Markus Kalijärvi
committed
render() {
case MenuType.desktop:
return (
<Host
class={{
'is-active': this.isActive,
'hy-menu-item': true,
'hy-menu-item--alternative': this.menuItemAlternative,
'hy-menu-item--desktop': true,
}}
>
<a
aria-current={this.isHeading.toString()}
href={this.url}
class={{
'is-active': this.isActive,
'in-active-trail': this.inActiveTrail,
'is-heading': this.isHeading,
'hy-menu-item--desktop': true,
{this.depth == 1 && (
<span class="hy-menu-item__heading__icon">
<hy-icon icon={'hy-icon-arrow-right'} size={40} />
</span>
)}
{this.depth == 2 && (
<span class="hy-menu-item__heading__icon">
<hy-icon icon={'hy-icon-caret-right'} size={12} />
<span class={'hy-menu-item__label'}>{this.label}</span>
</a>
{this.hasChildren && <slot />}
case MenuType.mobile:
return (
<Host
class={{
'is-active': this.isActive,
'hy-menu-item': true,
'hy-menu-item--alternative': this.menuItemAlternative,
'hy-menu-item--mobile': true,
}}
>
<a
aria-current={this.isHeading.toString()}
href={this.url}
class={{
'is-active': this.isActive,
'in-active-trail': this.inActiveTrail,
'is-heading': this.isHeading,
'hy-menu-item--mobile': true,
}}
>
<span class={'hy-menu-item__label'}>{this.label}</span>
</a>
{this.hasChildren && (
<button
aria-haspopup={'true'}
aria-label={`Open submenu for ${this.label}`}
onClick={() => this.handleClick()}
class={'hy-menu-item__button'}
>
<hy-icon icon={'hy-icon-caret-right'} size={14} />
Markus Kalijärvi
committed
{this.hasChildren && <slot />}
</Host>
);
case MenuType.sidenav:
let classAttributes = [
'hy-menu-item--sidenav',
this.isDemo ? 'is-demo' : '',
this.isActive ? 'is-active' : '',
this.isParent ? 'is-parent' : '',
this.isActiveChild ? 'is-active--child' : '',
this.inActiveTrail ? 'in-active-trail' : '',
this.depth != null ? 'hy-menu-item--level-' + this.depth : '',
this.isActive && this.hasChildren ? 'is-active--heading' : '',
];
let anchorClassAttributes = [...classAttributes, this.isHeading ? 'is-heading' : ''];
classAttributes = [...classAttributes, 'hy-menu-item'];
return this.isParent ? (
<Host class={classAttributes.join(' ')}>
<a href={this.url} class={anchorClassAttributes.join(' ')}>
<span class={'hy-menu-item__label__icon'}>
<hy-icon icon={'hy-icon-arrow-left'} fill={'currentColor'} size={18} />
<span class={'hy-menu-item__label'}>{this.label}</span>
</a>
</Host>
) : (
<Host class={classAttributes.join(' ')}>
<a aria-current={this.isHeading.toString()} href={this.url} class={anchorClassAttributes.join(' ')}>
<span class={'hy-menu-item__label'}>{this.label}</span>
{this.hasChildren && (
<span class={'hy-menu-item__label__icon'}>
<hy-icon icon={'hy-icon-caret-right'} fill={'currentColor'} size={12} />
</span>
)}
</a>
{this.hasChildren && <slot />}
</Host>
);
}