Sass configuration map with default values - sass

I am creating css using SASS and would like to make it possible for another developer to create a custom css by changing sass variables. This works fine when I in my base file use a single variable like this:
$text-color: #000 !default;
To test the override I create a new project where I first declare an override for the variable and then import the "base" sass file.
$text-color: #0074b;
#import "base-file";
But I would also like to use maps for configuration but then I do not get the override to work. How should I use configuration maps that can be overriden?
$colors: (text-color: #000, icon-color: #ccc );
Adding !default after #000 gives me a compilation error: expected ")", was "!default,")
Adding !default after the ) gives no error but the variables does not get overwritten either.
Any ideas on what I am doing wrong?

I don't think the functionality you want exists in standard Sass. I built this function though that does what you're asking for:
//A function for filling in a map variable with default values
#function defaultTo($mapVariable: (), $defaultMap){
//if it's a map, treat each setting in the map seperately
#if (type-of($defaultMap) == 'map' ){
$finalParams: $mapVariable;
// We iterate over each property of the defaultMap
#each $key, $value in $defaultMap {
// If the variable map does not have the associative key
#if (not map-has-key($mapVariable, $key)) {
// add it to finalParams
$finalParams: map-merge($finalParams, ($key : $value));
}
}
#return $finalParams;
//Throw an error message if not a map
} #else {
#error 'The defaultTo function only works for Sass maps';
}
}
Usage:
$map: defaultTo($map, (
key1 : value1,
key2 : value2
));
Then if you have a mixin for something, you can do this sort of thing:
#mixin someMixin($settings: ()){
$settings: defaultTo($settings, (
background: white,
text: black
);
background: map-get($settings, background);
color: map-get($settings, text);
}
.element {
#include someMixin((text: blue));
}
Outputted CSS:
.element { background: white; color: blue; }
So you would use it like this based on what you said in the question:
$colors: defaultTo($colors, (
text-color: #000,
icon-color: #ccc,
));

Bootstrap has solved this issue as:
$grays: () !default;
// stylelint-disable-next-line scss/dollar-variable-default
$grays: map-merge(
(
"100": $gray-100,
"200": $gray-200,
"300": $gray-300,
"400": $gray-400,
"500": $gray-500,
"600": $gray-600,
"700": $gray-700,
"800": $gray-800,
"900": $gray-900
),
$grays
);
https://github.com/twbs/bootstrap/blob/v4.1.3/scss/_variables.scss#L23

Related

SASS/SCSS, how to access a property/method in a dynamic way from a partial file?

Let's say for instance we have the next sass partial file:
//_colors.scss
$foo: red;
And we "use" it on another file:
//test.scss
#use './colors'
.test{
color: colors.$foo;
}
All good, but what if I would like to use/get the value in a dynamic way within a mixin? something like:
//test.scss
#use './colors'
#mixin getColor($type){
color: colors[$type]; //JavaScript example, * don't actually work *.
or
color: #{colors.{$type}; * don't work neither *
//The above returns `color: colors.foo` instead of `color: red` on compilation.
or
color: colors.#{$type}; * doesn't work neither *
}
.test{
#include getColor(foo);
}
Is it possible? thanks for the help!
For a color, I really much prefer a function so it can be used on any property (color, background-color, border, box-shadow...)
I usually declare a string equivalent to variable names, then define them inside a map. Finally this map is accessible via a dedicated function.
Something like
//_colors.scss
#use 'sass:map';
$favoriteRed: "favoriteRed";
$favoriteYellow: "favoriteYellow";
$favoriteBlue: "favoriteBlue";
$MyColors: (
$favoriteRed: #c00,
favoriteYellow: #fc0,
$favoriteBlue: #0cf
);
#function my-color($tone: $favoriteRed) {
#if not map.has-key($MyColors, $tone) {
#error "unknown `#{$tone}` in MyColors.";
}
#else {
#return map.get($MyColors, $tone);
}
}
This _colors.scss generates no code at all, it can be imported anywhere at no cost.
Then, in a specific style file:
//test.scss
#use './colors' as *;
//inside a mixin
#mixin special-hue-component($tone){
div.foo {
span.bar {
border-color: my-color($tone);
}
}
}
//or directly
.foobartest {
color: my-color($favoriteBlue);
}

Pass variable name as parameter in SCSS [duplicate]

