Angular Material Select w/ Columns - css

I am trying to style the selection panel such that the items display in several columns, but the last item of each column seems to become off-center and split, with the overflow starting at the top of the next column. Ideally the scroll would be vertical, but instead it seems to scroll horizontal to cover the overflow. My list has 30+ items and multiple selections are allowed. The goal is to display as many options as possible to the user at once so they don't have to scroll too much.
Full StackBlitz:
https://stackblitz.com/edit/angular-bt3gs6
select-multiple-example.scss
.toppings-panel.mat-select-panel {
column-count: 2;
column-width: 200px;
width: 400px;
max-width: 400px;
}
select-multiple-example.html
<mat-select [formControl]="toppings" panelClass="toppings-panel" multiple>
<mat-option *ngFor="let topping of toppingList" [value]="topping">{{topping}}</mat-option>
</mat-select>
select-multiple-example.ts
export class SelectMultipleExample {
toppings = new FormControl();
toppingList: string[] = ['Pepperoni', 'Sausage', 'Ham', 'Bacon', 'Chicken',
'Mushroom', 'Red onion', 'White onion', 'Tomato', 'Olives',
'Green bell peppers', 'Pineapple', 'Artichoke', 'Spinach',
'Basil', 'Hot pepper flakes',
'Parmesan', 'Shredded cheddar', 'Extra mozzarella'];
}

The problem with the columns that become off-center and split, is the height of the .mat-select-panel.
It has max-height:256px; (setted in Angular Material code). But, since it has a horizontal scrollbar (which has a height of 17px, in Windows - Chrome), the available remaining space will be: 256 - 17 = 239px.
The height of the mat-option is 48px, so 5 options in a column will take 240px.
A quick solution would be to increase the height of the .mat-select-panel to 257px:
Demo: https://stackblitz.com/edit/angular-bt3gs6-wxwkgg
But, the example above will not display correctly on MacOS; which displays scrollbars like an absolute positioned content, and it has more space available:
I've found a cross platform solution by removing columns (which are kind of difficult to implement cross-browser and cross-platform) and taking an
approach with display: flex for the .mat-select-panel element:
Horizontal scrolling:
.toppings-panel.mat-select-panel {
width: 400px;
max-width: 400px;
display: flex;
flex-direction: column;
flex-wrap: wrap;
min-height: 257px; // min-height needed for windows browsers
}
.toppings-panel.mat-select-panel .mat-option {
min-width: 50%; // 50% to have 2 columns visible
}
Demo: https://stackblitz.com/edit/angular-bt3gs6-lwvwav
Vertical scrolling:
If you prefer vertical scrolling, just remove flex-direction: column; from the code above and play with the max-height in order to set the default visible rows:
.toppings-panel.mat-select-panel {
width: 400px;
max-width: 400px;
display: flex;
flex-wrap: wrap;
max-height: 240px; /* 240px - for 5 items / column */
}
.toppings-panel.mat-select-panel .mat-option {
min-width: 50%;
}
Demo: https://stackblitz.com/edit/angular-bt3gs6-iykn4w

Related

Flex left div fill remaining space when right div is at maximum width

