Scenario
We followed this tutorial to provide our users a dark and a light theme.
Problem
The browser loads the expected css-File (dark or light). Nevertheless, the styles are not applied to our components.
This happens in <head></head>:
<link rel="stylesheet" type="text/css" href="theme-dark.css">
or
<link rel="stylesheet" type="text/css" href="theme-light.css">
As you can see, the browser correctly switches the two themes.
Code
angular.json
"styles": [
"projects/menu/src/styles.scss",
{
"input": "projects/menu/src/theming/theme-dark.scss",
"bundleName": "theme-dark",
"inject": false
},
{
"input": "projects/menu/src/theming/theme-light.scss",
"bundleName": "theme-light",
"inject": false
}
],
theme-service.ts
private _mainTheme$: BehaviorSubject<string> = new BehaviorSubject(
'theme-light'
);
private _darkMode$: BehaviorSubject<boolean> = new BehaviorSubject(false);
darkMode$: Observable<boolean> = this._darkMode$.asObservable();
private _renderer: Renderer2;
private head: HTMLElement;
private themeLinks: HTMLElement[] = [];
theme$: Observable<[string, boolean]>;
constructor(
rendererFactory: RendererFactory2,
#Inject(DOCUMENT) document: Document
) {
this.head = document.head;
this._renderer = rendererFactory.createRenderer(null, null);
this.theme$ = combineLatest([this._mainTheme$, this._darkMode$]);
this.theme$.subscribe(async ([mainTheme, darkMode]) => {
const cssExt = '.css';
const cssFilename = darkMode
? 'theme-dark' + cssExt
: mainTheme + cssExt;
await this.loadCss(cssFilename);
if (this.themeLinks.length == 2)
this._renderer.removeChild(this.head, this.themeLinks.shift());
});
}
setMainTheme(name: string) {
this._mainTheme$.next(name);
}
setDarkMode(value: boolean) {
this._darkMode$.next(value);
}
private async loadCss(filename: string) {
return new Promise((resolve) => {
const linkEl: HTMLElement = this._renderer.createElement('link');
this._renderer.setAttribute(linkEl, 'rel', 'stylesheet');
this._renderer.setAttribute(linkEl, 'type', 'text/css');
this._renderer.setAttribute(linkEl, 'href', filename);
this._renderer.setProperty(linkEl, 'onload', resolve);
this._renderer.appendChild(this.head, linkEl);
this.themeLinks = [...this.themeLinks, linkEl];
});
}
theme-base.scss
#import '~#angular/material/theming';
#include mat-core();
#mixin theming($theme) {
#include angular-material-theme($theme);
}
theme-dark.scss
#import '~#angular/material/theming';
#import './theme-base';
#include mat-core();
// color palette definitions
$primary: mat-palette($dark-md-mcgpaletteblack);
$accent: mat-palette($md-mcgpalettewhite);
$warn: mat-palette($dark-md-mcgpalettered);
$background:mat-palette($dark-md-mcgpalettebackground);
$confirm: mat-palette($dark-md-mcgpaletteorange);
$theme-dark: mat-dark-theme((color: (primary: $primary,
accent: $accent,
warn: $warn,
background: $background,
confirm: $confirm)));
theme-dark.scss - looks the same
In style.scss we import both themes:
styles.scss
#import 'projects/menu/src/theming/theme-dark.scss';
#import 'projects/menu/src/theming/theme-light.scss';
.theme-dark {
#include angular-material-theme($theme-dark)
}
.theme-light {
#include angular-material-theme($theme-light)
}
and then we use those variables:
styles.scss
body {
background: mat-color($background, default);
}
h1 {
color: mat-color($accent, default);
}
But currently we also import them in our components. I think this is one thing, that is wrong.
A dialog would look like this:
#import 'projects/menu/src/theming/theme-dark.scss';
#import 'projects/menu/src/theming/theme-light.scss';
.theme-dark {
#include angular-material-theme($theme-dark)
}
.theme-light{
#include angular-material-theme($theme-light)
}
.mat-button {
color: mat-color($accent, default);
background-color: mat-color($confirm, default);
}
What I tried:
Removing the import of both themes in every stylesheet. Colors are still influenced by the order of the theme import in style.scss. Dark theme last -> dark theme will be used and the other way round.
Using the same variables for the themes as in the tutorial. But then we had to change of all of the background-color: mat-color($confirm, default);
And so on. Spent lots of time...
We can't figure out how to put the missing parts together. Do we need to use mixins? Or are we missing setting the class dark-theme on the root component?
Thank you very much in advance.
We finally got it working.
Our mistakes:
#include mat-core(); must only be imported once (in theme-base.scss)
#import 'projects/menu/src/theming/theme-dark.scss'; and #import 'projects/menu/src/theming/theme-light.scss'; must only be imported once (in styles.scss). Importing them in all of the components lets bundle size explode.
Set theme-dark class directly in theme-dark.scss
Do not set classes theme-dark and theme-light in styles.scss or any other component theme
Create two wrapper elements for whole application: <div [ngClass]="{ 'theme-dark': darkMode$ | async }"> <div class="mat-app-background"> <router-outlet></router-outlet></div></div>
If components need to access the theme, use mixins (example shown below)
In theme-service.ts, use OverlayContainer to apply styles to material dialogs as well (as described here)
Mixin example:
settings-dialog.component.scss
#import '~#angular/material/theming';
#mixin settings-dialog-theme($theme) {
.settings-dialog {
#include mat-elevation(2);
.mat-button {
background-color: mat-color($confirm);
}
}
}
settings-dialog.component.html
<div mat-dialog-content class="settings-dialog"> Put your content here </div>
Create a component theme as described here
component-themes.scss
#import 'your-path/settings-dialog.component';
#mixin component-themes($theme) {
#include settings-dialog-theme($theme);
}
Import it into your base theme file
theme-base.scss
#import '~#angular/material/theming';
#import './component-themes.scss';
#include mat-core();
#mixin theming($theme) {
#include angular-material-theme($theme);
#include component-themes($theme);
}
Related
I'm trying to allow my users to have different themes to choose from however I can't seem to get it working. I've tried multiple different ways, one way is having two different files and i import them based on if the theme is light/dark however once it's imported it stays on the page. Another is within SCSS like this:
$enable-rounded: true;
[theme="dark"] {
$blue: #232c3b;
$body-bg: #262626;
$body-color: white;
}
[theme="light"] {
$body-bg: #ffffff;
$body-color: #000000;
}
#import 'bootstrap/scss/bootstrap.scss';
#import 'bootstrap-vue/src/index.scss';
then in my layouts/default.vue:
import(`~/assets/scss/main.scss`)
export default {
data() {
return {
darkMode: false,
}
},
mounted() {
let bodyElement = document.body;
bodyElement.classList.add("app-background");
let htmlElement = document.documentElement;
let theme = localStorage.getItem("theme");
if (theme === 'dark') {
bodyElement.setAttribute('theme', 'dark')
this.darkMode = true
} else {
bodyElement.setAttribute('theme', 'light');
this.darkMode = false
}
},
watch: {
darkMode: function () {
let htmlElement = document.documentElement;
if (this.darkMode) {
localStorage.setItem("theme", 'dark');
htmlElement.setAttribute('theme', 'dark');
} else {
localStorage.setItem("theme", 'light');
htmlElement.setAttribute('theme', 'light');
}
}
}
}
however nothing happens when I do it this way. I've been trying to figure it out for days and can't seem to get it. It makes me want to just use CSS and avoid SCSS even though I really want to use scss
I think you are importing your scss file in a wrong way. Please try importing it like below out of the script part:
<style lang="scss">
#import '~/assets/scss/main.scss';
</style>
I think you can use this for your project, you can switch into themes or custom on your own
https://color-mode.nuxtjs.org/
I'm using webpack and try to use css modules for my theming, for example:
<style lang="scss" module="theme1">
.header {
color: red;
}
</style>
<style lang="scss" module="theme2">
.header {
color: blue;
}
</style>
However, both .header tags get the same localIdentName from css-loader and because of that the second theme overrides the other everytime.
My loader chain: vue-loader, css-loader, sass-loader
My current localIdentName: '[local]_[hash:base64:5]' (neither path nor name result in anything, I just wish there was some sort of [value] tag.
Apparently it's because custom inject names were broken during the v14 -> v15 upgrade from vue-loader.
Heres the issue on GitHub with more details:
https://github.com/vuejs/vue-loader/issues/1578
Temporary solution (put this in the module options of css-loader):
{
localIdentName: '[module]_[local]_[hash:base64:5]',
getLocalIdent(context, localIdentName, localName) {
const { resourceQuery, resourcePath } = context;
const { index, module } = loaderUtils.parseQuery(resourceQuery);
const selector = loaderUtils.interpolateName(context, localIdentName, {
context: context.rootContext,
content: resourcePath + resourceQuery + localName,
})
.replace(/\[local\]/gi, localName)
.replace(/\[module\]/gi, typeof module === 'boolean' ? '' : module)
.replace(/\[index\]/gi, index)
.replace(new RegExp('[^a-zA-Z0-9\\-_\u00A0-\uFFFF]', 'g'), '-')
.replace(/^((-?[0-9])|--)/, '_$1');
return selector;
},
}
I'm trying to compile this little piece of .scss code:
$main-color:#06c;
:root{
--main-color-05: adjust-color($main-color, $lightness: +51%);}
When i compile i get this results:
local (sass -v: Sass 3.5.2 (Bleeding Edge))
:root {
--main-color-05: adjust-color($main-color, $lightness: +51%); }
Codepen (here's the code)
:root {
--main-color-05: #d1e8ff;}
Local compiler seems to ignore adjust-color function in custom properties, which works in class declarations:
$main-color:#06c;
:root{
--main-color-05: adjust-color($main-color, $lightness: +51%);}
.main-color-05{
background-color: adjust-color($main-color, $lightness: +51%);}
(compiled)
:root {
--main-color-05: adjust-color($main-color, $lightness: +51%); }
.main-color-05 {
background-color: #d1e8ff; }
Thanks in advance
You can use some sass mixin to set custom properties. Please have a look
https://codepen.io/malyw/pen/aNwKKv
// SCSS
$rootVars: ();
#mixin setVarSass($varName, $value){
$rootVars: map-merge($rootVars, (
#{$varName}: $value
)) !global;
}
#function getVarSass($varName){
#return map-get($rootVars, #{$varName});
}
#include setVarSass("color", #f00);
// CSS
#mixin setVarCss($varName, $value){
#supports ( (--a: 0)) {
// Custom properties are supported in the browser
$varName: unquote($varName);
:root{
#{--$varName}: $value;
}
}
}
#function getVarCss($varName){
$varName: unquote($varName);
#return #{var(--$varName)};
}
// Common
#mixin setVar($varName, $value){
#include setVarSass($varName, $value);
#include setVarCss($varName, $value);
}
#mixin setPropFromVar($propName, $varName){
#supports ( (--a: 0)) {
// Custom properties are supported in the browser
#{$propName}: getVarCss($varName);
}
#supports ( not (--a: 0)) {
// Custom properties are NOT supported in the browser
#{$propName}: getVarSass($varName);
}
}
// SET/GET
#include setVar('main-color', #f00);
body {
#include setPropFromVar('color', 'main-color');
}
or use npm module like
https://github.com/malyw/css-vars
https://www.npmjs.com/package/custom-properties-sass
I'm making a library containing some generic UI components, some kind of Bootstrap. Suppose I have the following LESS files structure:
button.less
checkbox.less
index.less
variables.less
Each component style file has #import "variables.less";. Index file imports all components files. Now I would like to distribute library.less and variables.less files into a package.
How do I bundle the index file? I used to concatenate all the files and remove repeated #import "variables"; line using regexps, maybe there is a Less API for doing this.
You can use less-bundle https://www.npmjs.com/package/less-bundle
// variables.less
#red: #ff0000;
#grey: #cccccc;
// button.less
#import 'variables.less';
.btn-danger {
background: #color;
}
// checkbox.less
#import 'variables.less';
input[type='checkbox'] {
background: #grey;
}
// index.less
#import 'button.less';
#import 'checkbox.less';
Here is main code to do that.
// bundler.js
const path = require('path')
const bundle = require('less-bundle');
const srcFile = path.join(__dirname, './index.less');
bundle({
src: srcFile,
dest: path.join(__dirname, './bundle.less')
}, function (err, data) {
console.log('Error Bundle', err, data);
});
Run bundler.js using command node bundler.js and it will create a bundle.less file containing all styles.
// bundle.less
#red: #ff0000;
#grey: #cccccc;
.btn-danger {
background: #color;
}
input[type='checkbox'] {
background: #grey;
}
chrome.tabs.insertCSS(tabId, { code : '#import url("custom.css");' });
OR
chrome.tabs.insertCSS(tabId, { file : 'importer.css' });
importer.css:
#import url("custom.css");
a { color:red!important; } /* this rule applied successfully though. */
Doesn't seem to work as expected.
Why doesn't it work and how to make it work?
Edit:
Maybe I am looking at the wrong source code location, but the source code indicates that it uses the regular style sheet parser to parse the injected CSS. In other words, if the #import directive works in regular CSS documents, it should also work in injected CSS.
https://code.google.com/p/chromium/codesearch#chromium/src/extensions/renderer/script_injection.cc&q=script_injection.cc&sq=package:chromium&type=cs&l=312-320
void ScriptInjection::InjectCss(blink::WebLocalFrame* frame) {
std::vector<std::string> css_sources =
injector_->GetCssSources(run_location_);
for (std::vector<std::string>::const_iterator iter = css_sources.begin();
iter != css_sources.end();
++iter) {
frame->document().insertStyleSheet(blink::WebString::fromUTF8(*iter));
}
}
Edit:
Sample code that's not working:
Directory structure:
ext.root
|-- custom.css
|-- custom.css.js
|-- importer.css
|-- manifest.json
manifest.json:
{
"background": {
"scripts": [ "custom.css.js" ],
"persistent": true
},
"manifest_version": 2,
"name": "custom.css",
"version": "1.0",
"web_accessible_resources" : [ "*" ],
"permissions" : [ "webNavigation", "http://*/", "https://*/" ]
}
custom.css.js:
chrome.webNavigation.onCommitted.addListener(function(details) {
console.log('inserting css');
console.log(chrome.runtime.getURL("custom.css"));
chrome.tabs.insertCSS(details.tabId, { file : 'importer.css' });
chrome.tabs.insertCSS(details.tabId, { code : '#import url("custom.css");' });
chrome.tabs.insertCSS(details.tabId, { code : '#import url(custom.css);' });
chrome.tabs.insertCSS(details.tabId, { code : '#import url("' + chrome.runtime.getURL("custom.css") + '");' });
chrome.tabs.insertCSS(details.tabId, { code : '#import url(' + chrome.runtime.getURL("custom.css") + ');' });
chrome.tabs.insertCSS(details.tabId, { code : '#import "custom.css";' });
chrome.tabs.insertCSS(details.tabId, { code : '#import custom.css;' });
chrome.tabs.insertCSS(details.tabId, { code : '#import "' + chrome.runtime.getURL("custom.css") + '";' });
chrome.tabs.insertCSS(details.tabId, { code : '#import ' + chrome.runtime.getURL("custom.css") + ';' });
});
importer.css:
#import "custom.css";
#import "chrome-extension://__MSG_##extension_id__/custom.css";
#import custom.css;
#import chrome-extension://__MSG_##extension_id__/custom.css;
#import url("custom.css");
#import url("chrome-extension://__MSG_##extension_id__/custom.css");
#import url(custom.css);
#import url(chrome-extension://__MSG_##extension_id__/custom.css);
body { background-color: red!important; } /* change page background color to red */
custom.css (Rules in this file are supposed to be applied but not):
body { border: 20px solid red!important; } /* add a 20px border around the body. */
a { background-color: blue!important; } /* change all link's background color to blue. */
If you try to inject such a file into a tab, relative URLs take its origin.
I.e. if you try to inject this into http://example.com/test/index.html, then the above CSS will try to load http://example.com/test/custom.css.
If custom.css is packed in the extension, you should use absolute path obtained from chrome.runtime.getURL():
chrome.tabs.insertCSS(
tabId,
{ code : '#import url("' + chrome.runtime.getURL(custom.css) + '");' }
);
You may try to use the special constant ##extension_id:
#import url("chrome-extension://__MSG_##extension_id__/custom.css");
but I'm not 100% sure it'll work.
Lastly, make sure the imported CSS file is in web_accessible_resources.
#important doesn't work with tabs.insertCSS(). insertCSS seem to be interpreted before injection in the page. How they are interpreted is not documented. For example !important do not works neither.