Passing arguments from a mixin to a content block - sass

This open issue in the Sass queue seems to imply passing arguments to #content is not a feature yet, but Susy 2 seems to be able to do this. Tracking down how it's done is a bit of a rabbit hole though and I haven't figured it out yet. Perhaps someone can shed some light with a straightforward example? I want to create a custom mixin that will inherit a layout passed from susy-breakpoint() using a custom map.
Example: Defining a 4 column layout in a global Sass map, will return a width of 100% when a span of 4 is specified inside susy-breakpoint()'s #content. When a custom layout of 8 cols is passed to directly tosusy-breakpoint() via the $layout argument, the nested span() mixin picks up the new layout. But a custom nested mixin will not pick up the new layout. Why?
#import 'susy';
$susy: (
columns: 4,
);
#mixin inherit-layout($layout: 4) {
columns: $layout;
}
#include susy-breakpoint(30em) {
// nested code uses an 4-column grid from global map
.global-cols {
#include span(4);
#include inherit-layout();
}
}
#include susy-breakpoint(48em, $layout: 8) {
// nested code uses an 8-column grid from $layout
.inherited-cols {
#include span(4);
#include inherit-layout();
}
}
Compiled CSS:
#media (min-width: 30em) {
.global-cols {
width: 100%;
float: left;
margin-left: 0;
margin-right: 0;
columns: 4;
}
}
#media (min-width: 48em) {
.inherited-cols {
width: 48.71795%;
float: left;
margin-right: 2.5641%;
columns: 4;
}
}
Update:
I've discovered that making the default variable for the inherit-value() mixin the value of columns key on the existing $susy map allows the mixin to grab context. But why? And why doesn't it work with a different map or outside of susy-breakpoint()?
See here: http://sassmeister.com/gist/d86e217aca3aa8337b83

Susy doesn't pass any arguments to the #content — instead, we change the global variable at the start of the content block, and then change it back at the end:
$example: 4;
#mixin local($local) {
$old: $example;
$example: $local !global;
#content
$example: $old !global;
}
// out here, $example == 4
#include local(12) {
// in here, $example == 12
}

Related

Sass manipulate with #content

Is it possible to manipulate with #content magic variable in SASS?
I would like to replace some stuff in here before output.
Or maybe can I fill some variable with it?
The conclusion is that, I want to make an mixin #important that create both versions. Important, and no-important.
Input
.test {
#include important {
color: red;
text-align: left;
}
}
Expected output
.test {
color: red;
text-align: left;
}
.test-i {
color: red !important;
text-align: left !important;
}
No, you can't. But I quickly wrote you a mixin to make it work. It doesn't accepts multiple properties (yet).
First Note: I changed the mixin it now does accept multiple properties. Here is the Codepen.
Second Note: I updated the mixin adding multiple properties does no longer compile to different classes for each property, instead you get two versions, one without the !important suffix and one with.
This is the mixin:
#function return($state) {
#return if($state == '', '', '-i');
}
#mixin loop($name, $items...) {
#each $item in $items / 2 {
#each $state in ('', '!important') {
$suffix: return($state);
.#{$name}#{$suffix} {
#for $i from 1 through (length($items) / 2) {
#{nth($items, ($i * 2) - 1)}: #{nth($items, ($i * 2))} #{$state};
}
}
}
}
}
This is how you include it:
// #include loop([classname], [property], [value]);
#include loop(whateverClassname, color, red);
This is what it compiles to:
.whateverClassname {
color: red ;
}
.whateverClassname-i {
color: red !important;
}
This is what it now compiles to, when you use multiple properties at once:
#include loop(whateverClassname, color, red, background-color, green, display, flex);
.whateverClassname {
color: red ;
background-color: green ;
display: flex ;
}
.whateverClassname-i {
color: red !important;
background-color: green !important;
display: flex !important;
}
Conclusion: it works as expected and does no longer bloat your CSS.
Hope I could help you at least a little ;-)

Passing multiple arguments in SASS Mixin to output set of classes or single class

$base-space: 1rem !default;
$space-map : (
'1o9': $base-space,
'1o8': $base-space/8,
'1o4': $base-space/4,
'1o2': $base-space/2,
) !default;
#mixin containers($new-space-map) {
#each $name, $value in $new-space-map {
.container--#{$name}{
margin: $value 0;
}
}
}
#include containers($space-map);
This outputs 4 CSS classes.
I am trying to generate all 4 classes or only one class based on the name I pass as argument.
my output needs to be:
.container--1o9 {
margin: 1rem 0;
}
.container--1o8 {
margin: 0.125rem 0;
}
.container--1o4 {
margin: 0.25rem 0;
}
.container--1o2 {
margin: 0.5rem 0;
}
Or
.container--1o2 {
margin: 0.5rem 0;
}
based on the argument I pass.
To explain further; I am trying trying to use #include on same SASS Map in both the ways mentioned below depending on my need:
#include containers(<SASS map file>); //This I achieved
or
#include containers(<any key from SASS Map>); //This I cant
I am stuck here and not sure if this is achievable or not. Any help will be very much appreciated.
Remove the !Default. This will work as you expect it to
Link to sassmeister
https://www.sassmeister.com/gist/2839eaf64bd441e73a92aed6bb980465
Remember all 4 classes are being compiled into your CSS file. Just use the one you want

