Less CSS responsive modifier - less

I'm trying to create a utility-first collection of CSS classes, similar to Tailwind but in Less. A big part of this is using responsive modifiers, using this className syntax: .large\:text-white.
The code below works great, except for one thing: the \: shouldn't be rendered on the default classes (the classes outside of the media queries. They should render using a period, as expected .foo.
I can't figure out how to solve this.
#screens: {
small: 320px;
medium: 768px;
large: 1024px;
}
#padding: {
0: 0;
10: 1rem;
20: 20rem;
30: 30rem;
}
#colors: {
white: #fff;
silver: hsla(0, 0%, 90%, 1);
}
#responsive-modifiers: true;
#config () {
.generate(pt, padding, #padding);
.generate(py, padding-top, #padding);
.generate(text, color, #colors);
.generate(background, background-color, #colors);
}
// Call the mixin
#config();
each(#screens, {
#media (min-width : #value) {
.#{key} when (#responsive-modifiers = true) {
#config();
}
}
})
.generate(#prefix, #property, #list) {
each(#list, {
&\:#{prefix}-#{key} {
#{property}: #value;
}
});
}

I solved it by adding a period/dot variable. Here's the final code. Works well:
#screens: {
small: 320px;
medium: 768px;
large: 1024px;
}
#padding: {
0: 0;
10: 1rem;
20: 20rem;
30: 30rem;
}
#colors: {
white: #fff;
silver: hsla(0, 0%, 90%, 1);
}
#responsive-modifiers: true;
#config () {
.generate(pt, padding, #padding);
.generate(py, padding-top, #padding);
.generate(text, color, #colors);
.generate(background, background-color, #colors);
}
#period: .;
#{period} {
#config();
}
each(#screens, {
#media (min-width : #value) {
.#{key}&\: when (#responsive-modifiers = true) {
#config();
}
}
})
.generate(#prefix, #property, #list) {
each(#list, {
&#{prefix}-#{key} {
#{property}: #value;
}
});
}

Related

LESS - How to use & when, if conditions in less css [duplicate]

I'm looking for some kind of if-statement to control the background-color of different div elements.
I have tried the below, but it doesn't compile
#debug: true;
header {
background-color: (yellow) when (#debug = true);
#title {
background-color: (orange) when (#debug = true);
}
}
article {
background-color: (red) when (#debug = true);
}
There is a way to use guards for individual (or multiple) attributes.
#debug: true;
header {
/* guard for attribute */
& when (#debug = true) {
background-color: yellow;
}
/* guard for nested class */
#title when (#debug = true) {
background-color: orange;
}
}
/* guard for class */
article when (#debug = true) {
background-color: red;
}
/* and when debug is off: */
article when not (#debug = true) {
background-color: green;
}
...and with Less 1.7; compiles to:
header {
background-color: yellow;
}
header #title {
background-color: orange;
}
article {
background-color: red;
}
LESS has guard expressions for mixins, not individual attributes.
So you'd create a mixin like this:
.debug(#debug) when (#debug = true) {
header {
background-color: yellow;
#title {
background-color: orange;
}
}
article {
background-color: red;
}
}
And turn it on or off by calling .debug(true); or .debug(false) (or not calling it at all).
I stumbled over the same question and I've found a solution.
First make sure you upgrade to LESS 1.6 at least.
You can use npm for that case.
Now you can use the following mixin:
.if (#condition, #property, #value) when (#condition = true){
#{property}: #value;
}
Since LESS 1.6 you are able to pass PropertyNames to Mixins as well. So for example you could just use:
.myHeadline {
.if(#include-lineHeight, line-height, '35px');
}
If #include-lineheight resolves to true LESS will print the line-height: 35px and it will skip the mixin if #include-lineheight is not true.
I wrote a mixin for some syntactic sugar ;)
Maybe someone likes this way of writing if-then-else better than using guards
depends on Less 1.7.0
https://github.com/pixelass/more-or-less/blob/master/less/fn/_if.less
Usage:
.if(isnumber(2), {
.-then(){
log {
isnumber: true;
}
}
.-else(){
log {
isnumber: false;
}
}
});
.if(lightness(#fff) gt (20% * 2), {
.-then(){
log {
is-light: true;
}
}
});
using on example from above
.if(#debug, {
.-then(){
header {
background-color: yellow;
#title {
background-color: orange;
}
}
article {
background-color: red;
}
}
});

LESS: Combine multiple CSS3 animation

