How do you check for unitless variables with multiple values SASS - css

I try to create a flexible mixin where you can set the space for padding or margin from the same mixin
I based it on a bourbon for positioning
mixin setSpace($setSpace: padding, $setSpaceValues: 0 0 0 0){
#if type-of($setSpace) == list{
$setSpaceValues :$setSpace;
$setSpace: padding;
}
$top: nth($setSpaceValues, 1);
$right: nth($setSpaceValues, 2);
$bottom: nth($setSpaceValues, 3);
$left: nth($setSpaceValues, 4);
#if unitless($top and $right and $bottom and $left){
#{$setSpace}: $top+px $right+px $bottom+px $left+px ;
}
}
But I try to get the flexibility to be able to add individual units to it as well so that I can do also
.test{
#include setSpace(margin, 10% 0 5 5);
}

You could use the Sass if() function on each value to check for unitless ... maybe make define a function that does this - something in this direction perhaps:
#function setUnit($val){
#return if(unitless($val), $val * 1px, $val);
}
And then you can use it in your mixin:
#mixin setSpace($setSpace: padding, $setSpaceValues: 0 0 0 0){
#if type-of($setSpace) == list{
$setSpaceValues: $setSpace;
$setSpace: padding;
}
$top: nth($setSpaceValues, 1);
$right: nth($setSpaceValues, 2);
$bottom: nth($setSpaceValues, 3);
$left: nth($setSpaceValues, 4);
#{$setSpace}: setUnit($top) setUnit($right) setUnit($bottom) setUnit($left) ;
}
DEMO
in addition you could also just set the values in a loop ( - a bit more flexible and shorter):
#mixin setSpace($setSpace: padding, $setSpaceValues: 0 0 0 0){
#if type-of($setSpace) == list{
$setSpaceValues: $setSpace;
$setSpace: padding;
}
$out: ();
#each $val in $setSpaceValues{
$out: append($out, if(unitless($val), $val * 1px, $val));
}
#{$setSpace}: $out;
}
DEMO
Hope this gives you some ideas.

Your mixin just might be better suited as a function:
#function spacing($values: 0) {
$collector: ();
#each $v in $values {
$collector: append($collector, if(unitless($v) and $v != 0, $v * 1px, $v));
}
#return $collector;
}
.test{
margin: spacing(10% 0 5 5);
}
Output:
.test {
margin: 10% 0 5px 5px;
}
If all you're doing is transforming a single value, functions make it a little more clear that's all that's happening when you come back to read the code later.

Related

CSS rules merging

