transform shows old and new position instead of only new one - css

the transform works fine, but not only the new positions of the dashes are shown also the old positions are shown on click.
i want the horizontal dashes to disappear on transform.
open === false
open === true
what I expect on open === true
const StyledBar = styled.span`
width: 2rem;
height: 0.25rem;
background: ${({ theme }) => theme.colors.primary};
border-radius: 10px;
transition: all 0.3s linear;
position: relative;
transform-origin: 1px;
`;
const StyledBurgerButton = styled.button`
position: absolute;
top: 24px;
right: 24px;
display: flex;
flex-direction: column;
justify-content: space-around;
width: 2rem;
height: 2rem;
background: transparent;
border: none;
cursor: pointer;
padding: 0;
z-index: 10;
${StyledBar} {
:first-child {
transform: ${({ open }) => (open ? "rotate(45deg)" : "rotate(0)")};
}
:nth-child(2) {
opacity: ${({ open }) => (open ? "0" : "1")};
transform: ${({ open }) => (open ? "translateX(20px)" : "translateX(0)")};
}
:nth-child(3) {
transform: ${({ open }) => (open ? "rotate(-45deg)" : "rotate(0)")};
}
}
`;
const BurgerButton = ({ open, setOpen }) => {
return (
<StyledBurgerButton open={open} onClick={() => setOpen(!open)}>
<StyledBar />
<StyledBar />
<StyledBar />
</StyledBurgerButton>
);
};

Related

How to change CSS of a child component from parent component

I have a react component called TeamBanner like so
import styles from "./teamBanner.module.css";
import { useNavigate } from "react-router-dom";
import { TeamBannerProps } from "../ProjectTypes.types";
export const TeamBanner = ({
team,
onBannerClick,
showName,
}: TeamBannerProps) => {
let navigate = useNavigate();
const handleOnClick = () => {
navigate(`/${team}`);
onBannerClick();
};
return (
<div
className={`${styles.container} flex theme-light ${
showName ? "" : styles["no-name"]
}`}
title={team}
onClick={handleOnClick}
>
<div>
<img
src={require(`../logos/${team}.svg`)}
alt="logo"
className={`${styles.logo} ${showName ? "" : styles["only-logo"]}`}
/>
</div>
{showName ? team : ""}
</div>
);
};
The teamBanner.module.css file is like so
.container {
margin: 0.25em;
align-items: center;
cursor: pointer;
top: 0;
z-index: 2;
background-color: hsl(var(--color-background));
padding: 0.1em 0.25em;
border-radius: 0.5em;
box-shadow: 0.05rem 0.05rem 0.2rem rgb(0 0 0 / 50%);
font-family: var(--ff-serif);
font-size: var(--fs-200);
}
.logo {
display: inline-block;
vertical-align: middle;
width: 3vmax;
height: 3vmax;
}
.container:hover {
outline: 0.15em solid hsl(240, 90%, 67%);
}
.no-name {
border-radius: 0.25em;
}
.only-logo {
width: 5vmax;
height: 5vmax;
}
I am using it to create a list of team of a page & this works just fine.
However, I want to use it in another place also. In the place, the to now want the .container:hover effect & I want the font properties of the .contaner and width & height properties of the .logo changed.
How can I accomplish this? only changing these items of the module.css? Can any one help? Thanks

Why isn't my custom CSS transition working when updating the state of a parent React component?

