BEM generator mixin want to match MDL structure - css

So I have the following mixins to generate my BEM classes:
$es: '__';
$ms: '--';
#function to-string($selector) {
$selector: inspect($selector); //cast to string
$selector: str-slice($selector, 2, -2); //remove brackets
#return $selector;
}
#function contains-modifier($selector) {
$selector: to-string($selector);
#if str-index($selector, $ms) {
#return true;
} #else {
#return false;
}
}
#function get-block($selector) {
$selector: to-string($selector);
$modifier-start: str-index($selector, $ms) - 1;
#return str-slice($selector, 0, $modifier-start);
}
#mixin blck($block) {
.#{$block} {
#content;
}
}
#mixin elem($element) {
$selector: &;
#if contains-modifier($selector) {
$block: get-block($selector);
#at-root {
#{$selector} {
#{$block+$es+$element} {
#content;
}
}
}
} #else {
#at-root {
#{$selector+$es+$element} {
#content;
}
}
}
}
#mixin modf($modifier) {
#at-root {
#{&}#{$ms+$modifier} {
#content;
}
}
}
#include blck(block) {
background: red;
#include elem(child){
color: blue;
};
#include modf(modifier) {
background: blue;
#include elem(child) {
color: red;
}
}
}
Now this actually generates perfect BEM style code but I want to to match the MDL code structure which means I want to more specificity nest my modifiers form this
.block
.block--modifer
to
.bock.block--modifier
The reason for this as said before is to match MDL an example of this formatting can be seen here: https://github.com/google/material-design-lite/blob/master/src/card/_card.scss
Now I can almost get the desired effect by changing this line:
#mixin modf($modifier) {
#at-root {
#{&}#{$ms+$modifier} {
#content;
}
}
}
To this:
#mixin modf($modifier) {
#at-root {
#{&}#{&}#{$ms+$modifier} {
#content;
}
}
}
But that changes the CSS output from this:
.block {
background: red;
}
.block__child {
color: blue;
}
.block--modifier {
background: blue;
}
.block--modifier .block__child {
color: red;
}
To this:
.block {
background: red;
}
.block__child {
color: blue;
}
.block.block--modifier {
background: blue;
}
.block.block--modifier .block.block__child {
color: red;
}
Now as you can see this fixes the modifier specificity but breaks the modifier child.
The desired output is as follows:
.block.block--modifier .block__child {
color: red;
}
You can see it all in action here: http://codepen.io/crashy/pen/wGWPvr

Here is something quick i came up with by forking your pen:
Add the #{$selector} in modf mixin.
Modify the get-block function so that it returns the actual base class. At first we slice the string up to the point of the modifier (--) and then we check if there are more than one classes in the concatenated string. If there are more than one, we get the first.
Link from codepen: http://codepen.io/MKallivokas/pen/bpBYEg
$es: '__'; // Element
$ms: '--'; // Modifier
$ns: 'ns'; // Name space (Set to null if un-wanted)
#if($ns) {
$ns: $ns + '-';
}
#function to-string($selector) {
$selector: inspect($selector); //cast to string
$selector: str-slice($selector, 2, -2); //remove brackets
#return $selector;
}
#function contains-modifier($selector) {
$selector: to-string($selector);
#if str-index($selector, $ms) {
#return true;
} #else {
#return false;
}
}
#function get-block($selector) {
// do what get-block was doing in order to
// remove the modifier's part from the string
$selector: to-string($selector);
$modifier-start: str-index($selector, $ms) - 1;
$selector: str-slice($selector, 0, $modifier-start);
// remove the first dot
$selector: str-slice($selector, 2, str-length($selector));
// check if there is another dot
$modifier-start: str-index($selector, '.') - 1;
#if $modifier-start >= 0 {
// if there's another dot we slice the string up to that point
$selector: str-slice($selector, 0, $modifier-start);
}
// we insert the dot that we removed to the start
$selector: str-insert($selector, '.', 1);
#return $selector;
}
#mixin blck($block) {
.#{$ns}#{$block} {
#content;
}
}
#mixin elem($element) {
$selector: &;
#if contains-modifier($selector) {
$block: get-block($selector);
#at-root {
#{$selector} {
#{$block+$es+$element} {
#content;
}
}
}
} #else {
#at-root {
#{$selector+$es+$element} {
#content;
}
}
}
}
#mixin modf($modifier) {
$selector: &;
#at-root {
#{$selector}#{$selector+$ms+$modifier} {
#content;
}
}
}
#include blck(block) {
background: red;
#include elem(child){
color: blue;
};
#include modf(modifier) {
background: blue;
#include elem(child) {
color: red;
}
}
}
I don't know if it suits exactly your needs or covers every case of what you want to do but i hope it helps.

Related

Syntax for if/else condition in SCSS mixins

