SCSS: Calculating with a value with unit from function - sass

I am defining values for a theme via a map, like this:
$sizes: (
small: 768,
medium: 1024,
large: 1200,
xlarge: 1600,
gutter: 48
);
and afterwards give them a unit with the following function:
#function sz($size) {
#return map-get($sizes, $size) + px;
}
How can I define a variable by calculating with one of the values out of the function, e.g.
$gutterhalf: (sz(gutter) / 2);
So the variable would return 48px / 2 = 24px? I know I could just go with the unitless value and add the unit afterwards, like so:
$gutterhalf: (map-get($sizes, gutter) / 2) + px;
Would there be a way to handle it differently with the value from the function?

Normally I would use sassmeister to do live sass demos but I can't seem to share my sass demo without it automatically switching to dart-sass. Very weird.
But I've made a fiddle outputting results to a before pseudo content style on divs by id.
See experimental ideas in fiddle... https://jsfiddle.net/joshmoto/0mbsc2g3/
And here is scss code below...
// array with no pixel unit, just integers
$sizes_0: (
sm: 768,
md: 1024,
lg: 1200,
xl: 1600,
gutter: 48
);
// both functions below use array above
// this function allows you pass diving data through
#function sz_0($size, $divide: 1) {
#return map-get($sizes_0, $size) / $divide + px;
}
// this outputs 24px
$gutterhalf_0: sz_0(gutter, 2);
// this function simply returns the array value
#function sz_2($size) {
#return map-get($sizes_0, $size);
}
// this outputs 24px
$gutterhalf_2: ( sz_2(gutter) / 2 ) + px;
// array with pixel unit
$sizes_1: (
sm: 768px,
md: 1024px,
lg: 1200px,
xl: 1600px,
gutter: 48px
);
// this function simply returns the array value with unit
#function sz_1($size) {
#return map-get($sizes_1, $size);
}
// this outputs 24px
$gutterhalf_1: sz_1(gutter) / 2;
// sass results in content
#sz_0::before {
content: '#{$gutterhalf_0}';
}
#sz_1::before {
content: '#{$gutterhalf_1}';
}
#sz_2::before {
content: '#{$gutterhalf_2}';
}

Related

Get a variable name as a string

