SASS loop on map, get next iteration - css

I have a color array like this :
$colors: (
'primary': '#aaa',
'secondary': '#bbb',
'color-3': '#ccc',
'color-4': '#ddd',
'color-5': '#eee',
);
and I want to automate class creation in loop like this :
#each $col in map-keys($theme-colors){
&.btn-#{$col} {
background-color: map-get($theme-colors, $col);
&:hover{
background-color: map-get($theme-colors, $col + 1); // <= my problem is here to get my "$col + 1"
}
}
}
My idea is to create a class btn-primary with primary color background but, on hover, secondary color at background.
A class btn-secondary secondary color background but, on hover, color-3 color at background, etc.
How can I do this?
Thanks ;)

Here you can find your solution: https://github.com/elcheio/sass-map-get-next-prev
Using that function (i.e. map-get-next function) you can resolve your problem.
So, first of all copy and paste that function... very very simple (^_^;)
#function map-get-next($map, $key, $fallback: false, $return: value) {
// Check if map is valid
#if type-of($map) == map {
// Check if key exists in map
#if map-has-key($map, $key) {
// Init index counter variable
$i: 0;
// Init key index
$key-index: false;
// Traverse map for key
#each $map-key, $map-value in $map {
// Update index
$i: $i + 1;
// If map key found, set key index
#if $map-key == $key {
$key-index: $i;
}
// If next index return next value or key based on $return
#if $i == $key-index + 1 {
#if $return == key {
#return $map-key;
} #else {
#return $map-value;
}
}
// If last entry return false
#if $i == length($map) {
#return $fallback;
}
}
#warn 'No next map item for key #{$key}';
#return $fallback;
}
#warn 'No valid key #{$key} in map';
#return $fallback;
}
#warn 'No valid map';
#return $fallback;
}
Then you can add your map:
$colors: (
'primary': #aaa,
'secondary': #bbb,
'color-3': #ccc,
'color-4': #ddd,
'color-5': #eee,
);
In the end, you have to create an #each loop to estract every pair name/value of your map:
#each $name, $value in $colors{
.btn-#{$name} {
background-color: $value;
&:hover{
background-color: map-get-next($colors, $name, #ffffff); // <== here you have to write the color for your last item (i.e. 'color-5'; in this example is white)
}
}
}
That's it! Your output will be:
.btn-primary {
background-color: #aaa;
}
.btn-primary:hover {
background-color: #bbb;
}
.btn-secondary {
background-color: #bbb;
}
.btn-secondary:hover {
background-color: #ccc;
}
.btn-color-3 {
background-color: #ccc;
}
.btn-color-3:hover {
background-color: #ddd;
}
.btn-color-4 {
background-color: #ddd;
}
.btn-color-4:hover {
background-color: #eee;
}
.btn-color-5 {
background-color: #eee;
}
.btn-color-5:hover {
background-color: #ffffff;
}

Related

How to change sass import file when body class is changing

Is any way to change scss file with color variables when class in body is changing?
I mean when I have in html:
<body class="custom"> ... </body>
I want to load in style.scss
#import 'custom';
and when I have
<body class="dark-mode"> ... </body>
I want to load in style.scss
#import 'dark-mode';
You can not make an #import depending on a condition, but there is a ton of possible other approaches to this. Here is a small framework I wrote back then.
#function keyFilter($iKey, $fKey, $rKey) {
#if ($iKey == $fKey) {
#return $rKey;
}
#return $iKey;
}
#function v($key) {
#return var(--#{$key});
}
//
$modes: (
"&": (
"color": #000,
),
"dark": (
"color": #fff,
),
);
//
#each $key, $map in $modes {
body#{keyFilter("[#{$key}]", "[&]", null)} {
#each $key, $value in $map {
--#{$key}: #{$value};
}
}
}
To "register" a new mode just nest another map in the $modes-map, you can add as many modes as you want. Keep in mind the "&"-mode represents the default-mode.
$modes: (
"&": (
//...
),
"dark": (
//...
),
//...
);
To register a new mode-depending variable just simply enter key and value to the respective mode.
$modes: (
"&": (
"color": #000,
"bgc": #fff,
"bgc-contrast": #eee,
//...
),
"dark": (
"color": #fff,
"bgc": #000,
"bgc-contrast": #424242,
//...
),
);
To call a variable just use the v($key) function.
body {
color: v(color);
background-color: v(bgc);
}
div.contrasted {
background-color: v(bgc-contrast);
}
This compiling to:
body {
--color: #000;
--bgc: #fff;
--bgc-contrast: #eee;
}
body[dark] {
--color: #fff;
--bgc: #000;
--bgc-contrast: #424242;
}
body {
color: var(--color);
background-color: var(--bgc);
}
div.contrasted {
background-color: var(--bgc-contrast);
}
Note: you do not need to declare each variable for each mode. If a variable wasn't found for the current mode, this won't throw an error.For Example: This...
$modes: (
"&": (
//...
),
"dark": (
"color": #fff,
"bgc": #000,
"bgc-contrast": #424242,
//...
),
);
//...
body {
color: v(color);
background-color: v(bgc);
}
div.contrasted {
background-color: v(bgc-contrast);
}
... is totally fine.