I created a SASS #mixin which contains #if conditions to assign styling to elements based on their z-index property to create some sort of elevation.
However what ever I am trying it will not work out.
I'm pretty sure I am doing something just slightly wrong that affects everything else.
I'd appreciate your feedback. Thanks in advance!
$background: #121212;
$surface: #1f1f1f;
$surface-shade_1: #282828;
$surface-shade_2: #303030;
%surface {
background-color: $surface;
}
%surface-shade_1 {
background-color: $surface-shade_1;
}
%surface-shade_2 {
background-color: $surface-shade_2;
}
#mixin elevation($elevationValue) {
#if $elevationValue>0 {
#extend %surface;
}
#else if $elevationValue>4 or $elevationValue=4 {
#extend %surface-shade_1;
}
#else if $elevationValue>8 or $elevationValue=8 {
#extend %surface-shade_2;
}
z-index: $elevationValue * 50;
}
nav {
#mixin elevation(4);
}
If you want to use #mixin inside the CSS files you can use like #include mixin-name and also use directly $elevationValue >= 4 instead of $elevationValue>4 or $elevationValue=4 it becomes much cleaner.
$background: #121212;
$surface: #1f1f1f;
$surface-shade_1: #282828;
$surface-shade_2: #303030;
%surface {
background-color: $surface;
}
%surface-shade_1 {
background-color: $surface-shade_1;
}
%surface-shade_2 {
background-color: $surface-shade_2;
}
#mixin elevation($elevationValue) {
#if $elevationValue > 0 {
#extend %surface;
}
#else if $elevationValue >= 4 {
#extend %surface-shade_1;
}
#else if $elevationValue >= 8 {
#extend %surface-shade_2;
}
z-index: $elevationValue * 50;
}
nav {
#include elevation(4);
}

SASS – looping through map via mixin doesn’t compile CSS

what I have is a simple SASS color map:
$brand_col: (
def: blue,
mus: red,
ser: yellow
);
The following:
#each $brand, $col in $brand_col {
body.#{$brand} {
background: $col;
}
}
leads to expected output:
body.def { background: blue; }
body.mus { background: red; }
body.ser { background: yellow; }
When I try to put the same thing into a mixin like so:
$color: null;
#mixin branding {
#each $brand, $col in $brand_col {
&.#{$brand} {
$color: $col;
#content;
}
}
}
.body { #include branding { background: $color; } }
I would expect the same output, but nothing is getting compiled at all. I copied the mixin from a sass specific site and don’t fully understand the whole process. Any hints what I'm doing wrong?
Thanks
Ralf
To achive the same result as in your first example, have two options:
Option 1
Make a simple non-reusable mixin:
$brand_col: (
def: blue,
mus: red,
ser: yellow
);
#mixin branding {
#each $brand, $col in $brand_col {
&.#{$brand} {
background: $col;
}
}
}
.body {
#include branding;
}
This will compile to:
.body.def {
background: blue;
}
.body.mus {
background: red;
}
.body.ser {
background: yellow;
}
Option 2
Make a reusable mixin, so you can pass the color map to apply:
$brand_colors: (
def: blue,
mus: red,
ser: yellow
);
#mixin branding($colors) {
#each $class, $color in $colors {
&.#{$class} {
background: $color;
}
}
}
.body {
#include branding($brand_colors);
}
// Latter you can use it to apply the same 'branding' for any other element
div {
#include branding($brand_colors);
}
Will compile to:
.body.def {
background: blue;
}
.body.mus {
background: red;
}
.body.ser {
background: yellow;
}
div.def {
background: blue;
}
div.mus {
background: red;
}
div.ser {
background: yellow;
}
You could even implement a second parameter to the mixin to specify which css property you want to apply, with background as a default:
#mixin branding($colors, $property: background) {
#each $class, $color in $colors {
&.#{$class} {
#{$property}: $color;
}
}
}
// Latter you can use it to apply the same 'branding' for any other element and property
h1 {
#include branding($brand_colors, color);
}
Will compile to:
h1.def {
color: blue;
}
h1.mus {
color: red;
}
h1.ser {
color: yellow;
}
You can find out more about mixins here.
Hope it helps!
What do you mean by $color: $col;? no such property like "null" in CSS, because when you set $color: null at top and then trying to set a property $color: $col; you actually trying to set like that null: blue; this is nothing mean anything to compiler.
I think you no need #content directive use here. You should try just following way:
$brand_col: (
def: blue,
mus: red,
ser: yellow
);
#mixin branding {
#each $brand, $col in $brand_col {
&.#{$brand} {
background: $col;
}
}
}
.body { #include branding(); }

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;
}
}

Sass: alternative variable in less [duplicate]

