Sass - How to Import a Message into Directive - sass

I use #warn a lot, but I'd like to import pre-written messages into my warnings that are stored for example in another function or file.
Is this possible?
I know the following won't work, but so you get the idea...
#if $a == 'red' {
color: red;
} #else if $a == 'blue' {
color: blue;
} #else {
#warn '#include error-msg_no-color-detected';
}

This can be done using variables...
$msg: 'Something went wrong!';
#if ... {
...
} #else {
#warn: '#{$msg}';
}
This allows me to create a _debug.scss file containing lots of messages.
#import 'config';
#import 'debug';
#import 'dark';
#import 'application';
_debug.scss:
#msg-1: 'Oops!';
#msg-2: 'No color defined. Check the config file.';

Related

Using local Sass variables in mixin

I have a bunch of variables that are defined for use in a light/dark theme (two separate files):
$primary-1: red;
$primary-2: green;
and I don't want to declare them all twice when consuming them. I wrote a mixin that does the assignments for me:
#mixin assign-vars {
--primary-1: #{$primary-1};
--primary-2: #{$primary-2};
}
and I would like to use it like this:
#import 'assign-vars';
:root,
:root[data-theme='light'] {
#import 'light-theme-variables';
#include assign-vars;
}
:root[data-theme='dark'] {
#import 'dark-theme-variables';
#include assign-vars;
}
but this does not work, as I get an error saying that $primary-1 is an undefined variable. How can I accomplish this without having to do all of the declarations twice?
I ended up moving the variables into a map:
$light-vars: (
$font-name: 'Some Font',
$primary-1: red
);
$dark-vars: (
$font-name: 'Some Other Font',
$primary-1: green
);
and created a helper mixin to assign them to CSS vars of the same name:
#mixin assign-map-properties($map) {
#each $key, $value in $map {
#if (type-of($value) == 'string') {
--#{$key}: '#{$value}';
}
#else {
--#{$key}: #{$value};
}
}
}
which allowed me to accomplish what I need:
#import 'assign-map-properties';
:root,
:root[data-theme='light'] {
#import 'light-vars';
#include assign-map-properties($light-vars);
}
:root[data-theme='dark'] {
#import 'dark-vars';
#include assign-map-properties($dark-vars);
}
Generated CSS:
:root,
:root[data-theme='light'] {
--font-name: 'Some Font';
--primary-1: red;
}
:root[data-theme='dark'] {
--font-name: 'Some Other Font';
--primary-1: green;
}

How to add "orientation media query" to bootstrap 4 sass media query

