Loop through nested SCSS list using key/value - css

I have a nested scss list with some font definitions:
$font-cinzel: "Cinzel", serif;
$font-poppins: "Poppins", sans-serif;
$fonts: (
"title": (
"all": (
font-family: $font-cinzel
),
"sm": (
font-size: 26px,
line-height: 1.4
),
"lg": (
font-size: 80px,
line-height: 1.6
)
),
"text": (
"all": (
font-family: $font-poppins
),
"sm": (
font-size: 16px,
line-height: 1.2
),
"lg": (
font-size: 36px,
line-height: 1.4
)
)
);
Now I would like to write a mixin and a function to generate a styling block when passing the font name for each viewport. So all styles defined in all should be rendered outside of a media query (rule for all viewports to avoid redundancy). All other style which are unique for the viewport are defined in the viewport block e.g. all small styles are in sm, all large styles with different values in lg etc. I also have a breakpoint mixin, where I map the min-width by using the viewport name form the font list:
breakpoint mixin:
$breakpoints: (
sm: 768px,
md: 1024px,
lg: 1280px,
xl: 1400px
);
#function get-bp($bp) {
#if $bp {
$bp: map-get($breakpoints, $bp);
} #else {
#error "Parameter #{$vp} is unknown or empty.";
}
#return $bp;
}
This is how I try to loop the stuff:
#function get-value($definition, $vp) {
#each $prop, $val in map-get($definition, $vp) {
#return $val;
}
}
#mixin font($name) {
$definition: map-get($fonts, $name);
#each $key, $value in $definition {
#if $key == "all" {
$prop: get-value($definition, $key);
} #else {
#media (min-width: get-bp($key)) {
$prop: get-value($definition, $key);
}
}
}
}
h1 {
#include font('title');
}
p {
#include font('text');
}
Expected output:
h1 {
font-family: 'Cinzel', serif;
#media (min-width: 768px) {
font-size: 26px;
line-height: 1.4;
}
#media (min-width: 1280px) {
font-size: 80px;
line-height: 1.6;
}
}
p {
font-family: 'Poppins', sans-serif;
#media (min-width: 768px) {
font-size: 16px;
line-height: 1.2;
}
#media (min-width: 1280px) {
font-size: 36px;
line-height: 1.4;
}
}
I don't get any errors, but the styles are not available for the elements.
Here is the codepen: https://codepen.io/STWebtastic/pen/QWdOZPN

As to your update in your question:
Here is a mixin example to your updated requirements.
The example is done in a more general way for multiple use and a good readability.
So font-sizing-settings works as well as margins or even (nearly) every other property setting you would like ot adapt to the breakpoints. And once more with map-element selector it works on simple elements (tags), on classes, id's and more complex selectors as well.
Additional to the $rules map it is based on a breakpoint map. That make sure it works on all breakpoints adviced to the project. But rules to the breakpoints are only added to an element if noted in the rule map ... so you can say: it's an universal swiss knife.
EXAMPLE:
//###### SASS
// this example assumes:
// breakpoints allways min-width ...
$breakpoints: (
sm: 768px,
md: 1024px,
lg: 1280px,
xl: 1400px
);
$rules: (
title: (
selector: 'h1',
all: (
font-family: 'Arial',
font-size: 26px,
line-height: 1.4,
),
sm: (
font-size: 26px,
line-height: 1.4,
),
lg: (
font-size: 80px,
line-height: 1.6,
),
xl: (
font-size: 100px,
),
),
text: (
selector: 'p',
all: (
font-family: 'Courier New',
font-size: 26px,
line-height: 1.4,
),
sm: (
font-size: 16px,
line-height: 1.2,
),
lg: (
font-size: 36px,
line-height: 1.4,
)
),
) !default;
#mixin fontSizing($rule-map, $breakpoints: $breakpoints){
#each $element, $settings-map in $rule-map {
$selector: map-get($settings-map, selector);
// write generel rules
#{$selector} {
#each $property, $value in map-get($settings-map, all){
#{$property}: $value;
}
}
// rules for every breakpoint
#each $breakpoint, $breakpoint-setting in $breakpoints {
// only if breakpoint values set for element
#if map-has-key( $settings-map, $breakpoint ){
// write breakpoints rule
#media ( min-width: #{$breakpoint-setting} ){
#{$selector} {
#each $property, $value in map-get($settings-map, $breakpoint){
#{$property}: $value;
}
}
}
}//if
}//each
}//each
}//mixin
//##### CALL MIXIN
#include fontSizing($rules);
//##### COMPILES TO...
h1 {
font-family: "Arial";
font-size: 26px;
line-height: 1.4;
}
#media (min-width: 768px) {
h1 {
font-size: 26px;
line-height: 1.4;
}
}
#media (min-width: 1280px) {
h1 {
font-size: 80px;
line-height: 1.6;
}
}
#media (min-width: 1400px) {
h1 {
font-size: 100px;
}
}
p {
font-family: "Courier New";
font-size: 26px;
line-height: 1.4;
}
#media (min-width: 768px) {
p {
font-size: 16px;
line-height: 1.2;
}
}
#media (min-width: 1280px) {
p {
font-size: 36px;
line-height: 1.4;
}
}
ADDITIONAL HINT/IMPULSE:
It is possible to compress the rule map. In that case the code working is less, - but more specialised to single tags only and the pre-defined font-sizing settings ... and it is less readable. The general construction of the mixin would be the same but code writing would change a little bit as you work with predefined properties and nested lists instead of nested maps. Feel free to adapt the code. Here is an example for the possible compression to the rule-map:
$rule: (
h1: (
fontFamily: 'Arial',
all: (10px, 1.2),
sm: (12px, 1.4),
lg: (24px, 1.6),
),
p: (
fontFamily: 'Courier New',
all: (8px, 1.2),
sm: (10px, 1.4),
lg: (20px, 1.6),
xl: (24px, 1.8),
),
) !default;

