How to conditionally override a sass variable? - sass

I'm working on a RTL sass solution, trying keep RTL changes next to their LTR counterparts (rather than a separate stylesheet). I have some functions that depend on a $dir variable. So if it is set to 'ltr' the LTR rules are used. If set to 'rtl', the RTL rules are used.
So my code is:
$dir: ltr;
#function if-ltr($if, $else: null) {
#if $dir != rtl {
#return $if;
}
#else {
#return $else;
}
}
#function if-rtl($if, $else:null) {
#return (if-ltr($else, $if);
}
#mixin if-ltr {
#if $dir != rtl {
#content;
}
}
#mixin if-rtl {
#if $dir == rtl {
#content;
}
}
html[dir='rtl'] {
$dir: rtl;
}
p {
color: black;
}
#include if-rtl {
p {
color: red;
}
}
Logically I would think that in the case of the html tag's dir attribute being set to 'rtl', the variable $dir would be set to 'rtl' but this does not happen. I can override this by setting $dir: rtl !global but this changes it for the default ltr case as well.
So it seems that a variable in sass can only be set to one value? (It would not negate the 'variable' moniker as it would still be useful for changing the same value in multiple places at once.) Or is there a way around this? Am I missing something?

Related

SASS Mixin Rewrite & (ampersand)

I'm trying to write a mixin that will modify the parent selector on output. The idea is that in cases where a mixin is called, the parent selector will need to have a string replacement done on it. I have most of this working, but I can't figure out how to swallow the &.
.test {
#include alt_parent() {
content: 'test';
}
}
The mixin is something like this:
#mixin alt_parent() {
#{str-replace(unquote("#{selector_append(&)}"), "s", "x")} {
#content;
}
}
I have the string replacement working, so that isn't the problem. What I get is this (and I understand why):
.test .text {
content: 'test';
}
What I want is this:
.text {
content: 'test';
}
You have to use the #at-root directive to defeat the automatic inclusion of the selectors represented by &.
http://alwaystwisted.com/articles/2014-03-08-using-sass-33s-at-root-for-piece-of-mind
#mixin alt_parent($parent) {
#at-root {
#{str-replace(unquote("#{selector_append(&)}"), "s", "x")} {
#content;
}
}
}

Conditionally output a part of SCSS rule selector

I would like to specify an additional default shortcut class to a set of classes, similarly to that
#each $pos, $some-css-rules in ("left": ..., "right": ..., ...) {
#if $pos == "left" {
.block,
}
.block-#($pos) {
...
}
}
that would be outputted as
.block,
.block-left {
...
}
.block-right {
...
}
However, it will stumble over .block, syntax error.
.block-left cannot be replaced here with .block.left because $pos will collide with existing classes (.left, etc).
I would prefer to avoid .block { #extend .block-left } if possible, there is a considerable amount of similar rules that will gain a lot of WET code this way.
Is there a way to conditionally output a part of rule selector? How can both SCSS and CSS be kept DRY in a pattern like that?
I'm not sure if I understand the question but I achieve the output CSS based on your code. I put the #if directive inside the selector to compare with $pos variable. Here is my code:
SASS
#each $pos, $some-css-rules in ("left": red, "right": blue) {
.block-#{$pos} {
#if $pos == "left" {
#at-root .block, &{
color:$some-css-rules;
}
}
#else{
color:$some-css-rules;
}
}
}
Output
.block, .block-left {
color: red;
}
.block-right {
color: blue;
}

Creating a sass map value from within a mixin - saved globally [duplicate]

This question already has answers here:
How to assign to a global variable in Sass?
(2 answers)
Closed 6 years ago.
Is it possible to update a value in a sass map from within a mixin so that the change is saved globally?
Eg
$obj: (
init: false
)
#mixin set($map) {
#if map-get($obj, init) != true {
// mixin hasn't been called before
$map: map-set($map, init, true);
}
#else {
// mixin has been called before
}
}
.test {
#include set($obj);
// sets the init value to true
}
.test-2 {
#include set($obj);
// init value has already been set to true
}
I'm not sure if I understood what you are trying to do, but your code seems to be fine (haven't tested it though), excepting that there is no map-set function, but you can create one or just use map-merge (check here: http://oddbird.net/2013/10/19/map-merge/). I hope that helps.
#update 1: I think I got your question now, you want to pass the reference through the mixin, so if you have multiple maps, you can send the one you want to update to the mixin, I don't think this is possible though, because no reference is kept, if you need to update the variable you have to link directly to it, for exemple, this works (tested):
$obj: (
init: false
);
#mixin set($map) {
#if map_get($map, init) != true {
$obj: map-set($map, init, true) !global;
body {
background-color: #000;
}
} #else {
body {
background-color: #ff0000;
}
}
}
#include set($obj);
#include set($obj);
But if you reference to $map instead of $obj (in this line $obj: map-set($map, init, true) !global;), then a new global map (called $map), will be created. And every time you call the mixin again, it will be replaced by the map you sent as a parameter.
#update 2: I found a way to do it, but you have to keep a global 'map of maps', and every time you update this guy, you send the name of the map you want to update as parameter, so I came up with the following code, it's tested and working fine :)
#function map-set($map, $key, $value) {
$new: ($key: $value);
#return map-merge($map, $new);
}
$maps: (
obj1: (
init: false
),
obj2: (
init: false
),
);
#mixin set($prop) {
#if map_get(map_get($maps, $prop), init) != true {
$obj: map-set(map_get($maps, $prop), init, true);
$maps: map-set($maps, $prop, $obj) !global;
body {
background-color: #000;
}
} #else {
body {
background-color: #ff0000;
}
}
}
#include set(obj1); //black
#include set(obj2); //black
#include set(obj1); //red
#include set(obj2); //red
source: myself
Following on from #Paulo Munoz
Here is the solution
#function map-set($map, $key, $value) {
$new: ($key: $value);
#return map-merge($map, $new);
}
$extend : ();
$obj : (
margin: 0,
padding: 10
);
#mixin set($map, $name) {
#if map-has-key($extend, $name) {
map: has-key;
// call placeholder class
} #else {
$extend: map-set($extend, $name, true) !global;
map: does-not-have-key;
// create placeholder class
// call placeholder class
}
}
.test {
#include set($obj, test);
}
.test-2 {
#include set($obj, test);
}
which generates
.test {
map: does-not-have-key;
}
.test-2 {
map: has-key;
}

