SASS #each skip specific line(s) if variable exists - css

I'm attempting to build a grid system using SASS and so far so good however if the breakpoint is 'xs' I'd like to skip the #media #{$query} { and } line for this as the xs no longer needs a media query.
Can anyone advise?
$default__gridColumns: 18;
$default__flexColumns: 12;
$default__gutter: 20px;
$default__breakpoints: (
'xs': 'screen and (max-width: 767px)',
'sm': 'screen and (min-width:768px) and (max-width:1023px)',
'md': 'screen and (min-width:1024px) and (max-width:1200px)',
'lg': 'screen and (min-width:1201px) and (max-width:1440px)'
);
.flex {
display: flex;
margin: 0 -$default__gutter;
padding: 0 $default__gutter;
> *[class^="span__"] {
padding: 0 $default__gutter;
position: relative;
}
#each $breakpoint, $query in $default__breakpoints {
#media #{$query} {
#for $i from 1 through $default__flexColumns {
> .span__#{$breakpoint}--#{$i} {
width: $i/$default__flexColumns * 100%;
}
> .span__#{$breakpoint}--offsetLeft-#{$i} {
margin-left: $i/$default__flexColumns * 100%;
}
> .span__#{$breakpoint}--offsetRight-#{$i} {
margin-right: $i/$default__flexColumns * 100%;
}
> .span__#{$breakpoint}--push-#{$i} {
left: $i/$default__flexColumns * 100%;
}
> .span__#{$breakpoint}--pull-#{$i} {
right: $i/$default__flexColumns * 100%;
}
> .visible__#{$breakpoint} {
display: block !important;
}
> .visible__#{$breakpoint}--flex {
display: flex !important;
}
> .visible__#{$breakpoint}--inline {
display: inline !important;
}
> .visible__#{$breakpoint}--inlineBlock {
display: inline-block !important;
}
> .hidden__#{$breakpoint}--inlineBlock {
display: none !important;
}
}
}
}
}

Updated to use a #mixin.
You can use an #if directive to test the value of the $breakpoint:
$default__gridColumns: 18;
$default__flexColumns: 12;
$default__gutter: 20px;
$default__breakpoints: (
'xs': 'screen and (max-width: 767px)',
'sm': 'screen and (min-width:768px) and (max-width:1023px)',
'md': 'screen and (min-width:1024px) and (max-width:1200px)',
'lg': 'screen and (min-width:1201px) and (max-width:1440px)'
);
#mixin flex_column( $breakpoint ) {
#for $i from 1 through $default__flexColumns {
> .span__#{$breakpoint}--#{$i} {
width: $i/$default__flexColumns * 100%;
}
> .span__#{$breakpoint}--offsetLeft-#{$i} {
margin-left: $i/$default__flexColumns * 100%;
}
> .span__#{$breakpoint}--offsetRight-#{$i} {
margin-right: $i/$default__flexColumns * 100%;
}
> .span__#{$breakpoint}--push-#{$i} {
left: $i/$default__flexColumns * 100%;
}
> .span__#{$breakpoint}--pull-#{$i} {
right: $i/$default__flexColumns * 100%;
}
> .visible__#{$breakpoint} {
display: block !important;
}
> .visible__#{$breakpoint}--flex {
display: flex !important;
}
> .visible__#{$breakpoint}--inline {
display: inline !important;
}
> .visible__#{$breakpoint}--inlineBlock {
display: inline-block !important;
}
> .hidden__#{$breakpoint}--inlineBlock {
display: none !important;
}
}
}
.flex {
display: flex;
margin: 0 -$default__gutter;
padding: 0 $default__gutter;
> *[class^="span__"] {
padding: 0 $default__gutter;
position: relative;
}
#each $breakpoint, $query in $default__breakpoints {
#if $breakpoint == xs {
#include flex_column( #{$breakpoint} )
} #else {
#media #{$query} {
#include flex_column( #{$breakpoint} )
}
}
}
}
There's not really a way to skip just the lines you want because of how the CSS will compile with the brackets present in the #media query var.