There are some huddles in your code ...
The mixin uses an #each loop ... which is not needed but malicious as you don't want to write all classes at once but to use it to include only a special group of properties to a single class/selector (in your example you try to advice to h1 {...} only.
The error:
In your loop the mixins calls every element in map-get($fonts, $name)' which in your example is inner map title`.
Now for every element of this map (= fontFamily, sm, lg) it should do something ...
In your case you assume all elements are maps to get a value from it.
So the loop takes the first element fontFamily
There you try to get the value for a inner element fontFamily ...
SASS first checks the funciton map-get and realize fontFamily you named as map for the function is not a map ...
At that point SASS and ends ...
... and you are in trouble.
What you may do:
Reorganize your map. You can do it easier: only store the values.
In your mixin call direct the value you need. No loop necessary! You only need two special values which you can call targeted.
And don't forget to advice a line height which fits your new font-size ;-)
Code could look like this:
// ### SASS
$fonts: (
title: (
fontFamily: 'Arial',
sm: 12px,
lg: 24px,
),
text: (
fontFamily: 'Courier New',
sm: 10px,
lg: 20px,
),
) !default;
#mixin font( $item, $size ) {
font-family: map-get(map-get($fonts, $item), fontFamily );
font-size: map-get(map-get($fonts, $item), $size );
line-height: 1.2em;
}
h1 {
#include font( title, sm);
}
// ### Compiles to CSS
h1 {
font-family: "Arial";
font-size: 12px;
line-height: 1.2em;
}

Related

how to change text of button based on media query

I want to change text "proceed to payment" to "Buy now", if media query is 767px
const CheckoutButton = styled(GreenButton)
text-transform: none;
font-family: 'Poppins', Arial, Helvetica, sans-serif;
font-weight: 500;
height: 40px ;
font-size: 1rem;
padding: 6px 20px !important;
margin-left: 100px;
<CheckoutButton disabled={isSubmitting || disabled} onClick={handleSubmit} type="submit">Proceed to Payment</CheckoutButton>
In pure CSS, you can add the button's label via a pseudo-element:
.testbutton::before {
content: 'buy now';
}
#media (min-width: 767px) {
.testbutton::before {
content: 'proceed to payment';
}
}
<button class='testbutton'></button>
That being said, I'm not thrilled about having the button label in the CSS. In React, I always have a "uiStore" that allows components to keep track of the screen size and brakepoints, if they must. Then you could have something like:
const CheckoutButton = props => {
const { layout } = uiStore
return (
<button>
{layout === 'mobile' ? 'Buy now' : 'Proceed to payment'}
</button>
)
}
Possible Solution
Add/Remove inline text elements as per media query
Solution
React
<CheckoutButton disabled={isSubmitting || disabled} onClick={handleSubmit} type="submit"><span className="desktop-text">Proceed to Payment</span><span className="mobile-text"></span></CheckoutButton>
CSS
.mobile-text {display: none;} #media screen and (max-width: 767px) {.desktop-text {display: none;}.mobile-text {display: inline-block;}

How to make a mixin helper responsive using breakpoints?

