2

I'm trying to make a sidebar that aligns vertically an undetermined number of list items. When the number of items reaches the bottom of the sidebar, they are grouped in columns via flex.

My problem is that I want the width of the sidebar to be 100px wide when closed and when opened, I want it to be the width of the columns. If I use a fixed width for the open state, this happens:

enter image description here

HTML:

<nav class="sidebar">
    <ul>
        <li>
            1
        </li>
        <li>
            2
        </li>
        <li>
            3
        </li>
        <li>
            4
        </li>
        <li>
            5
        </li>       
        <li>
            ...
        </li>       
    </ul>
</nav>

SCSS:

.sidebar {
    background: #ccc;
    width: 100px;

    ul {
        list-style-type: none;
        height: 100vh;

        display: flex;
        flex-wrap: wrap;
        flex-direction: column;
        align-content: flex-start;

        li {
            background: #000;
            height: 80px;
            width: 80px;
        }
    }

    &:hover
    {
        //width: !?!?!?!
    }
}

How can i accomplish this? How can a open sidebar and show all the columns?

UPDATE: The height of the sidebar can vary depending on the screen size. I updated the code with the original settings.

UPDATE #2: I tested both solutions, although @itay-gal was a clever CSS only option, it lacked the ability to animate. Since i'm already using JS, i ended up using @pablo-darde solution.

I made a small change in @pablo-darde's so that it could work with a variable height sidebar:

const sidebar = document.querySelector('.sidebar');
const ul = sidebar.querySelector('ul');
const li = ul.querySelector('li');

const showRemainder = () => {
    const ulHeight = ul.clientHeight;
    const liHeight = li.clientHeight;
    const width = ul.clientWidth * Math.ceil(ul.children.length / (ulHeight / liHeight));
    sidebar.style.maxWidth = `${width}px`;
}
Ricky
  • 2,912
  • 8
  • 47
  • 74
  • Not sure why @Michael_B marked it as duplicate, it doesn't seems to be the same question, and I found a solution with css only – Itay Gal Nov 13 '18 at 22:57
  • I marked it as a duplicate because this question has been asked and explored many times before. Plus, your answer doesn't explain the problem. It also doesn't explain the answer (which is really just a workaround). But if you think your answer is a useful solution to the overall problem, post it on the main (duplicate) post. @ItayGal – Michael Benjamin Nov 13 '18 at 23:05
  • I thought what we do here is find workarounds to make things work even if it's not trivial and easy :) And I don't think I have to explian the problem, just to explain how I overcome it, and I'll elaborate on my solution soon. – Itay Gal Nov 13 '18 at 23:09

4 Answers4

2

I really attempted to code only with CSS and your code, but without success. I reached the desired result with some JavaScript code. Please, let me know if you can get some more elegant.

Note the css pointer-events: none; property in your li elements. It is very important here. If you want to use href in lis, I believe some refactoring will be needed.

const sidebar = document.querySelector('.sidebar');
const ul = sidebar.querySelector('ul');

const showRemainder = () => {
  const width = ul.clientWidth * Math.ceil(ul.children.length / 5);
  sidebar.style.maxWidth = `${width}px`;
}

const hideRemainder = () => {
 sidebar.style.maxWidth = '80px';
}

sidebar.addEventListener('mouseover', showRemainder);

sidebar.addEventListener('mouseout', hideRemainder);
.sidebar {
  background: #ccc;
  max-width: 80px;
  width: auto;
  height: 400px;
  overflow: hidden;
  transition: all 0.5s ease;
}

.sidebar ul {
  list-style-type: none;
  height: 400px;
  display: flex;
  padding: 0;
  margin: 0;
  flex-flow: column wrap;
  align-content: flex-start;
}

.sidebar ul li {
  display: flex;
  pointer-events: none;
  justify-content: center;
  align-items: center;
  background: #000;
  height: 80px;
  width: 80px;
  color: #fff;
  box-sizing: border-box;
  border: 1px solid #fff;
}
<nav class="sidebar">
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
        <li>7</li>
        <li>8</li>
        <li>9</li>
        <li>10</li>
        <li>11</li>
        <li>12</li>
        <li>13</li>
    </ul>
</nav>
Pablo Darde
  • 5,844
  • 10
  • 37
  • 55
  • Does this solution work with a variable height? My sidebar can have a different height depending on the screen size. I've updated my question. – Ricky Nov 14 '18 at 08:56
  • Your solution doesn't work with a variable height sidebar, so i modified you code just a bit. I've updated my question with the changes i made to your code. – Ricky Nov 14 '18 at 10:52
  • This solution doesn't work with variable height. You need to change some code. I think you can get the `li`s height and add in the calculation. – Pablo Darde Nov 14 '18 at 11:33
  • i've updated my question with the changes i made to your solution. I also marked your answer as the solution to my problem. – Ricky Nov 14 '18 at 14:25
