Using sass #function inside #for - sass

I have some sass function to create same margin, and it is inside loop like this
$short-margins: ( top: 'mt', left: 'ml', bottom: 'mb', right: 'mr' );
#for $i from 0 through 200 {
#each $position, $prefix in $short-margins{
.#{$prefix}-#{$i} {
margin-#{$position}: #{$i}px;
}
}
}
This will create margin classes like this mr-0 and so on until mr-200, the problem is in line
margin-#{$position}: #{$i}px;
There I create px in loop, but i need that to be in rem? I ahve some function like this
$base-font-size: 14px;
// Remove units from the given value.
#function strip-unit($number) {
#if type-of($number) == 'number' and not unitless($number) {
#return $number / ($number * 0 + 1);
}
#return $number;
}
// convert only single px value to rem
#function single-px-to-rem($value) {
$unitless-root-font-size: strip-unit($base-font-size);
#return $value / $unitless-root-font-size * 1rem;
}
But when i want to use function inside loop like this
#for $i from 0 through 200 {
#each $position, $prefix in $short-margins{
.#{$prefix}-#{$i} {
margin-#{$position}: single-px-to-rem(#{$i});
}
}
}
It does not compile throw me error, does any body knows how ti use sass #function inside #for?

You are doing it right, but you need to send the $i value without interpolation:
#for $i from 0 through 200 {
#each $position, $prefix in $short-margins{
.#{$prefix}-#{$i} {
margin-#{$position}: single-px-to-rem($i);
}
}
}

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 :)

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;
}

Mixin (PXTOEM) provides error in CSS output

I use Drupal FortyTwo theme. The theme provides a mixin named PXTOEM:
// PXTOEM
// Calculate percentage with font-size as context
#function pxtoem($pixels...) {
$result: '';
#each $item in $pixels {
$result: $result + ($item + 0) / $default-font-size + em + ' ';
}
#return #{$result};
}
In my scss file I use it like:
.header-menus {
padding: pxtoem(0, $grid-gutter-width);
}
But after compiling it doesn't get the proper output?
padding: 0/pxem 0.75/pxem; (see screenshot)[![Screenshot][2]][2]
Instead of + 0 you should add pixels: + 0px. And instead of + em use + 0em.
Sassmeister demo.
If you can not modify source code of the theme, create your own function.
$default-font-size: 16px;
// PXTOEM
// Calculate percentage with font-size as context
#function pxtoem($pixels...) {
$result: '';
#each $item in $pixels {
$result: $result + ((($item + 0px) / $default-font-size) + 0em) + ' ';
}
#return #{$result};
}
.header-menus {
padding: pxtoem(0, 30, 30px);
}
Css output:
.header-menus {
padding: 0em 1.875em 1.875em ;
}

How to overwrite linear-gradient function in Compass?

I'd like to use the code here to overwrite the linear-gradient function that comes with Compass. How can I do this?
I think what I need is a way to import the linear-gradient function and then locally rename it to something else, so that my own linear-gradient function can call it. E.g. something like:
#import "compass/css3/images";
// somehow make `original-lg` alias the current `linear-gradient`
#function linear-gradient($args...) {
#return original-lg($args...) + ", " + fixed-lg-from-link-above($args...);
}
What you are attempting won't work, because we are dealing with prefixed values that have to be split apart into distinct properties. As the author of the linked code, here is how I recommend using it. You'll need these functions:
#function convert-gradient-angle(
$deg
) {
#if type-of($deg) == 'number' {
#return mod(abs($deg - 450), 360deg);
} #else {
$direction: compact();
#if nth($deg,1) == 'to' {
#if length($deg) < 2 {
$direction: top;
#warn "no direction given for 'to'. Using 'to bottom' as default.";
} #else { $direction: opposite-position(nth($deg,2)); }
#if length($deg) > 2 { $direction: append($direction, opposite-position(nth($deg,3)), space);}
} #else {
$direction: append($direction, to, space);
#each $pos in $deg { $direction: append($direction, opposite-position($pos), space); }
}
#return $direction;
}
}
#function convert-gradient(
$angle,
$details...
) {
#return linear-gradient(convert-gradient-angle($angle), $details...);
}
The problem is, if you use multiple-backgrounds or anything like that, you will have to repeat the functions yourself in different properties. If you just want a simple background-image gradient, you can use this to simplify:
#mixin gradient-background-image(
$gradient...
) {
#include background-image(convert-gradient($gradient...));
background-image: linear-gradient($gradient...);
}
Otherwise you will need to write those two lines by hand, adding the other layers as needed.

Sass function to convert pixels to ems

Take this function in Sass:
#function pem($pxval, $base: 16) {
#return #{$pxval / $base}em ;
}
(source: https://gist.github.com/2237465)
pem(16) returns 1em and it's ok, but pem(16px) returns 1pxem.
how can this function accept both types of input?
thanks
This seems like a good use for SASS's unitless() function.
#function pem($pxval, $base: 16) {
#if (unitless($pxval)) {
$pxval: $pxval * 1px;
}
#if (unitless($base)) {
$base: $base * 1px;
}
#return $pxval / $base * 1em;
}

Resources