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
Related
I'm trying to create a mixin that allows me to write code blocks flexibly depending on whether body has a certain class.
#mixin when($class) {
body.#{$class} & {
#content;
}
}
Use Cases
#hero {
#include when('theme--dark') {
span {
content: 'Good Evening';
}
}
}
#hero {
#include when('page-landing') {
button.cta {
padding: 3rem 5rem;
font-size: 3rem;
background-color: $green;
}
}
}
even better if the following can be achieved
#mixin when($parent, $class) {
#{$parent}.#{$class} & {
#content;
}
}
#hero {
#include when('body','page-landing') {
button.cta {
padding: 3rem 5rem;
font-size: 3rem;
background-color: $green;
}
}
}
non of the previous codes work, not even certain of the syntax but wondering if something similar can be produced, any help is appreciated thanks!
Though your approach is absolutely fine, here is a slightly cleaner implementation, that grants a little more flexibilty when it comes to the number of selectors you want to check for.
#mixin when($selectors...) {
$n: "";
#each $selector in $selectors {
$n: str-insert($n, "#{$selector}", str-length($n) + 1);
}
#{$n} {
#content;
}
}
#hero {
#include when('body', '[dark-mode=true]', '.primary') {
color: #fff;
}
}
I was using the following LESS mixins to generate css for bidi usage.
.ltr(#ltrRules) {
body[dir="ltr"] & ,
body[dir="rtl"] *[dir="ltr"] & ,
body[dir="rtl"] *[dir="ltr"]& { #ltrRules(); }
}
.rtl (#rtlRules) {
body[dir="rtl"] & ,
body[dir="ltr"] *[dir="rtl"] & ,
body[dir="ltr"] *[dir="rtl"]& { #rtlRules(); }
}
.bidi(#ltrRules,#rtlRules) {
.ltr(#ltrRules);
.rtl(#rtlRules);
}
those would be later used like this:
// padding
// ------------------------------------------
.padding-start(#p) {
.bidi(
{ padding-left: #p } ,
{ padding-right: #p }
)
}
.padding-end(#p) {
.bidi(
{ padding-right: #p } ,
{ padding-left: #p }
)
}
this way when I eventually wrote:
div.myclass {
.padding-start(10px)
}
I would get a set of rules that would style div.myclass with padding-left:10px if the div was in a left-to-right context or with with padding-right:10px if the div was in a right-to-left context.
I am now trying to convert this to SCSS and I have got to the following:
#mixin ltr {
body[dir="ltr"] & ,
//body[dir="rtl"] *[dir="ltr"]&
body[dir="rtl"] *[dir="ltr"] & { #content }
}
#mixin rtl {
body[dir="rtl"] & ,
//body[dir="ltr"] *[dir="rtl"]&
body[dir="ltr"] *[dir="rtl"] & { #content}
}
/*
#mixin bidi($ltrRules,$rtlRules) {
#include ltr(#include ltrRules);
#include rtl(#include rtlRules);
}
*/
#mixin bidi-padding-start($p) {
#include ltr {
padding-left: $p;
}
#include rtl {
padding-right: $p;
}
}
but I still have several issues:
1) If I uncomment the middle selectors in the rtl and ltr mixins I then get the following error: Invalid CSS after "[dir="ltr"]": expected "{", was "&". "&" may only be used at the beginning of a compound selector.
I can live without this rule but I would prefer not to... I would expect it to compile
.myclass {
#include padding-start(10px)
}
into:
body[dir="rtl"] .myclass ,
body[dir="ltr"] *[dir="rtl"] .myclass ,
body[dir="ltr"] *[dir="rtl"].myclass
2) I can't find a way to implement #mixin bidi because I cant find a way to pass along two content blocks to a mixin.
can someone help with this migration please
Currently SASS's functionality is limited to passing only a single #content block into a #mixin. There was a feature request discussion on a github issue but it was closed, and it looks like it never came to fruition.
In that discussion, someone suggested a workaround that effectively works with #extend. It works by using %placeholders to allow you to reference temporary rulesets. However, it seems a bit hacky and will need to be tested for more complicated rulesets with deeper nesting.
#mixin ltr {
body[dir="ltr"] & ,
//body[dir="rtl"] *[dir="ltr"]&
body[dir="rtl"] *[dir="ltr"] & { #content }
}
#mixin rtl {
body[dir="rtl"] & ,
//body[dir="ltr"] *[dir="rtl"]&
body[dir="ltr"] *[dir="rtl"] & { #content}
}
#mixin bidi {
#content;
#include ltr {
#extend %ltr !optional;
}
#include rtl {
#extend %rtl !optional;
}
}
#mixin padding-end($p) {
#include bidi {
#at-root {
%ltr {
padding-right: $p;
}
%rtl {
padding-left: $p;
}
}
}
}
.test {
#include padding-end(5px);
}
Another option that may be more readable for future devs may simply be to explicitly call both mixins in place of bidi. It may be possible to write a script to make this migration.
#mixin padding-end($p) {
#include rtl {
padding-left: $p;
}
#include ltr {
padding-right: $p;
}
}
I'm trying to style placeholders for input fields (for different browsers) in Sass 3.3.1, and want to change the opacity when the field is focused. I'm having a hard time combining the pseudo-class and pseudo-elements with the ampersand. The following gives a compilation error:
::-webkit-input-placeholder,
:-moz-placeholder,
::-moz-placeholder,
:-ms-input-placeholder{
... some default styling
:focus#{&}{
opacity: 0;
}
}
Can this be done?
Edit
This is the output I am looking for:
::-webkit-input-placeholder {
opacity: 1;
}
:-moz-placeholder{
opacity: 1;
}
::-moz-placeholder{
opacity: 1;
}
:-ms-input-placeholder{
opacity: 1;
}
:focus::-webkit-input-placeholder {
opacity: 0;
}
:focus:-moz-placeholder{
opacity: 0;
}
:focus::-moz-placeholder{
opacity: 0;
}
:focus:-ms-input-placeholder{
opacity: 0;
}
// Cross-browsers opacity: #include opacity(0.5);
#mixin opacity($opacity) {
opacity: $opacity;
$opacity-ie: $opacity * 100;
filter: alpha(opacity=$opacity-ie); //IE8
}
// Transitions for all: #include transition($transition);
$transition: all .3s ease;
#mixin transition($value) {
-webkit-transition: $value;
-moz-transition: $value;
-ms-transition: $value;
-o-transition: $value;
transition: $value;
}
// Input placeholder animation: #include placeholder { color: #000 }
#mixin placeholder {
&::-webkit-input-placeholder {
#content;
}
&:-moz-placeholder {
#content;
}
&::-moz-placeholder {
#content;
}
&:-ms-input-placeholder {
#content;
}
}
// How to use:
input {
text-overflow: ellipsis;
color: mediumseagreen;
#include placeholder {
color: cornflowerblue;
transition: $transition;
#include opacity(1);
}
&:focus {
#include placeholder {
#include opacity(0);
transition: $transition;
}
}
}
This is going to be the other way around, actually:
element:focus{
&::-webkit-input-placeholder,
&:-moz-placeholder,
&::-moz-placeholder,
&:-ms-input-placeholder{
opacity: 0;
}
}
Edit
I seem to have a problem combining them in my testing, but the following should work:
::-webkit-input-placeholder{
color: red;
&:focus{
color: blue;
}
}
On thing to note, though, is that this only works if they are separated out. You cannot combine multiple pseudo-selectors to one definition (like ::-webkit-input-placeholder, :-moz-input-placeholder{ /* this does not work in my testing */ }).
Update 2
Heres a quick SASS function I mocked up that will simplify the process:
#mixin input-placeholder($all:default){
#if $all == default {
$all : ("::-webkit-input-placeholder", ":-moz-placeholder","::-moz-placeholder",":-ms-input-placeholder");
}
#each $placeholder in $all {
#{unquote($placeholder)}{
#content;
}
}
}
You can use it by doing the following:
#include input-placeholder{
color: red;
&:focus {
color: blue;
}
}
This means you only have to write your code once. It will output all of them on individual lines and apply the same rules to them.
Solution from SASS Compass:
// Style the html5 input placeholder in browsers that support it.
//
// The styles for the input placeholder are passed as mixin content
// and the selector comes from the mixin's context.
//
// For example:
//
// #{elements-of-type(text-input)} {
// #include input-placeholder {
// color: #bfbfbf;
// font-style: italic;
// }
// }
//
// if you want to apply the placeholder styles to all elements supporting
// the `input-placeholder` pseudo class (beware of performance impacts):
//
// * {
// #include input-placeholder {
// color: #bfbfbf;
// font-style: italic;
// }
// }
#mixin input-placeholder {
#include with-each-prefix(css-placeholder, $input-placeholder-support-threshold) {
#if $current-prefix == -webkit {
&::-webkit-input-placeholder { #content; }
}
#elseif $current-prefix == -moz {
// for Firefox 19 and below
#if support-legacy-browser("firefox", "4", "19", $threshold: $input-placeholder-support-threshold) {
&:-moz-placeholder { #content; }
}
// for Firefox 20 and above
&::-moz-placeholder { #content; }
}
#elseif $current-prefix == -ms {
&:-ms-input-placeholder { #content; }
}
}
// This is not standardized yet so no official selector is generated.
}
I have a sass mixin that generates the selector name:
#mixin rocks($name){
#{$name}-rocks {
#content;
}
}
called by invoking the mixin:
#include rocks(dave){
color: red;
}
I'd like to create a custom function that calls this mixin for me, to shorten the syntax down to:
rocks(dave){
color: red;
}
Is it possible to 1. call sass #functions outside of a selector? and 2. Invoke mixins from it? Something along the lines of:
#function rocks($name){
#include #rocks($name)
}
I'd prefer custom sass functions over custom ruby functions if possible. Thanks!
No, and no. Functions in Sass can only return values, they cannot be used to create CSS. That is what mixins are for: create mixins to call other mixins.
#function class($name) {
#return $name;
}
#mixin className($classname) {
.#{class($classname)} {
#content;
}
}
#mixin concatinate($classname2) {
&.#{class($classname2)} {
#content;
}
}
#mixin parent($classname1) {
.#{class($classname1)} & {
#content;
}
}
#mixin withparent($classname1) {
#at-root .#{class($classname1)}#{&} {
#content;
}
}
#include className(red) {
color: red;
#include className(check) {
color: green;
#include concatinate(bcd) {
color: green2;
ul {
li {
color: red;
& {
color: blue;
}
#include withparent(colorwe) {
display: none;
}
}
}
#include parent(efg) {
color: green1;
}
}
}
#include className(blue) {
color: blue;
}
}
#DonaldDuck thanks for the feedback. Personally I think that there is no need to call selectors through mixins, if someone try to do that it will be solved by these mixins used.
e.g.:
//call a class
#include className(one) {
properties..
}//o/p .one { properties..}
//add another class with existing one
#include className(one) {
#include concatinate(two) {
#include className(three) {
properties...
}
}
}//o/p .one.two .three { properties...}
//add parent to hierarchy
#include className(one) {
#include parent(two) {
properties..
}
}//o/p .two .one { properties..}
//add class with top of the hierarchy
#include className(one) {
#include className(two) {
#include withparent(three) {
properties...
}
}
} o/p .one.three .two { properties..}
I'm trying to find a way to capture the resolved parent selector (&) in SASS (running 3.2.3), likely in a mixin.
Unfortunately (and I've found discussion of this on github) the following go-to solution doesn't work:
#mixin append-to-captured-parent($append) {
$selector: "&-#{$append}";
#{$selector} { #content; }
}
.foo {
#include append-to-captured-parent("bar") {
color: blue;
}
}
The desired output being:
.foo .foo-bar { color: blue; }
Are there any hacks or work-arounds that can result in the desired output? As I understand it, this is likely not possible, as the parent selector isn't resolved until the SASS parser has constructed the entire node tree, at which point it encounters &-bar { ... }, on which it dies:
Syntax error: Invalid CSS after "&": expected "{", was "-bar"
Can we trick SASS into resolving this pre-emptively?
Note: I'm more than open to SASS extensions (Ruby libs) that can accomplish this; unfortunately at this time I'm not Ruby-savvy enough to roll my own (I'm working on it though)
Well, I've come up with a work-around, sort of.
This approach merely isolates the intended functionality, and manages it independently of the actual hierarchy.
First, some quick utilities:
#function slice($list, $from, $into) {
$result: ();
#for $index from $from through $into {
$result: append($result, nth($list, $index));
}
#return $result;
}
#function implode($list, $separator) {
$result: nth($list, 1);
#for $index from 2 through length($list) {
$result: #{$result}#{$separator}#{nth($list, $index)};
}
#return $result;
}
Then the guts:
$-class: ();
#function get-class() {
$top: if(length($-class) - 2 < 1, 1, length($-class) - 2);
#return implode(slice($-class, $top, length($-class)), "-");
}
#mixin class($name) {
$-class: append($-class, $name);
.#{get-class()} {
#content;
}
$-class: slice($-class, 1, length($-class) - 1);
}
What happens here is, the "global" variable $-class, maintains a nesting stack with each invocation of the mixin class. It creates a CSS class declaration by imploding the top three names on the stack (this can be modified to the full stack if desired)
So replicating the question's example:
#include class (foo) {
color: red;
#include class (bar) {
color: blue;
}
}
Will produce:
.foo { color: red; }
.foo .foo-bar { color: blue; }
A less trivial example would be:
#include class(list) {
test: "list";
#include class(item) {
test: "item";
#include class(heading) {
test: "heading";
}
#include class(content) {
test: "content";
#include class(graphic) {
test: "graphic";
}
#include class(summary) {
test: "summary";
}
#include class(details) {
test: "details";
}
}
#include class(footing) {
test: "footing";
}
}
}
Producing:
.list { test: "list"; }
.list .list-item { test: "item"; }
.list .list-item .list-item-heading { test: "heading"; }
.list .list-item .list-item-content { test: "content"; }
.list .list-item .list-item-content .item-content-graphic { test: "graphic"; }
.list .list-item .list-item-content .item-content-summary { test: "summary"; }
.list .list-item .list-item-content .item-content-details { test: "details"; }
.list .list-item .list-item-footing { test: "footing"; }