Bourbon Neat grid-push not working as expected?

I have a really simple issue that seems to be difficult to solve. I want to stick with the normal standard 12 column neat grid, but I want the two .homeSplit divs to take up 5 columns each. I would like the second one(.insights) to get 1col of space in the middle so I gave it a grid-push(1). When it gets to mobile sizes, I want each of these divs to take up the full 12 columns and stack on top of each other. The issue is that once I get down to mobile size, that 1col space I assigned with grid-push(1) is persisting and I can't figure out how to get rid of it.
CSS/SASS:
.homeSplit {
position: relative;
#include grid-column(5);
&.news {
.post {
margin-bottom: 26px;
}
}
&.insights {
#include grid-push(1);
padding-left: 30px;
z-index: 999;
.post {
margin-bottom: 26px;
}
}
}
#media only screen and (max-width: 959px) {
.homeSplit {
#include grid-column(12);
&.insights {
#include grid-push(0);
}
}
}
I have tried grid-push(-1) as well but it goes too far. Am I misunderstanding how to use Neat? Pulling my hair out over here.
The best path here would be to use the grid-media() mixin to have a series of grids. Here is an example of what that would look like (with some of the extraneous code removed.
Also, neat by default favors min-width over max-width in media queries. based on your layout, min-width makes things a lot easier.
$my-desktop-grid: (
media: 959px,
);
.homeSplit {
#include grid-column(); // default's to 12 here
#include grid-media($my-desktop-grid) {
#include grid-column(5);
&.insights {
#include grid-push(1);
}
}
}
I've created a codepen as an example so you can see what this looks like in action.
https://codepen.io/whmii/pen/aVvqma
Option 1: the nesting is wrong.
In looking at the example above the #media declaration is nested within the .homeSplit block, but then .homeSplit is declared again w/in #media. However the code you have above would likely not run and would error out, so I'm going to assume there was something lost in translation when it was copped and pasted in to your question.
Option 2: grid-push wants false or just to be empty.
grid-push(0) isn't really a thing but in sass 0 may == false so you can try the following:
.homeSplit {
position: relative;
#include grid-column(5);
&.news {
.post {
margin-bottom: 26px;
}
}
&.insights {
#include grid-push(1);
padding-left: 30px;
z-index: 999;
.post {
margin-bottom: 26px;
}
}
#media only screen and (max-width: 959px) {
#include grid-column(12);
&.insights {
#include grid-push(); // 'false' is the default here
}
}
}
Note: I also cleaned up some of the nesting at the bottom
Im going to add a second answer that shows how to do this using the grid-media mixin.

Passing a variable from inside a mixin declaration into the attached content block?

