styled-components conditional props doesn't work in Material-ui components - reactjs

I'd like to set fade-in, fade-out animation on the Grid component using Material-UI and styled-components. But it doesn't work and there is an error about the conditional prop. Could you tell me how to do that, please?
import React from "react";
import styled, { keyframes } from "styled-components";
import { Grid } from "#material-ui/core";
const fadeIn = keyframes`
0% {
height: 0
}
50% {
height: 50%;
}
100% {
height: 100%;
}
`;
const fadeOut = keyframes`
0% {
height: 100%
}
50% {
height: 50%;
}
100% {
height: 0;
}
`;
const AnimationGrid = styled(Grid)<{ isOpen: boolean }>`
&& {
visibility: ${props => (props.isOpen ? "visible" : "hidden")};
animation: ${props => (props.isOpen ? fadeIn : fadeOut)}
0.3s linear 0s 1 forwards;
}
`;
type AnimationProps = {
isOpen: boolean;
};
const AnimationComp = ({isOpen}: AnimationProps) => {
return (
<AnimationGrid container isOpen={isOpen}>
<Grid item xs={6}>
Here is left section
</Grid>
<Grid item xs={6}>
Here is right section
</Grid>
</AnimationGrid>
)
}
Also there is a error on console. Fade-in animation is fine but fade-out doesn't work.
Warning: React does not recognize the `isOpen` prop on a DOM element.
If you intentionally want it to appear in the DOM as a custom attribute,
spell it as lowercase `isopen` instead. If you accidentally passed it from a parent component,
remove it from the DOM element.
I'd say isOpen prop doesn't work well. Thank you.

Your question consists of 2 parts, so I'll answer it that way.
For the console error: this behavior has been bugging many people. The reason you see this error, is because the property isOpen is passed to the underlying DOM element. However, DOM elements don't support a boolean attribute isOpen.
To work around this, you can use
styled(({ isOpen, ...props }) => <Grid {...props} />)<{ isOpen: boolean }>`...`
For the animation part: you don't see the fade-out animation, because you set visibility: hidden at the start of it. This way, the element disappears immediately. To get similar behavior to visibility: hidden,
you could add opacity: 0 to your animation at '100%'
and set pointer-events: ${props => props.isOpen ? 'initial' : 'none'}.
Hope this helps!

Related

CSS transition not working when changing react state

I want to see animation when I change width of my sidebar. Material UI is used here (AppBar). I think it's because React just re-renders component quickly. How you would do it? I think it would work if i just changed class or added class.
Also it' necessary to understand that content of StyledAppBar will depend on whether it's opened. So I will need re-render so that the content change
Additional question - do you know how to make AppBar render as tag instead oh tag? :)
const Header = () => {
const [open, setOpen] = useState(true);
const StyledAppBar = styled(AppBar)`
background-color: red;
height: 100vh;
width: ${open ? '240px' : '50px'};
transition: width 1s ease-in-out;
position: fixed;
left: 0;
`;
return (
<StyledAppBar>
<Toolbar>
<Typography>MUI SHOP</Typography>
<ShoppingBasket onClick={() => setOpen(prev => !prev)}/>
</Toolbar>
</StyledAppBar>
);
};

How to change other element when hover by using 'styled'

I'm new to Mui and trying to apply animation to components.
What I want to do is, I have four same component and each has its own image.
<MyComponent>some images...</MyComponent>
<MyComponent>some images...</MyComponent>
<MyComponent>some images...</MyComponent>
<MyComponent>some images...</MyComponent>
const MyComponent = styled("div")((theme) => ({
//... some styles.
// scale up when hovered
'&:hover': {
transform: "scale(1.2)",
marginRight: "20px",
}
}));
If I hover a <MyComponent>, I want to scale up hovered one, and scale down others.
Is there any ways to defined such action by using styled???
I would set a state then use a conditional to change styles .. IE
const [hoverState, setHoverState] = useState(false);
<MyComponent
onMouseOver={setHoverState(true)}
onMouseOut={setHoverState(false)}
style={hoverState ? {transform: "scale(1.2)",marginRight: "20px",} : ''}
>
some images...
</MyComponent>
You can do this with css and styled-components.
Note that the styled function is not meant to be called directly, but passed a template literal. Inside the template literal, you can write regular css expressions.
codesandbox
const MyComponent = styled.div`
transform: scale(1);
margin-right: 0px;
:hover {
transform: scale(1.2);
margin-right: 20px;
}
`;

