Is it possible to create functions (or something else) that include selectors with SASS? - sass

I want to be able to write:
div {
background-size: 100%;
#bgimgfunction('img1.png');
}
and have SASS produce something like:
div {
background-size: 100%;
/* Generated by the call to #bgimgfunction */
background-image:('/img/img1-medium.png');
#media (max-width: 640px) {
background-image:('/img/img1-low.png');
}
#media (min-width: 1600px) {
background-image:('/img/img1-high.png');
}
/* End generated by the call to #bgimgfunction */
}
mixins I think don't work because I can't pass a parameter
functions I think don't work because they are only valid after a selector.
Is there a way to do this?

What you need is a #mixin which does take parameters. Given the structure of your image URLs, I think you need two arguments, one for the image name and one for its extension:
#mixin bgImageFunction($imageName, $imageExt) {
$path: '/img/' + $imageName;
background-image: url("#{$path}-medium.#{$imageExt}");
#media (max-width: 640px) {
background-image: url("#{$path}-low.#{$imageExt}");
}
#media (min-width: 1600px) {
background-image: url("#{$path}-high.#{$imageExt}");
}
}
div {
background-size: 100%;
#include bgImageFunction('img1', 'png');
}
You can also use a default parameter for the extension and only pass the name as argument:
#mixin bgImageFunction($imageName, $imageExt: 'png') {
...
}

Related

check if the parent has a specific class on it

This is the usual list where one of the items is .open
for this I want to check if the parent (in this case is .item but its not relevant i think) has a specific class.
I've tried > but it doesnt seem to work.
Essentially how to put this:
&.open .info {
display: none;
}
&.open .inner-info {
display: flex;
}
inside of the their specific classes:
.info {
display: flex;
/* some other stuff */
}
.inner-info {
display: none;
/* some other stuff */
}
all of this is inside an .item{} block
So how do i have it so that i only have two blocks inside the .item{}?
It seems overkill to me, but you can use a hacky way to do that using a mixin and various functions. Please note that this will work for your specific example but probably not for something else.
I used the helper functions str-to-list and nth-delete, which are not native to SASS.
#mixin parentWithClass($class) {
$parent: nth-delete(str-to-list(#{&}), -1);
#at-root #{selector.replace(&, $parent, #{$parent}#{$class})} {
#content;
}
}
.item {
.inner {
color: blue;
#include parentWithClass(".open") {
color: orange;
}
}
.inner-info {
color: red;
#include parentWithClass(".open") {
color: grey;
}
}
}
You can also nest -info in inner.

sass merge selectors on ampersand

assuming I have an existing SASS rule like:
[dir] .foo {
background-image: ...;
// ... some more
}
And I want to add specific behaviors for ltr / rtl like:
[dir] .foo {
background-image: ...;
[dir='ltr'] & {
padding-right: ...;
}
[dir='rtl'] & {
padding-left: ...;
}
}
this would generate undesired css like:
[dir='rtl'] [dir] .foo {
padding-left: ...;
}
This will not match what I want.
Assuming I cannot change the parent selectors (due to specificity), is there any way I can write such nested selectors in a way that compiles to just [dir='rtl'] .foo {...} for the nested elements?
Some resources about the ampersand: https://css-tricks.com/the-sass-ampersand/#aa-qualifying-based-on-context
There is no way that I know to merge selectors as requested.
As you're not allowed to change the parent selector, the only solution I see would be to use the #at-root rule.
#at-root
The #at-root rule is usually written #at-root { ... } and causes everything within it to be emitted at the root of the document instead of using the normal nesting. It's most often used when doing advanced nesting with the SassScript parent selector and selector functions.
Definition on sass-lang.
Here is an example:
[dir] .foo {
$root: '.foo';
background-image: linear-gradient(black, white);
#at-root {
[dir=ltr] #{$root} {
padding-right: 1em;
}
[dir=rtl] #{$root} {
padding-left: 1em;
}
}
}
This will compile to:
[dir] .foo {
background-image: linear-gradient(black, white);
}
[dir=ltr] .foo {
padding-right: 1em;
}
[dir=rtl] .foo {
padding-right: 1em;
}
You could create a mixin to help you with that:
#mixin dir($dir: ltr, $selector: &) {
#at-root {
[dir=#{$dir}] #{$selector} {
#content;
}
}
}
[dir] .foo {
$root: '.foo';
background-image: linear-gradient(black, white);
#include dir(ltr, $root) {
padding-right: 1em;
}
#include dir(rtl, $root) {
padding-right: 1em;
}
}
Food for thougt
If you don't have to support internet explorer, you might want to check padding-inline-end and padding-inline-start properties.
They will free you from the need to have different rules for different directions.
padding-inline-end
The padding-inline-end CSS property defines the logical inline end padding of an element, which maps to a physical padding depending on the element's writing mode, directionality, and text orientation.
MDN Docs - padding-inline-end
padding-inline-start
The padding-inline-start CSS property defines the logical inline start padding of an element, which maps to a physical padding depending on the element's writing mode, directionality, and text orientation.
MDN Docs - padding-inline-start