I have a class with the name .text-black, I also have a map with some values.
$black: #000;
map:
dark: $black;
I want to loop through this map and create a new class with the $key and then extend they new class with using the value text-black.
There are 2 problems I have. The first I think I have solved, if I can get the $value as $black instead of #000. Then I can remove the $ using string replacement.
The second challenge however is proving a headache for me. I need to get $black in stead of #000.
Here is my code showing my approach so far.
// String Replacement to remove '$' from varaible name.
#function str-replace($string, $search, $replace: '') {
$index: str-index($string, $search);
#if $index {
#return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace)
}
#return $string;
}
// get color from map
#function text-color($key: "weekly-digest") {
#return map-get($text-colors, $key);
}
$black: #000000;
// map text-colors
$text-colors: () !default;
$text-colors: map-merge(
(
"news": $black,
),
$text-colors
);
// Extendable classes.
.text-black{
color: $black;
}
// Loop function
#each $name, $value in $text-colors {
&--#{$name} {
background-color: $value;
#extend .text-#{$value} // This should return '.text-black' not '.text-#000000'
}
}
I try to give you 3 different solutions. In all these solutions I used 2 colors (black & red) only to see if they could work in combination:
1. Using a function str-split() (maybe the most intricated, but use your code)
I found a magical function that splits a string in 2 elements How to split a string into two lists of numbers in SASS?.
So my idea is to use that function (thanks to #dayenite: if you like this solution, please upvote him ;)) in a string using a character (in my example "_") to split your maps in 3 different value (something like 2 keys and 1 value):
1. "news"
2. "black"
3. "#000"
Your map could become something like this:
$text-colors: map-merge(
(
"news_black":$black,
"info_red": $red
),
$text-colors
);
This is all the code in action:
#function str-split($string, $separator) {
// empty array/list
$split-arr: ();
// first index of separator in string
$index : str-index($string, $separator);
// loop through string
#while $index != null {
// get the substring from the first character to the separator
$item: str-slice($string, 1, $index - 1);
// push item to array
$split-arr: append($split-arr, $item);
// remove item and separator from string
$string: str-slice($string, $index + 1);
// find new index of separator
$index : str-index($string, $separator);
}
// add the remaining string to list (the last item)
$split-arr: append($split-arr, $string);
#return $split-arr;
}
/* Example with 2 colors */
$black: #000000;
$red: #ff0000;
$text-colors: () !default;
$text-colors: map-merge(
(
"news_black":$black,
"info_red": $red //my add
),
$text-colors
);
// Extendable classes.
.text-black{
color: $black;
}
.text-red{
color: $red;
}
// Loop function
.my-div{
#each $name, $value in $text-colors {
$list: (); // Create every time an empty list with my 2 argoments, for example "news" and "black"
$split-values: str-split($name, "_"); //use the function to split the string
#each $valore in $split-values {
$list: append($list, str-split($valore, " "));
}
//set here the first part of the string (i.e. news/info)
&--#{nth($list, 1)} {
background-color: $value;
#extend .text-#{nth($list, 2)} // set here the second part: black/red
}
}
}
https://www.sassmeister.com/gist/08f699dba4436d3bae6a4d8b666e815b
2. Using a nested list
This time I created a simple nested list with 3 value ("news", "black", $black), the result is the same.
$black: #000000;
$red: #ff0000;
// list text-colors
$text-colors: (
( "news", "black", $black ),
( "info", "red", $red )
);
// Extendable classes.
.text-black{
color: $black;
}
.text-red{
color: $red;
}
.mydiv{
// Loop function
#each $item in $text-colors {
&--#{nth($item, 1)} {
background-color: nth($item, 3);
#extend .text-#{nth($item, 2)}
}
}
}
https://www.sassmeister.com/gist/59adf5ee60ea46dd7a24e94d7db91d85
3. Using a nested map
Here I use nested maps, but the structure is different from yours and I don't know if it's ok for you.
$black: #000000;
$red: #ff0000;
// map text-colors
$text-colors: (
news:(black: $black),
info:(red: $red)
);
.text-black{
color: $black;
}
.text-red{
color: $red;
}
.mydiv{
// Loop function
#each $name, $value in $text-colors {
&--#{$name} {
#each $name-color, $value-color in $value {
background-color: $value-color;
#extend .text-#{$name-color}
}
}
}
}
https://www.sassmeister.com/gist/8ddec08755befc84f6e4846fbc625130
Well, I haven't another ideas. I hope at least one way could help you to solve your problem.
Cheers :)

the index() function returns null even if the value is present

I try to get the index of a value in a map, but it returns null. which is strange since I know the key/value is in the map.
And whats even stranger is that it works fine when using other values than the last one.
Code:
index(($mediaSizesMap), ($size $s))
$size = xl, $s = 1200px
Map:
$mediaSizesMap: (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px);
$mediaSizesMap: (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px);
$size: xl;
$s: 1200px;
div {
content: index($mediaSizesMap, ($size $s));
}
Output:
div {
content: 5;
}

SASS Multiplication with units

If I try to multiply two value with units I get an unexpected error.
$test: 10px;
.testing{
width: $test * $test;
}
result: 100px*px isn't a valid CSS value.
I have used interpolation in the past when I want to do math with variables, and I think it is the simplest solution. If this doesn't work for you, perhaps it is due to a difference in compilers?
$test: 10px;
.testing{
width: #{$test * 2};
}
In fact width: $test * 2; compiles to width: 20px for me, you don't even need to use interpolation for simple math. I am using ember-cli-sass, which uses broccoli-sass-source-maps, which uses node-sass, which wraps libsass, to compile my SCSS to CSS. But it seems to work fine in this jsbin which uses SCSS with Compass.
Where interpolation really helps is if you need to use calc.
$test: 10px;
.testing{
width: calc(50% + #{$test * 2}); // results in calc(50% - 20px)
}
Multiplying units in SASS works like multiplying units in physics / engineering / chemistry / [insert science here].
(see more about this at https://www.sitepoint.com/understanding-sass-units/)
Multiplying two pixel values, will get you px^2, which is an area, not a distance.
What can you do? If you are certain you will be multiplying pixels, use a function and divide by 1 pixel.
$test: 10px;
#function multiply-px($value1, $value2) {
#return $value1 * $value2 / 1px;
}
.testing {
width: multiply-px($test, $test);//100px
}
If you don't know which units you'll be using in advance, you can strip the units from $value2, such that you always get the units of $value1.
(read more on that at https://css-tricks.com/snippets/sass/strip-unit-function/)
$test: 10in;
#function strip-unit($number) {
#if type-of($number) == 'number' and not unitless($number) {
#return $number / ($number * 0 + 1);
}
#return $number;
}
#function multiply-use-first-unit($value1, $value2) {
#return $value1 * strip-unit($value2);
}
.testing {
width: multiply-use-first-unit($test, $test);//100in
}
//try this
$test: 10;
.testing{
width: $test * $test px;
}
You cannot multiply two px values. Better way of doing it is function, but you have to use add in it to achieve it: -
$test: 10px;
#function calc-width($value1, $value2) {
#return $value1 + $value2;
}
.testing {
width: calc-width($test, $test);
}