SCSS - How to loop nested maps within map-merge?

I have a nicely working SCSS function with which I can call any color like so:
color: clr(milk);
Now I would like to make some kind of loop within the map-merge so that when, for example I add a new nested map called 'tertiary' with some other colors, the colors automatically become available without having to add
map-get(colors, 'tertiary')
to the map-merge. Does anyone know how to do this? Below is my current function:
$colors: (
primary: (
milk: #fff,
cola: #000,
mine-shaft: #232323,
),
secondary: (
pampas: #f4f1ef,
pearl-brush: #e9e2dd,
alto: #ddd,
),
);
// Color generation
#function clr($color) {
$color: map-get(map-merge(map-get($colors, 'primary'), map-get($colors, 'secondary')), $color);
#return $color;
}
Nice project! map-merge() can be indeed used to achieve what you want, however it adds an unnecessary step. I'd recommend to simply use a nested loop coupled with an #if statement.
#function clr($find) {
#each $colorCategoryName, $colorCategory in $colors {
#each $colorName, $color in $colorCategory {
#if $find == $colorName {
#return $color;
}
}
}
}
For example: This...
$colors: (
"primary": (
"milk": #fff,
"cola": #000,
"mine-shaft": #232323,
),
"secondary": (
"pampas": #f4f1ef,
"pearl-brush": #e9e2dd,
"alto": #ddd,
),
);
#function clr($find) {
#each $colorCategoryName, $colorCategory in $colors {
#each $colorName, $color in $colorCategory {
#if $find == $colorName {
#return $color;
}
}
}
}
body {
color: clr(cola);
background-color: clr(pampas);
}
Will output:
body {
color: #000;
background-color: #f4f1ef;
}
Let me know if this isn't what you were looking for!

scss interpolation help for passing the values

I need to create dynamic classes for which I am creating a scss code to create the classes for all the possible values. Below is my code:-
$colors: (
"black": "0,0,0",
"white": "255,255,255",
"red" : "255,0,0"
);
$opacity:9;
#for $i from 0 through $opacity {
$j:$i/10;
#each $color, $rgb in $colors {
$rgba: "#{$rgb},#{$j}";
.background-#{$color}-#{$i} {
background: #{$rgba};
}
}
}
I want it to give out put as :-
.background-black-0 {
background: rgba(0,0,0,0);
}
.background-white-0 {
background: rgba(255,255,255,0);
}
.background-red-0 {
background: rgba(255,0,0,0);
}
.background-black-1 {
background: rgba(0,0,0,0.1);
}
.background-white-1 {
background: rgba(255,255,255,0.1);
}
.background-red-1 {
background: rgba(255,0,0,0.1);
}
struggling with the interpolation for rgba(). Otherwise its getting the exact values I want. If you check my code in https://www.sassmeister.com/ you will see it.
You could use directly your colors as rgb color in your map and then add opacity in your #for loop:
$colors: (
"black": rgb(0,0,0),
"white": rgb(255,255,255),
"red": rgb(255,0,0)
);
$opacity:9;
#for $i from 0 through $opacity {
$j:$i/10;
#each $color, $rgb in $colors {
.background-#{$color}-#{$i} {
background: rgba($rgb, $j);
}
}
}

