Header - advanced

Verze:

09. 09. 2022

Zodpovědná osoba:

Dominik Šlechta

Header and modules

Header

{* Header *}

<header class="header header--top js--header js--fixed-header">
    <div class="header__content --content --flex">

        {* Header | Top *}


        
        <div class="header__top --flex-centre-y">

                {* Header | Logo *}

            
                <div class="header__logo --flex-inline">
                        <a class="header__logo-link" href="/{$lang}">
                            <img class="header__logo-img" src="/img/logo.svg"
                                 alt="{$web['DEFAULT_TITLE']}">
                        </a>
            </div>

           {* Header | Menu *}


            
                <div class="header__menu">
                        {include 'menu.latte', nav => $nav}
            </div>

            {* Header | Call *}
		    {include "./header-call.latte"}

		    {* Header | Language *}

                <div class="header__lang --flex-inline --flex-centre-y" n:if="count($activeLangs) > 1">
                    {include 'lang.latte'}
                </div>

        </div>


		{* Header | Bottom *}
        <div class="header__bottom --flex-inline --flex-centre-y">
            <div class="--w-12 --w-l-3 header__column header__column--left header__desktop-part">

                <a href="#" class="header__button
                js--toggler button button--brand" data-target="#menuDesktop">

                    <div class="header__button-bar-wrap">
                        <span class="header__button-bar"></span>
                        <span class="header__button-bar"></span>
                        <span class="header__button-bar"></span>
                    </div>
                    {$web['HEADER_CHOOSE_CATEGORY']}
                </a>

                {* Header | Menu *}
                <div n:class="'header__menu js--header-menu', $menuExpanded ? 'js--active'" id="menuDesktop">
                    {include 'menu.latte', nav => $categories}
                </div>
            </div>

            <div class="--w-12 --w-l-9 --flex
            header__column header__column--right">

                {* Header | Search *}


                
                <div class="header__search header__desktop-part --flex-inline --flex-centre-y icon icon--search">
                        {form search class => "header__search-form form form--search"}
                            <input n:name="search_text"
                                    data-use="typeahead"
                                    placeholder="{$web['SEARCH_PLACEHOLDER']}"
                                    class="form__input"
                                    type="text">
                            <button class="button form__button js--header-search">
                                {$web['HEADER_SEARCH']}
                            </button>
                        {/form}
                </div>


                {* Header | Logo *}

                
                <div class="header__logo --flex-inline header__mobile-part">
                        <a class="header__logo-link" href="/{$lang}">
                            <img class="header__logo-img" src="/img/logo.svg"
                                 alt="{$web['DEFAULT_TITLE']}">
                        </a>
                </div>


			    {* Header | Search *}


                
                <div class="header__search-toggle --flex-inline --flex-centre-y header__desktop-part">
                        <button class="header__search-toggle-button icon icon--search --hide-text js--header-search-toggle">
                            {$web['HEADER_SEARCH']}
                        </button>
                </div>


			    {* Header | Login *}
                    {include "./login.latte"}


                {* Header | Basket *}
                <basket></basket>


                <div class="header__mobile-part">
                    <a href="#" class="header__button js--toggler
                    button button--brand" data-target="#menuMobile">

                        <div class="header__button-bar-wrap">
                            <span class="header__button-bar"></span>
                            <span class="header__button-bar"></span>
                            <span class="header__button-bar"></span>
                        </div>

                        Menu
                    </a>

                    {* Header | Menu *}
                    <div n:class="'header__menu js--header-menu'"
                            id="menuMobile">
                        {include 'menu.latte', nav => $categories, secondaryNav => $nav}
                    </div>
                </div>


            </div>

            <div class="--w-12 header__mobile-part">
                <div class="header__search --flex-inline --flex-centre-y icon icon--search">
                    {form search class => "header__search-form form form--search"}
                        <input n:name="search_text"
                                data-use="typeahead"
                                placeholder="{$web['SEARCH_PLACEHOLDER']}"
                                class="form__input"
                                type="text">
                        <button class="button form__button js--header-search">
                            {$web['HEADER_SEARCH']}
                        </button>
                    {/form}
                </div>
            </div>
        </div>
    </div>
