SASS: Proper way to target descendants based on page class - sass

This one got me stuck.
I want to add a background image to #page-title based on the body class.
I was sass to compile to:
body.front #page-title {
background-image: url('front.png');
}
body.blog #page-title {
background-image: url('blog.png');
}
Obviously the real code is more complex (and the actual will contain background-position and not a distinct image) but you get the idea. Each of the elements in the tree already has significant styling.
What is the best way to make this happen?
Thanks much!
EDIT: Well, I found a solution which works:
body {
/* body attributes */
#page-title {
/* #page-title attributes */
}
&.front #page-title {
/* body.front #page-title attributes */
}
&.blog #page-title {
/* body.blog #page-title attributes */
}
}
This renders properly, but reentering #page-title after each page class just feels wrong. Somehow I wonder if I'm not yielding to the sass way, but until I have a better solution (#import?), this works.
Thanks!

I would simplify the CSS before writing the SASS.
Assuming .blog and .front are the only classes you are using for body, then you have one too many classes. Why not set a default class (.blog) and then add a selector for .front?
body #page-title {
background-image: url('blog.png');
}
body.front #page-title {
background-image: url('front.png');
}
Alternatively, you could set the default value and then add the class to the #page-title element.
#page-title {
background-image: url('blog.png');
}
#page-title.front {
background-image: url('front.png');
}

Related

SASS/SCSS, how to access a property/method in a dynamic way from a partial file?

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

How to retain advanced SASS nesting option when using correct BEM methodology

This had been discussed multiple times, but I haven't seen a proper answer yet. I want to use BEM methodology in my code, but I want to preserve advanced SASS nesting options for better code readability.
Here's an example of my code:
<div class="education">
<h3 class="education__heading">Heading</h3>
<div class="education__items">
<div class="education__item">
<h4 class="education__faculty">Lorem ipsum</h4>
<span class="education__subject">Dolor sit amet</div>
</div>
</div>
</div>
This should be the correct BEM model with 3 level nesting. Here's how the SASS code for this model looks like:
.education {
&__heading {
}
&__items {
}
&__item {
}
&__faculty {
}
&__subject {
}
}
As far as I know, this is the correct approach, but I miss the option to nest classes in my SASS code (see simplified example below, without the BEM class names):
.education {
.heading {
}
.items {
.item {
.faculty {
}
.subject {
}
}
}
}
This structure basically copies the HTML structure and I believe it is very easy to maintain and read. Is it possible to retain multi-level nesting in SASS when using proper BEM methodology?
The only solution I know to do this is made of three tricks:
Use the #at-root rule, it will move a declaration to the top, no matter how deep the nesting goes
Save the block selector in a variable, using & as the value of the assignation
Use #{} to insert a variable inside a selector
In your case, it will look like this:
.education {
// Save `.education` in the $block variable
$block: &;
#at-root #{$block}__heading {
/* Heading styles */
}
#at-root #{$block}__items {
/* Items styles */
#at-root #{$block}__item {
/* Item styles */
#at-root #{$block}__faculty {
/* Faculty styles */
}
#at-root #{$block}__subject {
/* Subject styles */
}
}
}
}
And will compile to:
.education__heading {
/* Heading styles */
}
.education__items {
/* Items styles */
}
.education__item {
/* Item styles */
}
.education__faculty {
/* Faculty styles */
}
.education__subject {
/* Subject styles */
}

Extending a placeholder and a regular class

I'm having a little issue with SASS #extend, placeholder class and interpolation.
I'm trying to keep the HTML as clean as possible and that's I decided to go for the #extend function in pair with placeholder classes. However, I'm mainly extending layout-related classes like grid, list etc - that's why I'm mixing a placeholder with a regular class in the declaration, i.e:
%drawer,
.drawer {
...
}
Everything was going just fine except for a moment when I noticed the interpolation with the variable being the ampersand in the main class causes some issues. Sample code (with most of the CSS rules removed):
%drawer,
.drawer {
$this: &;
position: fixed;
z-index: 10;
&__content {
right: 0;
transform: translate(100%, 0);
}
&__optional-element {
background: red;
}
&--left { // I want this modifier to be applied to the parent element as it may affect more than one children element
#{$this}__content {
left: 0;
transform: translate(-100%, 0);
}
}
}
And the extension code:
.product-drawer {
#extend %drawer;
&__content {
#extend %drawer__content;
}
}
However, the compiled CSS output is the following:
.drawer--left .product-drawer,
.drawer--left .drawer__content {
left: 0;
transform: translate(-100%, 0);
}
You may notice the first line is redundant and actually wrong. In addition, the "&__optional-element" bit is not outputted for the "product-drawer" extension which makes it really strange. It happens only to rules with the $this interpolation.
As soon as I remove the regular ".drawer" class from the original declaration (and just leave %drawer there), the problem is gone but in these layout-related classes (.grid, .list), we want to keep the regular class name as well so in some various, simple cases it can be used as well, without a need to write new CSS and extending it the placeholder class.
I know that this could be resolved by separating the placeholder class (%drawer) from the regular one (.drawer) completely and then extend the placeholder class inside the regular ".drawer" declaration but that would simply duplicate the code... Or maybe my approach is wrong by design?
Thank you!
The problem is not the #extend rule. The thing is that placeholders are a shallow copy of a class. If you extend from a class you are going to inherit all its properties, but if you extend from a placeholder it is only going to copy the first level.
See this example:
%placeholder{
content: 'placeholder';
&__element{
content: 'placeholder__element';
}
}
.a{
#extend %placeholder;
}
.class{
content: 'class';
&__element{
content: 'class__element';
}
}
.b{
#extend .class;
}
.a {
content: 'placeholder';
}
.class, .b {
content: 'class';
}
.class__element {
content: 'class__element';
}
By using both you're forcing placeholder class to use also the properties:
%placeholder,
.class{
content: 'class';
&__element{
content: 'class__element';
}
}
.b{
#extend %placeholder;
}
.b,
.class {
content: 'class';
}
.class__element {
content: 'class__element';
}

