Sass: Function to map variable number of inputs - sass

Suppose I have the following in SCSS:
$browser-context: 16;
#function em($size, $context: $browser-context) {
#if (unitless($size)) { $size: $size * 1px; }
#if (unitless($context)) { $context: $context * 1px; }
#return ($size / $context) * 1em;
}
With this, I can write the following:
.test { padding: em(20px); }
Which will output the following CSS:
.test { padding: 1.25em; }
And everything is hunky dory. Is there any way that I can transform an unknown amount of inputs in one call? For example:
.text { padding: func(20px 30px); }
and
.text { padding: func(20px 30px 10px); }
would output
.text { padding: 1.25em 1.875em; }
and
.text { padding: 1.25em 1.875em 0.625em; }
respectively. I'm just hoping to avoid doing this:
.text { padding: em(20px) em(30px) em(10px); }

What you're looking for is a map function (commonly found in other languages to apply a function on each item in a list), but Sass does not provide one. However, you can write your own starting with Sass 3.3 by using the call() function as you loop over the entire list.
#function map($fun, $list) {
#for $i from 1 through length($list) {
$list: set-nth($list, $i, call($fun, nth($list, $i)));
}
#return $list;
}
Usage:
$bar: 10px 20px 30px;
#function times2($i) {
#return $i * 2;
}
.foo {
padding: map('times2', $bar); // note the quotes around the function name
}
Output:
.foo {
padding: 20px 40px 60px;
}

Related

CSS margin loop incrementing by X amount each time

I wonder if it is possible to do this with SCSS? Incrementing by X each time.
It is tricky because the -s or -md needs to be updated instead of the number.
#for $i from 1 through 6 {
.u-marg-t-#{$i} {
margin-top: 0px + ($i * 8);
}
}
Aside from this not even counting correctly, is a #for the best approach, here?
Plain CSS output
.u-marg-t {
&-xs {
margin-top: 8px;
}
&-sm {
margin-top: 16px;
}
&-md {
margin-top: 24px;
}
&-lg {
margin-top: 32px;
}
&-xl {
margin-top: 43px;
}
&-xxl {
margin-top: 50px;
}
}
.u-marg-b {
&-xs {
margin-bottom: 8px;
}
&-sm {
margin-bottom: 16px;
}
&-md {
margin-bottom: 24px;
}
&-lg {
margin-bottom: 32px;
}
&-xl {
margin-bottom: 43px;
}
&-xxl {
margin-bottom: 50px;
}
}
You could use a list with the suffixes you want to use and iterate through inside your for loop. Here's an example:
$sizes: xs, sm, md, lg, xl, xxl;
#for $i from 1 through 6 {
.u-marg-t-{
&#{nth($sizes, $i)} {
margin-top: 0px + ($i * 8);
}
}
}
$sizes is your list with the suffixes. Inside the loop I used the function nth($list, $i) to get the item at position $i in your list.

Increment headings and variables using a for loop that apply's font-size in scss

I am trying to loop through all 6 headings and apply font-size through a mixin from 6 font-size variables. But I keep getting an undefined variable. It is not recognizing the variable increment. Am I doing something wrong or is this simply not possible? Seems simple enough in my head anyway He is a link to sassmeister Thanks for any help or insight
// Variables
$font-h1: 40px;
$font-h2: 28px;
$font-h3: 24px;
$font-h4: 20px;
$font-h5: 18px;
$font-h6: 14px;
//Mixin
#mixin font-size($size) {
font-size: $size;
}
#for $i from 1 through 6 {
h#{$i} {
// font-size: #{$i};
#include font-size( $font-h#{$i} );
}
}
// Expected out
h1 {
font-size: 40px
}
etc...
// Actual Ouput
Undefined variable: "$font-h".
I would go with a map as it tends to be more flexible – e.g:
$font-size:(
h1 : 40px,
h2 : 28px,
h3 : 24px,
h4 : 20px,
h5 : 18px,
h6 : 14px
);
#each $header, $size in $font-size {
#{$header}{ font-size: $size; }
}
// Bonus
// If you need to apply a font-size to another
// element you can get the size using map-get
.class {
font-size: map-get($font-size, h3);
}
// Function and mixin to handle the above
#function font-size($key){
#return map-get($font-size, $key);
}
#mixin font-size($key){
font-size: font-size($key);
}
.class {
font-size: font-size(h3); // use it as function
#include font-size(h3); // use it as include
}
You can try refactor your var and use array or use map fn.
For example:
$font-h: 40px, 28px, 24px, 20px, 18px, 14px;
#mixin font-size($size) {
font-size: $size;
}
#for $i from 1 through length($font-h) {
$font: nth($font-h, $i);
h#{$i} {
#include font-size($font);
}
}

