NextJS: Modify third-party component CSS in different pages - css

With third-party components, the way to include their styles is by importing their stylesheet into _app.tsx or importing the stylesheet into your component that uses the third-party component, as described here: https://nextjs.org/docs/basic-features/built-in-css-support#import-styles-from-node_modules or by adding to next.config.js like so:
// next.config.js
const withTM = require("next-transpile-modules")([
"#fullcalendar/common",
"#fullcalendar/daygrid",
"#fullcalendar/timegrid",
"#fullcalendar/interaction",
"#fullcalendar/react",
"#fullcalendar/list",
To modify the third-party stylesheet, you need to create your own stylesheet and add it to _app.tsx; those modifications might look like this:
// styles/modified-fullcalendar.scss
.fc-col-header {
width: 100% !important;
}
Another option, at least for my use case (Full Calendar) is to use CSS variables as described here in technique 2 on this page: https://fullcalendar.io/docs/css-customization. There was a lengthy thread about this on the Full Calendar issues page, as seen here: https://github.com/fullcalendar/fullcalendar/issues/5393
The problem with all of these methods of customization is that they're global, and so anywhere you use this third-party component it'll look the same. However, in my case, I want to use the component on two different pages, with different styling modifications. With most frameworks, I would simply import the relevant modified stylesheet wherever I needed it, but NextJS doesn't allow that. How can I achieve the modifications I want?

The solution is to wrap the component in a div with a specific class name, then do the css overrides in a nested format for each use case in the override file.
Explanation:
Say your third-party component is FullCalendar. It's being imported and used in the files Foo.tsx and Bar.tsx. In Foo, let's say you want the calendar cells to be green.
To make the modification, you create the file modified-fc.scss and do the following:
// modified-fc.scss
.fc-cell {
background: green !important;
}
You then import modified-fc.scss into _app.tsx in order to apply the styles globally, and you're done. However, this prevents you from changing the cell color to orange in Bar. To circumvent this, just wrap the component:
// Foo.tsx
<div className=".wrapper1">
<FullCalendar/>
</div>
// Bar.tsx
<div className=".wrapper2">
<FullCalendar/>
</div>
and then nest the classes:
// modified-fc.scss
.wrapper1 {
.fc-cell {
background: green !important;
}
}
.wrapper2 {
.fc-cell {
background: orange !important;
}
}
OR
.wrapper1 > .fc-cell {
background: green !important;
}
.wrapper2 > .fc-cell {
background: orange !important;
}

Related

Angular: How to add global CSS (e.g. to the body), but only for one specific page?

How can I add separate CSS for one page in Angular?
This is the CSS I need, as per How to remove the URL from the printing page?:
#media print{
#page{
margin:0;
}
body{
margin:30px;
}
}
But putting CSS into the component with ::ng-deep or ViewEncapsulation.None won't help here, because when navigating away from a page, the CSS of the page isn't deleted.
I've added a Stackblitz, which explains the problem clearly.
I've come up with a potential solution, but it doesn't work:
encapsulation: ViewEncapsulation.None
...
constructor(private renderer: Renderer2) {
this.renderer.addClass(document.body, 'special-print');
}
ngOnDestroy() {
this.renderer.removeClass(document.body, 'special-print');
}
....
....
....
#media print{
#page{
margin:0;
}
body.special-print{
margin:30px;
}
}
Why it doesn't work:
While it would help with <body> CSS, it won't help with #page CSS. Perhaps the question would be better summarized as "How to add global CSS, but remove it when we leave the page?".
Solved!
We print the <style> block directly into the component's HTML, and therefore when the component gets removed, our <style> block gets removed too. (Normally this wouldn't work, but thanks to DomSanitizer.bypassSecurityTrustHtml, Angular won't break our code when running optimizations.)
Here's a StackBlitz.
First, create a new component to handle the work:
component.ts: (This is all we need. We don't need an HTML or style.css file.)
//Inside your local component, place this HTML
//<app-local-css [style]="'body{background:green !important;}'"></app-local-css>
// OR
//<app-local-css [scriptURL]="'/path/to/file.css'"></app-local-css>
#Component({
selector: "app-local-css",
template: '<span style="display:none" [innerHTML]="this.safeString"></span>'
})
export class LocalCSSComponent implements OnInit {
constructor(protected sanitizer: DomSanitizer) {}
#Input() scriptURL?: string;
#Input() style?: string;
safeString: SafeHtml;
ngOnInit() {
if (this.scriptURL) {
let string = '<link rel="stylesheet" type="text/css" href="' + this.scriptURL + '">';
this.safeString = this.sanitizer.bypassSecurityTrustHtml(string);
} else if (this.style) {
let string = '<style type="text/css">' + this.style + "</style>";
this.safeString = this.sanitizer.bypassSecurityTrustHtml(string);
}
}
}
And then use it like this:
mySample.component.html:
<app-local-css [style]="'body{background:green !important;}'"></app-local-css>
// OR
<app-local-css [scriptURL]="'/path/to/file.css'"></app-local-css>
Angular is doing client-side rendering, which is bad news, because you do not have separate pages. You have several possible solutions though:
1. Separate page
You can create another page with or without Angular, which includes the CSS you need and load that page. In the most simplistic approach to achieve this, the other page would have a different URL. If having a different URL is not to your liking, then you could hide your page's content and show the other page inside an iframe. It would admittedly be a hacky solution, but it is a solution.
2. Client-side CSS rendering
Instead of just loading the CSS, you could have a component which would control global CSS rules, matched by your view's name. You would have a template value rendered to a property, like:
#media print{
#page{
margin:0;
}
body{
margin:30px;
}
}
And when you visit the page where this needs to be activated, you would simply initialize a property with a style HTML element that was generated based on the template and added to head. Once you leave the given view, your component would detect that event and would remove() that element. If you choose this solution, then it would be wise to make sure that you are supporting this on more general terms, so that if some new views will have their custom global CSS, then they would be easy to integrate into your project in the future.
3. body classes
You could add/remove some custom-print or whatever class to/from body whenever the style is to be changed. This way you could add the CSS exactly once to your HTML and change the rules accordingly, like:
body.custom-print {
margin: 30px;
}
This would be a neat solution, but the problem in your case is that you have a #page rule as well and I'm not sure how you could make that dependant on body classes or some other HTML attributes. I would conduct quite a few experiments about this if I were you.
4. Iframe staging
You could avoid having that CSS in your main page, but would have a hidden iframe where you would have the CSS and would just copy the content into the CSS and once that's loaded, print that.
Don't change the whole body from apple. Instead, there are a few changes to make.
In the app component, hold a boolean for whether or not you are on apple, and use ngClass for class defined in scss.
Track which route you are on in appComponent, and set isApple accordingly
Add a div around all your html, for container to take full size
Add global html, body setting height to 100% so you see color everywhere
Remove body overriding in apple
so,
appComponent.ts:
isApple: Boolean;
constructor(router: Router) {
router.events.subscribe(v => {
if (v instanceof NavigationEnd) {
this.isApple = v.url === "/apple";
}
});
}
appComponent.html:
<div [ngClass]="{'red':isApple}" class="container">
<p>
There are two components: Apple and Banana. Switching between them will show
the problem.
</p>
<router-outlet></router-outlet>
</div>
appComponent.scss
.red {
background-color: red;
}
.container {
height: 100%;
}
apple.component.scss (remove body)
/*Sample "global" CSS, that affects something outside the current component.*/
::ng-deep {
#media print {
#page {
margin: 0;
}
}
}
styles.scss (global)
html, body {
height: 100%;
}
You can see this altogether at this Stackblitz link
You can add different css files in the component (for instance, app-task.component.ts):
#Component({
selector: 'app-task',
templateUrl: './app-task.component.html',
styleUrls: ['./app-task.component.scss', './styles2.scss', './styles3.scss']
})
In this example, the style files are in the same folder that the component, but this is not the best option: you have to put the files in assets, for example. Also, be careful with the thread of the styles, since the first one you put will be put before the second (obviously).

