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\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.