SASS offset position mixin troubles - css

I've been trying to make an offset positioning mixin for the past couple of hours. I've been struggling with lots of errors, I cant understand what is wrong.
Here is the latest version,
#function is-offset-prop-valid($value) {
$values: auto initial inherit 0;
#return (type-of($value) == number and not unitless($value))
and (index($values, $value) != false);
}
#mixin position($position, $args) {
$offsets: top right bottom left;
#each $offset in $offsets {
$i: index($args, $offset);
#if $i == length($args) {
#error "Empty offset values not allowed";
} else {
#if is-offset-prop-valid(nth($args, $i+1)) {
#{$offset}: nth($args, $i+1);
} else {
#error "Set value #{nth($args, $i+1)} not a valid property for #{$offset}";
}
}
}
positon: $position;
}
Normally I would have the nth($args, $i + 1) set as variable, but for the sake of this example, I left it like that.
When I use the mixin
.abs {
#include position(absolute, top 10px);
}
I get this error from the inner if statement:
Set value 10px not a valid property for top

I fixed your code and rewrited it a bit. Sassmeister demo.
At first, is-offset-prop-valid function is now more readable.
Secondly, mixin position does loop through arguments ($args), not through $offsets. And I added more argument checks (look at comments). And you need to write # symbol before else: #else.
#function is-offset-prop-valid($value) {
$values: auto initial inherit 0;
$is-numder: type-of($value) == number;
$is-unitless: unitless($value);
$match-default-values: index($values, $value) != null;
#return ($is-numder and not $is-unitless) or $match-default-values;
}
#mixin position($position, $args...) {
$offsets: top right bottom left;
#if length($args) == 0 {
#error "Empty offset values not allowed.";
}
#each $arg in $args {
// Check offset key-value pair
#if length($arg) != 2 {
#error "Invalid pair offset-name: offset-value.";
}
$offset: nth($arg, 1);
$value: nth($arg, 2);
// Check offset name parameter
#if index($offsets, $offset) == null {
#error "Invalid offset name: `#{$offset}`.";
}
// Check offset value parameter
#if not is-offset-prop-valid($value) {
#error "Set value `#{$value}` not a valid property for `#{$offset}`.";
}
#{$offset}: $value;
}
position: $position;
}
.abs {
#include position(absolute, top 10px, left 23px);
}
But it seems to me that a simple set position is much simpler and more understandable:
.abs {
top: 10px;
left: 23px;
position: absolute;
}

Related

SASS loop on map, get next iteration

I have a color array like this :
$colors: (
'primary': '#aaa',
'secondary': '#bbb',
'color-3': '#ccc',
'color-4': '#ddd',
'color-5': '#eee',
);
and I want to automate class creation in loop like this :
#each $col in map-keys($theme-colors){
&.btn-#{$col} {
background-color: map-get($theme-colors, $col);
&:hover{
background-color: map-get($theme-colors, $col + 1); // <= my problem is here to get my "$col + 1"
}
}
}
My idea is to create a class btn-primary with primary color background but, on hover, secondary color at background.
A class btn-secondary secondary color background but, on hover, color-3 color at background, etc.
How can I do this?
Thanks ;)
Here you can find your solution: https://github.com/elcheio/sass-map-get-next-prev
Using that function (i.e. map-get-next function) you can resolve your problem.
So, first of all copy and paste that function... very very simple (^_^;)
#function map-get-next($map, $key, $fallback: false, $return: value) {
// Check if map is valid
#if type-of($map) == map {
// Check if key exists in map
#if map-has-key($map, $key) {
// Init index counter variable
$i: 0;
// Init key index
$key-index: false;
// Traverse map for key
#each $map-key, $map-value in $map {
// Update index
$i: $i + 1;
// If map key found, set key index
#if $map-key == $key {
$key-index: $i;
}
// If next index return next value or key based on $return
#if $i == $key-index + 1 {
#if $return == key {
#return $map-key;
} #else {
#return $map-value;
}
}
// If last entry return false
#if $i == length($map) {
#return $fallback;
}
}
#warn 'No next map item for key #{$key}';
#return $fallback;
}
#warn 'No valid key #{$key} in map';
#return $fallback;
}
#warn 'No valid map';
#return $fallback;
}
Then you can add your map:
$colors: (
'primary': #aaa,
'secondary': #bbb,
'color-3': #ccc,
'color-4': #ddd,
'color-5': #eee,
);
In the end, you have to create an #each loop to estract every pair name/value of your map:
#each $name, $value in $colors{
.btn-#{$name} {
background-color: $value;
&:hover{
background-color: map-get-next($colors, $name, #ffffff); // <== here you have to write the color for your last item (i.e. 'color-5'; in this example is white)
}
}
}
That's it! Your output will be:
.btn-primary {
background-color: #aaa;
}
.btn-primary:hover {
background-color: #bbb;
}
.btn-secondary {
background-color: #bbb;
}
.btn-secondary:hover {
background-color: #ccc;
}
.btn-color-3 {
background-color: #ccc;
}
.btn-color-3:hover {
background-color: #ddd;
}
.btn-color-4 {
background-color: #ddd;
}
.btn-color-4:hover {
background-color: #eee;
}
.btn-color-5 {
background-color: #eee;
}
.btn-color-5:hover {
background-color: #ffffff;
}

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;
}
}

