SCSS reusable function - css

I have an error in my sass file, I've got no idea what it's not working...
Here's the message I get from calling .class{ col(6);
error scss/components.scss (Line 18: Invalid CSS after "...gin:7px 0; col": expected "{", was "(6);")
Here's the function and variables used to create the function (sorry if it's a bit confusing):
$columnWidth:40;
$numberOfColumns:16;
$gutterWidth: 20;
$fullWidth:($columnWidth * $numberOfColumns) + ($gutterWidth * $numberOfColumns);
#function perc($target) {
#return (($target / $fullWidth) * 100%);
}
#function gw($n, $fluid: false) {
$calculatedValue: ($n * $columnWidth + ($n - 1) * $gutterWidth);
#if $fluid == false {
#return $calculatedValue + px;
} #else {
#return perc($calculatedValue);
}
}
#function col($n, $fluid: false){
#return unquote("width: gw($n, $fluid);");
}
All im trying to do, is re use the gw() function, so that I can use it in css to output the number of columns as a width css property, ie grid(4); would output width: 200px;.
the function gw works because it generates my grid css properly, however I want to define a global function to use everywhere. Hence the col() function.

A mixin is like a function, but does not return anything other than its body, which can be full blown SCSS.
So you can fix this by making col a mixin, like so:
#mixin col($n, $fluid: false){
width: gw($n, $fluid);
}
Then you can call:
.class{ #include col(6) };
Which produces:
.class {
width: 340px; }

Functions can only be used to return simple types (string, color, number, etc.) or lists of simple types. It cannot be used to return a property/value (eg. "color: red"): all you can ever get when you do this is a string or a list of strings (and strings cannot be used this way). Mucking around with lists of strings is more work for you than simply using a mixin.
#function foo() {
#return color red;
}
.foo {
$kv: foo();
#{nth($foo, 1)}: #{nth($foo, 2)}
}
Compare to:
#mixin foo() {
color: red;
}
.foo {
#include foo;
}
There's nothing inherently non-reusable about your gw() function (in fact, it is more reusable than the impossible function you're dreaming of): it returns a numeric value that can be used for any property. In fact, using it in place of a mixin or a function that returns a list of strings is the most efficient way of going about what you're looking for:
.foo {
width: gw(1);
}

Related

Is it possible to use CSS custom property within the definition of the Sass variable, and if so, how?

this is my first question here, so please be patient if I am doing something wrong :)
I have some weird problem and I still haven't found a solution, even though I've spent a few hours searching on Google.
So I have a CSS custom property called --vh that I set up dynamically with JavaScript.
In order to make life easier and not write all this long syntax with the var every time, I wanted to create the Sass variable called $vh and use it wherever I need it:
$vh: var(--vh, 1vh);
$m-distance: $vh * 2;
$input-height: $m-distance * 1.6;
$sm-distance: $m-distance * 0.5;
$l-distance: $m-distance * 1.6;
$xl-distance: $m-distance * 2;
// And there are dosens of places in my app where I later use these variables...
But when I do that right away in the next line I get an error:
SassError: Undefined operation: "var(--vh, 1vh) times 2".
on line 21 of src/styles/setup/_variables.scss
from line 4 of /home/joisadler/github/fymdaily/client/src/styles/global.scss
>> $m-distance: $vh * 2;
-------------^
And if for example instead of $vh: var(--vh, 1vh); I write just $vh: 1vh;, everything passes successfully. But this is not what I want because I need my vh to be rather dynamic.
I have found lots of explanations for the reverse case when I want to use the Sass variable within the custom property definition. But I need to do the exact opposite and use custom property within the definition of the Sass variable.
Does anyone have any idea how to do such things?
Thanks in advance!
I have now come up with a pretty simple solution on how to do what I wanted, I checked and it really works great.
First of all I defined all my variables as custom properties:
// _base.scss
:root {
// --vh property itself defined dynamically by JavaScript
--m-distance: calc(var(--vh, 1vh) * 2.5);
--input-height: calc(var(--m-distance) * 1.6);
--sm-distance: calc(var(--m-distance) * 0.5);
--l-distance: calc(var(--m-distance) * 1.6);
--xl-distance: calc(var(--m-distance) * 2);
}
And after that I just defined sass variables with the same names:
// _variables.scss
$m-distance: var(--m-distance);
$input-height: var(--input-height);
$sm-distance: var(--sm-distance);
$l-distance: var(--l-distance);
$xl-distance: var(--xl-distance);
And now I enjoy two worlds: also get the --vh property dynamically from JavaScript and can also use Sass variables with shorter and nicer syntax in all the other files in my app.
I've tested the following on https://www.sassmeister.com/ and it seems to work.
Input
:root {
--vh: 10vh;
}
$var: var(--vh, 1vh);
body {
height:$var;
}
Output
:root {
--vh: 10vh;
}
body {
height: var(--vh, 1vh);
}
Have you tried declaring a default value for --vh inside the Sass file so the compiler recognises it and doesn't produce an error?
Yes, but not to calculate with SASS functions
Why assign a SASS variable to the value of a CSS custom property? For calculations (as in your question) and useful built-in functions like color.scale(). You won't be able to use these tools with values derived from CSS custom properties.
#use "sass:color";
:root {
--basic_color: #FF0000; // Red
}
$sassy_color: var(--basic_color, #00FF00); // Red with green fallback
$pure_color: #FF0000; // Red
body {
background-color: $sassy_color; // ✅ Red background
color: color.scale($pure_color, $lightness: +90%); // ✅ Red text, or
color: color.scale($sassy_color, $lightness: +90%); // 👎 Error: not a color
}
As Josh Bonnick shows, you can put a CSS custom property insertion statement into a SASS variable, as in background-color: $sassy-color, but that's not very useful; it might as well be a custom property insertion. Unfortunately, this is only a string, and SASS won't evaluate the var() function call.
Let's say we want to change the lightness of the color, as in color: color.scale(). If our SASS variable uses a color primitive ($pure_color), it works as intended, but if we use a variable that represents an CSS custom property ($sassy_color), it fails, because it can't do math on the string var(--basic_color, #00FF00);.
So why do it? Organization
In a beautiful SASS architecture proposal, Felippe Regazio lays out a method for making CSS custom properties his styling modules using SASS functions as getters and setters. This is essentially all string manipulation, and it shows how useful it can be to access CSS custom properties with SASS variables even if you're not going to use them for calculations.
/**
* Retrieve a css variable value with prefix
*
* Usage
*
* .selector {
* color: get(primary-color);
* }
*
* Will result in
*
* .selector {
* color: var(--prefix-primary-color);
* }
*/
#function get($name, $prefix: pm) {
#return var(--#{$prefix}-#{$name});
}
/**
* Update a css variable value
*
* Instead of
*
* .selector {
* --#{$prefix}-button-height: 56px;
* }
*
* Usage
*
* .selector {
* #include set(button-height, 56px);
* }
*/
#mixin set ($name, $value: '', $prefix: pm) {
--#{$prefix}-#{$name}: #{$value};
}
Read the article for more details.

