2
\$\begingroup\$

I would like some opinions on this Mobile Menu plugin I wrote for work. We develop custom WordPress themes, nothing too advanced or anything. I would like if anyone could just take a look and provide any pointers on what I could make better. This is my first actual plugin, so I am open to any critiques.

The functionality is simple, you can define any HTML element to act as a trigger to open a panel that holds the menu. The panel can either fade in, or slide into the viewport from any direction. Menus with submenus reveal their submenus with an accordion type toggle. I only coded the needed CSS to make it work, since the theming of it should be left to the developer to make it fit their project.

A demo is here

HTML (this example uses a button, but anything can be a trigger):

<button class="cm-hamburger">
    <span></span>
    <span></span>
    <span></span>
</button>

<nav id="panel" class="cm-panel">
    <ul>
        <li>
            <a href="#">Link</a>
            <ul>
                <li><a href="#">Sub Link</a></li>
                <li><a href="#">Sub Link</a></li>
                <li><a href="#">Sub Link</a></li>
            </ul>
        </li>
        <li><a href="#">Link</a></li>
        <li>
            <a href="#">Link</a>
            <ul>
                <li><a href="#">Sub Link</a></li>
                <li><a href="#">Sub Link</a></li>
                <li><a href="#">Sub Link</a></li>
            </ul>
        </li>
        <li><a href="#">Link</a></li>
    </ul>
</nav>

Calling with jQuery:

$( function() {
    $( '#panel' ).clearmenu( {
        hasSubMenu: true,
    } );
} );

CSS (Sass):

// Colors
// -----------------------------------------------------------------------------
$cm_color-white: rgb(255, 255, 255) !default;
$cm_color-black: rgb(0, 0, 0) !default;
$cm_color-panel: rgb(239, 239, 239) !default;

// Panel Settings
// -----------------------------------------------------------------------------
$cm_panel-width: 100% !default;
$cm_panel-height: 100% !default;
$cm_panel-position: absolute !default;
$cm_panel-top: 0 !default;
$cm_panel-speed: 0.25s !default;
$cm_panel-transition: ease-in-out !default;

/* -------------------------------------------------------------------------------------------------
  # Custom Normalize
------------------------------------------------------------------------------------------------- */
menu,
nav {
    display: block;
}

a {
    background-color: transparent;
    -webkit-text-decoration-skip: objects;

    &:active,
    &:hover {
        outline-width: 0;
    }
}

button {
    font: inherit;
    margin: 0;
    overflow: visible;
    text-transform: none;
    -webkit-appearance: button;

    &::-moz-focus-inner {
        border-style: none;
        padding: 0;
    }

    &:-moz-focusring {
        outline: 1px dotted ButtonText;
    }
}

/* -------------------------------------------------------------------------------------------------
  # Panel
------------------------------------------------------------------------------------------------- */
.cm-panel {
    position: $cm_panel-position;
    top: $cm_panel-top;
    left: 0;
    z-index: 99;

    width: $cm_panel-width;
    max-width: 100%;
    height: $cm_panel-height;
    max-height: 100%;

    background: $cm_color-panel;
    opacity: 1;
    visibility: hidden;
    transition: all $cm_panel-speed $cm_panel-transition;

    -webkit-transform: translateZ(0);
    -webkit-backface-visibility: hidden;

    // Reveal from the top
    &-top {
        transform: translateY(-100%);
    }

    // Reveal from the bottom
    &-bottom {
        transform: translateY(100%);
        top: unset;
        bottom: 0;
    }

    // Reveal from the left
    &-left {
        transform: translateX(-100%);
    }

    // Reveal from the right
    &-right {
        transform: translateX(100%);
        left: unset;
        right: 0;
    }

    // Fade in reveal
    &-fade {
        opacity: 0;
    }

    // For when the panel is visible
    &-open {
        visibility: visible;
        transform: translateY(0);
        opacity: 1;
    }
}

/* -------------------------------------------------------------------------------------------------
  # Menu(s)
------------------------------------------------------------------------------------------------- */
.cm-panel {
    > ul {
        margin: 0 auto;
        padding: 0;
        position: relative;
        width: 90%;
        display: block;
        list-style: none;

        li {
            position: relative;

            > ul {
                display: none;
                margin: 0;
                padding: 0;
                position: relative;

                a {
                    position: relative;
                    font-size: 1rem;
                    padding: 0.5em 2em;
                }
            }
        }

        a {
            position: relative;
            display: block;
            padding: 0.25em 0;
            overflow: hidden;
            text-decoration: none;
        }
    }
}

/* -------------------------------------------------------------------------------------------------
  # Hamburger
------------------------------------------------------------------------------------------------- */
.cm-hamburger {
    position: relative;
    border: none;
    width: 35px;
    height: 25px;
    transform: rotate(0deg);
    transition: all $cm_panel-speed $cm_panel-transition;
    cursor: pointer;
    background: $cm_color-white;

    span {
        display: block;
        position: absolute;
        height: 5px;
        width: 100%;
        background: $cm_color-black;
        border-radius: 9px;
        opacity: 1;
        left: 0;
        transform: rotate(0deg);
        transition: all $cm_panel-speed $cm_panel-transition;

        &:nth-child(1) {
            top: 0;
            transform-origin: left center;
        }

        &:nth-child(2) {
            top: 10px;
            transform-origin: left center;
        }

        &:nth-child(3) {
            top: 20px;
            transform-origin: left center;
        }
    }

    &-open {
        span:nth-child(1) {
            transform: rotate(45deg);
            top: -3px;
            left: 5px;
        }

        span:nth-child(2) {
            width: 0%;
            opacity: 0;
        }

        span:nth-child(3) {
            transform: rotate(-45deg);
            top: 22px;
            left: 5px;
        }
    }
}