Prefix css selectors for make styles from API safe

Let's assume that the user can add styles for every component in admin panel and I get it as string in my Node server:
const stylesFromAPI = ".p { color: red } .bg { background: lime }";
How to prefix this styles before append to my document to avoid conflicts?
I need something like CSS modules but working with strings (not as module loader):
const stylesFromAPI = css(".p { color: red } .bg { background: lime }"); // returns hashedClassname685946898456
<SomeCompontent className={stylesFromAPI} />
produces:
<style>
.hashedClassname685946898456 .p { color: red }
.hashedClassname685946898456 .bg { background: lime }
</style>
<div class="hashedClassname685946898456"></div>
Shadow DOM seems like a reasonable option here. You can create your style tags with inside the shadow DOM without having to deal with any selector prefixes. For example, using the react-shadow package:
import root from 'react-shadow';
Then in JSX, something like:
<root.div>
<style type="text/css">
{/* CSS string here */}
</style>
<div>
{/* Stuff here */}
</div>
</root.div>
Check out a working example of this here: https://github.com/joshdavenport/stack-overflow-61566764-react-css-shadow-dom
The main downside here is your styles from outside the shadow DOM will not apply. Those using the shadow DOM for components see this as a good thing, those simply trying to scope CSS do not. Not sure what it is you're scoping, so I can't really tell if that would be an issue for you.
If it is, you could re-import your styles within the shadow DOM, though I can't really point out how to do that without knowing what bundler is in use and how it is in use.
Alternatively you could pull apart your imported CSS using the css package, iterate over the selectors prefixing all with a randomly generated class, and then re-stringify.

