React - Apply **active** class to an element using CSS modules - css

I'm diving into React and I found a little problem managing active class names when using CSS modules.
Suppose I want to develop a Tabs React component. I would like to apply an active class to the current list item. The tab headers is built by the following component:
import React, { Component } from 'react';
import styles from './Tabs.scss';
export default class TabHeader extends Component {
render() {
let activeTabIndex = this.props.activeTabIndex;
return (
<ul className={styles['tabs-header']}>
{
this.props.data.map((item, index) => {
return (
<li key={index}>
<a className={(index === activeTabIndex) ? 'active' : ''} href="#">
<span>{item.header}</span>
</a>
</li>
)
})
}
</ul>
);
}
}
As you can see, I conditionally added the class active to the interested list item. The stylesheet code is Tabs.scss:
.tabs-header {
display: table;
width: 100%;
list-style-type: none;
& li {
display: table-cell;
text-align: center;
color: #ECF0F1;
cursor: pointer;
a {
display: block;
padding: 15px;
background: #212F3D;
transition: all .2s ease-in;
transform: skew(-40deg);
&:hover {
background: #2471A3;
color: #F7F9F9;
}
& span {
display: block;
transform: skew(40deg);
}
&.active {
background: #2471A3;
}
}
}
}
With this setup, the active item is not using the active css code. How can I solve the problem?
EDIT: the prop activeTabIndex (integer greater than or equal to zero) is correctly working. If I inspect the elements, I can see the class active being added to the active item list, but it is not pointing to the class defined in Tabs.scss. Just to point this out, when using className={styles['tabs-header']} in the ul element, this is going to be converted to Tabs__tabs-header__2LSPG.

I’d need to try it out to be confident of this, but I think you should be using styles.active instead of 'active’.

this should work<a
className={${index === activeTabIndex && css.active}}
href="#"
{item.header}

Your condition would leave an empty space in a class name because of the condition:
(index === activeTabIndex) ? 'active' : ''
Instead you can do a short-circuit evaluation:
<a className={(index === activeTabIndex) && styles['active']} href="#">
<span>{item.header}</span>
</a>

All about CSS modules in React:
For a className with the dash use: style["upper-level"]
Single word className: style.active
To combine a multiple classNames use: style["upper-level"] + " " + style.active
import style from "./style.module.css";
...
className={
menuOpened
? style["upper-level"] + " " + style.active
: style["upper-level"]
}

Related

P-Card component from PrimeNG simply won't accept CSS changes

I need to dynamically change a p-card component in my APP. But it's simply not working...
Heres what I've tried so far :
<div class="card-image-comp">
<p-card [class.activeCard]="profileCardSelected === 1" (click)="selectProfileType(1)">
<img src="../../../assets/icons/person.png">
</p-card>
<p>Paciente</p>
</div>
<div>
<p-card [class.activeCard]="profileCardSelected === 2" (click)="selectProfileType(2)">
<img src="../../../assets/icons/estetoscopio.png">
</p-card>
<p>Profissional de Saúde</p>
</div>
...
My function:
selectProfileType(numCard: number){
this.profileCardSelected = numCard;
}
This part is working just fine, the issue is that the component is not obeying it's new class.
I've tried the normal stuff:
.activeCard {
background-color: black;
}
...
div {
.activeCard {
background-color: black;
}
}
...
.personalCardComp {
.activeCard {
background-color: black;
}
}
... and even some nasty stuff
:host {
::ng-deep .activeCard {
background-color: black;
}
}
...
:host {
::ng-deep p-card.p-element.activeCard {
background-color: black;
}
}
As I said before, the class is applied correctly, but the card only changes when I apply the css to the div children of the p-card...
Basically if I could apply the class to this div children It would work just fine... Is there a way to do so? Apply the class to p-card but the div children obbey...
Be sure to properly import your .scss file and then:
:host ::ng-deep {
.p-card.p-component {
background: black;
}
}

How to remove ant-click-animating of button Ant design