You can use map-remove
$default__breakpoints: (
'xs': 'screen and (max-width: 767px)',
'sm': 'screen and (min-width:768px) and (max-width:1023px)',
'md': 'screen and (min-width:1024px) and (max-width:1200px)',
'lg': 'screen and (min-width:1201px) and (max-width:1440px)'
);
.flex {
// ...
$custom_breakpoints: map-remove($default__breakpoints, 'xs');
#each $breakpoint, $query in $custom_breakpoints {
#media #{$query} {
// code
}
}
}
It returns a copy of the map so you don't have to worry about it being removed from your $default__breakpoints variable.
EDIT: I misunderstood your question. You could do that with #at-root although that does add one extra indent:
$default__breakpoints: (
'xs': 'screen and (max-width: 767px)',
'sm': 'screen and (min-width:768px) and (max-width:1023px)',
'md': 'screen and (min-width:1024px) and (max-width:1200px)',
'lg': 'screen and (min-width:1201px) and (max-width:1440px)'
);
.flex {
// ...
#each $breakpoint, $query in $default__breakpoints {
#media #{$query} {
#at-root (with: if($breakpoint == 'xs', rule, all)) {
.selector {
content: 'value';
}
}
}
}
}
will compile to
.flex .selector {
content: "value";
}
#media screen and (min-width:768px) and (max-width:1023px) {
.flex .selector {
content: "value";
}
}
#media screen and (min-width:1024px) and (max-width:1200px) {
.flex .selector {
content: "value";
}
}
#media screen and (min-width:1201px) and (max-width:1440px) {
.flex .selector {
content: "value";
}
}

Related

Problem with CSS breakpoints selectors override with SASS

I have a _display.scss partial.
It contains #mixin and classes related to display CSS property.
_display.scss
#mixin d-block{
display: block;
}
#mixin d-none{
display: none;
}
.d-block{
#include d-block();
}
.d-none{
#include d-none();
}
I developer a #mixin generate-responsive-content that take the #content of a class and generate a different #media query for each breakpoint.
In this way:
.d-block{
#include generate-responsive-content() {
#include d-block();
}
}
.d-none{
#include generate-responsive-content() {
#include d-none();
}
}
// Generate all breakpoints from content
#mixin generate-responsive-content() {
// Responsive styles
// Loop over each size
#each $breakName, $width in $breakpoints {
// Check breakpoint
#if ($breakName != "") {
$breakName: '-' + $breakName;
#media (min-width: $width) {
&#{$breakName} {
#content
}
}
} #else {
#content;
}
}
}
eg. generated classes: .d-block, .d-block-xs, .d-block-sm...
But in this way, I cannot override the classes of .d-none with the classes of .d-block for each breakpoint because they have been generated before and are overwritten by those of .d-none.
I also have a class with the same name but without breakpoint variant, like d-none-lg, d-block-lg, these overwrite all others.
Check this CodePen. Here d-none variants overwrite every class of d-block.
How I can solve that?
I have created a quick demo for you - https://codepen.io/rhythm19/pen/OJVMyLa and its working as expected. I think you just need to swap the order. Generate d-none classes first and then d-block classes.
.d-none{
#include generate-responsive-content() {
#include d-none();
}
}
.d-block{
#include generate-responsive-content() {
#include d-block();
}
}
Updated answer to include max-width breakpoint.
.see{outline:1px solid black;padding:1em;}
// BREAKPOINT
$breakpoints: (
"xs": 575px,
"sm": 576px,
"md": 768px,
"lg": 992px,
"xl": 1200px
);
#mixin d-block() {
display: block;
}
#mixin d-none() {
display: none;
}
.d-block{
#include d-block();
}
.d-none{
#include d-none();
}
// Generate all breakpoints from content
#mixin generate-responsive-content() {
// Responsive styles
// Loop over each size
#each $breakName, $width in $breakpoints {
// Check breakpoint
#if ($breakName == 'xs' ) {
$breakName: '-' + $breakName;
#media (max-width: $width) {
&#{$breakName} {
#content
}
}
}
#else if ($breakName != 'xs' ) {
$breakName: '-' + $breakName;
#media (min-width: $width) {
&#{$breakName} {
#content
}
}
} #else {
#content;
}
}
}
.d-block{
#include generate-responsive-content() {
#include d-block();
}
}
.d-none{
#include generate-responsive-content() {
#include d-none();
}
}
This is what is output:
.see {
outline: 1px solid black;
padding: 1em;
}
.d-block {
display: block;
}
.d-none {
display: none;
}
#media (max-width: 575px) {
.d-block-xs {
display: block;
}
}
#media (min-width: 576px) {
.d-block-sm {
display: block;
}
}
#media (min-width: 768px) {
.d-block-md {
display: block;
}
}
#media (min-width: 992px) {
.d-block-lg {
display: block;
}
}
#media (min-width: 1200px) {
.d-block-xl {
display: block;
}
}
#media (max-width: 575px) {
.d-none-xs {
display: none;
}
}
#media (min-width: 576px) {
.d-none-sm {
display: none;
}
}
#media (min-width: 768px) {
.d-none-md {
display: none;
}
}
#media (min-width: 992px) {
.d-none-lg {
display: none;
}
}
#media (min-width: 1200px) {
.d-none-xl {
display: none;
}
}
UPDATED CODEPEN: Here's the OPs codepen, with this update:
https://codepen.io/chrislafrombois/pen/JjdGKGJ
Here is the code from the pen:
.see {
outline: 1px solid black;
padding: 1em;
}
.d-block {
display: block;
}
.d-none {
display: none;
}
#media (max-width: 575px) {
.d-block-xs {
display: block;
}
}
#media (min-width: 576px) {
.d-block-sm {
display: block;
}
}
#media (min-width: 768px) {
.d-block-md {
display: block;
}
}
#media (min-width: 992px) {
.d-block-lg {
display: block;
}
}
#media (min-width: 1200px) {
.d-block-xl {
display: block;
}
}
#media (max-width: 575px) {
.d-none-xs {
display: none;
}
}
#media (min-width: 576px) {
.d-none-sm {
display: none;
}
}
#media (min-width: 768px) {
.d-none-md {
display: none;
}
}
#media (min-width: 992px) {
.d-none-lg {
display: none;
}
}
#media (min-width: 1200px) {
.d-none-xl {
display: none;
}
}
<div class="see">
<span>I CANNOT SEE ANYTHING BECAUSE d-none OVERWRITE EVERYTHING</span>
<div class="d-none d-none-xs d-block-sm d-block-md d-block-lg">
CHECK CSS STYLE D-BLOCK-LG OVERWRITE EVERITHING
</div>
</div>
Per our discussion, you should not try and put the default d-none and d-block into this mixin. Because of how the code will output, you should just separate that concern and place the defaults before the media query blocks.

