BEM: adding modifier to already existed modifier - css

I'd been confused by simple scenario when I was working with BEM.
There is a base button in example:
.button {
// styles for button
}
and its modifier with more specific styles:
.button.button_run {
// additional styles for this type of button
// i.e. custom width and height
}
One moment I realize that I need modifier for button_run, let's name it like button_run_pressed:
.button_run_pressed {
// more styles, i.e. darker background color
}
The problem is that it's not correct to name the last element as I did above button_run_pressed according to BEM conventions. But I need to add "pressed" styles only to "run" button, not for all buttons by writing class like button_pressed and mixing modifier button button_run button_pressed.
How should I refactor my code to match BEM conventions?

According to http://getbem.com/naming/, the modifier classes are initiated with two hyphens (--). So a modifier for .button should look like
.button--modifier { /* ... */ }
In your case, I would suggest choosing the following names:
.button {}
.button--run {}
.button--run-pressed {}
Notice, that I also decoupled the modifier classes from the block class, which is more according to BEM rules. You want to avoid creating classes which depend on others to work.
Since you added less as a tag to the post, here's how this could look in less or scss:
.button {
// button styles
&--run {
// modified styles
}
&--run-pressed {
// more modifiers
}
}
This would result in the classnames I wrote above

Firstly, the name should be .block--modifier or .button--run
If you want it only works with both modifier run and press, you should name it as
.button.button--run.button--pressed
Hope this help

Related

Styling one element with multiple selectors (sass)