SCSS function with for loop inside not working

I'm trying to write a function that creates a grid layout based on multiple arrangements (as opposed to just the 12 column grid) in Sass (scss syntax). The for loop inside works properly on its own, but when I wrap it in a function it no longer works. I'm new to using Sass functions, so maybe I'm messing up the syntax? Or is this just not possible? I'm just trying to avoid having to write a new for loop for each layout I want to achieve. Thanks in advance.
#function create-grid($num-cols) {
#for $col from 1 through $num-cols {
.col-#{$col}-of-#{$num-cols} {
width: percentage($col / $num-cols);
}
}
}
Sass functions return a value.
If you want to generate css programatically like this, you want to use a mixin.
https://www.sitepoint.com/sass-basics-the-mixin-directive/
#mixin grid($num-cols) {
#for $col from 1 through $num-cols {
.col-#{$col}-of-#{$num-cols} {
width: percentage($col / $num-cols);
}
}
}
#include grid(12)
For anyone who stumbles across this post, the final product is:
#mixin create-grid($num-cols, $col-gutter) {
#for $col from 1 through $num-cols {
.col-#{$col}-of-#{$num-cols} {
width: calc(
((100% - ((#{$num-cols} - 1) * #{$col-gutter})) / #{$num-cols}) *
#{$col} +
((#{$col} - 1) * #{$col-gutter})
);
}
}
}
Thanks a lot to #HorusKol for introducing me to the world of mixins!

Less to Sass: How to translate less guard when (default()) to sass

I am trying to convert some mixins from less to sass.
I am not very skilled in either of these languages, so I'm not sure at all if I'm doing it right.
The original Less mixins:
.for(#i, #n) {.-each(#i)}
.for(#n) when (isnumber(#n)) {.for(1, #n)}
.for(#i, #n) when not (#i = #n) {
.for((#i + (#n - #i) / abs(#n - #i)), #n);
}
.for(#array) when (default()) {.for-impl_(length(#array))}
.for-impl_(#i) when (#i > 1) {.for-impl_((#i - 1))}
.for-impl_(#i) when (#i > 0) {.-each(extract(#array, #i))}
I have identified three main issues:
1) how do I translate guards in sass? Is it correct to gather mixins with the same number of arguments in one single mixin and write conditional blocks inside it?
2) how does when(default()) works? I've been trying to find a good explanation in the documentation but couldn't find any.
3) is there a function in sass which is equivalent to less extract?
Thank you very much!
First thing first, the mixin that you've given in question is one that was created by seven-phases-max to imitate the for and for-each loops because Less doesn't have built-in functions for them. Unlike Less, Sass already has built-in #for and #each directives to perform looping and so I'd recommend you to not spend time on converting these Less mixins to Sass.
Below are Sass samples for a for and a for-each loop:
For:
.column {
#for $i from 1 through 5 { /* execute the loop 5 times */
&-#{$i} { /* create .column-1 to .column-5 using selector interpolation */
width: 20%;
left: (($i - 1) / 5 * 100%);
}
}
}
For Each:
$country-code-list: US, IN, FR, SG; /* list for iteration */
.flag {
#each $country-code in $country-code-list { /* iterate for each value in the list */
&-#{$country-code} { /* create .flag-US, .flag-IN etc using selector interpolation */
background-image: url(http://yoursite.com/#{$country-code}.png);
}
}
}
You can try out the above samples # SassMeister.com and see the compiled output.
Now coming to your questions,
1) how do I translate guards in sass? Is it correct to gather mixins with the same number of arguments in one single mixin and write conditional blocks inside it?
The Less guards would translate to #if (conditional statements) in Sass. Below is an example:
#mixin list-style($val) {
#if ($val == i) { list-style-type: lower-roman; }
#else if ($val == I) { list-style-type: upper-roman; }
#else { list-style-type: decimal };
}
#demo { #include list-style(2); }
The second part of your question is probably opinion based as each one would have their own way of doing things. I'd personally prefer grouping them inside one mixin and writing conditional blocks.
2) how does when(default()) works?
The default() guard in Less is like the typical else or default: (in switch-case) statements. The mixin with this as guard gets called only when no other mixin with the same name and the same no. of arguments is matched.
For example, have a look at the below code. Here when the value passed as argument to the mixin is anything other than i or I neither of the first two mixin guards are matched and so those mixins do not get executed. In such a case, the third one (the default()) will get executed and set the list style type as decimal.
.mixin-list-style(#val) when (#val = i) { list-style-type: lower-roman; }
.mixin-list-style(#val) when (#val = I) { list-style-type: upper-roman; }
.mixin-list-style(#val) when (default()) { list-style-type: decimal; }
#demo {
.mixin-list-style(1); /* this will result in list-style-type: decimal */
}
3) is there a function in sass which is equivalent to less extract?
Yes, there is a nth() function in Sass which is equivalent to the Less extract() function. Below is a sample:
$country-code-list: US, IN, FR, SG;
.flag-FR {
$country-code: nth($country-code-list, 3); /* extract 3rd item from country-code-list */
background-image: url(http://yoursite.com/#{$country-code}.png);
}
Note: You don't need this with #each loop because Sass automatically extracts the item and assigns to the variable for each iteration.

Dynamically call mixin/function in SASS to DRY up redundant code block - some sort of eval() functionality? [duplicate]

Is there any way to pass a function or a mixin by reference to another function or mixin in SASS, and then call the referenced function or mixin?
For example:
#function foo($value) {
#return $value;
}
#mixin bob($fn: null) {
a {
b: $fn(c); // is there a way to call a referenced function here?
}
}
#include bob(foo); // is there any way I can pass the function "foo" here?
Functions and mixins are not first-class in Sass, meaning you can't pass them around as arguments like you can with variables.
Sass 3.2 and older
The closest you can get is with the #content directive (Sass 3.2+).
#mixin foo {
a {
#content;
}
}
#include bob {
b: foo(c); // this replaces `#content` in the foo mixin
}
The only caveat is that the #content can't see what's inside your mixin. In other words, if c was only defined inside the bob mixin, it essentially wouldn't exist because it isn't considered in scope.
Sass 3.3 and newer
Starting with 3.3, you can use the call() function, but it is only for use with functions, not mixins. This requires passing string containing the name of the function as the first argument.
#function foo($value) {
#return $value;
}
#mixin bob($fn: null) {
a {
b: call($fn, c);
}
}
#include bob('foo');

