SCSS / Sass: How do i change a mixin to output specific CSS? - css

I've got a function written in SCSS / Sass which loops every item of a map with the #each function.
$classes-map: ("class-a": "doesntmatter",
"class-b": "doesntmatter",
'class-c': 'doesntmatter');
#mixin function {
#each $class, $property in $classes-map {
%#{$class} {
content: $property;
}
#for $i from 1 to 4 {
&.#{$class}-#{$i} {
#extend %#{$class};
}
}
}
}
On every item of the map is also a #for loop, to make a list of classes with komma's and everytime a different number.
Here's what the output should be:
.class-a-1, .class-a-2, .class-a-3 {
content: "doesntmatter";
}
.class-b-1, .class-b-2, .class-b-3 {
content: "doesntmatter";
}
.class-c-1, .class-c-2, .class-c-3 {
content: "doesntmatter";
}
I'm calling this function by using this piece of code:
.parent {
&.child {
#include function;
}
}
CSS
.parent.child .parent.child.class-a-1, .parent.child .parent.child.class-a-2, .parent.child .parent.child.class-a-3 {
content: "doesntmatter";
}
.parent.child .parent.child.class-b-1, .parent.child .parent.child.class-b-2, .parent.child .parent.child.class-b-3 {
content: "doesntmatter";
}
.parent.child .parent.child.class-c-1, .parent.child .parent.child.class-c-2, .parent.child .parent.child.class-c-3 {
content: "doesntmatter";
}
Desired CSS
.parent.child.class-a-1, .parent.child.class-a-2, .parent.child.class-a-3 {
content: "doesntmatter";
}
.parent.child.class-b-1, .parent.child.class-b-2, .parent.child.class-b-3 {
content: "doesntmatter";
}
.parent.child.class-c-1, .parent.child.class-c-2, .parent.child.class-c-3 {
content: "doesntmatter";
}
How do i change the #mixin function to resolve this problem?

What the &!
Oh so close! As you want to chain / include the parent selector using &, then anything in the mixin should follow suit. The missing piece was the placeholder &%#{$class}.
SCSS:
$classes-map: ("class-a": "doesntmatter",
"class-b": "doesntmatter",
'class-c': 'doesntmatter');
#mixin function {
#each $class, $property in $classes-map {
&%#{$class} {
content: $property;
}
#for $i from 1 to 4 {
&.#{$class}-#{$i} {
#extend %#{$class};
}
}
}
}
.parent {
&.child {
#include function;
}
}
CSS:
.parent.child.class-a-1, .parent.child.class-a-2, .parent.child.class-a-3 {
content: "doesntmatter";
}
.parent.child.class-b-1, .parent.child.class-b-2, .parent.child.class-b-3 {
content: "doesntmatter";
}
.parent.child.class-c-1, .parent.child.class-c-2, .parent.child.class-c-3 {
content: "doesntmatter";
}

Related

how to use grandparent selector in scss

I need to style my button component in 5 states with differently colored icon and use for this following css
#add-member-dialog #add-username-to-list .to-list-button-icon:before {
content: url("../images/func_personplus_16_ena.png");
}
#add-member-dialog #add-username-to-list:hover .to-list-button-icon:before {
content: url("../images/func_personplus_16_hov.png");
}
#add-member-dialog #add-username-to-list:disabled .to-list-button-icon:before {
content: url("../images/func_personplus_16_dis.png");
}
#add-member-dialog #add-username-to-list:active .to-list-button-icon:before {
content: url("../images/func_personplus_16_act.png");
}
#add-member-dialog #add-username-to-list:active:hover .to-list-button-icon:before {
content: url("../images/func_personplus_16_onb.png");
}
such items differs in pseudoclasses related to #add-username-to-list.
I tried to switch entire css file to scss and wanted to optimize this style but I was not able move further than:
#add-member-dialog {
#add-username-to-list {
.to-list-button-icon:before {
content: url("../images/func_personplus_16_ena.png");
}
&:hover .to-list-button-icon:before {
content: url("../images/func_personplus_16_hov.png");
}
&:disabled .to-list-button-icon:before {
content: url("../images/func_personplus_16_dis.png");
}
&:active .to-list-button-icon:before {
content: url("../images/func_personplus_16_act.png");
}
&:active:hover .to-list-button-icon:before {
content: url("../images/func_personplus_16_onb.png");
}
}
}
Is it possible to do something like this?
#add-member-dialog {
#add-username-to-list {
.to-list-button-icon:before {
content: url("../images/func_personplus_16_ena.png");
&&:hover & {
content: url("../images/func_personplus_16_hov.png");
}
...
}
}
}
where && will represents grandparent selector #add-username-to-list.
I also tried to apply pattern #{$grandparent}:tmp $ but the result selector looked like this: #add-member-dialog #add-username-to-list:tmp #add-member-dialog #add-username-to-list .to-list-button-icon:before
#add-member-dialog {
#add-username-to-list {
$grandparent: &;
.to-list-button-icon:before {
content: url("../images/func_personplus_16_ena.png");
#{$grandparent}:hover & {
content: url("../images/func_personplus_16_hov.png");
}
...
}
}
}
Any advice whether is this possible?
You can use interpolation to print out your grandparent selector (note the #at-root):
#add-member-dialog {
#add-username-to-list {
$g: &; // grandparent
$p: '../images/func_personplus_16_'; // icon path
#at-root {
.to-list-button-icon:before {
#{$g} & { content: url(#{$p}ena.png); }
#{$g}:hover & { content: url(#{$p}hov.png); }
#{$g}:disabled & { content: url(#{$p}dis.png); }
#{$g}:active & { content: url(#{$p}act.png); }
#{$g}:active:hover & { content: url(#{$p}onb.png); }
}
}
}
}

