I am currently converting code from SASS to LESS.
I have a problem with the following line of code:
&[data-rating = "#{counter - 0.5}"] { // THIS DOES NOT WORK
How can I work with variables and subtract 0.5 from my counter var and have it in a pair of quotes so that it can sit inside the HTML data attribute.
I have included two code examples so you can take the code and run it to see my results.
SASS:
.reviews-stars {
display: inline-block;
#for $i from 1 through 5 {
&[data-rating = "#{$i}"] {
.star:nth-child(-n + #{$i}):before {
color: green;
}
}
&[data-rating = "#{$i - 0.5}"] {
.star:nth-child(-n + #{$i}):before {
color: red;
}
}
}
}
LESS:
.looper-review-stars(#counter) when (#counter > 0) {
.looper-review-stars((#counter - 1)); // next iteration
&[data-rating = "#{counter}"] { // THIS WORKS
.star:nth-child(-n + #{counter}):before { // THIS WORKS
color: green;
}
}
// THIS DOES NOT WORK IT RETURNS THE STRING "#{counter - 0.5}"
&[data-rating = "#{counter - 0.5}"] { // THIS DOES NOT WORK
.star:nth-child(-n + #{counter}):before { // THIS WORKS
color: red;
}
}
}
.reviews-stars {
display: inline-block;
.looper-review-stars(5); // launch the loop
}
You can do it using a temporary variable like in the below snippet. Since selectors are strings I think it is better to keep all math operations away from it and in a separate statement.
.looper-review-stars(#counter) when (#counter > 0) {
.looper-review-stars((#counter - 1)); // next iteration
&[data-rating = "#{counter}"] {
.star:nth-child(-n + #{counter}):before {
color: green;
}
}
#temp: #counter - .5; /* temporary variable */
&[data-rating = "#{temp}"] {
.star:nth-child(-n + #{counter}):before {
color: red;
}
}
}
.reviews-stars {
display: inline-block;
.looper-review-stars(5); // launch the loop
}
Related
We have a CSS style that adds dots before and after a heading indicating the nesting level of the heading:
.heading.nesting-1:before,.heading.nesting-1:after{content:"\00B7"}
.heading.nesting-2:before,.heading.nesting-2:after{content:"\00B7\00B7"}
.heading.nesting-3:before,.heading.nesting-3:after{content:"\00B7\00B7\00B7"}
...
I have tried the following LESS but it doesn't concatenate the strings correctly
#entity-dot: '\00B7';
#nesting-levels: 9;
.heading {
.nesting-loop (#i) when (#i <= #nesting-levels) {
&.nesting-#{i} {
&:before, &:after {
.nested-dots(#j, #current: "") when (#j > 0) {
#nested-dots: "#{current}#{entity-dot}";
.nested-dots((#j - 1), #nested-dots);
}
.nested-dots(#j: #i, "");
content: "#{nested-dots}";
}
}
.nesting-loop(#i + 1);
}
.nesting-loop (1);
}
The result is:
.heading.nesting-1:before,.heading.nesting-1:after{content:"\00B7"}
.heading.nesting-2:before,.heading.nesting-2:after{content:"\00B7"}
.heading.nesting-3:before,.heading.nesting-3:after{content:"\00B7"}
...
I think I understand that the problem is that the property #nested-dots cannot be iteratively updated in this way but I was wondering if anybody had a solution?
Thanks for your help.
This is how I resolved it with the help of this post
(thank you Martin Turjak)
#nesting-levels: 5;
#entity-space: '\00A0';
#entity-dot: '\00B7';
.content-dots (#numdots) {
//Mixin output
.nested-dots(1, #dots1) {
#dots: "#{dots1}#{entity-dot}";
}
//Mixin iterator
.nested-dots(#index, #dots1) when (#index > 1) {
#dots2: "#{dots1}#{entity-dot}#{entity-space}";
.nested-dots((#index - 1), #dots2);
}
//Call mixin
.nested-dots(#numdots, "");
//Output dots
content: "#{dots}";
}
.heading {
.nesting-loop (#i) when (#i >= 1) {
&.nesting-#{i} {
&:before, &:after {
.content-dots(#i);
}
}
.nesting-loop (#i - 1);
}
.nesting-loop (#nesting-levels);
}
The result is
.heading.nesting-5:before,.heading.nesting-5:after{content:"\00B7\00A0\00B7\00A0\00B7\00A0\00B7\00A0\00B7"}
.heading.nesting-4:before,.heading.nesting-4:after{content:"\00B7\00A0\00B7\00A0\00B7\00A0\00B7"}
.heading.nesting-3:before,.heading.nesting-3:after{content:"\00B7\00A0\00B7\00A0\00B7"}
.heading.nesting-2:before,.heading.nesting-2:after{content:"\00B7\00A0\00B7"}
.heading.nesting-1:before,.heading.nesting-1:after{content:"\00B7"}
The idea is to add a 'default' value to the list and if one exists, output a class. If the 'default' doesn't exists, the compiler fails. How can this be achieved?
I tried a few variations of Less Guards and couldn't figure out a way to solve the problem.
I also attempted to add a 'default' list for these situations but they aren't merging.
Current Less I'm testing:
// Map / List
#borderRadius: {
//default: .25rem;
none: 0;
small: .3rem;
medium: .5rem;
large: 1rem;
round: 9999px;
}
// Extract
.getKeyFromList(#list, #key, #fallback...) {
.-(length(#list));
.-(#i) when (#i > 0) {.-((#i - 1))}
.-(#i) when (#key = extract(extract(#list, #i), 1)) {
#value: extract(#list, #i);
}
.--() {#value: #fallback} .--;
}
// Generate the CSS
& when not (#borderRadius) {
.getKeyFromList(#borderRadius[default], #borderRadius[default]);
&radius {
border-radius: #value;
}
}
EDIT:
Here is my real-world example.
I'm trying to extract the 'responsive' key from each ##component to generate responsive modifier classes dynamically
for individual components/variants. I was able to get the :hover and :focus variants to work, but can't figure out a
good way to do it for media queries (responsive modifier classes)
#responsiveModifiers: true;
#breakpointOffset: 1;
#screens: {
small: 768px;
medium: 960px;
large: 1440px;
};
#opacity: {
0: 0;
25: .25;
50: .5;
75: .75;
100: 1;
}
#borderRadius: {
none: 0;
small: .3rem;
medium: .5rem;
large: 1rem;
round: 9999px;
}
#components: {
#opacity: {
enabled: false;
responsive: false;
hover: true;
focus: false;
}
#borderRadius: {
enabled: true;
responsive: false;
hover: true;
focus: false;
}
}
.config() {
// Create generators here
& when (#components[#opacity][enabled]) {
#component: opacity;
.generate(opacity, opacity, #opacity);
}
& when (#components[#borderRadius][enabled]) {
#component: opacity;
.generate(radius, border-radius, #borderRadius);
}
}
// Call the mixin for default, non media-query classes
// and prefix it with a period (.)
.groundwork-utility-classes() {
#period: .;
#{period} {
.config();
}
}
// Dynamically generate CSS responsive modifiers, per breakpoint,
// via the #screens list array
.groundwork-media-queries() when (#responsiveModifiers = true) {
each(#screens, {
#media screen and (min-width: (#value + #breakpointOffset)) {
.#{key}&\: {
.config();
}
}
})
}
// Main mixin to generate all CSS utility classes
.generate(#className; #property; #list) {
// Generate CSS classes from a #list array
// e.g `.generate(p, padding, #padding);`
each(#list, {
& when not (#key > 0) {
&#{className} {
#{property}: #value;
}
&hover\:#{className}:hover when (#components[##component][hover] = true) {
#{property}: #value;
}
&focus\:#{className}:focus when (#components[##component][focus] = true) {
#{property}: #value;
}
}
});
}
// Render non media-query classes together
.groundwork-utility-classes();
// Group and render media queries together,
.groundwork-media-queries();
This question already has answers here:
Conditionally setting one variable's value based on another
(3 answers)
Closed 5 years ago.
I am trying to create a mixin that takes several parameters and based on those returns a value. However, I tried testing my less file on http://fiddlesalad.com/less/, but nothing happens.
#volume-color: white;
.volume-color(#media, #volume) {
& when (#media = "novel") {
& when (#volume > 2) {
#volume-color: green;
}
& when (#volume = 1) {
#volume-color: red;
}
}
}
.test {
.volume-color("novel", 1);
color: #volume-color;
}
What am I doing wrong?
Try this:
.volume-color(#media, #volume) {
& when (#media = "novel") {
& when (#volume > 2) {
color: green;
}
& when (#volume = 1) {
color: red;
}
}
}
.test {
.volume-color("novel", 1);
}
.test2 {
.volume-color("novel", 3);
}
Compiled to:
.test {
color: red;
}
.test2 {
color: green;
}
//call the mixin
.mixin-loop(grey, 7);
//the implementation
.mixin-loop(#str, #count) {
.loop (#i) when (#i > 0) {
.#{str}-#{i} {
div { background: "#{#{str}-#{i}}"; }
}
.loop(#i - 1);
}
.loop (#count);
}
//globals.less
#grey-1: #ccc;
#grey-2: #999;
The output I want is this:
//output
.grey-1 div {
background: #ccc;
}
.grey-2 div {
background: #999;
}
But what I'm getting is this:
.#808080-1 div {
background: "#{#808080-1}";
}
.#808080-2 div {
background: "#{#808080-2}";
}
You can use variable interpolation (~) to help with this:
http://lesscss.org/features/#variables-feature-variable-interpolation
This will prevent grey from being converted into it's hex value, and then will allow "#{#{str}-#{i}}" to be displayed as a hex value instead of a string.
//call the mixin
.mixin-loop(~"grey", 2);
//the implementation
.mixin-loop(#str, #count) {
.loop (#i) when (#i > 0) {
.#{str}-#{i} {
div { background: ~"#{#{str}-#{i}}"; }
}
.loop(#i - 1);
}
.loop (#count);
}
//globals.less
#grey-1: #ccc;
#grey-2: #999;
I'm creating less grid sistem and i dont know how to optimise grid.less
If i chouse #gridColumns: 14; i need to add new lines in grid.less, maybe there is anothes option to automate this?
Variables.less
#gridColumns: 12;
#gridWidth: 62.5em;
#gridGutterWidth: 1.8em;
#grid.less
.l-1 { .grid(1); }
.l-2 { .grid(2); }
.l-3 { .grid(3); }
.l-4 { .grid(4); }
.l-5 { .grid(5); }
.l-6 { .grid(6); }
.l-7 { .grid(7); }
.l-8 { .grid(8); }
.l-9 { .grid(9); }
.l-10 { .grid(10); }
.l-11 { .grid(11); }
.l-12 { .grid(12); }
#mixins.less
.grid(#num) {
width: (100% / #gridColumns) * #num;
position: relative;}
The only sensible way of doing it using LESS is using a recursive mixin like Twitter Bootstrap.
.spanX (#index) when (#index > 0) {
.span#{index} { .span(#index); }
.spanX(#index - 1);
}
.spanX (0) {}
.span (#columns) {
width: (#gridColumnWidth * #columns) + (#gridGutterWidth * (#columns - 1));
}