I'm using functional components with hooks (state hooks mostly) and when I update the "local" state (state within the same component) my custom css transitions works fine. However, when I move this state to the parent component and update it from the child component the css transistion will not work. Instead the changes will render immediately. What am I doing wrong?
I have tried to search for a while but without success. I am making a navbar with a burger menu that will slide in a navigation list from the right when klicked.
This below will not work
import React, { useState, useEffect } from "react";
import "./navbar.css";
const Navbar = (props) => {
const [scrollState, setScrollState] = useState("top");
const [burgerOpen, setBurgerOpen] = useState(false); //updating this state transistions does not work!
const Burger = () => {
//const [open, setOpen] = useState(false); //updating this state transistions work!
const handleKeyDown = (event) => {
if (event.key === "Enter") {
setBurgerOpen(!burgerOpen);
}
};
return (
<>
<div className={"btn-container"} onKeyDown={handleKeyDown}>
<div
className={burgerOpen ? "navbar-btn open" : "navbar-btn"}
open={burgerOpen}
onClick={() => setBurgerOpen(!burgerOpen)}
>
<div className="navbar-burger" />
</div>
</div>
<Menu open={burgerOpen} key="menu" />
</>
);
};
const Menu = ({ open }) => {
return (
<ul className={open ? "open" : ""}>
<li>
<a to="/projects">Projects</a>
</li>
<li>
<a to="/design">Design</a>
</li>
<li>
<a to="/contact">Contact</a>
</li>
</ul>
);
};
return (
<nav
className={`navbar ${scrollState === "top" ? "dark-nav" : "light-nav"}`}
>
<Burger />
</nav>
);
};
Navbar.propTypes = {};
export default Navbar;
This however works but I need to move the state to the parent because of rerenders that I might add (e.g. I want to keep the menu open...)
import React, { useState, useEffect } from "react";
import "./navbar.css";
const Navbar = (props) => {
const [scrollState, setScrollState] = useState("top");
//const [burgerOpen, setBurgerOpen] = useState(false); //updating this state transistions does not work!
const Burger = () => {
const [open, setOpen] = useState(false); //updating this state transistions work!
const handleKeyDown = (event) => {
if (event.key === "Enter") {
open(!open);
}
};
return (
<>
<div className={"btn-container"} onKeyDown={handleKeyDown}>
<div
className={open ? "navbar-btn open" : "navbar-btn"}
open={open}
onClick={() => setOpen(!open)}
>
<div className="navbar-burger" />
</div>
</div>
<Menu open={open} key="menu" />
</>
);
};
const Menu = ({ open }) => {
return (
<ul className={open ? "open" : ""}>
<li>
<a to="/projects">Projects</a>
</li>
<li>
<a to="/design">Design</a>
</li>
<li>
<a to="/contact">Contact</a>
</li>
</ul>
);
};
return (
<nav
className={`navbar ${scrollState === "top" ? "dark-nav" : "light-nav"}`}
>
<Burger />
</nav>
);
};
Navbar.propTypes = {};
export default Navbar;
And the CSS:
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
position: fixed;
z-index: 999;
width: 100%;
top: 0;
padding: 0.7rem 2rem;
height: 170px;
transition: all 0.8s ease;
background: linear-gradient(
to bottom,
rgba(0, 0, 0, 0.815) 50%,
rgba(0, 0, 0, 0.35) 50%
)
right;
background-size: 100% 200%;
background-position: top;
}
.light-nav {
/*background-color: rgba(0, 0, 0, 0.35);*/
background-position: bottom;
}
.navbar ul {
display: flex;
flex-flow: row nowrap;
}
.navbar li {
padding: 10px 10px;
font-size: 20px;
}
.navbar a {
background: linear-gradient(to right, royalblue, royalblue 50%, #f4f4f4 50%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-size: 200% 100%;
background-position: 100%;
transition: background-position 0.5s ease;
}
.navbar a:hover {
background-position: 0 100%;
}
.navbar-logo {
height: auto;
width: 200px;
padding: 2px;
}
.navbar-logo img {
max-width: 100%;
max-height: 100%;
}
.navbar-btn {
visibility: hidden;
display: none;
position: relative;
justify-content: center;
align-items: center;
cursor: pointer;
transition: all 0.5s ease-in-out;
width: 50px;
height: 50px;
padding-top: 10px;
}
.btn-container:focus > .navbar-btn {
border: #f4f4f4 1px solid;
}
.btn-container:focus,
.navbar-btn:focus {
border: none;
}
.navbar-burger {
width: 35px;
height: 3px;
background: #f4f4f4;
border-radius: 5px;
transition: all 0.5s ease-in-out;
}
.navbar-burger::before,
.navbar-burger::after {
content: '';
position: absolute;
width: 35px;
height: 3px;
background: #f4f4f4;
border-radius: 5px;
transition: all 0.5s ease-in-out;
}
.navbar-burger::before {
transform: translateY(-10px);
}
.navbar-burger::after {
transform: translateY(10px);
}
.navbar-btn.open .navbar-burger {
transform: translateX(-50px);
background: transparent;
}
.navbar-btn.open .navbar-burger::before {
transform: rotate(45deg) translate(35px, -35px);
}
.navbar-btn.open .navbar-burger::after {
transform: rotate(-45deg) translate(35px, 35px);
}
.navbar-btn:hover .navbar-burger,
.navbar-btn:hover .navbar-burger::before,
.navbar-btn:hover .navbar-burger::after {
background: royalblue;
}
.navbar-btn.open:hover .navbar-burger {
background: transparent;
}
#media (max-width: 768px) {
.navbar {
height: 110px;
}
.navbar-logo {
width: 140px;
}
.navbar ul {
flex-flow: column nowrap;
position: fixed;
top: 110px;
right: 0;
height: 100vh;
width: 200px;
transform: translateX(100%);
transition: 0.5s ease-in-out;
background-color: rgba(0, 0, 0, 0.815);
}
.navbar ul li {
transform: translateX(100%);
transition: 0.5s ease-in-out;
}
.navbar ul.open li {
transform: translateX(0);
}
.navbar ul li:nth-child(1) {
transition-delay: 0.1s;
}
.navbar ul li:nth-child(2) {
transition-delay: 0.2s;
}
.navbar ul li:nth-child(3) {
transition-delay: 0.3s;
}
.navbar ul.open {
transform: translateX(0);
}
.navbar-btn {
visibility: visible;
display: flex;
padding-bottom: 10px;
}
}
Here is a sandbox of the working example but I need to move out the state of the Burger (resize for burger menu):
Working example (resize!)