Sass function to call more than one Palette

I am using sass palettes and I would like know how can I make this function works for more than 1 palettes.
right now I can just use 1 palette with this function, how can make this function works for more than 1 palette?
#function palette($palette, $shade: 'base') {
#return map-get(map-get($color-palettes, $palette), $shade);
}
$primary-palette: (
grey: (
xx-light : lighten($grey, 43%),
x-light : lighten($grey, 35%),
light : lighten($grey, 6%),
base : $grey,
dark : darken($grey, 8%),
x-dark : darken($grey, 16%)
),
);
// will be my secondary paletter
$secondary-palette: (
black: (
light : lighten($grey, 6%),
base : $grey,
dark : darken($grey, 8%),
x-dark : darken($grey, 16%)
),
);
applied in the css
body {
background-color: palette(grey,dark);
}
Someone can help? Thanks
I've not done anything like this before, but it looks really powerful. I did some searching and this article seems to suggest you'd put the black palette within $primary-palette as well, only having one master set of palettes.
From the post:
$colors: (
blue: (
0: hsl(198, 74%, 49%),
1: hsl(200, 72%, 61%),
2: hsl(200, 71%, 73%),
[...]
),
red: (
0: hsl(3, 72%, 62%),
1: hsl(4, 85%, 66%),
2: hsl(4, 84%, 78%),
[...]
)
)
#function get-color($color, $value: 0) {
#return map-get(map-get($colors, $color), $value);
}
.button--blue{
color: get-color(blue, 0);
background: get-color(blue, 4);
&:hover{
background: get-color(blue, 3);
}
}

LESS: Loop using data stored in an array (or something similar)

I found this example in LESS documentation:
LESS:
.generate-columns(4);
.generate-columns(#n, #i: 1) when (#i =< #n) {
.column-#{i} {
width: (#i * 100% / #n);
}
.generate-columns(#n, (#i + 1));
}
OUTPUT CSS:
.column-1 {
width: 25%;
}
.column-2 {
width: 50%;
}
.column-3 {
width: 75%;
}
.column-4 {
width: 100%;
}
This loop generates 4 different divs because '4' was the value passed by firs mixin's call, but generated values are entirely calculated inside mixin. In other words, #n implicitly indicates "number of iterations".
I would like to be able to set a sort of "array of values" such as:
PSEUDO-CODE:
.generate-columns( [25,50,75,100] );
that should be passed to loop mixin and then generates the same CSS code, using each array's value. Is it possible?
.generate-columns-loop(#i; #widths) when (#i <= length(#widths)) {
.column-#{i} {
#width: extract(#widths, #i);
width: (#width * 1%);
}
.generate-columns-loop((#i + 1), #widths);
}
.generate-columns(#widths...) {
.generate-columns-loop(1, #widths);
}
.generate-columns(25, 50, 75, 100);
You can pass an array list to the mixin and then use the extract function to extract the value corresponding to the iteration number and use it.
.generate-columns(#n: 4; #list: 10, 20, 30, 40 );
.generate-columns(#n; #i: 1; #list) when (#i =< #n) {
.column-#{i} {
width: percentage((extract(#list, #i)/100)); /* built-in function to convert numeric to percentage */
}
.generate-columns(#n; (#i + 1); #list);
}
or like below (basically same functionality as the above one with the only difference being that in the above snippet we are using named parameters feature because we are skipping providing a value for the #i variable in the mixin call.)
#widths: 10, 20, 30, 40, 50;
.generate-columns(5; 1; #widths);
.generate-columns(#n; #i: 1; #list) when (#i =< #n) {
.column-#{i} {
width: percentage((extract(#list, #i)/100));
}
.generate-columns(#n; (#i + 1); #list);
}

Resources