I want to make this mixin responsive means it generate the media queries
based on breakpoints.
Here is my SCSS Code :
$grid-breakpoints: (
xs: 0,
sm: 576px,
md: 768px,
lg: 992px,
xl: 1200px,
xxl: 1400px
) !default;
#mixin overscroll-behavior-auto() {
-ms-scroll-chaining: chained;
overscroll-behavior: auto;
}
#mixin overscroll-behavior-contain() {
-ms-scroll-chaining: none;
overscroll-behavior: contain;
}
#mixin overscroll-behavior-none() {
-ms-scroll-chaining: none;
overscroll-behavior: none;
}
.overscroll-auto {
#include overscroll-behavior-auto();
}
.overscroll-contain {
#include overscroll-behavior-contain();
}
.overscroll-none {
#include overscroll-behavior-none();
}
#each $breakpoint in map-keys($grid-breakpoints) {
#include media-breakpoint-up($breakpoint) {}
}
I include my sass code thank you guys
Codepen
I made some modifications to the syntax of overscroll-behavior(). Mainly that its not overscroll-behavior-auto(), overscroll-behavior-contain() etc. any longer, but:
overscroll-behavior($behavior, $breakpoint);
This was necessary to not write the same code three times, since you can't generate mixins.
$overscrollBehavior: (
"auto": ["chained", "none" ],
"contain": ["none", "contain"],
"none": ["none", "none" ],
)!default;
$breakpoints: (
"xs": 0px,
"sm": 576px,
"md": 768px,
"lg": 992px,
"xl": 1200px,
"xxl": 1400px,
) !default;
#mixin overscroll-behavior($behavior, $breakpoint) {
$map: $overscrollBehavior;
#for $i from 1 through length($map) {
$label: nth(nth($map, $i), 1);
$values: nth(nth($map, $i), 2);
#if $behavior == $label {
$map: $breakpoints;
#for $i from 1 through length($map) {
$bp: nth(nth($map, $i), 1);
$bpValue: nth(nth($map, $i), 2);
#if $breakpoint == $bp {
#media (min-width: #{$bpValue}) {
-ms-scroll-chaining: unquote(nth($values, 1));
overscroll-behavior: unquote(nth($values, 2));
}
}
}
}
}
}
body {
#include overscroll-behavior(none, xs);
}
#media (min-width: 0px) {
body {
-ms-scroll-chaining: none;
overscroll-behavior: none;
}
}
If that's not quite what your looking for, let me know!

mixins overwriting media queried style

I have some JS functionality that changes the css class on my body element. Based on a click, i will change the css class of my body element to one of the following: .font-default, .font-medium, and .font-large.
I have the following mixins that determines the font size of an element based on the body element's class. That looks like this:
#function rem-calc($size) {
$remSize: $size / 16;
#return #{$remSize}rem;
}
#mixin font-size($size) {
#if $size >= 20 {
font-size: rem-calc($size);
} #else if $size == 18 {
font-size: rem-calc(18);
.font-large & {
font-size: rem-calc(20);
}
} #else {
font-size: rem-calc($size);
.font-medium & {
font-size: rem-calc($size + 2);
}
.font-large & {
font-size: rem-calc($size + 4);
}
}
}
An example of me using this mixin is as follows:
.content {
#include font-size(16);
#media (min-width: 1024px) {
#include font-size(30);
}
}
Here's is the corresponding html and css on the linked codepen at the bottom:
<body class="body">
<div class="content">Content</div>
<button class="button">
click to add
</button>
</body>
<script>
const button = document.querySelector('.button')
button.addEventListener('click', () => {
console.log(document.querySelector('.body'))
document.querySelector('.body').classList.add('font-medium');
})
</script>
According to my ruleset (the mixin), in the desktop version, font-size should remain unchanged since size >= 20. However in practice, when I click the button that changes the class to medium, it uses the "mobile" version of the style and overwrites the style that's placed in the media query.
Is there anyway regarding specificity such that I can still use this mixin so that the mobile styles don't bleed into the styles nested in the media queries?
If not, what might be a different solution to this problem?
Here's a pen that shows the issue. When clicking the button, I want the font to remain unchanged. https://codepen.io/rv-akim/pen/WVJpWj
You can clearly see that .font-medium .content is overriding .content due to the fact the former is more specific even though .content is inside of a media query.
Update your code so your normal state of the font size uses a class
#mixin font-size($size) {
#if $size >= 20 {
.normal & {
font-size: rem-calc($size);
}
} #else if $size == 18 {
.normal & {
font-size: rem-calc(18);
}
.font-large & {
font-size: rem-calc(20);
}
} #else {
.normal & {
font-size: rem-calc($size);
}
.font-medium & {
font-size: rem-calc($size + 2);
}
.font-large & {
font-size: rem-calc($size + 4);
}
}
}
.content {
#include font-size(16);
#media (min-width: 1024px) {
#include font-size(30);
}
}
Add class normal to your body tag
<body class="body normal">
Basically, where you only declared the font size rule, I wrapped it with .normal & {}
If you learn to use the Inspector, it will save you tons of headaches later