:Active works in styled components but :checked doesn't

I am attempting to transition from css to styled components. I am creating a toggle switch component. The animation on the :active class works when the switch is checked but, when the checkbox is checked the switch does not move to the left like it would with just normal css.
import React from 'react';
import styled from 'styled-components'
const Button = styled.span`
content: '';
position: absolute;
top: 3.7px;
left: 5px;
width: 42px;
height: 42px;
border-radius: 45px;
transition: 0.2s;
background: #fff;
box-shadow: 0 0 2px 0 rgba(10, 10, 10, 0.29);
`;
const Label = styled.label`
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
width: 100px;
height: 50px;
background: dodgerblue;
border-radius: 100px;
position: relative;
transition: background-color 0.2s;
&:active ${Button} {
width: 50px;
}
`;
const Input = styled.input`
height: 0;
width: 0;
visibility: hidden;
&:checked ${Button} {
left: calc(100% - 5px);
transform: translateX(-100%);
}
`;
const Switch = ({ isOn, handleToggle, onColor }) => {
return (
<>
<Input
checked={isOn}
onChange={handleToggle}
className='react-switch-checkbox'
id={`react-switch-new`}
type='checkbox'
/>
<Label
style={{ background: isOn && onColor }}
className='react-switch-label'
htmlFor={`react-switch-new`}>
<Button className={`react-switch-button`} />
</Label>
</>
);
};
export default Switch;
Your problem is not related to styled components. The rule &:checked ${Button} { assumes the Button is a child of Input, but it's actually a child of Input's sibling Label.
Update the styled component rule to:
&:checked + ${Label} ${Button} {
left: calc(100% - 5px);
transform: translateX(-100%);
}
Sandbox

Reactjs Styled-components not working properly

I have a styled checkbox and I want to set its background to white when checked:false and to green when checked:true
But the problem is background color is always staying green and i dont know why.
The react component in which I'm using this checkbox component is acting right,Its just background color that i cant adjust
Any suggestions please?
const CheckboxContainer = styled.div`
display: inline-block;
vertical-align: middle;
`
const Icon = styled.svg`
fill: none;
stroke: white;
stroke-width: 2px;
`
const HiddenCheckbox = styled.input.attrs({ type: 'checkbox' })`
border: 0;
clip: rect(0 0 0 0);
clippath: inset(50%);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
white-space: nowrap;
width: 1px;
`
const StyledCheckbox = styled.div`
display: inline-block;
width: 16px;
height: 16px;
background: #ffffff;
border-radius: 3px;
transition: all 150ms;
margin-top: 10px;
${HiddenCheckbox}:focus + & {
background: ${({ checked }) => (checked ? '#06ba63' : '#ffffff')};
}
${Icon} {
visibility: ${({ checked }) => (checked ? 'visible' : 'hidden')};
}
`
const Checkbox = ({ className, checked, ...props }) => (
<CheckboxContainer className={className}>
<HiddenCheckbox checked={checked} {...props} />
<StyledCheckbox checked={checked}>
<Icon viewBox="0 0 24 24">
<polyline points="20 6 9 17 4 12" />
</Icon>
</StyledCheckbox>
</CheckboxContainer>
)
export default Checkbox
you should check for checked instead of focus of the checkbox :
${HiddenCheckbox}:checked + & {
background: ${({ checked }) => (checked ? '#06ba63' : '#ffffff')};
}
If both Hidden and Styled checkboxes are get the same value from checked, you only need to use the value of checked passed to StyledCheckbox (like you do with Icon):
const StyledCheckbox = styled.div`
display: inline-block;
width: 16px;
height: 16px;
background: #ffffff;
border-radius: 3px;
transition: all 150ms;
margin-top: 10px;
background: ${({ checked }) => (checked ? '#06ba63' : '#ffffff')};
${Icon} {
visibility: ${({ checked }) => (checked ? 'visible' : 'hidden')};
}
`
The other option is only to apply the style if HiddenCheckbox is checked via CSS selectors:
const StyledCheckbox = styled.div`
display: inline-block;
width: 16px;
height: 16px;
background: #ffffff;
border-radius: 3px;
transition: all 150ms;
margin-top: 10px;
background: white;
${Icon} {
visibility: hidden;
}
${HiddenCheckbox}:checked + & {
background: #06ba63;
}
${HiddenCheckbox}:checked + & > ${Icon} {
visibility: visible;
}
`

Prevent page jump on Button click? ReactJS

I have a ShowcaseMovie component which fetches data on componentDidMount() and sets it to state. The component renders Card components to display the data as well as four button elements upcoming, top_rated, popular and now_playing which allow the user to toggle between the relevant data. Each button has an onClick event which calls changeFilter and sets state currentFilter to the selected key.
The problem: When the filter buttons are clicked, sometimes the page will jump to the top (if it's not already there). I've tried to find solutions to this but I can't seem to understand what is happening. Any suggestions will be a great help, thank you in advance.
Update: This issue seems to happen when there is no height set to an element with dynamic children. If I set the height on ShowcaseMovie to something large like height: 200vh it goes away.
I believe I've solved my problem but would love to hear other thoughts as to why this happens and some other ways to fix it. It's difficult to set a height to a parent when you don't know how much content is going to be rendered (or the height of that content). min-height would help but still kind of a quick fix.
ShowcaseMovie.js
import React, { Component } from "react";
import Card from "./Card";
import "../css/ShowcaseMovie.css";
import { v4 as uuidv4 } from "uuid";
import { formatString, buildMovieState } from "../utilities";
class ShowcaseMovie extends Component {
static defaultProps = {
filterNames: ["upcoming", "popular", "now_playing", "top_rated"]
};
constructor(props) {
super(props);
this.state = {
upcoming: [],
now_playing: [],
popular: [],
top_rated: [],
currentFilter: this.props.filterNames[0]
};
}
changeFilter = e => {
e.preventDefault();
const type = e.target.name;
this.setState({ currentFilter: type });
};
componentDidMount() {
this.props.filterNames.map(name => this.fetchMovies(name));
// setInterval(() => {
// this.timeoutFilter();
// }, 10000);
}
async fetchMovies(type) {
try {
const res = await fetch(
`url`
);
const data = await res.json();
if (data) {
this.setState(state => ({
...state,
[type]: buildMovieState(data)
}));
}
} catch (error) {
console.log(error);
}
}
render() {
const { currentFilter } = this.state;
const movies = this.state[currentFilter].map((movie, i) => (
<Card key={uuidv4()} movie={movie} index={i} />
));
const buttons = this.props.filterNames.map(name => (
<button
type="button"
key={name}
name={name}
className={`ShowcaseMovie-btn ${
currentFilter === name ? "active" : ""
}`}
disabled={currentFilter === name}
onClick={this.changeFilter}>
{formatString(name)}
</button>
));
return (
<section className="ShowcaseMovie">
<div className="ShowcaseMovie-container">
<h2 className="ShowcaseMovie-header">Movies</h2>
<div className="ShowcaseMovie-btn-container">{buttons}</div>
</div>
<div className="ShowcaseMovie-grid">{movies}</div>
</section>
);
}
}
ShowcaseMovie.css
.ShowcaseMovie {
padding: 4rem 10%;
}
.ShowcaseMovie-container {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.ShowcaseMovie-button-container {
display: flex;
justify-content: center;
align-items: center;
}
.ShowcaseMovie-container::after {
content: "";
background-color: #64b5f6;
height: 80%;
width: 6px;
left: 0;
position: absolute;
border-radius: 1px;
}
.ShowcaseMovie-header {
font-size: 3rem;
font-weight: 200;
margin: 0 5rem;
}
.ShowcaseMovie-btn {
outline: none;
border: none;
background-color: transparent;
font-size: 1.6rem;
font-weight: 500;
letter-spacing: 1px;
padding: 1rem;
margin-left: 4rem;
color: white;
opacity: 0.5;
cursor: pointer;
transition-property: opacity;
transition-duration: 300ms;
transition-timing-function: ease;
}
.ShowcaseMovie-btn:hover {
opacity: 1;
transition-property: opacity;
transition-duration: 300ms;
transition-timing-function: ease;
}
.ShowcaseMovie-btn.active {
opacity: 1;
cursor: auto;
color: #64b5f6;
}
.ShowcaseMovie-grid {
display: grid;
gap: 3rem;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
Card.js
import React, { Component } from "react";
import "../css/Card.css";
class Card extends Component {
render() {
const { title, poster_path } = this.props.movie;
const style = { animationDelay: `${80 * this.props.index}ms` };
return (
<div className="Card">
<div className="Card-inner" style={style}>
<img
src={`https://image.tmdb.org/t/p/w500/${poster_path}`}
alt=""
className="Card-img"
/>
<p className="Card-name">{title}</p>
</div>
</div>
);
}
}
export default Card;
Card.css
.Card {
display: block;
transition: transform 300ms ease;
}
.Card:hover {
transform: translateY(-5px);
transition: transform 300ms ease;
}
.Card-inner {
position: relative;
display: block;
cursor: pointer;
height: 100%;
opacity: 0;
animation-name: moveUp;
animation-duration: 500ms;
animation-delay: 50ms;
animation-timing-function: ease;
animation-fill-mode: forwards;
}
.Card-inner::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: linear-gradient(transparent, rgba(33, 47, 61, 0.8));
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
z-index: 100;
opacity: 1;
transition: opacity 300ms ease;
}
.Card-inner:hover::after {
opacity: 0;
transition: opacity 300ms ease;
}
.Card-inner::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: linear-gradient(transparent, rgba(100, 180, 246, 0.6));
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
z-index: 100;
opacity: 0;
transition: opacity 300ms ease;
}
.Card-inner:hover::before {
opacity: 1;
transition: opacity 300ms ease;
}
.Card-img {
display: block;
position: relative;
object-fit: cover;
max-width: 100%;
min-height: 100%;
z-index: 0;
border-radius: 2px;
}
.Card-name {
position: absolute;
bottom: 0;
left: 0;
margin: 0 2rem 2rem 2rem;
z-index: 150;
font-weight: 400;
text-transform: uppercase;
font-size: 1.4rem;
letter-spacing: 2px;
}
#keyframes moveUp {
0% {
transform: translateY(5rem);
}
100% {
transform: translateY(0);
opacity: 1;
}
}
utilities.js
export const formatString = name => {
return name
.replace("_", " ")
.split(" ")
.map(w => w[0].toUpperCase() + w.slice(1))
.join(" ");
};
export const buildMovieState = data => {
if (data.results) {
const movies = data.results.filter(
d => d.backdrop_path && d.id && d.title && d.poster_path
);
return movies.length > 10 ? movies.slice(0, 10) : movies;
} else {
return [];
}
};
I was able to stop this from happening by returning false from the onClick call. Like this:
onClick={
doSomething()
return false
}
The page jumping could be from components unnecessarily re-rendering. Try wrapping all your components with React.memo( component name)

Resources