CSS Grid Ignores Max Height - css

I have a complex layout grid for a nonogram app, which should be flexible for different sized grids. A nonogram looks like the following, and the inner grid is not guaranteed to be any particular size or column to row ratio. Also, the headers for the rows and columns will have an unknown number of elements
On smaller screens, my app looks fine and the grid scales down; but as you increase the size, once you hit the point that the height should be limited, the grid instead expands to use up all its available width while keeping its other proportions and blowing out the bottom.
GIF of what's happening: https://ibb.co/YfsT8nL
Some additional sketches of the issue:
I've tried and experimented with a ton of different ways to limit the layout, but nothing seems to have any effect. It seems that the elements containing the grid will size to the limitations I want, but then the #cell-container just blows up and then blows up the #grid-container and then blows out and overflows the #game-container.
The only thing I've done that seems to actually do anything is directly size the #cell-container, but this is not a solution because then the outer grid will not size things properly to keep the headers lined up and center the grid.
The behavior I expect is to have the row and column headers size to min-content, then the cells grid area should size the cells inside to be contained.
Code from my app (written with Svelte, TailwindCSS)
// NonoGame.svelte
<div id="game-container" class="flex flex-col px-2 h-screen">
<div id="game-header" class="flex flex-shrink-0 flex-grow-0 items-center justify-end text-3xl">
<GameTimer />
<PauseButton {controller} />
</div>
<div class="flex-shrink my-auto">
<div class="h-auto">
<NonoGrid controller={controller} />
</div>
</div>
<div id="game-footer" class="flex flex-shrink-0 flex-grow-0 justify-center items-center py-2">
<button class="invisible mr-auto ml-16">Submit</button>
<button id="mark-button" class="mx-2" on:click={() => controller.selectionMode = SelectionMode.Marking} class:active={controller.selectionMode === SelectionMode.Marking}><div></div></button>
<button id="cross-button" class="mx-2" on:click={() => controller.selectionMode = SelectionMode.Crossing} class:active={controller.selectionMode === SelectionMode.Crossing}></button>
<button on:click={submitSolution} class="ml-auto mr-16">Submit</button>
</div>
</div>
<style>
#game-header, #game-footer {
height: 10vh;
}
</style>
// NonoGrid.svelte
<div id="grid-container">
<div id="column-headings">
{#each $gridStore as column}
<span>
{#each GridHelper.getColGroups(column[0].x) as groupNum}
{groupNum}<br>
{/each}
</span>
{/each}
</div>
<div id="row-headings">
{#each $gridStore[0] as rowCell}
<div>
<span>
{#each GridHelper.getRowGroups(rowCell.y) as groupNum}
{groupNum}
{/each}
</span>
</div>
{/each}
</div>
<div id="cell-container"
on:mousedown={dragSelector.onMouseDown}
on:touchstart={dragSelector.onTouchStart}
>
{#each $gridStore as column}
<div class="grid-column">
{#each column as gridCell}
<NonoCell gridCell={gridCell} />
{/each}
</div>
{/each}
</div>
</div>
<style lang="postcss">
#grid-container {
display: grid;
grid-template-rows: fit-content(100px) 1fr;
grid-template-columns: fit-content(150px) 1fr;
grid-template-areas: "x col"
"row cells";
}
#column-headings, #row-headings {
#apply text-xl sm:text-3xl md:text-4xl;
}
#column-headings {
grid-area: col;
display: flex;
align-items: flex-end;
}
#column-headings > span {
width: 100%;
text-align: center;
}
#row-headings {
grid-area: row;
display: flex;
flex-direction: column-reverse;
}
#row-headings > div {
height: 100%;
vertical-align: middle;
text-align: right;
display: flex;
justify-content: flex-end;
align-items: center;
}
#cell-container {
grid-area: cells;
display: flex;
flex-direction: row;
}
.grid-column {
display: flex;
flex-direction: column-reverse;
width: 100%;
}
</style>
If necessary, I can add some dynamic CSS styling with Svelte to respond to the size of the grid I'm rendering like so:
#cell-container {
grid-template-rows: var(--template-rows);
grid-template-columns: var(--template-columns);
}
But I really do NOT want to have Svelte be responsible for responding to screen size. That's what Grid and Flexbox are supposed to be for.
I tried restructuring my grid a little bit like so:
<div id="cell-container"
on:mousedown={dragSelector.onMouseDown}
on:touchstart={dragSelector.onTouchStart}
>
{#each $gridStore as column}
{#each column.reverse() as gridCell}
<NonoCell gridCell={gridCell} />
{/each}
{/each}
</div>
...
<style>
...
/* Static for 5x5 grid; can be dynamic with a css var if it worked in the first place */
#cell-container {
grid-area: cells;
display: grid;
grid-auto-flow: column;
grid-template-rows: repeat(5, 1fr);
grid-template-columns: repeat(5, 1fr);
}
...
</style>
But this doesn't really help the situation much; in fact, there are even more odd behaviors happening when I scale this up and down.
I've created a CodePen with simplified code and a 5x5 grid for anyone to test with. Sorry if it's not perfectly neat and formatted; I did my best.
https://codepen.io/brooksvb/pen/eYReGRR
Thanks in advance for any assistance. I've been fighting this issue for over a week.
Bonus points: A feature I wanted to add, but will accept not having for this project for now, was to have a small gap appear between groups of 5 rows/columns, like the following picture. I already did some research into how to have a variable grid gap, but have found that Grid currently has no way to define anything besides a grid-wide gap value. If you have an idea of how to implement this in your solution, I'd love to see anything.

I wanted to leave the solution here that I used; I wrote some Javascript to do the calculation of cell size. G-Cyrillus' comment above also has a good idea of how to restructure the grid to handle it which I may revisit.
// NonoGrid.svelte
<script>
...
// DOM Elements
let gridSlot;
let columnHeadings;
let rowHeadings;
let gridColumns = [];
// This function calculates the size of cells based on screen space
const resizeCells = () => {
// Max height and width available
let maxHeight = gridSlot.clientHeight;
let maxWidth = gridSlot.clientWidth;
// Get height and width occupied by headers
let colHeaderHeight = columnHeadings.clientHeight;
let rowHeaderWidth = rowHeadings.clientWidth;
// Divide remaining space among rows and cols
let availableHeight = (maxHeight - colHeaderHeight) / controller.getRows();
let availableWidth = (maxWidth - rowHeaderWidth) / controller.getCols();
// Choose smallest for both dimensions to make square
let targetCellSize = (availableHeight < availableWidth ? availableHeight : availableWidth) + 'px';
// console.log(`Resizing to target: ${targetCellSize}`);
gridColumns.forEach((colElem) => {
colElem.style.width = targetCellSize;
});
}
onMount(() => resizeCells());
</script>
<svelte:window on:resize={resizeCells}></svelte:window>
<!-- Add grid slot to set max-height and contain the container, centered inside. Also add DOM bindings for above access -->
<div id="grid-slot" class="flex" bind:this={gridSlot}>
<div id="grid-container">
<div id="column-headings" bind:this={columnHeadings}>
...
</div>
<div id="row-headings" bind:this={rowHeadings}>
...
</div>
<div id="cell-container"
on:mousedown={dragSelector.onMouseDown}
on:touchstart={dragSelector.onTouchStart}
>
{#each $gridStore as column, i}
<div class="grid-column" bind:this={gridColumns[i]}>
{#each column as gridCell}
<NonoCell gridCell={gridCell} />
{/each}
</div>
{/each}
</div>
</div>
</div>
<style lang="postcss">
#grid-slot {
height: 80vh; // Set the height limit
}
#grid-container {
width: min-content; // Needed to scale down the column headers when columns shrink
margin: auto; // Centers horizontally and vertically (thanks to flex container)
...
}
...

Related

How to make css grid height increase to accommodate grid gap?

i'm facing this problem with grid where when i add a row-gap property the grid items will overflow in the bottom and cover other elements..how can i prevent this from happening? is there a way to make the grid parent's height increase automatically to fit the changes after gap is added?
i'm using react and bootstrap in my project..but to style the grid i'm using mainly CSS only
CSS
.items-grid{
display: grid;
grid-template-columns: repeat(4,1fr);
row-gap: 3%;
}
REACT.JS JSX
<div>
<div>
<div className="items-grid w-80 mx-auto">
{itemsList.map((item) => {
return <Item key={item._id} title={item.title} image={item.image} />
})}
</div>
</div>
<div className="w-90 mx-auto text-center my-5">
<h4 className="d-inline">"Some text"</h4>
</div>
</div>
here is what i end up with, as you can see the last row is overflowing in the bottom covering the elements below..what can i do to fix this?

CSS either show all elements in a single row or each element on a new row

I have four buttons with varying content lengths. I want all buttons to have the same dimensions as the largest button. They should all be displayed in a single row if there is sufficient space otherwise each button should be displayed on a new row. I have attached a diagram below.
My HTML code is
<div class="answers">
<button class="button">A</button><br>
<button class="button">B</button><br>
<button class="button">C</button><br>
<button class="button">D</button><br>
</div>
I have given the following CSS properties to the container
.answers {
display: inline-flex;
flex-direction: column;
justify-content: center;
flex-wrap: wrap;
}
This ensures that all the buttons are the same width but it always displays them on four lines even when there is room to have them all on a single row.
TLDR:
I need a way to make sure all the buttons have the same width as the largest button, I do not know this value as the buttons can be updated with new content.
I then need to calculate if these four buttons can fit horizontally on the screen and if not display them vertically avoiding a situation where I might have a grid of 2x2.
One approach can be like this (you can use jquery to calculate width for all buttons according to the text in the largest button)
let maxWidth = 0;
$('.button').each(function(){
const width = parseFloat($(this).css('width'));
if(width > maxWidth) {maxWidth = width}
})
$('.button').css('width', maxWidth +'px');
.answers {
display: inline-flex;
justify-content: center;
table-layout: fixed;
flex-direction: row;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="answers">
<button class="button">small</button><br>
<button class="button">s</button><br>
<button class="button">large</button><br>
<button class="button">the largest</button><br>
</div>

Inserting component on click between grid rows

I have a grid of items and when I click on one of them I want to show another component that takes all the width on the row under the clicked item.
Here is a picture of what I want to achieve
I have a button-list-component which contain a button-component that display my items with an ngFor.
When I click on an item the size-selection-component is shown with a ngIf.
My problem is that if I have the size-selection-component in the button-list-component, it append at the end of the grid, and if I have this new component in the button-component then it only takes the width of the column containing the clicked item.
If I put it in position absolute I can make it the width of the container but then the following items won't go down.
Here is a piece of my code
button-list
<div class="wrapper">
<div class="container">
<app-button *ngFor="let button of buttons;" [button]="button" (buttonClicked)="onButtonClicked($event)">
</app-button>
</div>
</div>
.container {
position: relative;
display: grid;
grid-template-columns: repeat(3, minmax(250rem, 1fr));
grid-gap: 10rem;
place-items: start center;
overflow-x: hidden;
overflow-y: auto;
margin: 10rem;
}
button
<div class="product">
<div class="product-col">
<h2 class="product_title" (click)="onButtonClicked()">{{button.Caption}}</h2>
<img class="product_img" [src]="picture" alt="" (click)="onButtonClicked()">
<span class="product_price" *ngIf="button.Price && button.Price != '0'">{{button.Price / 100 | currency}}</span>
</div>
</div>
<app-size-selection-button-list *ngIf="isSizeSelectionOpen"></app-size-selection-button-list>
How would it be possible to achieve that ? Maybe there is a solution in CSS or maybe another way to insert my component in the grid of items that would work.
Thanks
You don't have to append the div, you can create it and hide it, when user hover over the item div, you show it and set its child tag's innerHTML to whatever you need, like this
<body>
<p onmouseover="show();"></p>AA</p>
<div style="width:100%">
<div style="width:33%">
<p>Name</p>
</div>
<div style="width:33%">
<p>Name</p>
</div>
<div style="width:33%">
<p>Name</p>
</div>
</div>
<div id="blah" style="display:none">
<!-- whatever here -->
</div>
<script>
function show() {
var b=document.getElementById("blah");
b.style.display="block";
// Change some innerHTML of elements inside the blah div here
}
</script>

TypeScript coded menu and Grid not displaying correctly

So I am trying to create a menu which derives its elements from code. This is to make it much easier to add new elements, and/or in the future put the menu items into a database to make them easier to edit and add to without recompiling.
I want the menu to look like this:
However the behavior is currently not working very well, i.e.
I have created a stackblitz here: https://stackblitz.com/edit/angular-ivy-ejlj7n?file=src/app/app.module.ts
I have tried a bunch of stuff, such as creating a sub grid for each hover section, setting width for items, rearranging what is listed in each grid section etc- but im not sure what else I can do. The erratic nature clearly comes from the fact that elements are moving when hovered over.
Another issue is that submenus are opening up in the same column as their master, not spanning the entire width of the grid, which I suspect is because those divs are (by necessity to create a HTML/CSS only menu system) sub-divs of their owning menu item.
Thanks!
Ok, so I have given up on the pure pure css version of this and with a little code made it work. Im not sure still why it didnt work before but what I did was to reiterate over the menu items, and on hover over a menu item update a variable in the code which allows the sub items to show, so all the [unstyled] code was:
menuHoverId: number;
menuMidHoverId: number;
menuHover(id: number) { this.menuHoverId = id; }
menuMidHover(id: number) { this.menuMidHoverId = id; }
and then in the HTML I am reiterating over each section each time and testing against those variables. As I hover over a parent menu item I reassign the id in the array I am searching for:
<header>
<div class="grid-container">
<div class="title">Test/Blocks/etc</div>
<div class="top-menu">
<div class="top-menu-item" *ngFor="let section of menuList; let i = index" (mouseover)="menuHover(i)">{{ section.title }}</div>
</div>
<div class="mid-menu">
<span *ngFor="let section of menuList; let i = index">
<span *ngIf="menuHoverId===i">
<div class="mid-menu-item" *ngFor="let link of section.items; let o = index" (mouseover)="menuMidHover(o)">{{ link.name }}</div>
</span>
</span>
</div>
<div class="bottom-menu">
<span *ngFor="let section of menuList; let i = index">
<span *ngIf="menuHoverId===i">
<span *ngFor="let link of section.items; let o = index">
<span *ngIf="menuMidHoverId===o">
<div class="bottom-menu-item" *ngFor="let subsim of link.subItems; let t = index">
{{ subsim.name }}
</div>
</span>
</span>
</span>
</span>
</div>
</div>
</header>
and then the css is straightforward grid layout:
.grid-container {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: repeat(4, auto);
gap: 4px 0px;
grid-template-areas:
"title"
"top-menu"
"mid-menu"
"bottom-menu";
}
.title { grid-area: title; }
.top-menu { grid-area: top-menu; }
.top-menu-item { display: inline-block; }
.mid-menu { grid-area: mid-menu; }
.mid-menu-item { display: inline-block; }
.bottom-menu { grid-area: bottom-menu; }
.bottom-menu-item { display: inline-block; }
It works smoothly - not the solution I was looking for but its the solution I can be bothered with :)
Thanks to all those who looked at the problem!

Suitable CSS display for showing 3 items in each row with arbitrary number of rows?

I have a list of items that I wish to display. At first I used display: flex but noticed that when the line wrap occurred, the items weren't aligned horizontally. Then I switched to display: grid but that requires me to know the number of rows in advance.
What kind of display is suitable for creating a grid of arbitrary many items with n items in each row?
<div class="element">
<app-check *ngFor="let item of items" [caption]="item.name">
</app-check>
</div>
div.elements {
display: ???; ...
}
app-check {
flex: 1 1 auto; ... ???
}
The above CSS would work if I somehow could push out the items three-by-three. I can do that using some logic in the component code but it seems like an ugly hack. And I prefer to resolve it in an appropriate way.
For 3 items you can do it in a similar way, just use flex-basis: 33% for the child element:
div.element {
display: flex;
flex-wrap: wrap;
}
app-check {
flex: 1 0 33%;
}
<div class="element">
<app-check *ngFor="let item of items" [caption]="item.name">
</app-check>
</div>
This is a good place to study flexbox:
https://css-tricks.com/snippets/css/a-guide-to-flexbox/

Resources