Pass variables from one mixin to another when called inside - variables

I'm working on a Sass framework to practice my skills, expand my knowledge, and have something I built myself to use in my future projects. The current phase of the framework is a simple modular scale for typography. I'm trying to keep it DRY, but also flexible, so I've created a separate mixin for font-size and line-height. However, these mixins use the same variables based on a map-deep-get function. So I've created a third mixin called font-vars to hold all these variables and call in the font-size and line-height mixins.
They're all based on breakpoints in the maps so using them as global variables wouldn't make sense. When defining the variables in the font-size and line-height mixins, everything works as expected, but when held in a separate mixin, they are not being passed to ones font-vars is being called in.
=font-vars($element, $size: null)
$element-exponent: map-deep-get($typography, font-sizing, $element)
$base-fs: map-deep-get($base, sizes, $size, font-size)
$base-lh: map-deep-get($base, sizes, $size, line-height)
$scale: map-deep-get($base, sizes, $size, scale)
$fs: pow($scale, $element-exponent) * 1em
$lh: $base-fs * $base-lh / $fs
=font-size($element, $size: null)
+font-vars($element, $size)
#if map-deep-get($base, sizes, type-breakpoint) != false
font-size: $fs
=line-height($element, $size: null)
+font-vars($element, $size)
#while $lh < 1
$lh: $lh + $lh
$lh: $lh * 1em
#if map-deep-get($base, sizes, type-breakpoint) != false
line-height: $lh
p
+font-size(p)
+line-height(p)
TL;DR: I want the variables held in font-vars to pass to font-size and line-height when the mixin is called inside them, but it only works when I define them in each mixin.

You can use a #function that return a map of the variables instead of a mixin. For example:
#function get-colors()
#return (red: #ff0000, blue: #0000ff)
=colors
$colors: get-colors()
color: map-get($colors, red)
p
+colors
Will return:
p { color: #ff0000; }
So in your case, your function will be:
#function get-font-vars($element, $size: null)
#return (
element-exponent: map-deep-get($typography, font-sizing, $element),
base-fs: map-deep-get($base, sizes, $size, font-size),
base-lh: map-deep-get($base, sizes, $size, line-height),
scale: map-deep-get($base, sizes, $size, scale),
fs: pow($scale, $element-exponent) * 1em,
lh: $base-fs * $base-lh / $fs
)
Which you can then call with:
$font-vars: get-font-vars($element, $size)

Related

How to iterate through a key value paired list, with Guards in LESS

