Sass change the value of variable based on body class? - sass

I am trying to create a sass mixin which allows to redefine the value of a variable according to a class added on . I am stuck at this stage (which does not work) and I wonder if it is finally possible to do so:
$color-1: #9E619B;
$color-2: #BB496A;
$themecolor: red !default;
$color-themes: (
   theme1-pages:
       (
           themecolor: $color-1
       )
   theme2-pages:
       (
           themecolor: $color-2
       )
);
#each $name, $theme in $color-themes {
   body.#{$name} {
       $themecolor: map-get ($name, themecolor);
   }
}

I think you've got a syntax error (extra space after map-get) and a mistake in the arguments for map-get(): the arguments should be $theme and themecolor respectively, not $name and themecolor:
#each $name, $theme in $color-themes {
body.#{$name} {
$themecolor: map-get($theme, themecolor);
}
}
That is because $name is simply the key, while $theme is the reference to the value. If you paste the fixed version of your code into SassMeister:
$color-1: #9E619B;
$color-2: #BB496A;
$themecolor: red !default;
$color-themes: (
theme1-pages:
(
themecolor: $color-1
),
theme2-pages:
(
themecolor: $color-2
)
);
#each $name, $theme in $color-themes {
body.#{$name} {
$themecolor: map-get($theme, themecolor);
color: $themecolor;
}
}
You should expect to get this back without any error:
body.theme1-pages {
color: #9E619B;
}
body.theme2-pages {
color: #BB496A;
}

Related

How do I export every key of a map in Sass?

I have this map
$colors: (
black: $black,
blue1: $blue1,
blue2: $blue2,
blue3: $blue3,
blue4: $blue4,
blue5: $blue5,
blue6: $blue6,
coral1: $coral1,
coral2: $coral2,
coral3: $coral3,
coral4: $coral4,
coral5: $coral5,
green1: $green1,
green2: $green2,
green3: $green3,
grey1: $grey1,
grey2: $grey2,
grey3: $grey3,
grey4: $grey4,
grey5: $grey5,
grey6: $grey6,
orange1: $orange1,
orange2: $orange2,
red1: $red1,
red2: $red2,
red3: $red3,
saffron2: $saffron2,
transparent: $transparent,
white: $white,
);
And I want to export every key of it, like:
:export {
#each $name, $color in $colors {
name: $name
}
}
What I get after importing it is an object with only the last key of the map, why is that? I want the object to be filled with every key
Don't ask me why but apparently putting the key in an interpolation made it to work as intented
:export {
#each $name, $color in $colors {
#{$name}: $name
}
}
Now I get the full object

Getting module from string parameter

I'm trying to get a module name from a string. Here is an example of what I imagine it would look like:
#use 'dark'; /* _dark.scss, with a $color variable */
#use 'light'; /* _light.scss, with a $color variable */
#mixin theme($name) {
body {
color: #{$name}.$color;
}
}
If I passed in "dark" as an argument, I would want this result:
...
color: dark.$color;
...
If I passed in "light" as an argument, I would want this result:
...
color: light.$color;
...

Creating a SASS function to fallback to a value

I would like to create a function that gets 2 variables as input and, if the first one exists, returns that variable and if not, it returns the fallback value.
I was trying this
#function component-token($token-name, $fallback) {
#if variable-exists($token-name) {
#return $token-name;
}
#return $fallback;
}
And I wanted to use it as
.my-class {
color: component-token($component-button-primary-color, $color-primary-base);
}
However, this poses two problems:
variable-exists expects a string to be passed in.
If $component-button-primary-color does not exist, compilation fails.
I tried calling the function by passing in a string, as such
.my-class {
color: component-token(component-button-primary-color, $color-primary-base);
}
but this left me with color being the string component-button-primary-color, which is of course not what I want.
To give a little bit of context, we're preparing a project for multibranding, in which we want to have our CSS have a base set of values, but every value should be overwriteable by a brand.
In the example above, we can assume that a brand will always have $color-primary-base. But a brand can also define the $component-button-primary-color variable, which should then overwrite the value.
Our first approach was going with !default as can be seen here. But this brings a lot of boilerplate, will require a lot of context switching because you can't find the needed information in the one line.
Any idea?
You can use optional parameters to get the required result.
Required parameters must precede optional parameters.
#function button-color($color-primary-base, $color-primary-button: null) {
#if $color-primary-button != null {
#return $color-primary-button;
}
#return $color-primary-base;
}
I changed the code / names to match your use case : changing the color of a button.
The caller:
$customer-color-primary-base : red;
$customer-color-primary-button: green;
button {
color: button-color($customer-color-primary-base, $customer-color-primary-button);
}
As you can see, it does not require a string as parameter.
You can experiment with keeping the parameters empty or not providing the optional parameter at all:
$customer-color-primary-button: null;
or
color: button-color($customer-color-primary-base);
it does allow you to change the variable later on (dynamic props are not possible, but it is possible to decalare it with a null value at first):
$customer-color-primary-base : red;
$customer-color-primary-button: null;
$customer-color-primary-button: green;
.button {
color: button-color($customer-color-primary-base, $customer-color-primary-button);
}
You can never call the mixin with an undefined variable. What you can do is make sure it exist just before calling the mixin and give a default value of 'false', for example.
EDIT: Another choice is to use a map or list to store the colors, instead of using regular variables, like so:
$color-primary-base: red;
$colors: (
component-button-primary-color: #007dc6
);
#function component-token($token-name, $fallback) {
#if (map-has-key($colors, $token-name)) {
#return map-get($colors, $token-name);
}
#return $fallback;
}
.my-class {
color: component-token(component-button-primary-color, $color-primary-base);
}
.my-class-2 {
color: component-token(unexistent-key, $color-primary-base);
}

