How to convert this SCSS mixin to LESS? - sass

I am used to coding in SCSS but pretty new to LESS.
I have the following code in SCSS but would like to know how to write this in LESS.
Here is the SCSS code...
#mixin posL($property, $value) {
{$property}: $value;
}
.box {
width:200px;
height:200px;
background:red;
position:absolute;
#include posL(left, 100px);
}
So far I have something like this in LESS but I have to declare selectors...
.posL(#property: 100px, #value: 2px) {
left: #property;
-rtl-left: #value;
}
.box {
width:200px;
height:200px;
background:red;
position:absolute;
.posL(200px);
}
Is there a better way to write my LESS code so the selectors in the mixin remain generic (not specified)?

Update for LESS 1.6+
It is almost a direct mapping now with the 1.6 update, like so:
LESS
.posL(#property, #value) {
#{property}: #value;
}
.box {
width:200px;
height:200px;
background:red;
position:absolute;
.posL(left, 100px);
}
CSS Output
.box {
width: 200px;
height: 200px;
background: red;
position: absolute;
left: 100px;
}
Original Answer (pre 1.6)
There is currently no real way to do dynamic property names in LESS (whether for prefixing or for full property names ,like you want), though there is discussion about it.
I recommend a generic mixin with a nested, guarded mixin for the logic. This still gives you selection to specific properties, but does require some more explicit code to set it up. Something like:
.posL(#property, #value) {
.getProp(left) {
left: #value;
}
.getProp(-rtl-left) {
-rtl-left: #value;
}
.getProp(#property);
}
Then use it very similar to how you do the SASS version:
.box {
width:200px;
height:200px;
background:red;
position:absolute;
.posL(left, 100px);
}
Compiles to:
.box {
width: 200px;
height: 200px;
background: red;
position: absolute;
left: 100px;
}

Related

Render comma separated selectors separately in SCSS

I'm trying to style progress bars using SCSS. To get this working in both Webkit and Gecko browsers I need to use both -webkit and -moz prefixes:
progress {
height: 50px;
-webkit-appearance: none;
appearance: none;
background: cyan;
&::-moz-progress-bar,
&::-webkit-progress-value {
background-color: orange;
}
&::-webkit-progress-bar {
background-color: cyan;
}
}
which renders to
progress {
height: 50px;
-webkit-appearance: none;
appearance: none;
background: cyan;
}
progress::-moz-progress-bar, progress::-webkit-progress-value {
background-color: orange;
}
progress::-webkit-progress-bar {
background-color: cyan;
}
This works great in Firefox, but Chrome doesn't seem to like it. Compare the following two implementations:
Comma separated selectors
progress {
height: 50px;
-webkit-appearance: none;
appearance: none;
background: cyan;
}
progress::-moz-progress-bar, progress::-webkit-progress-value {
background-color: orange;
}
progress::-webkit-progress-bar {
background-color: cyan;
}
<progress max="1" value="0.5"></progress>
Entirely separate declarations
progress {
height: 50px;
-webkit-appearance: none;
appearance: none;
background: cyan;
}
progress::-webkit-progress-value {
background-color: orange;
}
progress::-moz-progress-bar {
background-color: orange;
}
progress::-webkit-progress-bar {
background-color: cyan;
}
<progress max="1" value="0.5"></progress>
The above code snippets render in Firefox and Chrome as shown below
Firefox
Chrome
comma separated
separate declarations
It seems like the problem comes from rendering the CSS with vendor-specific pseudos in comma-separated lists. Is there any way to force the SASS processor to render each selector in a comma separated list as its own declaration?
It would be nice to not use mix-ins, but if it's the only way it's the only way.
Yes, you are able to do so! If you want to render SASS to seperate CSS rules simply divide the comma seperated list into two seperate rules. SASS keeps different rules seperate and will not wrap them together. Example:
// ### > SASS
xprogress {
height: 50px;
appearance: none;
background: cyan;
//## divide comma seperated selectors
//## into different rules
&::-moz-progress-bar {
background-color: orange;
}
&::-webkit-progress-value {
background-color: orange;
}
&::-webkit-progress-bar {
background-color: cyan;
}
}
// ### > compiles to css
progress {
height: 50px;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background: cyan;
}
//## when compiling CSS
//## different rules will survive
progress::-moz-progress-bar {
background-color: orange;
}
progress::-webkit-progress-value {
background-color: orange;
}
progress::-webkit-progress-bar {
background-color: cyan;
}

Bubble up rules in SASS for variation / theme classes

How does one handle making "variation classes" in Sass? Example:
.bookshelf {
color: brown;
height: 100px;
.panel {
trimmed: no;
height: 10px;
}
.door {
height: 100px;
.knob {
shape: circle;
height: 10px;
}
}
}
You want to make a .bookshelf.small variation. Is there a way to write the variation code inside the main element using mixins or something that would "bubble up" the data?
.bookshelf {
color: brown;
height: 100px;
#include small-version {
height: 50px;
}
.panel {
trimmed: no;
height: 10px;
#include small-version {
height: 5px;
}
}
.door {
height: 100px;
#include small-version {
height: 50px;
}
.knob {
shape: circle;
height: 10px;
#include small-version {
height: 5px;
}
}
}
}
And the output of the mixin would be
.bookshelf.small {
height: 50px;
.panel {
height: 5px;
}
.door {
height: 50px;
.knob {
height: 5px;
}
}
Several things looked promising, such as the #at-root function and #content function, but neither would work for this scenario. I know if I write #media settings, they will bubble up. But I do not want this tied to a media query, I want this tied to a specific class (if .bookshelf also has the class small, apply these rules).
There is a small hack about it. But you should work with it really carefully. And it's not so good as you ask.
First of all, it prepends theme class to selector, so you can't use any element selectors. There is actually lot of selector helpers in docs but i didn't found any that could help with real parsing selector string.
Second it's not grouping out selectors. It inserts rules right where mixin were included. But it generates higher priority rules for your styles, so it could be a half a way solution.
You can dig for postcss parser or any other file post processing tool that could help you to separate your styles in a different files.
#mixin small-version {
#at-root .small#{&} {
#content;
}
}
.bookshelf {
color: brown;
height: 100px;
#include small-version {
height: 60px;
}
.panel {
trimmed: no;
height: 10px;
#include small-version {
height: 5px;
}
}
.door {
height: 100px;
.knob {
shape: circle;
height: 10px;
}
}
}
Gist link
You can qualify a selector by putting & to the right of the intended parent of the selector. Wrapping it in #{} allows you to place it directly beside that parent.
The #at-root rule causes everything proceeding it to be emitted at the root instead of using regular nesting.
If you use both, you can achieve what you are looking for.
.flashlight {
.light {
background: yellow;
#at-root .dead-battery#{&} {
background: transparent;
}
.daytime &{
background: transparent;
}
}
}
This would compile to:
.flashlight .light {
background: yellow;
}
.dead-battery.flashlight .light {
background: transparent;
}
.daytime .flashlight .light {
background: transparent;
}

