If statements in SCSS using CSS variables - css

My JS code sets a variable on the page:
if (error) {
document.querySelector('body')?.style.setProperty('--pickererror', `'${ error }'`);
}
My SCSS code uses that variable for some content:
$error: var(--pickererror);
// ERROR MESSAGE
.picker-toolbar::before {
content: $error;
color: red;
padding: 5px 35px 0px 35px;
text-align: center;
}
.picker-toolbar {
#if str-length($error) > 0 {
order: 1 !important;
}
}
The ::before section works completely. However the #if clause executes even if there is no error, and the .picker-toolbar is always at order: 1.
I have checked that --pickererror is not present when there's no JS error. I've tried any number of permutations, such as
Putting the #if line before the .picker-toolbar line
Simply using #if $error
Using #if var(--pickererror) in place of the $error variable.
How do I make this work?

What if you remove the logic from your stylesheet entirely, and instead rely on the dynamic property value from your JavaScript?
For example:
body {
--pickererror: 0; // Default value for the "order" property
}
If your JavaScript detects an error, the --pickererror is given a value of 1 !important;
if (error) {
document.querySelector('body').style.setProperty('--pickererror', '1', 'important');
}
And in your stylesheet, you only need a single rule that changes the order if the JavaScript says so.
.picker-toolbar {
order: var(--pickererror, 0); // Defaults to "0" also if the variable doesn't exist
}
I realize this only makes sense if you are using --pickererror mostly as a boolean (I thought since you're checking if its length is greater than 0). If you're actually using the string value, it would be better to create an --error-order variable for this single purpose I guess.

Related

A scss variable doesn't return the css variable value

I have two css variables and two scss variables , the problem is the last scss variable doesn't return a value at all.
note that the values of the two sass variables comes from the css variables.
the first scss variable takes its value normally from the css variable.
:root {
--intro-img-url: url("../images/pexels-los-muertos-crew-7487374.jpg");
--dl-mode: black;
}
// the intro-img scss variable takes its value normally from the
// css variable and there's no problem with it.
$intro-img: var(--intro-img-url);
// but the dl-mode scss variable doesn't take the css variable val
// and just return nothing
$dl-mode: var(--dl-mode);
#if $dl-mode == black {
:root {
--dominant1-wmode-color: #030712;
--dominant1-bmode-color: #ffffff;
}
} #else {
:root {
--dominant1-wmode-color: green;
--dominant1-bmode-color: #030712;
}
}
Even though --dl-mode is set to black in the context of :root, the value of $dl-mode is set to var(--dl-mode). This is what your SCSS compiler will be seeing when it runs the check #if $dl-mode == black, so that check will always be false.
SCSS cannot look inside the values of CSS variables, because it needs to know those values at compile time but those values may depend on context within the HTML itself. For example, consider the following:
:root {
--my-var: black;
}
.class {
--my-var: white;
}
$my-var: var(--my-var);
#if $my-var == black {
// How can you know the value within --my-var if you don't know if you're in a .class element or not?
}

Passing conditions to mixins in LESS

First of all, I am using LESS 1.7 (and there is no way I can change it). I have the following if-mixin in my less file that is just supposed to set the property and its value if the condition is met:
.if(#condition, #property, #value) when (#condition){
#{property}: #value !important;
}
It works fine if I pass the condition as true/false like this:
.column-header {
font-family: 'Something';
.if(true, color, green);
}
However, I am not able to pass the condition as an expression. If I want to pass a comparison such as 2 > 1, I get a parsing error: expected ')' got '>'. Is this not supported (or was this not supported in 1.7)? Am I doing something wrong? Any workaround? Thanks!
Not shure it's possible to pass contions to mixin.
But you can just write code witout .if mixin:
.column-header {
font-family: 'Something';
& when (2 > 1) {
color: green;
font-size: 20px;
}
}

SASS Customize Class Names with Variables