I have a very simple mixin which looks like this:
#mixin global( $variable-name ) {
font-size: #{$variable-name}-font-size;
}
I have previously defined variable $input-font-size and pass it into the mixin in the following format
#include global( input );
Problem is that the sass is not converting it and browser returns :
font-size:input-font-size
How should I write my mixin to actually return the value from $input-font-size please?
Thank you for your advice in advance!
You can't create a dynamic variables in sass.
'#{}' means it will convert whatever attribute to its plain css form, it won't be treated as a variable it will be treated as a text.
What you can do is create a map for the list of properties and call them inside the mixin.
$input-font-size: 16px;
$textarea-font-size: 14px;
$var-map: (
input: $input-font-size,
textarea: $textarea-font-size,
);
#mixin global( $variable-name ) {
font-size: map-get($var-map, $variable-name);
}
body {
#include global( input );
}
or if you dont want to create the map then you can simply pass the variable name in the mixin
#mixin sec( $variable-name ) {
font-size: $variable-name;
}
.text-area {
#include sec( $textarea-font-size );
}
Sample pen
https://codepen.io/srajagop/pen/aWedNM

Looping and assigning values through SASS

Before judging my situation, I am not using a typical Bootstrap approach to assign custom colors to variables. I am in a unique situation of depending on the Bootstrap CDN, and re-creating custom SASS variables that look like BS4 variables. Read on!
I feel like I am so close on the the following process. All I want to do is assign my array values to a class property name like so, (i.e. background-color: $theme-primary!important;)
//ORIGINAL THEME VARIABLES
$theme-colors: (primary:$m-color-blue, secondary: $m-color-off-white, success: $m-color-grey, info: $m-color-grey-light, warning: $m-color-gold, light: $m-color-white, dark: $m-color-grey-dark);
$theme-primary: map-get($theme-colors, "primary");
$theme-secondary: map-get($theme-colors, "secondary");
$theme-success: map-get($theme-colors, "success");
$theme-info: map-get($theme-colors, "info");
$theme-warning: map-get($theme-colors, "warning");
$theme-light: map-get($theme-colors, "light");
$theme-dark: map-get($theme-colors, "dark");
//MY LOOP TO ASSIGN BS4 BG COLORS TO MY CUSTOM COLORS.
$classes: primary secondary success warning danger light;
#each $class in $classes {
html body .bg-#{$class} {
//MY ISSUE IS HERE...IT DOES NOT LIKE HOW I AM FORMING THIS PROPERTY. SYNTAX ISSUE???
background-color: $theme-#{class} !important;
}
}
But when I attempt to compile it, I get the following error:
messageOriginal: Undefined variable: "$theme-".
I think I get the error, but how do I resolve?
I'm not sure why this would be necessary since there's already utility classes available for this; https://getbootstrap.com/docs/4.0/utilities/colors/#background-color
You can also feed the bootstrap sass straight into your build pipeline to use all their vars, mixins, functions already;
https://getbootstrap.com/docs/4.0/getting-started/theming/
However, I think you're looking for something more like this amigo; Cheers
$classes: (
primary: "#f00",
secondary: "#ddd",
success: "#00f",
warning: "#0f0",
danger: "#f00",
light: "#eee"
);
#each $key, $val in $classes {
.bg-#{$key} {
background-color: #{$val} !important;
}
}
If you're not importing the $theme variable from your _base / other directory then how do you expect the script to know what to fill it in with?
Your syntax is wrong, you need to wrap $theme with #{} as well so it's #{$theme}-#{class}
working example:
$classes: primary secondary success warning danger light;
$theme: 'blue'; // switch this with import theme.
#each $class in $classes {
html body .bg-#{$class} {
background-color: #{$theme}-#{$class} !important;
}
}
generated css:
html body .bg-primary {
background-color: blue-primary !important;
}
html body .bg-secondary {
background-color: blue-secondary !important;
}
html body .bg-success {
background-color: blue-success !important;
}
html body .bg-warning {
background-color: blue-warning !important;
}
html body .bg-danger {
background-color: blue-danger !important;
}
html body .bg-light {
background-color: blue-light !important;
}
If you are using Bootstrap4, you can directly add a new color to $theme-colors, add the new key and value
$theme-colors: (
"custom-color": #900
);

Creating class names from source map keys