Wrap class list with media queries and suffix in SASS

I'm looking for a way to generate responsive utility classes in SASS. I had this CSS
.text-left { text-align: left; }
.text-right { text-align: right; }
#media (min-width: 480px) {
.text-left-sm { text-align: left; }
.text-right-sm { text-align: right; }
}
#media (min-width: 800px) {
.text-left-md { text-align: left; }
.text-right-md { text-align: right; }
}
and I would like to add some more, but I don't want to repeat myself. It would be best if SASS could generate all those responsive (media query) variants for me. So far I was able to write a mixin that I could call with suffix param and get what I want
#mixin textalign($suffix: "") {
.text-left#{$suffix} { text-align: left; }
.text-right#{$suffix} { text-align: right; }
}
#include textalign();
#media (min-width: 480px) {
#include textalign("-sm");
}
#media (min-width: 600px) {
#include textalign("-lg");
}
but I would like to go one step further and be able to do something like this
/* Unfortunatelly this doesn't work */
#include generate-responsive() {
.text-left { text-align: left; }
.text-right { text-align: right; }
}
Is there a way to achieve something like this? Having a general purpose mixin that I can use to generate all kind of utility classes?
I don't think you can accomplish your goal when nesting your selector in the #include, but you can do it when nesting the #include inside the selector.
SCSS input:
#mixin generate-responsive() {
// Create a list of sizes and widths
$sizes: (
sm: "480px",
md: "600px",
lg: "800px"
);
// Base style, without a suffix
#content;
// Responsive styles
// Loop over each size
#each $suffix, $width in $sizes {
#media (min-width: $width) {
&-#{$suffix} { #content; }
}
}
}
.text-left {
#include generate-responsive() {
text-align: left;
}
}
// You'll have to include the mixin for every class
.text-right {
#include generate-responsive() {
text-align: right;
}
}
CSS output:
.text-left {
text-align: left;
}
#media (min-width: 480px) {
.text-left-sm {
text-align: left;
}
}
#media (min-width: 600px) {
.text-left-md {
text-align: left;
}
}
#media (min-width: 800px) {
.text-left-lg {
text-align: left;
}
}
.text-right {
text-align: right;
// Etc...

SASS: Reverse map-keys

I have a problem with the generated CSS order.
An element should not be visible on phones (but on tablets & desktops), so it gets the following class:
.d-none .d-tablet-block
In the CSS file 'd-none' comes after 'd-tablet-block'. Thus 'd-tablet-block' is always overwritten.
This is my current CSS. It is generated by a SASS Mixin.
#media (min-width: 840px) {
.d-desktop-inline {
display: inline!important
}
}
#media (min-width: 480px) {
.d-tablet-inline {
display: inline!important
}
}
.d-inline {
display: inline!important
}
I need the same CSS but in that order
.d-inline {
display: inline!important
}
#media (min-width: 480px) {
.d-tablet-inline {
display: inline!important
}
}
#media (min-width: 840px) {
.d-desktop-inline {
display: inline!important
}
}
My breakpoints come from Google Web Components. If I put Phone up here and Desktop down, the CSS will be generated in the correct order.
But unfortunately other components of Google Web Components will not work anymore.
$mdc-layout-grid-breakpoints: (
desktop: 840px,
tablet: 480px,
phone: 0
);
Here is the SASS Mixin:
#function breakpoint-infix($name, $breakpoints: $mdc-layout-grid-breakpoints) {
#return if(breakpoint-min($name, $breakpoints) == null, "", "-#{$name}");
}
#function breakpoint-min($name, $breakpoints: $mdc-layout-grid-breakpoints) {
$min: map-get($breakpoints, $name);
#return if($min != 0, $min, null);
}
#mixin media-breakpoint-up($name, $breakpoints: $mdc-layout-grid-breakpoints) {
$min: breakpoint-min($name, $breakpoints);
#if $min {
#media (min-width: $min) {
#content;
}
} #else {
#content;
}
}
#each $breakpoint in map-keys($mdc-layout-grid-breakpoints) {
#include media-breakpoint-up($breakpoint) {
$infix: breakpoint-infix($breakpoint, $mdc-layout-grid-breakpoints);
.d#{$infix}-none { display: none !important; }
.d#{$infix}-inline { display: inline !important; }
.d#{$infix}-inline-block { display: inline-block !important; }
.d#{$infix}-block { display: block !important; }
.d#{$infix}-table { display: table !important; }
.d#{$infix}-table-row { display: table-row !important; }
.d#{$infix}-table-cell { display: table-cell !important; }
.d#{$infix}-flex { display: flex !important; }
.d#{$infix}-inline-flex { display: inline-flex !important; }
}
}
Can I somehow reverse the map-keys ($ mdc-layout-grid-breakpoints)?

