<div class="nav-in-page nav-in-page--not-sticky">
    <span class="nav-in-page__anchor"></span>

    <div class="nav-in-page__inner">
        <div class="nav-in-page__content">
            <a class="nav-in-page__link nav-in-page__trigger mobile">
                <span class="link__label">Overview</span>
                <div class="link__icon">
                    <svg class="icon-chevron" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
                        <path fill="currentColor" d="M11.172 9.82L13 8 9.906 4.99 6.04 1.21c-.281-.28-.703-.28-.914 0l-.914.91c-.281.28-.281.7 0 .91L9.273 8l-5.062 4.97c-.281.28-.281.7 0 .91l.914.91c.281.28.703.28.914 0l5.133-4.97z" />
                    </svg>

                </div>
            </a>

            <nav class="nav-in-page__nav">
                <ul class="nav-in-page__items">
                    <li class="nav-in-page__item active">
                        <a href="#" class="nav-in-page__link">
                            <span class="link__label">Overview</span>
                        </a>
                    </li>
                    <li class="nav-in-page__item">
                        <a href="#" class="nav-in-page__link">
                            <span class="link__label">Videos</span>
                        </a>
                    </li>
                    <li class="nav-in-page__item">
                        <a href="#" class="nav-in-page__link">
                            <span class="link__label">Specifications</span>
                        </a>
                    </li>
                    <li class="nav-in-page__item">
                        <a href="#" class="nav-in-page__link">
                            <span class="link__label">Combine with</span>
                        </a>
                    </li>
                    <li class="nav-in-page__item">
                        <a href="#" class="nav-in-page__link">
                            <span class="link__label">Support</span>
                        </a>
                    </li>
                </ul>
            </nav>
        </div>

        <div class="nav-in-page__actions">
            <div class="nav-in-page__action mobile">
                <span>€ 1999</span>
                <a href="#" class="link link--colored">
                    <span class="link__label">Buy now</span>
                </a>

            </div>
            <div class="nav-in-page__action desktop">
                <span>€ 1999</span>

                <a href="#" target="_blank" class="action action--normal action--on-light">
                    <span class="action__label">Buy now</span>
                    <span class="action__bg"></span>
                </a>

            </div>
        </div>
    </div>