I want the arrow icon to flip up and down every time the state changes.and I want to animate it

I am using react, styled-components.
When state(visible) is set to true, DropMenu box1 and box2 will be displayed.
We want the ArrowDown icon to flip upward when state is true, and downward when false.
I also want to apply an animation when flipping it.
I want to add an animation like the Dropdown in the following site.  
Reference site
code
import "./styles.css";
import styled from "styled-components";
import React, { useState, useCallback } from "react";
import { ArrowDown } from "./ArrowDown";
const Item = styled.div<{ active?: boolean }>`
height: 40px;
width: 300px;
padding: 0px 30px;
&:hover {
background: #fafbfb;
}
`;
const DropMenu = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
color: #899098;
width: 100%;
height: 100%;
font-size: 14px;
font-weight: bold;
gap: 12px;
:hover {
color: gray;
}
div {
display: flex;
align-items: center;
gap: 12px;
}
`;
const DropText = styled.div`
padding-left: 32px;
`;
export const App = () => {
const [visible, setVisible] = useState(false);
const handleDropVisibleChange = useCallback(() => {
setVisible((prevVisible) => !prevVisible);
}, [visible]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<Item onClick={handleDropVisibleChange}>
<DropMenu>
<div>
<span>Menu</span>
</div>
<ArrowDown />
</DropMenu>
</Item>
{visible && (
<div style={{ transition: "all 0.5s ease" }}>
<Item>
<DropMenu>
<DropText>box1</DropText>
</DropMenu>
</Item>
<Item>
<DropMenu>
<DropText>box2</DropText>
</DropMenu>
</Item>
</div>
)}
</div>
);
};
export default App;
TLDR
Change your MenuItem component warpper to something like
const DropMenuWrapper = styled.div<{ visible: boolean }>`
transition: all 0.5s ease;
opacity: ${(props) => (props.visible ? 1 : 0)};
`;
replace the visibility switch mechanism with following
- {visible && (
- <div style={{ transition: "all 0.5s ease" }}>
+ <DropMenuWrapper visible={visible}>
similar action can be added to the arrow-down icon also with style
(The ArrowDown SVG icon must accept style if it is custom written component)
<ArrowDown
style={{
transition: "all 0.5s ease",
transform: `rotate(${visible ? 0 : "0.5turn"})`
}}
/>
Why this happened:
When a component (sub-component/element) is mounted in react, it starts a complete life cycle toward browser paint.
So it is must have the property which causes the element to animate, for example, I added the opacity transition to the example itself, forcing it to animate in the first look and in disappearing.
Although it comes with some performance cost of having unseen elements still in the dom (but not visible), making it bad for accessibility too, it is the simplest way to achieve this behavior.
Consider this example If you have an animated element, does it show the animation if you refresh the browser if the answer is yes, it will show animation in react too.
Another way of doing some animation in react.
Using third-party library react-transtion-group which is heavily used in lots of packages e.g. Material-UI.
In this case you can also trigger the end event and start to unmount the component as the animation disappears and end completely.
Using framer motion
If you want to take your understanding of what is needed for the transition when the component is unmounted and removed from aka dom, I highly encourage you to read the animation section of svelte docuementation
What I did, what might look stupid to more advanced developers was implement a simple check that would switch icons.
Note: This doesn't have an animation, though. It's just a simple switcharoo
define state in component
const [isOpen, setIsOpen] = useState(false);
Check whether icon is open or closed, if open, ExpandLessIcon, if closed ExpandMoreIcon.
<ExpandLessIcon
onClick={() => {
setIsOpen(!isOpen);
}}
/>
) : (
<ExpandMoreIcon
onClick={() => {
setIsOpen(!isOpen);
}}
/>
)}
The way it works is, once clicked, it'll just flip the true false state over and over, which in turn will change icons.

How to add a smooth sliding transition effect to a react-bootstrap based Column component?

To summarize the problem, I am trying to work through a component that has 2 columns (react-bootstrap Column component). The left container being collapsible and the right always there. On a click of a button, I will toggle show/hide on the left column. It works fine but the behavior is rugged. Whereas, I want to add a transition effect to achieve a smoother slide behavior.
More info...I have a page where I want to display products and filter the products based on different properties such as cost, size, category etc. So, I use a container inside and got 2 columns inside it a row.
Problem: Transition not so smooth...
Something like this in the code below,
import React, { Component } from 'react';
import { ProductsViewFilter } from '../../Controls/ProductsViewFilter/ProductsViewFilter';
import { ProductsView } from '../../Controls/ProductsView/ProductsView';
import { Container, Row, Col, Collapse } from 'react-bootstrap';
import './ProductsViewContainer.css';
export class ProductsViewContainer extends Component {
constructor(props) {
super(props);
this.state = {
filterExpanded: false
}
}
render() {
return (
<Container className='products-view-filter-container'>
<Row>
<Collapse in={this.state.filterExpanded}>
<Col sm={2} className={this.state.filterExpanded ? 'products-view-filter products-view-filter-transition-slide-in' : 'products-view-filter products-view-filter-transition-slide-out'}>
<ProductsViewFilter></ProductsViewFilter>
</Col>
</Collapse>
<Col className='products-view-container-productsview'>
<div className='slider-icon-div' >
<img className='slider-icon' src='/images/icons/slider.svg'
onClick={(e) => { e.preventDefault(); this.setState({ filterExpanded: !this.state.filterExpanded }); }} />
</div>
<ProductsView></ProductsView>
</Col>
</Row>
</Container>);
}
}
.products-view-filter-container
{
max-width: 100% !important;
}
.products-view-filter
{
background-color: wheat;
transform: translateX(-150px);
transition: transform 400ms ease-in;
}
.products-view-filter-transition-enter
{
transform: scale(0.8);
opacity: 0;
}
.products-view-filter-transition-enter-active
{
opacity: 1;
transform: translateX(0);
transition: opacity 300ms, transform 300ms;
}
.products-view-filter-transition-exit
{
opacity: 1;
}
.products-view-filter-transition-exit-active
{
opacity: 0;
transform: scale(0.9);
transition: opacity 300ms, transform 300ms;
}
.products-view-filter-transition-slide-in {
transform: translateX(0);
}
.products-view-filter-transition-slide-out {
transform: translateX(-100%);
}
.products-view-container-productsview
{
background-color: lavender;
}
.slider-icon
{
height: 1.5rem;
}
.slider-icon:hover
{
cursor: pointer;
}
.slider-icon-div
{
margin-top: 15px;
text-align: left;
}
I would like to see the behavior as something like https://codepen.io/bjornholdt/pen/MpXmmL/.
I know this is easily achievable by using bunch of pure divs. But, my desire is to use bootstrap components as it responsive in mobile devices.
Any advice, suggestion as to how to achieve sticking to Col and Row components with a smooth transitions will be really helpful.
This might be too late, but in case anyone else has this issue...
I have not tested this myself, but try what the docs (https://react-bootstrap.github.io/utilities/transitions/) suggest.
...
Collapse#
Add a collapse toggle animation to an element or component.
Smooth animations
If you're noticing choppy animations, and the component that's being collapsed has non-zero margin or padding, try wrapping the contents of your inside a node with no margin or padding, like the in the example below. This will allow the height to be computed properly, so the animation can proceed smoothly.
...

CSSTransition from react-transition-group does not exit

Trying to get used CSSTransiction component. I need to show a panel which is animated by using css-classes. The first class defines the primary location, the second defines position when the panel is opened (top: 0).
Appearing is working, but exiting does not. onEntered fires, onExied does not.
There is lack of documentation for CSSTransition component, I tried different combinations, but without success. Any ideas?
CSS:
.getSecondLevelPanel{
position: fixed;
left: 450px;
top: -100%;
height: 100%;
width: 400px;
transition: all 250ms ease-out;
}
.getSecondLevelPanelOpened {
top: 0;
}
React component:
import * as React from 'react';
import { CSSTransition } from 'react-transition-group';
export interface Props {
isVisible: Boolean;
children: React.ReactChild;
onClose: () => void;
}
const SecondLevelPanel = (props: Props) => {
return (
<CSSTransition
timeout={{
enter: 0,
exit: 500
}}
in={props.isVisible}
appear={true}
enter={true}
exit={true}
classNames={{
appear: 'getSecondLevelPanel',
enterDone: 'getSecondLevelPanel getSecondLevelPanelOpened',
exit: 'getSecondLevelPanel'
}}
unmountOnExit={true}
onEntered={() => {
alert('entered');
}}
onExited={() => {
alert('exited');
}}
>
<div>
<a onClick={() => props.onClose()}>Close</a>
{props.children}
</div>
</CSSTransition>
);
};
export default SecondLevelPanel;
After searching for answers for sometime, I realised my problem had to do with my toggle function (onClick={() => props.onClose()} in your case).
It seems my toggle function was not working as it was supposed to. Since onEntered is triggered with "in{true}" and onExited is triggered with "in{false}", you have to make sure the boolean is being changed with each click on your anchor tag.
Looking at your code is seems your onClose function only returns a void function instead of a toggle. Try toggling between true and false and pass that into the in{//toggle function} param.
The problem is in your CSS and classNames props on you. The CSSTransition component applies the classes like so.
appear and enter classes are added as soon as the in prop is set to true.
appear-active and enter-active are added right after the appear and enter classes are set.
appear-done and enter-done are added after the duration set in the timeout passed.
the exit classes repeat Step 1-3 just after the in prop is set to false.
The reason your exit calls don't work is probably due to only setting one exit class and 0 set to your enter duration. Try setting your transition rule on the active classes to ensure that the transition is set for the whole duration of the transition and separate the moving parts from the static ones. Like so
.panel{
position: fixed;
left: 450px;
top: -100%;
height: 100%;
width: 400px;
}
.panel-appear,
.panel-enter {
top: -100%;
}
.panel-appear-active,
.panel-enter-active {
top: 0;
transition: all 250ms ease-out;
}
.panel-exit {
top: 0;
}
.panel-exit-active {
top: -100%
transition: all 250ms ease-out;
}
Take a look at this codepen, this is how I create a panel transitions.
Install:
npm install react-transition-group
Usage:
import { CSSTransition } from 'react-transition-group';
<CSSTransition
in={toShow} // boolean value passed via state/props to either mount or unmount this component
timeout={300}
classNames='my-element' // IMP!
unmountOnExit
>
<ComponentToBeAnimated />
</CSSTransition>
NOTE: Make sure to apply below styles using the class property in CSS:
.my-element-enter {
opacity: 0;
transform: scale(0.9);
}
.my-element-enter-active {
opacity: 1;
transform: translateX(0);
transition: opacity 300ms, transform 300ms;
}
.my-element-exit {
opacity: 1;
}
.my-element-exit-active {
opacity: 0;
transform: scale(0.9);
transition: opacity 300ms, transform 300ms;
}

Resources