Search code examples
sasssass-maps

Auto generated sass maps


I'm trying to generate an automated system to define colors in SASS.

I have a map of colors named $brand-colors, and I would like the colors of this map to be used to generate the tints and shades in a second map, no matter how many colors there are in $ brand-colors.

This is the point where I arrived:

$brand-colors: (
  brand-color: (
    primary-color:        #0096d6,
    secondary-color:      #1a4760,
  ),
) !default;

@function generate-map($map) {
  @each $item, $colors in $map {
    @each $color-name, $value in $colors {
      @return(
        $color-name: (
          light-30:   mix(white, $value, 30%),
          light-20:   mix(white, $value, 20%),
          light-10:   mix(white, $value, 10%),
          base:       $value,
          dark-10:    mix(black, $value, 10%),
          dark-20:    mix(black, $value, 20%),
          dark-30:    mix(black, $value, 30%),
        ),
      );
    };
  };
};

$brand-palette: (
  brand-palette:(
    generate-map($_new-brand-colors)
  ),
) !default;

With the above code, I get this result from the terminal:

brand-palette:(
  primary-color:(
    light-30: #4db6e2,
    light-20: #33abde,
    light-10: #1aa1da,
    base: #0096d6,
    dark-10: #0087c1,
    dark-20: #0078ab,
    dark-30: #006996
  )
)

In short, only the first key-value pair is taken, and I can not understand why. Can someone give me an answer?


Solution

  • Functions and return statements

    The @return statement tells the function to stop everything and return the value.

    @function myFunction() {
        @each $item in [a, b, c, d] {
            @return $item;
        }
    }
    

    myFunction() will only return a. Even though we have a loop with four items, it will run only once (the first time) because it immediately hits the @return statement.


    Best practice to return once at end of function

    One best practice with functions is to have a $result variable, and only call @return $result once at the end of the function

    @function myFunctionFixed() {
        $result;
        @each $item in [a, b, c, d] {
            $result: $result + a;
        }
        @return $result;
    }
    

    myFunctionFixed() will return abcd because we allow the loop to run from beginning to end without interupting it with @return, and we only return at the end of the function after the loop has completed.


    Using map-merge to incrementally build a map

    Applying the best practice described above, we can move the @return statement to the end of your function and use map-merge to incrementally build a $result-map variable.

    @function generate-map($map) {
        $result-map: () !default;
        @each $item, $colors in $map {
            @each $color-name, $value in $colors {
                $result-map: map-merge($result-map, 
                    ($color-name: (
                      light-30:   mix(white, $value, 30%),
                      light-20:   mix(white, $value, 20%),
                      light-10:   mix(white, $value, 10%),
                      base:       $value,
                      dark-10:    mix(black, $value, 10%),
                      dark-20:    mix(black, $value, 20%),
                      dark-30:    mix(black, $value, 30%),
                    ))
                );
            };
        };
        @return $result-map;
    };