In the following scenario I need to support as many browsers as possible.
I am building a SCSS #mixin that prefixes background-image with vendor prefixes, but also listens to see if a linear-gradient is declared, and if it is, then prefix that as well.
My code looks like this:
#function str-coerce($string) {
#return inspect($string);
}
#function str-replace($string, $find, $replace: "") {
$index: str-index($string, $find);
#if $index {
#return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($find)), $find, $replace);
}
#return $string;
}
#mixin background-image($values...) {
$vendors: (-webkit-, -moz-, -o-);
#each $vendor in $vendors {
$string: str-coerce($values);
#if (str-index($string, "linear-gradient")) {
$string: str-replace($string, "linear-gradient", #{$vendor + "linear-gradient"});
#if (str-index($vendor, "-o-")) {
$vendor: str-replace($vendor, "-o-");
}
#{$vendor + "background-image"}: $string;
} #else {
#if not (str-index($vendor, "-o-")) {
#{$vendor + "background-image"}: $values;
}
}
}
background-image: $values;
}
Usage and output looks like this:
// usage
.foo {
#include background-image(url("../example.svg"));
}
.bar {
#include background-image(linear-gradient(45deg, red, blue));
}
// output
.foo {
-webkit-background-image: url("../example.svg");
-moz-background-image: url("../example.svg");
background-image: url("../example.svg");
}
.bar {
-webkit-background-image: (-webkit-linear-gradient(45deg, red, blue),);
-moz-background-image: (-moz-linear-gradient(45deg, red, blue),);
background-image: (-o-linear-gradient(45deg, red, blue),);
background-image: linear-gradient(45deg, red, blue);
}
My question is, what is going wrong in my type coercion that is causing my linear-gradient vendor prefixes to be wrapped in brackets and followed with a comma? e.g. (-moz-linear-gradient(45deg, red, blue),).
While I'm not entirely sure why your values are being wrapped in (), you can use the str-replace function to remove them.
To remove the leading ( update your str-replace function to:
#function str-replace($string, $search, $replace: '') {
$index: str-index($string, $search);
#if $index {
#return str-slice($string, 1, $index - 2) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
}
#return $string;
}
Then update this line to remove the trailing ),
#{$vendor + "background-image"}: str-slice($string, 1, -3);
Related
Below is some sass Im trying out, The css generated is correct, Im just trying to reduce my mixin duplication. The two mixins genProps and genPropsAmp are exactly the same except that the Amp mixin uses the & to prefix the rule with &. I tried to just add another var to the original mixin $amp: false and prefix my rule with an if so it became #if $amp {&}.#{$class}-... but that didnt work nor did trying to have it as an interpolation. Is there away of conditionaly placing the ampersand so I can just reuse the one mixin?
SASS:
$max_size: 0;
$sub_suffixes: '', 'q', 'h', 't';
$sub_divs: '', '.25', '.5', '.75';
#mixin genProps($class, $props, $val_prefix: r, $max_size: $max_size, $sub_suffixes: $sub_suffixes) {
#for $i from 0 through $max_size {
#each $sub_suffix in $sub_suffixes {
.#{$class}-#{$i}#{$sub_suffix} {
#each $prop in $props {
#{$prop}: var(--#{$val_prefix}-#{$i}#{$sub_suffix});
}
}
}
}
}
#mixin genPropsAmp($class, $props, $val_prefix: r, $max_size: $max_size, $sub_suffixes: $sub_suffixes) {
#for $i from 0 through $max_size {
#each $sub_suffix in $sub_suffixes {
&.#{$class}-#{$i}#{$sub_suffix} {
#each $prop in $props {
#{$prop}: var(--#{$val_prefix}-#{$i}#{$sub_suffix});
}
}
}
}
}
.root{
#include genProps(f, font-size, a);
#include genPropsAmp(m, margin);
}
CSS:
/* without amp */
.root .f-0 {font-size: var(--a-0);}
.root .f-0q {font-size: var(--a-0q);}
/*...*/
/* with amp */
.root.m-0 {margin: var(--r-0);}
.root.m-0q {margin: var(--r-0q);}
/*...*/
You can use a ternary operator: if($amp, "&", "")
#mixin genProps($class, $props, $amp, $val_prefix: r, $max_size: $max_size, $sub_suffixes: $sub_suffixes) {
#for $i from 0 through $max_size {
#each $sub_suffix in $sub_suffixes {
#{if($amp, "&", "")}.#{$class}-#{$i}#{$sub_suffix} {
#each $prop in $props {
#{$prop}: var(--#{$val_prefix}-#{$i}#{$sub_suffix});
}
}
}
}
}
#mixin genProps($class, $props, $amp: false, $val_prefix: r, $max_size: $max_size, $sub_suffixes: $sub_suffixes) {
#for $i from 0 through $max_size {
#each $sub_suffix in $sub_suffixes {
#if $amp {
&.#{$class}-#{$i}#{$sub_suffix} {
#each $prop in $props {
#{$prop}: var(--#{$val_prefix}-#{$i}#{$sub_suffix});
}
}
} #else {
.#{$class}-#{$i}#{$sub_suffix} {
#each $prop in $props {
#{$prop}: var(--#{$val_prefix}-#{$i}#{$sub_suffix});
}
}
}
}
}
}
this is the best Iv got so far, not great as theres still a lot of duplication in the if elses but at least I only have one mixin now
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;
}
I am trying to make a mixin for standard background images and high contrasted background images, not sure how do i do it. can someone pl. shade some light
I have a mixin for text color and background color :
$colors:(
standard: (
primary-color-blue: #2aace2,
mid-blue-color:#2695d2,
dark-blue-color:#124b62
),
contrasted: (
primary-color-blue: #177eab,
mid-blue-color:#1c6f9b,
dark-blue-color:#124b62
)
);
#function get-color($key, $type: 'standard'){
#each $name, $color in map-get($colors, $type){
#if($key == $name){
#return $color
}
}
}
#mixin get-color($property,$color){
#{$property}: get-color($color);
#at-root body.contrasted & {
#{$property}: get-color($color, contrasted);
}
}
.className{
#include get-color(background-color, primary-color-blue)
}
output :
.className {
background-color: #2aace2;
}
body.contrasted .className {
background-color: #177eab;
}
Thanks !!
EDITED AS PER ANSWER : Edited with extra color / transparent variable, but throws error Edited with extra color / transparent variable, but throws error
$images:(
standard: (
pdp-more-icon-mob: white + $img_file_path + 'product-detail-page/Product-surface-display/more-icon/More-Mobile.png',
pdp-more-icon-mob-retina : #f00 + $img_file_path + 'product-detail-page/Product-surface-display/more-icon/More-Mobile__2x.png'
),
contrasted: (
pdp-more-icon-mob: green + $img_file_path + 'product-detail-page/Product-surface-display/more-icon/More-Desktop-High-Contrast.png',
pdp-more-icon-mob-retina : $green + $img_file_path + 'product-detail-page/Product-surface-display/more-icon/More-Desktop-High-Contrast__2x.png'
)
);
#function get-images($key, $type: 'standard'){
#each $name, $image in map-get($images, $type){
#if($key == $name){
#return $image
}
}
}
#mixin get-images($property, $color, $image, $retina:false){
#{$property}: get-images($color + $image);
#at-root body.contrasted & {
#{$property}: get-images($color + $image, contrasted);
}
#if($retina){
#media (-webkit-min-device-pixel-ratio: 2){
#{$property}: get-images($color + $image#{-retina});
#at-root body.contrasted & {
#{$property}: get-images($color + $image#{-retina}, contrasted);
}
}
}
}
.className{
#include get-images(background, color, image, true);
}
Without refactoring the mixin is possible to use it this way—by adding your images properly in your map:
$colors:(
standard: (
primary-color-blue: #2aace2,
mid-blue-color:#2695d2,
dark-blue-color:#124b62,
image: 'img.png',
image-retina: 'img-retina.png'
),
contrasted: (
primary-color-blue: #177eab,
mid-blue-color:#1c6f9b,
dark-blue-color:#124b62,
image: 'img2.png',
image-retina: 'img2-retina.png'
)
);
#function get-color($key, $type: 'standard'){
#each $name, $color in map-get($colors, $type){
#if($key == $name){
#return $color
}
}
}
#mixin get-color($property,$color){
#{$property}: get-color($color);
#at-root body.contrasted & {
#{$property}: get-color($color, contrasted);
}
}
.className{
#include get-color(background-image, image);
#media (-webkit-min-device-pixel-ratio: 2){
#include get-color(background-image, image-retina)
}
}
But you can also adapt this mixin for images and do so:
$images:(
standard: (
image: 'img.png',
image-retina: 'img-retina.png'
),
contrasted: (
image: 'img2.png',
image-retina: 'img2-retina.png'
)
);
#function get-images($key, $type: 'standard'){
#each $name, $image in map-get($images, $type){
#if($key == $name){
#return $image
}
}
}
#mixin get-images($property,$image, $retina:false){
#{$property}: get-images($image);
#at-root body.contrasted & {
#{$property}: get-images($image, contrasted);
}
#if($retina){
#media (-webkit-min-device-pixel-ratio: 2){
#{$property}: get-images($image#{-retina});
#at-root body.contrasted & {
#{$property}: get-images($image#{-retina}, contrasted);
}
}
}
}
.className{
#include get-images(background-image, image, true);
}
Where the true in 'get-images' is calling to image + '-retina' in the $images map, and outputs:
.className {
background-image: "img.png";
}
body.contrasted .className {
background-image: "img2.png";
}
#media (-webkit-min-device-pixel-ratio: 2) {
.className {
background-image: "img-retina.png";
}
body.contrasted .className {
background-image: "img2-retina.png";
}
}
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;
}
}
I want generate classes like grid-col-1, grid-col-md-1, grid-col-lg-1, grid-col-2, grid-col-md-2, grid-col-lg-2...
So, I have created this mixin to do it:
$grid-columns: 12;
#mixin grid-col-builder($type: false) {
#for $i from 1 through $grid-columns {
$selector: 'grid-col-';
#if $type { $selector: $selector + $type + '-' + $i; }
#else { $selector: $selector + $i; }
.#{$selector} { width: $i/$grid-columns*100%; }
}
}
But I still think the code a bit repetitive. Is there a smarter way to make the condition that adds the type in the selector name?
You can make things more compact by using the if() function:
$grid-columns: 12;
#mixin grid-col-builder($type: false) {
#for $i from 1 through $grid-columns {
.#{'grid-col-' + if($type, $type + '-', '') + $i} {
width: $i / $grid-columns * 100%;
}
}
}