i am using the "vanilla" bootstrap 4 sass media query in my scss files:
#include media-breakpoint-up(xs){}
#include media-breakpoint-up(sm){}
#include media-breakpoint-up(lg){}
#include media-breakpoint-up(xl){}
i know that if i use the css width media query i can couple it with the orientation media query, but i want to use the sass framework.
I want to add the orientation media query in on of them, the XS one. thus it is specific. Because as you know bootsrap 4 is not supporting orientation query for now (strangely).
i tried to concatenat the "orientation query" with the "SASS bootstrap media query (xs)" in different way but i always have a sass error.
Thus What i did is to nest it in the SASS bootstrap media query (xs):
#include media-breakpoint-up(xs){
... some SCSS rules
#media (orientation: landscape){
header{
display:none !important;
}
.navbar{
display:none !important;
}
}
}
The problem i have even tought it is nested into the XS query is that it apply to all breakpoint. it s like it does nt take into account to be nested.
My question: how to concatenate the "orientation query" with the "SASS bootstrap media query (xs)"? Or how to make it specific to the XS breakpoint by nesting it.
Thank you
I've found the solution.
It's possible to combine sass mixin by nesting them, thus I've created the following mixin in my _mixins.scss file:
#mixin orientation($direction) {
$orientation-landscape: "(orientation:landscape)";
$orientation-portrait: "(orientation:portrait)";
#if $direction == landscape {
#media #{$orientation-landscape} { #content; }
}
#if $direction == portrait {
#media #{$orientation-portrait} { #content; }
}
}
Note: i didn't put the "and" in the variable value: "and (orientation:landscape)". SASS or bootstrap put it automatically i suppose.
Then in my SCCS file I've added the following rules:
#include media-breakpoint-down(sm) {
#include orientation(landscape) {
.path-frontpage header {
display: none !important;
}
.path-frontpage .navbar {
display: none !important;
}
}
}
Note: in my first post i was saying that the CSS rules I've nested was applied to all breakpoints, it s because when the CSS is generated the SASS Bootstrap 4 XS breakpoint of is not written, i suppose it's because the value is 0. thus the orientation media query was not combines with a min-width value. So i changed the value to a max-width instead of a min-width, as the Bootstrap 4 SM breakpoint have the 576px value.
The result in the CSS file is what i wanted:
#media (max-width: 767.98px) and (orientation: landscape) {
.path-frontpage header {
display: none !important;
}
.path-frontpage .navbar {
display: none !important;
}
}
I hope it will help the community.
I use this outside of Bootstrap. You should be able to use it with Bootstrap or any other framework, giving you more flexibility in your media queries.
// Extra map functions by Hugo Giraudel
#function map-deep-get($map, $keys...) {
#each $key in $keys {
$map: map-get($map, $key);
}
#return $map;
}
#function map-has-keys($map, $keys...) {
#each $key in $keys {
#if not map-has-key($map, $key) {
#return false;
}
}
#return true;
}
#function map-has-nested-keys($map, $keys...) {
#each $key in $keys {
#if not map-has-key($map, $key) {
#return false;
}
$map: map-get($map, $key);
}
#return true;
}
These are extra map functions Hugo Giraudel wrote up. map-deep-get is basically a simplified nested map-get function. map-has-keys is just like map-has-key, which is built-in to sass, but checks for multiple keys. map-has-nested-keys expands on that by checking for nested keys. This is crucial for this method. I'd definitely look into the extra Sass functions he's built. I've quite easily found use for just about all of them.
// Map
$sizes: (
null: (
breakpoint: 0,
container: 100%
),
xs: (
breakpoint: 480px,
container: 464px
),
sm: (
breakpoint: 768px,
container: 750px
),
md: (
breakpoint: 992px,
container: 970px
),
lg: (
breakpoint: 1200px,
container: 1170px
)
);
This is a simple breakpoint map. I usually use this as a base map for all settings on my projects, so I'll include things like base font-sizes and whatnot in it.
// Breakpoint mixin
#mixin break($screen-min: null, $screen-max: null, $orientation: null) {
$min: $screen-min;
$max: $screen-max;
$o: $orientation;
$query: unquote("only screen");
#if $min != null and $min != "" {
#if map-has-nested-keys($base, sizes, $screen-min) {
$min: map-deep-get($base, sizes, $screen-min, breakpoint);
}
#else {
$min: $screen-min;
}
#if is-number($min) {
$query: append($query, unquote("and (min-width: #{$min})"));
}
}
#if $max != null and $max != "" {
#if map-has-nested-keys($base, sizes, $screen-max) {
$max: map-deep-get($base, sizes, $screen-max, breakpoint);
}
#else {
$max: $screen-max;
}
#if is-number($max) {
$query: append($query, unquote("and (max-width: #{$max})"));
}
}
#if $orientation == landscape or $orientation == portrait {
$o: $orientation;
$query: append($query, unquote("and (orientation: #{$o})"));
}
#else {
$o: null;
}
#media #{$query} {
#content;
}
};
Here's the mixin. You can use the keys from the sizes map (xs, sm, md, lg) for the first two arguments, or you can use custom values (like 30em). The third argument accepts either landscape or portrait. You could even customize the mixin the make l = landscape and p = portrait if you wanted.
Additionally, if you only wanted, for example, an orientation, you could pass the arguments (null, null, landscape).
For clarity, here's some examples:
#include break(null, md, landscape) {
...
}
#include break(null, null, landscape) {
...
}
#include break(md) {
...
}
#include break(null, md) {
...
}
#include break(480px) {
...
}
Output:
#media only screen and (max-width: 992px) and (orientation: landscape) {
...
}
#media only screen and (orientation: landscape) {
...
}
#media only screen and (min-width: 992px) {
...
}
#media only screen and (max-width: 992px) {
...
}
#media only screen and (min-width: 480px) {
...
}

Variable values based on class