SCSS Placeholder and Mixin to generate

I want to generate multiple CSS-Files
class.css
Should contain from an configuration file (tailwindcss-like) generated utility-first CSS-Classes
.block {display:block; }
.text-center { text-align:center; }
#media (min-width: 768px) {
.md:text-center {
text-align:center;
}
}
components.css
Should contain component-based CSS-Classes
// Input
.avatar { #extend block, background($blue)}
// Output
.avatar { display: block; background-color: blue;}
Question
Is there a way to generate these separated files? I have no idea!
My way to first generate #mixins (or %placeholders, I tried both ways) from a config-file (colors, spacing,...) and afterwards in an second step, building my components.css from these #mixins is not very maintainable

Setting value of a global variable in media query (revisited)

Based on this reply, I wrote:
$global-padding: 1em;
#media only screen and (max-width: 20em) {
$global-padding: 0.5em !global;
}
For some reason, the padding is always 0.5em, not only when the screen is at most 20em wide. What am I missing?
As far as I understand the documentation the global variable gets overwritten during compile time, as soon as the nested rule is processed - and not if any condition is met. This is not a runtime feature. All SCSS is compile to plain old CSS during compile time.
A variable declaration flagged as !global will always assign to the global scope.
So doing something like this:
$variable: foo;
.content {
$variable: bar !global;
value: $variable;
}
.sidebar {
value: $variable;
}
will produce
.content {
value: bar;
}
.sidebar {
value: bar;
}
What you are trying to do can be done with css custom variables:
:root {
--global-padding: 1em;
#media only screen and (max-width: 20em) {
--global-padding: 0.5em;
}
}

SCSS How to change order of rendering included mixins

This is a hard question, so I am aware that no one may come up with solution, but that's the problem I really need to solve in my framework.
I have a screen() mixin written in SCSS, which takes $size as an argument, to return any #content wrapped in a media query.
The problem occurs when one element #includes multiple screen() mixins, because resulting media queries will overwrite each other in the same order as they were included. How can I make sure the resulting media queries will be rendered in the correct order (biggest screen to smallest), even if I forget to include them in the right order?
http://sassmeister.com/gist/951520fa83d1e1c69c9d
#mixin screen(
$size: null
){
#if $size == md {
#media (max-width: 1024px) {
#content;
}
}
#if $size == sm {
#media (max-width: 768px) {
#content;
}
}
#if $size == xs {
#media (max-width: 320px) {
#content;
}
}
}
/* output should be 1024, 768, 320 */
.screen {
&:before {
// this should be included as the Last one
#include screen(xs){
content: "xs";
}
#include screen(sm){
content: "sm";
}
// this should be included as the First one
#include screen(md){
content: "md";
}
}
}
I tried to solve that issue by creating placeholder selectors in the right order %media-sm{...}, %media-xs {...}..., and #extend them from the mixin, but #content can't be passed through the #extend directive.
Another solution is a hard one - create an array of keys - sizes, and values - #contents and render them from another function.
No. Sass only does exactly what you tell it to do. If you want your styles to appear in a specific order, write them in that specific order.
Might be easier to pass in the media width you are trying to target:
#mixin media($width) {
#media only screen and (max-width: $width) {
#content;
}
}
#include media(320px) {
background: red;
}

Resources