Home > Enterprise >  Collapsible side menu with CSS flexbox
Collapsible side menu with CSS flexbox

Time:12-22

I'm trying to create a collapsible sidebar on the left. I want to set it up real simple: 2 columns which contains of 2 flex boxes, and when a button is pushed: the left flex box increases in width and the tight flexbox just moves with. When the button is clicked again, the flexbox on the left decreases again in side, back to the first state where the menu cannot be seen.

My problem is that I don't know how a click event of a button can control the width size of the flexbox. What I have now is this:

html

<div>
    <button
        onClick={handleViewMenu}??
        style={{ height: "30px", width: "30px" }}>
    </button>
</div>
<div className='container'>
    <div className='container-left'>
      Left
    </div>
    <div className='container-right'>
      right
    </div>
</div>

scss

.container { width: 100vw; 
    height: 100vh; 
    display: flex; 
    
&-left {   
    flex-grow: 0;
    flex-shrink: 0;
    flex-basis: auto; 
    // flex: 0.3 0 auto;  
    // background-color: aqua;
} &-right {    
    flex: 1 0 auto; 
}
}

I just don't know how to deal with the onClick event (where I put the ??. I work in React so I found different things like:

const [sideMenuOpen, setMenuOpen] = useState(false);

  const handleViewMenu = () => {
    setMenuOpen(!sideMenuOpen);
  };

But it should be pretty easy to handle this I think, but I can't find a solution..

CodePudding user response:

Here's a solution that doesn't need javascript by using the :has() pseudo class. Just set the width of the side bar when the checkbox is clicked and if you're using normal flexbox the right hand one will automatically shift to suit. See below. Any questions drop me a comment.

/* essential to add this as there's a default 8px margin */

body {
  margin: 0;
}


/* this is also essential to avoid a world of width-based pain */

* {
  box-sizing: border-box;
}


/* Just making things pretty here */

nav {
  display: flex;
  width: 100%;
  gap: 1rem;
  padding: 0.5rem 1rem;
  background-color: purple;
  color: white;
}


/*the menu button is basically a hidden check box, we use label to style it as a button */

.menubutton>input {
  display: none;
}


/*these toggles the display of the menu button, it works because the label after the input element */

.menubutton>input:checked label .not-active {
  display: none;
}

.menubutton>input:not(:checked) label .active {
  display: none;
}

.container {
  display: flex;
  width: 100%;
}

.container-left {
  background-color: plum;
  height: 50vh;
  padding: 0.5rem 0.5rem;
  border: 1px solid transparent;
  width: 3rem;
  transition: width 300ms;
}


/* this is the bit that styles the width of the sidebar when the checkbox is checked. */

body:has(.menubutton > input:checked) .container-left {
  width: 10rem;
}


/* just style the right box for visibility */

.container-right {
  border: 1px solid lightgray;
  height: 50vh;
  flex-grow: 1;
  padding: 0.5rem 0.5rem;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css" integrity="sha512-MV7K8 y gLIBoVD59lQIYicR65iaqukzvf/nwasF0nqhPay5w/9lJmVM2hMDcnK1OnMGCdVK iQrJ7lzPJQd1w==" crossorigin="anonymous" referrerpolicy="no-referrer"
/>

<nav>
  <div class='menubutton'><input type='checkbox' id='menubuttoninput'><label for='menubuttoninput'><i ></i><i ></i></label></div>
  This is a navbar!
</nav>
</div>
<div class='container'>
  <div class='container-left'>
    Left
  </div>
  <div class='container-right'>
    right
  </div>
</div>

If you do need a javascript solution then attach a listener to the checkbox input element and toggle the sidebar class to change the width as below:

window.onload = () => {
  document.querySelector('.menubutton input').addEventListener('change', (e) => {
    const sidebar = document.querySelector('.container-left');
    if (e.target.checked) {
      sidebar.classList.add('sidebar-active');
    } else {
      sidebar.classList.remove('sidebar-active');
    }
  });
}
/* essential to add this as there's a default 8px margin */

body {
  margin: 0;
}


/* this is also essential to avoid a world of width-based pain */

* {
  box-sizing: border-box;
}


/* Just making things pretty here */

nav {
  display: flex;
  width: 100%;
  gap: 1rem;
  padding: 0.5rem 1rem;
  background-color: cornflowerblue;
  color: white;
}


/*the menu button is basically a hidden check box, we use label to style it as a button */

.menubutton>input {
  display: none;
}


/*these toggles the display of the menu button, it works because the label after the input element */

.menubutton>input:checked label .not-active {
  display: none;
}

.menubutton>input:not(:checked) label .active {
  display: none;
}

.container {
  display: flex;
  width: 100%;
}

.container-left {
  background-color: lightblue;
  height: 50vh;
  padding: 0.5rem 0.5rem;
  border: 1px solid transparent;
  width: 3rem;
  transition: width 300ms;
}


/* this is the bit that styles the width of the sidebar when the checkbox is checked. We just add this using javascript*/

.sidebar-active {
  width: 10rem;
}


/* just style the right box for visibility */

.container-right {
  border: 1px solid lightgray;
  height: 50vh;
  flex-grow: 1;
  padding: 0.5rem 0.5rem;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css" integrity="sha512-MV7K8 y gLIBoVD59lQIYicR65iaqukzvf/nwasF0nqhPay5w/9lJmVM2hMDcnK1OnMGCdVK iQrJ7lzPJQd1w==" crossorigin="anonymous" referrerpolicy="no-referrer"
/>

<nav>
  <div class='menubutton'><input type='checkbox' id='menubuttoninput'><label for='menubuttoninput'><i ></i><i ></i></label></div>
  This is a navbar!
</nav>
</div>
<div class='container'>
  <div class='container-left'>
    Left
  </div>
  <div class='container-right'>
    right
  </div>
</div>

  • Related