CSS Transition not working with react and styled components - css

I have a problem with css transition, i use styled component and the element add its className based on the changing of react useState which is triggered by onClick back and forth,
here is the part of the code that dont work as expected:
export const SearchProduct = ({ product }) => {
const [descStatus, setdescStatus] = useState(false);
const handleDesc = () => {
setdescStatus(!descStatus);
};
return (
<li>
<Item>
<Photo>
<img src={`${product.productImg}`} alt={product.productTitle}></img>
</Photo>
<Information>
<h3> {product.productTitle} </h3>
<Desclook>
<div className={descStatus ? 'active' : null} onClick={handleDesc}>
{descStatus ? 'Close' : 'See Desc ...'}
</div>
</Desclook>
{descStatus && (
<Description --> this is part that dont work
className={descStatus ? 'showContent content' : 'content'}
>
{product.productDesc}
</Description>
)}
Here is the styled components part :
const Description = styled.p`
margin: 10px;
padding: 0;
transition: all 0.3s ease-in-out;
&.content {
height: 0;
overflow: hidden;
}
&.showContent {
height: 70px;
overflow-y: scroll;
}
`;
Does anybody have any idea what happened with my code here cause i'm kinda new to react and styled component

Remove the check for descStatus and always render <Description> instead.
So instead of this:
{descStatus && (
<Description
className={descStatus ? 'showContent content' : 'content'}
>
{product.productDesc}
</Description>
)}
Do this:
<Description
className={descStatus ? 'showContent content' : 'content'}
>
{product.productDesc}
</Description>
The reason behind this is a CSS transition needs to transition from a different value than the current value. In your code when you check if descStatus is true before rendering, your Description component will never have the className="content" and will always be rendered initially with a height of 70px, so no transition will occur.

Hey you can solve it easily if you send the state as a prop instead of setting className
And you should update the state based on previous state and as useState setter sets the state asynchronously you might need asynchronous version of setState this is irrelevant for this problem but can cause problems in some cases
const handleDesc = () => {
setdescStatus(p => !p);
};
For the styled component part
<Description --> this is part that dont work
show={descStatus}
>
{product.productDesc}
</Description>
and inside the styled component you can handle it like
import styled,{css} from 'styled-components';
const Description = styled.p`
margin: 10px;
padding: 0;
transition: all 0.3s ease-in-out;
//content class styles applied by default
height: 0;
overflow: hidden;
//these styles will be applied only if show is true (css you can import from
//styled component as a named import)
${({show}) => show && css`
height: 70px;
overflow-y: scroll;
`}
`;

Related

Change css className of a div with animated transition

I am replacing the className of a div with button. But while doing this, I want to do it with animated transition while className change. In the code below, the className is changing, but without animated transition. How can I add the animated transition while class is changing?
import React, { useRef, useEffect, useState } from "react";
import classes from "./Card.module.css";
import { ClickAwayListener } from "#mui/base";
function Card(props) {
const [containerStyle, setStyle] = useState(classes.container);
const handleClick = () => {
setStyle(classes.containerMore);
};
const handleClickAway = () => {
setStyle(classes.container);
};
return (
<ClickAwayListener
mouseEvent="onMouseDown"
touchEvent="onTouchStart"
onClickAway={handleClickAway}
>
<div className={containerStyle}>
CONTAINER TITLE
<button className={classes.moreButton} onClick={handleClick}>MORE</button>
</div>
</ClickAwayListener>
);
}
export default Card;
This can be done just by writing some CSS transition in your code. You may do something like this :
.containerMore {
background-color: #05040f;
color: white;
height: 220px;
width: 220px;
border-style: solid;
text-align: center;
transition-property: background-color, width, height;
transition-duration: 1s;
transition-timing-function: linear;
}
now when the state is changing from the initial value classes.container to updated value classes.containerMore .it makes this change in the state a little smoother.
I made a demo in codesandbox that applies How You can add the animated transition while class is changing.

How do I reference another component's styled-components generated className while creating a (hover) rule in a different component?

I'm creating a menu with styled-components and React, and want the color of the icon to change on hover, but I need it to change when the icon's parent is hovered, so that hovering the text next to the icon also activates the icon's hover styles. Here is the code I'm using to get close:
import React from 'react';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
const Menu = styled.div`
display: flex;
flex-direction: column;
`;
const LinkContainer = styled.div`
display: flex;
flex-direction: row;
`;
const FontAwesomeIconExtended = styled.div`
flex: 1;
display: grid;
place-items: center;
height: 40px;
width: 40px;
padding: 10px 2px;
border-radius: 10px;
transition: color 0.5s ease;
color: ${(props) => (props.$isactive ? '#fff' : '#CBE3EB')};
background: ${(props) =>
props.$isactive
? 'linear-gradient(96.34deg, #004157 0%, #0090b2 100%)'
: '#fff'};
${LinkContainer}:hover & {
color: ${(props) => (props.$isactive ? '#fff' : 'green')};
} /* Problem occurring here */
`;
const LinkText = styled.div`
flex: 1 0 100px;
`;
function NavLink({ ...props }) {
return (
<Link to={props.path}>
<LinkContainer $isactive={props.$isactive}>
<FontAwesomeIconExtended
$isactive={props.$isactive}
icon={props.icon}
size='2x'
as={FontAwesomeIcon}
/>
<LinkText $isactive={props.$isactive}>{props.name}</LinkText>
</LinkContainer>
</Link>
);
}
export default function NavMenu() {
return (
<Menu>
<NavLink path='/' name='Home' icon='house' $isactive />
<NavLink path='/profile' name='Profile' icon='user' />
<NavLink path='/payments' name='Payments' icon='credit-card-front' />
<NavLink path='/contracts' name='Contracts' icon='file-contract' />
<NavLink path='/messages' name='Messages' icon='mail-bulk' />
<NavLink path='/messages' name='Documents' icon='folders' />
<NavLink path='/locations' name='Transfer' icon='truck-moving' />
</Menu>
);
}
The way you reference another styled component in a later component is very clever, but in this case when it creates the hover rule, it creates without consideration of the different type of parent container ($isactive === true, or $isactive === false), so all LinkContainers have two rules for hovering, and use the last defined rule. This can be seen by moving $isactive to the last NavLink component.
Here is a screenshot of the devtools showing what I mean about the two hover rules not taking into consideration the parents class, just the general type of the parent.
I think the solution might involve being specific about the two types of LinkContainer's classNames while creating the hover rule, but that doesn't seem well supported. Thanks for taking a look.
Increase the specificity by repeating the class name, using another '&'
${LinkContainer}:hover && {

Using Prismjs for syntax highlighted code blocks is breaking layout on mobile - <pre> element wont take dynamic width

I'm using Prismjs alongside Mdx for a code-related blog. I'm using it to show code blocks in a manner consistent with other blogs.
I'm running into an issue where the rendered code blocks (inside a <pre> element are too wide on my mobile layout. For now I am content to have things scroll on the horizontal axis. I'm 99% certain that the <pre> elements are what's breaking the layout because when I comment them out of the blog post, the layout works as expected.
Specifically, I'm using a package called prism-react-renderer (alongside Gatsby), and the code I have for the CodeBlock element (that handles the syntax highlighting) is more or less verbatim from the documentation for prism-react-renderer, but is included here for convenience:
import React from 'react'
import Highlight, { defaultProps } from 'prism-react-renderer'
import theme from 'prism-react-renderer/themes/nightOwl'
const CodeBlock = (props) => {
const className = props.children.props.className || ''
const matches = className.match(/language-(?<lang>.*)/)
return (
<Highlight {...defaultProps} code={props.children.props.children.trim()} language={
matches && matches.groups && matches.groups.lang
? matches.groups.lang
: ''
}
theme={theme}>
{({ className, style, tokens, getLineProps, getTokenProps }) => (
<pre className={className} style={{ ...style }}>
<code>
{tokens.map((line, i) => (
<div key={i} {...getLineProps({ line, key: i })}>
{line.map((token, key) => (
<span key={key} {...getTokenProps({ token, key })} />
))}
</div>
))}
</code>
</pre>
)}
</Highlight>
)
}
export default CodeBlock
This is the component used in the blog post template that handles rendering the .mdx files into HTML:
import React from 'react'
import { Link, graphql } from 'gatsby'
import { MDXRenderer } from 'gatsby-plugin-mdx'
import { MDXProvider } from '#mdx-js/react'
import Layout from '../components/layout'
import CodeBlock from '../components/code-block'
const components = {
pre: CodeBlock
}
const BlogPostTemplate = ({ data, pageContext, location }) => {
const post = data.mdx
const { previous, next } = pageContext
return (
<Layout>
*** Removed irrelevant component ***
<MDXProvider components={components}>
<div className='blog-post-wrapper'>
<article className='blog-post-content'>
<header>
<h1>
{post.frontmatter.title}
</h1>
<time dateTime={post.frontmatter.date}>
{post.frontmatter.date}
</time>
</header>
<MDXRenderer>{post.body}</MDXRenderer>
</article>
<footer className='blog-post-footer'>
*** Removed irrelevant components ***
</footer>
</div>
</MDXProvider>
</Layout>
)
}
export default BlogPostTemplate
I have tried a few different things: flex shrink, applying overflow-x: scroll and overflow-x: auto to both the <pre> element and its parents. When I apply a fixed width to the <pre> element and overflow-x: scroll I can get the behavior I want but I'd like to not have to use a fixed width on this if possible. The .css I have looks like this, including some obviously ineffectual styles:
.blog-post-wrapper {
display: flex;
flex-direction: column;
overflow-y: scroll;
overflow-x: scroll;
}
.blog-post-content {
flex-grow: 1;
margin-bottom: 2rem;
width: 100%;
display: flex;
flex-direction: column;
overflow-y: scroll;
overflow-x: scroll;
}
.blog-post-content .prism-code {
padding: 20px;
border: 3px solid red;
flex-shrink: 1;
overflow-y: scroll;
overflow-x: scroll;
}
I'll attach images of the way the <pre> element is rendering presently, in inspector:
And this is how it looks if I set a fixed width (in inspector):
It's probably too late, but I had the same issue and I was able to fix it by
Adding max-width css property to the main layout. The value should be equal to window.screen.width. I had to use the following hack to be able to get the screen size:
const [windowWidth, setWindowWidth] = useState(width)
useEffect(() => {
setWindowWidth(window.screen.width)
}, [])
Adding overflow: scroll to the pre in the CodeBlock
Not ideal, but I found this combination of CSS properties working together:
pre code {
display: inline-block;
width: 80vw;
overflow-x: auto;
}

How can I set background image by dynamic names with styled-component?

I'm trying to set background-image with styled component. In the code below, I want to set background image with different divs, with img_01, img_02, img_03, .....
I saw many cases importing img path and use that, but I want to use dynamic name depending on the variable. Do I need to import all the images and set each of them?
import styled, { css } from 'styled-components';
const Div1 = styled.div`
width: ${props => props.width};
background: url('asset/images/img_0${props=>props.num}.png');
`;
class Main extends Component {
render() {
return (
<div>
<Div1 width={"475px"} num={1}>
</Div1>
<Div1 width={"154px"} num={2}>
</Div1>
</div>
)
}
}
How Can I do that without importing all ?
You can write it like that :
const Div1 = styled.div`
width: ${props => props.width};
background-image: ${props => `url('asset/images/img_0${props.num}.png')`};
`;

How to style body tag with CSS-in-JS approach?

I am a beginner to CSS-in-JS and emotion, and trying to port a sass react app to emotion. Right from the start I already have the issue of not knowing how to style the body tag.
Do people generally use document.body.style to do this? I can't find this covered anywhere ...
Suppose I want to port following code to emotion, how would that be accomplished?
$bodyFillColor: rgb(218, 236, 236);
body {
margin: 0;
padding: 0;
min-height: 100vh;
max-width: 100vw;
background-color: $bodyFillColor;
.noScroll {
overflow: hidden;
}
}
Have any best practices evolved yet that cover this?
With Emotion you can set something up, like the following create-react-app example, to inject global styles:
import React from 'react';
import ReactDOM from 'react-dom';
import { Global, css } from '#emotion/core'
const bodyFillColor = `rgb(218,236,236)`;
class App extends React.Component {
render() {
return(
<div>
<Global
styles={css`
body {
background: ${bodyFillColor};
margin: 0;
padding: 0;
min-height: '100vh';
max-width: '100vw';
}
`}
/>
<Global
styles={{
'body.noScroll': {
// Prevent scrolling; conditionally activate this
// in subcomponents when necessary ...
overflow: 'hidden',
},
}}
/>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
This shows an example of injecting a style on the body and also assigning a class to the body that can conditionally be activated later on.
eg.
{this.state.activate && <Global styles={{`stylesetc`}}/>}
https://emotion.sh/docs/globals
Alternative
StyledComponents uses a CSS-in-JS approach and works great with React applications. This is a technique I've used in the past straight from the documentation:
import { createGlobalStyle } from 'styled-components'
const GlobalStyle = createGlobalStyle`
body {
color: ${props => (props.whiteColor ? 'white' : 'black')};
}
`
// later in your app
<React.Fragment>
<Navigation /> {/* example of other top-level stuff */}
<GlobalStyle whiteColor />
</React.Fragment>
If you're using react application you can create index.css file and set your wanted properties for the body. Then you must import the index.css file in your index.js file and the changes will take place.
As per the question if the task is as small as changing body's background color in js then below approach can also be followed any where in your code most probably in App.js
if(theme==='dark')
document.body.style.background = '#000'
else
document.body.style.background = '#FFF'
No need to use a whole styling library for it.
Also i tried editing document.body.style, you can try that too according to below example
if(theme==='dark')
bgColor = '#000'
else
bgColor = '#FFF'
document.body.style= `background: ${bgColor}`
Remember following 2nd approach you may overwrite whole body style so please take care of that.
I hope this helps :)

Resources