BEM generator mixin want to match MDL structure

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.

Reverse states on hover with Less

I have the following LESS code:
.favourite-action-link {
&:after {
color:#grey;
content: '\e836';
}
&.is-favourite:after {
color:#red;
content: '\e811';
}
&:hover {
&:after {
color:#red;
content: '\e811';
}
&.is-favourite:after {
color:#grey;
content: '\e836';
}
}
}
With the essential goal being that there is a normal state and a hover state, that are reversed when another class is present. I'll be repeating this for other actions (eg .share-action-link, .review-action-link etc) and this just seems messy the way I have it. Is there a way to create a mixin such that I could provide this like so:
.favourite-action-link {
&:after {
color:#grey;
content: '\e836';
&:hover {
color:#red;
content: '\e811';
}
.reverseOnClass(is-favourite);
}
}
Or something like that? The only way I can think of so far would be to do:
.favourite-action-link {
&:after {
color:#grey;
content: '\e836';
}
&.active:after {
color:#red;
content: '\e811';
}
}
and then to use jQuery instead to do the hover - toggling .active on (isHovering XOR hasClass(is-favourite)) - but turning LESS into LESS + jQuery is the opposite of fixing a mess/maintainability issue.
I would really recommend writing it like below because it keeps the code simple and easy to read.
.favourite-action-link {
&:after, &.is-favourite:hover:after {
color: #grey;
content: '\e836';
}
&:hover:after, &.is-favourite:after {
color: #red;
content: '\e811';
}
}
But if you really want to use a mixin to avoid repeating the selectors then you could write it like below. This mixin takes two rulesets as input and they are applied to the required selectors.
.favourite-action-link {
.rules-gen(
{
color: #grey;
content: '\e836';
};
{
color: #red;
content: '\e811';
}
);
}
.rules-gen(#rule1; #rule2){
&:after, &.is-favourite:hover:after {
#rule1();
}
&:hover:after, &.is-favourite:after {
#rule2();
}
}
In both these methods, the selectors are also grouped and that also means reduced lines of code.
Demo
Or, if the extra class is not always is-favourite and it could also be something else then you could pass it also to the mixin as a parameter like below:
.favourite-action-link {
.rules-gen(
{
color: grey;
content: '\e836';
};
{
color: red;
content: '\e811';
};
~"is-favourite"
);
}
.share-action-link {
.rules-gen(
{
color: yellow;
content: '\e836';
};
{
color: gold;
content: '\e811';
};
~"active"
);
}
.rules-gen(#rule1; #rule2; #addedClass){
&:after, &.#{addedClass}:hover:after {
#rule1();
}
&:hover:after, &.#{addedClass}:after {
#rule2();
}
}
Demo

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) { }

Extending a Nested Placeholder in SCSS

Is it possible to #extend a SCSS placeholder with nesting, and have that nesting reflected in the resulting class?
Given a nested placeholder:
%my-form-field {
...
&__label {
...
}
&__feedback {
...
}
}
I currently have to do the following:
.one-of-many-targets {
#extend %my-form-field;
&__label {
#extend %my-form-field__label;
}
&__feedback {
#extend %my-form-field__feedback;
}
}
But I'd like to be able to simplify this to:
.one-of-many-targets {
#extend %my-form-field;
}
... and have it resolve to:
.one-of-many-targets { ... }
.one-of-many-targets__label { ... }
.one-of-many-targets__feedback { ... }
Is there a different way to write my placeholder and #extends to make the SCSS cleaner, as in the 2nd example?
You can use a mixin instead:
#mixin my-form-field() {
width: 10px;
&__label {
width: 20px;
}
&__feedback {
width: 30px;
}
}
.one-of-many-targets {
#include my-form-field();
}
will generate:
.one-of-many-targets {
width: 10px;
}
.one-of-many-targets__label {
width: 20px;
}
.one-of-many-targets__feedback {
width: 30px;
}
You could try use selector.append()
See: https://github.com/sass/sass/issues/2808#issuecomment-574413393
Also see more info why parent selector didn't work as you expect in extend-only selectors: https://github.com/sass/sass/issues/2262#issuecomment-291645428

Resources