I'm trying to create a TextField component that takes an optional left adornment component, which renders a container with an SVG icon within it, where the SVG icon is centered within the parent.
The problem here is that the parent of the SVG element is, for some reason, setting an (arbitrary?) height for itself. Not sure why this is happening, maybe it's got something to do with the SVG props themselves.
If I set the Container height to 3rem, for example, then the adornments' parent containers height does change, but the SVG itself keeps extending outside of the bounds.
Here's how it looks like now:
And here is how I'd like it to look like:
Here's the code:
// TextField
import * as React from "react";
import clsx from "clsx";
import styled from "#emotion/styled";
const Container = styled.div`
--input-color: #99a3ba;
--input-border: #cdd9ed;
--input-border-error: #a30000;
--input-background: #fff;
--input-placeholder: #cbd1dc;
--input-border-focus: #275efe;
--group-color: var(--input-color);
--group-border: var(--input-border);
--group-background: #eef4ff;
--group-color-focus: #fff;
--group-border-focus: var(--input-border-focus);
--group-background-focus: #678efe;
position: relative;
display: flex;
width: 100%;
& > .adornment_container,
.form-field {
white-space: nowrap;
display: block;
&:not(:first-child):not(:last-child) {
border-radius: 0;
}
&:first-child {
border-radius: 0.5rem 0 0 0.5rem;
}
&:last-child {
border-radius: 0 0.5rem 0.5rem 0;
}
&:not(:first-child) {
margin-left: 1px;
}
}
.form-field {
position: relative;
z-index: 1;
flex: 1 1 auto;
width: 1%;
margin-top: 0;
margin-bottom: 0;
}
& > .adornment_container {
width: 5rem;
height: 100%;
text-align: center;
color: var(--group-color);
background: var(--group-background);
border: 1px solid var(--group-border);
transition: background 0.3s ease, border 0.3s ease, color 0.3s ease;
}
&:focus-within {
& > .adornment_container {
color: var(--group-color-focus);
background: var(--group-background-focus);
border-color: var(--group-border-focus);
path {
fill: #ffffff;
}
}
}
`;
const Input = styled.input`
display: block;
width: 100%;
height: 100%;
padding: 0.5rem 1rem;
font-weight: 500;
font-family: inherit;
border-radius: 6px;
-webkit-appearance: none;
color: var(--input-color);
border: 1px solid var(--input-border);
background: var(--input-background);
transition: border 0.3s ease;
&::placeholder {
color: var(--input-placeholder);
}
&:focus {
outline: none;
border-color: var(--input-border-focus);
}
`;
const AdornmentContent = styled.div`
height: 100%;
width: 100%;
`;
type TextFieldProps = {
adornmentContent?: React.ReactNode;
value: string;
disabled?: boolean;
} & Omit<React.HTMLAttributes<HTMLInputElement>, "name">;
const TextField = React.memo(
({
className,
adornmentContent = null,
disabled,
...props
}: TextFieldProps) => {
const adornment = adornmentContent ? (
<AdornmentContent className="adornment_container">
{adornmentContent}
</AdornmentContent>
) : null;
return (
<Container>
{adornment}
<Input className={clsx("form-field", className)} {...props} />
</Container>
);
}
);
export default TextField;
// EmailIcon
import * as React from "react";
const EmailIcon = React.memo(() => {
return (
<svg
aria-hidden="true"
viewBox="0 0 100% 100%"
preserveAspectRatio="xMidYMid meet"
style={{
width: "100%",
height: "100%"
}}
>
<path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10h5v-2h-5c-4.34 0-8-3.66-8-8s3.66-8 8-8 8 3.66 8 8v1.43c0 .79-.71 1.57-1.5 1.57s-1.5-.78-1.5-1.57V12c0-2.76-2.24-5-5-5s-5 2.24-5 5 2.24 5 5 5c1.38 0 2.64-.56 3.54-1.47.65.89 1.77 1.47 2.96 1.47 1.97 0 3.5-1.6 3.5-3.57V12c0-5.52-4.48-10-10-10zm0 13c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3z"
fill={"#232323"}
/>
</svg>
);
});
export default EmailIcon;
What am I doing wrong here? Here is how I would like it to look like:
Related
I am new to React and did not find any solution to this simple problem.
I want to revert LangText colors when the toggle button is clicked - initially ENG is white and EST is black and every time input is clicked the colors invert.
I have separate files for returning and styling components.
I tried to change the color of a variable (located on top of the style-components file) inside the styled component tag, so with CSS - inside Input pseudo-class &:checked + span. Looked surreal and didn't work in any way.
I would be very thankful for an example of how to change two colors at the same, time in this case. There are always too few examples with components, usually, CSS is used separately, but for me this method is more readable and logical.
import React from 'react'
import { Input, InputWrapper, Slider, ToggleContainer, LangText} from './LangugageToggleElements';
const LanguageToggle = ({onChange}) => {
return(
<ToggleContainer>
<InputWrapper>
<Input type="checkbox" onChange={onChange}/>
<Slider>
<LangText>ENG</LangText>
<LangText>EST</LangText>
</Slider>
</InputWrapper>
</ToggleContainer>
)} ;
export default LanguageToggle
import styled from "styled-components";
let toggleBackground = "#000";
let textColor = "#fff";
export const ToggleContainer = styled.div`
position: absolute;
top: 10px;
right: 20px;
`
export const InputWrapper = styled.label`
position: relative;
display: flex;
justify-content: center;
text-align: center;
`
export const Input = styled.input`
position: absolute;
left: -9999px;
top: -9999px;
&:checked + span {
&:before {
left: 52px;
}
}
`
export const Slider = styled.span`
display: flex;
justify-content: center;
text-align: center;
cursor: pointer;
width: 105px;
border-radius: 100px;
position: relative;
transition: background-color 0.2s;
color: ${textColor};
&:before {
content: "";
position: absolute;
top: 15px;
left: 2px;
width: 50px;
height: 20px;
border-radius: 45px;
transition: 0.2s;
background: ${toggleBackground};
box-shadow: 0 2px 4px 0 rgba(0, 35, 11, 0.2);
}
`;
export const LangText = styled.p`
padding-right: 5px;
padding-left: 5px;
z-index: 1;
letter-spacing: 3px;
`;
I think you can simply use a useState hook to maintain checkbox and the use it for your LangText.
<ToggleContainer>
<InputWrapper>
<Input type="checkbox" onChange={()=>{
onChange();
//use state hook to maitain the checkbox value
}}/>
<Slider>
<LangText className={isChecboxValue === x ? color : invert }>ENG</LangText>
<LangText className={isChecboxValue === y ? invert: color}>EST</LangText>
</Slider>
</InputWrapper>
</ToggleContainer>
i'm building an app where i fetch data from an api and i'm trying to reload those datas automatically after minute along with a progress bar. i made a progress bar that is filling the width withoin a minute. I can't get to work the reloading after i reached the 100% automatically. this is the code for the peogress bar component:
import React, {useState, useEffect} from "react";
import styles from "./ProgressBar.module.css";
const Progress = ({reloading}) => {
const [style, setStyle] = useState({});
setInterval(() => {
const newStyle = {
opacity: 1,
width: `${reloading}%`
}
setStyle(newStyle);
}, 100);
return (
<div className={styles.progress}>
<div className={styles.progressBar} style={style}>
</div>
</div>
)
}
export default Progress;
And this is the css:
* {
box-sizing: border-box;
}
.progress {
background-color: #d8d8d8;
border-radius: 20px;
position: relative;
margin: 15px 0;
height: 30px;
width: 100%
}
.progressBar {
background: linear-gradient(to left,#dd9214, #ecc480);
box-shadow: 0 3px 3px -5px #F2709C, 0 2px 5px #F2709C;
border-radius: 20px;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: 0;
opacity: 0;
transition: 5s;
}
with this code my bar is filling to 100% within a minute, but i can't get how to refresh every time. Shall i use Js methods or i can achieve through css?
I'm trying to add a scale animation to an SVG. Its working fine in Webkit based browsers, but on iOS Chrome and Safari the animation is super slow. Here is the page I am trying to animate. Here is the relevant code...
const HomeHeading = styled.svg`
margin: 0;
color: rgba(255, 255, 255, 1);
height: 100vh;
width: 100vw;
position: fixed;
top: 0;
left: 0;
backface-visibility: hidden;
perspective: 1000;
transform: scale(
${props =>
props.scrollPosition / props.scale < 1
? 1
: props.scrollPosition / props.scale}
)
translateZ(0);
transform-origin: 42% 56%;
#media screen and (max-width: 480px) {
transform-origin: 43% 38% !important;
}
rect {
-webkit-mask: url(#mask);
mask: url(#mask);
fill: #f00;
}
defs {
mask {
rect {
fill: white;
}
text {
transform: translateY(10%);
font-size: 8vw;
#media screen and (max-width: 480px) {
transform: translateY(0);
}
&:last-child {
#media screen and (max-width: 480px) {
transform: translateY(0);
}
transform: translateY(20%);
}
}
}
}
`;
const HomeSubHomeSectionHeading = styled.section`
width: 100vw;
height: 100vh;
top: 0;
left: 0;
background: #1ecbe1;
position: fixed !important;
display: flex;
align-content: center;
justify-content: center;
flex-wrap: wrap;
`;
const ColorChanger = styled.div`
position: fixed;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
z-index: 3;
background-color: rgb(255, 255, 255);
opacity: ${props => 3000 / props.scrollPosition / 20};
`;
const ImageStamp = styled.div`
width: 280px;
height: auto;
display: inline-block;
padding: 10px;
background: white;
position: relative;
-webkit-filter: drop-shadow(0px 0px 10px rgba(0, 0, 0, 0.5));
background: radial-gradient(
transparent 0px,
transparent 4px,
white 4px,
white
);
background-size: 20px 20px;
background-position: -10px -10px;
&:after {
content: "";
position: absolute;
left: 5px;
top: 5px;
right: 5px;
bottom: 5px;
z-index: -1;
}
#media screen and (max-width: 768px) {
width: 200px;
margin-bottom: 30px;
}
`;
const MeSection = styled.section`
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
`;
const MePhoto = styled.div`
display: flex;
align-items: center;
justify-content: center;
flex: 1 0 33.333%;
`;
const MeBio = styled.div`
flex: 1 0 66.666%;
color: #ffffff;
padding: 0 30px;
`;
const MeSocials = styled.div`
svg {
margin: 0 10px;
}
`;
const SocialLink = styled.a`
color: #fff;
&:hover {
color: #f1f1f1;
}
`;
const IndexPage = props => {
const [scrollPosition, setScrollPosition] = useState(0);
const [scale, setScale] = useState(0);
useEffect(() => {
window.addEventListener("scroll", handleScroll, { passive: true });
});
const handleScroll = () => {
const position = window.scrollY;
setScale(document && document.width > 500 ? 20 : 5);
setScrollPosition(position);
};
return (
<Layout>
<HomeSubHomeSectionHeading>
<ColorChanger scrollPosition={scrollPosition}></ColorChanger>
<MeSection>
<div className="container">
<MePhoto>
<ImageStamp>
<Img fluid={props.data.mattImage.childImageSharp.fluid} />
</ImageStamp>
</MePhoto>
<MeBio>
<h3>Hi, I'm Matt!</h3>
<hr />
<p>
I'm a Lead Frontend Developer currently based at Oliver Wyman
Digital. I have experience in a range of frontend technologies
and practices; more recently dabbling with AB testing, VueCLI
and Typescript.
</p>
<p>
Outside of the web world, I like to run, travel and like to
watch movies. Apart from Toy Story 1, I cried when I found out
Buzz Lightyear couldn't fly.
</p>
<h4>Find out more</h4>
<hr />
<MeSocials>
<SocialLink
href="https://uk.linkedin.com/in/mattmaclennan"
target="_blank"
>
<FontAwesomeIcon icon={faLinkedin} />
</SocialLink>
<SocialLink href="https://github.com/mmaclenn" target="_blank">
<FontAwesomeIcon icon={faGithub} />
</SocialLink>
</MeSocials>
</MeBio>
</div>
</MeSection>
</HomeSubHomeSectionHeading>
<HomeHeading
preserveAspectRatio="xMinYMin meet"
scale={scale}
scrollPosition={scrollPosition}
>
<defs>
<mask id="mask" x="0" y="0" width="100%" height="100%">
<rect x="0" y="0" width="100%" height="100%" fill="#fff"></rect>
<text x="50%" y="40%" textAnchor="middle">
Matt Maclennan
</text>
<text id="editText" x="50%" y="45%" textAnchor="middle">
Web Developer
</text>
</mask>
</defs>
<rect
x="0"
y="0"
width="100%"
height="100%"
fill="#E1341E"
id="mask"
></rect>
</HomeHeading>
</Layout>
);
};
export const fluidImage = graphql`
fragment fluidImage on File {
childImageSharp {
fluid(maxWidth: 1000) {
...GatsbyImageSharpFluid
}
}
}
`;
export const pageQuery = graphql`
query {
mattImage: file(relativePath: { eq: "me.jpg" }) {
...fluidImage
}
}
`;
export default IndexPage;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I'm aware I can only animate transforms and opacity, also I've added translateZ to use hardware rendering and backface-visibility to the CSS with no luck.
Any ideas where I'm going wrong?
Edit: As per the comments, I have tried to throttle the scroll callback by using this package. Here is the code I'm using based on this package...
useScrollPosition(
({ prevPos, currPos }) => {
setScale(document && document.width > 500 ? 20 : 5);
const shouldBeStyle = {
transform: `scale(${
Math.abs(currPos.y) < 9 ? 1 : Math.abs(currPos.y) / scale
}) translateZ(0)`,
pointerEvents: `${Math.abs(currPos.y) > 1000 ? "none" : "auto"}`,
};
const opacityStyle = {
opacity: 1000 / Math.abs(currPos.y) / 20,
};
if (JSON.stringify(shouldBeStyle) === JSON.stringify(scrollStyling))
return;
setOpactiyStyling(opacityStyle);
setScrollStyling(shouldBeStyle);
},
[scrollStyling, opacityStyling]
);
useEffect(() => {
window.addEventListener("scroll", handleScroll, { passive: true });
});
This adds an event listener every time props change. You probably only want to do this once, e.g.:
useEffect(() => {
window.addEventListener("scroll", handleScroll, { passive: true });
}, []);
or at the very least put some dependencies in those brackets. See: https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
You can use this, also you need to return a cleanup after the component is unmounted. I hope it helps
useEffect(() => {
window.addEventListener("scroll", handleScroll, { passive: true });
return () => {
window.removeEventListener("scroll", handleScroll, { passive: true });
};
}, []);
I'm building a react app using facebook's:
https://github.com/facebookincubator/create-react-app
along w SASS: https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-a-css-preprocessor-sass-less-etc
I'm at a point now where I need to add a dropdown menu to the header. Similar to the header icons on StackOverflow in the top right that open and close on click.
I know this sounds like a dumb question but what is the right way to do this? Do I need to add a UI Framework like a bootstrap for something like this? I have no need for all the bootstrap theming etc...
Thank you - and please be kind to the question given I'm a solo developer and could really use some help building a solid foundation on my app.
Thanks
Yes you can do this easily with just React:
class Hello extends React.Component {
render() {
return <div className="nav">
<Link />
<Link />
<Link />
</div>;
}
}
class Link extends React.Component {
state = {
open: false
}
handleClick = () => {
this.setState({ open: !this.state.open });
}
render () {
const { open } = this.state;
return (
<div className="link">
<span onClick={this.handleClick}>Click Me</span>
<div className={`menu ${open ? 'open' : ''}`}>
<ul>
<li>Test 1</li>
<li>Test 2</li>
<li>Test 3</li>
</ul>
</div>
</div>
)
}
}
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
.nav {
display: flex;
border-bottom: 1px solid gray;
background: white;
padding: 0 10px;
}
.link {
width: 100px;
border-right: 1px solid gray;
padding: 10px;
text-align: center;
position: relative;
cursor: pointer;
}
.menu {
opacity: 0;
position: absolute;
top: 40px; // same as your nav height
left: 0;
background: #ededed;
border: 1px solid gray;
padding: 10px;
text-align: center;
transition: all 1000ms ease;
}
.menu.open {
opacity: 1;
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>
you can use react-select like this :
var Select = require('react-select');
var options = [
{ value: 'one', label: 'One' },
{ value: 'two', label: 'Two' }
];
function logChange(val) {
console.log("Selected: " + JSON.stringify(val));
}
<Select
name="form-field-name"
value="one"
options={options}
onChange={logChange}
/>
https://github.com/JedWatson/react-select
also this library :
https://www.npmjs.com/package/react-dropdown
Seems like your project is still in his infancy. And that you willing to incorporate a library to your project. So I would definitely recommend you to choose a library right now.
With React you could create your own menu without much effort. But you will also need other components for the rest of your app. And the quality of your menu (and other components) will most likely be greater if you choose a library used by many (rather than your own). For "quality" I mean: UX design, HTML standards, API reusability, number of defects, etc.
If you think your app will be small and won't need an entire UI Framework, you might want to search for an isolated component for menu. Here is a list of navigation components (including the number of github stars of each project):
https://devarchy.com/react/topic/navigation
But in most cases I would choose an entire UI Framework instead: https://devarchy.com/react/topic/ui-framework
And here are some demos of the menu/nav/app-bar of some popular UI Frameworks:
https://ant.design/components/menu/
https://react-bootstrap.github.io/components.html#navs-dropdown
http://www.material-ui.com/#/components/app-bar
Custom dropdown
Dropdown.js
import React, { useState } from "react";
import { mdiMenuDown } from "#mdi/js";
import Icon from "#mdi/react";
export default function DropDown({ placeholder, content }) {
const [active, setactive] = useState(0);
const [value, setvalue] = useState(0);
return (
<div className={active ? "dropdown_wrapper active" : "dropdown_wrapper"}>
<span
onClick={() => {
setactive(active ? 0 : 1);
}}
>
{value ? value : placeholder} <Icon path={mdiMenuDown} />
</span>
<div className="drop_down">
<ul>
{content &&
content.map((item, key) => {
return (
<li
onClick={() => {
setvalue(item);
setactive(0);
}}
key={key}
>
{item}
</li>
);
})}
</ul>
</div>
</div>
);}
dropdown.css
.dropdown_wrapper {
position: relative;
margin-left: 100px;
cursor: pointer;
}
.dropdown_wrapper span {
padding: 12px;
border: 1px solid #a8aaac;
border-radius: 6px;
background-color: #ffffff;
display: flex;
color: #3d3e3f;
font-size: 16px;
letter-spacing: 0;
line-height: 26px;
justify-content: space-between;
text-transform: capitalize;
}
.dropdown_wrapper span svg {
width: 20px;
margin-left: 80px;
fill: #fbb800;
transition: 0.5s ease all;
}
.dropdown_wrapper.active span svg {
transform: rotate(180deg);
transition: 0.5s ease all;
}
.dropdown_wrapper .drop_down {
background-color: #fff;
box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.2);
position: absolute;
top: 53px;
width: 100%;
z-index: 9;
border-radius: 6px;
border: 1px solid #a8aaac;
height: 0px;
opacity: 0;
overflow: hidden;
transition: 0.5s ease all;
}
.dropdown_wrapper.active .drop_down {
height: fit-content;
opacity: 1;
transition: 0.5s ease all;
}
.dropdown_wrapper .drop_down ul {
list-style: none;
padding-left: 0;
margin: 0;
padding: 10px;
}
.dropdown_wrapper .drop_down ul li {
padding: 10px 0px;
color: #3d3e3f;
font-size: 16px;
letter-spacing: 0;
line-height: 26px;
text-transform: capitalize;
cursor: pointer;
font-weight: 300;
}
.dropdown_wrapper .drop_down ul li:hover {
color: #faab1e;
transition: 0.5s ease all;
}
parent.js
<DropDown placeholder="select a type" content={["breakfast", "lunch", "dinner", "Snacks"]} />
I've implemented my own responsive accordion in React, and I can't get it to animate the opening of a fold.
This is especially odd because I can get the icon before the title to animate up and down, and, other than the icon being a pseudo-element, I can't seem to see the difference between the two.
JS:
class Accordion extends React.Component {
constructor(props) {
super(props);
this.state = {
active: -1
};
}
/***
* Selects the given fold, and deselects if it has been clicked again by setting "active to -1"
* */
selectFold = foldNum => {
const current = this.state.active === foldNum ? -1 : foldNum;
this.setState(() => ({ active: current }));
};
render() {
return (
<div className="accordion">
{this.props.contents.map((content, i) => {
return (
<Fold
key={`${i}-${content.title}`}
content={content}
handle={() => this.selectFold(i)}
active={i === this.state.active}
/>
);
})}
</div>
);
}
}
class Fold extends React.Component {
render() {
return (
<div className="fold">
<button
className={`fold_trigger ${this.props.active ? "open" : ""}`}
onClick={this.props.handle}
>
{this.props.content.title}
</button>
<div
key="content"
className={`fold_content ${this.props.active ? "open" : ""}`}
>
{this.props.active ? this.props.content.inner : null}
</div>
</div>
);
}
}
CSS:
$line-color: rgba(34, 36, 38, 0.35);
.accordion {
width: 100%;
padding: 1rem 2rem;
display: flex;
flex-direction: column;
border-radius: 10%;
overflow-y: auto;
}
.fold {
.fold_trigger {
&:before {
font-family: FontAwesome;
content: "\f107";
display: block;
float: left;
padding-right: 1rem;
transition: transform 400ms;
transform-origin: 20%;
color: $line-color;
}
text-align: start;
width: 100%;
padding: 1rem;
border: none;
outline: none;
background: none;
cursor: pointer;
border-bottom: 1px solid $line-color;
&.open {
&:before {
transform: rotateZ(-180deg);
}
}
}
.fold_content {
display: none;
max-height: 0;
opacity: 0;
transition: max-height 400ms linear;
&.open {
display: block;
max-height: 400px;
opacity: 1;
}
}
border-bottom: 1px solid $line-color;
}
Here's the CodePen: https://codepen.io/renzyq19/pen/bovZKj
I wouldn't conditionally render the content if you want a smooth transition. It will make animating a slide-up especially tricky.
I would change this:
{this.props.active ? this.props.content.inner : null}
to this:
{this.props.content.inner}
and use this scss:
.fold_content {
max-height: 0;
overflow: hidden;
transition: max-height 400ms ease;
&.open {
max-height: 400px;
}
}
Try the snippet below or see the forked CodePen Demo.
class Accordion extends React.Component {
constructor(props) {
super(props);
this.state = {
active: -1
};
}
/***
* Selects the given fold, and deselects if it has been clicked again by setting "active to -1"
* */
selectFold = foldNum => {
const current = this.state.active === foldNum ? -1 : foldNum;
this.setState(() => ({ active: current }));
};
render() {
return (
<div className="accordion">
{this.props.contents.map((content, i) => {
return (
<Fold
key={`${i}-${content.title}`}
content={content}
handle={() => this.selectFold(i)}
active={i === this.state.active}
/>
);
})}
</div>
);
}
}
class Fold extends React.Component {
render() {
return (
<div className="fold">
<button
className={`fold_trigger ${this.props.active ? "open" : ""}`}
onClick={this.props.handle}
>
{this.props.content.title}
</button>
<div
key="content"
className={`fold_content ${this.props.active ? "open" : ""}`}
>
{this.props.content.inner}
</div>
</div>
);
}
}
const pictures = [
"http://unsplash.it/200",
"http://unsplash.it/200",
"http://unsplash.it/200",
];
var test = (title, text, imageURLs) => {
const images=
<div className='test-images' >
{imageURLs.map((url,i) => <img key={i} src={url} />)}
</div>;
const inner =
<div className='test-content' >
<p>{text} </p>
{images}
</div>;
return {title, inner};
};
const testData = [
test('Title', 'Content',pictures ),
test('Title', 'Content',pictures ),
test('Title', 'Content',pictures ),
test('Title', 'Content',pictures ),
test('Title', 'Content',pictures ),
];
ReactDOM.render(<Accordion contents={testData} />, document.getElementById('root'));
.accordion {
width: 100%;
padding: 1rem 2rem;
display: flex;
flex-direction: column;
border-radius: 10%;
overflow-y: auto;
}
.fold {
border-bottom: 1px solid rgba(34, 36, 38, 0.35);
}
.fold .fold_trigger {
text-align: start;
width: 100%;
padding: 1rem;
border: none;
outline: none;
background: none;
cursor: pointer;
border-bottom: 1px solid rgba(34, 36, 38, 0.35);
}
.fold .fold_trigger:before {
font-family: FontAwesome;
content: "\f107";
display: block;
float: left;
padding-right: 1rem;
transition: transform 400ms;
transform-origin: 20%;
color: rgba(34, 36, 38, 0.35);
}
.fold .fold_trigger.open:before {
transform: rotateZ(-180deg);
}
.fold .fold_content {
max-height: 0;
overflow: hidden;
transition: max-height 400ms ease;
}
.fold .fold_content.open {
max-height: 400px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.4.0/css/font-awesome.min.css" rel="stylesheet" />
<div id='root'></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Note:
I used ease instead of linear on the transition because I think it's a nicer effect. But that's just personal taste. linear will work as well.
Also, you can continue to conditionally render the content. A slide-down animation is possible, but a slide-up can't be easily achieved. There are some transition libraries that you could explore as well.
However, I think it's easiest to use the state just for conditional classes (as you are doing with the open class). I think conditionally rendering content to the DOM makes your life difficult if you're trying to do CSS animations.