</header>
.header {
	background: $color__header;
	left: 0;
	position: absolute;
	right: 0;
	top: 0;
	z-index: 100;
	overflow: visible;
	border-bottom: 1px solid $color__gray;
	padding-bottom:25px;
	
}

.header--fixed{
	position: fixed;
	left:0;
	top:0;
	right:0;
}


.header--hide {
	top: -100%;
}

.header__menu{
	position: relative;
	
	.header__bottom &{
		max-height: 0px;
		transition: max-height 0.3s ease-in-out;
		overflow: hidden;
		right: -16px;
		left: -16px;
		
		@include media($large){
			left: 0;
			right: auto;
		}
	}
	
	
}

.header__menu{
	
	.header__top &{
		display: none;
		
		@include media($large){
			display: flex;
		}
	}
	
	.header__bottom &{
		position: absolute;
		z-index: 4;
		
		@include media($large){
			width: 100%;
		}
	}
}

.header__mobile-part{
	display: initial;
	
	@include media($large){
		display: none;
	}
}


.header__desktop-part{
	display: none;
	
	@include media($large){
		display: initial;
	}
}

.header__call{
	white-space: nowrap;
	margin-right: 15px;
	line-height: 1.2;
	
	@include media($large){
		margin-left: auto;
	}
	
	.header__top &{
		display: none;
		
		@include media($large){
			display: flex;
		}
	}
}

.header__call-link{
	color: $color__green;
	margin-right: 8px;
	
	&::before{
		font-size: 1.3rem;
	}
}

.header__call-content{
	flex-direction: column;
	@include media($large){
		flex-direction: row;
	}
}

.header__call-text{
	color: $color__gray-darker;
	display: block;
}

.header__logo{
	width: 110px;
	margin-right:15px;
	
	
	@include media(375px){
		width: 130px;
	}
	
	@include media($large){
		margin-right: 25px;
	}
	@include media($xlarge){
		margin-right: 40px;
		width: 190px;
	}
}


.header__bottom{
	align-items: center;
	width: 100%;
}

.header__top{
	padding: 25px 0;
	font-size: 1.4rem;
	display: none;
	width: 100%;
	
	@include media($large){
		display: flex;
	}
}

.header__button{
	width: 100%;
	flex-direction: row;
	
	
	padding: 4px 11px;
	
	@include media(375px){
		padding: 8px 17px;
	}
	
	@include media($large){
		padding: 8px 32px;
	}
}

.header__button-bar-wrap{
	margin-right: 10px;
	pointer-events: none;
}

.header__button-bar{
	display: block;
	background: white;
	width: 18px;
	height: 2px;
	
	&:not(:last-of-type){
		margin-bottom: 3px;
	}
}



.header__search{
	width: 100%;
	position: relative;
	max-width: 525px;
	
	
	&::before{
		position: absolute;
		left: 18px;
		top: 19px;
		color: $color__primary;
		font-size: 1.6rem;
	}
	
	@include media($xlarge){
		max-width: 665px;
	}
}

.header__search-form{
	width: 100%;
	display:flex;
}




.header__column{
	position:relative;
}


.header__column--right{
	align-items: center;
	padding: 15px 0;
	

	@include media($large){
		padding:  0 0 0 55px;
	}
}

.header__login{
	display: flex;
	align-items: center;
	margin-right: 10px;
	margin-left: auto;
	color: $color__text;
	
	@include media($large){
		margin-right: 25px;
	}
	
}

Menu