1

Got close with CSS only. You won't be able to animate the open/close.

My suggestion is using a little javascript to count the elements and set the width you need, but if you really can't this might help:

First I set the height of the side bar, then, set the ul to wrap the lis inside. This will set the lis in the correct place.

Adding overflow: hidden will hide everything but the first column.

on hover, I'm setting the overflow to be visible - so you can see the rest of the lis.

since we can't add background to the part which overflows I simulated it by adding a div inside each li.

This will almost do the trick. BUT, when the last column isn't full, you'll see an empty gap without a background in the last column.

So, to overcome it, I added an after selector to the last element, with the height of the whole ul and added a container with overflow: hidden to hide the unnecessary part.

What might be missing?

We can't we animate it because overflow doesn't support animation.

.sidebar {
  list-style-type: none;
  overflow: hidden;
  width: 100px;
  transition: all 1s;
  height: 400px;
  display: flex;
}

.sidebar ul {
  list-style-type: none;
  margin: 0px;
  padding: 0px;
  display: flex;
  flex-wrap: wrap;
  flex-direction: column;
  align-content: flex-start;
}

.sidebar li {
  background-color: #ccc;
  padding: 10px;
}

.sidebar li div {
  background-color: red;
  height: 80px;
  width: 80px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 32px;
  font-weight: bold;
  position: relative;
}

.sidebar:hover {
  overflow: visible;
}

.sidebar li:last-child div:after{
  height: 400px;
  background-color: #ccc;
  content:"     htr   ";
  position: absolute;
  display: none;
  z-index: -1;
  top: 0px;
  width: 100px;
}

.sidebar:hover li:last-child div:after{
  display: inline-block;
}
.container{
 height: 400px;
 overflow-y: hidden;
}
<div class="container">
<nav class="sidebar">
  <ul>
    <li>
      <div>1</div>
    </li>
    <li>
      <div>2</div>
    </li>
    <li>
      <div>3</div>
    </li>
    <li>
      <div>4</div>
    </li>
    <li>
      <div>5</div>
    </li>
    <li>
      <div>6</div>
    </li>
    <li>
      <div>7</div>
    </li>
    <li>
      <div>8</div>
    </li>
    <li>
      <div>9</div>
    </li>
    <li>
      <div>10</div>
    </li>
    <li>
      <div>11</div>
    </li>
    <li>
      <div>12</div>
    </li>
    <li>
      <div>13</div>
    </li>
     <li>
      <div>14</div>
    </li>
  </ul>
</nav>
</div>
Itay Gal
  • 10,706
  • 6
  • 36
  • 75
  • I'm going to try your solution today. I'll get back with feedback. – Ricky Nov 14 '18 at 08:58
  • You solution works, but i really need some animations. For that reason i didn't mark yours as the answer. Clever one thought ;) – Ricky Nov 14 '18 at 11:05
0

This can easily be done with a little bit of Javascript. You would get the number of list items then do a little calculation to determine the number of columns needed, and apply that width style to the sidebar.

QuattroDog
  • 41
  • 3
-1

This snippet isn't pretty but it may give you something to work with. I've placed an overflow hidden on the sidebar. Set it to the width of your first column. Then on hover of the sidebar, change the width to the size of both columns.

.sidebar {
  background: #ccc;
  width: 100px;
  overflow:hidden;
  -webkit-transition: width 1s;
  transition: width 1s;    
}

ul {
  margin-left: -20px;
  list-style-type: none;
  height: 500px;
  display: flex;
  flex-wrap: wrap;
  flex-direction: column;
  align-content: flex-start;
  justify-content: center;
}

li {
  background-color: #000;
  height: 80px;
  width: 80px;
  color: white;
  text-align: center;
}

.sidebar:hover {
  width: 200px;
}
<nav class="sidebar">
  <ul>
    <li>
      1
    </li>
    <li>
      2
    </li>
    <li>
      3
    </li>
    <li>
      4
    </li>
    <li>
      5
    </li>
    <li>
      6
    </li>
    <li>
      7
    </li>
    <li>
      8
    </li>
    <li>
     9
    </li>
    <li>
     10
    </li>
    <li>
      11
    </li>
    <li>
      12
    </li>    
    
  </ul>
</nav>
MichaelvE
  • 2,558
  • 2
  • 9
  • 18