shawty shawty - 4 months ago 13
CSS Question

Flexible centered dialog using flexbox with a scrolling region

TL;DR - if your looking for the quick copy/paste answer, the code is at the bottom of this question.

I've been trying now for at least a day, to try and get a flexbox dialog I'm creating to clip/scroll it's content correctly.

Essentially what I'm trying to create is this:

enter image description here

The dialog overlay (Shaded screen) is 100vh/100vw and z-ordered to sit in front of any content already on the page, it's set to display:flex with centering, so that it's ONLY child (the dialog box) always sits in the center of the page no matter what size it is, the 2 css rules to make this happen are as follows:

.dialogOverlay {
z-index: 600;
position: absolute;
left: 0;
top: 0;
height: 100vh;
width: 100vw;
background-color: rgba(0,0,0,0.4);
display: flex;
justify-content: center;
align-items: center;
}

.dialogBase {
position: relative;
min-width: 120px;
min-height: 120px;
max-width: 75%;
max-height: 75%;
overflow: hidden;
background-color: white;
border: 1px solid black;
z-index: 601;
}


The size of the dialog has a minimum of 120px by 120px and is not allowed to grow to more than 75% of the width or height of the page.

This bit all works great, and as anticipated once the dialog reaches maximum size any overflowing content is hidden.

Inside the "dialogBase" there is a dialog content body, and this is set to display:flex, so that it's 2 children (The title bar and the content area) are stacked in row order on top of each other.

The title bar has a fixed height and is also a flex container (As it has 3 children, title text and 2 icons).

The remaining dialog body is flex: 1 so that it uses the remaining space and again this all works perfectly fine. If I fill the dialog body with content it forces the size of the dialog to stretch to accommodate, and at max size hides the content.

The CSS for these items is as follows:

.dialogContentBody {
z-index: 602;
display: flex;
-ms-flex-direction: column;
-webkit-flex-direction: column;
flex-direction: column;
}

.dialogContentBody .titleBar {
background-color: #6e6e6e;
color: white;
height: 50px;
width: 100%;
border-bottom: 1px solid black;
display: flex;
align-items: center;
justify-content: center;
}

.dialogContentBody .bodyContent {
overflow-y: scroll;
background-color: yellow;
flex: 1;
}


The problem however is that I want the body content to scroll once the y direction reaches maximum height, as there are times when the content added into "bodyContent" WILL cause an overflow, however no matter how hard I try, I cannot in any way get the overflow to give me a scroll bar.

For now I'd settle for body content being y scrollable, however ultimately what I want to achieve, is to have a full width/height div for the body content area, which has a fixed width side bar, with a flex 1 content area that is scrollable.

The HTML I've been using so far is:

<div class="dialogOverlay" data-bind="visible: isOpen">

<div class="dialogBase">

<div class="dialogContentOverlay" data-bind="visible: isBlocked">
<loadspinner params="{fontAwesomeSize: 5}"></loadspinner>
</div>

<div class="dialogContentBody">

<div class="titleBar">
<div class="titleContent" data-bind="text: pageTitle"></div>
<div class="iconContent okIcon">
<i class="fa fa-check-circle" data-bind="click: handleOkButton"></i>
</div>
<div class="iconContent closeIcon">
<i class="fa fa-times-circle" data-bind="click: handleCloseButton"></i>
</div>
</div>

<div class="bodyContent" data-bind="component: { name: bodyContentComponentName , params: bodyContentComponentParams}"></div>

</div>

</div>

</div>


Warning Yes, they are knockout 'data-binds' on the HTML as I'm using a KO component architecture here, the "bodyContentComponentName" will eventually be the inner content I'm talking about and will be injected from a stand alone component set. For now however, we can just imagine there is other content inside that div.

The one thing I do know, is that the 'overflow: hidden' absolutley MUST be kept on "dialogBase" as everything just overflows to the base of the screen if not.

Interestingly enough however, if the hidden overflow is removed, and the content is allowed to spill out of the dialog to the bottom of the screen, any overflow scrolls in the content still don't work correctly.

Any help anyone can give, or any ideas on making this work are greatly appreciated.

Shawty

Update (about 10 mins later)



Evan IF I reduce things to the following most simple possible case with a fixed height of 500px, I still cannot get the dialog body to scroll:

<style>
.dialogOverlay {
z-index: 600;
position: absolute;
left: 0;
top: 0;
height: 100vh;
width: 100vw;
background-color: rgba(0,0,0,0.4);
display: flex;
justify-content: center;
align-items: center;
}

.dialogBase {
position: relative;
min-width: 120px;
max-width: 75%;
overflow: hidden;
height: 500px;
background-color: white;
border: 1px solid black;
margin: 10px;
z-index: 601;
}

.bodyContent {
background-color: yellow;
overflow-y: scroll;
}

</style>

<div class="dialogOverlay" data-bind="visible: isOpen">

<div class="dialogBase">