I've an element I want to style only if it's got two classes applied to it:
custom-select-value--companies
and
custom-select-value--companies-disabled
It's actually the pseudo element I want to style, and the following css works:
.custom-select-value--companies.custom-select-value--companies-disabled::after { // Styles }
It's probably very simple, but I was just struggling to translate that to sass and was hoping someone could help? The following doesn't work:
.custom-select-value {
&--companies.&--companies-disabled::after {
// Styles
}
}
Also, just wondered as I was writing this - what's the main element of a pseudo element called? "Parent" doesn't seem quite right?
Thanks
Managed to get it working by typing the second selector out in full:
.custom-select-value {
&--companies.custom-select-value--companies-disabled::after {
// Styles
}
}

Pierce component style globally

I need to pierce the styles of my component from the global styles.scss file.
Basically, I've used mat-card component wrapped in a custom-component. In
some cases, I want to change styles to the mat-card when a custom-component is preceded by another custom-component
The idea would be:
global-styles.scss
custom-component ~ custom-component::ng-deep {
.mat-card {
background-color: red;
}
}
The host-context seemed like a good idea, I tried it this way
custom-component.scss
// does not work
host-context(~custom-component) { background-color: red; }
I've tried this and some other combinations, but they don't seem to work. How are we supposed to use the >, ~, + selectors to style angular components?.
Cheers
Personally I avoid piercing selectors at all costs. It breaks the entire component model, and tightly couples code.
I would approach this in a slightly different way. I would have my custom-component have an optional Input() embedded = false
Your usage could be as follows:
// top level
<custom-component></custom-component>
// nested level
<custom-component [embedded]="true"></custom-component>
Then use ngClass with the embedded property to trigger the color change.
See docs for more info on ngClass
https://angular.io/api/common/NgClass
Ok not a solution for this, but there's one thing to consider.
If you want to apply styles to your component using the selector your-component, you have to set display: block; property in your component :host. I totally missed this, but it would look like this:
your-component.css
:host {
display: block;
}
your parent component css
your-component ~ your-component {
margin-top: 15px;
}
And it works. My problem was originally related to that.

How to dynamically generate CSS class and/or set its property

The title is not really a question it is more like an idea, I don't know what approach is best for my situation.
So, the problem. I have some 3rd party component that have some complex structure and styling. Some part of it has some predefined CSS class that I can override with CSS in my surrounding component. Something like this:
my component:
<div class="my-cmp-container">
<some-3rd-party-cmp></some-3rd-party-cmp>
</div>
3rd party component:
<div class="3rd-party-css-class">
...
</div>
For example, 3rd-party-css-class has style background-color: #f00, I can override it with .my-cmp-container .3rd-party-css-class { background-color: #fff; } etc. But. What if I need to set color dynamically, it's stored in a DB for example and I can't predefine each case in my class' CSS. I just have the color in hex.
In theory I can generate unique string to set as CSS class for every instance of some-3rd-party-cmp and somehow generate CSS in my component? I'm lost a little, what is the best approach for this?
Edit: Code sample to illustrate the situation https://stackblitz.com/edit/angular-kxdatq
What you are trying to do is the subject of this open issue about stylesheet binding in Angular. Until that feature is available, you can get what you want with a custom directive. Here is a directive that retrieves the checkbox element generated by ng-zorro-antd and applies two color attributes to it. The two colors are #Input properties and the directive implements OnChanges which allows to react to property binding changes.
#Directive({
selector: "[nz-checkbox][nz-chk-style]"
})
export class CheckBoxStyleDirective implements OnInit, OnChanges {
#Input("nz-chk-bkgnd") chkBkgndColor: string;
#Input("nz-chk-border") chkBorderColor: string;
private checkbox: HTMLElement;
constructor(private renderer: Renderer2, private el: ElementRef) { }
ngOnInit() {
this.checkbox = this.el.nativeElement.querySelector(".ant-checkbox-inner");
this.updateBackgroundColor();
this.updateBorderColor();
}
ngOnChanges(changes: SimpleChanges) {
if (changes.chkBkgndColor) {
this.updateBackgroundColor();
}
if (changes.chkBorderColor) {
this.updateBorderColor();
}
}
updateBackgroundColor() {
if (this.checkbox) {
this.renderer.setStyle(this.checkbox, "background-color", this.chkBkgndColor);
}
}
updateBorderColor() {
if (this.checkbox) {
this.renderer.setStyle(this.checkbox, "border-color", this.chkBorderColor);
}
}
}
Once the directive attribute selector nz-chk-style is applied to the 3rd party element, you can set the checkbox background and border colors with property binding as follows:
<span nz-checkbox nz-chk-style [nz-chk-bkgnd]="bkgndColor" [nz-chk-border]="borderColor" >
See this interactive stackblitz for a demo.
Not sure if you are using Angular but you tagged it, so I guess you are.
If you want to change only the color and nothing more, instead of having a .3rd-party-css-class class, you could just have your with an ng-style like so:
<some-3rd-party-cmp ng-style="{ color: your_color_hex_variable }"></some-3rd-party-cmp>
You can also define a whole object if styles and pass it.
You can also use ng-class and pass one or an array of class names what you want to put additionally on your component:
<some-3rd-party-cmp ng-class="[cls1, cls2, cls3]"></some-3rd-party-cmp>
<some-3rd-party-cmp ng-class="[3rd-party-css-class, someCondition ? 'another-class-name' : '']"></some-3rd-party-cmp>
In the classes you can define the css rules you want to apply and thats it.
With this solutions you can avoid having extra wrapper elements for styling purposes which is a nice thing.

HIghlighting row during runtime