Why can't I change a scss variable based on a class? I want the variable to be green when in class dark-mode.
Css:
$test: red;
#mixin darkTheme {
$test: green;
}
.theme-dark {
#include darkTheme();
.test {
background-color: $test;
}
}
Html:
<body class="dark-mode">
<div class="test">test</div>
</body>
How do I accomplish this? What I don't want is 2 variables.
This is because of Variable Scoping.
In Sass, all variables declared outside of a mixin or function will have a global scope and can be referenced in any Sass selector that uses the variable. (source)
This means that any variable value set inside of a mixin or function is only available within that mixin or function, even if the variable was previously set globally.
Switching between different sets of Sass variables
Partials
You could have a partial file for each theme, and import those under each theme's parent class.
_theme-dark.scss
$background-color: #000;
$text-color: #fff;
_theme-light.scss
$background-color: #fff;
$text-color: #000;
_themed-page.scss
body {
background: $background-color;
color: $text-color;
}
theme-styles.scss
.theme-dark {
#import "theme-dark";
#import "themed-page";
}
.theme-light {
#import "theme-light";
#import "themed-page";
}
Maps
Another option is to store the theme values in a map and have a utility function to retrieve the desired value. (source)
_theme-variables.scss
$theme: 'default';
$theme-values: (
'background-color': (
'default': #eee,
'light': #fff,
'dark': #000
),
'text-color': (
'default': #333,
'light': #000,
'dark': #fff
)
);
#function theme-value($key) {
$map: map-get($theme-values, $key);
#return map-get($map, $theme);
}
_themed-page.scss
body {
background: theme-value('background-color');
color: theme-value('text-color');
}
theme-styles.scss
.theme-dark {
$theme: 'dark';
#import "themed-page";
}
.theme-light {
$theme: 'light';
#import "themed-page";
}

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

What is the SASS equivalent of additive mixin definitions in LESS?

Is there a way to have SASS emulate the way LESS concatenates repeated mixin definitions (redefining a mixin in LESS doesn't overwrite the original).
For instance, would it be possible to push a block of CSS rules into a buffer, and then flush them all at once?
Example:
With LESS I'd do this:
// _menu.less
#_base () { .menu{ /*styles*/ } }
#_mobile () { .menu{ /*styles*/ } }
#_desktop () { .menu{ /*styles*/ } }
...
// _widget.less
#_base () { .widget{ /*styles*/ } }
#_mobile () { .widget{ /*styles*/ } }
#_desktop () { .widget{ /*styles*/ } }
...
and then:
// styles.less
#import "_menu.less";
#import "_widget.less";
#media screen { #_base(); }
#media screen and (max-width:700px) { #_mobile(); }
#media screen and (min-width:701px) { #_desktop(); }
...
// styles-oldie.less
#import "_menu.less";
#import "_widget.less";
#media screen {
#_base();
#_desktop();
}
To the best of my knowledge, there is no way to replicate what you want to achieve by building on to existing mixins. If you define a mixin two times, the second will overwrite the first. See example
AFAIK the common practice in Sass is to use media query mixins inside the selector to keep the code clean and readable. Breakpoint is a popular library that adds a lot of nice functionality for doing this.
An example of the code would be.
#import "breakpoint";
$medium: 600px;
$large: 1000px;
$breakpoint-no-queries: false; // Set to true to ignore media query output
$breakpoint-no-query-fallbacks: true; // Set to true to output no-query fallbacks
$breakpoint-to-ems: true; // Change px to ems in media-queries
.menu {
content: "base";
// Mobile styles
#include breakpoint(min-width $medium - 1) {
content: "mobile";
}
// Tablet styles
#include breakpoint($medium $large) {
content: "tablet";
}
// Desktop styles with no-query fallback
#include breakpoint($large, $no-query: true) {
content: "large";
}
}
This could output (depending on your settings)
.menu {
content: "base";
content: "large";
}
#media (min-width: 37.4375em) {
.menu {
content: "mobile";
}
}
#media (min-width: 37.5em) and (max-width: 62.5em) {
.menu {
content: "tablet";
}
}
#media (min-width: 62.5em) {
.menu {
content: "large";
}
}
You can play around with the settings here
I often have a stylesheet for modern browsers that support media queries set up like this:
// main.scss
$breakpoint-no-queries: false;
$breakpoint-no-query-fallbacks: false;
#import "imports";
And another stylesheet for older browsers that don't support media queries
// no-mq.scss
$breakpoint-no-queries: true;
$breakpoint-no-query-fallbacks: true;
#import "imports";

Resources