SASS media queries best practise?

Is it ok to nest media queries inside an element? If I want to use min-width: 480px in another places there will be huge repetition. Please look at my code example. Or just use the old way? Any idea?
SASS
.navbar {
height: 500px;
width: 500px;
#media screen and (min-width: 480px) {
background-color: lightgreen;
}
}
.items {
padding: 15px;
color: red;
#media screen and (min-width: 480px) {
border: 1px solid black;
}
}
CSS
#media screen and (min-width: 480px) {
.navbar {
background-color: lightgreen;
}
.items {
border: 1px solid black;
}
}
$pc: 1024px; // PC screen size.
$tablet: 720px; // Tablet screen size.
$phone: 320px; // Phone screen size.
#mixin responsive($media) {
#if $media= phone {
#media only screen and (max-width: $tablet - 1) {
#content;
}
}
#else if $media= tablet {
#media only screen and (min-width: $tablet - 1) and (max-width: $pc) {
#content;
}
}
#else if $media= pc {
#media only screen and (min-width: $pc + 1) and (min-width: $pc) {
#content;
}
}
#else if $media= pc_tablet {
#media only screen and (min-width: $tablet - 1) {
#content;
}
}
}
Examples
body {
#include responsive(pc) {
background: red;
}
#include responsive(tablet) {
background: yellow;
}
#include responsive(phone) {
background: green;
}
}

Less important mixin

I've a mixing in order to generate some utilities class like that :
.margin(#name, #size){
.#{name} {margin: (#size)px;}
#media (max-width: 767px) {
.xs-#{name} {margin: (#size)px;}
}
}
It's working fine, but when I call
.margin(m-n, 0) !important;
The css generated is :
.m-n {
margin: 0 !important;
}
#media (max-width: 767px) {
.xs-m-n {
margin: 0;
}
}
But I would like to have :
.m-n {
margin: 0 !important;
}
#media (max-width: 767px) {
.xs-m-n {
margin: 0 !important;
}
}
Any idea ?
Here is a solution that works with a supplementary param
.margin(#name, #size, #important: false){
.#{name} when (#important = false){
margin: (#size)px;
}
.#{name} when (#important = true) {
margin: (#size)px !important;
}
#media (max-width: 767px) {
.xs-#{name} when (#important = false) {
margin: (#size)px;
}
.xs-#{name} when (#important = true) {
margin: (#size)px !important;
}
}
}
Then you can simply call .margin(m-n, 0) or .margin(m-n, 0, true)

Resources