javascripthtmljquerycssreactjs

Dropdown Button in HTML/CSS/JS


I have the code:

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=PT+Sans+Narrow:wght@400;700&display=swap" rel="stylesheet">

<style>
  .wrapper {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-gap: 10px;
  }
  
  .tilecontainer {
    padding: 5px;
    font-size: 25px;
    width: 100%;
  }
  
  @media screen and (max-width: 768px) {
    .tilecontainer {
      font-size: 16px;
    }
  }
  
  .box {
    background-color: #0051a5;
    padding-right: 1em;
    padding-top: 1em;
    border-radius: 1em;
    display: grid;
    grid-template-rows: 1fr auto;
    grid-template-columns: auto 1em;
    grid-template-areas: "sample plus" "extratext extratext";
  }
  
  .plus {
    grid-area: plus;
    background: linear-gradient(#0051a5 0 0), linear-gradient(#0051a5 0 0), #fff;
    background-position: center;
    background-size: 60% 2.5px, 2.5px 60%;
    background-repeat: no-repeat;
    transition: transform 0.3s ease;
  }
  
  .sign {
    border-radius: 50%;
    width: 1em;
    height: 1em;
    margin-right: 1em;
  }
  
  .tilelabel {
    grid-area: sample;
    font-family: 'PT Sans Narrow', sans-serif;
    font-size: 1em;
    text-transform: uppercase;
    cursor: pointer;
    font-weight: 600;
    color: #fff;
    padding-left: 1em;
    height: 2em;
  }
  
  .tileaccent {
    color: #FFC72C;
  }
  
  .hidden-text {
    grid-area: extratext;
    display: none;
    font-family: 'PT Sans Narrow', sans-serif;
    font-size: 0.75em;
    background-color: #F5F5F4;
    color: #000;
    margin: 1em;
    padding-top: 0.5em;
    padding-left: 1em;
    border-radius: 1em;
  }
  
  .expanded>.hidden-text {
    display: block;
    animation: fade-in 1s;
  }
  
  @keyframes fade-in {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }
  
  @keyframes fade-out {
    from {
      opacity: 1;
    }
    to {
      opacity: 0;
    }
  }
  
  .hidden-text ul li ul {
    display: none;
  }
  
  .hidden-text ul li.active ul {
    display: block;
  }
  
  li .plus {
    transform: rotate(0);
  }
  
  li.active .plus {
    transform: rotate(45deg);
  }
  /* Changes the cursor to a pointer for the list items that have a child ul */
  
  .hidden-text ul li:has(ul) .plus {
    float: right;
  }
  
  .hidden-text ul li:has(ul) {
    list-style: none;
    /* Remove the default bullet point */
  }
  
  .hidden-text ul li:has(ul) {
    cursor: pointer;
  }
  
  .hidden-text ul {
    list-style: none;
    /* Remove the default bullet point for ul elements */
  }
  
  .hidden-text ul li {
    list-style: none;
  }
</style>

<div class="wrapper">
  <div class="tilecontainer">
    <div class="box">
      <div class="plus sign"></div>
      <div class="tilelabel">Sample<span class="tileaccent"> Text</span></div>
      <div class="hidden-text">
        <ul>
          <li>Sample
            <div class="plus sign"></div>
            <ul>
              <li>Sample
                <div class="plus sign"></div>
                <ul>
                  <a href="">Text</a>
                </ul>
                <ul>
                  <a href="">Text</a>
                </ul>
                <ul>
                  <a href="">Text</a>
                </ul>
                <ul>
                  <a href="">Text</a>
                </ul>
                <ul>
                  <a href="">Text</a>
                </ul>
              </li>
            </ul>
        </ul>
        </li>
      </div>
    </div>
  </div>

  <script>
    const tileLabels = document.querySelectorAll('.box .tilelabel');

    tileLabels.forEach((label) => {
      label.addEventListener('click', function() {
        const parent = this.closest('.box'); // Find the closest parent with class 'box'
        parent.classList.toggle('expanded');

        const plus = parent.querySelector('.plus');
        plus.style.transform = parent.classList.contains('expanded') ? 'rotate(45deg)' : 'rotate(0)';
      });
    });

    const toggleChild = (e) => {
      if (e.currentTarget == e.target) {
        const el = e.target;
        el.classList.toggle('active');
      }
    };

    const level2 = document.querySelectorAll('.hidden-text ul li:has(ul)');

    level2.forEach((li) => li.addEventListener('click', toggleChild));
  </script>

in this code, when I click on "Sample", then the inner dropdown automatically expands out, but how can I make it so the inner dropdowns don't automatically dropdown? Is there a way to modify the JS code to handle that?

The inner dropdown button also doesn't seem to function, is the inner dropdown also controlled by the main dropdown menu? How can I separate them both? One option could be duplicating the class but that would be too tedious and ineffective if I want to have multiple inner dropdown menus.


Solution

  • Use > child combinator to aviod inner dropdown opening automatically

    Everything you have done is okay some changes in styles can make your wish.

    The reason for child ul opens because you were selecting all the ul inside the .active class that's not we want.

    Instead select only the child of .active by using >

     .hidden-text ul li.active > ul { /* magic is here */
         display: block;
     }
     li.active > .plus { /* here also add > */
        transform: rotate(45deg);
      }
    

    To Learn more about >

    const tileLabels = document.querySelectorAll('.box .tilelabel');
    
        tileLabels.forEach((label) => {
          label.addEventListener('click', function() {
            const parent = this.closest('.box'); // Find the closest parent with class 'box'
            parent.classList.toggle('expanded');
    
            const plus = parent.querySelector('.plus');
            plus.style.transform = parent.classList.contains('expanded') ? 'rotate(45deg)' : 'rotate(0)';
          });
        });
    
        const toggleChild = (e) => {
          if (e.currentTarget == e.target) {
            const el = e.target;
            el.classList.toggle('active');
          }
        };
    
        const level2 = document.querySelectorAll('.hidden-text ul li:has(ul)');
    
        level2.forEach((li) => li.addEventListener('click', toggleChild));
    .wrapper {
        display: grid;
        grid-template-columns: 1fr 1fr;
        grid-gap: 10px;
      }
      
      .tilecontainer {
        padding: 5px;
        font-size: 25px;
        width: 100%;
      }
      
      @media screen and (max-width: 768px) {
        .tilecontainer {
          font-size: 16px;
        }
      }
      
      .box {
        background-color: #0051a5;
        padding-right: 1em;
        padding-top: 1em;
        border-radius: 1em;
        display: grid;
        grid-template-rows: 1fr auto;
        grid-template-columns: auto 1em;
        grid-template-areas: "sample plus" "extratext extratext";
      }
      
      .plus {
        grid-area: plus;
        background: linear-gradient(#0051a5 0 0), linear-gradient(#0051a5 0 0), #fff;
        background-position: center;
        background-size: 60% 2.5px, 2.5px 60%;
        background-repeat: no-repeat;
        transition: transform 0.3s ease;
      }
      
      .sign {
        border-radius: 50%;
        width: 1em;
        height: 1em;
        margin-right: 1em;
      }
      
      .tilelabel {
        grid-area: sample;
        font-family: 'PT Sans Narrow', sans-serif;
        font-size: 1em;
        text-transform: uppercase;
        cursor: pointer;
        font-weight: 600;
        color: #fff;
        padding-left: 1em;
        height: 2em;
      }
      
      .tileaccent {
        color: #FFC72C;
      }
      
      .hidden-text {
        grid-area: extratext;
        display: none;
        font-family: 'PT Sans Narrow', sans-serif;
        font-size: 0.75em;
        background-color: #F5F5F4;
        color: #000;
        margin: 1em;
        padding-top: 0.5em;
        padding-left: 1em;
        border-radius: 1em;
      }
      
      .expanded>.hidden-text {
        display: block;
        animation: fade-in 1s;
      }
      
      @keyframes fade-in {
        from {
          opacity: 0;
        }
        to {
          opacity: 1;
        }
      }
      
      @keyframes fade-out {
        from {
          opacity: 1;
        }
        to {
          opacity: 0;
        }
      }
      
      .hidden-text ul li ul {
        display: none;
      }
      
      .hidden-text ul li.active > ul { /* magic is here */
        display: block;
      }
      
      li .plus {
        transform: rotate(0);
      }
      
      li.active > .plus { /* here also add > */
        transform: rotate(45deg);
      }
      /* Changes the cursor to a pointer for the list items that have a child ul */
      
      .hidden-text ul li:has(ul) .plus {
        float: right;
      }
      
      /* .hidden-text ul li:has(ul) {
        list-style: none;
      }  does not required for li , ul style is enough */  
      
      .hidden-text ul li:has(ul) {
        cursor: pointer;
      }
      
      .hidden-text ul {
        list-style: none;
        /* Remove the default bullet point for ul elements does not required for li */
      }
      
     /* .hidden-text ul li {
        list-style: none;
      } */
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=PT+Sans+Narrow:wght@400;700&display=swap" rel="stylesheet">
    
    
    <div class="wrapper">
      <div class="tilecontainer">
        <div class="box">
          <div class="plus sign"></div>
          <div class="tilelabel">Sample<span class="tileaccent"> Text</span></div>
          <div class="hidden-text">
            <ul>
              <li>Sample
                <div class="plus sign"></div>
                <ul>
                  <li>Sample
                    <div class="plus sign"></div>
                    <ul>
                      <a href="">Text</a>
                    </ul>
                    <ul>
                      <a href="">Text</a>
                    </ul>
                    <ul>
                      <a href="">Text</a>
                    </ul>
                    <ul>
                      <a href="">Text</a>
                    </ul>
                    <ul>
                      <a href="">Text</a>
                    </ul>
                  </li>
                </ul>
               </li>
            </ul>
          </div>
        </div>
      </div>