<div class="bodyContent" data-bind="compinent: { name: bodyContentComponentName , params: bodyContentComponentParams}">
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
<h1>Body content, Body Content</h1>
<p>body content</p>
</div>

</div>

</div>


Update following Micheal_B's answer



Adding 100% to the doc body still did not enable the scroll bar

<style>
.dialogOverlay {
z-index: 600;
position: absolute;
left: 0;
top: 0;
height: 100vh;
width: 100vw;
background-color: rgba(0,0,0,0.4);
display: flex;
justify-content: center;
align-items: center;
}

.dialogBase {
position: relative;
min-width: 120px;
//min-height: 120px;
max-width: 75%;
//max-height: 75%;
overflow: hidden;
height: 500px;
background-color: white;
border: 1px solid black;
margin: 10px;
z-index: 601;
}

.dialogContentBody {
z-index: 602;
display: flex;
-ms-flex-direction: column;
-webkit-flex-direction: column;
flex-direction: column;
}


.bodyContent {
//padding: 100px;
//overflow: hidden;
background-color: yellow;
flex: 1;
overflow-y: scroll;
height: 100%;
}

.dialogConAtentBody .bodyContent:empty {
display: none;
}
</style>

<div class="dialogOverlay" data-bind="visible: isOpen">

<div class="dialogBase">

<div class="dialogContentBody">

<div class="bodyContent" data-bind="compinent: { name: bodyContentComponentName , params: bodyContentComponentParams}">
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
<h1>Test Contnet, Test Content</h1>
<p>test content</p>
</div>

</div>

</div>

</div>


More updates



With micheal b's help, I got the container to scroll with the height fixed at 500px, unfortunately even when reverting back to min/max flex height the scrolling still does not work. I've also since traced the container back up the chain to HTML and there is a fixed "height: 100%" on every element all the way back up to the top.

This one does not work

<style>
.dialogOverlay {
z-index: 600;
position: absolute;
left: 0;
top: 0;
height: 100vh;
width: 100vw;
background-color: rgba(0,0,0,0.4);
display: flex;
justify-content: center;
align-items: center;
}

.dialogBase {
position: relative;
min-width: 120px;
min-height: 120px;
max-width: 75%;
max-height: 75%;
overflow: hidden;
background-color: white;
border: 1px solid black;
margin: 10px;
z-index: 601;
}

.dialogContentBody {
z-index: 602;
display: flex;
-ms-flex-direction: column;
-webkit-flex-direction: column;
flex-direction: column;
height: 100%;
}


.bodyContent {
background-color: yellow;
flex: 1;
overflow-y: scroll;
height: 100%;
}

.dialogConAtentBody .bodyContent:empty {
display: none;
}
</style>

<div class="dialogOverlay" data-bind="visible: isOpen">

<div class="dialogBase">

<div class="dialogContentBody">

<div class="bodyContent" data-bind="compinent: { name: bodyContentComponentName , params: bodyContentComponentParams}">
.
.
.
lots of content
.
.
.
</div>

</div>

</div>

</div>


This one Does

<style>
.dialogOverlay {
z-index: 600;
position: absolute;
left: 0;
top: 0;
height: 100vh;
width: 100vw;
background-color: rgba(0,0,0,0.4);
display: flex;
justify-content: center;
align-items: center;
}

.dialogBase {
position: relative;
min-width: 120px;
max-width: 75%;
overflow: hidden;
height: 500px;
background-color: white;
border: 1px solid black;
margin: 10px;
z-index: 601;
}

.dialogContentBody {
z-index: 602;
display: flex;
-ms-flex-direction: column;
-webkit-flex-direction: column;
flex-direction: column;
height: 100%;
}


.bodyContent {
background-color: yellow;
flex: 1;
overflow-y: scroll;
height: 100%;
}

.dialogConAtentBody .bodyContent:empty {
display: none;
}
</style>

<div class="dialogOverlay" data-bind="visible: isOpen">

<div class="dialogBase">

<div class="dialogContentBody">

<div class="bodyContent" data-bind="compinent: { name: bodyContentComponentName , params: bodyContentComponentParams}">
.
.
.
lots of content
.
.
.
</div>

</div>

</div>

</div>


As per Micheal B's last comment, absolute and relative positioning is also placed on the 2 outer containers.

Final Update



So after a couple of hours of head scratching, some furious white board scribbles with my colleague here in the UK, and some fantastic input from Michael_B and LGSon , the solution turned out to be quite descriptively simple.

Repeat your min/max height all the way down the chain

Michael_B was the first one to realize it was an inheritance problem, in so much that there where gaps going back up to the parent where 100% on the height calculation where missing.

However, while this worked, there where some missing links. LGSon realized that these links where in fact to duplicate the min/max rules down the chain, so that any children also had a height up the chain to work with.

I wish I could mark BOTH responses as the answer folks, but alas LGSon's answer was the one that finally got things going, unfortunately, I cant share all the code, but here's what I can share:

The outer dialog

<style>
.dialogOverlay {
z-index: 600;
position: absolute;
left: 0;
top: 0;
height: 100vh;
width: 100vw;
background-color: rgba(0,0,0,0.4);
display: flex;
justify-content: center;
align-items: center;
}

