Can I set styles to document body from styled-components? - javascript

I want to know if there is a possibility to cast styles from styled-components to wrapping element, like <body> tag in the way that looks like this:
class SomePageWrapper = styled.div`
body {
font-size: 62.5%
}
`

No, but you can use the global inject function to set stuff on your body like this:
import { injectGlobal } from 'styled-components';
injectGlobal`
#font-face {
font-family: 'Operator Mono';
src: url('../fonts/Operator-Mono.ttf');
}
body {
margin: 0;
}
`;
The example is from here: https://www.styled-components.com/docs/api#injectglobal

As it turns out - you can't set styled components to outer elements. This violates the philosophy of encapsulation - a benefit from styled-components.
So the way to do this would be to add a new class to body element called classList via JS in the parent component with componentDidMount() and remove it with componentWillUnmount().

For v4 styled-components, use createGlobalStyle .
import { createGlobalStyle } from 'styled-components'
const GlobalStyle = createGlobalStyle`
body {
color: ${props => (props.whiteColor ? 'white' : 'black')};
}
`
// later in your app
<React.Fragment>
<GlobalStyle whiteColor />
<Navigation /> {/* example of other top-level stuff */}
</React.Fragment>

I think you can't do that.
class SomePageWrapper = styled.body`
font-size: 62.5%
`
So,when you put <SomePageWrapper/> in the render,it turns to be <body></body>.Then you need to put it in the root part,to replace <body>.
But how do you replace the <body> you have in index.html.When you can't replace it,you will end up two <body> in browser,or something weird will happen.
Just simply use css file for

Related

Adding multiple local fonts to NextJs 13

I'm trying to add multiple local fonts to nextjs 13.
The documentation says to do the following for a single file.
I tried importing the two files in the following way:
import '#/styles/globals.css';
import localFont from '#next/font/local';
const myFonts = localFont({
src: '../public/Fonts/AnultraSlabRegular.ttf',
variable: '--Anultra-Slab',
});
const myFonts2 = localFont({
src: '../public/Fonts/IntroRegular.ttf',
variable: '--Intro-Regular',
});
export default function App({ Component, pageProps }) {
return (
<main className={'${myFonts2.className} ${myFonts.className}'}>
<Component {...pageProps} />
</main>
);
}
this method did not work when assigning a font-family to specific elements.
Thanks in advance!
Are you using Tailwind? I see you are defining a variable for the font but not using it here, and there are a couple of extra steps to get Tailwind wired up.
The only syntax issue I see is the backticks on the class name that you already mentioned.
import '#/styles/globals.css';
import localFont from '#next/font/local';
const myFonts = localFont({
src: '../public/Fonts/AnultraSlabRegular.ttf',
variable: '--Anultra-Slab',
});
const myFonts2 = localFont({
src: '../public/Fonts/IntroRegular.ttf',
variable: '--Intro-Regular',
});
export default function App({ Component, pageProps }) {
return (
<main className={`${myFonts2.className} ${myFonts.className}`}>
<Component {...pageProps} />
</main>
);
}
you can also use the variable you defined in your CSS once you have them loaded. It should look something like:
button {
font-family: var(--Anultra-Slab);
}
I had similar issues where the font was being loaded but not applied because a global style was overriding the font-family to nothing, making it default to the system font. I expanded font-family in the "computed" view in dev tools to find the issue, then added font-family: inherit to the offending class to fix it.
You should be able to apply the font-family: var(--Anultra-Slab) in the browser dev tools to make sure the font is being loaded correctly, poke around, and locate the issue.
Hope this helps!
If you are using tailwind then this is how I did it -
Firstly add the font faces at the top in your root styles file (probably named as either globals.css or style.css):
#font-face {
font-family: "IntroRegular";
src: url("/Fonts/IntroRegular.ttf");
font-weight: 400;
font-display: swap;
font-style: normal;
}
#font-face {
font-family: "AnultraSlabRegular";
src: url("/Fonts/AnultraSlabRegular.ttf");
font-weight: 400;
font-display: swap;
font-style: normal;
}
And then in your tailwind.config.js file, extend the
fontFamily to include your font like this:
theme: {
extend: {
fontFamily: {
IntroRegular: ["IntroRegular", "sans-serif"],
AnultraSlabRegular: ["AnultraSlabRegular", "sans-serif"]
}
}
}
Now, you can use these fonts just like you use tailwind ones, for example:
<main className="font-AnultraSlabRegular">
I had to read the documentation more than once. (Since it is referencing the variable name in the example using Google fonts.) It took me quite some time to only discover adding multiple classes should not be done by using the className (as most examples use) to apply one font like;
// Do not use className for importing multiple local fonts
<body className={`bg-black ${nantes.className} ${spezia.className}`}>
{children}
</body>
But by using the variable name like this;
// use variable for importing multiple fonts which you can use throughout the application
<body className={`bg-black ${nantes.variable} ${spezia.variable}`}>
{children}
</body>
To be complete, this is what I have right now, which works;
layout.tsx
import localFont from '#next/font/local'
const spezia = localFont({
src: '../fonts/FaroVariableWeb.woff2',
variable: '--font-spezia'
})
const nantes = localFont({
src: '../fonts/Nantes_Variable_Upright_Web.woff2',
variable: '--font-nantes'
})
import './globals.css'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
{/*
<head /> will contain the components returned by the nearest parent
head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
*/}
<head />
<body className={`bg-black ${nantes.variable} ${spezia.variable}`}>
{children}
</body>
</html>
)
}
global.css
#tailwind base;
#tailwind components;
#tailwind utilities;
html, body {
font-family: var(--font-spezia);
color:blueviolet;
}
h1 {
font-family: var(--font-nantes);
color: aqua;
}