SASS offset position mixin troubles

I've been trying to make an offset positioning mixin for the past couple of hours. I've been struggling with lots of errors, I cant understand what is wrong.
Here is the latest version,
#function is-offset-prop-valid($value) {
$values: auto initial inherit 0;
#return (type-of($value) == number and not unitless($value))
and (index($values, $value) != false);
}
#mixin position($position, $args) {
$offsets: top right bottom left;
#each $offset in $offsets {
$i: index($args, $offset);
#if $i == length($args) {
#error "Empty offset values not allowed";
} else {
#if is-offset-prop-valid(nth($args, $i+1)) {
#{$offset}: nth($args, $i+1);
} else {
#error "Set value #{nth($args, $i+1)} not a valid property for #{$offset}";
}
}
}
positon: $position;
}
Normally I would have the nth($args, $i + 1) set as variable, but for the sake of this example, I left it like that.
When I use the mixin
.abs {
#include position(absolute, top 10px);
}
I get this error from the inner if statement:
Set value 10px not a valid property for top
I fixed your code and rewrited it a bit. Sassmeister demo.
At first, is-offset-prop-valid function is now more readable.
Secondly, mixin position does loop through arguments ($args), not through $offsets. And I added more argument checks (look at comments). And you need to write # symbol before else: #else.
#function is-offset-prop-valid($value) {
$values: auto initial inherit 0;
$is-numder: type-of($value) == number;
$is-unitless: unitless($value);
$match-default-values: index($values, $value) != null;
#return ($is-numder and not $is-unitless) or $match-default-values;
}
#mixin position($position, $args...) {
$offsets: top right bottom left;
#if length($args) == 0 {
#error "Empty offset values not allowed.";
}
#each $arg in $args {
// Check offset key-value pair
#if length($arg) != 2 {
#error "Invalid pair offset-name: offset-value.";
}
$offset: nth($arg, 1);
$value: nth($arg, 2);
// Check offset name parameter
#if index($offsets, $offset) == null {
#error "Invalid offset name: `#{$offset}`.";
}
// Check offset value parameter
#if not is-offset-prop-valid($value) {
#error "Set value `#{$value}` not a valid property for `#{$offset}`.";
}
#{$offset}: $value;
}
position: $position;
}
.abs {
#include position(absolute, top 10px, left 23px);
}
But it seems to me that a simple set position is much simpler and more understandable:
.abs {
top: 10px;
left: 23px;
position: absolute;
}

SASS and data attribute multiple

I have a problem with the nesting of SAS to make multiple selections, nose much about it, I hope you can help me and understand (because I do not write very good English).
SASS mixin:
#mixin data($x) {
$sel: &;
$collector: ();
#for $i from 1 through length($sel) {
$s: nth($sel, $i);
$last: nth($s, -1);
#if str-slice($last, -1) == "]" {
// if is just the bare attribute with no value, $offset will be -1, otherwise it will be -2
$offset: -1;
$current-x: $x;
#if str-slice($last, -2) == '"]' {
// this attribute already has a value, so we need to adjust the offset
$offset: -2;
} #else {
// no attribute value, so add the equals and quotes
$current-x: '="' + $x + '"';
}
$last: str-slice($last, 1, $offset - 1) + $current-x + str-slice($last, $offset);
$collector: append($collector, set-nth($s, -1, $last), comma);
} #else {
// following line will append $x to your non-attribute selector
$collector: append($collector, selector-append($s, $x), comma);
// the following line will not change your non-attribute selector at all
//$collector: append($collector, $s, comma);
}
}
#at-root #{$collector} {
#content;
}
}
SASS:
[data-content] {
#include data("content") {
background: black;
}
}
Output:
[data-content="content"] {
background: black;
}
The problem is I can not nest more than one item, for example does not work:
[data-content] {
#include data("content", "menu") {
background: black;
}
}
Output:
[data-content="content"],
[data-content="menu"] {
background: black;
}
Any way to solve?
You can always do something like this if you don't mind having to specify your selectors instead of passing them through as variables.
[data-content="content"], [data-content="menu"]{
#include data() {
background: black;
}
}

Resources