Ampersand and mixins in SCSS

Searched but can't find an answer..
I have an element which gets generated (by an external platform) with the following classes: p-button and button.
Now the SCSS is like this:
.p-button {
&.button {
margin: 10px;
}
}
But I want to refactor using mixin includes (this is a big project so there is no other way of making this code better except using mixins). The mixin takes the given selector and applies a . to it. I can't change the mixin, as it is used by many other teams, so I can't pass the ampersand together with the selector. I tried this:
.p-button {
& {
#include button-appearance("button") {
margin: 10px;
}
}
}
But that doesn't work (puts a space between it). You can't do this:
.p-button {
&#include button-appearance("button") {
margin: 10px;
}
}
Anyone have a clue?
EDIT: Here is the mixin
#mixin button-appearance(
$appearance-class,
$show,
$background-color,
$background-image,
$background-position) {
$sel: $button-selector;
#if $appearance-class {
$sel: $sel + '.' + $appearance-class;
}
#{$sel} {
#include normalized-background-image($background-image);
#include show($show);
background-color: $background-color;
background-position: $background-position;
}
#content;
}
EDIT 2: Here is the $button-selector (I can not edit this in the platform, but maybe overwrite it in my own project?)
$button-class: 'p-button';
$button-selector: '.#{$button-class}';
Everyone, finally found the solution. I just removed the &.button from the .p-button mixin include and now it works:
#include button-appearance ("button") { *styles* }
#include button-appearance () { *styles* }
Edited the answer after the original question was edited adding the used and un modifiable mixin
The original mixin does not append the ‘#content’ passed to the mixin to the generated selector. So if you cannot modify the original mixin, the only way is to add your properties outside the mixin. According to the mixin the selector will match a predefined ‘$button-selector’ variable, so it won’t use your class.
So, if you want to use the same class defined in ‘$button-class’, try the following:
#{$button-selector}.button {
margin: 10px;
}
Will output:
.p-button.button {
margin: 10px;
}

CSS Modules, Composition in nested class

I am working on a project where there is a main style.scss file for a number of components. I want to restructure the code so as every component has its own folder (index.js, styles.scss). There is a nested class that is using a class from another component and now that I have to separate all the styles, this part is broken. I can't use composition since it is a nested class. What other approaches can I take?
The code looks like this:
// Component A styless.scss
.component-a-class {
}
// Component B styless.scss
.component-b-class{
.component-a-class {
}
}
Use Sass's #import directive to import the external classes. Your code would become something like this:
// ComponentA/styless.scss
.component-a-class {
...
}
// ComponentB/styless.scss
.component-b-class{
#import "../ComponentA/styless.scss"
}
This will inject .component-a-class into the .component-b-class as a nested rule.
Edit: To import a style and also be able to modify one of its properties' values, you have to make use of Sass mixins:
// ComponentA/styless.scss
#mixin component-a-class($width: 100) {
.component-a-class {
width: $width + px;
}
}
#include component-a-class();
// ComponentB/styless.scss
#import "../ComponentA/style.scss";
.component-b-class{
#include component-a-class(500);
}
This will get you what you want, though it isn't ideal. The resulting compiled ComponentB/styless.css file will include everything written in ComponentA/styless.scss because the #import directive is an "all-or-nothing" feature (there is no selective import). The result would look like this:
// ComponentB/styless.css (compiled)
.component-a-class {
width: 100px;
}
.component-b-class .component-a-class {
width: 500px;
}

Resources