Applying CSS stylesheet only to active component

I'm working on a ReactJS app that has a header at the top, a menu on the left, and the "frame" in the middle is where routes and their corresponding components are loaded. I want to be able to apply a CSS stylesheet to specific components only when they are loaded. I also don't want them applied all the time or to the top header or left menu.
My expectation was that adding import 'custom.css'; to a specific component would only apply the stylesheet's styles to that component and it's children when the route is active. Instead, it applies it to the entire page even when the route/component are not loaded.
I understand that an alternative approach is styled components, but, for my use-case, a design company is supplying a stylesheet (which should remain unchanged) that we need to consume only for the sub-module I'm working on and I don't want its styles to affect the rest of the app.
How can I have a stylesheet only applied to my active route/component?
Use simple CSS technique. Suppose you have two components with different css files (say about.css and contact.css). Now consider your both CSS file have one common class with different style properties, like:
about.css
.container{
max-width: 400px;
}
contact.css
.container{
max-width: 500px;
}
Yes in ReactJS both the CSS files will load at the same time and will override any one of the style. so to solve this problem add class to differentiate this styles, like:
about.css
.about-component.container{
max-width: 400px;
}
contact.css
.contact-component.container{
max-width: 500px;
}
If you want apply only when the component is mounted, you can use the lifecycle.
The follow example is based in the idea you are using sass, React, sass-node and have the loaders into webpack.
<pre>
import React from 'react';
import './styles.scss';
class MyComponent {
constructor(props) {
super(props);
this.state = { className: '' }
}
componentDidMount() {
this.setState({
className: 'myOwnClass'
});
}
render(){
return (
<div className={this.state.className}>This is a example</div>
);
}
}
export default myComponent;
</pre>
To be able to only call that specific CSS when you need it you can use CSS Modules. You may need to update your version of react.
When saving your CSS file save it with a ".module.css" eg. "styles.module.css". The CSS in these files can only be used and accessed by hte components where are they are imported. As stated in a tutorial from W3Schools.
Let's say this is your CSS code in styles.module.css:
.container {
color: white;
}
.cont-child {
background-color: red;
}
Then in your JS file you can import the CSS file like this if the JS and CSS files are in the same directory. Make sure you point to the correct path.
import styles from './styles.module.css'
Then in your HTML section you can use it like this:
class Home extends React.Component {
render() {
return(
<main className={ styles.container } >
<div className={ styles["cont-child"]} >
Some div text about something...
</div>
</main>
);
}
}
I currently use both ways to access the selectors, since the styles variable acts like an object. I placed both of them here because the second option is capable of fetching selectors named like "btn-active". Which comes in handy in some situations. Camelcasing is considered cleaner though.
Please note: I originally posted this answer as a reply to a similar question here React CSS - how to apply CSS to specific pages only
I want to be able to apply a CSS stylesheet to specific components
only when they are loaded.
Why not apply the styles inline via React.js?
Step 1. Create the style object for the component:
var componentOneStyle = {
color: 'white',
backgroundColor: 'red'
};
Step 2. Populate the component's style attribute with the style object:
ReactDOM.render(<div style={componentOneStyle}>This is Component One</div>, mountNode);

CSS Specificity with CSS Module

