Search code examples
htmlcsslayoutflexboxresponsive

Horizontal full width with overflow in vertical flexbox


I'm trying to create a flexbox that is both horizontally as vertically scrollable in case its needed. It's kind of a table layout in flexbox. In the picture below you can see the concept that I'm trying to achieve. This works correctly when the viewport is not too small or too short. Normal layout

We can then resize the viewport. This works correctly for the vertical overflow. A scrollbar appears and we can scroll downwards. This sadly doesn't work correctly horizontally. We also get a scrollbar for the horizontal part. But the yellow rows (with test) are not the full width I need it to be.

Issue in horizontal flexbox

const groups = [];
for (let i = 0; i < 15; i++) {
  const rows = [];
  for (let j = 0; j < 15; j++) {
    rows.push({
      cols: [
        "col 1",
        "col 2",
        "col 3",
        "col 4",
        "col 5",
        "col 6",
        "col 7",
        "col 8",
        "col 9",
      ]
    });
  }

  groups.push({
    group: 'test' + i,
    open: false,
    rows
  });
}

var app = new Vue({
  el: '#app',
  data: {
    rows: groups
  }
})
.container {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-width: 100vh;
}

.header {
  position: sticky;
  top: 0;
  display: flex;
  flex-direction: row;
}

.header .col {
  flex-basis: 100px;
  flex-grow: 1;
  flex-shrink: 0;
  background: red;
  padding: 0 2px;
  border: 2px solid black;
}

.vertical-content {
  display: flex;
  flex-direction: column;
}

.vertical-content .grouped-row {
  flex-grow: 1;
  background: yellow;
  border: 2px solid black;
  display: flex;
  flex-direction: column;
}

.vertical-content .row {
  display: flex;
  flex-direction: row;
}

.vertical-content .row .col {
  flex-basis: 100px;
  flex-grow: 1;
  flex-shrink: 0;
  background: blue;
  padding: 0 2px;
  border: 2px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.1/vue.js"></script>

<body>
  <div class="container" id="app">
    <div class="header">
      <div class="col">Col 1</div>
      <div class="col">Col 2</div>
      <div class="col">Col 3</div>
      <div class="col">Col 4</div>
      <div class="col">Col 5</div>
      <div class="col">Col 6</div>
      <div class="col">Col 7</div>
      <div class="col">Col 8</div>
      <div class="col">Col 9</div>
    </div>
    <div class="vertical-content">
      <div class="grouped-row" v-for="row of rows" @click="row.open = !row.open">
        <div>
          {{ row.group }}
        </div>
        <div class="row" v-for="actualRow of row.rows" v-if="row.open">
          <div class="col" v-for="col of actualRow.cols">{{ col }}</div>
        </div>
      </div>
    </div>
  </div>
</body>

In the fiddle click the yellow bars to expand a row with more content.

How can I make the yellow bars the full-width instead of partially?


Solution

  • Couple of issues:

    • You need to use view-width(vw) for min-width. Change min-width: 100vh; to min-width: 100vw;. This alone doesn't resolve the issue.
    • Main issue is because the .header is overflowing. You can check this by adding a fat border to it and .container element. We can fix this using two changes:
        .container {
          ...
          width: max-content;
        }
    
        .header .col {
          ...
          width: 100px;
        }
    

    Demo:

    const groups = [];
    
    for (let i = 0; i < 15; i++) {
      const rows = [];
      for (let j = 0; j < 15; j++) {
        rows.push({ cols: ["col 1", "col 2", "col 3", "col 4", "col 5", "col 6", "col 7", "col 8", "col 9" ] });
      }
    
      groups.push({ group: 'test' + i, open: false, rows});
    }
    
    var app = new Vue({el: '#app', data: { rows: groups } })
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }
    
    .container {
      display: flex;
      flex-direction: column;
      min-height: 100vh;
      min-width: 100vw;
      
      width: max-content;
    }
    
    .header {
      position: sticky;
      top: 0;
      display: flex;
      flex-direction: row;
    }
    
    .header .col {
      flex-basis: 100px;
      flex-grow: 1;
      flex-shrink: 0;
      background: rgb(250, 189, 189);
      padding: 0 2px;
      outline: 2px solid black;
      
      width: 100px;
    }
    
    .vertical-content {
      display: flex;
      flex-direction: column;
    }
    
    .vertical-content .grouped-row {
      flex-grow: 1;
      background: rgb(251, 251, 180);
      outline: 2px solid black;
      display: flex;
      flex-direction: column;
    }
    
    .vertical-content .row {
      display: flex;
      flex-direction: row;
      width: 100%;
    }
    
    .vertical-content .row .col {
      flex-basis: 100px;
      flex-grow: 1;
      flex-shrink: 0;
      background: rgb(150, 150, 238);
      padding: 0 2px;
      outline: 2px solid black;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.1/vue.js"></script>
    
    <body>
      <div class="container" id="app">
        <div class="header">
          <div class="col">Col 1</div>
          <div class="col">Col 2</div>
          <div class="col">Col 3</div>
          <div class="col">Col 4</div>
          <div class="col">Col 5</div>
          <div class="col">Col 6</div>
          <div class="col">Col 7</div>
          <div class="col">Col 8</div>
          <div class="col">Col 9</div>
        </div>
        <div class="vertical-content">
          <div class="grouped-row" v-for="row of rows" @click="row.open = !row.open">
            <div>
              {{ row.group }}
            </div>
            <div class="row" v-for="actualRow of row.rows" v-if="row.open">
              <div class="col" v-for="col of actualRow.cols">{{ col }}</div>
            </div>
          </div>
        </div>
      </div>