<header class="header ">
<div class="header__inner">
<div class="header__grid grid grid--no-gutter">
<div class="header__column grid__col grid__col--md-5">
<button type="button" class="button button--icon header__burger-button ">
<span class="button__pseudo-icon">
<svg class="icon button__icon">
<use xlink:href="../../inc/svg/global.49d3f64de1a3f5ccf16c763316702027.svg#menu"></use>
</svg>
</span>
<span class="button__inner" data-text="">
<span class="button__text">
<svg class="icon button__icon">
<use xlink:href="../../inc/svg/global.49d3f64de1a3f5ccf16c763316702027.svg#menu"></use>
</svg>
</span>
</span>
</button>
<div class="logo header__logo">
<a href="#" class="logo__content logo__link">
<figure class="image logo__image">
<img loading="lazy" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-srcset="../../inc/logo/logo.svg" data-sizes="auto" alt="letters-logo" class="image__img lazyload">
</figure>
</a>
</div>
<button type="button" class="button button--icon header__search-button header__search-button--mobile ">
<span class="button__pseudo-icon">
<svg class="icon button__icon">
<use xlink:href="../../inc/svg/global.49d3f64de1a3f5ccf16c763316702027.svg#search"></use>
</svg>
</span>
<span class="button__inner" data-text="">
<span class="button__text">
<svg class="icon button__icon">
<use xlink:href="../../inc/svg/global.49d3f64de1a3f5ccf16c763316702027.svg#search"></use>
</svg>
</span>
</span>
</button>
</div>
<div class="header__column header__column--desktop grid__col grid__col--md-19">
<div class="header__theme-wrapper">
<div class="theme-switcher header__theme">
<button data-theme="theme-beige" class="theme-switcher__button theme-switcher__button--beige">
<span class="theme-switcher__icon-cross"></span>
</button>
<button data-theme="theme-gray" class="theme-switcher__button theme-switcher__button--gray">
<span class="theme-switcher__icon-cross"></span>
</button>
<button data-theme="theme-yellow" class="theme-switcher__button theme-switcher__button--yellow">
<span class="theme-switcher__icon-cross"></span>
</button>
<button data-theme="theme-green" class="theme-switcher__button theme-switcher__button--green">
<span class="theme-switcher__icon-cross"></span>
</button>
<button data-theme="theme-dark" class="theme-switcher__button theme-switcher__button--dark">
<span class="theme-switcher__icon-cross"></span>
</button>
</div>
</div>
<button type="button" class="button button--icon-text header__search-button ">
<span class="button__inner" data-text="Search">
<span class="button__text">
Search
<svg class="icon button__icon">
<use xlink:href="../../inc/svg/global.49d3f64de1a3f5ccf16c763316702027.svg#arrow-down-left"></use>
</svg>
</span>
</span>
</button>
<nav class="navigation header__navigation text-button">
<ul class="navigation__list">
<li class="navigation__item is-current navigation__item--only-mobile " data-text="home">
<a href="#" class="navigation__link">
<span class="navigation__link-text">
home
</span>
</a>
</li>
<li class="navigation__item " data-text="fonts">
<a href="#" class="navigation__link">
<span class="navigation__link-text">
fonts
</span>
</a>
</li>
<li class="navigation__item " data-text="about">
<a href="#" class="navigation__link">
<span class="navigation__link-text">
about
</span>
</a>
</li>
<li class="navigation__item navigation__item--only-mobile " data-text="contact">
<a href="#" class="navigation__link">
<span class="navigation__link-text">
contact
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
<div class="header__burger-top">
<button type="button" class="button button--icon header__burger-button header__burger-button--close ">
<span class="button__pseudo-icon">
<svg class="icon button__icon">
<use xlink:href="../../inc/svg/global.49d3f64de1a3f5ccf16c763316702027.svg#exit"></use>
</svg>
</span>
<span class="button__inner" data-text="">
<span class="button__text">
<svg class="icon button__icon">
<use xlink:href="../../inc/svg/global.49d3f64de1a3f5ccf16c763316702027.svg#exit"></use>
</svg>
</span>
</span>
</button>
</div>
</div>
<div class="header_appear-container">
<div class="header__search">
<form method="get" action="the.url.com">
<div class="textfield textfield--label-hidden header__search-field">
<div class="textfield__inner">
<input class="textfield__input" type="text" id="search" name="textfield" placeholder="Search">
<label class="textfield__label text-button " for="search">
Textfield label
</label>
</div>
</div>
</form>
</div>
<div class="header__burger">
<nav class="navigation header__navigation text-button">
<ul class="navigation__list">
<li class="navigation__item is-current navigation__item--only-mobile " data-text="home">
<a href="#" class="navigation__link">
<span class="navigation__link-text">
home
</span>
</a>
</li>
<li class="navigation__item " data-text="fonts">
<a href="#" class="navigation__link">
<span class="navigation__link-text">
fonts
</span>
</a>
</li>
<li class="navigation__item " data-text="about">
<a href="#" class="navigation__link">
<span class="navigation__link-text">
about
</span>
</a>
</li>
<li class="navigation__item navigation__item--only-mobile " data-text="contact">
<a href="#" class="navigation__link">
<span class="navigation__link-text">
contact
</span>
</a>
</li>
</ul>
</nav>
<div class="header__burger-heading text-small">switch themes</div>
<div class="theme-switcher header__theme">
<button data-theme="theme-beige" class="theme-switcher__button theme-switcher__button--beige">
<span class="theme-switcher__icon-cross"></span>
</button>
<button data-theme="theme-gray" class="theme-switcher__button theme-switcher__button--gray">
<span class="theme-switcher__icon-cross"></span>
</button>
<button data-theme="theme-yellow" class="theme-switcher__button theme-switcher__button--yellow">
<span class="theme-switcher__icon-cross"></span>
</button>
<button data-theme="theme-green" class="theme-switcher__button theme-switcher__button--green">
<span class="theme-switcher__icon-cross"></span>
</button>
<button data-theme="theme-dark" class="theme-switcher__button theme-switcher__button--dark">
<span class="theme-switcher__icon-cross"></span>
</button>
</div>
<div class="header__burger-heading text-small">Instagram</div>
<a href="#" class="header__link h2">@lettersfromtartu</a>
</div>
</div>
</header>
<header class="header {{ class }} {{ modifier }}">
<div class="header__inner">
<div class="header__grid grid grid--no-gutter">
<div class="header__column grid__col grid__col--md-5">
{% include '@button' with {class: 'header__burger-button', modifier: "button--icon", data: {icon: "menu"}} %}
{% if data.logo %}
{% include '@logo' with {class: 'header__logo', data: data.logo} %}
{% endif %}
{% include '@button' with {class: 'header__search-button header__search-button--mobile', modifier: "button--icon", data: {icon: "search"}} %}
</div>
<div class="header__column header__column--desktop grid__col grid__col--md-19">
{% if data.themeSwitcher %}
<div class="header__theme-wrapper">
{% include '@theme-switcher' with {class: 'header__theme', data: data.themeSwitcher} %}
</div>
{% endif %}
{% if data.searchButton %}
{% include '@button' with {class: 'header__search-button', modifier: "button--icon-text", data: data.searchButton|merge({icon: "arrow-down-left"})} %}
{% endif %}
{% if data.navigation %}
{% include '@navigation' with {class: 'header__navigation text-button', data: data.navigation} %}
{% endif %}
</div>
</div>
<div class="header__burger-top">
{% include '@button' with {class: 'header__burger-button header__burger-button--close', modifier: "button--icon", data: {icon: "exit"}} %}
</div>
</div>
<div class="header_appear-container">
{% if data.search %}
<div class="header__search">
<form method="get" action="{{data.search.url}}">
{% include '@textfield' with {class: 'header__search-field', data: data.search, modifier: 'textfield--label-hidden'}%}
</form>
</div>
{% endif %}
<div class="header__burger">
{% if data.navigation %}
{% include '@navigation' with {class: 'header__navigation text-button', data: data.navigation} %}
{% endif %}
{% if data.themeHeading %}
<div class="header__burger-heading text-small">{{ data.themeHeading }}</div>
{% endif %}
{% if data.themeSwitcher %}
{% include '@theme-switcher' with {class: 'header__theme', data: data.themeSwitcher} %}
{% endif %}
{% if data.instagram %}
<div class="header__burger-heading text-small">{{ data.instagram.heading }}</div>
<a href="{{ data.instagram.link }}" class="header__link h2">{{ "@" ~ data.instagram.handle }}</a>
{% endif %}
</div>
</div>
</header>
{
"language": "en-US",
"data": {
"themeHeading": "switch themes",
"instagram": {
"heading": "Instagram",
"link": "#",
"handle": "lettersfromtartu"
},
"logo": {
"image": {
"alt": "letters-logo",
"srcset": "../../inc/logo/logo.svg"
},
"link": "#"
},
"themeSwitcher": {
"palettes": [
{
"class": "beige"
},
{
"class": "gray"
},
{
"class": "yellow"
},
{
"class": "green"
},
{
"class": "dark"
}
]
},
"searchButton": {
"text": "Search"
},
"navigation": {
"items": [
{
"link": "#",
"title": "home",
"current": "true",
"is_only_mobile": true
},
{
"link": "#",
"title": "fonts"
},
{
"link": "#",
"title": "about"
},
{
"link": "#",
"title": "contact",
"is_only_mobile": true
}
]
},
"search": {
"label": "Textfield label",
"id": "search",
"name": "textfield",
"placeholder": "Search",
"url": "the.url.com"
}
}
}
@keyframes bounceDiagonal {
0% {
transition-timing-function: $transition-easing;
position: relative;
top: 0;
left: 0;
}
50% {
transition-timing-function: $transition-easing;
position: relative;
top: 4px;
left: 4px;
}
100% {
transition-timing-function: $transition-easing;
position: relative;
top: 0;
left: 0;
}
}
@keyframes bounceDiagonalUp {
0% {
transition-timing-function: $transition-easing;
position: relative;
top: -2px;
left: 0;
}
50% {
transition-timing-function: $transition-easing;
position: relative;
top: -6px;
left: 4px;
}
100% {
transition-timing-function: $transition-easing;
position: relative;
top: -2px;
left: 0;
}
}
.header {
position: fixed;
top: 0;
left: 0;
display: flex;
flex-direction: column;
transition: $transition-duration $transition-easing;
transition-property: transform;
transform: translateY(-100%);
width: 100%;
z-index: map-get($zindex, header);
.header-is-visible & {
transform: translateY(0);
}
}
.header_appear-container {
position: relative;
margin-top: -40px;
height: 40px;
@include bp(sm-min) {
margin-top: -60px;
height: 60px;
}
}
.header__grid {
flex-wrap: nowrap;
flex: 1 0 auto;
}
.header__inner {
background-color: var(--color-background);
display: flex;
flex-direction: row;
position: relative;
z-index: 2;
transition: $transition-duration $transition-easing;
transition-property: background-color;
}
.header__burger-top {
position: absolute;
display: flex;
justify-content: flex-end;
top: 0;
left: 0;
right: 0;
bottom: 0;
box-shadow: 0 -1px 0 0 var(--color-text) inset;
background-color: var(--color-background);
transform: translateX(-100%);
transition: $transition-duration $transition-easing;
transition-property: transform, background-color, box-shadow;
&.is-visible {
transform: translateX(0);
}
@include bp(md-min) {
display: none;
}
}
.header__burger-button {
box-shadow: 0 -1px 0 0 var(--color-text) inset;
transition: $transition-duration $transition-easing;
transition-property: box-shadow;
@include bp(md-min) {
display: none;
}
}
.header__burger-button--close {
box-shadow: 1px 0 0 0 var(--color-text) inset;
}
.header__theme-wrapper {
flex-grow: 1;
display: none;
flex-direction: row;
align-items: center;
box-shadow: 1px -1px 0 0 var(--color-text) inset;
transition: $transition-duration $transition-easing;
transition-property: box-shadow;
.header__burger & {
display: flex;
box-shadow: none;
}
@include bp(md-min) {
display: flex;
}
}
.header__theme {
position: absolute;
margin: 0;
top: 0;
left: 0;
display: none;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
pointer-events: none;
.header__burger & {
position: relative;
display: flex;
box-shadow: none;
flex-basis: 10%;
justify-content: flex-start;
margin-left: 16px;
margin-top: 24px;
}
@include bp(md-min) {
display: flex;
}
}
.header__column {
display: flex;
}
.header__column--desktop {
display: none;
@include bp(md-min) {
display: flex;
}
}
.header__logo {
box-shadow: 1px -1px 0 0 var(--color-text) inset;
flex-grow: 1;
height: 100%;
max-height: 40px;
transition: $transition-duration $transition-easing;
transition-property: box-shadow;
@include bp(sm-min) {
max-height: 60px;
}
@include bp(md-min) {
box-shadow: 0 -1px 0 0 var(--color-text) inset;
display: block;
}
}
.header__search-button {
box-shadow: 1px -1px 0 0 var(--color-text) inset;
padding: 0 24px;
display: none;
transition: $transition-duration $transition-easing;
transition-property: box-shadow;
@include bp(md-min) {
display: block;
}
}
.header__search-button--mobile {
box-shadow: 1px -1px 0 0 var(--color-text) inset;
padding: 0;
display: block;
@include bp(md-min) {
display: none;
}
}
.header__navigation {
flex-grow: 1;
display: none;
.header__burger & {
display: block;
margin-bottom: 30px;
}
@include bp(md-min) {
flex-grow: 0;
display: block;
}
}
.header__search {
position: absolute;
top: 0;
left: 0;
right: 0;
background-color: var(--color-background);
transition: $transition-duration $transition-easing;
transition-property: top, background-color;
.search-is-visible & {
top: 100%;
}
}
.header__search-field {
width: 100%;
}
.header__search-field .textfield__input {
border-top: none;
width: 100%;
padding-left: 60px;
@include bp(md-min) {
padding-left: 90px;
}
}
.header__burger-close-button {
box-shadow: 1px 0 0 0 var(--color-text) inset;
transition: $transition-duration $transition-easing;
transition-property: box-shadow;
}
.header__burger {
position: absolute;
overflow: hidden;
overflow-y: auto;
top: 40px;
left: 0;
right: 0;
transform: translateY(-100%);
display: flex;
flex-direction: column;
background-color: var(--color-background);
border-bottom: 1px solid var(--color-brand);
height: calc(100vh - 39px); /* stylelint-disable-line plugin/no-unsupported-browser-features */
flex-basis: 33%;
transition: $transition-duration $transition-easing;
transition-property: transform, background-color, border-bottom;
&.is-visible {
transform: translateY(0);
}
@include bp(sm-min) {
height: calc(100vh - 60px); /* stylelint-disable-line plugin/no-unsupported-browser-features */
top: 60px;
}
@include bp(md-min) {
display: none;
}
}
.header__burger-heading {
padding: 6px 16px;
margin-top: 30px;
border-top: 1px solid var(--color-brand);
border-bottom: 1px solid var(--color-brand);
transition: $transition-duration $transition-easing;
transition-property: border-top, border-bottom;
text-transform: uppercase;
color: rgba(var(--color-text--rgb), .7);
}
.header__link {
margin: 40px 16px;
cursor: pointer;
white-space: normal;
font-size: #{'min(8vw,#{$font-size-h2})'};
line-height: $font-line-height-h2;
@include bp(sm-min) {
font-size: $font-size-h2-lg;
line-height: $font-line-height-h2-lg;
}
&:after {
content: none;
}
&:before {
content: none;
}
}
.button__icon {
.header & {
transition: transform $transition-duration $transition-easing;
}
.search-is-visible .header__search-button & {
@include bp(md-min) {
position: relative;
top: -2px;
left: 0;
transform: rotate(-90deg);
}
}
.header__search-button:hover & {
@include bp(md-min) {
@media (hover: hover) {
position: relative;
animation: bounceDiagonal 500ms infinite;
}
}
}
.search-is-visible .header__search-button:hover & {
@include bp(md-min) {
@media (hover: hover) {
animation: bounceDiagonalUp 500ms infinite;
}
}
}
.search-is-visible .header__search-button:active & {
@include bp(md-min) {
animation: none;
}
}
.header__search-button:active & {
animation: none;
}
}
import './header.scss';
import Component from '@component';
import ClickEvent = JQuery.ClickEvent;
import Helpers from '@helpers';
interface IHeaderSettings {
searchButtonClass: string;
burgerButtonClass: string;
searchClass: string;
searchIsVisibleClass: string;
burgerClass: string;
burgerTopClass: string;
isVisibleClass: string;
stickyHeaderSelector: string;
headerIsVisibleClass: string;
}
export default class Header extends Component {
static initSelector: string = '.header';
searchButtons: JQuery;
burgerButton: JQuery;
settings: IHeaderSettings;
searchField: JQuery;
searchInput: JQuery;
burgerMenu: JQuery;
stickyHeaders: JQuery;
previousScrollY: number;
constructor(target: HTMLElement) {
super(target);
this.settings = {
burgerButtonClass: 'header__burger-button',
burgerClass: 'header__burger',
burgerTopClass: 'header__burger-top',
headerIsVisibleClass: 'header-is-visible',
isVisibleClass: 'is-visible',
searchButtonClass: 'header__search-button',
searchClass: 'header__search',
searchIsVisibleClass: 'search-is-visible',
stickyHeaderSelector: '.content-section--sticky .content-section__title',
};
this.previousScrollY = window.scrollY;
$('body').addClass(this.settings.headerIsVisibleClass);
this.searchButtons = this.element.find('.' + this.settings.searchButtonClass);
this.burgerButton = this.element.find('.' + this.settings.burgerButtonClass);
this.searchField = this.element.find('.' + this.settings.searchClass);
this.searchInput = this.searchField.find('input');
this.burgerMenu = this.element.find('.' + this.settings.burgerClass);
this.stickyHeaders = $(document).find(this.settings.stickyHeaderSelector);
this.init();
}
init(): void {
this.searchButtons.on('click', this.toggleSearch.bind(this));
this.burgerButton.on('click', this.toggleBurger.bind(this));
this.element.on('click', this.stopPropagation.bind(this));
window.addEventListener('scroll', this.toggleHeader.bind(this));
window.addEventListener('click', this.closeSearch.bind(this));
window.addEventListener('resize', this.resizeHandler.bind(this));
requestAnimationFrame(this.stickyHeadingHandler.bind(this));
}
resizeHandler(): void {
if (window.matchMedia('only screen and (min-width: 768px)').matches) {
this.closeBurger();
}
this.stickyHeadingHandler();
}
stopPropagation(event: ClickEvent): void {
event.stopPropagation();
}
closeSearch(): void {
this.element.removeClass(this.settings.searchIsVisibleClass);
}
openSearch(): void {
this.element.addClass(this.settings.searchIsVisibleClass);
}
toggleSearch(): void {
if (this.element.hasClass(this.settings.searchIsVisibleClass)) {
this.closeSearch();
} else {
this.openSearch();
}
requestAnimationFrame(this.stickyHeadingHandler.bind(this));
}
closeBurger(): void {
this.burgerMenu.removeClass(this.settings.isVisibleClass);
this.element.find('.' + this.settings.burgerTopClass).removeClass(this.settings.isVisibleClass);
Helpers.enableScroll();
this.previousScrollY = window.scrollY;
}
openBurger(): void {
this.burgerMenu.addClass(this.settings.isVisibleClass);
this.element.find('.' + this.settings.burgerTopClass).addClass(this.settings.isVisibleClass);
Helpers.disableScroll();
this.closeSearch();
}
toggleBurger(): void {
if (this.burgerMenu.hasClass(this.settings.isVisibleClass)) {
this.closeBurger();
} else {
this.openBurger();
}
}
getMaxTop(): number {
if (this.element[0] && this.searchField[0]) {
const headerRect: DOMRect = this.element[0].getBoundingClientRect();
const headerTop: number = headerRect.y + headerRect.height;
const searchRect: DOMRect = this.searchField[0].getBoundingClientRect();
const searchTop: number = searchRect.y + searchRect.height;
return Math.max(0, Math.max(headerTop, searchTop) - 2);
} else {
return 0;
}
}
stickyHeadingHandler(previousTop?: number): void {
this.setStickyHeadingTop(this.getMaxTop());
requestAnimationFrame(() => {
this.setStickyHeadingTop(this.getMaxTop());
if (previousTop === undefined || previousTop !== this.getMaxTop()) {
requestAnimationFrame(() => this.stickyHeadingHandler(this.getMaxTop()));
}
});
}
setStickyHeadingTop(top: number): void {
this.stickyHeaders.css('top', top - 2);
}
toggleHeader(): void {
this.stickyHeadingHandler();
}
}