First, let me say I understand that I have a custom component "Card" that I use in one of my routes, "Home".
Card.js
import s from 'Card.css';
class Card {
...
render(){
return (<div className={cx(className, s.card)}>
{this.props.children}
</div>);
}
}
Card.css
.card {
display: block;
}
Home.js
<Card className={s.testCard}>
...
</Card>
Home.css
.testCard { display: none; }
A problem I faced here, is that the card remained visible even though I set the display to none, because of seemingly random CSS ordering in the browser. This ordering did not change even if Javascript was disabled.
To get .testCard to correctly load after .card, I used "composes:":
Home.css
.testCard {
composes: card from 'components/Card.css';
display: none;
}
Apparently this causes css-loader to recognize that .testCard should load after .card. Except, if I disable Javascript, the page reverts back to the old behavior: the .card is loaded after .testCard and it becomes visible again.
What is the recommended way to get our page to prioritize .testCard over card that behaves consistently with or without Javascript enabled?
As I'm using CSS modules, charlietfl solution wouldn't really work as is. .card is automatically mangled to a name like .Card-card-l2hne by the css-loader, so referencing it from a different component wouldn't work. If I import it into the CSS file of Home.css, that also doesn't work, because it creates a new class with a name like .Home-card-lnfq, instead of referring to .Card-card-l2hna.
I don't really think there's a great way to fix this so I've resorted to being more specific using a parent class instead.
As an example, Home.js:
import s from 'Home.css';
import Card from './components/Card';
class Home {
...
render(){
return (
<div className={s.root}>
<Card className={s.testCard}>Hi</Card>
</div>
);
}
}
Home.css
.root {
margin: 10px;
}
.root > .testCard {
display: none;
}
This way, we don't need to know what class names component Card is using internally, especially since in cases like CSS Modules or styled components, the class name is some unique generated name.
I don't think I would have come to this solution if it wasn't for charlieftl's solution, so thank you very much for that.
Just make the testCard rule more specific by combining classes
.card {display: block;}
.card.testCard { display: none; }

CSS Modules & ReactJS: Parent and child CSS classes in different components

So I am building a react application and have a quick question. If I have two separate components:
and
with CSS classes navigation.css and navigationLogo.css respectively. In navigation.css I have a class named .main and in navigationLogo.css I want to have a class like so:
.main .main_in_logo {
color: red;
}
But with CSS Modules I am unable to do this, any ideas on a work around?
I just feel that the explanations here are not complete enough. In css you do .parentSelector .childSelector in order to select the child. The same rule is for css modules, but in your html/jsx you should add to the parent and the child the relevant className -> styles.parentSelector , styles.childSelector.
<div className={styles.container}>text</div>
This way you can have in your css something like:
.banner .container{
background-color:reb;
}
.banner .container{
background-color:blue;
}
Sometimes you use libraries and you want to change something somewhere down the DOM inside the library and you can't change its source code. In this case you can use the :global like this:
.parentElement :global(div)
.parentElement :global(#some-lib-element-selector)
I was looking for the same problem and didn't find the solution here, maybe because the post is 3 years old. The accepted answer is, in my opinion but not mine only, not scalable.
I don't really know if this is something new, but I found out what I would do in vanilla CSS adapted to CSS modules.
Here is what I did and fully suits my needs:
/* parent.css */
.main {
...some CSS...
}
/* logo.css */
#value main from "./parent.css";
.logo {
...some CSS...
}
.main .logo {
color: red
}
Here, we are using #value, which is a CSS modules variable and allows us to bind with another file to build a selector including the final name of the parent "main".
As strange as it looks to me, it took some time to find out about this solution, I hope this will save some time and help other people!
Why you need to create .main .main_in_logo - the main idea of styles with parent elements its not to broke your css with other styles in the future. But its impossible with css modules, because your styles will be unique forever.
But even you really need it you can use global css for these 2 components - documentation about global css for react-css-modules.
The child component should not have a css rule that is dependent upon the parent css classname.
the child should just be:
.main_in_logo { color: red; }
If you need to define styles that involve both parent and child, then the easiest way is to define the styles completely in the parent:
/* navigation.css */
.main .main_in_logo {
color: red;
}
Then have the parent pass the css class to the child and tell the child to use it:
// Navigation.js
<NavigationLogo className={navigationCss.main_in_logo} />
// NavigationLogo.js
<div className={"foo " + this.props.className}>stuff</div>
You don't need to be specify which child class you are referring to when using CSS modules in ReactjS.
so doing:
.main_in_logo {
color: red;
}
will be enough in the stylesheet.
I ended up using CSS the normal way but with BEM convention.
I mean after all, what the CSS modules do is adding the [this_name].module.css to your css classes anyway. If you typed it correctly in the first place, there's no need of using this. It's just a new abstract that allow newbies so they can just do stuff without having to worry about class names clashing.
// Main.jsx
import './Main.css'
import Logo from './Logo.jsx'
const Main = () => {
return (
<div className="main">
<Logo className="main__logo" />
</div>
)
}
/* Main.css */
.main {/* do magic */}
.main__logo {/* do magic but for Logo component */}
So maybe you had Logo component like this..
// Logo.jsx
import './Logo.css'
const Logo = () => {
return (
<div className="logo">
<img className="logo__img" />
</div>
)
}
/* Logo.css */
.logo {/* do magic for logo */}
.logo__img {/* do magic for logo's image */}
This feels much more natural.

Resources