Isolate nested sass map into new map

How can I isolate nested sass map into a new map? For example I have sass map like this:
$susy-setting: (
s: (
'columns': 4,
'gutters': 30px,
),
m: (
'columns': 8,
'gutters': 30px,
),
l: (
'columns': 12,
'gutters': 30px,
)
);
Then I need to isolate each map inside after the loop, because my other mixin need map for its parameter.
#each $setting in $susy-setting{
#include susy-settings-block($setting) { // This mixin need map
#for $i from 1 through map-get($setting, 'columns') {
#content;
}
}
}
To answer your primary question, you'll need to get both the key and value in your loop: #each $size, $setting in $susy-setting. The $size variable will store the key (e.g. s, m, l) while the $setting variable stores the map value assigned to that key.
EDIT: I saw your comment on my gist, which provides a bit more context. Try this:
#mixin susy-settings-block(
$name,
$config: $susy-settings
) {
// store the old settings
$global: $susy;
// Get the new settings by name
$new: map-get($config, $name);
// apply the new settings
$susy: map-merge($susy, $new) !global;
// allow nested styles, using new settings
#content;
// return to the initial settings
$susy: $global !global;
}

Sass won't compile when using Libsass (argument must not be empty)

So I'm using some pretty complicated Sass to remove every other selector from "&"
.test, .test-2, .test-3, .test-4 {
$selectors: (&);
#if length(&) != 1 {
$selectors: (); // Works only with Ruby Sass
$i: 1;
#each $item in (&) {
$i: $i + 1;
#if $i % 2 == 0 {
$i: 0;
}#else{
$selectors: append($selectors, $item);
}
}
$selectors: to-string($selectors, ", ");
content: "#{$selectors}";
}
}
My expected result for the content attribute is:
content: ".test-2, .test-4";
When using Ruby Sass, this is precisely what I get. When using Libsass, I get this error:
argument `$list` of `nth($list, $n)` must not be empty
This error is referring to code within the custom "to-string" function I'm using:
#function to-string($list, $glue: '', $is-nested: false, $recursive: false) {
$result: null;
#for $i from 1 through length($list) {
$e: nth($list, $i);
#if type-of($e) == list and $recursive {
$result: $result#{to-string($e, $glue, true)};
}
#else {
$result: if(
$i != length($list) or $is-nested,
$result#{$e}#{$glue}, $result#{$e}
);
}
}
#return $result;
}
More specifically, this line:
$e: nth($list, $i);
It appears that the value I am passing to the to-string function (which is the $selectors variable) is empty, when it shouldn't be. I initially define it as being empty ($selectors: ();), but then I append every other item from &. So I'm unsure why it's empty at this point.
Here is a Sassmesiter demonstrating the issue: http://sassmeister.com/gist/332dae9a27983edd9d15
Change the compiler in the above demo to Libsass to see it error.
I'm unsure if this is an issue with my code or with Libsass. I don't want to open an issue on Libsass unless I know for sure it is a Libsass problem.
Thanks
Ok I got to the bottom of this. Fairly certain it's a Libsass issue.
It looks like you can't loop through each item in & directly, but if I store & in a variable, it proceeds to work.
So to clarify, changing this:
#each $item in & {
...
}
to this:
$this: &;
#each $item in $this {
...
}
Solves the issue.

Resources