Issue mixing variable with mixin with keyframe animation?

First time using SCSS, and testing my knowledge from the Sass-Lang.com guide. According to the guide, it is possible to both set variables and use mixins to simplify your CSS.
I was coding an animation where the div is clipped from bottom to top. I used variables to set the initial and final clip-path settings, and used them while calling a mixin. Yet I get the error, 'Invalid CSS after "...slider-initial)": expected "{", was "; }"'. What am I doing wrong?
Here is my code:
<body>
<section id='main'>
<div id='left'></div>
<div id='right'></div>
<section>
</body>
$slider-initial: inset(0 0 0 0);
$slider-final: inset(0 0 100% 0);
#mixin slider-clip($slider-state) {
-webkit-clip-path: $slider-state;
clip-path: $slider-state;
}
body {
height: 100%; width: 100%;
margin: 0 auto;
}
#main {
height: 64vh; width: 38vw;
margin: 0 auto;
margin-top: 10%;
position: relative;
display: flex;
flex-direction: row;
justify-content: center;
border: 1vh solid black;
}
#left {
order: 1;
width: 4%;
height: 100%;
margin-left: 46%;
background: green;
}
#right {
opacity: 1;
order: 2;
width: 4%;
height: 100%;
margin: auto;
margin-left: 0;
animation-name: dropdown;
animation-duration: 4s;
background: red;
}
#keyframes dropdown {
from { #mixin slider-clip($slider-initial); }
to { #mixin slider-clip($slider-final); }
}
You called your mixin in a wrong way:
#keyframes dropdown {
from { #mixin slider-clip($slider-initial); }
to { #mixin slider-clip($slider-final); }
}
In the guide on sass-lang.com, you can see the following example of how to include a mixin:
.box { #include border-radius(10px); }
Applied to your case, your code should look like this:
#keyframes dropdown {
from { #include slider-clip($slider-inital); }
to { #include slider-clip($slider-final); }
}

Sass / Scss: Changing the order of nested psuedo selectors for :before and :hover in generated CSS?

Given the following Sass:
div.test {
display: inline-block;
background-color: #ffffff;
color: #000000;
&:before {
& {
&:hover {
border: 1px solid salmon;
}
}
width: 25px;
height: 25px;
content: "";
}
}
The resulting CSS compiles to:
div.test {
display: inline-block;
background-color: #ffffff;
color: #000000;
}
div.test:before {
width: 25px;
height: 25px;
content: "";
}
div.test:before:hover {
border: 1px solid salmon;
}
What I am attempting to do is generate div.test:hover:before (the current output is before:hover).
NOTE: I am able to generate the expected CSS by using the following Sass:
div.test {
display: inline-block;
background-color: #ffffff;
color: #000000;
&:hover {
&:before {
border: 1px solid salmon;
}
}
&:before {
width: 25px;
height: 25px;
content: "";
}
}
However I would like to know if it is possible using the first nested approach or some modification of it.
The goal was to avoid having to repeat &:before if there was such a way to do so using Sass syntax. I am also OK with knowing it isn't possible.
While initially the plan was to have '&' available in SassScript as a string that could be manipulated so that you could insert values wherever you wanted, those plans have been abandoned for 3.3 due to complication. Unfortunately you'll have to wait a while to be able to do this. At the moment '&' is immutable and just means "whatever the selector chain up to this point is".
EDIT (2020.02.15):
it is now technically possible to achieve this with recent versions of dart-sass:
#use "sass:selector";
#mixin unify-parent($child) {
#at-root #{selector.unify(&, $child)} {
#content;
}
}
div.test {
display: inline-block;
background-color: #ffffff;
color: #000000;
&:before {
width: 25px;
height: 25px;
content: "";
#include unify-parent(":hover") {
border: 1px solid salmon;
}
}
}
Sources:
https://sass-lang.com/blog/the-module-system-is-launched
https://sass-lang.com/documentation/style-rules/parent-selector#advanced-nesting

Are there "hooks" in SASS?

I was reviewing UiKit, a frontend framework built with LESS, and noticed a rather interesting feature: hooks. Look at the following in the base framework:
.uk-panel-badge {
position: absolute;
top: 0;
right: 0;
z-index: 1;
.hook-panel-badge;
}
.hook-panel-badge() {}
If you want to override that in a theme let's say you are building outside of the core files (files that come later in compilation), then you would do the following:
.hook-panel-badge() {
right:auto;
}
So, in essence it looks like you are able to override/customize selectors without adding in additional selectors, so it keeps your markup as small as possible.
Is anything like this available in SASS? This looks like an awesome feature that I'd love to use as a primarily SASS user, but I haven't found #extend to be the same. Thanks!
Update
The placeholder feature seems to be the closest thing I've found:
.panel {
background:red;
#extend %hook-panel;
}
%hook-panel {
color:blue;
}
Which renders as:
.panel {
background: red; }
.panel {
color: blue; }
and can be defined after (which is awesome), but this still duplicates the selector. Is there anyway to only render the value of the placeholder in the original selector it is included in?
SASS has a similar facility called mixins:
// Define the mixin
#mixin large-text
font-family: Arial
color: #ff0000
// Use it
.page-title
#include large-text
padding: 10px
// Compiled CSS
.page-title {
font-family: Arial;
color: #ff0000;
padding: 10px;
}
The beauty of mix-ins is that they can take arguments, so you don't need to override commonly-changed selectors:
// Define
#mixin sexy-border($color, $width)
border-color: $color
border-width: $width
border-style: dashed
// Paragraphs in general will have a sexy blue 10px dashed border
p
#include sexy-border(blue, 10px)
// Paragraphs of class "plain" will have a plain old black 1px solid border
p.plain
#include sexy-border(black, 1px)
border-style: solid
// Compiled CSS
p {
border-color: blue;
border-width: 10px;
border-style: dashed;
}
p.plain {
border-color: black;
border-width: 1px;
border-style: solid;
}
SASS mixin documentation.
As far as I can see, there's no exact mapping of the LESS behaviour to a SASS equivalent.
You have the following options:
#extend
.uk-panel-badge {
position: absolute;
top: 0;
right: 0;
z-index: 1;
#extend .hook-panel-badge;
}
.hook-panel-badge{
right:auto;
}
Resulting in:
.uk-panel-badge {
position: absolute;
top: 0;
right: 0;
z-index: 1;
}
.hook-panel-badge, .uk-panel-badge {
right: auto;
}
This produces slightly more CSS than the LESS equivalent does, because the original (unhooked) class is preserved.
#mixin
//theme.scss:
#mixin hook-panel-badge(){
}
#import "hooks";
.uk-panel-badge {
position: absolute;
top: 0;
right: 0;
z-index: 1;
#include hook-panel-badge;
}
_hooks.scss:
//theme creator can override hooks in here.
#mixin hook-panel-badge(){
right:auto;
}
This creates the exact same code as your SASS, but the downside is that you need to define a blank mixin for every hookable class, which is a bit of effort. (You could of course put these all in the _hooks.scss file, but that would make the override code harder to read.
EDIT:
I guess there is one more option as below, which saves a little bit of typing on the extendee's side, but moves away from standard CSS syntax for the extender a bit too much for my liking. You could of course use a partial file as in 2.
#mixin hook($class){
//generic override.
#if($class == "uk-panel-badge"){
right:auto;
}
#else if($class== "selector2"){
//override other classes in this way.
}
}
.uk-panel-badge {
position: absolute;
top: 0;
right: 0;
z-index: 1;
#include hook("uk-panel-badge");
}
Overall I still feel 1 is the best approach.

Resources