I have this code:
import React, {useState} from 'react';
import PropTypes from 'prop-types';
import styled from "styled-components";
import ArrowTemplate from "./ArrowTemplate";
const AccordionBtn = styled.button`
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
display: flex;
align-items: center;
border: none;
outline: none;
transition: background-color 0.6s ease;
:hover,
:focus,
:active {
background-color: #ccc;
}
`;
const AccordionTitle = styled.p`
font-family: "Open Sans", sans-serif;
font-weight: 600;
font-size: 14px;
`;
const AccordionContent = styled.div`
background-color: red;
overflow: hidden;
transition: 0.6s;
`;
const AccordionText = styled.div`
font-family: "Open Sans", sans-serif;
font-weight: 400;
font-size: 14px;
padding: 18px;
`;
const AccordionSection = styled.div`
display: flex;
flex-direction: column;
`;
Accordion.propTypes = {
title: PropTypes.string.isRequired,
content: PropTypes.node.isRequired,
id: PropTypes.string.isRequired,
};
function Accordion(props) {
const [isAccordionExpanded, setIsAccordionExpanded] = useState(false);
const toggleAccordion = () => {
setIsAccordionExpanded(!isAccordionExpanded);
};
return (
<AccordionSection>
<AccordionBtn onClick={toggleAccordion}>
<AccordionTitle>
{props.title}
</AccordionTitle>
<ArrowTemplate
color={'black'}
direction={isAccordionExpanded === true ? 'up' : 'down'}
onClick={toggleAccordion}
/>
</AccordionBtn>
<AccordionContent
style={{height: isAccordionExpanded === true ? "100px" : "0"}}
>
<AccordionText>
{props.content}
</AccordionText>
</AccordionContent>
</AccordionSection>
);
}
export default Accordion;
What this code does, is extends the accordeon on click. Preety simple. But now, I want to move this height:
<AccordionContent
style={{height: isAccordionExpanded === true ? "100px" : "0"}}
>
here:
const AccordionContent = styled.div`
background-color: red;
overflow: hidden;
transition: 0.6s;
`;
The problem is I need to use state, and if I declare it in the function, it will get re-rendered, and not run the animation. How can I pass the state to the styled-component?
You can just pass the isAccordionExpanded directly to the styled component. Change your style from this:
const AccordionContent = styled.div`
background-color: red;
overflow: hidden;
transition: 0.6s;
`;
to include this
height: ${({ isAccordionExpanded }) => (isAccordionExpanded ? "100px" : "0"};
Another way to do animation in React is using React Transition Group, Quite good
Related
I have a main page, with a button component i create to reuse in my project. My issue is when i add the onClick event to my external component, the click event is not working, but is i create the same button inside my main page, the click event works just fine
Button Component
import React from "react";
import styled from "styled-components";
const BigButton = (props): JSX.Element => {
return <>{props.red ? <BigBtn red={props.red}>{props.val}</BigBtn> : <BigBtn>{props.val}</BigBtn>}</>;
};
export default BigButton;
const BigBtn = styled.button`
font-style: normal;
font-weight: 500;
font-size: 12px;
line-height: 15px;
color: #f5f5f5;
width: 78px;
height: 30px;
background: ${(props) => (props.red ? "#BD2129" : "#2e3034")};
border: ${(props) => (props.red ? "initial" : "1px solid #494b4f")};
border-radius: 2px;
display: flex;
align-items: center;
justify-content: center;
`;
This works on main page
<button onClick={buttonClose}>Close</button>
Button Component on main page - This doesn't work on main page
<BigButton val="Cancel" onClick={handleClose} />
Close function
const handleClose = (e) => {
e.preventDefault();
props.onClose();
};
Your component does not look correct. You do not have button inside the component and onClick event. You should update like this
import React from "react";
import styled from "styled-components";
const BigButton = (props): JSX.Element => {
const handleClick = () => {
props.onClick()
}
return <>{props.red ?
<BigBtn red={props.red}>
<button onClick={handleClick}>
{props.val}
</button>
</BigBtn> :
<BigBtn>
<button onClick={handleClick}>
{props.val}
</button>
</BigBtn>}
</>
};
export default BigButton;
const BigBtn = styled.button`
font-style: normal;
font-weight: 500;
font-size: 12px;
line-height: 15px;
color: #f5f5f5;
width: 78px;
height: 30px;
background: ${(props) => (props.red ? "#BD2129" : "#2e3034")};
border: ${(props) => (props.red ? "initial" : "1px solid #494b4f")};
border-radius: 2px;
display: flex;
align-items: center;
justify-content: center;
`;
Error:
./src/card.js
Attempted import error: 'Bottom' is not exported from './styles/cards.style'.
card.js
import React from 'react'
import {
Bottom,
Color,
Text,
Image
} from "./styles/cards.style";
function Card(props) {
return (
<div>
<Bottom>
<Color />
<Text>{props.text}</Text>
<Text>{props.text}</Text>
</Bottom>
<Image
alt=""
src={props.image}
/>
</div>
);
}
export default Card;
cards.style
import styled from "styled-components";
export default {
colors: {
black: "rgba(0,0,0,1)",
brandPrimary: "rgba(238,120,36,1)",
brandPrimaryLight: "rgba(255,184,8,1)",
brandTertiary: "rgba(0,65,125,1)",
darkSlateGray: "rgba(51,51,51,1)",
white: "rgba(255,255,255,1)"
},
fonts: {
uiMainContent: {
family: "Poppins",
size: "15px",
weight: "300",
lineHeight: "21px"
},
uiSubContent: {
family: "Poppins",
size: "13px",
weight: "300",
lineHeight: "20px"
}
}
};
export const Bottom = styled.div`
width: 100%;
height: calc(100% - 20px);
background-color: ${props => props.theme.colors.white};
border-radius: 4px;
padding: 0 0 20px;
display: flex;
flex-direction: column;
align-items: flex-start;
position: relative;
`;
export const Color = styled.div`
height: 120px;
background-color: ${props =>
props.theme.colors.brandPrimary};
margin-bottom: 16px;
border-radius: 4px 4px 0px 0px;
align-self: stretch;
`;
export const Text = styled.p`
color: ${props => props.theme.colors.black};
margin-left: 16px;
letter-spacing: 0.1px;
font-family: ${props =>
props.theme.fonts.uiSubContent.family};
font-size: ${props =>
props.theme.fonts.uiSubContent.size};
font-weight: ${props =>
props.theme.fonts.uiSubContent.weight};
line-height: ${props =>
props.theme.fonts.uiSubContent.lineHeight};
&:not(:last-of-type) {
margin-bottom: 4px;
}
`;
export const Image = styled.img`
width: 150px;
height: 92px;
position: absolute;
left: 29px;
top: 14px;
`;
I am trying to build cards in reactjs. I usually stick to scss however cannot use props with scss which I will have to use later to dynamically generate components. Not sure what is wrong here as I did export Button. Please can someone shed some insight you see what is so blatantly wrong it is causing this error.
in that case you have an export default as the first thing of you code, if you are exporting more than one thing from the same file, you should stick to exporting each const/function by itself and not having any export default
If you are using ReactJS/NextJS I would really recommend creating a global theme that you normally write and import with the application, so you could have things like
// global.js
const GlobalStyle = createGlobalStyle`
* {
box-sizing: border-box;
}
:root {
--black: rgba(0,0,0,1);
--brandPrimary: rgba(238,120,36,1);
...
...
`
}
Take a look here, it should help you a lot.
I have made a button component using styled-components like below.
And I am using this button component in another component, even made a parent component to the button component, hoping that if the parent component has "text-align: center" attribute, it'd center texts in my button, which turned out not... maybe I did not do it right?
Below is my code for Button.js
import React from "react";
import styled, { css } from "styled-components";
const Container = styled.div`
text-align: center;
`;
const colorStyles = css`
${(props) =>
props.color === "blue" &&
css`
background: #588ced;
color: #ffffff;
&:hover {
background: #3866bc;
}
`}
${(props) =>
props.color === "gray" &&
css`
background: #eeeef2;
color: #65636a;
&:hover {
background: #dbdadf;
}
`}
`;
const sizeStyles = css`
${(props) =>
props.size === "normal" &&
css`
width: 181px;
height: 48px;
`}
${(props) =>
props.size === "small" &&
css`
width: 100px;
height: 40px;
`}
`;
const shapeStyles = css`
${(props) =>
props.shape === "round" &&
css`
border-radius: 50px;
`}
${(props) =>
props.shape === "squared" &&
css`
border-radius: 10px;
`}
`;
const StyledButton = styled.button`
/* common styles */
display: flex;
outline: none;
border: none;
font-family: Roboto;
font-style: normal;
font-weight: bold;
font-size: 16px;
line-height: 19px;
cursor: pointer;
align-items: center;
text-align: center;
/* sizes */
${sizeStyles}
/* colors */
${colorStyles}
/* shapes */
${shapeStyles}
/* etc */
// & + & {
// margin-left: 1rem;
// }
`;
function Button({ children, color, size, shape, ...rest }) {
return (
<Container>
<div>
<StyledButton color={color} size={size} shape={shape} {...rest}>
{children}
</StyledButton>
</div>
</Container>
);
}
export default Button;
AND this is a part from the other component where the button component is used.
import styles from "./Page.module.css";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import Button from "../button/Button";
export default function Page({ data, setModalVisibleState }) {
const addButtonHandler = () => {
setModalVisibleState(true);
};
return (
<div className={styles.container}>
<div className={styles.titleBox}>
<FontAwesomeIcon icon={faGraduationCap} />
<p>{data.title}</p>
<div className={styles.buttonWrap}>
<Button size="normal" shape="squared" color="blue" onClick={addButtonHandler}>{data.button}</Button>
</div>
</div>
//...and more...
And under "./Page.module.css" I have done this.
.buttonWrap {
display: flex;
text-align: center;
align-items: center;
}
This is how my buttons look right now. Before I used text-align and align-items attributes the text was located on the upper left side of the button. Is there an way to center texts in the button?
I am wondering if the purple box on the right has anything to do with centering the text..
Thank you very much in advance for reading and kindly answering my question. :)
Just add: justify-content: center; Aligning Items in a Flex Container
To center our box we use the align-items property to align our item on the cross axis, which in this case is the block axis running vertically. We use justify-content to align the item on the main axis, which in this case the inline axis running horizontally.
DEMO
button{
display: flex;
outline: none;
border: none;
font-family: Roboto;
font-style: normal;
font-weight: bold;
font-size: 16px;
line-height: 19px;
cursor: pointer;
align-items: center;
text-align: center;
border-radius: 50px;
background: #588ced;
color: #ffffff;
width:189px;
height:48px;
}
<button>button</button>
<button style="justify-content: center;">button</button>
I have built a mobile menu in react that uses the Context API. It works as intended in browser but when on a mobile device the menu lags severely. I have tested in dev tools with CPU throttling and it still didn't lag, but on actual mobile device it slows up significantly. Any help would be greatly appreciated.
Mobile Nav Component:
import { useContext } from "react";
import styled from "styled-components";
import CloseIcon from "../assets/close.svg";
import { NavbarContextList } from "../context/NavbarContext";
import { motion } from "framer-motion";
const NavWrapper = styled(motion.div)`
position: absolute;
top: 0;
height: 100vh;
right: 0;
width: 100%;
z-index: 5;
`;
const NavMain = styled(motion.div)`
width: 100%;
height: 100%;
/* border-top-left-radius: 20px;
border-bottom-left-radius: 20px; */
display: flex;
flex-direction: column;
overflow-y: scroll;
`;
const NavTop = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
padding: 32px 32px 0px 32px;
`;
const NavTitle = styled.h3`
color: #50616b;
`;
const CloseButton = styled.button`
width: 32px;
height: 32px;
border-radius: 8px;
border: none;
`;
const NavM = styled.div`
display: flex;
flex-direction: column;
margin-top: 32px;
padding-left: 64px;
`;
const NavItems = styled(motion.h1)`
font-weight: 700;
font-size: 2rem;
margin-bottom: 32px;
:nth-child(3) {
margin-bottom: 0px;
}
`;
const SocialTop = styled.div`
padding: 32px 32px 0px 32px;
`;
const ContactButton = styled.button`
height: 40px;
padding-right: 24px;
padding-left: 24px;
background: #ff661a;
border: 1px solid rgba(255, 255, 255, 0.39);
box-sizing: border-box;
border-radius: 8px;
font-weight: 700;
font-size: 0.8rem;
color: white;
width: 100%;
`;
const NavBottom = styled.div`
padding-left: 32px;
padding-right: 32px;
position: absolute;
bottom: 32px;
width: 100%;
`;
const variants = {
hidden: {
opacity: 0,
transition: {
duration: 0.05,
},
},
show: {
opacity: 1,
transition: {
staggerChildren: 0.2,
},
},
};
const item = {
hidden: {
opacity: 0,
x: -50,
transition: {
duration: 0.05,
},
},
show: {
opacity: 1,
x: 0,
transition: {
duration: 0.5,
},
},
};
const MobileNav = () => {
const { mobileNav, setMobileNav } = useContext(NavbarContextList);
const toggleMenu = () => {
setMobileNav(!mobileNav);
};
return (
<NavWrapper
key="menu"
exit={{ opacity: 0 }}
initial={{ x: 400 }}
animate={{ x: 0 }}
>
<NavMain
initial="hidden"
animate="show"
variants={variants}
delay="0.5"
className="mobile-nav"
>
<NavTop>
<NavTitle>Navigation</NavTitle>
<CloseButton onClick={toggleMenu} className="toggle">
<img
style={{ paddingTop: "2px" }}
alt="Close Button"
src={CloseIcon}
></img>
</CloseButton>
</NavTop>
<NavM>
<NavItems variants={item} className="nav-item">
Work
</NavItems>
<NavItems variants={item} className="nav-item">
About Me
</NavItems>
<NavItems variants={item} className="nav-item">
Skills
</NavItems>
</NavM>
<SocialTop>
<NavTitle>Social Media</NavTitle>
</SocialTop>
<NavM>
<NavItems variants={item} className="nav-item">
LinkedIn
</NavItems>
<NavItems variants={item} className="nav-item">
Dribble
</NavItems>
</NavM>
<NavBottom>
<ContactButton>Contact Me!</ContactButton>
</NavBottom>
</NavMain>
</NavWrapper>
);
};
export default MobileNav;
Home Component (where the component is rendered)
import "./App.css";
import { useContext } from "react";
import Navbar from "./components/Navbar";
import Background from "./components/Background";
import { AnimatePresence } from "framer-motion";
import MobileNav from "./components/MobileNav";
import { NavbarContextList } from "./context/NavbarContext";
import Hero from "./components/Hero";
import Work from "./components/Work";
function Home() {
const { mobileNav } = useContext(NavbarContextList);
return (
<div className="App">
<Background />
<Navbar />
<AnimatePresence>
{mobileNav && <MobileNav key="menu" />}
</AnimatePresence>
<Hero />
<Work />
</div>
);
}
export default Home;
Context File
import React, { useState, createContext } from "react";
export const NavbarContextList = createContext();
const NavbarContextListProvider = (props) => {
const [mobileNav, setMobileNav] = useState(false);
return (
<NavbarContextList.Provider value={{ mobileNav, setMobileNav }}>
{props.children}
</NavbarContextList.Provider>
);
};
export default NavbarContextListProvider;
I want to run an animation using transition. The problem is I am using state to control it. So now, the components are created in render method. But every time the state is changed, the component is re-rendered. So how can I create the smaller components, so they are in scope, and I can use state to animate them?
import React, {Component, Fragment} from 'react';
import ArrowTemplate from "./ArrowTemplate";
import styled from 'styled-components';
class Accordion extends Component {
constructor(props) {
super(props);
this.state = {isAccordionExpanded: false};
this.toggleAccordion = this.toggleAccordion.bind(this);
}
toggleAccordion() {
this.setState({isAccordionExpanded: !this.state.isAccordionExpanded})
}
render() {
const {isAccordionExpanded} = this.state;
const AccordionSection = styled.div`
display: flex;
flex-direction: column;
`;
const AccordionBtn = styled.button`
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
display: flex;
align-items: center;
border: none;
outline: none;
transition: background-color 0.6s ease;
:hover,
:focus,
:active {
background-color: #ccc;
}
`;
const P = styled.p`
font-family: "Open Sans", sans-serif;
font-weight: 600;
font-size: 14px;
`;
const AccordionContent = styled.div`
background-color: white;
${isAccordionExpanded === true ? `height: 100px` : `height: 0`};
overflow: hidden;
transition: max-height 0.6s ease;
`;
const AccordionText = styled.div`
font-family: "Open Sans", sans-serif;
font-weight: 400;
font-size: 14px;
padding: 18px;
`;
return (
<AccordionSection>
<AccordionBtn
onClick={this.toggleAccordion}
>
<P>
<ArrowTemplate
color={"black"}
direction={"down"}
aria={"aria-roles: 'button'"}
onClick={this.toggleAccordion}
/>
</P>
</AccordionBtn>
<AccordionContent>
<AccordionText>
test
</AccordionText>
</AccordionContent>
</AccordionSection>
)
}
}
export default Accordion;
ArrowTemplate
import React from "react";
import BlackDownArrowSVG from './svgs/black-down-arrow.svg';
import WhiteDownArrowSVG from './svgs/white-down-arrow.svg';
import styled from 'styled-components';
import PropTypes from 'prop-types';
ArrowTemplate.propTypes = {
color: PropTypes.string.isRequired,
direction: PropTypes.string.isRequired,
styles: PropTypes.string,
aria: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
};
function ArrowTemplate(props) {
const {color, direction, styles, aria, onClick} = props;
const StyledArrowTemplate = styled.img.attrs({
src: color.toLowerCase() === "black" ? BlackDownArrowSVG : WhiteDownArrowSVG,
aria,
})`
${direction.toLowerCase() === "up" ? "transform: rotate(180deg);" : ""}
${styles}
`;
return <StyledArrowTemplate onClick={onClick}/>;
}
export default ArrowTemplate;
Try using
direction={this.state.isAccordionExpanded ? 'up' : 'down'}
for <ArrowTemplate />
I managed to do this by passing the properties (state) to a styled component like so:
<AccordionContent
ref={content}
isAccordionExpanded={isAccordionExpanded}
height={content.current === null ? '0' : content.current.scrollHeight}
>
const AccordionContent = styled.div`
max-height: ${({ isAccordionExpanded, height }) => (isAccordionExpanded ? height : '0')}px;
background-color: red;
overflow: hidden;
transition: max-height 0.7s;
`;