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.
Related
I try to do any less function which will be called to create some classes.
Here is the way I tried :
.makeCssColor{#couleur) {
.coul_#{couleur} {
background-color: fade(~"#{couleur}, 'Fonce'", 15%);
&.open, &:hover {
background-color: ~"#{couleur}, 'Fonce'";
}
.btMod {
background : url('/img/btModEvt_#{couleur}.png') left top no-repeat transparent;
}
}
}
And I try to call it to create the classes :
.makeCssColor("bleu");
.makeCssColor("rouge");
But it generate an error. I don't find the good way to do it... And it bothers me to repeat all these code for each color (there is more than these line code and more thant two colors !).
Can anyone give me a little help ? :)
[edit]
ok, thanks to your help, this code does not generate an error, but there is a mistake in the CSS file :
#marronFonce = #9d5a1e;
.makeCssColor(#couleur) {
.coul_#{couleur} {
.top {
background-color: #couleur, 'Fonce';
}
.mod {
background : url('/img/btModEvt_#{couleur}.png') left top no-repeat transparent;
}
}
}
.makeCssColor(marron);
Generate this into the css file :
.coul_marron .top{background-color:marron,'Fonce'}
.coul_marron background : url('/img/btModEvt_marron.png') left top no-repeat transparent;
So the background color isn't good :
.coul_marron .top{background-color:#9d5a1e}
.coul_marron background : url('/img/btModEvt_marron.png') left top no-repeat transparent;
I need to evaluate #couleur, 'Fonce' : #marronFonce => #9d5a1e.
I tried #{#couleur, 'Fonce'} but it doesn't works...
Fade function takes a colour and a fade percentage, in your case you are passing 2 colours. Pass them one at a time. I also made some adjustments on #couleur since i some cases they don't need to be escaped
.makeCssColor{#couleur) {
.coul_#{couleur} {
background-color: fade(#couleur, 15%), fade(Fonce, 15%);
&.open, &:hover {
background-color: #couleur, 'Fonce';
}
.btMod {
background : url('/img/btModEvt_#couleur.png') left top no-repeat transparent;
}
}
}
when you call the mixin use the below, no need to use quotes
.makeCssColor(bleu);
UPDATE - just pass it in
.makeCssColor(#couleur, #name) {
.coul_#{name} {
.top {
background-color: #couleur;
}
.mod {
background : url('/img/btModEvt_#{name}.png') left top no-repeat transparent;
}
}
}
then when you call it
.makeCssColor(#marronFonce, marron);
OR
other option is you can make a loop, it's more complicated but you can try it. I am using an example I already have on my computer
first define a variable with the colour and names
#sample:
~"0070" '#ebebe7',
~"08x2" '#00247a',
~"01k0" '#92918e';
then loops thru it
.sample-loop ( #l ) when ( #l > 0 ) {
#item: extract( #sample #l );
#code: extract( #item, 1 );
#colour: color(extract( #item, 2 ));
.ext-#{code} {
background-color: #colour;
}
.sample-loop( #l - 1 );
}
and finally call the loop to generate your classes
.sample-loop( 3 );
depending on which version of less you have, the 3 can coded so it is dynamic. If you have older version of less then you have to hard code the length of the variable, or assign the length to a variable so you can use it anywhere
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
I am trying to realize a modular approach to text sizing using the following starting variables:
#font-size: 1.7rem;
#line-height: 1.414;
I would like to write a mixin that would create this result but have not quite fully grasped LESS yet:
h4 {
font-size: #font-size * #line-height;
}
h3 {
font-size: (#font-size * #line-height) * #line-height;
}
h2 {
font-size: ((#font-size * #line-height) * #line-height) * #line-height;
}
h1 {
font-size: (((#font-size * #line-height) * #line-height) * #line-height) * #line-height;
}
You have an array with your selectors - #elements.
Then you get the length of #elements - it will be used as iterator through an array.
Then the loop starts. It has the name set-font-size. In Less loops are recursive mixins. This mixin takes some parameters and calls itself until some condition is correct. In this example, it works while #_i > 0. Every time it calls itself, the value of #_i decreases.
Inside mixin (or loop) you get current element from array by calling extract(#array, #index) command. Then you set the font size to your selector. Evry time mixin call itself, the value of font-size is increased #_font-size * #_line-height.
That's all :)
Codepen DEMO.
Less:
#elements: h1, h2, h3, h4;
#iterations: length(#elements); // lehgth of #elements
#font-size: 1.7rem;
#line-height: 1.414;
.set-font-size(#_i, #_elements, #_font-size, #_line-height) when (#_i > 0) {
#selector: extract(#_elements, #_i);
#{selector} {
font-size: #_font-size;
}
.set-font-size(#_i - 1, #_elements, #_font-size * #_line-height, #_line-height);
}
.set-font-size(#iterations, #elements, #font-size, #line-height);
Css output:
h4 {
font-size: 1.7rem;
}
h3 {
font-size: 2.4038rem;
}
h2 {
font-size: 3.3989732rem;
}
h1 {
font-size: 4.8061481rem;
}
I realise this is a similar question to this Conditional CSS based on background color variable
but I need to do it inside a loop in LESS. If a background colour is too dark I want to generate another class so I can make the text on top lighter but not sure how as I don't think you can use lighten and darken functions with hex colours...?
Here is my LESS http://codepen.io/anon/pen/IlsJE?editors=110
.for(#i, #n) {.-each(#i)}
.for(#n) when (isnumber(#n)) {.for(1, #n)}
.for(#i, #n) when not (#i = #n) {
.for((#i + (#n - #i) / abs(#n - #i)), #n);}
// .for-each
.for(#array) when (default()) {.for-impl_(length(#array))}
.for-impl_(#i) when (#i > 1) {.for-impl_((#i - 1))}
.for-impl_(#i) {.-each(extract(#array, #i))}
// PAs
#pa1: "pa1";
#pa2: "pa2";
#pa3: "pa3";
#pa4: "pa4";
// Colors
#pa1-color: #72afb6;
#pa2-color: #9fad9f;
#pa3-color: #8dd8f8;
#pa4-color: #00567A;
// Setting variables and escaping them
#pas: ~"pa1" ~"pa2" ~"pa3" ~"pa4";
// Define our variable
.define(#var) {
#pa-color-primary: '#{var}-color';
}
// Starting the PA mixin
.pacolors() {
// Generating the loop for each PA
.for(#pas); .-each(#name) {
// After loop happens, it checks what brand is being called
.define(#name);
.#{name} .bg-accent {
background-color: ##pa-color-primary;
}
}
}
.pacolors();
Any help would be appreciated.
You can achieve this by using the built-in contrast function provided by LESS.
// Starting the PA mixin
.pacolors() {
// Generating the loop for each PA
.for(#pas); .-each(#name) {
// After loop happens, it checks what brand is being called
.define(#name);
.#{name} .bg-accent {
background-color: ##pa-color-primary;
color: contrast(##pa-color-primary,
lighten(##pa-color-primary, 100%),
darken(##pa-color-primary, 100%),10%);
/* syntax - contrast(color-for-comparison,
color1 - lighten (100%) is essentially white,
color 2 - darken (100%) is essentially black,
threshold percentage based on which decision is taken
*/
}
}
}
Demo | LESS Function Spec - Contrast
Simplified Version: (Courtesy - seven-phases-max)
// Colors
#pas:
pa1 #72afb6,
pa2 #9fad9f,
pa3 #8dd8f8,
pa4 #00567A;
// Styles
& {
.for(#pas); .-each(#pa) {
#name: extract(#pa, 1);
#color: extract(#pa, 2);
.#{name} .bg-accent {
background-color: #color;
color: contrast(#color, white, black, 10%);
}
}
}
p {padding: 10px}
// ........................................................
#import "https://raw.githubusercontent.com/seven-phases-max/less.curious/master/src/for";
Demo 2
I am trying to generate a number of classes in a loop based on a number of pre-defined variable snippets.
I have a variables.less document that I am importing at the top of this less file containing my color variables. I then want to generate matching classes for these, but I am unable to get less to compile the variable.
My code:
.loop-class(~"primary", ~"success", ~"info", ~"warning", ~"danger";);
.loop-class(#list, #index: 1) when (isstring(extract(#list, #index))) {
#status: extract(#list, #index);
.button-#{status} {
color: ~'#button-#{status}';
}
.loop-class(#list, (#index + 1));
}
Which compiles to:
.button-primary {
color: #button-primary;
}
.button-success {
color: #button-success;
}
etc etc
As you can see, I get the variable name to concatenate correctly, but I can not get it to resolve, so I'm guessing that LESS has already done it's variable compilation before getting to this function?
I have already tried moving the variables into this document, as well as wrapping the variables in a mixin and adding that inside the .loop-class, but neither of these seemed to help.
I also tried something like:
#status: extract(#list, #index);
#compileClass: ~'#button-#{status}';
.button-#{status} {
color: #compileClass;
}
where I am saving the variable in a another one and then referencing that, but it yields the same result.
I looked at less css calling dynamic variables from a loop and tried implementing that as follows:
.loop-class(~"primary", ~"success", ~"info", ~"warning", ~"danger";);
.define(#var) {
#fallback: ~'#button-#{var}';
}
.loop-class(#list, #index: 1) when (isstring(extract(#list, #index))) {
#status: extract(#list, #index);
.button-#{status} {
.define(#status);
color: ##fallback;
}
.loop-class(#list, (#index + 1));
}
But that gave me the error that ##button-danger (last in the index) is undefined, so it still can't resolve the variable.
Is it obvious to you guys what I'm doing wrong?
Thanks for your help!
Missing Brackets
You are missing a set of needed brackets to resolve the variable:
LESS
//imported from another file
#button-primary: cyan;
#button-success: green;
#button-info: orange;
#button-warning: yellow;
#button-danger: red;
//in your mixin file
.loop-class(~"primary", ~"success", ~"info", ~"warning", ~"danger";);
.loop-class(#list, #index: 1) when (isstring(extract(#list, #index))) {
#status: extract(#list, #index);
.button-#{status} {
color: ~'#{button-#{status}}'; /* two more brackets needed */
| |
here here
}
.loop-class(#list, (#index + 1));
}
CSS Output
.button-primary {
color: #00ffff;
}
.button-success {
color: #008000;
}
.button-info {
color: #ffa500;
}
.button-warning {
color: #ffff00;
}
.button-danger {
color: #ff0000;
}
Cleaner More Friendly Code
Also, as a matter of less cluttered and more user friendly code, you can remove your multiple string interpolations needed for the mixing call by changing isstring to iskeyword in your mixin:
.loop-class(primary, success, info, warning, danger;); /* cleaner code on call */
.loop-class(#list, #index: 1) when (iskeyword(extract(#list, #index))) {
#status: extract(#list, #index);
.button-#{status} {
color: ~'#{button-#{status}}';
}
.loop-class(#list, (#index + 1));
}