Switch theme Angular with SCSS - css

I'm trying to create a switch between 2 themes i have in sass.
They are quite simply variables with the same name, but have different colors, depending on the theme to be applied.
What i did was:
angular.json
"architect": {
"build": {
"builder": "#angular-devkit/build-angular:browser",
"options": {
...
"styles": [
"src/styles.scss",
{
"input": "src/app/core/theme/light.scss",
"bundleName": "light",
"inject": true
},
{
"input": "src/app/core/theme/dark.scss",
"bundleName": "dark",
"inject": false
}
],
In my theme folder, as the angular.json file says, i have 2 files, dark.scss & light.scss
dark.css
$PRIMARY: '#52DBCB';
$ON_PRIMARY: '#003732';
light.css
$PRIMARY: '#006a61';
$ON_PRIMARY: '#73f8e7';
Then i created a theme service
export class ThemeService {
public static default = 'light';
public get current(): string {
return localStorage.getItem('theme') ?? ThemeService.default;
}
public set current(value: string) {
localStorage.setItem('theme', value);
this.style.href = `/${value}.css`;
}
private readonly style: HTMLLinkElement;
constructor() {
this.style = document.createElement('link');
this.style.rel = 'stylesheet';
document.head.appendChild(this.style);
if (localStorage.getItem('theme') !== undefined) {
this.style.href = `/${this.current}.css`;
}
}
}
The issue im having is that when i try to call the variable $PRIMARY on a scss file, it says it doesnt exist, so i cant set the styles to it.
I never got to implement the switch through this path, because it wont recognize the variables
I'm pretty sure that with angular.json config should be enough, it wouldnt make a lot of sense to to import those 2 style sheet because i believe it would crash

Related

How to dynamically load CSS with Angular

For the last couple of days I've been trying several answers, suggestions and tutorials for the problem, but unfortunately non of them did the trick.
The closest one was this:
https://juristr.com/blog/2019/08/dynamically-load-css-angular-cli/
But it uses "extractCss" which has been deprecated since the article has been published.
According to the article:
"styles.js" file should disappear in the Inspector > Network > JS
Clicking the button should add its css file in Inspector > Network > CSS
But neither of these two is happening at the moment.
app.component.ts
const head = this.document.getElementsByTagName('head')[0];
console.log(head);
let themeLink = this.document.getElementById(
'client-theme'
) as HTMLLinkElement;
if (themeLink) {
themeLink.href = styleName;
} else {
const style = this.document.createElement('link');
style.id = 'client-theme';
style.href = `${styleName}`;
head.appendChild(style);
}
}
app.component.html
<head>
</head>
<body>
<button type="button" (click)="loadStyle('client-a-style.css')">STYLE 1</button>
<button type="button" (click)="loadStyle('client-b-style.css')">STYLE 2</button>
</body>
</html>
angular.json
"styles": [
"src/styles.css",
{
"input": "src/client-a-style.css",
"bundleName": "client-a",
"inject": false
},
{
"input": "src/client-b-style.css",
"bundleName": "client-b",
"inject": false
}
These are the main parts of my code.
Hopefully I've explained the problem sufficiently.
Thank you for helping!
You can put your additionals .css in the folder assets (and remove from angular.json)
Then the only change is add the "assets" folder to the href
loadStyle(styleName: string) {
const head = this.document.getElementsByTagName('head')[0];
let themeLink = this.document.getElementById(
'client-theme'
) as HTMLLinkElement;
if (themeLink) {
themeLink.href = `assets/${styleName}`; //<--add assets
} else {
const style = this.document.createElement('link');
style.id = 'client-theme';
style.rel = 'stylesheet';
style.type = 'text/css';
style.href = `assets/${styleName}`; //<--add assets
head.appendChild(style);
}
}
a stackblitz
I think you are missing a property in the link tag, add this to the place you create the link element and it should work.
style.rel = 'stylesheet';
One of possible solution to this task:
import { DOCUMENT } from '#angular/common';
import { Inject, OnDestroy, OnInit, Renderer2 } from '#angular/core';
export class MyComponent implements OnInit, OnDestroy {
private style?: HTMLLinkElement;
constructor(
#Inject(DOCUMENT) private document: Document,
private renderer2: Renderer2,
) {}
public ngOnInit(): void {
const cssPath = '/link/to/style.css';
// Create a link element via Angular's renderer to avoid SSR troubles
this.style = this.renderer2.createElement('link') as HTMLLinkElement;
// Add the style to the head section
this.renderer2.appendChild(this.document.head, this.style);
// Set type of the link item and path to the css file
this.renderer2.setProperty(this.style, 'rel', 'stylesheet');
this.renderer2.setProperty(this.style, 'href', cssPath);
}
public ngOnDestroy(): void {
// Don't forget to remove style after component destroying
this.renderer2.removeChild(this.document.head, this.style);
}
}
If and if your css-files are on the server, so you probably should update your proxy.conf.json file to have access this file from localhost while serve mode is on.

Configure eslint to read common types in src/types/types.d.ts vue3

My eslint .eslintrc.js, now properly in the src folder, is the following:
module.exports = {
env: {
browser: true,
commonjs: true,
es2021: true
},
extends: [
'plugin:vue/vue3-recommended',
'standard',
'prettier'
],
parserOptions: {
ecmaVersion: 2020,
parser: '#typescript-eslint/parser',
'ecmaFeatures': {
'jsx': true
}
},
plugins: [
'vue',
'#typescript-eslint'
],
rules: {
'import/no-unresolved': 'error'
},
settings: {
'import/parsers': {
'#typescript-eslint/parser': ['.ts', '.tsx']
},
'import/resolver': {
'typescript': {
'alwaysTryTypes': true,
}
}
}
}
I'm attempting to use eslint-import-resolver-typescript, but the documentation is a bit opaque.
I currently get errors on lines where a externally defined type is used (StepData in this example):
setup() {
const data = inject("StepData") as StepData;
return {
data,
};
},
The answer was the following. In types.d.ts (or other file if you want to have different collections of your custom types):
export interface MyType {
positioner: DOMRect;
content: DOMRect;
arrow: DOMRect;
window: DOMRect;
}
export interface SomeOtherType {
.. and so on
Then in the .vue files, import the types I need for the component:
import type { MyType, SomeOtherType } from "../types/types";
Before I was not using the export keyword and the types just worked without being imported. They have to be imported like this if you use export. It's kind of amazing how you are just expected to know this, the documentation for Typescript or Vue is sorely lacking in examples.

Theming with SCSS & nuxt

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/

CRA + Craco + multiple CSS themes

Folder structure:
src/index.tsx
src/themes/dark.scss
src/themes/light.scss
...
craco webpack modifications:
const path = require('path');
module.exports = {
webpack: {
configure: (webpackConfig, { env, paths }) => {
webpackConfig.entry.push(
path.join(process.cwd(), "src/themes/dark.scss"),
path.join(process.cwd(), "src/themes/light.scss")
);
webpackConfig.module.rules.splice(1, 0, {
test: /\.themes\/dark.scss$/,
use: [
{ loader: require.resolve('sass-loader') },
{ loader: require.resolve('css-loader') }
]
});
webpackConfig.module.rules[3].oneOf[5].exclude = /\.(module|themes)\.(scss|sass)$/;
webpackConfig.module.rules[3].oneOf[6].exclude = /\.themes\.(scss|sass)$/;
webpackConfig.module.rules[3].oneOf[7].exclude.push(/\.themes\.(scss|sass)$/);
return webpackConfig;
}
}
};
the intent is I am hoping obvious - we are trying to generate two theme css files from src/themes directory, which will be later changed manually with unloading / loading <link in DOM directly, I was inspired by Output 2 (or more) .css files with mini-css-extract-plugin in webpack and https://github.com/terence55/themes-switch/blob/master/src/index.js.
Now comes the troubles - after build process:
Creating an optimized production build...
Compiled successfully.
File sizes after gzip:
122.24 KB build/static/css/2.2e93dcba.chunk.css
762 B build/static/js/runtime~main.a8a9905a.js
191 B build/static/css/main.d0c4fa77.chunk.css
157 B build/static/js/main.2063d3e0.chunk.js
109 B build/static/js/2.9b95e8c0.chunk.js
(CSS files are OK, there are few generic CSS files and few of them are from libraries). But no theme files... I try to combine with file-loader, but it does not work either.
I would recommend configuring webpack to pack all assets related to a particular theme into a single chunk:
const themeFileRegex = /(\w+)\.theme\.(scss|sass)$/;
// recursively searches for a theme stylesheet in parent issuers
function getIssuerTheme(module) {
const matches = themeFileRegex.exec(module.resource);
if (matches) {
return matches[1];
} else {
return module.issuer && getIssuerTheme(module.issuer);
}
}
...
webpackConfig.optimization.splitChunks.cacheGroups = {
...webpackConfig.optimization.splitChunks.cacheGroups,
themes: {
test: getIssuerTheme,
name: m => {
const name = getIssuerTheme(m);
return `theme.${name}`;
},
reuseExistingChunk: false,
},
};
With that, you should get chunks named theme.light, theme.dark, etc.

stylelint - Where are there examples to create your own plugin?

I have gone to the stylelint website, github and downloaded locally via npm. The stylelint website advises that to create my own plugin I should use this format:
var myPluginRule = stylelint.createPlugin(ruleName, function(primaryOption, secondaryOptionObject) {
return function(postcssRoot, postcssResult) {
var validOptions = stylelint.utils.validateOptions(postcssResult, ruleName, { .. })
if (!validOptions) { return }
// ... some logic ...
stylelint.utils.report({ .. })
}
})
When I do a 'find' inside the npm folder for stylelint, I cannot find any examples that use this format. Can anyone advise a really good tutorial on creating your own plugin?
thanks
ok after playing around with it I have literally found a way.
1) prerequisites:
$ npm init
$ npm install gulp stylelint gulp-style-lint --save-dev
2) Create some scss files at ./scss/myfile.scss
body{background:red;}
3) create ./gulpfile.js
var gulp = require('gulp');
var gulpStylelint = require('gulp-stylelint');
gulp.task('stylelint',function(){
return gulp
.src('./scss/*.scss')
.pipe(
gulpStylelint({
reporters: [
{formatter: 'string', console: true}
]
})
);
})
4) create ./stylelintCustom/index.js
var stylelint = require("stylelint");
var ruleName = "steves/steve1";
var messages = stylelint.utils.ruleMessages(ruleName, {
rejected: 'steve rejects this',
});
module.exports = stylelint.createPlugin(ruleName, function(max, options) {
return function(root, result) {
// to access the variable for the whole of this file scss =
console.log(root.source.input);
// apply rules now...
// run reporter to output
}
});
module.exports.ruleName = ruleName;
module.exports.messages = messages;
Being sure to name ruleName: "plugins/plugin". ie steves/steverule1 etc.
5) Be sure to create stylelintCustom/package.json
{
"name": "stylelint-steves-steve1",
"version": "0.0.1",
"main": "index.js",
"devDependencies": {
"stylelint": "~2.6.0"
},
"engines": {
"node": ">=0.10.0"
}
}
6) create: stylelint.rc
{
"plugins": [
"./stylelintCustom/index.js"
],
"rules": {
"steves/steve1": true
}
}
7) run gulp
$ gulp stylelint
Will output the scss, so you can run whatever js, regex you need here.
For reference of how the existing rules work in stylelint you can go to:
yourproject/node_modules/stylelint/dist/rules/*

Resources