Removing quotes from selector names when generating styles with Less mixin - css

I'm working on a legacy project that uses an old version of Less, and moving off it isn't an option anytime soon. I'm refactoring some repetitive theming and I've been trying to use Less mixins to help, but I'm a little stuck, and Less isn't something I've used to any great extent.
I've defined the following themes and mixin:
#theme-green: #91ab7a;
#theme-green-text: #fff;
#theme-yellow: #efdf91;
#theme-yellow-text: #00395d;
#theme-red: #722d3d;
#theme-red-text: #fff;
#theme-brown: #a9a196;
#theme-brown-text: #fff;
#theme-grey: #f3f3f3;
#theme-grey-text: #00395d;
#theme-darkBlue: #00395d;
#theme-darkBlue-text: #fff;
#theme-default: #theme-darkBlue;
#theme-default-text: #theme-darkBlue-text;
#theme-navyBlue: #007db5;
#theme-navyBlue-text: #fff;
#theme-cyan: #00adee;
#theme-cyan-text: #fff;
#theme-lightBlue: #b1e7ff;
#theme-lightBlue-text: #00395d;
#themes: 'green' 'yellow' 'red' 'brown' 'grey' 'darkBlue' 'navyBlue' 'cyan' 'lightBlue' 'default';
#themes-light: 'grey' 'lightBlue';
#themes-dark: 'green' 'yellow' 'red' 'brown' 'darkBlue' 'navyBlue' 'cyan' 'default';
#theme-count: length(#themes);
.createThemes(#theme-count);
.createThemes(#n, #i: 1) when (#i =< #n) {
#theme : extract(#themes,#i) ;
.dashboard-#{theme} {
background-color: ~'#{theme-#{theme}}' ;
color: ~'#{theme-#{theme}-text}';
}
.createThemes(#n, (#i + 1));
}
And here's the output:
.dashboard-'green' {
background-color: #91ab7a;
color: #fff;
}
.dashboard-'yellow' {
background-color: #efdf91;
color: #00395d;
}
.dashboard-'red' {
background-color: #722d3d;
color: #fff;
}
.dashboard-'brown' {
background-color: #a9a196;
color: #fff;
}
.dashboard-'grey' {
background-color: #f3f3f3;
color: #00395d;
}
.dashboard-'darkBlue' {
background-color: #00395d;
color: #fff;
}
.dashboard-'navyBlue' {
background-color: #007db5;
color: #fff;
}
.dashboard-'cyan' {
background-color: #00adee;
color: #fff;
}
.dashboard-'lightBlue' {
background-color: #b1e7ff;
color: #00395d;
}
.dashboard-'default' {
background-color: #00395d;
color: #fff;
}
This is very nearly what I want, but for the fact that the theme name is quoted in the generated selector, eg instead of dashboard-'navyBlue' I should see dashboard-navyBlue. How can I adjust the mixin to remove these quotes?

After a little bit of tinkering, I was able to find the following working solution:
#theme-green: #91ab7a;
#theme-green-text: #fff;
#theme-yellow: #efdf91;
#theme-yellow-text: #00395d;
#theme-red: #722d3d;
#theme-red-text: #fff;
#theme-brown: #a9a196;
#theme-brown-text: #fff;
#theme-grey: #f3f3f3;
#theme-grey-text: #00395d;
#theme-darkBlue: #00395d;
#theme-darkBlue-text: #fff;
#theme-default: #theme-darkBlue;
#theme-default-text: #theme-darkBlue-text;
#theme-navyBlue: #007db5;
#theme-navyBlue-text: #fff;
#theme-cyan: #00adee;
#theme-cyan-text: #fff;
#theme-lightBlue: #b1e7ff;
#theme-lightBlue-text: #00395d;
#themes: 'green' 'yellow' 'red' 'brown' 'grey' 'darkBlue' 'navyBlue' 'cyan' 'lightBlue' 'default';
#themes-light: 'grey' 'lightBlue';
#themes-dark: 'green' 'yellow' 'red' 'brown' 'darkBlue' 'navyBlue' 'cyan' 'default';
#theme-count: length(#themes);
.createThemes(#theme-count);
.createThemes(#n, #i: 1) when (#i =< #n) {
#theme : extract(#themes,#i) ;
#selector: ~'dashboard-#{theme}';
#{selector} {
background-color: ~'#{theme-#{theme}}' ;
color: ~'#{theme-#{theme}-text}';
}
.createThemes(#n, (#i + 1));
}

Related

Sass #mixin - prevent selector repetition and combine all styles under single selector

I have written this #mixin to set styles for light and dark theme in one line.
#mixin setThemeDynamicCSSRule($property, $light-theme-value, $dark-theme-value) {
#{$property}: #{$light-theme-value};
.dark-mode & {
#{$property}: #{$dark-theme-value}
}
}
body {
#include setThemeDynamicCSSRule(color, black, white);
#include setThemeDynamicCSSRule(background-color, white, black);
#include setThemeDynamicCSSRule(font-size, 16px, 32px);
}
This returns me:
body {
color: black;
background-color: white;
font-size: 16px;
}
.dark-mode body {
color: white;
}
.dark-mode body {
background-color: black;
}
.dark-mode body {
font-size: 32px;
}
it keeps repeating .dark-mode body selector for each individual setThemeDynamicCSSRule() call.
It still does the job but ideally I would like css to compile to:
body {
color: black;
background-color: white;
font-size: 16px;
}
.dark-mode body {
color: white;
background-color: black;
font-size: 32px;
}
Not exactly the way your are looking for ... but thinking forward it is the wanted result. And mayby an easier and more neat way to organize your special theme properties in one place.
Wrap your different theme property/values at to a nested map. Than use an adapted mixin.
$themes: (
color: (
'light': 'black',
'dark': 'white',
),
background-color: (
'light': 'white',
'dark': 'black',
),
font-size: (
'light': 16px,
'dark': 32px,
),
);
#mixin setThemeStyles($themes){
#each $property, $values in $themes {
#{$property}: map-get($values, 'light' );
}
.dark-mode & {
#each $property, $values in $themes{
#{$property}: map-get($values, 'dark');
}
}
}
body {
#include setThemeStyles($themes);
}
//######>> RESULT IS YOUR WANTED CSS
body {
color: "black";
background-color: "white";
font-size: 16px;
}
.dark-mode body {
color: "white";
background-color: "black";
font-size: 32px;
}