</div>
<div class="nav-in-page{{#if modifier }} nav-in-page--{{ modifier }}{{/if}}">
  <span class="nav-in-page__anchor"></span>

  <div class="nav-in-page__inner">
    <div class="nav-in-page__content">
      <a class="nav-in-page__link nav-in-page__trigger mobile">
        <span class="link__label">{{ label }}</span>
        <div class="link__icon">
          {{render '@icon-chevron' }}
        </div>
      </a>

      <nav class="nav-in-page__nav">
        <ul class="nav-in-page__items">
          {{#if items }}
            {{#each items }}
              <li class="nav-in-page__item{{#if active}} active{{/if}}">
                <a href="{{ href }}" class="nav-in-page__link">
                  <span class="link__label">{{ label }}</span>
                </a>
              </li>
            {{/each}}
          {{/if}}
        </ul>
      </nav>
    </div>

    {{#if action}}
    <div class="nav-in-page__actions">
      <div class="nav-in-page__action mobile">
        {{#if outofstock}}
        <span>Out of stock</span>
        {{else}}
        <span>€ 1999</span>
        {{/if}}
        {{render '@link--colored' actionMobile merge=true }}
      </div>
      <div class="nav-in-page__action desktop">
        {{#if outofstock}}
        <span>Out of stock</span>
        {{else}}
        <span>€ 1999</span>
        {{/if}}
        {{render '@action' action merge=true }}
      </div>
    </div>
    {{/if}}
  </div>
</div>
modifier: not-sticky
label: Overview
items:
  - href: '#'
    label: Overview
    active: true
  - href: '#'
    label: Videos
  - href: '#'
    label: Specifications
  - href: '#'
    label: Combine with
  - href: '#'
    label: Support
action:
  label: Buy now
  url: '#'
  • Content:
    const NAVINPAGE = (function(window, undefined) {
      const modes = {
        mobile: 'mobile',
        desktop: 'desktop',
      };
    
      const settings = {
        debug: false,
        mode: modes.mobile,
        delay: 100,
        observer: {
          threshold: [0, .5, 1],
        },
        defaultLabel: '',
      };
    
      const selectors = {
        wrapper: '.nav-in-page',
        anchor: '.nav-in-page__anchor',
        trigger: '.nav-in-page__trigger',
        items: '.nav-in-page__items',
        item: '.nav-in-page__item',
        link: '.nav-in-page__link:not(.nav-in-page__trigger)',
        linkLabel: '.link__label',
        header: '.header',
        hero: '.hero-product__actions',
        target: '[data-nav-in-page]',
        notSticky: '.nav-in-page--not-sticky',
      };
    
      const classes = {
        open: 'nav--open',
        isSticky: 'is-sticky',
        active: 'active',
        heroIsOutView: 'hero-is-out-view',
      };
    
      let resizeTimer = 0;
      let canBeSticky = true;
    
      const openIt = ($wrapper) => {
        if (!$wrapper) { return; }
    
        if (settings.debug) { console.log('NAV IN PAGE OPEN', $wrapper); }
    
        $wrapper.classList.add(classes.open);
      };
    
      const closeIt = ($wrapper) => {
        if (!$wrapper) { return; }
    
        if (settings.debug) { console.log('NAV IN PAGE CLOSE', $wrapper); }
    
        $wrapper.classList.remove(classes.open);
      };
    
      const toggle = ($wrapper) => {
        if (!$wrapper) { return; }
    
        if (settings.debug) { console.log('NAV IN PAGE TOGGLE', $wrapper); }
    
        if ($wrapper.classList.contains(classes.open)) {
          closeIt($wrapper);
        } else {
          openIt($wrapper);
        }
      };
    
      const updateMode = () => {
        const $wrapper = document.querySelector(selectors.wrapper);
    
        if (!$wrapper) { return; }
    
        let mode = window.getComputedStyle($wrapper, ':after').getPropertyValue('content');
    
        if (mode) { mode = mode.replace(/"/g, ''); }
    
        settings.mode = mode;
      }
    
      const onObserve = (entries) => {
        const [entry] = entries;
        if (!entry) { return; }
    
        const { target: $target } = entry;
    
        if (!$target) { return; }
    
        const targetID = `#${$target.id}`;
        const $link = document.querySelector(`${selectors.link}[href="${targetID}"]`);
        const $wrapper = $link.closest(selectors.wrapper);
        const $links = $wrapper.querySelectorAll(selectors.link);
        const $trigger = $wrapper.querySelector(selectors.trigger);
        const $triggerLabel = $trigger.querySelector(selectors.linkLabel);
    
        if (settings.debug) { console.log('NAV IN PAGE OBSERVE', targetID, $link, $trigger, entry.isIntersecting); }
    
        if (entry.isIntersecting) {
          [...$links].forEach($link => {
            if ($link.getAttribute('href') === targetID) {
              $triggerLabel.innerText = $link.innerText;
              $link.closest(selectors.item).classList.remove(classes.active);
    
              $link.closest(selectors.item).classList.add(classes.active);
              $trigger.classList.remove(classes.active);
            } else {
              $links[0].closest(selectors.item).classList.remove(classes.active);
            }
          });
        } else {
          $triggerLabel.innerText = settings.defaultLabel;
          $link.closest(selectors.item).classList.remove(classes.active);
        }
    
        const $activeLinks = $wrapper.querySelectorAll(`${selectors.item}.${classes.active}`);
    
        if (!$activeLinks.length) {
          $links[0].closest(selectors.item).classList.add(classes.active);
        }
      }
    
      const observeTarget = ($link) => {
        const href = $link.getAttribute('href');
    
        if (href.indexOf('#') !== 0 || href.length < 3) { return; }
        // anchor link, find target
        $target = document.querySelector(href);
    
        if (!$target) { return; }
    
        const observer = new window.IntersectionObserver(onObserve, settings.observer);
        observer.observe($target);
      }
    
      const observeHero = () => {
        const $wrapper = document.querySelector(selectors.wrapper);
        const $hero = document.querySelector(selectors.hero);
    
        if (!$hero) { return; }
    
        const observer = new window.IntersectionObserver((entries) => {
          if (!entries[0].isIntersecting) {
            $wrapper.classList.add(classes.heroIsOutView);
          } else {
            $wrapper.classList.remove(classes.heroIsOutView);
          }
        }, settings.observer);
    
        observer.observe($hero);
      };
    
      const observeSelf = () => {
        const $wrapper = document.querySelector(selectors.wrapper);
        const $anchor = document.querySelector(selectors.anchor);
    
        const observer = new window.IntersectionObserver((entries) => {
         if (!entries[0].isIntersecting) {
            $wrapper.classList.add(classes.isSticky);
          } else {
            $wrapper.classList.remove(classes.isSticky);
          }
        }, settings.observer);
    
        observer.observe($anchor);
      };
    
      const updateTop = () => {
        const $wrapper = document.querySelector(selectors.wrapper);
        const $header = document.querySelector(selectors.header);
    
        if ($wrapper && canBeSticky && $header) {
          $wrapper.style.top = `${$header.offsetHeight}px`;
        }
      }
    
      const onResize = () => {
        clearTimeout(resizeTimer);
    
        resizeTimer = setTimeout(() => {
          const $wrappers = document.querySelectorAll(selectors.wrapper);
    
          [...$wrappers].forEach(closeIt);
    
          updateMode($wrappers[0]);
    
          canBeSticky && updateTop();
        }, 200);
      }
    
      const onTriggerClick = (e) => {
        e.preventDefault();
    
        if (settings.mode === modes.desktop) {
          window.scrollTo({
            top: 0,
            behavior: 'smooth',
          });
          return;
        }
    
        if (settings.debug) { console.log('NAV IN PAGE TRIGGER CLICK', e); }
    
        const $trigger = e.currentTarget;
        const $wrapper = $trigger.closest(selectors.wrapper);
    
        toggle($wrapper);
      };
    
      const onLinkClick = (e) => {
        const $link = e.currentTarget;
    
        if (settings.debug) { console.log('NAV IN PAGE LINK CLICK', $link); }
    
        const $wrapper = $link.closest(selectors.wrapper);
    
        closeIt($wrapper);
      };
    
      const onDocumentClick = ({ target: $target }) => {
        if (settings.mode === modes.desktop) { return; }
    
        const $targetWrapper = $target.closest(selectors.wrapper);
    
        if (!$targetWrapper) {
    
          if (settings.debug) { console.log('NAV IN PAGE OUTSIDE CLICK', $targetWrapper); }
    
          const $wrappers = document.querySelectorAll(selectors.wrapper, settings.observer);
    
          [...$wrappers].forEach(closeIt);
        }
      }
    
      const addItem = (href, label) => {
        const $items = document.querySelector(selectors.items);
    
        if (!$items || !href || !label) { return; }
    
        const $item = document.createElement('li');
        const html = `
          <a href="#${href}" class="nav-in-page__link">
            <span class="link__label">${label}</span>
          </a>
        `;
    
        $item.classList.add('nav-in-page__item');
        $item.innerHTML = html;
    
        $items.appendChild($item);
      };
    
      const init = () => {
        const $wrapper = document.querySelector(selectors.wrapper);
    
        if (!$wrapper) { return; }
    
        if (settings.debug) { console.log('NAV IN PAGE INIT'); }
    
        canBeSticky = !document.querySelector(selectors.notSticky);
        const $trigger = document.querySelector(selectors.trigger);
    
        $trigger && $trigger.addEventListener('click', onTriggerClick);
    
        settings.defaultLabel = $trigger.innerText || '';
    
        const $links = document.querySelectorAll(selectors.link);
    
        [...$links].forEach($link => {
          $link.addEventListener('click', onLinkClick);
    
          if ($link.getAttribute('href').indexOf('#') !== 0) { return; }
    
          observeTarget($link);
        });
    
        observeHero();
        observeSelf();
    
        const $items = document.querySelectorAll(selectors.item);
        [...$items].forEach(($item, i) => $item.style.transitionDelay = `${(i + 1) * settings.delay}ms`);
    
        const $targets = document.querySelectorAll(selectors.target);
        const targetArray = [...$targets];
    
        targetArray.sort((a, b) => {
          if (a.id > b.id) {
            return 1;
          }
    
          if (a.id < b.id) {
            return -1;
          }
    
          return 0;
        });
    
        targetArray.forEach(($target) => {
          if (!$target.dataset.navInPage) { console.error('Label for page-in-nav is missing!'); return; }
          if (!$target.id) { console.error(`ID on ${$target} for page-in-nav is missing!`); return; }
    
          addItem($target.id, $target.dataset.navInPage);
        });
    
        document.addEventListener('click', onDocumentClick);
    
        updateMode($wrapper);
    
        canBeSticky && updateTop();
      };
    
      document.addEventListener('DOMContentLoaded', init);
      window.addEventListener('resize', onResize);
    } (window));
    
  • URL: /components/raw/nav-in-page-v1/nav-in-page-v1.js
  • Filesystem Path: ../src/04_organisms/nav-in-page-v1/nav-in-page-v1.js
  • Size: 8.4 KB
  • Content:
    .nav-in-page {
      background: var(--color--neutrals-0);
      padding: 0 var(--gutter--bucket);
      position: sticky;
      top: 0;
      z-index: 998;
    
      &.is-sticky {
        border-bottom: var(--divider);
      }
    
      &.nav-in-page--not-sticky,
      .nav-in-page--not-sticky & {
        position: relative;
        border: none;
      }
    
      &.nav-in-page--background,
      .nav-in-page--background & {
        background: var(--color--neutrals-1);
      }
    }
    
    .nav-in-page:after {
      content: 'mobile';
      display: none;
    }
    
    .nav-in-page:before {
      background: rgba(0,0,0,.2);
      content: '';
      display: block;
      height: 0;
      left: 0;
      opacity: 0;
      position: absolute;
      top: 100%;
      transition: opacity 300ms;
      width: 100%;
    }
    
    .nav--open:before {
      height: 100vh;
      opacity: 1;
    }
    
    .nav-in-page__anchor {
      transform: translateY(calc((var(--header-height) * -1) - 3px));
      position: absolute;
      top: 0;
      left: 0;
      display: block;
      width: 100%;
      height: 1px;
    }
    
    .nav-in-page__inner {
      align-items: center;
      background: var(--color--neutrals-0);
      display: flex;
      flex-flow: row nowrap;
      height: 50px;
      justify-content: space-between;
      position: relative;
      z-index: 10;
      flex: 1 1 auto;
      margin-right: auto;
      margin-left: auto;
      max-width: 1140px;
      width: 100%;
    
      .nav-in-page--not-sticky & {
        border-bottom: var(--divider);
      }
    
      .nav-in-page--background & {
        background: var(--color--neutrals-1);
      }
    }
    
    .nav-in-page__content {
      display: flex;
      flex-flow: column;
    }
    
    .nav-in-page__nav {
      background: var(--color--neutrals-0);
      left: calc(var(--gutter--bucket) * -1);
      right: calc(var(--gutter--bucket) * -1);
      opacity: 0;
      padding-left: var(--gap--3);
      pointer-events: none;
      position: absolute;
      transition: opacity 300ms;
      top: 100%;
    
      .nav-in-page--background & {
        background: var(--color--neutrals-1);
      }
    
      .nav--open & {
        opacity: 1;
        pointer-events: initial;
      }
    }
    
    .nav-in-page__items {
      font-size: var(--p--2);
      margin: 0 !important;
      padding: var(--gap--3) 0 0 0;
      list-style: none;
    }
    
    .nav-in-page__item {
      border-bottom: var(--divider);
      opacity: 0;
      padding: var(--gap--3) var(--gap--3) var(--gap--3) 0;
      transition: all 300ms;
    
      &:last-child {
        border-bottom: none;
      }
    
      .nav--open & {
        opacity: 1;
        padding-left: var(--gap--3);
    
        &.active {
          display: none;
        }
      }
    }
    
    .nav-in-page__link {
      color: inherit;
      padding: var(--gap--1) 0;
      transition: opacity 300ms;
      opacity: .6;
      text-decoration: none;
    
      &:active, &:focus, &:hover, .active & {
        color: inherit;
        opacity: 1;
      }
    
      .nav-in-page--not-sticky & {
        opacity: 1;
      }
    }
    
    .nav-in-page__trigger {
      align-items: center;
      cursor: pointer;
      display: flex;
      flex-flow: row nowrap;
      font-size: var(--h--6);
      font-weight: bold;
    
      .link__icon {
        display: inline-flex;
        color: var(--color--action);
        height: var(--p--2);
        margin-left: var(--gap--2);
        transition: transform 300ms;
        transform: rotate(90deg);
        width: var(--p--2);
    
        .nav--open & {
          transform: rotate(-90deg);
        }
      }
    
      &:active, &:focus, &:hover {
        opacity: 1;
      }
    }
    
    .nav-in-page__actions {
      transition: opacity 250ms;
      opacity: 0;
      pointer-events: none;
      flex-shrink: 0;
    
      .hero-is-out-view & {
        opacity: 1;
        pointer-events: all;
      }
    }
    
    .nav-in-page__action {
      display: flex;
      align-items: center;
    
      & > * {
        flex: 0 1 auto;
        margin-left: var(--gap--4);
    
        &:first-child {
          margin-left: 0;
        }
      }
    
      & > span {
        flex: 0 0 auto;
      }
    }
    
    
    @media screen and (min-width: 768px) {
      .nav-in-page:after {
        content: 'desktop';
      }
    
      .nav-in-page__inner {
        height: 70px;
      }
    
      .nav-in-page__trigger {
        font: inherit;
    
        .link__icon {
          display: none;
        }
      }
    
      .nav-in-page__content {
        flex-flow: row wrap;
        width: 100%;
      }
    
      .nav-in-page__nav {
        display: flex;
        opacity: 1;
        padding-left: 0;
        position: static;
        pointer-events: initial;
        top: 0;
        width: auto;
        background-color: transparent;
      }
    
      .nav-in-page__items {
        display: flex;
        flex-flow: row wrap;
        font-size: inherit;
        padding: 0;
      }
    
      .nav-in-page__item {
        border-bottom: none;
        display: flex !important;
        opacity: 1;
        padding-left: var(--gap--12);
        padding-right: 0;
        padding-bottom: 0;
        padding-top: 0;
        width: auto;
    
        &:first-child {
          padding-left: 0;
        }
      }
    
      .nav-in-page__link {
        display: flex;
        position: relative;
        align-items: center;
        height: 70px;
        padding: 0;
    
        &:active, &:focus, &:hover, .active & {
          opacity: 1;
        }
    
        &:after {
          transition: height 250ms;
          display: block;
          height: 0;
          content: '';
          left: 0;
          position: absolute;
          bottom: 0;
          width: 100%;
          background: var(--color--action);
        }
    
        &:hover, .active & {
          &:after {
            height: 6px;
          }
        }
      }
    }
    
  • URL: /components/raw/nav-in-page-v1/nav-in-page-v1.scss
  • Filesystem Path: ../src/04_organisms/nav-in-page-v1/nav-in-page-v1.scss
  • Size: 4.9 KB

No notes defined.