<nav class="menu">

    {default $secondaryNav = false}
	{* Menu | List *}
    <div class="menu__content --flex --flex-centre js--menu-content">
        <div class="menu__list">
            <div class="menu__top">

                {* Header | Call *}
		        {include "./header-call.latte"}

                {* Header | Lang *}
		        {include "./lang.latte"}

            </div>
            {foreach $nav as $item}
                <div class="menu__item">
                    <a n:class="'menu__link', $iterator->isLast() ? 'menu__link--last'" href="{$item|link|noescape}">
                        {$item|name|striptags}
                    </a>
                </div>
            {/foreach}
        </div>

        <div class="menu__additional-list --flex --flex-column" n:if="$secondaryNav">
            {foreach $secondaryNav as $item}
                <div class="menu__additional-item">
                    <a n:class="'menu__additional-link', $iterator->isLast() ? 'menu__link--last'" href="{$item|link|noescape}">
                        {$item|name|striptags}
                    </a>
                </div>
            {/foreach}
        </div>

    </div>
</nav>
.menu__list {
	display: flex;
	
	.header__bottom & {
		background: $color__gray;
		flex-direction: column;
		position: relative;
		padding: 6px 15px;
		font-size: 1.5rem;
		margin-top: 15px;
		width: 100%;
		max-height: calc(100vh - 250px);
		overflow-y: auto;
		
		@include media($large){
			padding: 6px 30px;
			max-height: calc(100vh - 200px);
		}
		
		&::before {
			@include pseudo();
			bottom: 100%;
			width: 0;
			height: 0;
			border-style: solid;
			border-width: 0 16px 11px 16px;
			border-color: transparent transparent $color__gray transparent;
			right: 54px;
			
			@include media($large) {
				left: calc(50% - 16px);
				right: auto;
			}
		}
	}
}

.menu__top{
	display: flex;
	justify-content: space-between;
	align-items: center;
	padding:15px 0 8px;
	flex-shrink: 0;
	
	
	@include media($large){
		display: none;
	}
}


.menu__additional-list {
	background: $color__gray;
	width: 100%;
	padding: 35px 15px;
	max-height: 150px;
}

.menu__additional-link{
	padding: 10px 0;
	text-transform:uppercase;
	font-size: 1.4rem;
	color: $color__text;
}

.menu__list--main {
	display: flex;
}


.menu__button {
	display: inline-block;
	height: 36px;
	padding: 3px;
	position: absolute;
	right: 8px;
	top: -4px;
	width: 40px;
	z-index: 1;
	
	@include media($large) {
		display: none;
	}
}


.menu__link {
	color: $color__text;
	display: block;
	
	&:hover, &:focus {
		color: $color__primary;
	}
	
	.header__top & {
		font-size: 1.4rem;
		text-transform: uppercase;
		padding: 3px 8px;
		
		@include media($xlarge) {
			padding: 5px 13px;
		}
	}
	
	.header__bottom & {
		width: 100%;
		padding: 9.65px 2px;
		
		
		@include media($xlarge) {
			padding: 12.3px 2px;
		}
		
		border-bottom: 1px solid $color__border;
	}
}

.menu__link--last {
	.header__bottom & {
		border-bottom: 0;
	}
}

Header-call

<div class="header__call --flex-centre-y">
    <div class="header__call-content --flex --flex-nowrap">
        <a class="header__call-link --flex --flex-centre icon icon--phone"
           href="tel:{$web['PER_PHONE']}
                        {$web['PHONE']}">
            <span>{$web['PER_PHONE']} <b>{$web['PHONE']}</b></span>
        </a>

        <span class="header__call-text">
						{$web['OPEN_HOURS']|noescape}
					</span>
    </div>
</div>

Lang

{* Lang *}

<div class="lang" n:if="count($activeLangs) > 1">

	{* Lang | List *}
	<ul class="lang__list --flex --flex-nowrap ">
		<li n:class="'lang__item', $activeLangId === $lang_id ? 'lang__item--selected'"
			n:foreach="$activeLangs as $activeLangId => $activeLang">
			<a class="lang__link --flex --flex-nowrap --flex-y-centre" role="button" title="{$activeLang}" href="/{$activeLang}">
				<img src="/img/flags/{$activeLang}.svg" alt="{$activeLang}">
			</a>
		</li>

	</ul>

</div>
.lang{
	.header__top &{
		display: none;
		
		@include media($large){
			display: block;
		}
	}
}