I am working to implement RTL support on a large framework. For that, I replaced all of the rules that are directional with mixins like:
a {
#include padding(0, 1px, 0, 0);
#include margin(0, 1px, 0, 0);
}
What this does, is to add the relevant padding/margin depending on the direction of the document.
Every mixin creates a case for ltr and a case for rtl.
This is the result from those mixins:
[dir="ltr"] a {
padding: 0 1px 0 0;
}
[dir="rtl"] a {
padding: 0 0 0 1px;
}
[dir="ltr"] a {
margin: 0 1px 0 0;
}
[dir="rtl"] a {
margin: 0 0 0 1px;
}
Which works, and is fine, but creates a lot of duplicate selectors (2 per mixin), so the css bundle size increases by 100kb (20%), and a large part of it is because of this duplication.
Expected result:
[dir="ltr"] a {
padding: 0 1px 0 0;
margin: 0 1px 0 0;
}
[dir="rtl"] a {
padding: 0 0 0 1px;
margin: 0 0 0 1px;
}
What post-processing I can do to merge relevant duplicate selectors, without hurting the order of css execution?
Undesired case:
Lets say I have this code:
b.s1 {
padding-left: 1px;
margin: 0;
}
b.s2 {
padding-left: 0;
margin: 1px;
}
b.s1 {
padding-left: 1px;
}
If I merge b.s1 upwards, then s2's padding-left can override it.
If I merge b.s1 downwards, then s2's margin is overriden.
Is there any solution to this problem?
EDIT: Original code
// Add property for all sides
// #param {string} $prop
// #param {string} $top
// #param {string} $end
// #param {string} $bottom
// #param {string} $start
// #param {boolean} $content include content or use default
// ----------------------------------------------------------
#mixin property($prop, $top, $end: $top, $bottom: $top, $start: $end, $content: false) {
#if $top == $end and $top == $bottom and $top == $start {
#include multi-dir() {
#{$prop}: $top;
}
} #else if $top == $bottom and $end == $start and $top != null and $end != null {
#include multi-dir() {
#{$prop}: $top $end;
}
} #else if $end == $start and $top != null and $end != null and $bottom != null {
#include multi-dir() {
#{$prop}: $top $end $bottom;
}
} #else if $top != null and $end != null and $bottom != null and $start != null {
#include ltr() {
#{$prop}: $top $end $bottom $start;
}
#include rtl() {
#{$prop}: $top $start $bottom $end;
}
} #else {
#if $content == true { // TODO check if #content exists instead
#content;
} #else {
#include property-horizontal($prop, $start, $end);
#include multi-dir() {
#{$prop}-top: $top;
#{$prop}-bottom: $bottom;
}
}
}
}
// Add padding for all sides
// #param {string} $top
// #param {string} $end
// #param {string} $bottom
// #param {string} $start
// ----------------------------------------------------------
#mixin padding($top, $end: $top, $bottom: $top, $start: $end) {
#include property(padding, $top, $end, $bottom, $start);
}
// Add margin for all sides
// #param {string} $top
// #param {string} $end
// #param {string} $bottom
// #param {string} $start
// ----------------------------------------------------------
#mixin margin($top, $end: $top, $bottom: $top, $start: $end) {
#include property(margin, $top, $end, $bottom, $start);
}
I wrote a specific fix for dir, as it is my main problem, not other duplications which are a tiny proportion of my bundle.
Pretty simple, and does the job, 0 seconds. It does not merge up or down, but instead removes the code and adds a chunk with the merged directional code at the end. (it doesn't matter for directional, as everything maintains its order and specificity)
function joinDirections(contents) {
// This includes multi directional selectors, like `[dir="ltr"] sel, [dir="rtl"] sel`,
// Which go into the ltr pile, but it is ok as the rest (`sel, [dir="rtl"] sel`) is still good.
const dirExp = /\[dir="(.*?)"\](.*?){\s*([^}]*?)\s*}/gm;
let directions = {};
let matches;
while (matches = dirExp.exec(contents)) {
if (!(matches[1] in directions))
directions[matches[1]] = {};
if (!(matches[2] in directions[matches[1]]))
directions[matches[1]][matches[2]] = '';
directions[matches[1]][matches[2]] += matches[3];
}
contents = contents.replace(dirExp, '');
let directionalContents = '';
Object.keys(directions).forEach(dir => {
Object.keys(directions[dir]).forEach(selector => {
directionalContents += `[dir="${dir}"]${selector}{${directions[dir][selector]}}\n`;
});
});
return contents + directionalContents;
}

SASS and data attribute multiple

I have a problem with the nesting of SAS to make multiple selections, nose much about it, I hope you can help me and understand (because I do not write very good English).
SASS mixin:
#mixin data($x) {
$sel: &;
$collector: ();
#for $i from 1 through length($sel) {
$s: nth($sel, $i);
$last: nth($s, -1);
#if str-slice($last, -1) == "]" {
// if is just the bare attribute with no value, $offset will be -1, otherwise it will be -2
$offset: -1;
$current-x: $x;
#if str-slice($last, -2) == '"]' {
// this attribute already has a value, so we need to adjust the offset
$offset: -2;
} #else {
// no attribute value, so add the equals and quotes
$current-x: '="' + $x + '"';
}
$last: str-slice($last, 1, $offset - 1) + $current-x + str-slice($last, $offset);
$collector: append($collector, set-nth($s, -1, $last), comma);
} #else {
// following line will append $x to your non-attribute selector
$collector: append($collector, selector-append($s, $x), comma);
// the following line will not change your non-attribute selector at all
//$collector: append($collector, $s, comma);
}
}
#at-root #{$collector} {
#content;
}
}
SASS:
[data-content] {
#include data("content") {
background: black;
}
}
Output:
[data-content="content"] {
background: black;
}
The problem is I can not nest more than one item, for example does not work:
[data-content] {
#include data("content", "menu") {
background: black;
}
}
Output:
[data-content="content"],
[data-content="menu"] {
background: black;
}
Any way to solve?
You can always do something like this if you don't mind having to specify your selectors instead of passing them through as variables.
[data-content="content"], [data-content="menu"]{
#include data() {
background: black;
}
}