I have 2 CSS animations defined in LESS:
.color_animation (#color, #time:1s)
{
#stop-1:~"#{color}_SOPRA";
#stop-2:~"#{color}_SOTTO";
#name: ~"blink-#{color}";
animation:#name #time ease-in-out infinite alternate;
.steps()
{
0% { color:##stop-1; }
50% { color:##stop-2; }
100% { color:##stop-1; }
}
#keyframes #name { .steps(); }
}
.zoom_animation (#ratio, #time:1s)
{
#zoom-ratio:round(#ratio*100);
#name: ~"zoom-#{zoom-ratio}";
animation:#name #time ease-in-out infinite alternate;
.steps()
{
0% { .scale(1.0); }
50% { .scale(#ratio); }
100% { .scale(1.0); }
}
#keyframes #name { .steps(); }
}
each one is called by a different CSS class:
.blink
{
.color_animation(red);
}
.animated-zoom
{
.zoom_animation(1.05);
}
I would like to be able to execute one of them or both in the same time, adding one or both css classes to a DOM element, for example:
<p class='blink'>Loading...</p>
<p class='animated-zoom'>Highlight</p>
<p class='blink animated-zoom'>Data not Saved!!!</p>
But in last case, second animation overrides the first one.
How to combine them in the special case in which both classes are added?
Thanks to #seven-phases-max's suggestion, I elaborated a possible solution, pipelining animations in case more than one CSS class is assigned to an element.
Here my original (now slightly modified) code, in which I move common parts in dedicated COMMONS mixins:
.color_animation (#color, #time:1s)
{
#stop-1:~"#{color}_SOPRA";
#stop-2:~"#{color}_SOTTO";
#name: ~"blink-#{color}";
.steps()
{
0% { color:##stop-1; }
100% { color:##stop-2; }
}
#value:#name #time ease-in-out infinite alternate;
.INITIALIZE_keyframes();
.CALL_animation();
}
.zoom_animation (#ratio, #time:1s)
{
#zoom-ratio:round(#ratio*100);
#name: ~"zoom-#{zoom-ratio}";
.steps()
{
0% { .scale(1.0); }
100% { .scale(#ratio); }
}
#value:#name #time ease-in-out infinite alternate;
.INITIALIZE_keyframes();
.CALL_animation();
}
Here the COMMONS mixins declaration (I'm missing browsers browsers prefix for simplicity, but they could be added here):
.INITIALIZE_keyframes()
{
#keyframes #name { .steps(); }
}
.CALL_animation()
{
animation+:#value;
}
Please, note the '+' sign in animation declaration, it's the secret of my solution, coupled with the following CSS classes declaration:
.blink
{
.color_animation(red);
}
.animated-zoom
{
.zoom_animation(1.05);
}
.blink
{
&.animated-zoom
{
.color_animation(red);
.animated-zoom;
}
}
The answer that was posted was great and helped me find my own solution to the problem. I use a generic keyframe and animation mixin to create my animations like this:
// Generic keyframe class
.keyframes(#name; #arguments) {
#-moz-keyframes #name { #arguments(); }
#-webkit-keyframes #name { #arguments(); }
#-ms-keyframes #name { #arguments(); }
#-o-keyframes #name { #arguments(); }
#keyframes #name { #arguments(); }
}
// Generic animation class
.animation(#arguments) {
-webkit-animation+: #arguments;
-moz-animation+: #arguments;
-ms-animation+: #arguments;
-o-animation+: #arguments;
animation+: #arguments;
}
Note the + in the .animation class. Then I can build animations quickly like so:
// Fade in animation
.fadeIn() {
.keyframes(fade-in; {
from { opacity: 0; }
to { opacity: 1; }
});
.animation(fade-in 1.5s);
}
// Pan animation
.pan() {
.keyframes(pan; {
from { transform: translateX(-2%); }
to { transform: translateX(2%); }
});
.animation(pan 5s infinite alternate ease-in-out);
}
Now if I want to use animations together, I can simply add them to an existing class, element, etc.:
.multipleAnimations {
.fadeIn();
.pan();
}

Less adds unexpected space to variable

For some reason, the output of nth child is rendered with un unexpected space. Can anyone help?
Renders:
// Part of render
body.domain-bsci-fta-local #block-domain-switcher ul li:nth-child( 3) {
background-color: #e14313;
}
From code:
// Variables
#a-primary: #018f9e;
#b-primary: #2b6a7c;
#c-primary: #e14313;
#d-primary: #009966;
#domain-a: 'a-local';
#domain-b: 'b-fta-local';
#domain-c: 'c-fta-local';
#domain-d: 'd-fta-local';
#domains: #domain-a #a-primary 1, #domain-b #b-primary 2, #domain-c #c-primary 3, #domain-d #d-primary 4;
// Call
body {
.generate-menus();
}
// Functions
.generate-menus() {
.for(#domains);
.-each(#domain) {
#dn: e(extract(#domain, 1));
#dc: extract(#domain, 2);
#dr: extract(#domain,3);
.generate-menu(#dn, #dc, #dr);
}
}
.generate-menu(#domainname, #domaincolor, #domaincount) {
&.domain-#{domainname} {
#block-domain-switcher {
ul {
li {
&:nth-child(#{domaincount}) {
background-color: #domaincolor;
a {
border-bottom: 5px solid;
color: white !important;
}
}
}
}
}
.navigation .submenu {
background-color: #domaincolor;
}
}
}
// ............................................................
// .for
.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) when (#i > 0) {
.-each(extract(#array, #i))
}
Note: As mentioned by seven-phases-max in his comments to the question, this was a bug which has already been fixed in v2.x. Leaving this answer (with the work-around solution) as-is to help future readers who can't upgrade their compiler for whatever reason.
The problem happens only for selectors which use selector interpolation and are nested within one or more parent selectors. It can be solved by using a temporary variable which contains the pseudo-selector like below: (it uses escaped string feature)
Option 1:
ul {
li {
#selector: ~":nth-child(#{domaincount})"; /* the selector is formed here */
&#{selector} { /* and used here */
background-color: #domaincolor;
a {
border-bottom: 5px solid;
color: white !important;
}
}
}
}
Option 2:
li {
#count: ~"(#{domaincount})";
&:nth-child#{count} { /* and used here */
background-color: #domaincolor;
a {
border-bottom: 5px solid;
color: white !important;
}
}
}
Sample Compiled Output:
body.domain-a-local #block-domain-switcher ul li:nth-child(1) {
background-color: #018f9e;
}
Related Links:
concatenate values in less (css) without a space
Redudant space in interpolated selectors like nth(...)
As mentioned above and in the linked issue thread, the issue happens only when the selector is formed using selector interpolation and is nested under one or more parents.
This works
// Variables
#list: a 1;
#num: extract(#list, 2);
// Usage
body div:nth-child(#{num}) {
color: #444;
}
But this doesnt
// Variables
#list: a 1;
#num: extract(#list, 2);
// Usage
body {
div:nth-child(#{num}) {
color: #444;
}
}

How to use if statements in LESS

I'm looking for some kind of if-statement to control the background-color of different div elements.
I have tried the below, but it doesn't compile
#debug: true;
header {
background-color: (yellow) when (#debug = true);
#title {
background-color: (orange) when (#debug = true);
}
}
article {
background-color: (red) when (#debug = true);
}
There is a way to use guards for individual (or multiple) attributes.
#debug: true;
header {
/* guard for attribute */
& when (#debug = true) {
background-color: yellow;
}
/* guard for nested class */
#title when (#debug = true) {
background-color: orange;
}
}
/* guard for class */
article when (#debug = true) {
background-color: red;
}
/* and when debug is off: */
article when not (#debug = true) {
background-color: green;
}
...and with Less 1.7; compiles to:
header {
background-color: yellow;
}
header #title {
background-color: orange;
}
article {
background-color: red;
}
LESS has guard expressions for mixins, not individual attributes.
So you'd create a mixin like this:
.debug(#debug) when (#debug = true) {
header {
background-color: yellow;
#title {
background-color: orange;
}
}
article {
background-color: red;
}
}
And turn it on or off by calling .debug(true); or .debug(false) (or not calling it at all).
I stumbled over the same question and I've found a solution.
First make sure you upgrade to LESS 1.6 at least.
You can use npm for that case.
Now you can use the following mixin:
.if (#condition, #property, #value) when (#condition = true){
#{property}: #value;
}
Since LESS 1.6 you are able to pass PropertyNames to Mixins as well. So for example you could just use:
.myHeadline {
.if(#include-lineHeight, line-height, '35px');
}
If #include-lineheight resolves to true LESS will print the line-height: 35px and it will skip the mixin if #include-lineheight is not true.
I wrote a mixin for some syntactic sugar ;)
Maybe someone likes this way of writing if-then-else better than using guards
depends on Less 1.7.0
https://github.com/pixelass/more-or-less/blob/master/less/fn/_if.less
Usage:
.if(isnumber(2), {
.-then(){
log {
isnumber: true;
}
}
.-else(){
log {
isnumber: false;
}
}
});
.if(lightness(#fff) gt (20% * 2), {
.-then(){
log {
is-light: true;
}
}
});
using on example from above
.if(#debug, {
.-then(){
header {
background-color: yellow;
#title {
background-color: orange;
}
}
article {
background-color: red;
}
}
});

LESS Preprocessing and null arguments?

This might be difficult to explain. Is there a way to have less not write out the #child argument without overloading the mix-in? I really don't want two mix-ins. If I use "" double quotes are outputted. I would like the LESS compiler to leave it blank.
LESS CODE
.build-on(size, #child)
{
&--1-1 #{child}
{
width: 100%;
}
&--1-2 #{child}
{
width: 50.0%;
}
&--1-3 #{child}
{
width: 33.3%;
}
&--1-4 #{child}
{
width: 25.0%;
}
&--1-5 #{child}
{
width: 20.0%;
}
}
// I might need to provide a child element
.data-table
{
.build-on(size, table);
}
// I might not
.grid
{
.build-on(size, "");
}
Pass it like so:
.yourClass
{
.build-on(size, ~'');
}
Or Better Yet...
Define a default: .build-on(size, #child: ~'') { ... } then no second is needed:
.yourClass
{
.build-on(size);
}