I'm working on a Next.js project where the menu opens with a click on a burger button. For this I toggle a class on the button, and the styles for this class use styled-components.
When I click on the button it changes like expected, but when I add a CSS transition it's still not animated. I tested the same code in vanilla HTML and it's working well.
Where did I go wrong?
This is my code:
Burger js:
<BurgerStyled
onClick={() => setIsBurgerOpen(!isBurgerOpen)}
className={BurgerOpen}
>
<span />
<span />
<span />
</BurgerStyled>
OnClick behaviour:
const [isBurgerOpen, setIsBurgerOpen] = useState(false);
const BurgerOpen = isBurgerOpen ? "BurgerOpen" : "";
Styled component:
const BurgerStyled = styled.div`
width: 32px;
height: 21px;
position: relative;
span {
position: absolute;
left: 0;
background-color: var(--gray);
width: 100%;
height: 3px;
transition: all 0.3s ease;
&:first-child {
top: 0;
}
&:nth-child(2) {
top: calc(50% - 3px / 2);
}
&:last-child {
bottom: 0;
}
}
&.BurgerOpen span {
&:first-child {
transform: rotate(45deg);
}
&:nth-child(2) {
width: 0;
}
&:last-child {
transform: rotate(-45deg);
}
}
`;
Thanks for your answers!
You can pass the property to your styled component like so:
JSX:
return (
<BurgerStyled
onClick={() => setIsBurgerOpen(!isBurgerOpen)}
open={isBurgerOpen}
>
<span />
<span />
<span />
</BurgerStyled>
);
Style:
const BurgerStyled = styled(({ open, ...restProps }) => <div {...restProps} />)`
${({ open }) => open ? ` styles open here ` : ` styles closed here `};
`;
What you do here: you pass the open prop to your styled component, and then pass all other props to the div. The open prop is now available in your style template string. This prevents the open prop to be passed to the DOM-element, because a <div> DOM-element has no open property, so passing it through to the DOM would result in warnings.
You can try svg and transition to have the desired output. Check my sample code below, it might help you.
import { motion } from './framer-motion'; //install it with npm
const [isBurgerOpen, setIsBurgerOpen] = useState(false);
const HamburgerMenuContainer = styled.div`
display:flex;
`;
const MenuContainer = styled.div`
min-width:300px;
width:100%;
max-width:44%;
height:100%;
background-color: #fff;
box-shadow: -2px 0 2px rgba(15,15,15,0.3);
z-index:90;
position:fixed;
top:0;
right:0;
user-select:none;
padding: 1em 2.5em;
`;
const Button = styled.button`
z-index:99;
cursor:pointer;
`;
const Path = props =>{
<motion.Path fill="transparent" strokeLinecap="round" strokeWidth="3" {...props}/>
}
const transition = {duration: 0.3};
return (
<>
<HamburgerMenuContainer>
<Button onClick={() => setIsBurgerOpen(true)}>
<svg width="23" height="23" viewBox="0 0 23 23">
<Path animate={isBurgerOpen ? "open" : "closed"} initial={false} variants={{
closed: {d: "M 2 2.5 L 20 2.5",stroke:"hsl(0, 0%, 100%)"},
open: {d: "M 3 16.5 L 17 2.5",stroke:"hsl(0, 0%, 18%)"},
}}
transition={transition}
/>
<Path animate={isBurgerOpen ? "open" : "closed"} initial={false} variants={{
closed: {d: "M 2 2.5 L 20 2.5",stroke:"hsl(0, 0%, 100%)"},
open: {d: "M 3 16.5 L 17 2.5",stroke:"hsl(0, 0%, 18%)"},
}}
transition={transition}
/>
<Path animate={isBurgerOpen ? "open" : "closed"} initial={false} variants={{
closed: {d: "M 2 2.5 L 20 2.5",stroke:"hsl(0, 0%, 100%)"},
open: {d: "M 3 16.5 L 17 2.5",stroke:"hsl(0, 0%, 18%)"},
}}
transition={transition}
/>
</svg>
</Button>
</HamburgerMenuContainer>
</>
);
}
I could fix the problem. My Burger component was outside the header, like this:
function Header() {
const Burger = () => {
// Burger component
}
return (
<Burger />
)
}
I just put it like this and it worked:
function Header() {
return (
<BurgerStyled ...>
<span />
<span />
<span />
</BurgerStyled>
// Content header
)
}
Now the animation works, thanks for your answers!
Related
I'm hitting an API to get these data. I mapped the data to my select menu. I'm not able to change the height of the dropdown box . any idea on how it can be achieved?
react.js
inside render
<div className={styles.nationality}>
<label htmlFor="nationality">Nationality<span className={styles.star}>*</span></label><br/>
{/* <img className={styles.nationalitydrop} src={drop} alt='drop' /> */}
{['nationality'].map(key => (
<select
key={key}
className={styles.nationalitybox}
type="text"
placeholder="select"
menuPlacement="bottom"
onChange={(event) => this.handleUserInput(event)}
value={this.state.nationality}
name='nationality'
> {this.props.nationalityData.map(({[key]:id}) => <option className={styles.data}key={id}>{id}</option>)}
</select>
))}
</div>
stylesheet
.nationalitybox
{
width: 408px;
height: 56px;
background: #30333F 0% 0% no-repeat padding-box;
border-radius: 3px;
opacity: 1;
color: #B4B6C4;
font-size: 20px;
border-style:none none solid;
outline: none;
}
.nationalitybox:placeholder-shown{
font-size: 14px;
color: #B4B6C4;
}
.data{
background-color: #B4B6C4;
color: black;
width: 220px;
}
[![dropdown][1]][1]
I think you are using wrong to style your select menu, you cannot use 'style.nationalitybox' because that not a class.
I suggest you use these options.
1st Option:
Change className={styles.nationalitybox} To className = "nationalitybox".
2nd Option:
If you are using Material UI then you have to use useStyles above the component.
For example:
const useStyles = makeStyles((theme) => ({
nationalitybox: {
height: "56px"
}
}));
const ComponentName = () => {
const styles= useStyles();
}
I am trying to make a slide out drawer utilizing the npm package react-transition-group. For whatever reason, I cannot seem to get the drawer to slide out from left to right on clicking the additional criteria button. If you can solve this issue without using the package, that is ok too!
Here is the code I am trying to get to work as a React component:
{/* DeveloperSearch.js */}
<CSSTransition
in={sidebarClicked}
appear
timeout={1000}
classNames="drawer"
mountOnEnter
unmountOnExit
>
<div className="DevSearch__additional-search-criteria">
Additional Search Criteria
<div className="DevSearch__additional-search-criteria-individual">
<div
style={{
fontSize: '0.8rem',
marginBottom: '5px',
fontWeight: 'bold',
}}
>
Only show people who match more than {criteriaMatch}% of all
search criteria
</div>
<input
className="form-control"
type="number"
value={criteriaMatch}
onChange={(e) => setCriteriaMatch(e.target.value)}
min={0}
max={100}
step={5}
/>
</div>
</div>
</CSSTransition>
I also have a css file that is specifically for the CSS Transition component called DeveloperSearch.css:
.drawer-exit {
width: 250px;
}
.drawer-exit.drawer-exit-active {
width: 250px;
transition: width 1000ms ease-in;
}
.drawer-exit-done {
width: 0px;
}
.drawer-enter {
width: 250px;
}
.drawer-enter.drawer-enter-active {
width: 250px;
transition: all 1000ms ease-in;
}
Unfortunately, my results are no where near what I was wanting, as the drawer does not seem to slide out at all...
I also have replicated this issue in a codesandbox that can be found by clicking here. Thanks for your help!
Here is a pure css based solution but this is a bit hacky
Markup
const Drawer = ({ transitionExit, handleExit }) => (
<div
onClick={handleExit}
className={`drawer ${transitionExit ? "exit" : ""}`}
>
<p>Home</p>
<p>About</p>
<p>Contact</p>
<p>Close Drawer</p>
</div>
);
export default function App() {
const [isOpen, setIsOpen] = useState(false);
const [transitionExit, setTransitionExit] = useState(false);
const handleExit = () => {
setTransitionExit(true);
setTimeout(() => {
setIsOpen(false);
setTransitionExit(false);
// timeout should be less than animation time otherwise state might still be true
// after animation ends and drawer appears for few milliseconds
}, 450);
};
return (
<div className="App">
<div className="wrapper">
<div className="sidebar_container">
<button onClick={() => setIsOpen(true)}>open</button>
</div>
{isOpen && (
<div className={`container ${transitionExit ? "exit" : ""}`}>
<Drawer handleExit={handleExit} transitionExit={transitionExit} />
</div>
)}
</div>
</div>
);
}
CSS
.wrapper {
height: 90vh;
max-width: 60vw;
display: grid;
grid-template-columns: 30% 70%;
overflow: hidden;
margin: 40px;
}
.sidebar_container {
width: 100px;
height: 100%;
background-color: rgb(250, 207, 213);
padding: 30px;
position: relative;
z-index: 30;
}
#keyframes containerTransitionEnter {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
#keyframes drawerTransitionEnter {
0% {
opacity: 0;
left: -10vw;
}
100% {
opacity: 1;
left: 0vw;
}
}
#keyframes containerTransitionExit {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
#keyframes drawerTransitionExit {
0% {
opacity: 1;
left: 0vw;
}
100% {
opacity: 0;
left: -10vw;
}
}
.container {
position: relative;
z-index: 10;
height: 90vh;
animation: containerTransitionEnter 0.5s;
}
.drawer {
box-sizing: border-box;
position: relative;
height: 90vh;
width: 25vw;
padding: 20px;
background-color: rgb(4, 118, 156);
border-right: 1px solid rgba(0, 0, 0, 0.3);
animation: drawerTransitionEnter 0.5s;
}
p {
margin-bottom: 10px;
color: white;
}
.container.exit {
animation: containerTransitionExit 0.5s;
}
.drawer.exit {
animation: drawerTransitionExit 0.5s;
}
Here is the link to codesandbox
Since you are using react you can use Material UI for this Here
and you can try this in your case
<Drawer
className={classes.drawer}
variant=''
anchor='left'
open={open}
classes={{
paper: classes.drawerPaper,
}}>
<div className={classes.drawerHeader}>
<IconButton onClick={handleDrawerClose}>
{theme.direction === 'ltr' ? (
<ChevronLeftIcon />
) : (
<ChevronRightIcon />
)}
</IconButton>
</div>
<Divider />
<List>
{arr.map((text, index) => (
<ListItem
button
key={text}
onClick={
text === 'Home'
? goToHome
: text === 'About'
? handleOpenAbout
: text === 'Contact'
? goToContact
: text == 'Team'
? goToMyTea,
: goToDashboard
}>
<ListItemIcon>
{text === 'Home' ? (
<HomeIcon />
) : text === 'About' ? (
<NoteAddIcon />
) : text === 'About' || text === 'Contact' ? (
<ListAltIcon />
) : text === 'Dashboard' ? (
<DashboardIcon />
) : (
<></>
)}
</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</Drawer>
You should not delete div that has class="DevSearch__additional-search-criteria drawer-enter-done" from the DOM. In this case, Transition will not work. If you want to delete it, you must use css animation.
In this way, after adding div to the DOM, put animation on it to enter as a slider
here is a code example where i use Ref's to change style
import React, {useRef, useState, useEffect} from 'react'
import S from "./collapsible.module.css"
import PropTypes from 'prop-types'
import { faMinus, faPlus } from '#fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
function Collapsible(props) {
let myRef = useRef(null);
let buttonRef = useRef(null);
let [ button, setButton] = useState(true)
let Show = () => {
if(button) {
setButton(false)
buttonRef.current.style.backgroundColor = "#555"
myRef.current.style.maxHeight = myRef.current.scrollHeight + "px";
} else {
setButton(true)
buttonRef.current.style.backgroundColor = "hsl(181, 100%, 11%)"
myRef.current.style.maxHeight = "0px";
}
}
return (
<div
className={S.body}
>
<button
className={S.collapsible}
onClick={Show}
ref={buttonRef}
> {props.label}
<div className={S.icon}>
{button? <FontAwesomeIcon icon={faPlus} />:
<FontAwesomeIcon icon={faMinus} />}
</div>
</button>
<div
className={S.content}
ref={myRef}
>
<h3>{props.body}</h3>
</div>
</div>
)
}
Collapsible.propTypes = {
label: PropTypes.string,
body: PropTypes.string,
}
Collapsible.defaultProps = {
label: '',
body: "",
}
export default Collapsible
css:
.collapsible {
display: flex;
background-color: hsl(181, 100%, 11%);
color: white;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
outline: none;
font-size: 15px;
border-radius: 3px;
/* margin-bottom: 3px; */
box-shadow: 0px 1px 5px 1px black;
margin-top:13px;
}
.icon{
color:white;
position:absolute;
right:50px;
text-align:right;
justify-content: flex-end;
}
.active, .collapsible:hover {
background-color: #555;
}
.content {
padding: 0 18px;
max-height: 0px;
overflow: hidden;
transition: max-height 0.2s ease-out;
background-color: #f1f1f1;
}
This is just replicating this in React:
https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_collapsible_animate
I have read that using Refs is bad, especially when using it to change the DOM, but if I didn't change the style with the exact amount shown in "scrollHeight" then the transition would be a messed up speed.
If there is another method Is this still bad practice?
It's more common practice to use a state value to determine the style like this:
<button
className={S.collapsible}
onClick={Show}
style={{backgroundColor: button ? "#555" : "hsl(181, 100%, 11%)"}
>
{props.label}
<div className={S.icon}>
{button ? (
<FontAwesomeIcon icon={faPlus} />
) : (
<FontAwesomeIcon icon={faMinus} />
)}
</div>
</button>
Or have a conditional className for your styles:
<button
className={`${S.collapsible} ${
button ? S.buttonColor : S.anotherButtonColor
}`}
onClick={Show}
>
{props.label}
<div className={S.icon}>
{button ? (
<FontAwesomeIcon icon={faPlus} />
) : (
<FontAwesomeIcon icon={faMinus} />
)}
</div>
</button>
and add .buttonColor and .anotherButtonColor to your CSS Module file (collapsible.module.css).
.buttonColor {
background-color: #555;
}
.anotherButtonColor {
background-color: hsl(181, 100%, 11%);
}
For the maxHeight on myRef, I'd do something like:
<div className={S.content} ref={myRef}>
<div style={{ maxHeight: myRef.current.scrollHeight }}>
<h3>{props.body}</h3>
</div>
</div>
I'm trying to create the material ripple effect with styled-components (which is unable to import the material web-components mixins). I want to stick with using the after element for the foreground effect, to keep the accesibility tree intact.
However, most notably on mobile, the ripple transition is causing reflow in the button's content. It would seem to happen because of the display change (from none to block), but I have tried some alternatives which don't share this artifact, and this side-effect is still present.
Here's my code (I'm using some props to set the ripple, but you can hard-set them if you want to reproduce): [Here was an outdated version of the code]
Thanks for the attention.
Edit: The bug only happens when I add a hover effect to the button, very weird. Below follows the link and a code sample (you will have to set a react repository in order to reproduce it, unfortunately)
https://github.com/Eduardogbg/ripple-hover-reflow-bug
import React, { useRef, useReducer } from 'react';
import ReactDOM from 'react-dom';
import styled from 'styled-components'
const ButtonBase = styled.button`
cursor: pointer;
width: 250px;
height: 6vh;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
outline: none;
position: relative;
overflow: hidden;
border-width: 0;
background-color: cyan;
:hover {
filter: brightness(1.06);
}
::after {
content: '';
pointer-events: none;
width: ${({ ripple }) => ripple.size}px;
height: ${({ ripple }) => ripple.size}px;
display: none;
position: absolute;
left: ${({ ripple }) => ripple.x}px;
top: ${({ ripple }) => ripple.y}px;
border-radius: 50%;
background-color: ${({ ripple }) => ripple.color};
opacity: 0;
animation: ripple ${({ ripple }) => ripple.duration}ms;
}
:focus:not(:active)::after {
display: block;
}
#keyframes ripple {
from {
opacity: 0.75;
transform: scale(0);
}
to {
opacity: 0;
transform: scale(2);
}
}
`
const rippleReducer = ref => (ripple, event) => {
const { x, y, width, height } = ref.current.getBoundingClientRect()
const size = Math.max(width, height)
return {
...ripple,
size,
x: event.pageX - x - size / 2,
y: event.pageY - y - size / 2
}
}
const DEFAULT_RIPPLE = {
size: 0,
x: 0,
y: 0,
color: 'white',
duration: 850
}
const Button = props => {
const ref = useRef(null)
const [ripple, dispatch] = useReducer(
rippleReducer(ref),
{ ...DEFAULT_RIPPLE, ...props.ripple }
)
return (
<ButtonBase
ref={ref}
className={props.className}
ripple={ripple}
onClick={event => {
event.persist()
dispatch(event)
}}
>
{props.children}
</ButtonBase>
)
}
ReactDOM.render(
<div style={{
backgroundColor: 'red',
width: '500px', height: '500px',
display: 'grid',
placeItems: 'center'
}}>
<Button>
<span style={{ fontSize: '30px' }}>
abacabadabaca
</span>
</Button>
</div>,
document.getElementById('root')
);
The problem seems to be related to this chromium bug that was supposedly solved a few years ago: Image moves on hover when changing filter in chrome
Setting transform: translate3d(0,0,0); looks like a fix, though my eye isn't pixel-perfect.
In the case I have on toggle button that will bring this "mini drawer" that has some chips,
After pressing the Button
The issue in this case is that this chips just POP on the screen and I need to add some animation like this Drawer http://www.material-ui.com/#/components/drawer, I tried to add the transition and transform on CSS but It wont work
JS Part:
renderChip() {
console.log("1.30");
return this.state.listChip.map( (i, index) => (
<Chip className="chips">
{i.nome}
</Chip>
));
}
toggleList() {
this.setState({
visivel: !this.state.visivel,
});
// (this.state.visivel) ? document.getElementById("push").style.width = "0" : document.getElementById("push").style.width = "10px"};
}
render()
{
return (
<MuiThemeProvider>
<div >
<div className="anchor">
<RaisedButton
label="Label before"
labelPosition="before"
primary={true}
onClick = {this.toggleList}
/>
</div>
<div id="push" className="anchorz" style={(this.state.visivel) ? styles.wrapper : styles.closed} >
{this.renderChip()}
</div>
</div>
</MuiThemeProvider>
);
}
CSS part:
position: fixed;
display: flex;
right: 0;
bottom: 110px;
margin-right: 0;
margin-bottom: 0;
margin-left: 10px;
z-index: 900;
transition: transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;*/
overflow-x: hidden;
transition: 0.5s;