I have two divs inside a flex box. Div A is on the left, and div B is on the right.
I would like div A to take up 60% of the flex box, and div B to take up 40% of the flex box. As you can imagine, my css will look like this:
.container {
display: flex;
justify-content: start;
flex-direction: column;
}
.div_a {
width: 60%;
}
.div_b {
width: 40%;
}
Also, the browser will look like this:
Now, I would like div B to have a maximum and minimum width, the code will now look like this:
.container {
display: flex;
justify-content: start;
flex-direction: column;
}
.div_a {
width: 60%;
}
.div_b {
width: 40%;
max-width: 768px;
min-width: 480px;
}
Unfortunately, this will lead to this situation large browsers.
When Div B has reached its maximum width, I would like div A to fill up the rest of the space, like this:
Any ideas or fixes would be appreciated, thank you very much in advance.
I should mention that min-width: 60% for Div A produces the same situation, unfortunately.
This is a perfect case where you would turn towards the property flex-grow.
Since you're working with a 60%/40% size, you can use flex-grow:6 and flex-grow:4, or alternatively: flex-grow:3 and flex-grow:2. Or even: flex-grow:1.5 and flex-grow:1 since the property also accepts decimals!
.div_a {
flex-grow:3;
}
.div_b {
flex-grow: 2;
max-width: 768px;
min-width: 480px;
}
flex-grow is a property that will tell the parent (flexbox) to divide the available width into whatever the sum is of the amount of flex-grow specified in its child elements. By limiting the max width of .div_b, you tell flexbox to stop increasing the width after it reached that max, and the remaining width will be reserved for the other elements (.div_a in this case).
Also
I do want to point out that you're using flex-direction:column, but you're trying to create a row based layout. It's a better idea to use flex-flow: row nowrap. Which is a shorthand to declare both flex-direction and flex-wrap together and, with the value row nowrap will tell the parent to force everything on one line in a horizontal layout.
.container {
display: flex;
justify-content: start;
// flex-direction: column;
flex-flow: row nowrap;
}

Flexbox - Variable Number of Columns Problem

I'm trying to aim for a responsive design wherein a long list of links is arranged in columns, the number of columns varying according to the width of the display device screen. As I understand it, I must specify the height of the container to get multiple columns. However, then the columns continue to the right off the screen. I do not know the length of the links. Is there any way to do this through Flexbox? It seems like such an obvious requirement.
The CSS I have so far is:
/* Container */
.links {
display: flex;
flex-direction: column;
flex-wrap: wrap;
width: 100vw !important;
height: 90vh;
}
/* Links in Container */
.links a {
white-space: nowrap;
flex: 1;
margin: 5px 5px 0 20px;
}
Edit: this it NOT a duplicate as commented. The problem not that the container width doesn't grow horizontally. The problem is that it DOES grow horizontally, not vertically.
Have you considered just using CSS Columns?
You wouldn't need to specify any height and then as the screen width changes, the number of columns will adjust based on the width you specify - taking up whatever height it needs, accordingly.
Your CSS could just look like this:
.links {
columns: 5 100px; // # of columns | minimum column width
column-gap: 40px; // space between columns
}
.links > a {
display: block;
padding: 10px 5px;
}
<div class="links">
link1
link2
link3
link4
link5
link6
link7
link8
link9
link10
link11
link12
link13
link14
link15
link16
link17
link18
link19
link20
</div>
Browser support for columns is pretty good.
Hope this helps!

Get flexbox column wrap to use full width and minimize height

I have a container with a fixed width and variable height. I'm filling the container with an unknown amount of elements.
I'd like the elements to arrange themselves in columns, from top to bottom and then left to right.
I could use column, but I don't know the maximum width of the child elements, so I can't set a column-width or column-count.
I think display: flex with flex-flow: column wrap is the way to go, but if I maintain height: auto on the container, it will generate as a single column without wrapping elements to use all the available width.
Can I convince flexbox to use all the available width and thus minimize the container's height?
Would you suggest a different solution?
Fiddle: http://jsfiddle.net/52our0eh/
Source:
HTML:
<div>
<span>These</span>
<span>should</span>
<span>arrange</span>
<span>themselves</span>
<span>into</span>
<span>columns,</span>
<span>using</span>
<span>all</span>
<span>available</span>
<span>width</span>
<span>and</span>
<span>minimizing</span>
<span>the</span>
<span>container's</span>
<span>height.</span>
</div>
CSS:
div {
outline: 1px solid red;
width: 100%;
display: flex;
flex-flow: column wrap;
align-items: flex-start;
/*height: 8em;*/
}
span {
outline: 1px solid blue;
}
What you look for is more like the column rules: DEMO
div {/* do not set column numbers rule */
width: 100%;
-moz-column-width:4em;
column-width:4em;
-moz-column-gap:0;
column-gap:0;
-moz-column-rule:solid 1px;
column-rule:solid 1px;
text-align:center;
}
I've compromised and set height: 10em (which seems acceptable) along with overflow-y: auto (to add a horizontal scrollbar in case of overflow) on the container element.
I would still like to know if there is a way to use all available width and minimize the height, though.
In the end, your options for overflowing are hide, scroll, or wrap. How about this version instead? It takes any overflowing items and puts them on a second row. Items on the second row still fill the available space, but are larger due to the smaller number of items sharing the space.
http://jsfiddle.net/52our0eh/14/
div {
outline: 1px solid red;
display: flex;
flex-flow: row wrap;
}
span {
outline: 1px solid blue;
flex:1;
}