In Ruby, you can easily pass a variable from inside a method into the attached code block:
def mymethod
(1..10).each { |e| yield(e * 10) } # Passes a number to associated block
end
mymethod { |i| puts "Here comes #{i}" } # Outputs the number received from the method
I would like to do the same thing in SASS mixin:
=my-mixin
#for $i from 1 to 8
.grid-#{$i}
#content
+my-mixin
color: nth("red green blue orange yellow brown black purple", $i)
This code won't work because $i is declared inside the mixin declaration and cannot be seen outside, where the mixin is used. :(
So... How do i leverage variables declared inside the mixin declaration?
When i work with a grid framework and media queries, i need this functionality badly. Currently i have to duplicate what's inside the mixin declaration every time i need it, violating the DRY rule.
UPD 2013-01-24
Here's a real-life example.
I have a mixin that cycles through breakpoints and applies the provided code once for every breakpoint:
=apply-to-each-bp
#each $bp in $bp-list
+at-breakpoint($bp) // This is from Susy gem
#content
When i use this mixin i have to use this $bp value inside #content. It could be like this:
// Applies to all direct children of container
.container > *
display: inline-block
// Applies to all direct children of container,
// if container does not have the .with-gutters class
.container:not(.with-gutters) > *
+apply-to-each-bp
width: 100% / $bp
// Applies to all direct children of container,
// if container has the .with-gutters class
.container.with-gutters > *
+apply-to-each-bp
$block-to-margin-ratio: 0.2
$width: 100% / ($bp * (1 + $block-to-margin-ratio) - $block-to-margin-ratio)
width: $width
margin-right: $width * $block-to-margin-ratio
&:nth-child(#{$bp})
margin-right: 0
But this won't work, because the value of $bp is not available inside #content.
Declaring the variable before calling the mixin won't help, because #content is parsed once and before the mixin is parsed.
Instead, EACH time i need that, i have to do two ugly thighs:
declare an ad-hoc mixin,
write the cycle, violating the DRY principle:
// Each of the following mixins is mentioned in the code only once.
=without-gutters($bp)
width: 100% / $bp
=with-gutters($bp)
$block-to-margin-ratio: 0.2
$width: 100% / ($bp * (1 + $block-to-margin-ratio) - $block-to-margin-ratio)
width: $width
margin-right: $width * $block-to-margin-ratio
&:nth-child(#{$bp})
margin-right: 0
// Applies to all direct children of container
.container > *
display: inline-block
// Applies to all direct children of container,
// if container does not have the .with-gutters class
.container:not(.with-gutters) > *
#each $bp in $bp-list
+at-breakpoint($bp) // This is from Susy gem
+without-gutters($bp)
// Applies to all direct children of container,
// if container has the .with-gutters class
.container.with-gutters > *
#each $bp in $bp-list // Duplicate code! :(
+at-breakpoint($bp) // Violates the DRY principle.
+with-gutters($bp)
So, the question is: is there a way to do this Ruby-style?
Variables in Sass have scope to them. They're only visible in the block they were created in. If you want the variable to be accessible both inside and outside of the mixin, it has to be defined in the global scope:
$var: 0;
#mixin test {
$var: $var + 1;
color: red;
}
.test {
$var: 5;
#include test;
#debug $var; // DEBUG: 6
}
As long as you don't care about the state of $var for very long, this should work out ok for your purposes.
For your example, this won't work because it looks like the #content is processed first. What you need is a mixin that's written differently:
#mixin test($properties...) {
#for $i from 1 to 8 {
.grid-#{$i} {
#each $p in $properties {
$list: nth($p, 2);
#if length($list) > 1 {
#{nth($p, 1)}: nth($list, $i);
} #else {
#{nth($p, 1)}: $list;
}
}
#content;
}
}
}
.test {
#include test(color (red green blue orange yellow brown black purple));
}
The generated CSS:
.test .grid-1 {
color: red;
}
.test .grid-2 {
color: green;
}
.test .grid-3 {
color: blue;
}
.test .grid-4 {
color: orange;
}
.test .grid-5 {
color: yellow;
}
.test .grid-6 {
color: brown;
}
.test .grid-7 {
color: black;
}
A mixin like this can be fed any number of arguments and still allows you to use #content if you wish.
I have run into this problem myself and AFAIK this is a current limitation in SASS.
So this is currently unavailable in Sass.
There's a relevant ticket in the Sass issue queue: https://github.com/nex3/sass/issues/871 It's in the planned state but will probably not make it until at least Sass 4.0.

Passing a list to a mixin in SASS as parameters?

I'm trying to make a SCSS stylesheet easily configurable by defining a set of constants that will be used in a number of mixins and with the Compass library. Ideally, I'd like to be able to do the following:
$item-bgs: linear-gradient(white, black), #ccc;
#mixin some-mixin() {
#include background-with-css2-fallback($item-bgs*);
}
The background-with-css2-fallback is a Compass mixin that accepts up to 10 params. I'm assuming that SASS does not currently support passing a list parameter as the argument list, otherwise Compass would probably use it, but I'm wondering if I can get the $item-bgs list to be the first 2 arguments to the background-with-css2-fallback mixin. Is there a way to do this currently, or is it even planned for SASS in the future?
It may not be supported by SASS natively, but Compass does support passing a list as the first argument to the background-with-css2-fallback mixin. If you look at the source for the mixin, you'll see that it uses a compact function that handles the logic for collapsing the arguments into a single list, whether passed individually or in a single list parameter.
For example, this works fine for me:
#import "compass";
$item-bgs: (linear-gradient(white, black), #ccc);
.test {
#include background-with-css2-fallback($item-bgs);
}
Examples of useing maps as arguments:
Example 1 (list)
#mixin transition($property...){
#if $property {
transition-property: $property;
}
#else {
transition-property: all;
}
transition-timing-function: ease-in-out;
transition-duration: .3s;
transition-delay: 0;
}
.btn {
color: black;
border: 1px solid black;
#include transition(color, border-color);
&:hover {
color: red;
border-color: red;
}
}
Example 2 (Custom params)
#use 'sass:meta';
#mixin example2($args...) {
#each $key, $value in meta.keywords($args) {
#{$key}: #{$value};
}
}
.shape {
#include example2($width:200px, $height:100px);
}
Example 3 (map)
#mixin colors($args:()) {
#if length($colors) > 0 {
#each $key, $val in $args{
.txt-#{$key} {
color: #{$value};
}
.bg-#{$key} {
background-color: #{$value};
}
}
}
}
$colors_map: (
primary: blue,
secondary: green,
accent: red,
light: white,
dark: black
);
#include colors($colors_map);

Resources