Variables in Bourbon size() mixin

I've recently started using an updated version of the Bourbon SASS plugin. I've previously used a custom mixin to set the width and height using the following syntax;
$width: 350;
$height: 200;
.wrapper {
#include size($width $height);
}
This would assume px as the unit of measurement. However with the updated version of Bourbon, it has it's own size() mixin which doesn't work quite the same.
I cannot figure out how to use variables for the width and height properties. I've tried the following to no avail;
#include size(#{$width}px #{$height}px); - interpolation doesn't appear to work directly in the mixin. I tried doing something similar by making a new variable that had the unit on the end.
$width: 350;
$height: 200;
$width_str: #{$width}px;
$height_str: #{$height}px;
.wrapper {
#include size($width_str $height_str);
}
Finally, I tried setting the variables like this as I've used similar syntax elsewhere (albeit not for a mixin);
$width_str: ($width) + px;
$height_str: ($height) + px;
I don't get any errors when compiling the SCSS, instead the width and heigth properties are just missing from the stylesheet. I can confirm that using string values like so: #include size(350px 200px); does work, I just cannot get variables to play nice with this mixin. Any ideas?
UPDATE: While I still can't get the bourbon size mixin to work with variables, I can still use the custom one I was using previously, as long as that's defined after including bourbon in my project. For reference, this is the size mixin I use, works with everything I've ever thrown at it;
#mixin size($size) {
#if length($size) == 1 {
#if $size == auto {
width: $size;
height: $size;
}
#else if unitless($size) {
width: $size + px;
height: $size + px;
}
#else if not(unitless($size)) {
width: $size;
height: $size;
}
}
// Width x Height
#if length($size) == 2 {
$width: nth($size, 1);
$height: nth($size, 2);
#if $width == auto {
width: $width;
}
#else if not(unitless($width)) {
width: $width;
}
#else if unitless($width) {
width: $width + px;
}
#if $height == auto {
height: $height;
}
#else if not(unitless($height)) {
height: $height;
}
#else if unitless($height) {
height: $height + px;
}
}
}
In the code of new Bourbone library you can find the following code for the "size" mixin:
#if $height == auto or (type-of($height) == number and not unitless($height)) {
height: $height;
}
#if $width == auto or (type-of($width) == number and not unitless($width)) {
width: $width;
}
The main problem is about "unitless" function that returns:
unitless(100) => true
unitless(100px) => false
that's why you have to always pass into the mixin values such as "350px", "250px"
You can try to use the following "size" mixin%
#mixin size($size) {
$height: nth($size, 1);
$width: $height;
#if length($size) > 1 {
$height: nth($size, 2);
}
#if $height == auto or type-of($height) == number {
#if unitless($height){
height: ($height) + px;
} #else {
height: $height;
}
}
#if $width == auto or type-of($width) == number {
#if unitless($width){
height: ($width) + px;
} #else {
height: $width;
}
}
}

SASS :not selector

I have a :not css selector in SASS mixin but it doesn't do anything:
Code Snippet:
#mixin dropdown-pos($pos:right) {
&:not(.notip) {
#if $comp-tip == true{
#if $pos == right {
top:$dropdown-width * -0.6;
#include tip($pos:$pos);
}
}
}
&.notip {
#if $pos == right {
top: 0;
left:$dropdown-width * 0.8;
}
}
}
The .notip class is being generated but no CSS is being generated for :not(.notip).
I tried re-creating this, and .someclass.notip was being generated for me but .someclass:not(.notip) was not, for as long as I did not have the #mixin tip() defined. Once I had that, it all worked.
http://sassmeister.com/gist/9775949
$dropdown-width: 100px;
$comp-tip: true;
#mixin tip($pos:right) {
}
#mixin dropdown-pos($pos:right) {
&:not(.notip) {
#if $comp-tip == true{
#if $pos == right {
top:$dropdown-width * -0.6;
background-color: #f00;
#include tip($pos:$pos);
}
}
}
&.notip {
#if $pos == right {
top: 0;
left:$dropdown-width * 0.8;
background-color: #00f;
}
}
}
.someclass { #include dropdown-pos(); }
EDIT: http://sassmeister.com/ is a good place to debug your SASS because it gives you error messages. Undefined mixin 'tip'. it what I get when I remove #mixin tip($pos:right) { }

Resources