Is there any way to customize the variables in SASS?
For example:
.m-b-{$number} {
margin-bottom: $number;
}
If I give class="m-b-50" to an element, it should take margin-bottom 50. I just want to know if it is possible with SASS.
Yes it is possible with the help of variable interpolation or variable substitution which uses #{} for variable substitution in SASS and mixins which is a block of code just like function.
Interpolation is the process of evaluating an expression or a string containing one or more variables, yielding a result in which the variables are replaced with their corresponding values.
Simple example of interpolation and set values to the css property in SASS:
$number:60;
$n: 20px;
.m-b-#{$number}{
margin-bottom: #{$number}px;
margin-top: $n;
}
To create customize class names, will use mixins:
#mixin margin-class($side, $number) {
$firstLetter: str-slice($side, 0, 1);
.m-#{$firstLetter}-#{$number}{
margin-#{$side}: #{$number}px;
}
}
$margins: (10, 20);
$sides: ("top", "right", "bottom", "left");
#mixin generate-margin(){
#each $margin in $margins{
#each $side in $sides{
#include margin-class($side, $margin);
}
}
}
#include generate-margin();
Here, generate-margin() will get executed which will call margin-class() for each $margins and $sides, and will generate the below CSS classes:
.m-t-10 {
margin-top: 10px;
}
.m-r-10 {
margin-right: 10px;
}
.m-b-10 {
margin-bottom: 10px;
}
.m-l-10 {
margin-left: 10px;
}
.m-t-20 {
margin-top: 20px;
}
.m-r-20 {
margin-right: 20px;
}
.m-b-20 {
margin-bottom: 20px;
}
.m-l-20 {
margin-left: 20px;
}
That's the one way when you want only for specific values, but if you want to create margin class for 0-20, you can loop thru 0 to 20 as shown below:
#mixin generate-margin(){
#for $margin from 1 through 20{
#each $side in $sides{
#include margin-class($side, $margin);
}
}
}
For anyone else facing this issue, here is how one can achieve this:-
#for $i from 1 through 10 {
.mb-#{$i} {
margin-bottom: #{$i}rem;
}
}
The answer is: no it is not possible. SASS is just a language to pre-generate CSS for you. There is no on-demand, dynamic creation of classes triggered by the contents of your HTML markup. When it comes time for the browser to render your HTML and apply your specified classes, it is still just using CSS. I.e. if you assign class="m-b-50" to an element, the class .m-b-50 must already be explicitly defined somewhere. As noted in the other answers, SASS can make it easier to generate a bunch of pre-defined classes but you must know which values you want to support up front.
Now, you could generate classes for some very large, all-inclusive range like -1000 to 1000 to effectively support all values you might ever try to use and it would seem to do what you wanted, but you would be forcing your users to download a larger CSS file with, most likely, a large percentage of it being unused CSS which is wasteful and can be inconsiderate in a world of paid & limited data plans.

Less if string is not empty conditional logic