SASS/SCSS - Making a selector that will "back out" to modify the parent of the selector

I would like to contain all relevant styles for a selector within a single code block, so that it can be easily referenced.
In my application, a selectors effective styles will be altered dramatically depending on the context in which it sits. For instance, let's assume this CSS:
.container.theme-dark .message
{
font-size: 16px;
background-color: black;
color: white;
}
.container.theme-light .message
{
font-size: 16px;
background-color: white;
color: black;
}
Then, imagine I have the following HTML:
<div>
<div class="container theme-dark">
<div class="message">Hello World</div>
</div>
<div class="container theme-light">
<div class="message">Hello World</div>
</div>
</div>
Right now with SCSS, I would create the relevant CSS like this:
.container
{
&.theme-dark
{
.message
{
background-color: black;
color: white;
}
}
&.theme-light
{
.message
{
background-color: white;
color: black;
}
}
.message
{
font-size: 16px;
}
}
I want to be able to generate that CSS using SCSS, with all of the relevant information for the .message element in one place. For instance (using a made-up $ operator that would do what I'm trying to accomplish):
.container
{
.message
{
font-size: 16px;
$.theme-light
{
background-color: white;
color: black;
}
$.theme-dark
{
background-color: black;
color: white;
}
}
}
Any ideas?
I'm thinking this might work, and is like what you're saying? (It would help me currently if you labeled each example as "Ideal CSS output", "Current SCSS, too many .message blocks", and "Ideal SCSS format")
.container
{
#at-root .message
{
font-size: 16px;
.theme-light &
{
background-color: white;
color: black;
}
.theme-dark &
{
background-color: black;
color: white;
}
}
}
With the #at-root there, it will generate .theme-light .message, which might be too permissive for some usages, so not the ideal solution...
https://codepen.io/anon/pen/ZMxjEq
Basically & gets replaced with the full tree-path, so .container .message, which without #at-root, will generate .theme-light .container .message, which does not work with the structure. Perhaps also consider this, which makes a reasonable compromise I would say:
.container
{
.message
{
font-size: 16px;
}
#at-root .message
{
.theme-dark
{
...
}
.theme-light
{
...
}
}
}
It's apparently a kind of hacky solution, but apparently works
This page might have some better guidance as well
This organization can be achieved if you use sass programatically:
$themes: light dark;
.container {
#for $i from 1 through length($themes) {
&.theme-#{nth($themes,$i)} {
.message {
font-size: 16px;
#if nth($themes,$i) == light {
background-color: white;
color: black;
} #else if nth($themes,$i) == dark {
background-color: black;
color: white;
}
}
}
}
}
This generates:
.container.theme-light .message {
font-size: 16px;
background-color: white;
color: black;
}
.container.theme-dark .message {
font-size: 16px;
background-color: black;
color: white;
}
The nested looping automatically groups the details at each level in the same block of code. This also scales to multiple levels of nesting. The critical point is that at inner loops you can reference the selectors of outer loops.
I eventually found this GitHub:
https://github.com/imkremen/sass-parent-append/blob/master/parrent-append.scss
Which I have adapted into this solution:
#function str-to-list($string, $separator: ' ', $startAt: 1)
{
$workStr: str-slice($string, $startAt);
$list: ();
$indexOfFirstSpace: str-index($workStr, $separator);
#if ($indexOfFirstSpace == null)
{
$list: ($workStr);
}
#else
{
$list: (str-slice($workStr, 1, $indexOfFirstSpace - 1));
$list: join($list, str-to-list($workStr, $startAt: $indexOfFirstSpace + 1));
}
#return $list;
}
#function getBase($appendix)
{
$parentSelector: str-to-list(#{&});
$pos: (length($parentSelector) - 1);
$selector: set-nth($parentSelector, $pos, nth($parentSelector, $pos) + $appendix);
#return $selector;
}
#mixin base($appendix)
{
#at-root #{getBase($appendix)}
{
#content;
}
}
Which I can then use like this:
.container
{
.message
{
font-size: 16px;
}
#include base(".theme-light")
{
background-color: white;
color: black;
}
#include base(".theme-dark")
{
background-color: black;
color: white;
}
}
which compiles into this:
.container .message
{
font-size: 16px;
}
.container.theme-light .message
{
background-color: white;
color: black;
}
.container.theme-dark .message
{
background-color: black;
color: white;
}

Extract value from variable

I just want to ask if it is possible in LESSCSS to use the extract function while the value is stored in LESS Variable?
This is what I'm using:
#basic: #ffffff, #000000, #333333;
#classes: white, black, gray;
.make-color(#i: length(#basic)) when (#i > 0) {
.make-color(#i - 1);
#color: extract(#basic, #i);
#class: extract(#classes, #i);
.#{class} {
color: #color;
}
}
}
This is what I want
#white: #ffffff;
#black: #000000;
#gray: #333333;
#basic: #white, #black, #gray;
#classes: white, black, gray;
.make-color(#i: length(#basic)) when (#i > 0) {
.make-color(#i - 1);
#color: extract(#basic, #i);
#class: extract(#classes, #i);
.#{class} {
color: #color;
}
}
}
When I tried the second one it returns a compiled css like this:
.white{
color: #white;
}
.black{
color: #black;
}
.gray{
color: #gray;
}
but my target is like this:
.white{
color: #ffffff;
}
.black{
color: #000000;
}
.gray{
color: #333333;
}