Make one variable double the value of the other with SASS

Im using SASS. I need on value to be double another value so the logic is this:
.one {
padding: 'value';
}
.two {
padding: 'value times 2';
}
So it could be this:
.one {
padding: 2px;
}
.two {
padding: 4px;
}
Or this:
.one {
padding: 10px;
}
.two {
padding: 20px;
}
How can I write this?
If you want a value you can reuse, you need a variable.
$my-padding: 1em;
.one {
padding: $my-padding;
}
.two {
padding: $my-padding * 2;
}

aggregation of selectors created by mixin possible?

I try to get a mixin "remebering" the selectors it was generating so I can make a bulk-selector at the end.
To illustrate what I am trying to do – My mixin looks like so:
#mixin fontcustom($name) {
#if $name == "heart" {
$glyph: '\2764'; // a special character in my own font -> ❤
}
#else if $name == "foo" { ... }
#else if $name == "bar" { ... }
#else if $name == "baz" { ... }
// ... much much more characters ...
&:before {
content:"#{$glyph}";
}
/* aggreagation of selectors ? */
}
#function selectorsUsingFontcustom() {
/* this should somehow result in a list of selectors, see above */
font-family: fontcustom;
color: red;
/* ... */
}
Obviously there are some more style declarations needed, for example font-family, colors and so on.
I want to avoid repetive declarations so my question is: is there a way to make the mixin "remember" the selectors which resulted in applying it and genarate a comma-separated list of them, which results in something like the following?
SCSS:
#my-fancy-selector [data-is-liked] {
#include fontcustom("heart");
}
.another>.fancy+.foo-selector {
#include fontcustom("foo");
}
.another>.fancy+.baz-selector {
#include fontcustom("baz");
}
/* no clue about the following: */
selectorsUsingFontcustom();
CSS:
#my-fancy-selector [data-is-liked]:before {
content:"\2764";
}
.another>.fancy+.foo-selector:before {
content:"\2765";
}
.another>.fancy+.baz-selector:before {
content:"\2767";
}
/* selectorsUsingFontcustom() should return sth like the following then: */
#my-fancy-selector [data-is-liked]:before,
.another>.fancy+.foo-selector:before,
.another>.fancy+.baz-selector:before {
font-family: fontcustom;
color: red;
/* ... */
}
Any ideas?
Use #extend with placeholder selectors like this:
%heart {
color: red;
}
h1 {
#extend %heart;
font-size: 3em;
}
h2 {
#extend %heart;
font-size: 2em;
}
li {
#extend %heart;
text-decoration: strikethrough;
}
Output:
h1, h2, li {
color: red;
}
h1 {
font-size: 3em;
}
h2 {
font-size: 2em;
}
li {
text-decoration: strikethrough;
}

Access the parent selector from within a SASS mixin