How to combine 2 SASS functions as they do almost the same thing just using a different calculation?

I was wondering if someonce could advise me on how I could combine the SASS functions below, basically they do the same thing but one is used specifically for IE so having 2 is a waste. I've tried adding if..else statements with an additional parameter like if ( $property == 'ie' ) but with no success.
Functions:
// To use width, margin, padding...
#function percentageWidth( $elementWidth, $containerWidth ){
#return $elementWidth/$containerWidth * 100%;
}
// To use *width ie6/7 workarounds
#function iePercentageWidth( $elementWidth, $containerWidth ){
#return $elementWidth/$containerWidth - 0.5/$containerWidth * 100%;
}
Current CSS:
width: percentageWidth( 214,945 );
*width: iePercentageWidth( 214,945 );
I would also like to use this function on things like:
margin: percentageWidth( 23,945 );
Here's a general mixin you can use to create a pair of properties:
#mixin ieprop($name, $elementWidth, $containerWidth) {
#{$name}: percentageWidth($elementWidth, $containerWidth);
*#{$name}: iePercentageWidth($elementWidth, $containerWidth);
}
You pass the property name as the first parameter, like so:
.cls {
#include ieprop('width', 214, 945);
#include ieprop('margin', 23, 945);
}
Is this what you need?
#mixin twoWidths($x, $y){
width: percentageWidth($x, $y);
*width: iePercentageWidth($x, $y);
}
Then in your stylesheet you'd call it like:
#include twoWidths(500,700);

Resources