header
  h1 Universal Popouts
  nav
    button.nav--trigger.popout--trigger(data-popout='nav-wrapper') Nav
    .popout.popout--flex.popout--static#nav-wrapper
      button.nav--close.popout--trigger(data-popout='nav-wrapper') Close
      ul.nav-menu
        li
          a.popout--trigger(href='#' data-popout='subnav-1') Nav 1
          ul.popout#subnav-1
            li
              a(href='#') Nav 1-1
            li
              a(href='#') Nav 1-2
            li
              a(href='#') Nav 1-3
        li
          a.popout--trigger(href='#' data-popout='subnav-2') Nav 2
          ul.popout#subnav-2
            li
              a(href='#') Nav 2-1
            li
              a(href='#') Nav 2-2
            li
              a(href='#') Nav 2-3
        li
          a(href='#') Nav 3
article
  h2 An example of a universal popout module for JS

  ul
    li Each of the poppable elements has a class of .popout and a unique ID
    li Each popout trigger has a class of .popout--trigger has a data-popout attribute corresponding to the popout it controls 
    li A popout will be visible with display: block, unless it has a class of .popout--flex, when it will be display: flex
    li In this example we're using the same script to control: 
      ul 
        li Menu dropdowns 
        li A modal 
        li A mobile menu - this triggers at 768px
    li The mobile menu has a class of .popout--static; this prevents it from being closed when ALL popout elements are shut and is ONLY closed by one of its triggers. This is really only ideal for things like mobile menus where you may want to contain popouts within popouts

  p
    button.popout--trigger(data-popout="modal-1") View modal
  .modal.popout#modal-1
    p Some content in a modal
    button.close--modal.popout--trigger(data-popout="modal-1") Close

  h2 Some things I want to do with this: 
  ul 
    li Add in some accessibility controls to allow popouts to be closed anywhere outside of the element, on an esc and also to move the focus into the popout when active 
    li Add and remove active/inactive identifying classes on the triggers - this would allow them to be styled to show state
    li Test the script with some css transitions to allow elements to animate when they become visible,  i.e. mobile menus sliding in, modals fading up
  p If you want to play around with this in your own code, grab the popout JS and CSS
  p A Node.JS project of this code is available on GitHub - 
   a(href="https://github.com/abeeken/universal_popout") https://github.com/abeeken/universal_popout
footer
  p Built by Andrew Beeken
View Compiled
// Breakpoints
$sm: 480px;
$md: 768px;
$lg: 1024px;
$xl: 1200px;

body{
    font-family: Arial, Helvetica, sans-serif;
    margin: 0;
    position: relative;
}

header, article, footer{
    max-width: $xl;
    margin: 0 auto;
    padding: 10px;
}

// Nav styles
nav{
    .nav--trigger{
        display: none;
        @media screen and (max-width: $md) {
            display: block;
        }
    }
    .nav--close{
        display: none;
        @media screen and (max-width: $md) {
            display: block;
            position: absolute;
            right: 20px;
            top: 20px;
        }
    }
    #nav-wrapper{
        @media screen and (min-width: $md) {
            display: block;
        }
        @media screen and (max-width: $md) {
            align-items: center;
            justify-content: center;
            position: fixed;
            left: 0;
            top: 0;
            width: 100vw;
            height: 100vh;
            background: #ccc;
            z-index: 50;
            overflow-x: scroll;
        }
    }
    ul{
        display: flex;
        padding: 0;
        list-style: none;
        flex-direction: row;
        @media screen and (max-width: $md) {
            flex-direction: column;
        }
        li{
            position: relative;
            a{
                display: block;
                padding: 5px 10px;
                color: black;
                text-decoration: none;
                &:hover{
                    background: #ccc;
                }
            }
            @media screen and (min-width: $md) {
                ul{
                    position: absolute;
                    background: #ccc;
                    top: 100%;
                    width: 100%;
                    li{
                        display: block;
                        position: unset;
                    }
                }
            }
        }
    }
}

// Modal styles
.modal{
    position: absolute;
    background: #ccc;
    padding: 20px;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    margin-top: 50px;
    border-radius: 10px;
    .close--modal{
        position: absolute;
        right: 10px;
        top: 10px;
    }
}

// Popout styles
.popout{
    display: none;
    &.popout--visible{
        display: block;
        &.popout--flex{
            display: flex;
        }
    }
}
View Compiled
// JS to control universal popouts

// Get all the popout and popout--trigger elements on the page
const popouts = document.querySelectorAll('.popout');
const popoutTriggers = document.querySelectorAll('.popout--trigger');

// Watch for a click on a .popout--trigger element
popoutTriggers.forEach(el => el.addEventListener('click', event => {
    var popout = document.querySelector('#' + event.target.dataset.popout);
    // Get the target popout and either make it visible or close it if it's already open
    if( popout.classList.contains('popout--visible') ){
        popout.classList.remove('popout--visible');
    } else {
        // Check whether the popout is static, i.e it can only be closed by one of its own triggers
        if( popout.classList.contains('popout--static') ){
            closePopouts(true);
        } else {
            closePopouts(false);
        }
        popout.classList.add('popout--visible');
    }
}));

function closePopouts(closeStatic){
    // Hide any open popouts
    // Only close static popouts if closeStatic is true, i.e. it's been called by its own trigger
    popouts.forEach(popout => {
        if( popout.classList.contains('popout--static') && closeStatic ){
            popout.classList.remove('popout--visible');
        } else if( !popout.classList.contains('popout--static') ) {
            popout.classList.remove('popout--visible');
        }
    });
}
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.