gulp-sass new line for multi selector

I want to build
.btn-default {
color: #ccc;
&:hover,
&:focus {
color: #ddd;
}
}
into
.btn-default {
color: #ccc;
}
.btn-default:hover,
.btn-default:focus {
color: #ddd;
}
But instead, it compares into
.btn-default {
color: #ccc;
}
.btn-default:hover, .btn-default:focus {
color: #ddd;
}
On the other hand, it works fine without &, which build
a,
.btn {
color: #ccc;
}
into
a,
.btn {
color: #ccc;
}
So, how should I build & into multi-lines?
I use gulp-sass. Option is available here.
Sass only provide 4 compile output style:
(https://github.com/sass/node-sass#outputstyle)
:nested
:expanded
:compact
:compressed
There's no way to get the result you want, but you can use something like : https://github.com/csscomb/csscomb.js to format it.

How do I apply the same styling to all immediate children classes?

In less I have the following:
.some-class{
> li{
a{
color: white;
background: #fti-lightgrey;
border-radius: 0px;
padding: 1px 15px;
// a color for the partcular tab that is chosen. (the color for each tab can be set inside mura)
&.orange{
&:hover{ background: #fti-orange; }
&:hover{ color: white; }
}
&.black {
&:hover{ background: black; }
&:hover{ color: white; }
}
&.topaz{
&:hover{ background: #fti-topaz; }
&:hover{ color: white; }
}
}
}
}
How do I avoid writing &:hover{ color: white; } multiple times?
Is there a way to apply this line to all of the immediate class descendants somewhere inside the a tag?
It depends on the desired result.
Do you want:
1) White hover color by default, regardless of whether it also has the one of the .orange, .black, or .topaz classes?
.some-class{
> li{
a{
color: white;
background: #fti-lightgrey;
border-radius: 0px;
padding: 1px 15px;
// a color for the partcular tab that is chosen. (the color for each tab can be set inside mura)
&.orange{
&:hover{ background: #fti-orange; }
}
&.black {
&:hover{ background: black; }
}
&.topaz{
&:hover{ background: #fti-topaz; }
}
}
a:hover{ color: white; }
}
}
2) Or do you only want it to be white on hover if it also has one of .orange, .black, .topaz classes?
.some-class{
> li{
a{
color: white;
background: #fti-lightgrey;
border-radius: 0px;
padding: 1px 15px;
// a color for the partcular tab that is chosen. (the color for each tab can be set inside mura)
&.orange{
&:hover{ background: #fti-orange; }
}
&.black {
&:hover{ background: black; }
}
&.topaz{
&:hover{ background: #fti-topaz; }
}
}
a:hover {
&.orange, &.black, &.topaz{
color: white;
}
}
}
}
You could do
a:hover {
&.orange,
&.black,
&.topaz { color: white; }
}
then define the background individually. This is assuming the hover for your anchor is different colour than white by default and you want the coloured classes to be white(not in a human race way!).
or use the same style as you have
a {
&.orange, &.black, &.topaz {
&:hover { color: white; }
}
}
if you have a common class for the colours then you could always target that common class
In this case I would recommend to simply remove &:hover { color: white; } rules, as long as you have it set on a tag already and there is no something like a:hover rules which might override this.
In case if you have some a:hover rule with different color, simply add &:hover { color: white } right inside of a block.
.some-class{
> li{
a{
color: white;
background: #fti-lightgrey;
border-radius: 0px;
padding: 1px 15px;
// a color for the partcular tab that is chosen. (the color for each tab can be set inside mura)
&.orange{
&:hover{ background: #fti-orange; }
}
&.black {
&:hover{ background: black; }
}
&.topaz{
&:hover{ background: #fti-topaz; }
}
}
}
}

Resources