After I click on the button, it has the animation around the button. So I want to turn it off, I try to set CSS for element and Pseudo-classes but it's not working.
.ant-switch,
.ant-switch:focus,
.ant-switch:active {
border-color: white !important;
box-shadow: none !important;
outline: unset;
}
My code:
import React from 'react';
import { Switch } from 'antd';
import styled from 'styled-components';
const RSwitch = styled(Switch)`
background-color: ${props => props.backgroundcolor};
.ant-switch-handle::before {
background-color: #9b9b9b;
right: 0;
}
&[aria-checked='true'] {
.ant-switch-handle {
::before {
background-color: ${props => props.color};
}
}
}
`;
export default function SwitchComponent({
onChange,
checked,
color = '#00afdb',
backgroundColor = ''
}) {
return (
<RSwitch
onChange={onChange}
checked={checked}
size="small"
color={color}
backgroundcolor={backgroundColor}
/>
);
}
ant switch picture
HTML of switch
I fixed it with this solution
create a button.less file in your ant overrides and put this inside
[ant-click-animating-without-extra-node='true']::after{display:none;}
and it will work like a charm.
I realize it is just a div tag so I don't display it. If everybody has a better solution, please let me know.
.ant-click-animating-node {
display: none;
}
.ant-btn {
&::after {
all: unset;
}
}

Material-UI: Remove TimelineItem missingOppositeContent:before element

I'm using Material-UI and building a timeline. My code is as follows:
<Timeline align="right" className={classes.monthlyContainer}>
<TimelineItem >
<TimelineSeparator className={classes.timelineSeparator}>
<TimelineDot className={classes.timelineDot} />
<TimelineConnector className={classes.timelineConnector} />
</TimelineSeparator>
{(data.map(url =>
<TimelineContent className={classes.memsImageContainer}>
<img
className={classes.memsImage}
src={url}
alt="MEMs"
/>
</TimelineContent>
))}
</TimelineItem>
</Timeline>
When I render the webpage, the Material-UI timeline keeps creating a .MuiTimelineItem-missingOppositeContent:before element which is shifting the layout of my timeline to the left.
When I inspect the element, this is what I see:
<li class="MuiTimelineItem-root MuiTimelineItem-alignRightMuiTimelineItem-missingOppositeContent">
<div class="MuiTimelineSeparator-root makeStyles-timelineSeparator-4">
<span class="MuiTimelineDot-root makeStyles-timelineDot-5 MuiTimelineDot-defaultGrey">
</span>
<span class="MuiTimelineConnector-root makeStyles-timelineConnector-6">
</span>
</div>
</li>
When I inspect the styles, this is what I have:
.MuiTimelineItem-missingOppositeContent:before {
flex: 1;
content: "";
padding: 6px 16px;
padding-left: 0px;
padding-right: 0px;
I have recreated it in codesandbox here
How can I remove this element?
The definition of the default styles for the missingOppositeContent element is as follows:
/* Styles applied to the root element if no there isn't TimelineOppositeContent provided. */
missingOppositeContent: {
'&:before': {
content: '""',
flex: 1,
padding: '6px 16px',
},
},
You can override the default styles using the same structure. Overriding this in the theme would look like the following:
const Theme = createMuiTheme({
overrides: {
MuiTimelineItem: {
missingOppositeContent: {
"&:before": {
display: "none"
}
}
}
}
});
You can also do this on a case-by-case basis (in case you have other situations in your code where you want the missing-opposite-content styling) using withStyles:
const TimelineItem = withStyles({
missingOppositeContent: {
"&:before": {
display: "none"
}
}
})(MuiTimelineItem);
You won't believe that you just need to add the <TimelineOppositeContent> component and set display property as 'none'. And it will be solved.

How do I make a Material toolbar opaque on scroll and transparent at start?

I'm currently trying to familiarise myself with angular. I'm using angular material and I'm looking to make the material toolbar sticky and opaque on scroll and, transparent with toolbar text still visible when at the very top of the page. Everything I've searched for so far involved javascript or jquery. How do I go about it in angular 8 precisely?
This is my HTML & CSS respectively:
<mat-toolbar color="primary">
<a mat-button [routerLink]="['home']" >
<h1>PETER<span class="light">CONSTRUCTION</span></h1>
</a>
<span class="spacer"></span>
<a mat-button [routerLink]="['home']" routerLinkActive="active" >HOME</a>
<a mat-button [routerLink]="['about']" routerLinkActive="active">ABOUT</a>
<a mat-button [routerLink]="['contact']" routerLinkActive="active">CONTACT</a>
</mat-toolbar>
mat-toolbar {
position: absolute;
z-index: 1;
overflow-x: auto;
background-color: #c3cfd2;
}
mat-toolbar-row {
justify-content:space-between;
}
.spacer {
flex: 1 1 auto;
}
a.active {
background-color: rgba(0,0,0, 0.3);
}
h1 {
margin: 0;
color: black;
}
h1 .light {
font-weight: 100;
}
/*.x-bar.x-bar-absolute{*/
/* background-color: hsla(276, 6%, 63%, 0.15) !important;*/
/* transition: none !important;*/
/*}*/
/*.x-bar.x-bar-fixed{*/
/* background-color: hsla(276, 6%, 63%, 1) !important;*/
/*}*/
/*.x-bar [class^="x-bg"] {*/
/* background-color: transparent !important;*/
/*}*/
/*.x-bar.x-bar-absolute .hm5.x-menu > li > .x-anchor .x-anchor-text-primary {*/
/* color: #fff;*/
/*}*/
/*.x-bar.x-bar-fixed .hm5.x-menu > li > .x-anchor .x-anchor-text-primary {*/
/* color: #000;*/
/*}*/
There are multiple ways of achieving this, but since you're already using #angular/material, you can take advantage of the #angular/cdk and it's ScrollDispatchModule (see docs).
It allows you for easy and clean observing of scroll events for registered elements, outside of the NgZone, meaning it will have small impact on the performance.
See the example stackblitz:
https://stackblitz.com/edit/angular-npdbtp
First, you need to import ScrollDispatchModule and register provider for ScrollDispatcher:
import {ScrollDispatchModule, ScrollDispatcher} from '#angular/cdk/scrolling';
#NgModule({
imports: [
(other imports)
ScrollDispatchModule
],
providers: [ScrollDispatcher]
})
export class AppModule {}
Then in your template you can mark an html element with the cdkScrollable directive. This will automatically register it in the ScrollDispatcher.
You can also bind component's style (e.g. opacity) to a property defined in your component:
<div class="scroll-wrapper" cdkScrollable>
<mat-toolbar class="sticky-toolbar" [style.opacity]="opacity">My App</mat-toolbar>
<div>content</div>
</div>
You can make html element sticky using the display: sticky together with top: 0:
.sticky-toolbar {
position: sticky;
top: 0px;
}
Then you will need to inject the ScrollDispatcher and NgZone into your component and define opacity property:
opacity = 1;
constructor(
private scrollDispatcher: ScrollDispatcher,
private zone: NgZone
) {}
Then you can subscribe to scrolled events of the ScrollDispatcher. Those are emitted for all the registered components. You can also register to scroll events of a single element - refer to the docs if needed by.
ngOnInit(): void {
this.scrollDispatcher.scrolled().subscribe((event: CdkScrollable) => {
const scroll = event.measureScrollOffset("top");
let newOpacity = this.opacity;
if (scroll > 0) {
newOpacity = 0.75;
} else {
newOpacity = 1;
}
if (newOpacity !== this.opacity) {
this.zone.run(() => {
this.opacity = newOpacity;
});
}
});
}
The ScrollDispatcher runs outside of NgZone, meaning it will not run change detection in the whole application. This allows for better performance, and it's why we're also injecting NgZone and running the property change inside the zone - this calls the proper change detection along the tree of components.

