<div class="modal-wrapper js-modal" id="default">
    <div class="modal-overlay js-modal-overlay"></div>
    <div class="modal modal--default">
        <div class="modal__inner">
            <button class="modal__close js-modal-close-button">
        <svg class="icon icon--close-large">
  <use xlink:href="/assets/icons/icons.svg#close-large"></use>
</svg>

      </button>
            <span class="modal__title js-modal-title">Lorem Ipsum Dolor</span>
            <div class="modal__content">
                <p class="modal__description js-modal-description">Etiam vel quam quis tellus sodales imperdiet nec vitae mauris.</p>
            </div>
        </div>
    </div>
</div>
<div class="modal-wrapper js-modal" id="{{id}}">
  <div class="modal-overlay js-modal-overlay"></div>
  <div class="modal modal--{{id}}{{#each classes}} {{this}}{{/each}}">
    <div class="modal__inner">
      <button class="modal__close js-modal-close-button">
        {{render '@icons--close-large'}}
      </button>
      {{#if title}}
      <span class="modal__title js-modal-title">{{title}}</span>
      {{/if}}
      <div class="modal__content">
        {{#if description}}
        <p class="modal__description js-modal-description">{{description}}</p>
        {{/if}}
      </div>
    </div>
  </div>
</div>
{
  "modal": true,
  "id": "default",
  "title": "Lorem Ipsum Dolor",
  "description": "Etiam vel quam quis tellus sodales imperdiet nec vitae mauris."
}
  • Content:
    (function (window) {
      'use strict';
    
      const modalIdAttribute = 'data-modal-id';
      const modalTitleAttribute = 'data-modal-title';
      const modalDescriptionAttribute = 'data-modal-description';
      const modalVideoAttribute = 'data-modal-video';
      const modalShowClass = 'js-modal-show';
      const modalShowBodyClass = 'js-modal-open';
      let previousScrollTop = null;
    
      // Events are emitted for modal actions so that implementation JS can react
      // as needed. An example would be removing form validation errors that were
      // added by the implementation after an unsuccessful submission.
      function emitModalEvent(modalId, action) {
        componentEvents.emitEvent('modal-change', [{
          id: modalId,
          action: action
        }]);
      }
    
      // Allows modals to be open with the `'open-modal'` event and id argument.
      function handleOpenModalEvent(args) {
        if (args.id) {
          openModal(args.id);
        }
      }
    
      // Allows modals to be closed with the `'close-modal'` event and id argument.
      function handleCloseModalEvent(args) {
        if (args.id) {
          closeModal(args.id, args.formReset);
        }
      }
    
      function openModal(modalId, modalTitle, modalDescription, modalVideo) {
        const modal = document.getElementById(modalId);
    
        if (modal) {
          const form = modal.querySelector('.modal-form');
          const video = modal.querySelector('.js-modal-video');
    
          // iOS requires `body { position: fixed }` to prevent background scrolling
          // while the modal is open, which forces the page to jump to the top. The
          // following counteracts that jump.
          previousScrollTop = (document.documentElement.scrollTop || document.body.scrollTop);
          document.body.style.top = (-1 * previousScrollTop) + 'px';
    
          if (modalTitle) {
            modal.querySelector('.js-modal-title').innerText = modalTitle;
          }
    
          if (modalDescription) {
            modal.querySelector('.js-modal-description').innerText = modalDescription;
          }
    
          // Set focus on the first input and keep tabbing focused within the modal.
          if (form) {
            const firstInput = findFirstInput(form);
            const lastInput = findLastInput(form);
    
            // setTimeout needed for this to work in Chrome.
            window.setTimeout(function () {
              firstInput.focus();
            }, 5);
    
            firstInput.addEventListener('keydown', handleFirstInputKeydown);
            lastInput.addEventListener('keydown', handleLastInputKeydown);
          };
    
          // Prevents unnecessary downloads by only creating the video at all when
          // the modal is opened.
          if (video) {
            const videoId = modalVideo || video.getAttribute('data-video-id');
            const youtubeScriptId = 'youtube-api';
            const youtubeScript = document.getElementById(youtubeScriptId);
    
            if (youtubeScript === null) {
              const tag = document.createElement('script');
              const firstScript = document.getElementsByTagName('script')[0];
    
              tag.src = 'https://www.youtube.com/iframe_api';
              tag.id = youtubeScriptId;
              firstScript.parentNode.insertBefore(tag, firstScript);
            } else if (typeof window.player !== 'undefined') {
              window.player.loadVideoById(videoId)
            }
    
            window.onYouTubeIframeAPIReady = function() {
              window.player = new window.YT.Player(video, {
                videoId: videoId,
                playerVars: {
                  autoplay: 1,
                  modestbranding: 1,
                  rel: 0
                }
              });
            }
          }
    
          document.body.classList.add(modalShowBodyClass);
          modal.classList.add(modalShowClass);
    
          emitModalEvent(modalId, 'open');
        }
      }
    
      function closeModal(modalId, formReset = true) {
        const modal = document.getElementById(modalId);
    
        document.body.classList.remove(modalShowBodyClass);
        document.getElementById(modalId).classList.remove(modalShowClass);
    
        // Return the body to its state before the modal was opened.
        document.body.style.top = '';
        document.documentElement.scrollTop = document.body.scrollTop = previousScrollTop;
    
        if (modal) {
          const form = modal.querySelector('.js-modal-form');
          const video = modal.querySelector('.js-modal-video');
    
          // Add this class to modal forms that shouldn't be reset upon close.
          if (form && form.classList.contains('js-modal-form-no-reset')) {
            formReset = false;
          }
    
          if (form && formReset) {
            const selectItems = form.querySelectorAll('select');
            const firstInput = findFirstInput(form);
            const lastInput = findLastInput(form);
    
            form.reset();
    
            // Trigger JS that sets the color when the empty option is selected.
            for (let i = 0; i < selectItems.length; i++) {
              triggerNativeEvent(selectItems[i], 'change');
            }
    
            firstInput.removeEventListener('keydown', handleFirstInputKeydown);
            lastInput.removeEventListener('keydown', handleLastInputKeydown);
          }
    
          if (video) {
            window.player.pauseVideo();
          }
    
          emitModalEvent(modalId, 'close');
        }
      }
    
      function handleOpenModal(e) {
        const modalLink = e.target;
        const modalId = modalLink.getAttribute(modalIdAttribute);
        const modalTitle = modalLink.getAttribute(modalTitleAttribute);
        const modalDescription = modalLink.getAttribute(modalDescriptionAttribute);
        const modalVideo = modalLink.getAttribute(modalVideoAttribute);
    
        e.preventDefault();
    
        // Modal open links can dynamically alter the title and description of a
        // modal by having values for the `data-modal-title` and
        // `data-modal-description` attributes. Video modals can also be passed a
        // video URL with `data-modal-video`.
        openModal(modalId, modalTitle, modalDescription, modalVideo);
      }
    
      function handleCloseModal(e) {
        e.preventDefault();
    
        closeModal(e.target.closest('.js-modal').getAttribute('id'));
      }
    
      function handleModalClick(e) {
        // Closes the modal if the overlay was clicked. The overlay element itself
        // has `pointer-events: none` assigned to it to fix scrolling on small
        // screens, so the event has to be added to the modal itself.
        if (e.target.classList.contains('js-modal')) {
          closeModal(e.target.getAttribute('id'));
        }
      }
    
      function findFirstInput(form) {
        const inputs = form.querySelectorAll('select, input, textarea, button');
    
        for (let i = 0; i < inputs.length; i++) {
          if (!isHidden(inputs[i])) {
            return inputs[i];
          }
        }
      }
    
      function findLastInput(form) {
        const inputs = form.querySelectorAll('select, input, textarea, button');
        let lastInput = null;
    
        for (let i = 0; i < inputs.length; i++) {
          if (!isHidden(inputs[i])) {
            lastInput = inputs[i];
          }
        }
        return lastInput;
      }
    
      // Redirect first shift+tab to last input.
      function handleFirstInputKeydown(e) {
        if ((e.which === 9 && e.shiftKey)) {
          const form = e.target.closest('form');
          const lastInput = findLastInput(form);
    
          e.preventDefault();
          lastInput.focus();
        }
      }
    
      // Redirect last tab to first input.
      function handleLastInputKeydown(e) {
        if ((e.which === 9 && !e.shiftKey)) {
          const form = e.target.closest('form');
          const firstInput = findFirstInput(form);
    
          e.preventDefault();
          firstInput.focus();
        }
      }
    
      function init() {
        const modals = document.querySelectorAll('.js-modal');
        const modalOpenLinks = document.querySelectorAll('.js-modal-open-link');
    
        for (let i = 0; i < modals.length; i++) {
          if (!modals[i].classList.contains('js-component-init')) {
            const closeButtons = modals[i].querySelectorAll('.js-modal-close-button');
    
            modals[i].addEventListener('click', handleModalClick);
    
            for (let i = 0; i < closeButtons.length; i++) {
              closeButtons[i].addEventListener('click', handleCloseModal);
            }
    
            // Open modals when previewed individually in Fractal.
            if (document.body.classList.contains('js-modal-preview')) {
              openModal(modals[i].getAttribute('id'));
            }
    
            modals[i].classList.add('js-component-init');
          }
        }
    
        for (let i = 0; i < modalOpenLinks.length; i++) {
          if (!modalOpenLinks[i].classList.contains('js-component-init')) {
            modalOpenLinks[i].addEventListener('click', handleOpenModal);
            modalOpenLinks[i].classList.add('js-component-init');
    
            // Remove disabled class after component init if present.
            if (modalOpenLinks[i].classList.contains('js-button-disabled')) {
              modalOpenLinks[i].classList.remove('js-button-disabled');
            }
          }
        }
      }
    
      // Allow application JS to reinitialize any instances added with Ajax, etc.
      if (typeof componentEvents !== 'undefined') {
        componentEvents.on('component-init', init);
        componentEvents.on('open-modal', handleOpenModalEvent);
        componentEvents.on('close-modal', handleCloseModalEvent);
      }
    
      init();
    
    })(this);
    
  • URL: /components/raw/default-modal/default-modal.js
  • Filesystem Path: src/components/01-elements/modals/default-modal/default-modal.js
  • Size: 9 KB
  • Content:
    .js-modal-open {
      position: fixed;
      width: 100%;
      height: 100%;
      overflow-x: hidden;
      overflow-y: scroll;
    }
    
    .modal-wrapper {
      display: none;
      position: fixed;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      width: 100%;
      height: 100%;
      z-index: 50;
    
      &.js-modal-show {
        display: flex;
        flex: 1;
        flex-direction: column;
        box-sizing: border-box;
        overflow-y: auto;
    
        &::before,
        &::after {
          flex: 0 0 30px;
          content: '';
        }
      }
    }
    
    .modal-overlay {
      position: fixed;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      height: 100%;
      background-color: $color-black;
      content: '';
      opacity: .5;
      z-index: 51;
      pointer-events: none;
    }
    
    .modal {
      position: fixed;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      z-index: 52;
    
      @include breakpoint($breakpoint-md) {
        display: block;
        position: relative;
        margin-top: auto;
        margin-bottom: auto;
        pointer-events: none;
      }
    }
    
    .modal__inner {
      @include clearfix;
      height: 100%;
      background-color: $color-white;
    
      @include breakpoint($breakpoint-md) {
        position: relative;
        width: calc(100% - 40px);
        max-width: 940px;
        height: auto;
        margin: 0 auto;
        padding-bottom: 20px;
        box-sizing: border-box;
        pointer-events: auto;
      }
    }
    
    // Mimic non-mobile modal styles for mobile modals with this class.
    .modal--mobile-overlay {
      @include breakpoint($breakpoint-sm-only) {
        & {
          display: table-cell;
          position: relative;
          vertical-align: middle;
          pointer-events: none;
        }
    
        .modal__inner {
          position: relative;
          width: calc(100% - 20px);
          height: auto;
          margin: 0 auto;
          padding-bottom: 20px;
          box-sizing: border-box;
          pointer-events: auto;
        }
    
        .modal__title {
          box-shadow: none;
        }
    
        .modal__description {
          margin-top: 15px;
        }
      }
    }
    
    .modal__close {
      @include reset-button;
      position: absolute;
      top: 0;
      right: 0;
      width: 50px;
      height: 50px;
      z-index: 1;
    
      .icon {
        width: 13px;
        height: 13px;
        pointer-events: none;
      }
    
      @include breakpoint($breakpoint-md) {
        .icon {
          width: 16px;
          height: 16px;
          margin-top: 7px;
          margin-right: 5px;
        }
      }
    }
    
    .modal__alternate-close {
      @include reset-button;
      margin-bottom: 5px;
      color: $color-error;
      font-size: 1.4rem;
      font-weight: $font-weight-bold;
      letter-spacing: 2px;
      text-transform: uppercase;
    
      @include breakpoint($breakpoint-md) {
        margin-bottom: 25px;
      }
    
      @include breakpoint($breakpoint-xl) {
        margin-bottom: 15px;
      }
    }
    
    .modal__title {
      display: block;
      color: $color-darkest;
      font-size: 1.6rem;
      line-height: 5.0rem;
      text-align: center;
      box-shadow: 0 0 10px rgba(0, 0, 0, .1);
    
      @include breakpoint($breakpoint-md) {
        box-shadow: none;
      }
    
      @include breakpoint($breakpoint-xl) {
        padding: 10px 0;
        font-size: 2.4rem;
        line-height: 4.0rem;
      }
    }
    
    .modal__content {
      padding: 10px;
      box-sizing: border-box;
    
      @include breakpoint($breakpoint-sm-only) {
        max-height: calc(100% - 50px);
        overflow-y: scroll;
      }
    
      @include breakpoint($breakpoint-md) {
        padding: 10px 12.8%;
      }
    }
    
    .modal__description {
      @include text-label($color-dark);
      margin-top: 8px;
      margin-bottom: 18px;
      line-height: 1.7rem;
    
      @include breakpoint($breakpoint-md) {
        margin-top: 0;
        margin-bottom: 30px;
      }
    
      @include breakpoint($breakpoint-xl) {
        font-size: 1.9rem;
        line-height: 2.3rem;
      }
    }
    
  • URL: /components/raw/default-modal/default-modal.scss
  • Filesystem Path: src/components/01-elements/modals/default-modal/default-modal.scss
  • Size: 3.5 KB

There are no notes for this item.