I'm working with LESS version 1.6.0 (not by choice). I read through the LESS documentation on Mixin Guards. I created a LESS function to cycle through a key-value list of colors, to generate CSS classes. Within this function, I want to set a condition to change the properties of my styles, if my prefixes are different. (i.e. txt and bg should have the same CSS declaration, while border has a different one). I believe my code below "is" doing that, but its also compounding my CSS properties for each class. Here is what I tried...
/*MY COLORS*/
#black-n25: #bfbfbf;
#black-n75: #4d4d4d;
#black: #000;
#white: #fff;
/*MY LIST (KEY VALUE PAIRS)*/
#colors: ~'black-n25' #black-n25, ~'black-n75' #black-n75, ~'black' #black, ~'white' #white;
/*MY FUNCTION TO CYCLE THROUGH MY LIST AND GENERATE CLASSES*/
.generate-classes(#list, #prefix, #mprop) {
.iter(length(#list));
.iter(#i) when (#i > 0) {
.iter(#i - 1);
#pair: extract(#list, #i); /*#colors -> #list -> #pair*/
#key: extract(#pair, 1); /*the key from #colors above*/
#value: extract(#pair, 2); /*the value from #colors above*/
/*this is where things go wrong...*/
.mixin(#prefix) when (#prefix = txt), (#prefix = bg) {
#{mprop}: #value;
}
.mixin(#prefix) when (#prefix = border) {
#{mprop}: solid 2rem #value;
}
.#{prefix}-#{key} {
.mixin(#prefix);
}
}
}
/*CALL FUNCTION FOR EACH CASE | PARAMETERS ARE...THE LIST, PREFIX (CSS SELECTOR NAME), AND CSS PROPERTY VALUE*/
.generate-classes(#colors,txt,color);
.generate-classes(#colors,bg,background-color);
.generate-classes(#colors,border,border);
...and a visual of what's being generated in the browser...
As you can see its compounding declarations for each class except the first class. Each CSS class should only have one declaration. What am I doing wrong?
This took some further digging. but now I can see, as of LESS version 1.5.0, CSS Guards can help when iterating through a key-value paired list items in LESS. I removed my use of Mixin Guards from my original logic and instead have the following setup, which now works.
This now gives me the flexibility to add many more colors and custom CSS declarations for custom prefixes, all within a single function.
/*MY COLORS*/
#black-n25: #bfbfbf;
#black-n75: #4d4d4d;
#black: #000;
#white: #fff;
/*MY LIST (KEY VALUE PAIRS)*/
#colors: ~'black-n25' #black-n25, ~'black-n75' #black-n75, ~'black' #black, ~'white' #white;
/*MY FUNCTION TO CYCLE THROUGH MY LIST AND GENERATE CLASSES*/
.generate-classes(#list, #prefix, #mprop) {
.iter(length(#list));
.iter(#i) when (#i > 0) {
.iter(#i - 1);
#pair: extract(#list, #i); /*#colors -> #list -> #pair*/
#key: extract(#pair, 1); /*the key from #colors above*/
#value: extract(#pair, 2); /*the value from #colors above*/
/*this now works and sets a single declaration for each class...*/
.#{prefix}-#{key} when (#prefix = txt), (#prefix = bg) {
#{mprop}: #value;
}
.#{prefix}-#{key} when (#prefix = border) {
#{mprop}: solid 2rem #value;
}
}
}
/*CALL FUNCTION FOR EACH CASE | PARAMETERS ARE...THE LIST, PREFIX (CSS SELECTOR NAME), AND CSS PROPERTY VALUE*/
.generate-classes(#colors,txt,color);
.generate-classes(#colors,bg,background-color);
.generate-classes(#colors,border,border);

SASS / SCSS: Interpolate variable from string / name

Is it possible to get a variable by name?
I tried building the following function, but it's not working as expected...
#function variable-lookup($variable, $suffix: "") {
$value: null;
#if ($suffix != "" and global-variable-exists($variable+"-"+$suffix)) {
$value: #{$variable+"-"+$suffix};
}
#else if (global-variable-exists($variable)) {
$value: #{$variable};
}
#return $value;
}
Here's an example of how it might be used:
$primary: #000;
$primary-hover: blue;
a {
color: variable-lookup("primary", "base");
&:hover {
color: variable-lookup("primary", "hover");
}
}
The real power would come in when I want to write a bunch of context-specific, shorthand wrapper functions around this "variable-lookup" function.
Any ideas how to achieve this?
Trying to interpolate #{$variable+"-"+$suffix} to give the value primary-base and further trying to get the value of same variable name is not possible. primary-base is already a value and and can't be interpreted as a variable name. That sort of thing could lead to a lot of chaos.
For what you want to accomplish, you are better of using a map and checking for the key in that map
$colours: (
'primary': red,
'primary-base': blue
);
#function variable_lookup($colour, $suffix: '') {
$value: null;
#if ( $suffix != '' and map-has-key($colours, unquote($colour+'-'+$suffix)) ) {
$value: map-get($colours, unquote($colour+'-'+$suffix));
} #else if ( map-has-key($colours, unquote($colour)) ) {
$value: map-get($colours, unquote($colour));
}
#return $value;
}
div {
color: variable-lookup(primary, base);
}
p {
color: variable-lookup(primary);
}
This compiles to the following css
div {
color: blue; }
p {
color: red; }
Your code stored colours as variables but I used those names as keys in maps
This allowed to simulate the checking of variables in your code using the map-has-key method. If that returns true, the key exists and we can get the value which in this case would be the colour using map-get
UPDATED ANSWER
One way to address the issues you raised in your comments would be to define the variables and use them in as values in the map
$primary: #fff;
$warning: yellow;
$colours: ( primary: $primary,
primary-hover: darken($primary, 5%),
secondary: $warning,
secondary-hover: darken($warning, 5%) );
Another way would be to iterate through two lists and map colour to a style
$colours: ();
$list: primary success warning; //map primary to blue, success to green and so on
$shades: blue green yellow;
#for $i from 1 through length($list) {
$key: nth($list, $i);
$value: nth($shades, $i);
$colours: map-merge($colours, ($key: $value));
$colours: map-merge($colours, (unquote($key+'-hover'): darken($value, 5% )) );
}
#debug $colours // (primary: blue, primary-hover: #0000e6, success: green, success-hover: #006700, warning: yellow, warning-hover: #e6e600)
The variable_lookup function remains the same.
Hope this is able to help

Bootstrap convert spacing mixin from sass to less

I have a site running Bootstrap 3.3.7. I use less to adjust the styling. In version 4 of Bootstrap sass is introduced instead of less, and I noticed a new mixin which adds the ability to easily use predefined paddings and margins:
// Width
.w-100 { width: 100% !important; }
// Margin and Padding
.m-x-auto {
margin-right: auto !important;
margin-left: auto !important;
}
#each $prop, $abbrev in (margin: m, padding: p) {
#each $size, $lengths in $spacers {
$length-x: map-get($lengths, x);
$length-y: map-get($lengths, y);
.#{$abbrev}-a-#{$size} { #{$prop}: $length-y $length-x !important; } // a = All sides
.#{$abbrev}-t-#{$size} { #{$prop}-top: $length-y !important; }
.#{$abbrev}-r-#{$size} { #{$prop}-right: $length-x !important; }
.#{$abbrev}-b-#{$size} { #{$prop}-bottom: $length-y !important; }
.#{$abbrev}-l-#{$size} { #{$prop}-left: $length-x !important; }
// Axes
.#{$abbrev}-x-#{$size} {
#{$prop}-right: $length-x !important;
#{$prop}-left: $length-x !important;
}
.#{$abbrev}-y-#{$size} {
#{$prop}-top: $length-y !important;
#{$prop}-bottom: $length-y !important;
}
}
}
// Positioning
.pos-f-t {
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: $zindex-navbar-fixed;
}
Source at GitHub
I would like to convert this mixin to less, and use it in my own Bootstrap 3.3.7 project. How would this mixin look like in less?
Less does not have any #each function or map like Sass does but even then converting this Sass code into its Less equivalent is fairly easy. All that is needed are a couple of loops each of which will mimic the two #each function in Sass and associative arrays.
In Less, we can use both comma and space as delimiters for values. So by using both of them we can achieve a behavior similar to that of maps. Even multi-level maps can be mimicked using this.
(Note: You need to know the basics of Less loops to understand this code but since you've already used Less, I assume that you are familiar with the concepts. If not, have a look at docs)
#props: margin m, padding p; /* the property and abbreviation */
#spacers: xs 10px 20px, md 20px 30px; /* the sizes, its length-x and length-y */
.loop-props(#prop-index) when (#prop-index > 0){ /* outer each loop */
#prop: extract(#props, #prop-index); /* get each prop-abbrev pair based on loop index */
#prop-name: extract(#prop, 1); /* the first value in each pair is the prop name */
#abbrev: extract(#prop, 2); /* the second value in each pair is the prop's abbrev */
/* call size loop mixin with each property name + abbreviation */
.loop-sizes(#prop-name; #abbrev; length(#spacers));
.loop-props(#prop-index - 1); /* call the next iteration of the outer each loop */
}
.loop-props(length(#props)) !important; /* initial mixin/loop call */
.loop-sizes(#prop-name; #abbrev; #size-index) when (#size-index > 0){ /* inner each */
#spacer: extract(#spacers, #size-index); /* extract each spacer value based on index */
#size: extract(#spacer, 1); /* first value in each spacer is the size */
#x: extract(#spacer, 2); /* second value is the length in X axis */
#y: extract(#spacer, 3); /* third value is the length in Y axis */
/* create the selectors and properties using interpolation */
.#{abbrev}-a-#{size} {
#{prop-name}: #y #x;
}
.#{abbrev}-t-#{size} {
#{prop-name}-top: #y;
}
.#{abbrev}-r-#{size} {
#{prop-name}-right: #x;
}
.#{abbrev}-b-#{size} {
#{prop-name}-bottom: #y;
}
.#{abbrev}-l-#{size} {
#{prop-name}-left: #x;
}
.#{abbrev}-x-#{size} {
#{prop-name}-right: #x;
#{prop-name}-left: #x;
}
.#{abbrev}-y-#{size} {
#{prop-name}-top: #y;
#{prop-name}-bottom: #y;
}
.loop-sizes(#prop-name; #abbrev; #size-index - 1); /* call next iteration */
}
As you'd have noticed, I have attached the !important to the mixin call itself instead of attaching it each property. When this is done, the Less compiler automatically attaches the !important to every property and so we needn't repeat it.

