VueJS & Vuetify - How to use JS variables in Sass - vue.js

Using VueJS, I need to display different colors for each user. The color depends on the user settings.
In my vuetify.js, I have:
export default new Vuetify({
theme: {
themes: {
light: {
primary: user.colorMain ? user.colorMain : '#F39200',
It works when I use:
$vuetify.theme.themes.light.primary
in my components.
But I would need to override the Sass variables too, in my variable.scss file:
$primary-color: #f39200;
Is there a way to override my sass variables dynamically from a JS variable?

tl:dr; no, it's not possible to change Sass variable values at runtime, because they no longer exist at runtime. They have been translated into plain (static) CSS.
However, like with any CSS values, you can override them.
Sass variables are only used to pre-process SCSS into CSS at compile time. The result of compilation is static CSS, loaded when the app is mounted. In simpler terms, the app doesn't know that CSS was preprocessed from an SCSS source. For it, it's static CSS.
Example:
$primary-color: #f39200;
.my-button { color: $primary-color; }
will output the following CSS code:
.my-button { color: #f39200; }
If you want runtime dynamic values, you have two options:
Use CSS variables.
Produce the following CSS, via your preferred method (from SCSS/CSS/Stylus, doesn't matter, as long as this is the output):
.my-button { color: var(--primary-color); }
... and, anywhere in the chain of parents or on the element itself:
<div :style="{'--primary-color': someDynamicColor }" />
With the above in place, when you change someDynamicColor, at runtime, the color changes in DOM.
Use Vue3's "reactive styles" feature:
<script>
export default {
data: () => ({ someDynamicColor: 'red' })
}
</script>
<style>
.my-button {
color: v-bind('someDynamicColor');
}
</style>
Again, this is dynamic. If you change/animate the value of someDynamicColor on the element, the CSS value will be applied in DOM. It doesn't have to be a data prop, it can be a prop, computed, ...
Important notes:
when using CSS variables (1.), the value of var(--primary-color) doesn't have to be set in the same component, but it has to be set on a direct ancestor of the current DOM element.
when using reactive styles (2.), the prop/computed referenced in CSS/SCSS has to be set in the current component's scope.
Under the hood, reactive styles also use CSS variables: they're uniquely named at compile time.
CSS variables don't use specificity. If you override the value set by some grand-parent at parent level, the child has no way of reading the grand-parent's value, regardless of specificity. If you have such a case, you probably want to manage the grandparent value in external state and provide it to both grand-parent and child.

Related

How to access only Vue.js components elements using a global mixin?

Trying to create a plugin with global mixin which would automatically look for specific element and change its attributes.
export default {
// called by Vue.use(ThisPlugin)
install(Vue, options) {
Vue.mixin({
created() {
console.log($("div").length); // get rid of jQuery and global content
},
});
},
};
As this is called on every vue component I want to limit content mixin accesses with similar like el parameter in directives or like components have element querySelector (this.$el.querySelector("div")) and to replace jquery usage. Is my approach correct and how would I access only components contents in a mixin?
Want to skip directives as those would need to modify tons of existing components, rather introduce a plugin for a component.

How do I use SCSS variables with data-attributes in Vue JS and Bootstrap Vue

I'm trying to have a Dark Theme button on my application and change the whole theme in a click. It is already working but I wanted to find an easier way to accomplish that.
I have created a button in the navbar that sets a localStorage variable to "dark" or "light" on click. Upon loading the application, its store will read the localStorage and have it available to the whole application.
Excerpt from store.js:
state {
...
theme: localStorage.getItem('theme') || 'light',
...
}
In my application, if I want to change the theme to a breadcrumb, I would do:
<b-breadcrumb :data-theme="theme">
<b-breadcrumb-item active>Start</b-breadcrumb-item>
</b-breadcrumb>
import {mapState} from 'vuex'
export default {
...
computed: {
...mapState(['theme'])
}
}
and in the custom.scss file:
[data-theme="dark"] {
$breadcrumb-bg: $dark !important;
}
And it would have changed the whole component color.
This does NOT work.
However, this DOES:
.breadcrumb[data-theme="dark"] {
background-color: $dark !important;
}
My question is: Is there an easy way to change all components using data attributes and SCSS variables or do I have to mannually select classes and change the components I want?
Unfortunately it is not possible to use Sass variables in custom data attributes as I wanted because the specification for data attributes won't allow them. They expect a DOMString name, not a Sass variable.
https://html.spec.whatwg.org/multipage/dom.html#custom-data-attribute

Vue3 scoped classes are not applied for elements not declared in the template

I have a single-file vue3 component.
Its template looks like this.
<template>
<div ref="elements"></div>
</template>
I have scoped styles in the same file:
<style scoped>
.el {
color: red;
}
</style>
Now I want to add element in the script.
<script>
export default {
name: "App",
mounted() {
const div = this.$refs.elements;
const el = document.createElement("div");
el.setAttribute("class", "el");
el.innerText = "Hello World!";
div.appendChild(el);
},
};
</script>
The result shows that the element is not styled according to the class in the scoped styles.
Is there a way to apply styling to the elements added through script, while keeping styles in the scope?
After some research this seems to be an answer.
Vue scoped styling is the internal Vue feature. So, there should be a way to distinguish same class names on different components. Vue does it by adding a special id to each class name. You can find it by inspecting elements in the browser (e.g. data-v-a2c3afe).
When building a component, Vue processes its template and properly tracks only nodes you have declared for rendering there. It does not know anything it might get from somewhere. It actually makes sense and pushes you to write everything you expect to see in the template, so that it does not happen that you suddenly see something wrong in the DOM (especially if you are taking someone's code).
I have rewritten code, so that I still have scoped styles, but with no elements appending from the script and the code now allows to clearly see what is being rendered. This is probably the property of any framework - it makes you to stick to some patterns, so that everyone in the team/community knows what behavior to expect and writes more consistent code.

Preventing parent site styling affecting Vue JS component styling

I'm building a Vue JS plguin with some scoped styling, it works perfectly fine within another website when including it as a component, e.g: <my-component></my-component>.
However, the styling that I've added to the component is scoped to the component which means it doesn't affect the parent's site styling, however the parent's site styling DOES affect my component's styling, is there a way to prevent this without being super specific with my component's styling and using !important for everything?
Scoped styles work by adding a special key to your selectors. So your component would not affect other components. Parent components still may affect your component styling. And global styles also can intervene.
Well, you have 2 options if you don't want to use !important
First, and preferred - use specific BEM naming.
It is really easy to implement by using sass (scss).
e.g.
.mycompoment{
background:#fff;
&--body{
color:#eb0b0;
&--title{
font-size:5rem;
}
}
&--footer{
position:relative;
}
//etc
}
which would compile to the following css:
.mycompoment {
background: #fff;
}
.mycompoment--body {
color: #eb0b0;
}
.mycompoment--body--title {
font-size: 5rem;
}
.mycompoment--footer {
position: relative;
}
The other option is to increase your selector's specificity.
Try to use more direct descendant selectors. >
But still, outer css may still affect the values you don't even think of specifying.
For example position:absolute; top:-20px; or box-sizing/display/opacity and many different props your aren't aware of.

initialize sass variable with property defined in vue as computed

I'm working on a component in VueJS which will present a bar chart. For that i need to calculate a number and that's why it's computed.
in my sass code i have a varaible - $totalRows: that i want to initialize with the computed property value:
sass:
<style lang="scss" scoped>
//$totalRows: $someNum;
</style>
computed property:
computed: {
someNum(){
return 10 //for example purposes
}
}
any idea how can i init the $totalRows to be equal to the value returned from someNum
Your scss styling is turned into css when you compile for production. Since computed properties are evaluated at runtime, it is not possible to have a computed variable as a sass variable.
You can use inline styling to set styling based on your computed property. Since your component is rerendered whenever the data it relies on changes, you can set things like a column width based on that.
<div :style="styling"></div>
computed: {
someNum() {
return 10;
},
styling() {
const fullWidth = 1000; //px
return {
width: fullWidth / this.someNum;
}
}
}
If you do not need to calculate a variable, you can share it between scss and javascript using json and a scss json importer.