Using passed Variable as Key/Value pair in Mixins - sass

I tried looking this up, but have not had success. I could not be looking with the right search parameters.
I'm creating a mixin in SASS the will allow me to create keyframes by passing the animation name, from value and to value. Here's an example:
#mixin keyframes($name, $from, $to) {
#-webkit-keyframes #{$name} {
from {
left: $from
}
to {
left: $to
}
}
}
This is a shorter version as I would also add lines for #-moz-keyframes and #keyframes. I prefer this method so that I'm not having to repeat "from" and "to" in the animations and having the mixin just grab it using #content, but I also don't want to assume that "left" is the only property that is going to be affected.
What I'd like to do is treat both $from and $to variables as Objects so that they can contain a series of key/value pairs. When I attempt this:
$mixin keyframes($name, $from, $to) {
#-webkit-keyframes #{$name} {
from {
$from
}
to {
$to
}
}
}
...I get compile errors because it's excepting a key/value pair and not a variable.
Is there a way to tell SASS to treat $from and $to as a series of key/value pairs? I tried #{$from} already and it still throws that compile error.
Thanks!

You can't do that because property/values aren't strings. You would have to write it using mappings, like this:
#mixin keyframes($name, $from, $to) {
#-webkit-keyframes #{$name} {
from {
#each $prop, $val in $from {
#{$prop}: $val;
}
}
to {
#each $prop, $val in $to {
#{$prop}: $val;
}
}
}
}
#include keyframes(foo, (top: 10px), (top: 50px));
However, I would recommend not doing this at all if your goal is to write a flexible mixin. Just write out your own from/to statements:
#mixin keyframes($name) {
#-webkit-keyframes #{$name} {
#content
}
}
#include keyframes(foo) {
from {
top: 10px;
}
to {
top: 50px;
}
}

Related

global var update from scss function

I have a scss function
#function darken($color,$percent) {
...
#return $calculated-color;
}
This function overrides the darken() function, but adds some extra features.
Now, I wonder if it is possible to somehow store all calls to this function in some map and then after all function calls has been made run that map trough a mixin such as:
$calc-colors:() !global;
$calc-colors:map-merge(('thisvaluewillbeexported':1),$calc-colors);
#function test($color,$percent) {
$col: darken($color,$percent);
// $calc-colors: append($calc-colors,'--'$color); --not working
// $calc-colors:map-merge(('--'+$color:$col),$calc-colors); --not working
#return $col;
}
.test {
color:test(pink,24%);
}
.test2 {
color:test(red,24%);
}
:export{
#each $bp, $value in $calc-colors {
#{$bp}: #{$value};
}
}
//gives only thisvaluewillbeexported:1
My goal would to somehow get all calls to my test function recorded into the :export{} attribute in order to be able to fetch the values from javascript.
// My preferred output would be:
{
'thisvaluewillbeexported':1,
'--pink':'#ff4666',
'--red':'#850000'
}
You should set variable !global inside function.
Sassmeister demo.
Article about variable scope in Sass.
#function set-color($color-name, $darken-ration) {
$darken-color: darken($color-name, $darken-ration);
$calc-colors: map-merge($calc-colors, ('--' + $color-name: $darken-color)) !global;
#return $darken-color;
}
$calc-colors: map-merge(('thisvaluewillbeexported': 1), ());
a {
color: set-color(green, 10%);
}
b {
color: set-color(red, 10%);
}
c {
#each $name, $value in $calc-colors {
#{$name}: #{$value};
}
}
Css output:
a {
color: #004d00;
}
b {
color: #cc0000;
}
c {
thisvaluewillbeexported: 1;
--green: #004d00;
--red: #cc0000;
}

Using a sass variable in an #each loop

I'm trying to create a little overview for all the colors we use in our corporate identity. All our colors have been defined in _settings-colors.scss, and the only reason I need this bit of css is for the library, where the colors need to be listed.
What I have now is as follows:
$colors-brand: color-brand, color-brand-40, color-brand-60, color-brand-70;
.prfx-color {
display: block;
height: 5rem;
width: 100%;
#each $color in $colors-brand {
&--#{$color} {
background-color: #{'$'+$color};
&::after {
content: '$'+$color;
}
}
}
}
These color-brand variables are set in another file which I'm including in this scss file.
The code above outputs this:
.prfx-color {
display: block;
height: 5rem;
width: 100%;
}
.prfx-color--color-brand {
background: $color-brand;
}
.prfx-color--color-brand::after {
content: "$color-brand";
} [...etc]
What I'm after however, is this:
.prfx-color--color-brand {
background: #00ff11; // don't worry, brand is not actually this color
}
The problem I'm having is that the $color-brand variable isn't interpreted as a sass variable anymore, but is a literal value. I need the #hheexx that this variable refers to!
All the solutions I've found so far consist of using two lists, or a key-value pair. In my situation these variables have already been set once, and I want a solution where I don't want to have to manually edit the library if the colors change.
Is this at all possibe, or am I too greedy here?
And I realized I overcomplicated it. You don't need any extra functions because the #each is designed to work with maps and iterating over multiple values.
$cool: blue;
$mad: red;
$colors: (
cool: $cool,
mad: $mad
);
.prfx-color {
#each $key, $val in $colors {
&--#{$key} {
background-color: $val;
&::after { content: "$#{$key}"; }
}
}
}
You could use a map.
Here's a sassmeister playground for you.
$cool: blue;
$mad: red;
$colors: (
cool: $cool,
mad: $mad
);
.prfx-color {
#each $color in map-keys($colors) {
&--#{$color} {
background-color: map-get($colors, $color);
&::after { content: "$#{$color}"; }
}
}
}