.lang__item{
	opacity: 0.4;
	transition: opacity $ease;
	
	&:not(:last-of-type){
		margin-right: 8px;
	}
	
	
	&:hover, &:focus{
		opacity: 1;
	}
}

.lang__item--selected{
	opacity: 1;
}

Login

<div class="header__login js--login login"
     data-target=".js--login-wrap">
    <span class="login__icon icon icon--user"></span>
    <div class="login__wrap js--login-wrap">
        <div class="login__box --flex --flex-column --flex-end">
            {if $user->isLoggedIn()}
                <a class="login__link --link-underline"
                   href="/{$lang}/account/orders">{$web['ACCOUNT_ORDERS']}</a>
                <a class="login__link --link-underline"
                   href="/{$lang}/account/address">{$web['ACCOUNT_ADDRESS']}</a>
                <a class="login__link --link-underline"
                   href="/{$lang}/account/setting">{$web['ACCOUNT_SETTING']}</a>
                <a class="login__link --link-underline"
                   href="/{$lang}/logout">{_front.layout.logout}</a>
            {else}
                {form signInForm class => "login__form form form--contact"}
                    <label class="form__label" n:name="$form['email']">
                        {_}{$form['email']->caption}{/_}
                        {input email class => "form__input"}
                    </label>

                    <label class="form__label" n:name="$form['password']">
                        {_}{$form['password']->caption}{/_}
                        {input password class => "form__input"}
                    </label>

                    <label>
                        {input send class => "--hide"}
                        <span class="form__button button button--brand">
                            {_}{$form['send']->caption}{/_}
                        </span>
                    </label>
                {/form}

                <a class="login__link --link-underline" href="/{$lang}/register">{$web['LOGIN_REGISTER']}</a>
                <a class="login__link --link-underline" href="/{$lang}/password">{$web['LOGIN_FORGOTTEN_PASS']}</a>
            {/if}
        </div>
    </div>
</div>
.login{
	position: relative;
	cursor:pointer;
}

.login__form{
	width: 100%;
	margin: 0 0 10px 0;
}

.login__icon{
	
	&:hover, &:focus{
		color: $color__primary;
	}

	&::before{
		font-size: 2.1rem;
		
		@include media(375px){
			font-size: 2.6rem;
		}
	}
}

.login__wrap{
	position: absolute;
	background: #fff;
	right: -92px;
	top: 100%;
	width: 290px;
	z-index: 2;
	max-height: 0px;
	overflow: hidden;
	transition: max-height $ease;
	
	
	@include media(375px){
		right: -107px;
	}
	
	@include media($large){
		right: 0px;
	}
}

.login__box{
	padding: 10px;
	border: 1px solid $color__gray;
	
}
.login__title{
	width: 100%;
	font-size: 1.5rem;
}

Necessary JavaScript

const headerBasket = document.querySelector(".js--basket");
    headerBasket.addEventListener("mouseenter", function (e) {
        collapseElement(headerBasket.dataset.target, 'show');
    })
    headerBasket.addEventListener("mouseleave", function (e) {
        collapseElement(headerBasket.dataset.target, 'hide');
    })

    const headerLogin = document.querySelector(".js--login");
    headerLogin.addEventListener("mouseenter", function (e) {
        collapseElement(headerLogin.dataset.target, 'show');
    })
    headerLogin.addEventListener("mouseleave", function (e) {
        collapseElement(headerLogin.dataset.target, 'hide');
    })


    }

    const collapseFnMap = {
        'toggle': 'toggle',
        'show': 'add',
        'hide': 'remove'
    };

    function collapseElement(selector, cmd = 'toggle') {
        let target = document.querySelector(selector);
        if(cmd === 'toggle'){
            if (target.classList.contains("js--active")) {
                target.style.maxHeight = "0px";
            } else {
                target.style.maxHeight = target.scrollHeight + "px";
            }
        }
        else if(cmd === 'show'){
            target.style.maxHeight = target.scrollHeight + "px";
        }
        else if(cmd === 'hide'){
            target.style.maxHeight = "0px";
        }

        target.classList[collapseFnMap[cmd]]('js--active');
    }