Cannot override CSS Module class style with more specific one

I'm having a problem applying a CSS Module to a React Component (in an app created with 'create-react-app' and using webpack). I have an element that has a 'className' and an 'id' attribute. The 'className' style get applied, but when I attempt to 'override' it with a more specific 'id' style, it doesn't take. My Use Case is that the more-specific style override is in a separate file, but the problem exists with the two styles in the same CSS file.
Here's my component:
import React from 'react';
import styles from './styles.module.css'
class QuestionTag extends React.Component {
render() {
let c1 = "qumultiline";
return (
<>
<div className={`${styles[c1]}`} id={`${c1}`}>
Hello there
</div>
</>
);
}
}
export default QuestionTag;
This is how the element gets rendered:
<div class="styles_qumultiline__iXUv1" id="qumultiline">Hello there</div>
Here's styles.module.css:
.qumultiline {
background-color: #006699;
color: white;
}
div#qumultiline {
background-color: red;
color: red;
}
Is there something special I need to do to be able to override the 'className'?

Pass Styled component as className to another component

I am using react-paginate library that accepts class names in props to style internal components:
<ReactPaginate
...
breakClassName={'break-class-name'}
containerClassName={'pagination-class-name'}
activeClassName={'active-class-name'} />
I am also using styled-components, so I would like to avoid style react components using plain CSS or createGlobalStyle. Is there any way to pass styles from styled-components to breakClassName and containerClassName props of ReactPaginate?
I tried this, but it does not work, because Button.toString() returns class name without styles:
const Button = Styled.button`
color: green;
`
export default () => (
<ReactPaginate
...
breakClassName={Button.toString()} />
)
Code bellow also does not work, because Button has class name my-class-name, but this class name is without styles:
const Button = Styled.button.attrs({ className: 'my-class-name' })`
color: green;
`
export default () => (
<ReactPaginate
...
breakClassName='my-class-name' />
)
Is there any way to do that?
have you tried to make <Button as={Component} />?
UPDATE:
You can use wrapper with classes
const ReactPaginateStyled = styled(ReactPaginate)`
&.break-class-name {
//your styles
}
&.pagination-class-name {
//your styles
}
&.active-class-name {
//your styles
}
`;
I don't think there's a simple way to pass a className down like that. But if you're trying to style a component from a library, they should allow for this pattern to work:
const Button = styled(SomeLibraryComponent)`
color: green;
`;
Styled components will "wrap" around the base component and try to pass styles to it. Most library components should work with this but I can't speak for all of them.

Styled-component object can't observe mobx changes

I would like an HOC generated by styled-components to re-render when one of its properties get changed. I'm using MobX for change detection.
This doesn't respond to changes, I think I understand why. The question is if there is a simple workaround to make it work.
const DemoComponent = observer(styled.div`
background-color: ${props => props.myObject.myObservableIsTrue ? 'red' :
'green'};
`);
It's hard to tell by this little snippet, but one of my guesses would be you are not injecting any store, so currently, no store is being connected to your component.
here's a simple example of how I used styled-components with mobx if it helps:
EDITED:
I've updated the code example.
Do you know the Container / Presentational pattern?
This was the missing link.
In order to keep your renders as little as possible
you need to separate your stateful component from each other.
Spread them across a Container component (aka Dumb Components)
This way you separate state concerns and render only the component with the changed state.
UPDATED:
https://codesandbox.io/s/zxx6o2pq3l
Sorry!!! A bit of a hack job, but attempt to bring your entire code inside the #inject("store") class:
import React from "react";
import { observer, inject } from "mobx-react";
import styled, { css } from "styled-components";
#inject("store")
#observer
export default class OtherComponent extends React.Component {
render() {
const MyWrapper = (store) => {
const Wrapper = styled.div`
border: 1px solid black;
display: flex;
justify-content: space-between;
color: ${({ color }) => color};
border: 2px solid ${({ color }) => color || "black"};
padding: 10px;
margin-bottom: 10px;
`;
return (
<Wrapper {...store}>
styled-component
<button onClick={store.changeColor}>change color</button>
</Wrapper>
);
}
const { store } = this.props;
return (
<div>
{
MyWrapper(store)
}
</div>
);
}
}
Mobx is actually read like this: #inject("store") #observer export default class...
So it really an extension of an extended component; only wrapped variables will apply!