Avoid repeat the same mixin in Sass

I have this Mixin for padding utility:
Sass code:
$padding: (
top: "top",
right: "right",
bottom: "bottom",
left: "left",
all: "all"
);
#mixin no-padding($map) {
#each $padding-side, $side in $map {
#if $side == 'all' {
& {
padding: 0 !important;
}
} #else {
&-#{$side} {
padding-#{$side}: 0 !important;
}
}
}
}
Use of it:
.u-noPadding {
#include no-padding($padding);
}
I want to use the same Mixin but now for margin, is there any solution to avoid repeating the same mixin and make a good use of best practices?
#mixin no($type,$sides:null) {
$i:0 !important;
#if $sides == null {
#{$type}:$i;
} #else {
#each $side in $sides {
#{$type}-#{$side}:$i;
}
}
}
.u-noPadding {
#include no(padding, top left etc...); // choose any side separated with a space
}
.u-noMargin {
#include no(margin); // instead of 'all', type nothing
}
Like this? Your $sides will be stored in a temporary map automatically if your second parameter is set, no need extra map for this.
About the second parameter: If you want no sides, let it empty and all sides will have 0. Similiar to your 'all' idea.. it's shorter.

Conditionally output a part of SCSS rule selector

I would like to specify an additional default shortcut class to a set of classes, similarly to that
#each $pos, $some-css-rules in ("left": ..., "right": ..., ...) {
#if $pos == "left" {
.block,
}
.block-#($pos) {
...
}
}
that would be outputted as
.block,
.block-left {
...
}
.block-right {
...
}
However, it will stumble over .block, syntax error.
.block-left cannot be replaced here with .block.left because $pos will collide with existing classes (.left, etc).
I would prefer to avoid .block { #extend .block-left } if possible, there is a considerable amount of similar rules that will gain a lot of WET code this way.
Is there a way to conditionally output a part of rule selector? How can both SCSS and CSS be kept DRY in a pattern like that?
I'm not sure if I understand the question but I achieve the output CSS based on your code. I put the #if directive inside the selector to compare with $pos variable. Here is my code:
SASS
#each $pos, $some-css-rules in ("left": red, "right": blue) {
.block-#{$pos} {
#if $pos == "left" {
#at-root .block, &{
color:$some-css-rules;
}
}
#else{
color:$some-css-rules;
}
}
}
Output
.block, .block-left {
color: red;
}
.block-right {
color: blue;
}

SASS : compare variable name within #each loop with a string

I'm trying to find a way of comparing the variable name e.g. $topLeft within the #each loop with a string which would be for instance 'topLeft' - an example would be:
#mixin getCorner($topLeft:false, $topRight:false, $bottomRight:false, $bottomLeft:false) {
#each $corner in $topLeft, $topRight, $bottomRight, $bottomLeft {
#if #{$corner} == topLeft {
border-top-left-radius: $corner;
}
}
}
The above obviously doesn't work, but is there a way of doing it in Sass?
If you use the name top-left instead of topLeft, you can reduce the amount of code you have to write.
Here I have a list which does not do EXACTLY what you want, but you can easily use this to go ahead and do the comparison you want to do.
$corners: (top-left, top-right, bottom-left, bottom-right);
#mixin getCorner($cornerName, $cornerVal) {
$max: length($corners);
#for $i from 1 through $max {
$temp: nth($corners, $i);
#if ($temp == $cornerName) {
border-#{$temp}-radius: $cornerVal;
}
}
}
body {
#include getCorner(top-left, 2px);
}
When you assign a variable, all the interpreter knows is the value it contains, not what its name is. So when you're looping over your values, $corner is getting set to one of the values in the list. It will never be topLeft unless you pass that as the value for the $topLeft argument, which is why your #if statement never evaluates to true.
If you use a default value of null instead of false, you can simplify a lot:
#mixin getCorner($topLeft: null, $topRight: null, $bottomRight: null, $bottomLeft: null) {
border-top-left-radius: $topLeft;
border-top-right-radius: $topRight;
border-bottom-right-radius: $bottomRight;
border-bottom-left-radius: $bottomLeft;
}
.foo {
#include getCorner($topLeft: 50%, $bottomRight: 50%);
}
Output:
.foo {
border-top-left-radius: 50%;
border-bottom-right-radius: 50%;
}

Resources