I have set up a mixin for a button using display:inline-block. I am trying to get to the parent of whatever class that will eventually end up using the mixim, so I can add the font-size: 0px line there to make sure that I don't need to make adjustments to my HTML to avoid unwanted space between each button.
Here's an example... I want the. parent class to receive the font-size: 0px line.
#mixin button() {
display:inline-block;
font-size: 1em;
//other stuff to make a pretty button
&& { font-size: 0px; }
}
.parent{
.child {
#include button();
}
}
As of Sass 3.4 this is now possible.
#mixin parent {
#each $selector in & {
$l: length($selector);
#if ($l == 1) {
#error "Used parent mixin on a top-level selector";
} #else {
$parent: nth($selector,1);
#for $i from 2 to $l {
$parent: append($parent,nth($selector,$i));
}
#at-root #{$parent} {
#content;
}
}
}
}
// Use
.grandparent {
.parent{
.child {
font-size: 1em;
#include parent {
font-size: 0px;
}
}
}
}
// Result
.grandparent .parent .child {
font-size: 1em;
}
.grandparent .parent {
font-size: 0px;
}
// Errors:
.root {
#include parent {
content: "Won't work";
}
}
.grandparent .parent, .root {
#include parent {
content: "Also won't work";
}
}
No, this is not possible. You could do something like this, though:
#mixin button($child: '.child') {
font-size: 0px;
//other stuff to make a pretty button
#{$child} {
display:inline-block;
font-size: 1em;
}
}
.parent{
#include button();
}
Output:
.parent {
font-size: 0px;
}
.parent .child {
display: inline-block;
font-size: 1em;
}
There is a XXX! selector in the draft for the CSS 4 spec, which will act as the way you like. It announces the subject of the CSS style declarations, if the selectors match
So if you have this selector
.a > .b! > .c
It will match e.g. for this
<div class="a">
<div class="b">
<div class="c">
</div>
</div>
</div>
but the style declarations will not take effect on .c, but on .b, because I announced by the exclamation mark, that this element should be the subject of the style
http://dev.w3.org/csswg/selectors4/#subject
You cannot use it right now out of the box. But there is one jQuery plugin, that is a polyfill for that. http://dev.w3.org/csswg/selectors4/
See also this stack: Is there a CSS parent selector?
How to apply?
Well, I don't know exactly in SASS, but in LESS it would be
*! > & {
/* ... */
}
While Karol's answer is near perfect, it doesn't take into account pseudo-elements or pseudo-selectors. Furthermore, code is duplicated if using more than one complex selector. I came up with a simplified version:
#mixin parent {
$parents: ();
$parent: '';
#each $selector in & {
$length: length($selector);
$index: 0;
$last-selector: nth($selector, $length);
#if ($length == 1) {
#error "Used parent mixin on a top-level selector";
} #else {
$index: str-index($last-selector, '::');
#if ($index) {
$last-selector: str-slice($last-selector, 1, $index - 1);
} #else {
$last-selector: null;
}
// Inspect allows us to combine two selectors in one block.
$parent: inspect(set-nth($selector, $length, #{$last-selector}));
$parents: join($parents, $parent, comma);
}
}
#at-root #{$parents} {
#content;
}
}
There's a first loop to iterate over the selector list (selectors with commas at the end). Because complex selectors are also treated as a list, we just need to remove the last element of the list. There's no loop to iterate over the compound or simple selectors since we only need to discard the last one.
There's no function in Sass to remove an element of a list, but we can set the value of an element with set-nth. By making the last element as an empty string and unquoting it, we can remove the last element from the printed representation (string) of the list. Since selectors can be strings, we simply use the new string as a selector.
When using the following:
.grandmother,
.grandfather {
.parent {
.child {
font-size: 10em;
#include parent {
font-size: 5em;
}
&::after {
font-size: 1px;
#include parent {
font-weight: bold;
}
}
}
}
}
We get the following:
.grandmother .parent .child,
.grandfather .parent .child {
font-size: 10em;
}
.grandmother .parent,
.grandfather .parent {
font-size: 5em;
}
.grandmother .parent .child::after,
.grandfather .parent .child::after {
font-size: 1px;
}
.grandmother .parent .child,
.grandfather .parent .child {
font-weight: bold;
}
Note: pseudo-elements and pseudo-selectors are not children of an element but are attached to it and have therefore no parents in themselves. I assumed parents would mean the parent in the sense of Sass nesting.

Resources