webta.st.ic
webta.st.ic

Reputation: 5179

Loop through nested SCSS list using key/value

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

Upvotes: 0

Views: 1301

Answers (2)

Brebber
Brebber

Reputation: 3084

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;

Upvotes: 1

Brebber
Brebber

Reputation: 3084

There are some huddles in your code ...

  1. 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.

  2. 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:

  1. Reorganize your map. You can do it easier: only store the values.
  2. In your mixin call direct the value you need. No loop necessary! You only need two special values which you can call targeted.
  3. 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;
}


Upvotes: 1

Related Questions