Combining Sass mixin selectors [duplicate]

This question already has answers here:
Styling a specific set of input types in a reusable way with Sass
(2 answers)
Closed 7 years ago.
I'm trying to figure out if it's at all possible to combine mixin selector strings. I don't believe this is possible in the context of my code, but I could very well be missing something!
Let's say I have the following scss:
// Apply a set of rules to input form fields.
#mixin input-form-fields {
input:not([type="hidden"]),
textarea {
#content;
}
}
// Apply a set of rules to button form fields.
#mixin button-form-fields {
button, button {
#content;
}
}
// Apply a set of rules to select form fields.
#mixin select-form-fields {
select {
#content;
}
}
// Apply a set of rules to all form fields.
#mixin all-form-fields {
#include input-form-fields {
#content;
}
#include button-form-fields {
#content;
}
#include select-form-fields {
#content;
}
}
Basically the all-form-fields mixin will call other mixins, thus generating the same set of rules for different selectors.
If I compile the following code:
#include all-form-fields {
margin-bottom: .5em;
}
I would get something like:
input:not([type="hidden"]),
textarea {
margin-bottom: .5em;
}
button,
.button {
margin-bottom: .5em;
}
select {
margin-bottom: .5em;
}
This is not ideal, I would love it if I could combine those selectors.
Does anyone have any ideas on how I could possibly combine the selector strings returned by the 3 different mixins?
If you don't mind storing your selectors in strings, you could define different field types using variables:
$input-form-fields: "input:not([type=hidden]), textarea";
$button-form-fields: "button";
$select-form-fields: "select";
Your then define your mixins with interpolated strings like so:
// Apply a set of rules to input form fields.
#mixin input-form-fields {
#{$input-form-fields} {
#content;
}
}
// Apply a set of rules to button form fields.
#mixin button-form-fields {
#{$button-form-fields} {
#content;
}
}
// Apply a set of rules to select form fields.
#mixin select-form-fields {
#{$select-form-fields} {
#content;
}
}
// Apply a set of rules to all form fields.
#mixin all-form-fields {
#{$input-form-fields},
#{$button-form-fields},
#{$select-form-fields} {
#content;
}
}
As a result, #include all-form-fields will result in
input:not([type=hidden]), textarea,
button,
select {
margin-bottom: .5em; }

How to overwrite linear-gradient function in Compass?

I'd like to use the code here to overwrite the linear-gradient function that comes with Compass. How can I do this?
I think what I need is a way to import the linear-gradient function and then locally rename it to something else, so that my own linear-gradient function can call it. E.g. something like:
#import "compass/css3/images";
// somehow make `original-lg` alias the current `linear-gradient`
#function linear-gradient($args...) {
#return original-lg($args...) + ", " + fixed-lg-from-link-above($args...);
}
What you are attempting won't work, because we are dealing with prefixed values that have to be split apart into distinct properties. As the author of the linked code, here is how I recommend using it. You'll need these functions:
#function convert-gradient-angle(
$deg
) {
#if type-of($deg) == 'number' {
#return mod(abs($deg - 450), 360deg);
} #else {
$direction: compact();
#if nth($deg,1) == 'to' {
#if length($deg) < 2 {
$direction: top;
#warn "no direction given for 'to'. Using 'to bottom' as default.";
} #else { $direction: opposite-position(nth($deg,2)); }
#if length($deg) > 2 { $direction: append($direction, opposite-position(nth($deg,3)), space);}
} #else {
$direction: append($direction, to, space);
#each $pos in $deg { $direction: append($direction, opposite-position($pos), space); }
}
#return $direction;
}
}
#function convert-gradient(
$angle,
$details...
) {
#return linear-gradient(convert-gradient-angle($angle), $details...);
}
The problem is, if you use multiple-backgrounds or anything like that, you will have to repeat the functions yourself in different properties. If you just want a simple background-image gradient, you can use this to simplify:
#mixin gradient-background-image(
$gradient...
) {
#include background-image(convert-gradient($gradient...));
background-image: linear-gradient($gradient...);
}
Otherwise you will need to write those two lines by hand, adding the other layers as needed.

Resources