Using SASS to pass in a variable into a mixin where the declaration is a string

I am building a SASS mixin that allows me to pass in various vars to build a font-awesome declaration like so:
#mixin after-font-awesome($unicode, $size, $margin, $color, $weight) {
&:after {
content:$unicode;
font-family:"FontAwesome";
font-size:$size;
color:$color;
font-weight:$weight;
margin:$margin;
#content;
}
}
the usage would be as such:
#include after-font-awesome('\f078', 15px, 0 0 0 0.3em, orange, 900) {}
which requires me to pass in the unicode with '\XXX' the single quotes and the slash as a string. this works, but i am trying to do the following where i pass in just the unicode numbers such as f078, and when it gets to the mixin it prints it as '\f078'. after reading some documentation i tried as such:
#mixin after-font-awesome($unicode, $size, $margin, $color, $weight) {
&:after {
content:'\#{$unicode}';
font-family:"FontAwesome";
font-size:$size;
color:$color;
font-weight:$weight;
margin:$margin;
#content;
}
}
or
#mixin after-font-awesome($unicode, $size, $margin, $color, $weight) {
&:after {
content:'\{$unicode}';
font-family:"FontAwesome";
font-size:$size;
color:$color;
font-weight:$weight;
margin:$margin;
#content;
}
}
or
#mixin after-font-awesome($unicode, $size, $margin, $color, $weight) {
&:after {
content:'\$unicode';
font-family:"FontAwesome";
font-size:$size;
color:$color;
font-weight:$weight;
margin:$margin;
#content;
}
}
but alas it did not work. any help would be appreciated in figuring this out with me.
I had same issue with my project.
$unicode: f0ca;
$withslash: "\"\\#{$unicode}\"";
I tried this. Hope it helps.
The nuances of the backslash behavior is an ongoing issue in Sass (e.g.,
libsass#1115).
That being said, there are a few hacks provided in sass#659, but they are strongly dependent on the version and implementation of Sass. The solution provided by andrewgrewell seemed to work well for many people but in any case see which solutions/hacks work for you. SassMeister Demo
#mixin after-font-awesome($unicode, $size, $margin, $color, $weight) {
&:after {
content: #{"\"\\"}#{$unicode + "\""}; // <-- andrewgrewell's hack
font-family: "FontAwesome";
font-size: $size;
color: $color;
font-weight: $weight;
margin: $margin;
#content;
}
}
// Usage
.selector {
#include after-font-awesome('f078', 15px, 0 0 0 0.3em, orange, 900);
}
// CSS Output
.selector:after {
content: "\f078";
font-family: "FontAwesome";
font-size: 15px;
color: orange;
font-weight: 900;
margin: 0 0 0 0.3em;
}

Multiple maps, one loop Sass

I have two different maps in my _variables.scss file:
$breakpoints: (
bkp-320: 320px,
bkp-480: 480px,
bkp-768: 768px,
bkp-992: 992px,
bkp-1200: 1200px
);
$fontsizes: (
tiny: (
null: 12px,
bkp-768: 11px
),
small: (
null: 14px,
bkp-768: 13px
),
base: (
null: 16px,
bkp-768: 15px
),
large: (
null: 18px,
bkp-768: 17px
)
);
I would combine them in a "#mixin / function font-size" and recall it in my scss:
.selector{
#import font-size(small);
}
to produce in my css something like this:
.selector {
/* null: default value of font-size, not need mediaqueries */
font-size: 14px;
}
#media only screen and (min-width: 768px) {
.selector {
font-size: 13px;
}
}
thxxx
you could try to change $fontsizes map with this structure
small: (
default: 14px,
resolutions:(
(
breakpoint: 'bkp-768',
default: 13px
),
(
breakpoint: 'bkp-480',
default: 20px
)
)
),
and call mixin font-size with a key
#function getMap($map,$key) {
#if map-has-key($map, $key) {
#return map-get($map, $key);
}
#warn "Unknown `#{$key}` in $map.";
#return null;
}
// font-size
#mixin font-size($key){
$fontsize: getMap($fontsizes, $key);
$resolutions: getMap($fontsize, resolutions);
font-size: getMap($fontsize, default);
#each $resolution in $resolutions {
#media only screen and (min-width: getMap($breakpoints,getMap($resolution, breakpoint)) ) {
font-size: getMap($resolution, 'default');
}
};
}
p {
#include font-size(small);
}
h1 {
#include font-size(large);
}

Resources