The list of support chats in our back office support app can get quite long, so I'm trying to apply the theme of the client's product to their chat in the list so that it has that product's colour. Since all of the theme class styles are generated with the same SCSS mixins, they all have the same specificity, so if a support user has changed their theme to a class that happens to fall after that of a given conversation, it incorrectly overrides the chat theme.
styles.scss
.theme {
&--pink {
$primary: mat-palette($vt-primary);
$accent: mat-palette($mat-pink, A200, A100, A400);
$theme: mat-light-theme($primary, $accent);
#include angular-material-theme($theme);
}
&--red {
$primary: mat-palette($vt-primary);
$accent: mat-palette($mat-red, A200, A100, A400);
$theme: mat-dark-theme($primary, $accent);
background: #303030;
color: white;
#include angular-material-theme($theme);
}
&--orange {
$primary: mat-palette($vt-primary);
$accent: mat-palette($mat-orange, A200, A100, A400);
$theme: mat-dark-theme($primary, $accent);
background: #303030;
color: white;
#include angular-material-theme($theme);
}
&--green {
$primary: mat-palette($vt-primary);
$accent: mat-palette($mat-green, A200, A100, A400);
$theme: mat-dark-theme($primary, $accent);
background: #303030;
color: white;
#include angular-material-theme($theme);
}
component HTML
<div class="red">
<ul class="chat-list">
<li class="theme--pink">
<mat-icon color="accent">circle</mat-icon>
Example Chat
</li>
<li class="theme--red">
<mat-icon color="accent">circle</mat-icon>
Example Chat
</li>
<li class="theme--orange">
<mat-icon color="accent">circle</mat-icon>
Example Chat
</li>
<li class="theme--green">
<mat-icon color="accent">circle</mat-icon>
Example Chat
</li>
</ul>
</div>
Here the chat that should be pink inherits the red theme from the parent. I can't just change the order or add !important because in another case the chat may be pink and the root red.
Here is a stackblitz showing my problem. I want the chat icons to keep their colour even when the root theme is changed.
I started to follow this tutorial on dynamic theming in angular, thinking I could use TypeScript to change the order of the cascade dynamically, but that relies on disabling scss extraction which, as far as I can tell, is not possible in the most recent Angular releases.
Related
In my sidebar, there are some <router-link>and I want their style is to be different when they are active. I am using router active-class for the <router-link> but the other elements inside the router-link are not taking some css properties. Here is my <router-link> looks like;
<router-link tag="li" to="/dashboard" class="nav-item has-treeview" active-class="active">
<a class="nav-link" id="dashboard_tab">
<i class="sidebar-icons">dashboard</i>
<p class="sidebar-links" active-class="active-p">Dashboard</p>
</a>
</router-link>
And here is my .active class;
.active {
background-color: #FF7400;
border-color: #FF7400;
border-radius: 4px;
color: white;
}
Now when a specific link is clicked and active, I have orange background and some border around that router-link. But my problem is, I also want the color of the icon and the p element is to be white, but they are not being effected by the color: white; property. What am I missing? active-class is only working for the router-link element so I cannot use it in the i or p. What should i do?
The Bootstrap version is 4.4.1.
I'm also using fontawesome, v5.0.7.
I've created a main.scss file with the following contents:
$theme-colors: ( "primary": orange, "danger": purple );
#import "../node_modules/bootstrap/scss/bootstrap";
This was compiled to /css/main.css that has:
:root {
...
--primary: orange;
...
}
.text-primary {
color: orange !important; }
So far, it seems fine.
In the html file, there's an import for the main.css style file.
<link rel="stylesheet" href="/css/main.css">
When trying to render text in orange, it is displayed in blue.
<p class="card-text text-primary">Some text.</p>
The browser dev tools show indeed that primary continues to be the blue color.
Where should I look for the cause of this?
I'm theming my app defining colors and typography. But it's not working for the heading in my app header component based on mat-toolbar. My theme is overridden by default theme css rules for .mat-toolbar h1.
index.html
<html>
...
<body class="mat-typography">
<app-root></app-root>
</body>
</html>
_theme.scss file:
#import '~#angular/material/theming';
#import 'utils/palette';
// Plus imports for other components in your app.
// Define the palettes for your theme using the Material Design palettes available in palette.scss
// (imported above). For each palette, you can optionally specify a default, lighter, and darker
// hue. Available color palettes: https://material.io/design/color/
$fem-theme-primary: mat-palette($fem-palette-primary);
$fem-theme-accent: mat-palette(
$fem-palette-primary
); // NOT USED, same as $fem-theme-primary!
$fem-theme-warn: mat-palette($fem-palette-warn);
// Create the theme object (a Sass map containing all of the palettes).
$fem-theme: mat-light-theme(
$fem-theme-primary,
$fem-theme-accent,
$fem-theme-warn
);
// Include theme styles for core and each component used in your app.
// Alternatively, you can import and #include the theme mixins for each component
// that you are using.
#include angular-material-theme($fem-theme);
// Define a custom typography config that overrides the font-family as well as the
// `headlines` and `body-1` levels.
$fem-typography: mat-typography-config(
$font-family: $font-family,
$headline: mat-typography-level(32px, 48px, 700),
);
#include angular-material-typography($fem-typography);
// Include the common styles for Angular Material. We include this here so that you only
// have to load a single css file for Angular Material in your app.
// Be sure that you only ever include this mixin once!
#include mat-core($fem-typography);
topbar.component.html
<div class="topbar">
<mat-toolbar>
<h1 class="topbar__logo mat-headline">App Title</h1>
<mat-form-field
class="topbar__search"
appearance="outline"
color="primary"
>
<input
matInput
data-e2e="topbar-search-input"
class="topbar__search-field"
placeholder="Søg"
(input)="handleSearchChange($event)"
/>
<mat-icon matSuffix inline="true">search</mat-icon>
</mat-form-field>
<div>
<span class="topbar__current-user">{{ currentUser.name }}</span>
<a
mat-button
class="topbar__log-out"
href=""
data-e2e="btn-logout"
(click)="logout()"
>Log ud</a
>
</div>
</mat-toolbar>
</div>
Font family is working... But I expect the <h1 class="topbar__logo mat-headline">App Title</h1> to have css:
font-size: 32px;
line-height: 48px;
font-weight: 700;
Instead it has the default styling:
css:
font-size: 20px;
line-height: 32px;
font-weight: 500;
How can I make ALL Angular Material components (including mat-toolbar) use my own theme?
Component mat-toolbar deliberately overrides all heading tags (h1 through h6) to "title" typography, which maps to h2.
You can make it show "regular" typography for h1 with:
#import '~#angular/material/theming';
.mat-toolbar h1 {
#include mat-typography-level-to-styles($fem-typography, headline); // where headline maps to h1
}
Here are all the mappings, if you'd like to apply the above for other heading sizes:
headline: h1
title: h2
subheading-2: h3
subheading-1: h4
caption: h5
slot is good to make a reusable web component, however, it has a limit so far. What I faced is the style problem. You just can't define the style inside a component, even you know what the inject content's structure would be.
Details found from my post in github here
I write a component, and try to inject content through slot from the outside and try to add style to the specific content in the component's shadow root.
Demo
HTML file
<my-navbar>
<ul>
<li>link1</li>
<li>link2</li>
<li>link3</li>
</ul>
</my-navbar>
JS file
customElements.define('my-navbar', class extends HTMLElement {
constructor () {
super();
const sr = this.attachShadow({ mode: 'open' });
sr.innerHTML = `
<style>
/*worked*/
::slotted(ul)
{
color:green;
}
/*
Suppose I know the outside content is "ul li", and I directly define the
style after they injected into component's slot. However, it just doesn't
work because the slotted selector is just a compound selector. It can only
affect the first layer 'ul'. It can't affect the child dom 'li' */
::slotted(ul li)
{
color:red;
}
</style>
<slot></slot>
`;
}
});
However, it just doesn't work directly because you just can't use a complex selector for ::slot(simple_selector)
Reason
I found an indirect solution, and that's to re-append the outside content into the slots inside the component's shadow root.
Demo
HTML file
<my-navbar>
<!--a dom defined a slot property-->
<ul slot='t'>
<li>link1</li>
<li>link2</li>
<li>link3</li>
</ul>
<!--A dom not define slot property-->
<span>1234</span>
</my-navbar>
JS file
customElements.define('my-navbar', class extends HTMLElement {
constructor () {
super();
const sr = this.attachShadow({ mode: 'open' });
sr.innerHTML = `
<style>
ul li
{
color:red;
}
</style>
<slot name='t'></slot>
<slot ></slot>
`;
// Do something later...
setTimeout(this.appendOutsideSlotContentIntoInsideSlot.bind(this), 1000)
}
appendOutsideSlotContentIntoInsideSlot()
{
// Insert outside dom element which has define slot property into the specify slot inside the shadow root
debugger;
for (let objIndex=0;objIndex<this.children.length;)
{
var obj=this.children[objIndex];
if(obj.slot){
var slot=this.shadowRoot.querySelector('slot[name='+obj.slot+']');
if(slot)
{
slot.appendChild(obj);
continue;
}
}
objIndex++;
}
// Insert the rest dom which has not define slot property values into the anonymous slot
var defaultSlot=Array.prototype.slice.call(this.shadowRoot.querySelectorAll('slot')).filter(function(el){ return !el.name})[0];
debugger;
if(defaultSlot)
{
while (this.children.length>0)
{
defaultSlot.appendChild(this.children[0])
}
}
}
});
Well, it works for the contents which have defined the slot property, but doesn't work again with content that has no slot property.
With the exception of a few inheritable rules the contents of the slot are not supposed to be directly affected by your component's shadow CSS. They are designed to allow the CSS outside of your component to be in control.
That is by design.
This is similar to the protection given to elements within the shadow DOM not being affected by external CSS.
Read the section Styling distributed nodes found here: https://developers.google.com/web/fundamentals/web-components/shadowdom#stylinglightdom
You are only allowed to change CSS rules for the top level elements inside the slot. And you are even limited to what you can do to that. All child elements are controlled by the CSS outside of the shadow DOM.
In the example below you will see that we can change the color and background color of the top level elements, or the <ul> tags:
customElements.define('my-navbar', class extends HTMLElement {
constructor () {
super();
const sr = this.attachShadow({ mode: 'open' });
sr.innerHTML = `
<style>
::slotted(ul)
{
color: blue;
}
::slotted(.bold) {
font-weight: bold;
background-color: #222;
color: #FFF;
}
::slotted(.italic) {
font-style: italic;
background-color: #AAA;
color: #000;
}
::slotted(*)
{
color: red;
}
</style>
<slot></slot>
`;
}
});
<my-navbar>
<ul class="bold">
<li>link1</li>
<li class="italic">link2</li>
<li>link3</li>
</ul>
<ul class="italic">
<li>link1</li>
<li class="bold">link2</li>
<li>link3</li>
</ul>
</my-navbar>
In the example above, the only reason the text is red and not blue is because ::slotted(*) affects just the two <ul>, has the same specificity as ::slotted(ul) and is placed after ::slotted(ul). The color is inherited by the <li> tags because that is how CSS works.
The background colors only affect the <ul> tags based on their classes and not the <li> tags with identical classes.
In the example below, the <li> color and background-color are controlled by the CSS outside the shadow DOM. The external rules act as if they are more specific then the shadow DOM rules even though the shadow DOM rules included both a tag and a class selector (ul.bold).
Again, this is by design.
customElements.define('my-navbar', class extends HTMLElement {
constructor () {
super();
const sr = this.attachShadow({ mode: 'open' });
sr.innerHTML = `
<style>
::slotted(ul)
{
color: blue;
}
::slotted(ul.bold) {
font-weight: bold;
background-color: #222;
color: #FFF;
}
::slotted(ul.italic) {
font-style: italic;
background-color: #AAA;
color: #000;
}
::slotted(*)
{
color: red;
}
</style>
<slot></slot>
`;
}
});
li {
color: #555;
backgroung-color: #ddd;
}
.bold {
font-weight: bold;
background-color: #FF0;
}
.italic {
font-style: italic;
background-color: #0FF;
}
<my-navbar>
<ul class="bold">
<li>link1</li>
<li class="italic">link2</li>
<li>link3</li>
</ul>
<ul class="italic">
<li>link1</li>
<li class="bold">link2</li>
<li>link3</li>
</ul>
</my-navbar>
You will note that the background colors of the <ul> and <li> tags are set based on the external classes of bold and italic.
If you want to use a <slot> the you agree that the developer using your component has the override power for anything that is placed into the slot.
If you don't want the user to have that kind of control then the only way to prevent it is to move the component's children into the component's shadow DOM.
But be careful when you do it.
According to the rules of Web Component constructors you can not access or change the children of a component while in the constructor.
But you have to remember that the connectedCallback is called every time the component is inserted into the DOM. So if the developer removes and then re-appends your component then the connectedCallback will be called again. So you have to add a gate to prevent it from getting called twice.
Also you might want to add a MutationObserver to see when the user changes the children of your components.
I have a website where each section has its own primary color.
I'd like to put something in Bootstrap 4's Sass code, to override the primary color depending on a body class I set.
I have this, but it's not working so far:
$theme-colors: () !default;
$theme-colors: map-merge((
// primary: $blue,
// secondary: $gray-600,
success: $green,
info: $cyan,
warning: $yellow,
danger: $red,
light: $gray-100,
dark: $gray-800
), $theme-colors) !default;
// override according to portal
html {
&.portal-1 {
$theme-colors: map-merge((
primary: #f1b36d
), $theme-colors);
}
How can this be implemented in Bootstrap 4's Sass file?
You can't change the sass variables which are already converted to static CSS dynamically at the client side.
However, to build theme system you can apply one of the following options:
1. Generate independent theme
Put a theme argument in your build system which will generate different themes CSS Ex: grunt build-sass --theme=brown
var themes = {
"default": "https://bootswatch.com/flatly/bootstrap.min.css",
"cerulean" : "https://bootswatch.com/cerulean/bootstrap.min.css"
}
// Use something like this to add theme: (You can also append instead of overriding whole head html)
var headHTML = document.getElementsByTagName('head')[0].innerHTML;
headHTML += '<link type="text/css" rel="stylesheet" href="' + themes.cerulean +'">';
document.getElementsByTagName('head')[0].innerHTML = headHTML;
2. Change properties based upon the parent class
You can have a base CSS which defines general CSS. And then you can have separate CSS Properties based upon the parent
In the following example update green-theme class to blue-theme and vice versa
div[class^=component] {
display: inline-block;
width:150px;
height: 150px;
margin: 1em;
border: 1px solid black;
box-shadow: 1px 1px 5px gray;
border-radius:1em;
}
/* Colors*/
$blue1: #3066BE;
$blue2: #090C9B;
$green1: #5C946E;
$green2: #80C2AF;
/* Blue Theme */
.blue-theme {
.component1 {
background-color: $blue1;
}
.component2 {
background-color: $blue2;
}
}
/* Green Theme */
.green-theme {
.component1 {
background-color: $green1;
}
.component2 {
background-color: $green2;
}
}
<div class="green-theme" id="mainBody">
<div class="component1">
</div>
<div class="component2">
</div>
</div>
*Run Snippet won't work as we are using SCSS *
Make a scss file structure like this,
- vendors
- bootstrap
- stylesheets /*[Bootstrap 4 official sass files]*/
- _bootstrap.scss
- scss
- themes
- _theme-1.scss
- _theme-2.scss
- _variables.scss
- styles.scss
- portal-1.scss
- portal-2.scss
_variables.scss
#import "../vendors/bootstrap/stylesheets/bootstrap/variables";
#import "../vendors/bootstrap/stylesheets/bootstrap/mixins";
/* override bootstrap default variables ... */
/* custom variables with !default ... */
styles.scss
#import "variables";
#import "../vendors/bootstrap/stylesheets/bootstrap";
/* custom styles ... */
themes/_theme-1.scss
#import "../variables";
/* change value bootstrap and custom default variables ... */
$brand-primary: #428bca
$brand-success: #5cb85c;
$brand-info: #5bc0de;
$brand-warning: #f0ad4e;
$brand-danger: #d9534f;
$body-bg: #eff;
themes/_theme-2.scss
#import "../variables";
/* change value bootstrap and custom default variables ... */
$brand-primary: #C04848;
portal-1.scss
#import "themes/_theme-1";
/* change style with new variable bootstrap default components ... */
.portal-1 {
#import "../vendors/bootstrap/stylesheets/bootstrap/buttons";
#import "../vendors/bootstrap/stylesheets/bootstrap/alerts";
/* custom style overrides ... */
}
portal-2.scss
#import "themes/_theme-2";
/* change style with new variable bootstrap default components ... */
.portal-2 {
#import "../vendors/bootstrap/stylesheets/bootstrap/buttons";
#import "../vendors/bootstrap/stylesheets/bootstrap/alerts";
/* custom styles from style.scss overrides ... */
}
After sass compilation we will get 3 files styles.css, portal-1.css and portal-2.css.
Use style.css by default and others include in head tag for theming.
<html>
<head>
<link href="styles.css" rel="stylesheet" />
<link href="portal-1.css" rel="stylesheet" />
</head>
<body>
<button class="btn btn-primary">BUTTON</button>
<div class="portal-1">
<button class="btn btn-primary">BUTTON</button>
</div>
</body>
</html>
NOTE: After searching a while for a simple solution, I though I could avoid the pain for anyone trying to achieve this:
You can use CSS variables to achieve this.
There is a thread on the main bootstrap github page Github Issue and user #johanlef has posted a gist showing his method and the relevant code gist.
Then you can just make sure the css variables is initialized in you page
.body.theme1{
--primary: #5d8be5;
}
I used for one of my multi tenanted website and it works great.