markieo
markieo

Reputation: 484

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?

Upvotes: 9

Views: 1035

Answers (2)

the Hutt
the Hutt

Reputation: 18398

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>

Upvotes: 1

Joel
Joel

Reputation: 1239

Every red and blue cells have a minimal width (with flex-basis and flex-shrink: 0) but not the yellow.

The yellow are using the largest width possible for them, but the others are going out their container.

In this situation, the simplest way to "fix" it is to set a minimal width to the yellow bars too.

A small example (with variables to simplify maintainability)

Diff:

:root {
  --nb-cells: 9;
  --cell-width: 100px;
}

.container {
  box-sizing: border-box;
}

.header .col,
.vertical-content .row .col {
  box-sizing: border-box;
  flex-basis: var(--cell-width);
}

.vertical-content .grouped-row {
  box-sizing: border-box;
  min-width: calc(var(--cell-width) * var(--nb-cells));
}

Full test

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
  }
})
:root {
  --nb-cells: 9;
  --cell-width: 100px;
}

.container {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-width: 100vh;
  box-sizing: border-box;
}

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

.header .col {
  flex-basis: var(--cell-width);
  flex-grow: 1;
  flex-shrink: 0;
  background: red;
  padding: 0 2px;
  border: 2px solid black;
  box-sizing: border-box;
}

.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;
  min-width: calc(var(--cell-width) * var(--nb-cells));
  box-sizing: border-box;
}

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

.vertical-content .row .col {
  flex-basis: var(--cell-width);
  flex-grow: 1;
  flex-shrink: 0;
  background: blue;
  padding: 0 2px;
  border: 2px solid black;
  box-sizing: border-box;
}
<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>

Upvotes: 2

Related Questions