Next.js custom class on body using _document.js

I'm using the example here
https://github.com/zeit/next.js#custom-document
and I want to change the custom_class on the body tag
I have tried passing props on calling components but nothing works. I want to add a 'dark' class based on some condition but I have no idea how to change this.
EDIT:
I'm not sure this is possible. After getting help from the nextjs slack channel I was told
"Pages get rendered client side with next/link
And body is not touched after server side rendering"
I'm going to try and wrap things in another tag that I generate and try and change that.
The cleanest solution I found is not declarative, but it works well:
import Head from "next/head"
class HeadElement extends React.Component {
componentDidMount() {
const {bodyClass} = this.props
document.querySelector("body").classList.add(bodyClass || "light")
}
render() {
return <Head>
{/* Whatever other stuff you're using in Head */}
</Head>
}
}
export default HeadElement
With this Head component you would pass in "dark" or "light" (following the question's example for light/dark themes) as the bodyClass prop from the page.
As of current Next (10) and React (17) versions, If you'd like to change the body class from a page, you can can do it like this:
// only example: maybe you'll want some logic before,
// and maybe pass a variable to classList.add()
useEffect( () => { document.querySelector("body").classList.add("home") } );
Please note that useEffect is a Hook, so it can be used only in modern function components, not class ones.
The useEffect Hook can be used instead of the 'old' componentDidMount LifeCycle.
https://reactjs.org/docs/hooks-effect.html
https://reactjs.org/docs/hooks-overview.html
https://reactjs.org/docs/components-and-props.html
The only way to directly access the body tag on Next js is via the _document.js file but this file is rendered server side as stated in the Documentation.
The work around I suggest is to access the body tag from the component directly. Example:
const handleClick = (e) => {
document.querySelector('body').classList.toggle('dark')
}
<div onClick={handleClick}>Toggle</div>
The solution I came up with for my situation where I don't need a lot of unique body classes was to create a component called BodyClass.js and import that component into my Layout.js component.
BodyClass.js
import { Component } from 'react';
class BodyClass extends Component {
componentDidMount() {
if (window.location.pathname == '/') {
this.setBodyClass('home');
} else if (window.location.pathname == '/locations') {
this.setBodyClass('locations');
}
}
setBodyClass(className) {
// remove other classes
document.body.className ='';
// assign new class
document.body.classList.add(className);
}
render() {
return null;
}
}
export default BodyClass;
Layout.js
import Header from './Header';
import Footer from './Footer';
import BodyClass from '../BodyClass';
const Layout = ({ children }) => {
return (
<React.Fragment>
<BodyClass />
<Header />
{children}
<Footer />
</React.Fragment>
)
}
export default Layout;
Unfortunately, next/head does not allow specifying body class like React Helmet does:
<Helmet>
<body className="foo"/>
</Helmet>
Luckily, you can use this.props.__NEXT_DATA__.props.pageProps inside _document.js to get access to the page props and use them to to set the class on the <body> element:
import Document, { Html, Head, Main, NextScript } from 'next/document';
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
const pageProps = this.props?.__NEXT_DATA__?.props?.pageProps;
console.log('pageProps => ', pageProps);
return (
<Html>
<Head />
<body className={pageProps.bodyClassName}>
<Main />
<NextScript />
</body>
</Html>
);
}
}
More info here.
Directly, it's not possible. But with another way, you can use framework like tailwind and insert the class directly in your css.
Here an example using tailwind:
.dark {
background-color: black;
color: white;
}
body {
#apply dark
}
It's not exactly the answer you are looking for, but I was able to accomplish the same thing functionally by passing a prop through the component which then updates a class on a top-level element that wraps everything in my app and sits just under the . This way each page can have it's own unique class and operates the same way I'd use a body class.
Here is where I pass the prop through
<Layout page_title={meta_title} page_desc={meta_desc} main_class={'contact_page'}>
And here is where I use it in the Layout component:
<main className={props.main_class}>
<Header page_title={props.page_title} page_desc={props.page_desc} />
{props.children}
<Footer />
</main>
This is my CSS only solution:
(I first looked at https://stackoverflow.com/a/66358460/729221 but that felt too complex for changing a bit of styling.)
(This uses Tailwind CSS, but does not need to.)
index.css:
body:has(#__next .set-bg-indigo-50-on-body) {
#apply bg-indigo-50;
}
layout.tsx:
//…
return (
<div className="set-bg-indigo-50-on-body">{children}</div>
)
This will work, as long as that div is a direct parent of <div id="__next">. Otherwise you need to update the css :has-rule.

Categories

Resources