I'm using dotless to dynamically change the look of my site from an admin page.
Essentially I use regular expressions to read the variables defined in the less and then give the user the option to change the value of the variables.
I'm wanting to have the option to set a background image. Essentially I need a way to check if the string is empty if its not then add the background image mixin.
#BackgroundImage: '';
.showBackground(#fileName) when (#fileName != '') {
background-image: url('../Themes/images/backgrounds/#{fileName}');
}
body {
.showBackground(#BackgroundImage)
}
So the default is no background '' when the user sets a background the variable #BackgroundImage will be set to 'backgroundImage1.jpg'
How can I get this empty string logic to work?
P.S I've tried setting the variable to #000000 and using isstring() but it would appear to return true.
You want to use the when not instead of negating the condition.
LESS
.showBackground(#fileName) when not (#fileName = '') {
background-image: url('../Themes/images/backgrounds/#{fileName}');
}
Output
#BackgroundImage: ''; // No output.
#BackgroundImage: 'foo'; // background-image: url('../Themes/images/backgrounds/foo');
Here's another possible solution that sets defaults and checks for use cases when the variable is not used. The problem it attempts to solve is using text as an indicator in some cases and a background image in others.
This worked out well enough even if there is some duplication. Your mileage may vary.
.pointer( #ico ) when not ( #ico ) {
&:extend(.global-sprite-image);
.sprite-position(#ico);
.sprite-height(#ico);
.sprite-width(#ico);
content: '';
margin-left: 2px;
line-height: 1;
}
.pointer( #ico:'›') when ( default() ) {
content: #ico;
font-size: 130%;
font-weight: bold;
font-family: #font-title;
padding-left: 2px;
line-height: 1;
}

Creating variable groups in Sass

On the site I'm working on we were using Scaffold, which is a PHP-based system similar to Sass. It also can process Sass functions\files. Unfortunately that system is now abandonware, and we are looking on a way to move completely to Sass. There is one big feature with Scaffold though that I'm not finding a way to port to Sass, the variable groups.
Variable in Scaffold can be organized in groups and used with a point-separated markup. For example I would define them as:
#variables vargroup1{
variable1: ####;
variable2: ####;
variable3: ####;
variable4: ####;
}
And later use on the code as, for example.
body{ width: vargroup1.variable1; margin: vargroup1.variable2 + 10;}
This helps development a lot, since you can group together variables from a system and reading the CSS files you can easily know what to reference. I didn't find anything like that on the Sass documentation, anyone knows if it is possible? Or if there is anyway using Mixins to do this?
Thanks
I came across this somewhat clunky solution (see Chris Eppstein's reply) using zip and index. Apparently a maintainer of SASS added these built-in functions in response to a similar question.
To quote his reply:
$border-names: a, b, c;
$border-widths: 1px, 1px, 2px;
$border-styles: solid, dashed, solid;
$border-colors: red, green, blue;
$borders: zip($border-widths, $border-styles, $border-colors);
#function border-for($name) {
#return nth($borders, index($border-names, $name))
}
#each $name in $border-names {
.border-#{$name} {
border: border-for($name);
}
}
Would generate:
.border-a { border: 1px solid red; }
.border-b { border: 1px dashed green; }
.border-c { border: 2px solid blue; }
The "naming your variables" comes from the list "-names" at the top; you then use the index of a desired variable name from that variable list to get the nth value from another variable lists. zip is used to mush separate lists together, so that you can retrieve the same index from all lists at the same time. Wrapping that behavior in a function makes it easier to retrieve a set.
There is no equivalent in Sass. But I can think in two workarounds:
1) Sass lists and its related list functions.
Your code could look like the following:
$variables = 40px 30px 20px 10px;
body {width: nth($variables, 1); margin: nth($variables, 2) + 10;}
It's not the same because list indexes can't be strings, so you haven't any way to name your variables.
2) Define a custom function. Look at Function Directives section in Sass reference
#function variables($variable_name) {
#if ($variable_name == 'variable1') {
#return 40px;
} #else if ($variable_name == 'variable2') {
#return 30px;
}
}
body {width: variables('variable_1'); margin: variables('variable_2') + 10;}
This way is less intuitive and uglier but you can 'name your variables'.
You could use the scss/sass map function:
#use "sass:map";
$variables: (
"variable1": ####;
"variable2": ####;
"variable3": ####;
"variable4": ####;
}
body {
width: map.get($variables, "variable1");
margin: map.get($variables, "variable2") + 10;
}
Documentation
You can use SASS lists a it's related functions on a way similar to that:
// List order: top, bottom, left, right, width, height, ...
$Header: 10px,auto,10px,auto,100%,50px;
$Footer: auto,0px,0px,auto,100%,20px;
#function getVar($variable,$name:top){
$var_index:1;
#if $name==bottom {
$var_index:2;
} #else if $name==left {
$var_index:3;
}
// Continue de if else for each property you want.
return nth($variable,$var_index);
}
That way calling something like:
getVar($Header,left)
Should return the value of the left property for the list of Header, but changing it to getVar($Footer,top) would return the value for the top property of the "Footer Group" (Footer List of Values).
That works for the time of using the values, but a the definition, you must follow the exact order and cannot leave any empty value, the nearest to an empty value that I found is #{''} what means "Empty String with no quotes", an empty value, but is added to the CSS.

Resources