How do I Reverse Variable Order using @function in SCSS

29 Views Asked by At

Ok, so I'm working on a custom scss framework, and I'm trying to make all predefined variables easily configurable by the user, however because of how CSS works I need to have a certain variable reversed in some configurations so that they override each other in the right way. Right now my variables are configured like this:

$order-direction: 'ascending' !default;
$order: null! default;

$1: '1' !default;
$2: '2' !default;
$3: '3' !default;

@if $order-direction == 'ascending' {
     $order:(
          "1":$1,
          "2":$2,
          "3":$3
     );
}
@if $order-direction != 'ascending' {
     $order:(
          "3":$3,
          "2":$2,
          "1":$1
     );
}

This works, but it makes it so that I can't change the name of the variables (which I reference later for my CSS class names) and also makes it so that I can't add more to this afterward. What I would like to do is to use a single variable that would then be reversed if I change the order direction, something like this:

$order-direction: 'ascending' !default;
$order: null! default;

$numbers:(
     "1":1,
     "2":2,
     "3":3
) !default;

@function reverse($v){
     $r: null;
     @for 1 through length($numbers){
          something that makes $r = reverse of $v
     };
     @return $r
};

@if $order-direction == 'ascending' {
     $order: $numbers;
};
@if $order-direction != 'ascending' {
     $order: reverse($numbers);
};

If I can get this to work I can just have users redefine the $numbers & $order-direction variables to set all the corresponding logic, rather than each of the $1,$2...etc as well as the $order-direction variables individually. This change would allow me to use a standard @each $key, $value{} operator to make class names so that the user could both change the names and number of the derived classes.

However, I don't know how to pull out something from a sass variable based on a number like you would in .js (which would be something like numbers[i]). I know you can use Sass map to find something in an object like a .js dictionary lookup, but I can't think of a way to make this work as in Sass.

Thoughts?

(P.S. I may have missed a semicolon or a comma somewhere, I am fully aware, but I retyped this with different names for this to make more sense)

As I said, my code is functioning currently but isn't expandable by the user. I tried to find a way to use an @map, but for that to work I needed to keep the names identical and the user still wouldn't be able to add more to the $numbers for my sass to evaluate.

1

There are 1 best solutions below

0
Auston Robertson On

Ok, so I did a lot of digging and I found a way to do this, but it takes a few steps. first lets look at my example I posted:

$order-direction: 'ascending' !default;
$order: null !default;

$numbers:(
     "1":1,
     "2":2,
     "3":3
) !default;

@function reverse($v){
     $r: null;
     @for 1 through length($numbers){
          something that makes $r = reverse of $v
     };
     @return $r
};

@if $order-direction == 'ascending' {
     $order: $numbers;
};
@if $order-direction != 'ascending' {
     $order: reverse($numbers);
};

We keep the defined variables as they are, and the function then needs to take a few steps. First we take the variable map and convert it to 2 lists, one for the names and one for the values:

@function reverse($val){

$names: null;
$values: null;
$r:();

@each $n, $v in $val{
    $names: append($names, $n);
    $values: append($values, $v);
};

....

Now we have a set of 2 lists from our original, one with the names, another with the values. It is important that you assign the variables to themselves as well since sass does not dynamically update variables using append(). Now we have to recombine them into a map again, and we do so like this:

@for $i from 1 through length($names){
$r: map.set( $r, list.nth($names, ($i * -1) ), list.nth($values, ($i * -1) ));
};
        
@return $r;
};

After doing this we combine the lists from last to first into a new mapped variable. Since we used both a map and a list we also need the appropriate @use so we include both of those, and we get this:

@use "sass:map";
@use 'sass:list';

$order-direction: 'ascending' !default;
$order: null !default;

$numbers:(
     "1":1,
     "2":2,
     "3":3
) !default;

@function reverse($val){

    $names: null;
    $values: null;
    $r:();

    @each $n, $v in $val{
        $names: append($names, $n);
        $values: append($values, $v);
    };
    @for $i from 1 through length($val){
        $r: map.set( $r, list.nth($names, ($i * -1) ), list.nth($values, ($i * -1) ));
    };
        
    @return $r;
};

@if $order-direction == 'ascending' {
     $order: $numbers;
};
@if $order-direction != 'ascending' {
     $order: reverse($numbers);
};

With this configuration when the variable $order is set to 'ascending' we get a $numbers variable that goes 1,2,3, and when it is anything other than 'ascending' we get 3,2,1. This is super helpful in arranging overrides in css since the latest class in css is applied. For my use case I am using it to change the behavior of breakpoints to use either min-width or max-width to determine their @media rules, and if the user wants to change that functionality (perhaps to work more like bootstrap, backwards though it may be) it is now as easy as flipping a single variable. However I don't know if my answer is the most efficient one.

EDIT: Ok, after working through compilation issues, you need to make sure that the references variable is null before setting it or else the reversing will not apply. I did this in my original example above, but I didn't note how important that is as it effectively tells the compiler to wait until the variable is set before compiling the styles.