Define LESS variable from string

I'm trying to write a mixin that returns a variable for converting px to em. I've toyed with a few things, but ultimately i'd like to call a mixin and get a return value similar to SASS's functions. Based on on return values here: http://www.lesscss.org/#-return-values, I can only define a variable once as a return value. Example:
Mixin
.px-to-emz( #size, #base: #font-size-base ){
#em: round( unit( (#size / #base), ~"em" ), 3 );
}
Call it:
.foo {
font-size: #em;
.px-to-emz(10, 16px);
height: #em;
.px-to-emz(200, 16px);
}
Fine, if you want to only return one variable, but if i want to return multiple variables I need to define new variable names. Here's what i'd ideally like to do
Mixin:
.px-to-ems( #size, #var: 'em', #base: #font-size-base ){
~'#{var}': round( unit( (#size / #base), ~"em" ), 3 );
}
Call it:
.foo {
font-size: #font-size;
.px-to-ems(10, 'font-size', 16px);
height: #height;
.px-to-ems(200, 'height', 16px);
}
#1
So far the best known solution for this problem is to put each mixin call into its own scope:
.px-to-ems(#size, #base: #font-size-base) {
#-: round(unit((#size / #base), em), 3);
}
.foo {
.-() {font-size: #-; .px-to-ems( 10, 16px)}
.-() {height: #-; .px-to-ems(200, 16px)}
.-;
}
Replace #- and .- with whatever identifiers you find suitable.
#2
The other way around is to use recently added (Less 1.6.x) property interpolation feature:
.px-to-ems(#property, #size, #base: #font-size-base) {
#{property}: round(unit((#size / #base), em), 3);
}
.foo {
.px-to-ems(font-size, 10, 16px);
.px-to-ems(height, 200, 16px);
}
It's more clean than #1 if you simply need to assign the "function" result to a property.

assign variables with LESS css using mixins

Is it possible to assign a variable based on another variable using less css?
pseudo code:
#userpick = red; /* this changes based on user pick */
#color1 = red;
#color2 = blue;
if( #userpick == #color1)
#systempick = #color2
else( #userpick == #color2)
#systempick = #color1
I want to be able to use #systempick as a color value not as a css style. For example:
border: 5px dashed #systempick;
Doesn't use a mixin, but resolves your desire
If the #userpick is stored as a string instead, then that could be used to access another variable name based off of it (a variable variable), like so:
LESS
#userpick: "blue"; /* this changes based on user pick, saved as a string */
#setopposite: "#{userpick}-opp";
//set opposing values
#red-opp: blue;
#blue-opp: red;
#systempick: ##setopposite;
.test {
color: #systempick;
}
CSS Output (note how blue outputs #ff0000, i.e. red)
/* this changes based on user pick */
.test {
color: #ff0000;
}
Since you are dealing with colors, the #userpick needs to be saved as a string because otherwise, LESS will automatically assign it a hex value rather than the color name.
You can use guards in mixins to change the color that is used, e.g.
.a when (#userpick = #color1) {
color: #color2;
}
.a when (#userpick = #color2) {
color: #color1;
}
You might get code duplication but its the only way to get an if statement.
Other ways are possible (e.g. inline javascript if using less.js or a function plugin if using dotless) but are probably a little hacky.
I was able to get what I needed, however this may not work on the PHP port of Less.
This was a bit tricky since you can assign variables only once. There are also some restrictions regarding scope of variables. I'm also comparing color values using HSV (hue, saturation, value)
.set_colors(#F00, #00F, #F00); /* pass red, blue and user pick */
.set_colors(#c1, #c2, #u) when (hue(#c1) = hue(#u)) and (saturation(#c1) = saturation(#u)) and (lightness(#c1) = lightness(#u)){
#color1 = #c1;
#color2 = #c2;
#userpick = #c1;
#systempick = #c2;
}
.set_colors(#c1, #c2, #u) when (hue(#c2) = hue(#u)) and (saturation(#c2) = saturation(#u)) and (lightness(#c2) = lightness(#u)){
#color1 = #c1;
#color2 = #c2;
#userpick = #c2;
#systempick = #c1;
}