Vertical menu collapsible v3

by Plippie Plop

HTML

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<div id="page-container">
  <div id="page-header"><button type="button" class="btn toggle-side-menu" data-menu="#side-menu-left"><i class="fa fa-bars"></i></button>
    <nav class="header-menu">
      <ul>
        <li><a href="#">The first menu item</a></li>
        <li><a href="#">Menu 2</a></li>
        <li><a href="#">Menu 3</a></li>
      </ul>
    </nav>
    <nav class="header-menu">
      <ul>
        <li><a href="#">Menu 4</a></li>
        <li><a href="#">Menu 5</a></li>
        <li><a href="#">Menu 6</a></li>
        <li class="more hidden" data-width="80"></li>
      </ul>
    </nav>
  </div>

  <div id="main-container">
    <div id="side-menu-left" class="has-footer">
      <header>
        <h3>Header</h3>
      </header>
      <div class="side-content">
        <nav class="collapsible-menu">
          <ul class="menu">
            <li class="first-menu"><a>The top menu item has a very long text</a></li>
            <li class="first-menu"><a>Item 2</a>
              <ul>
                <li><a>item 2 sub 1</a></li>
                <li><a>Item 2 sub 2</a></li>
              </ul>
            </li>
            <li class="first-menu"><a>Item 3</a>
              <ul>
                <li><a>item 3 sub 1</a></li>
                <li><a>Item 3 sub 2</a>
                  <ul>
                    <li><a>Item 2 sub-sub 1</a></li>
                    <li><a>Item 2 sub-sub 2</a></li>
                  </ul>
                </li>
                <li><a>Item 3 sub 3</a>
                <li><a>Item 3 sub 4</a>
                <li><a>Item 3 sub 5</a>
                <li><a>Item 3 sub 6</a>
              </ul>
            </li>
            <li class="first-menu"><a>Item 4</a>
              <ul>
                <li><a>item 4 sub 1</a></li>
                <li><a>Item 4 sub 2</a>
                  <ul>
                    <li><a>Item 4 sub-sub 1</a></li>
        ...

CSS

::-webkit-scrollbar {
  width: 0.5rem;
}

::-webkit-track {
  background: transparent;
}

::-webkit-scrollbar-thumb {
  background: rgba(0, 0, 0, 0.4);
  border-radius: 0.25rem;
}

::-webkit-scrollbar-thumb:hover {
  background: rgba(0, 0, 0, 0.8);
}

:root {
  --page-header-height:54px;
  --menu-bg: rgb(250, 250, 250);
  --menu-item-bg: rgb(255, 255, 255);
  --menu-item-hover-bg: rgb(242, 242, 245);
  --menu-item-active-bg: rgb(124 192 251);
  --menu-item-active-hover-bg: rgb(171 214 251);
  --menu-color: rgb(20, 20, 20);
  --menu-active-color: rgb(255, 255, 255);
  --menu-border-radius: 0;
  --menu-sub-height: 30em;
  --w-header-bg: rgb(52, 121, 180);
  --transition-01: cubic-bezier(0.4, 0.1, 0.7, 0.95);
  --transition-02: cubic-bezier(0.1, 0.7, 0.6, 0.9);

  /* using font awesome 4.7 for chevron icons */
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html,
body,
body>header {
  height: 100%;
  margin: 0;
  padding: 0;
}

/* respect viewport height */
body {
  font-family: system-ui, Sans-Serif;

}

ul,
li {
  list-style: none;
  position: relative;
  outline: none;
}


#page-container {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: 0fr 1fr;
  background-color: rgb(255, 255, 255);
  height: 100%;
}

#page-header {
  display: grid;
  grid-template-columns:50px 1fr 1fr; 
  background-color: var(--w-header-bg);
}

#main-container {
  display: grid;
  min-height: 0;
}

.toggle-side-menu {
  cursor: pointer;
  background-color: rgba(0, 0, 0, 0.2);
  color: white;
  border: 0;
  outline: 0;
  padding: 0.5rem 1.2rem;
}

.toggle-side-menu:hover {
  background-color: rgba(255, 255, 255, 0.1);
}

.header-menu,
.header-menu ul {
  display: flex;
  align-items: stretch;
}

.header-menu li {
  position: relative;
}

.header-menu li a {
  padding: 1rem 0.8rem;
  text-decoration: none;
  display: flex;
  align-items: stretch;
  color: white;
  white-space: nowrap;
}

.header-menu li a:hover {
  background-color: rgba(0, 0, 0,...

JavaScript

class CollapsibleMenu {
  constructor(menuContainer, options = {
    exclusiveActive: true
  }) {
    this.menu = menuContainer;
    this.menuItems = this.menu ? this.menu.querySelectorAll('li') : [];
    this.exclusiveActive = options.exclusiveActive;

    if (!this.menu || !this.menuItems.length) {
      console.warn("No menu or list items found");
      return;
    }

    this.init();
  }

  init() {
    // Check for submenus
    this.menuItems.forEach(item => {
      const submenu = item.querySelector('ul');
      if (submenu) {
        item.classList.add('has-sub');
      }
    });

    // Bind click event using event delegation
    this.menu.addEventListener('click', (e) => {
      const clickedElement = e.target;

      // Check if the clicked element is an anchor tag inside an li
      if (clickedElement.tagName === 'A' && clickedElement.closest('li')) {
        e.preventDefault();
        const menuItem = clickedElement.closest('li');

        // Toggle 'active' class for the clicked menu item
        menuItem.classList.toggle('active');



        // Toggle 'active' class for parent menu items
        if (this.exclusiveActive) {
          this.menuItems.forEach(item => {
            const isAncestor = item.contains(menuItem);
            if (item !== menuItem && !isAncestor) {
              item.classList.remove('active');
            }

          });
        } else {
          // If exclusiveActive is false, remove 'active' class from siblings
          const siblings = menuItem.parentElement.children;
          for (const sibling of siblings) {
            if (sibling !== menuItem) {
              sibling.classList.remove('active');

            }
          }
        }
      }
    });
  }
}






document.addEventListener('DOMContentLoaded', function() {

  const sideMenu = document.querySelector(".collapsible-menu");

  // If the element exists, instantiate MyClass and bind it to the element
  if (sideMenu) {
    const menuClass = new...