.dialogBase {
position: relative;
min-width: 120px;
min-height: 120px;
max-width: 75vw;
max-height: 75vh;
background-color: white;
border: 1px solid black;
margin: 10px;
z-index: 601;
overflow: hidden;
}

.dialogBase .titleBar {
background-color: #6e6e6e;
color: white;
height: 50px;
width: 100%;
border-bottom: 1px solid black;
display: flex;
align-items: center;
justify-content: center;
}

.dialogBase .titleBar .titleContent {
padding-left: 10px;
font-size: 2.5rem;
-ms-flex: 1;
-webkit-flex: 1;
flex: 1;
}

.dialogBase .titleBar .iconContent {
width: 50px; /* Square icons, 50x50 */
text-align: center;
font-size: 2.5rem;
}

.dialogBase .titleBar .iconContent.okIcon {
color: #449D44;
cursor: pointer;
}

.dialogBase .titleBar .iconContent.okIcon:hover {
color: #03FF03;
}

.dialogBase .titleBar .iconContent.closeIcon {
color: #C9302C;
cursor: pointer;
}

.dialogBase .titleBar .iconContent.closeIcon:hover {
color: #FF0600;
}

.bodyContent {
overflow-y: hidden;
min-height: 120px;
max-height: calc(75vh - 50px);
}

</style>

<div class="dialogOverlay" data-bind="visible: isOpen">

<div class="dialogBase">

<div class="titleBar">
<div class="titleContent" data-bind="text: pageTitle"></div>
<div class="iconContent okIcon">
<i class="fa fa-check-circle" data-bind="click: handleOkButton"></i>
</div>
<div class="iconContent closeIcon">
<i class="fa fa-times-circle" data-bind="click: handleCloseButton"></i>
</div>
</div>

<div class="bodyContent" data-bind="component: { name: bodyContentComponentName , params: bodyContentComponentParams}">ANOTHER COMPONENT IS INJECTED HERE</div>

</div>

</div>


The inner dialog markup (INJECTED COMPONENT[s])

<style>
.dialogInnerContainer {
display: flex;
align-items: stretch;
-ms-align-content: stretch;
-webkit-align-content: stretch;
align-content: stretch;
}

.dialogInnerContainer .tabBar {
width: 60px;
background-color: orange;
border-right: 1px solid black;
}

.dialogInnerContainer .mainContentArea {
overflow-y: auto;
overflow-x: hidden;
min-height: 120px;
max-height: calc(75vh - 50px);
}
</style>

<div class="dialogInnerContainer">

<div class="tabBar">
Mini side bar content get's put in here
</div>

<div class="mainContentArea">
Generated main contnet that needs to scroll gets put in here
</div>

</div>


Many thanks to ALL who had an input on this.

Shawty

Answer

Updated with the title and side bar as well

Working with percent and flexbox can sometimes give you unwanted problems.

This sample use viewport units all the way

Could this be a start?

Fiddle demo, without side bar

Fiddle demo, with side bar

.dialogOverlay {
  z-index: 600;
  position: absolute;
  left: 0;
  top: 0;
  height: 100vh;
  width: 100vw;
  background-color: rgba(0,0,0,0.4);
  display: flex;
  justify-content: center;
  align-items: center;
}
.dialogBase {
  position: relative;
  min-width: 120px;
  min-height: 120px;
  max-width: 75vw;
  max-height: 75vh;
  background-color: white;
  border: 1px solid black;
  margin: 10px;
  z-index: 601;
  overflow: hidden;
  display: flex;
  flex-direction: column
}
.bodyTitlebar {
  background-color: gray;
  color: white;
  height: 50px;
}
.bodyWrapper {
  display: flex;
}
.bodySidebar {
  background-color: lime;
  overflow: hidden;
  max-height: calc(75vh - 50px);
  width: 100px;
}
.bodyContent {
  background-color: yellow;
  overflow-y: scroll;
  min-height: 120px;
  max-height: calc(75vh - 50px);
  flex:1
}
<div class="dialogOverlay">
  <div class="dialogBase">

    <div class="bodyTitlebar">
      Title bar
    </div>

    <div class="bodyWrapper">
      <div class="bodySidebar">
        <h1>Side bar</h1>
        <p>side bar</p>
        <h1>Side bar</h1>
        <p>side bar</p>
        <h1>Side bar</h1>
        <p>side bar</p>
        <h1>Side bar</h1>
        <p>side bar</p>
        <h1>Side bar</h1>
        <p>side bar</p>
      </div>

      <div class="bodyContent">
        <h1>Body content Body content </h1>
        <p>body content</p>
        <h1>Body content</h1>
        <p>body content</p>
        <h1>Body content</h1>
        <p>body content</p>
        <h1>Body content</h1>
        <p>body content</p>
        <h1>Body content</h1>
        <p>body content</p>
      </div>

    </div>
  </div>
</div>

Comments