Force flex element not to grow in cross-axis direction

I'm making a page with a vertical menu using display: flex;. I want the menu's width to fit snuggly around a few buttons, without having to use a fixed width.
However, I also want the menu box to have a status message, which can have quite a long text. I'd like this status-div to have the width of the menu, while not forcing the menu container to grow its width. Instead, the status-div should grow its height and wrap the text.
Explaining this in words is pretty difficult, so I suggest you checkout out this fiddle: http://jsfiddle.net/bXL3q/
Note the difference when setting .statusmessage to display: none;.
Any ideas, or is what I'm trying to do not feasible? ..should it be?
What I've tried:
width: 100% fails, obviously it just assumes the parent width
width: -webkit-min-content sort of works, but it makes the element too narrow
flex-basis and flex-grow affect the height of the element, and do nothing to affect the width
position: absolute will solve the width issues, but now I have no way to define the height of the status-div.. (for the purpose of forcing a scroll bar in windows with small height - instead it will just flow over the button elements)
body {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
flex-flow: row nowrap;
align-items: stretch;
}
.page {
flex-grow: 1;
background-color: yellow;
}
.menu {
background-color: red;
height: 100%;
display: flex;
flex-flow: column nowrap;
}
.somechildren {
white-space: nowrap;
background-color: green;
border: 1px solid black;
}
.menu>* {
flex-shrink: 0;
}
.separate {
flex-grow: 1;
}
.statusmessage {
background-color: magenta;
align-self: flex-end;
/*display: none;*/
}
<div class=menu>
<div class=somechildren>I'd like the menu's</div>
<div class=somechildren>width to fit nicely</div>
<div class=somechildren>around these children</div>
<div class=separate></div>
<div class=statusmessage>
While forcing this status message to wrap and grow its height, without affecting the width of the container.
</div>
</div>
<div class=page>
The page
</div>
You were almost there with width. What you need to do is set width and min-width (demo):
.statusmessage {
width:0; /* Collapses .statusmessage so it doesn't affect column width */
min-width:100%; /* Expands .statusmessage to width of column */
}
The width can be (and probably should be) set to a value other than 0. It should just be the minimum width of the column or smaller. So use a value that works for you.
I've tested this on Chrome and Firefox and seems to work in both. Now, is it supposed to work? I'm not sure, I haven't read into the spec that much (it could be undefined). Make sure to test in all browsers you need it to work in. (And check the spec to see if this behavior is undefined/incorrect.)
width: 0; min-width: 100%; didn't work for me.
Instead, I set
position: relative;
on the flex child and wrapped its contents in an inner div with
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
This prevents the contents from contributing to the flex container size while still matching the cross-axis size determined by the rest of the flex children.
For me, the issue was that the default value of align-items is stretch. So the items stretch out the cross axis by default.
You can either set align-items: flex-start or align-self: flex-start for the single flex child.
The visual here is quite illustrative.

Flexible box layout model: How should auto margins in the cross-axis direction behave?