I'm trying to loop through a list of values in Sass and use interpolation of the current key to dynamically output class names that utilize #include and #extend, respectively.
Here is a pen showing the problem, simplified. http://codepen.io/ghepting/pen/vBmLy
As you can see in the markup, I have tried including the "_" inside of the interpolated string as well as outside of it. Is there something I'm missing to work around this limitation of how Sass supports interpolation?
(Note: the OP's pen has disappeared. This is not the original code found in the pen, but a rough approximation of the problem)
$error-light: red;
$error-dark: darken(red, 10%);
$success-light: green;
$success-dark: darken(green, 10%);
$dialogs: error, success;
#each $d in $dialogs {
.#{$d} {
background: $#{$d}-light;
}
}
Interpolation doesn't work on mixins or variables at this point in time. You'll have to come up with a different way to achieve your goal.
As of Sass 3.3, you can use mappings for this purpose for variables:
$dialogs:
( error:
( light: red
, dark: darken(red, 10%)
)
, success:
( light: green
, dark: darken(green, 10%)
)
);
#each $name, $colors in $dialogs {
.#{$name} {
color: map-get($colors, dark);
}
}
And for functions:
#function green() {
#return lighten(green, 10%);
}
#function red() {
#return lighten(red, 10%);
}
#mixin my-bg($function-name) {
background: call($function-name);
}
.foo {
#include my-bg('red');
}
Alternative workaround (for a particular use case):
https://sass-lang.com/documentation/at-rules/mixin#passing-arbitrary-arguments
💡 Fun fact:
Because an argument list keeps track of both positional and keyword arguments, you use it to pass both at once to another mixin. That makes it super easy to define an alias for a mixin!
If you are interested in mixin interpolation because you have a group of mixins, like this:
//_mixins.scss
#mixin text-style-1($args...){ //sass here }
#mixin text-style-2($args...){ //sass here }
#mixin text-style-3($args...){ //sass here }
//_text.scss
.text-style-1 {
#include text-style-1;
}
.text-style-1-contrast {
#include text-style-1($contrast: true);
}
.text-style-2 {
#include text-style-2;
}
.text-style-2-contrast {
#include text-style-2($contrast: true);
}
We can take advantage of passing arbitrary arguments and use an alias for the group:
//_mixins.scss
#mixin text-style-1($args...){ //sass here }
#mixin text-style-2($args...){ //sass here }
#mixin text-style-3($args...){ //sass here }
#mixin text($mixin, $args...) {
#if $mixin == 'style-1' { #include text-style-1($args...); }
#else if $mixin == 'style-2' { #include text-style-2($args...); }
#else if $mixin == 'style-3' { #include text-style-3($args...); }
}
//_text.scss
$text-styles: 'style-1', 'style-2', 'style-3';
#each $style in $text-styles {
.text-#{$style} {
#include text($style);
}
.text-#{$style}-contrast {
#include text($style, $contrast: true);
}
}
Ran into this issue of trying to include an interpolated variable inside a mixin and was able to resolve it with placeholders:
%color-scheme-dark-bg-1 { background-color: #4e5163; }
%color-scheme-dark-color-1 { color: #4e5163 !important; }
%color-scheme-light-bg-1 { background-color: #c7c8ce; }
%color-scheme-dark-bg-2 { background-color: #fd6839; }
%color-scheme-dark-color-2 { color: #fd6839 !important; }
%color-scheme-light-bg-2 { background-color: #fecfc1; }
.card_color {
#mixin CardColorScheme($arg: 1) {
.borderPercent {
#extend %color-scheme-dark-bg-#{$arg};
}
.border {
#extend %color-scheme-light-bg-#{$arg};
}
ul li:before {
#extend %color-scheme-dark-color-#{$arg};
}
.percent {
#extend %color-scheme-dark-color-#{$arg};
}
.heading {
#extend %color-scheme-dark-color-#{$arg};
}
}
&--scheme {
&-1 {
#include CardColorScheme(1);
}
&-2 {
#include CardColorScheme(2);
}
}
}
Hat tip to: https://krasimirtsonev.com/blog/article/SASS-interpolation-in-a-name-of-variable-nest-variables-within-variables

SASS :not selector

I have a :not css selector in SASS mixin but it doesn't do anything:
Code Snippet:
#mixin dropdown-pos($pos:right) {
&:not(.notip) {
#if $comp-tip == true{
#if $pos == right {
top:$dropdown-width * -0.6;
#include tip($pos:$pos);
}
}
}
&.notip {
#if $pos == right {
top: 0;
left:$dropdown-width * 0.8;
}
}
}
The .notip class is being generated but no CSS is being generated for :not(.notip).
I tried re-creating this, and .someclass.notip was being generated for me but .someclass:not(.notip) was not, for as long as I did not have the #mixin tip() defined. Once I had that, it all worked.
http://sassmeister.com/gist/9775949
$dropdown-width: 100px;
$comp-tip: true;
#mixin tip($pos:right) {
}
#mixin dropdown-pos($pos:right) {
&:not(.notip) {
#if $comp-tip == true{
#if $pos == right {
top:$dropdown-width * -0.6;
background-color: #f00;
#include tip($pos:$pos);
}
}
}
&.notip {
#if $pos == right {
top: 0;
left:$dropdown-width * 0.8;
background-color: #00f;
}
}
}
.someclass { #include dropdown-pos(); }
EDIT: http://sassmeister.com/ is a good place to debug your SASS because it gives you error messages. Undefined mixin 'tip'. it what I get when I remove #mixin tip($pos:right) { }

Resources