/* -------------------------------------------------------------------------------------------------
  # Icons
------------------------------------------------------------------------------------------------- */
.icon {
    display: inline-block;
    position: absolute;
    top: 0;
    right: 0;
    font-style: normal;
    color: $cm_color-black;
    text-align: left;
    text-indent: -9999px;
    direction: ltr;

    &::before,
    &::after {
        content: "";
        pointer-events: none;
    }

    &-caret {
        width: 20%;
        height: 20px;
        margin: 3px 10px;

        &::before,
        &::after {
            width: 7px;
            height: 1px;
            position: absolute;
            top: 0;
            bottom: 0;
            margin: auto 0;
            right: 2px;
            box-shadow: inset 0 0 0 32px;
            transform-origin: right;
        }

        &::before {
            transform: rotate(45deg);
        }

        &::after {
            transform: rotate(-45deg);
        }
    }

    &-close {
        width: 30px;
        height: 30px;
        margin: 2px;
        transform: rotate(45deg);

        &::before,
        &::after {
            position: absolute;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%);
            box-shadow: inset 0 0 0 32px;
        }

        &::before {
            width: 20px;
            height: 2px;
        }

        &::after {
            height: 20px;
            width: 2px;
        }
    }
}

JS (compiled from CoffeeScript):

'use strict';
(function($) {
  $.fn.clearmenu = function(options) {
    var animationSpeed, classes, revealMenu, setUpClearMenu, settings, toggleSubMenu;
    classes = void 0;
    settings = void 0;
    setUpClearMenu = void 0;
    animationSpeed = void 0;
    toggleSubMenu = void 0;
    revealMenu = void 0;

    /**
     * Establish the default CSS classes to use.
     */
    classes = {
      subMenu: 'cm-submenu',
      top: 'cm-panel-top',
      bottom: 'cm-panel-bottom',
      left: 'cm-panel-left',
      right: 'cm-panel-right',
      fade: 'cm-panel-fade',
      visible: 'cm-panel-open'
    };

    /**
     * Establish the default plug-in settings.
     */
    settings = $.extend({
      panel: this,
      close: true,
      trigger: '.cm-hamburger',
      reveal: 'fade',
      hasSubMenu: true,
      subMenu: '.cm-submenu',
      wordpress: false
    }, options);

    setUpClearMenu = function() {
      /**
       * Determine how to reveal the target panel.
       */
      switch (settings.reveal) {
        case 'bottom':
          $(settings.panel).addClass(classes.bottom);
          break;
        case 'left':
          $(settings.panel).addClass(classes.left);
          break;
        case 'right':
          $(settings.panel).addClass(classes.right);
          break;
        case 'fade':
          $(settings.panel).addClass(classes.fade);
          break;
        default:
          $(settings.panel).addClass(classes.top);
          break;
      }

      /**
       * Establish functionality if sub-menus are active.
       */
      if (settings.hasSubMenu) {
        animationSpeed = 200;
        if (!settings.wordpress) {
          toggleSubMenu = function() {
            $(this).parent().find('ul').toggleClass('cm-submenu-open').slideToggle(animationSpeed);
          };
          $(settings.panel).find('> ul ul').addClass(classes.subMenu);
          $(settings.subMenu).parent().append('<i class="icon icon-caret"></i>');
          $(settings.subMenu).next().on('click.clearmenu', toggleSubMenu);
        }
        if (settings.wordpress) {
          $(settings.panel).find('.menu-item-has-children').append('<i class="icon icon-caret"></i>');
          $('.menu-item-has-children').on('click.clearmenu', 'i', function() {
            $(this).prev('.sub-menu').slideToggle(animationSpeed);
          });
        }
      }

      /**
       * Set up the close button for the panel.
       */
      if (settings.close) {
        $(settings.panel).prepend('<i class="icon icon-close"></i>');
        $('.icon-close').on('click', function() {
          $(settings.panel).removeClass(classes.visible);
          if (settings.trigger == '.cm-hamburger') {
            $(settings.trigger).removeClass('cm-hamburger-open');
          }
        });
      }
    };

    /**
     * Function to reveal the target panel that contains the menu.
     */
    revealMenu = function() {
      if (settings.trigger == '.cm-hamburger') {
        $(this).toggleClass('cm-hamburger-open');
      }
      $(settings.panel).toggleClass(classes.visible);
    };
    $(settings.trigger).on('click.clearmenu', revealMenu);
    setUpClearMenu();
  };
})(jQuery);

I am not trying to make this a solution to fit every need, and I probably won't try to release it to a plugin repo or anything like that. It is really just for projects, both personal and at work.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.