Please help me to understand one issue with the flexible box layout model for which I get different results in Firefox and Chrome.
Consider the following HTML fragment:
<body>
<header>Header</header>
<footer>Footer</footer>
</body>
styled via
body {
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
}
header {
max-width: 400px;
height: 20px;
background-color: yellow;
margin: 0 auto;
}
footer {
width: 400px;
height: 20px;
background-color: green;
margin: 0 auto;
}
The header box has a maximum width constraint of 400px while the footer has a fixed width of 400px. When I try this code in Gecko-based browsers (Firefox 21 and 24 in my case) both header and footer are horizontally centered (as I hoped for by giving them left and right auto margins) but only the footer has a width of 400px while the header's width is just the width of the content even if enough horizontal space was available.
In WebKit/Blink-based browsers (Chrome 25 and 28 in my case) the header and footers are both centered and are both 400px wide (in case there is enough horizontal space), and this is exactly what I want to achieve.
Obviously, either Firefox or Chrome must be wrong. How do you understand the spec: http://www.w3.org/TR/css3-flexbox/? What is the desired behaviour?
If you want to play around, here is a JSFiddle: http://jsfiddle.net/4Rv7K/.
Note that one has to enable the flexible box layout model in the release version of Firefox. It is the setting layout.css.flexbox.enabled. (Without it, one is actually not testing anything about flexboxes.)
P.S.: The bug was in Chromium's engine and has apparently been fixed by now: https://code.google.com/p/chromium/issues/detail?id=242654
The Firefox/Gecko behavior is correct.
WebKit is stretching up to 400px (the max-width) due to the header element's default "align-self: stretch" value. However, the spec is clear that "align-self: stretch" is only supposed to stretch if you have no auto margins in the cross axis. Quoting the spec:
If a flex item has ‘align-self: stretch’, [...] and neither of its
cross-axis margins are ‘auto’, the used outer cross size is the used
cross size of its flex line, clamped according to the item's min and
max cross size properties
http://www.w3.org/TR/css3-flexbox/#cross-sizing
The exception for "neither of its cross-axis margins are auto" is what Firefox is honoring here and WebKit/Blink appear to be ignoring.
Now, to achieve your desired layout: It looks like you want both stretchiness and centering, and I don't think you can get both of those things simultaneously in the cross axis.
You can get them simultaneously in the main axis of a flex container, though -- so you can fix this by adding a horizontal flex container around the header and the footer.
I've done that here:
http://jsfiddle.net/4Rv7K/16/
The relevant code (just with the 'header' for simplicity):
body {
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
}
horizFlex {
display: -webkit-flex;
display: flex;
}
header {
-webkit-flex: 1 0 auto;
flex: 1 0 auto;
max-width: 400px;
height: 20px;
background-color: yellow;
margin: 0 auto;
}
[...]
<body><horizFlex><header>Header</header></horizFlex></body>
I think this achieves the layout you're looking for by wrapping the header and footer each in a horizontal flex container. The flex container stretches to the width of its parent (the body), and inside of it we have a single flex item (e.g. the ), which is flexible up to its max-width, and which we center (with auto margins) once it has reached its max-width.
For an element that lacks a definite size with auto margins, it looks like the element's fit-content width is supposed to be used as the element's actual width while the remaining space is counted as margin. For Chrome, it appears to be behaving inappropriately only when using flex-direction: column.
http://codepen.io/cimmanon/pen/fuhyF
ul {
display: -webkit-flex;
display: flex;
height: 5em;
background: yellow;
}
li {
margin: auto;
border: 1px solid;
}
ul.column {
-webkit-flex-direction: column;
-flex-direction: column;
}
If you look at a list with the above styles, Opera, Firefox, and Chrome agree that the li elements are shrink wrapped when the direction is row. Under the column direction, only Firefox and Opera shrink wrap the li, while Chrome has the li take the full width of the flex container.

Resources