I'm getting an error saying that ('background-color': #333) is not valid css. I'm trying to get the theme name to append to .body-- for each theme (there's only one them for now). The error looks to me like it's trying to add the contents of theme-name to .body-- instead of just the title of it.
$themes: (
'dark': (
'background-color': #333
),
);
#each $theme-name in $themes {
.body--#{$theme-name} {
& .container {
background-color: map-get($theme-name, background-color);
}
}
}
I'm trying to get:
.body--dark {}
.body--dark .container {background-color: #333}
Thanks
The problem is in your #each statement. You are getting just the map value, instead of the key and the value.
$themes: (
'dark': (
'background-color': #333
),
);
// get the key and the value in two separate variables: first variable is the key, second one the value
#each $theme-name, $theme-config in $themes {
.body--#{$theme-name} {
.container {
background-color: map-get($theme-config, 'background-color');
}
}
}
As a note, you don’t need the & for nested selectors. That’s just the default behavior. Use the & only for pseudo classes, concatenate class names, aggregated class names and some other strange selector usages. Check my answer about this note on this other question.

SASS dynamic variable names and nested loops throwing error

I'm not totally sure if what I'm attempting is possible, but I've searched around as much as I can and build the following nested loop to create many variants of hex colors (rather than manually typing out the variable names).
$colors: (
green: #006938,
pink: #9d1e65,
//...
);
$variations: (
30%, 50%, 70%,
);
#each $hex, $name in $colors {
#each $v in $variations {
#{$name}-#{$v}: lighten($hex, $v);
}
}
However, I'm getting the following error on my second loop:
$ gulp sass
[17:01:14] Using gulpfile ~/Sites/webcomponents/gulpfile.js
[17:01:14] Starting 'sass'...
events.js:163
throw er; // Unhandled 'error' event
^
Error: src/scss/app.scss
Error: Invalid CSS after "...n $variations {": expected 1 selector or at-rule, was "#{$name}-#{$v}: lig"
on line 72 of src/scss/app.scss
>> #each $v in $variations {
-----------------------------^
at options.error (/Users/martynbisset/Sites/webcomponents/node_modules/node-sass/lib/index.js:291:26)
Is this not the correct syntax for a nested loop? Also, when I try to do the variable name dynamically without the loop, I also get an error .. sorry, kinda two questions in one but would appreciate any advice on this as I'm quite a noob with SASS/SCSS.
$name: "Hello";
$v: "70%";
#{$name}-#{$v}: lighten($hex, $v); // throws error too :(
You can't declare new css property or dynamic variable name in SASS, but you can definitely do something better by converting variable name into different css classes which we will learn step by step and do corrections in your SASS.
Map: Map is a data-type in SASS which represents one or more key value pairs. Map keys and value can be any SASS datatype(like number, string, color, boolean, map, list of values, null).
Syntax of map
map-name1{
key1: value1,
key2: value2,
...
}
map-name2{
key1:(key11:value11, key12: value12), //value is of map datatype
key2:(key21:value21, key22: value22)
}
So, correct the definition of $variations. Even though if you don't specify key it will work.
SASS also provides map-get() to get the value using key.
example,
$font: ( /*define 'font' map*/
color: #666,
size: 16px
);
body {
color: map-get($font, color); /*get value of 'color' property of 'font'*/
font-size: map-get($font, size);
}
2. As we can't declare variable name dynamically in SASS, so better to create some css class using map and #each loop.
Use the below SASS code:
$color:(
green: #006938,
pink: #9d1e65
);
$variations: (
thirty: 30%,
fifty: 50%
);
#each $name, $hex in $color {
#each $n, $v in $variations {
.color-#{$name}-#{$n}{
color: lighten($hex, $v);
}
}
}
After compilation, it will generate the below css,
.color-green-thirty {
color: #03ff89;
}
.color-green-fifty {
color: #69ffb9;
}
.color-pink-thirty {
color: #e470b1;
}
.color-pink-fifty {
color: #f4c6e0;
}
You cannot create dynamic variables in scss. What you can do is instead convert the variable name to a class name and use that everywhere. You also had a syntax issue in #each. It's key,value so your each will be $name,$hex
$colors: (
green: #006938,
pink: #9d1e65,
//...
);
$variations: (
30, 50, 70,
);
#each $name, $hex in $colors {
#each $v in $variations {
$perc: percentage($v/100);
.#{$name}-#{$v} {
background-color: lighten($hex, $perc);
}
}
}

Resources