Darkening a SCSS Mixin based on percentage

I have a sass mix-in for my angular to do list, that lightens the color of the app as you go, 1 being dark blue, 50 being white.
How can I go about making the hover make each item's background 50-60% darker?
My mixin:
#mixin shades-of-blue ( $count, $startcolor ) {
#for $i from 0 through $count {
$background-color: lighten( $startcolor, $i + $i );
.tasks:nth-of-type(#{$i}) {
background-color: $background-color;
}
}
}
#include shades-of-blue( 50, #2d89ef);
You should probably be using mix instead of lighten/darken, otherwise everything is white at around 26.
#mixin shades-of-blue ( $count, $startcolor ) {
#for $i from 0 through $count {
$background-color: mix(white, $startcolor, $i + $i);
.tasks:nth-of-type(#{$i}) {
background-color: $background-color;
&:hover {
background-color: mix(black, $background-color, 50);
}
}
}
}
#include shades-of-blue( 50, #2d89ef);
You can try it out here: http://sassmeister.com/

Create a recursion mixin scss

I am trying to create a recursion #mixin. The mixin has the task to append a selector from a given list and apply a CSS rules. The result of this has to look like this:
[data-tag]:not([conref]) {
border: dashed 2px #2999d1;
}
[data-tag]:not([conref])[nodeid] {
border: dashed 2px #2999d1;
}
[data-tag]:not([conref])[nodeid][draggable] {
border: dashed 2px #2999d1;
}
[data-tag]:not([conref])[nodeid][draggable][class] {
border: dashed 2px #2999d1;
}
The #mixin which I have created looks like this:
#mixin set-border-to-selectors($list, $item){
#if false == (index($list, $item)){
#error "Fail: #{$item} not in list: #{$list}";
}
#else{
$index: index($list, $item);
#debug "index: #{$index}";
$item: nth($list, $index);
&[#{$item}]{
#include set-data-tag-border;
#if($index + 1 <= length($list)){
$item: nth($list, $index + 1);
#include set-border-to-selectors($list, $item);
}
}
}
}
The first time I use the function it works pretty well.
#mixin show-data-tag-border{
$data-tag-list: (#{$id}, draggable, class);
$data-tag-list-two: (#{$id}, #{$conref}, class);
&[data-tag]:not([#{$conref}]){
#include set-data-tag-border;
#include set-border-to-selectors($data-tag-list, #{$id});
}
&[data-tag]{
#include set-border-to-selectors($data-tag-list-two, #{$id});
}
But the second call of the set-border-to-selector function throws an error, because the $index in the recusion #mixin does not contain a number. It is empty.
I call the function from p{#include show-data-tag-border;} and the border will be set in #mixin set-data-tag-border{border: solid 1px black}. And this two variables:
$id: id;
$conref: conref;
Is there something wrong what I do not see or is this approach wrong to create an recursion in SCSS? I won't use this function only one time and write one or more similar function below to accomplish this.
I'm not gonna try to repair your code, as it's too big. Recursion should work just fine like this:
#mixin set-borders ($list, $index: 1) {
#if $index <= length($list) {
#if $index == 1 {
&#{nth($list, $index)} {
#include set-borders($list, $index + 1);
}
} #else {
&,
&#{nth($list, $index)} {
#include set-borders($list, $index + 1);
}
}
} #else {
// ... (set the required properties)
}
}
The problem was the declaration of the list elements. I have to make tick marks.
So instead of this:
$data-tag-list: (#{$id}, draggable, class);
$data-tag-list-two: (#{$id}, #{$conref}, class);
I have to enclose every element in the list with tick marks:
$data-tag-list: ('#{$id}', 'draggable', 'class');
$data-tag-list-two: ('#{$id}', '#{$conref}', 'class');
and it works!
Thank you hon2a for your help!
Here is the full code for check up:
$id: id;
$conref: conref;
#mixin set-data-tag-border{border: solid 1px black}
#mixin set-border-to-selectors($list, $item){
#if false == (index($list, $item)){
#error "Fail: #{$item} not in list: #{$list}";
}
#else{
$index: index($list, $item);
#debug "index: #{$index}";
$item: nth($list, $index);
&[#{$item}]{
#include set-data-tag-border;
#if($index + 1 <= length($list)){
$item: nth($list, $index + 1);
#include set-border-to-selectors($list, $item);
}
}
}
}
#mixin show-data-tag-border{
$data-tag-list: ('#{$id}', 'draggable', 'class');
$data-tag-list-two: ('#{$id}', '#{$conref}', 'class');
&[data-tag]:not([#{$conref}]){
#include set-data-tag-border;
#include set-border-to-selectors($data-tag-list, #{$id});
}
&[data-tag]{
#include set-border-to-selectors($data-tag-list-two, #{$id});
}
}
p{#include show-data-tag-border;}

Subtracting blur value from box-shadow property through SCSS

I have been cogitating on this for a while now. In my SCSS I have the following:
$shadow: 0 0 25px rgb(46, 46, 46);
div {
box-shadow: $shadow;
}
How can I subtract the 25px of blur by, let's say 10 so that I may use a blur of 15 pixels? Furthermore, how do I select the blur value? Considering I want to apply a box-shadow on a div element:
<div>Lorem Ipsum</div>
The $shadow variable is simply a list of values. If the blur is always going to be the 3rd item in the list (such as in your example), then what you're looking at is something like this:
div {
box-shadow: nth($shadow, 1) nth($shadow, 2) nth($shadow, 3) - 15px nth($shadow, 4);
}
If the blur is in another valid position in the list (maybe it's an inset shadow), then you'll need to start doing things like checking the length of the list and/or examine the first element to see if it is inset:
div {
#if nth($shadow, 1) == inset {
box-shadow: nth($shadow, 1) nth($shadow, 2) nth($shadow, 3) nth($shadow, 4) - 15px nth($shadow, 5);
} #else {
box-shadow: nth($shadow, 1) nth($shadow, 2) nth($shadow, 3) - 15px nth($shadow, 4);
}
}
Alternately, you could do it programatically:
$shadow: 0 0 25px rgb(46, 46, 46);
#function adjust-shadow($shadow, $position, $adjustment) {
$x: ();
$shift: if(nth($shadow, 1) == inset, -1, 0);
#for $i from 1 through length($shadow) {
$p: $i + $shift;
#if $position == color and $i == length($shadow) {
$x: append($x, $adjustment);
} #else if ($position == x-offset and $p == 1) or ($position == y-offset and $p == 2) or ($position == blur and $p == 3) {
$x: append($x, nth($shadow, $i) + $adjustment);
} #else {
$x: append($x, nth($shadow, $i));
}
}
#return $x;
}
.foo {
box-shadow: adjust-shadow($shadow, blur, -15px);
}
.bar {
box-shadow: adjust-shadow($shadow, color, blue);
}
.baz {
box-shadow: adjust-shadow(adjust-shadow($shadow, blur, -15px), y-offset, -2), adjust-shadow($shadow, blur, 10px);
}
Make a new variable for the blur value:
$blur: 25px - 10;
$shadow: 0 0 $blur rgb(46, 46, 46);
div {
box-shadow: $shadow;
}
Depending on your overall needs, you might be better served by writing a mixin.

Resources