I am trying to highlight a row based user input. I am using Angular 5, with ag-grid-angular 19.1.2. Setting the style with gridOptions.getRowStyle changes the background, but I would rather use scss classes if possible. The function setHighlight() is called in the html file through (change)=setHighlight()
setHighlight() {
const nextChronoId = this.getNextChronoDateId();
// this.highlightWithStyle(nextChronoId); // Working solution
this.highlightWithClass(nextChronoId);
const row = this.gridApi.getDisplayedRowAtIndex(nextChronoId);
this.gridApi.redrawRows({ rowNodes: [row]})
}
Function definitions:
highlightWithStyle(id: number) {
this.gridApi.gridCore.gridOptions.getRowStyle = function(params) {
if (params.data.Id === id) {
return { background: 'green' }
}
}
}
highlightWithClass(id: number) {
this.gridApi.gridCore.gridOptions.getRowClass = function(params) {
if (params.data.Id === id) {
return 'highlighted'
}
}
}
My scss class:
/deep/ .ag-theme-balham .ag-row .ag-row-no-focus .ag-row-even .ag-row-level0 .ag-row-last, .highlighted{
background-color: green;
}
My issue
Using getRowClass does not apply my highlighted class correctly to the rowNode. After reading (and trying) this, I think that my custom scss class overwritten by the ag-classes. The same problem occurs when using rowClassRules.
Question
How can I make Angular 5 and ag-grid work together in setting my custom scss class correctly?
Stepping with the debugger shows the class is picked up and appended to the native ag-grid classes.
In rowComp.js:
Addition, screen dump from dev tools:
angular's ViewEncapsulationis the culprit here.
First be aware that all shadow piercing selectors like /deep/ or ::ng-deep are or will be deprecated.
this leaves, to my knowledge, two options.
use ViewEncapsulation.None
add your highlighted class to the global stylesheet
setting ViewEncapsulation.None brings its own possible problems:
All components styles would become globally available styles.
I would advise to go with option two.
this answers sums it up pretty well.
Additionally:
.ag-theme-balham .ag-row .ag-row-no-focus .ag-row-even .ag-row-level0 .ag-row-last
this selector will never match anything, you should change it to
.ag-theme-balham .ag-row.ag-row-no-focus.ag-row-even.ag-row-level0.ag-row-last
every class after ag-theme-balham exists on the same element.
with the selector you wrote, you would denote a hierarchy.
Hope this helps

How to think about styling AngularJS components?

I'm working on an AngularJS project with the aim of slowly getting things in order for Angular 6, or whatever version is out when we start on the upgrade. One of the big pieces of that work is converting existing directives into components.
The thing I'm struggling the most with, is that every instance of a component introduces an extra element into the DOM that wraps my actual component HTML and breaks the hierarchy, making it very hard to write CSS that does what it needs to.
To illustrate my dilemma, imagine a simple component called alert that provides styling for various types of messages you want a user to pay attention to. It accepts two bindings, a message and a type. Depending on the type we will add some special styling, and maybe display a different icon. All of the display logic should be encapsulated within the component, so the person using it just has to make sure they are passing the data correctly and it will work.
<alert message="someCtrl.someVal" type="someCtrl.someVal"></alert>
Option A: put styling on a <div> inside the extra element
Component template
<div
class="alert"
ng-class="{'alert--success': alert.type === 'success', 'alert--error': alert.type === 'error'}">
<div class="alert__message">{{alert.message}}</div>
<a class="alert__close" ng-click="alert.close()">
</div>
Sass
.alert {
& + & {
margin-top: 1rem; // this will be ignored
}
&--success {
background-color: green; // this will work
}
&--error {
background-color: red; // this will work
}
}
This works fine as long as the component is completely ignorant of everything around it, but the second you want to put it inside a flex-parent, or use a selector like "+", it breaks.
Option B: try to style the extra element directly
Component template
<div class="alert__message">{{alert.message}}</div>
<a class="alert__close" ng-click="alert.close()">
Sass
alert {
& + & {
margin-top: 1rem; // this will work now
}
.alert--success {
background-color: green; // nowhere to put this
}
.alert--error {
background-color: red; // nowhere to put this
}
}
Now I have the opposite problem, because I have nowhere to attach my modifier classes for the success and error states.
Am I missing something here? What's the best way to handle the presence of this additional element which sits above the scope of the component itself?
I personally do option A. This allows you to easily identify and create specific styles for your components without fear that they will overwrite site-wide styles. For instance, I'll use nested styles to accomplish this:
#componentContainer {
input[type=text] {
background-color: red;
}
}
This will allow you to make generic styles for your component that won't spill out into the rest of your solution.

Resources