StencilJS | Use CSS "+ Selector " in component

I am working on a web component library with StencilJS, and I have a problem using the CSS + Selector. I have a Breadcrumb web component, which will contain multiple breadcrumb items (web component as well). Every Breadcrumb item after the first item should add > smybol with ::before. Therefore I use the CSS + selector
df-breadcrumb.tsx
export class DFBreadcrumb {
render() {
return <ol class="breadcrumb">
<slot></slot>
</ol>
;
}
}
df-breadcrumb-item.tsx
export class DFBreadcrumbItem {
/**
* Link
*/
#Prop() link: string;
render() {
return this.link ? <li class="breadcrumb-item"><a href={this.link}><slot></slot></a></li> :
<li class="breadcrumb-item"><slot></slot></li>
;
}
}
test.html
<df-breadcrumb>
<df-breadcrumb-item link="#">Start</df-breadcrumb-item>
<df-breadcrumb-item link="#">Library</df-breadcrumb-item>
<df-breadcrumb-item>Item</df-breadcrumb-item>
</df-breadcrumb>
my css rule
.breadcrumb-item+.breadcrumb-item:before {
display: inline-block;
padding-right: .5rem;
color: #6c757d;
content: ">";
}
expected output: Start > Library > Item
current output: Start Library Item
I think this is not working cause Stencil ecapsulates my li tags and their direct parent is not the ol. I read something about using the :host() pseudo class, but could not got it working. Also I have set shadow: falsein my components.
You're right, the problem is the df-breadcrumb-item element.
A simple alternative would be to apply your CSS to the df-breadcrumb-item elements:
df-breadcrumb-item + df-breadcrumb-item:before {
display: inline-block;
color: #6c757d;
content: ">";
}
Alternatively you could add the arrow to the .breadcrumb-item element inside the df-breadcrumb-item component, either depending on a property or by manually checking if the #Element() is the last node.

Resources