Search code examples
htmlcssflexboxheightoverflow

CSS: forbid flex element grow because of its content



Hello! I am now trying to create table component in React.
The problem looks simple, but hard to solve, at least for me.
Table consist of three main blocks: toolbar on the top, pagination on bottom, and table data in middle.
The idea is that table body must fill space between toolbar and pagination, and if there are more items in table, than can be shown, scrolling should be possible inside this middle element. I am using flexbox for this solution, and it all looks quite well when there are just a few items inside:

3 items in table

But when I add more items, overflow happens, and you have to scroll page to see bottom elements: More items in table

I reproduced problem here: https://codesandbox.io/s/charming-browser-v09uml?file=/src/App.js

Is there any way to solve it better than settings max-height for elements?

export default function Example() {
const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
return (<div className="root">
  <div className="tableHolder">
    <div className="header">toolbar</div>
      <div className="tableData">
        {items.map((item) => {
            return (
              <div className="item" key={item}>
                item
              </div>
            );
        })}
      </div>
      <div className="footer">pagination</div>
    </div>
  <div className="expectedSize" />
</div>)
}
.root {
  display: flex;
}

.tableHolder {
  background-color: darkgray;
  width: 600px;
  min-height: 400px;
  display: flex;
  flex-direction: column;
}

.expectedSize {
  background-color: lightcoral;
  width: 20px;
  height: 400px;
}

.header {
  height: 50px;
  background-color: darkgreen;
}

.footer {
  height: 50px;
  background-color: yellow;
}

.tableData {
  flex-grow: 1;
  overflow-y: auto;
}

.item {
  height: 60px;
  background-color: lightblue;
  margin: 10px;
}


Solution

  • Someone else said some helpful things, but they didn't work in my case on a real page. It is caused because this max-height was not applied, any content was getting out of it instead of somehow fitting inside.

    I found a solution. Not CSS, unfortunately. I created an additional <div> element on the place where my table list was, and changed the real table list positioning to fixed and hid it:

    enter image description here

    <div ref={fillerRef} className={s.positioningElement}/>
    <TableContainer style={tablePosition} className={s.tableContainer}>
      <!-- Real table internals -->
    </TableContainer>
    
    .positioningElement {
        pointer-events: none;
        flex-grow: 1;
    }
    

    In the screenshot, you see this element shown. This <div> filler has flex-grow: 1, so it fills all available space between the table header and footer, but since there is nothing inside, it has no limit for minimal space and doesn't have this problem that I had with the original list.

    Then I added listeners to the window scroll and resize events, and on these events fired I get the size & position of this filler div, and apply them to the real table list through style.

    useEffect(() => {
        const updatePosition = () => {
            eventBus.emit('update-table-position')
        }
    
        window.addEventListener('resize', updatePosition)
        window.addEventListener('scroll', updatePosition)
    
        eventBus.emit('update-table-position')
        return () => {
            window.removeEventListener('resize', updatePosition)
            window.removeEventListener('scroll', updatePosition)
        }
    
    }, [])
    
    eventBus.on('update-table-position', function () {
        const { top, left, height, width } = this.fillerRef.current.getBoundingClientRect()
        console.log({ top, left, height, width })
        if (height < 50) {
            this.setTablePosition({
                position: "fixed",
                display: "none"
            })
        } else {
            this.setTablePosition({
                position: "fixed",
                left: `${left}px`,
                top: `${top}px`,
                height: `${height}px`,
                width: `${width}px`,
                overflow: `auto`
            })
        }
        this.forceUpdate()
    })
    

    Finally, the real table follows the filler <div> position and size and works just like I expect from it. I also had to add pointer-events: none to this filler div, to avoid problems with accessing the table in case the <div> covers it.