I'm working with a fluent UI react command bar. I'm using the SCSS themify pattern to theme elements.
Theming works for the top level command bar class names, but certain themify blocks seem to be skipped entirely.
For example, the block shown below never executes so the icon path will have red fill.
.chartCommandIcon {
path{
fill: red;
#include themify{
fill: themed('color-theme-accent');
}
}
}
This is another example. The themify block works on the top level commandItem styles, but not on the dropdownMenu (a fluent UI subMenu). The .dropdownMenu selector does work however. If I set the dropdown menu background-color: red outside of the themify block, the color is updated.
.commandItem{
#include themify{
color: themed('color-text-rest'); // works here (top level?)
}
}
.dropdownMenu{
background-color: red; // works here
#include themify{
background-color: themed('color-bg-panel-contextual'); // not here
}
}
Any ideas why this may be happening would be super helpful! Thanks : )
Here is the themify SCSS mixin code taken from an external package.
/// Creates theme variations
///
/// #param {Map} $themes - Theme map to loop through. Optional.
#mixin themify($themes: $themes) {
// for each theme, get its name and color variable map:
#each $theme, $colors in $themes {
// re-export the color map under the global scope so the
// `themed` function below can access it inside the content:
$theme-map: $colors !global;
:global(.#{$default-prefix}-#{$theme}) & {
#content;
}
// reset the theme-map global variable:
$theme-map: null !global;
}
}
/// Gets a value from the color map.
///
/// #param {String} $key - Name of the color variable
/// #param {Map} $theme-map - Theme map to use. Optional.
///
/// #returns {String} The color for the given key
#function themed($key, $theme-map: $theme-map) {
$value: map-get($theme-map, $key);
#if not $value {
#error 'There is no `#{$key}` in your theme colors.';
}
#return $value;
}
Related
Let's say for instance we have the next sass partial file:
//_colors.scss
$foo: red;
And we "use" it on another file:
//test.scss
#use './colors'
.test{
color: colors.$foo;
}
All good, but what if I would like to use/get the value in a dynamic way within a mixin? something like:
//test.scss
#use './colors'
#mixin getColor($type){
color: colors[$type]; //JavaScript example, * don't actually work *.
or
color: #{colors.{$type}; * don't work neither *
//The above returns `color: colors.foo` instead of `color: red` on compilation.
or
color: colors.#{$type}; * doesn't work neither *
}
.test{
#include getColor(foo);
}
Is it possible? thanks for the help!
For a color, I really much prefer a function so it can be used on any property (color, background-color, border, box-shadow...)
I usually declare a string equivalent to variable names, then define them inside a map. Finally this map is accessible via a dedicated function.
Something like
//_colors.scss
#use 'sass:map';
$favoriteRed: "favoriteRed";
$favoriteYellow: "favoriteYellow";
$favoriteBlue: "favoriteBlue";
$MyColors: (
$favoriteRed: #c00,
favoriteYellow: #fc0,
$favoriteBlue: #0cf
);
#function my-color($tone: $favoriteRed) {
#if not map.has-key($MyColors, $tone) {
#error "unknown `#{$tone}` in MyColors.";
}
#else {
#return map.get($MyColors, $tone);
}
}
This _colors.scss generates no code at all, it can be imported anywhere at no cost.
Then, in a specific style file:
//test.scss
#use './colors' as *;
//inside a mixin
#mixin special-hue-component($tone){
div.foo {
span.bar {
border-color: my-color($tone);
}
}
}
//or directly
.foobartest {
color: my-color($favoriteBlue);
}
I have setup Bootstrap 4 theme variables as follows:
// custom-theme.scss
$primary: green;
$secondary: purple;
Then custom variables like:
// custom-variables.scss
#import "~bootstrap/scss/functions";
#import "~bootstrap/scss/variables";
$idi-primary-12: theme-color-level(primary, -12);
Then importing all as follows:
//main.scss,
#import "./bootstrap-theme";
#import "./custom-variables";
#import '~bootstrap/scss/bootstrap';
This updates bootstrap classes like btn-primary and text-secondary AS EXPECTED (nice);
But the custom variable ($idi-primary-12) based on my $primary doesn't work. I was using the theme-color-level SASS function as given here in the official documentation.
When I use this in my component,
// myComponent.scss
#import "../custom-variables";
.myUserInfo {
background-color: $idi-primary-12;
color: color-yiq($idi-primary-12);
}
I get BLUE shade (which is the default in the bootstrap/scss/variables.scss). Github source instead of my override (green - as set above)
Question: How do I use theme-color-level function, to use my $primary (green) variable to generate a lighter version of that green? (and not the default blue).
Additional info:
official documentation for SASS functions
theme-color-level uses theme-color
theme-color extracts from object $theme-colors by key (I am using primary)
$theme-colors primary key is set to $primary (Github for $theme-colors)
$primary is set to blue (Github for $primary)
This should have be overridden by my $primary = green; from custom-theme.scss. That is why btn-primary is working. (shown as green). But why isn't it using that same overridden variable to create my $idi-primary-12 variable?
AFAIK theme-color-level function in Bootstrap will take 2 parameters, color-name from the theme in string (eg, 'primary', 'info') and level in number as shown below:
// Request a theme color level
#function theme-color-level($color-name: "primary", $level: 0) {
$color: theme-color($color-name);
$color-base: if($level > 0, $black, $white);
$level: abs($level);
#return mix($color-base, $color, $level * $theme-color-interval);
}
and if we want to use this function for some colors other then the theme colors (eg. 'primary'), maybe we can write another function for that, eg:
#function custom-color-level($color: pink, $level: 0) {
$color-base: if($level > 0, $black, $white);
$level: abs($level);
#return mix($color-base, $color, $level * $theme-color-interval);
}
with this custom function, we can pass the first param as color (eg. #007bff, orange)
I think you have a typo, $primary instead of primary
$idi-primary-12: theme-color-level($primary, -12);
theme-color-level surprisingly is not internally using the overridden $primary value. Instead it is taking the default $primary value (blue).
I was able to use other function, which directly works on my overridden $primary value.
// custom.scss
$primary: green;
darken($primary, 10% )
lighten($primary, 10% )
Reference
I am trying make this mixing work.. Any ideas how to concancate a variable name on the fly and make it processed.
$colors: purple pink;
#each $color in $colors {
.box--#{$color} {
background-color: #{'$ui'}-$color;
}
}
In this case $ui-red is a red color variable.
Unfortunately, you can't generate or reference to sass single variables in runtime. But you can store your color codes and names in sass maps (requires sass v3.3) and use it in cycle like this:
$colors: ("purple": #f7f,
"pink": #ffa);
#each $color-name, $color-code in $colors {
.box--#{$color-name} {
background-color: $color-code;
}
}
In CSS you get:
.box--purple {
background-color: #f7f;
}
.box--pink {
background-color: #ffa;
}
Example: http://www.sassmeister.com/gist/c1285109946e5207e441c7ee589dd382
LESS has a great little feature called Space that allows mixins to append rules to existing properties. Really useful for transform() mixins, because you can append many transform rules to the same property, just by calling the mixin multiple times, eg.
Example:
.scale() {
transform+_: scale(2);
}
.rotate() {
transform+_: rotate(15deg);
}
.myclass {
.scale();
.rotate();
}
Outputs:
.myclass {
transform: scale(2) rotate(15deg);
}
I'm trying to get into SASS, but I don't understand how to achieve this with the available syntax. Whatever I do, the output only ever applies one of the transformations, not both. What is the best way to achieve this behaviour using SASS alone?
You can use variable arguments in a mixin like so:
#mixin transform($transforms...) {
transform: $transforms;
}
.myclass {
#include transform(scale(0.5) rotate(30deg));
}
this will output:
.myclass {
transform: scale(0.5) rotate(30deg);
}
You can see a working example here:
http://codepen.io/sonnyprince/pen/RaMzgb
A little more info:
Sometimes it makes sense for a mixin or function to take an unknown
number of arguments. For example, a mixin for creating box shadows
might take any number of shadows as arguments. For these situations,
Sass supports “variable arguments,” which are arguments at the end of
a mixin or function declaration that take all leftover arguments and
package them up as a list. These arguments look just like normal
arguments, but are followed by ....
http://sass-lang.com/documentation/file.SASS_REFERENCE.html#variable_arguments
Sass does not offer such a feature.
You can get reasonably close by using global variables. However, every single mixin you use, including ones provided by a 3rd party, will have to be modified to work this way.
// the setup
$append-property-vals: (); // global variable
$old-append-property-vals: (); // global variable
#mixin append-property($key, $val, $separator: comma) {
$old-val: map-get($append-property-vals, $key);
#if $old-val {
$append-property-vals: map-merge($append-property-vals, ($key: append($old-val, $val, $separator))) !global;
} #else {
$append-property-vals: map-merge($append-property-vals, ($key: $val)) !global;
}
}
#mixin append-properties {
// cache the original value
$old-append-property-vals: $append-property-vals !global;
#content;
// write out the saved up properties
#each $key, $val in $append-property-vals {
#{$key}: $val;
}
// restore the original value
$append-property-vals: $old-append-property-vals !global;
}
// modify the OP's provided mixins to work
#mixin scale {
// if the vals should be comma delimited, write `comma` instead of `space` for the 3rd argument
#include append-property(transform, scale(2), space);
}
#mixin rotate {
#include append-property(transform, rotate(15deg), space);
}
// call the mixins
.myclass {
#include append-properties {
#include scale;
#include rotate;
}
}
Output:
.myclass {
transform: scale(2) rotate(15deg);
}
I am creating css using SASS and would like to make it possible for another developer to create a custom css by changing sass variables. This works fine when I in my base file use a single variable like this:
$text-color: #000 !default;
To test the override I create a new project where I first declare an override for the variable and then import the "base" sass file.
$text-color: #0074b;
#import "base-file";
But I would also like to use maps for configuration but then I do not get the override to work. How should I use configuration maps that can be overriden?
$colors: (text-color: #000, icon-color: #ccc );
Adding !default after #000 gives me a compilation error: expected ")", was "!default,")
Adding !default after the ) gives no error but the variables does not get overwritten either.
Any ideas on what I am doing wrong?
I don't think the functionality you want exists in standard Sass. I built this function though that does what you're asking for:
//A function for filling in a map variable with default values
#function defaultTo($mapVariable: (), $defaultMap){
//if it's a map, treat each setting in the map seperately
#if (type-of($defaultMap) == 'map' ){
$finalParams: $mapVariable;
// We iterate over each property of the defaultMap
#each $key, $value in $defaultMap {
// If the variable map does not have the associative key
#if (not map-has-key($mapVariable, $key)) {
// add it to finalParams
$finalParams: map-merge($finalParams, ($key : $value));
}
}
#return $finalParams;
//Throw an error message if not a map
} #else {
#error 'The defaultTo function only works for Sass maps';
}
}
Usage:
$map: defaultTo($map, (
key1 : value1,
key2 : value2
));
Then if you have a mixin for something, you can do this sort of thing:
#mixin someMixin($settings: ()){
$settings: defaultTo($settings, (
background: white,
text: black
);
background: map-get($settings, background);
color: map-get($settings, text);
}
.element {
#include someMixin((text: blue));
}
Outputted CSS:
.element { background: white; color: blue; }
So you would use it like this based on what you said in the question:
$colors: defaultTo($colors, (
text-color: #000,
icon-color: #ccc,
));
Bootstrap has solved this issue as:
$grays: () !default;
// stylelint-disable-next-line scss/dollar-variable-default
$grays: map-merge(
(
"100": $gray-100,
"200": $gray-200,
"300": $gray-300,
"400": $gray-400,
"500": $gray-500,
"600": $gray-600,
"700": $gray-700,
"800": $gray-800,
"900": $gray-900
),
$grays
);
https://github.com/twbs/bootstrap/blob/v4.1.3/scss/_variables.scss#L23