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.
Related
I have a navigation bar that has a logo and a burgerbar that changes from a burger bar to an "x" when opened.
The problem is that the dropdown should go down when you click on the burger and go back up when you press the "x". This functions smoothly and perfectly so far, but the problem I noticed is that when I resize the browser window from >700px to <700px, the dropdown menu animation going up occurs.
import React, { useState, useRef, useEffect } from "react";
import { BurgerBar } from "../../styles/Navbar.style";
import Navlinks from "./Navlinks";
function Burger() {
const [open, setOpen] = useState(false);
const [width, setWidth] = useState(window.innerWidth);
const prevWidth = useRef();
useEffect(() => {
prevWidth.current = width;
}, [width]);
function checkResize() {
setWidth(window.innerWidth);
if (prevWidth.current <= 700 && width > 700) {
setOpen(false);
};
};
window.addEventListener("resize", checkResize);
return (
<>
<BurgerBar open={open} onClick={() => {setOpen(!open);}}>
<span />
<span />
<span />
</BurgerBar>
<Navlinks open={open} />
</>
)
};
export default Burger;
This is my styled-components file with the relevant styles:
export const BurgerBar = styled.span`
z-index: 1;
width: 2rem;
position: fixed;
display: flex;
justify-content: space-around;
flex-flow: column;
top: ${({open}) => open ? 22 : 24}px;
height: ${({open}) => open ? 2.3 : 2}rem;
right: ${({open}) => open ? 17 : 20}px;
span {
width: 2rem;
height: 0.25rem;
background-color: rgb(46, 203, 64);
border-radius: 10px;
transform-origin: 1px;
&:nth-child(1) {
transform: ${({open}) => open ? 'rotate(45deg)' : 'rotate(0)'};
width: ${({open}) => open ? 2.3 : 2}rem;
}
&:nth-child(2) {
transform: ${({open}) => open ? 'translateX(100%)' : 'translateX(0)'};
opacity: ${({open}) => open ? 0 : 1};
}
&:nth-child(3) {
transform: ${({open}) => open ? 'rotate(-45deg)' : 'rotate(0)'};
width: ${({open}) => open ? 2.3 : 2}rem;
}
}
&:hover {
opacity: 0.7;
cursor: pointer;
}
#media (min-width: 701px) {
visibility: hidden;
}
`;
export const Dropdown = styled.ul`
z-index: 1;
position: fixed;
list-style: none;
display: flex;
flex-flow: row nowrap;
padding: 0;
margin: 0;
top: 0;
li {
padding: 20px 20px;
&:hover {
opacity: 0.7;
cursor: pointer;
}
}
#media (max-width: 700px) {
position: fixed;
z-index: 0;
flex-flow: column nowrap;
background-color: rgb(32, 80, 36);
transform: ${({ open }) => (open) ? 'translateY(0)' : 'translateY(-100%)'};
vertical-align: middle;
transition: transform 0.3s ease-in-out;
top: 85px;
align-items: center;
border-bottom-left-radius: 4%;
li {
padding-top: 10px;
padding-bottom: 4px;
}
}
`;
I recognize that the problem lies in these two lines:
transform: ${({ open }) => (open) ? 'translateY(0)' : 'translateY(-100%)'};
and
transition: transform 0.3s ease-in-out;
I'm new to Javascript and React though and I cannot think of an efficient way to block the transition animation from going off upon browser resizing.
I can link my repo if more code/reference is necessary.
I have one large image on the top and three smaller images on the bottom of my page. I want to make it so when you hover the small image (it's also a link which goes to different place) the large image changes to this small image. So clearly explained, two images change/swap places. And when I unhover the large image changes back. I tried a couple solution but it didn't work so I'm in trouble, can you please help me?
I'm looking for CSS/Javascript-solution NOT JQUERY!
Thanks and sorry for bothering!
import React from "react";
import { Link } from "react-router-dom";
import "../screens/StyleName.css";
import { Card } from "react-bootstrap";
import Categories from "../components/Categories.json";
import { useLocation } from "react-router-dom";
// images:
import LargeImage from "../images/largeimage.jpg;
import SmallImage1 from "../images/small_image1.jpg";
import SmallImage2 from "../images/small_image2.jpg";
import SmallImage3 from "../images/small_image3.jpg";
import Placeholder from "../images/placeholder.jpg";
function FunctionName() {
let location = useLocation();
const ShowImage = (ImageName) => {
if (ImageName === "SmallImage1") {
return SmallImage1;
} else if (ImageName === "SmallImage2") {
return SmallImage2;
} else if (ImageName === "SmallImage3") {
return SmallImage3;
} else {
return Placeholder;
}
};
const AllCategories = () => {
return Categories.map((category, index) => {
const data = () => {
if (category.children) {
return { data: category.children };
} else {
return { data: undefined };
}
};
return (
<div key={index} className="category-card">
<Card style={{ border: "none" }}>
<Link
to={{
pathname: location.pathname + "/" + category.name,
state: data(),
}}
className="category-link link"
>
<div className="img__wrap">
<Card.Img
variant="top"
alt="card-img-top"
className="small-img-down img-responsive img__img"
src={ShowImage(category.name)}
/>
<p className="img__description">{category.name}</p>
</div>
</Link>
</Card>
</div>
);
});
};
return (
<div className="maincategory-top">
<div className="category-container">
<div className="col-sm-8">
<Link to="/product" className="category-link link">
<Card.Img
alt="card-img-top"
className="big-img img-responsive"
src={LargeImage}
/>
</Link>
<div className="row">{AllCategories()}</div>
</div>
</div>
</div>
);
}
export default FunctionName;
CSS:
.big-img {
display: block;
margin-left: auto;
margin-right: auto;
margin-bottom: 3%;
overflow: auto;
width: 470px;
height: 300px;
}
.category-container {
padding-left: 8%;
padding-bottom: 10%;
margin-top: 10%;
}
.card-title-main {
font-size: 10px;
letter-spacing: 1.2px;
font-weight: normal;
text-transform: uppercase;
}
a.category-link:link {
color: black;
background-color: transparent;
text-decoration: none;
}
a.category-link:visited {
color: black;
background-color: transparent;
text-decoration: none;
}
a.category-link:hover {
color: black;
background-color: transparent;
text-decoration: none;
}
a.category-link:active {
color: black;
background-color: transparent;
text-decoration: none;
}
.category2 {
margin-top: 1%;
text-align: center;
}
.small-img-down {
width: 120px;
height: 80px;
margin-right: 13px;
margin-bottom: 13px;
display: inline-block;
}
.small-img-down:hover {
transform: scale(0.9);
}
.category-card {
display: block;
margin-left: auto;
margin-right: auto;
}
.img__wrap:hover .img__description {
visibility: visible;
opacity: 1;
padding: 20px;
}
.img__description {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
text-transform: uppercase;
background: rgba(245, 245, 245, 0.363);
color: black;
letter-spacing: 1.2px;
text-align: center;
visibility: hidden;
font-size: 16px;
opacity: 0;
transition: opacity 0.2s, visibility 0.2s;
}
() => {
const default_large = "image_one"
const [largeImage, setLargeImage] = useState(default_large);
return(
<div>
<img
src={image_one}
onMouseEnter={() => setLargeImage("image_one")}
onMouseOut={() => setLargeImage(default_large)}
className={largeImage === "image_one" ? "large" : "small"}
/>
<img
src={image_two}
onMouseEnter={() => setLargeImage("image_two")}
onMouseOut={() => setLargeImage(default_large)}
className={largeImage === "image_two" ? "large" : "small"}
/>
<img
src={image_three}
onMouseEnter={() => setLargeImage("image_three")}
onMouseOut={() => setLargeImage(default_large)}
className={largeImage === "image_three" ? "large" : "small"}
/>
</div>
)
}
In your css.
img.large{
width: 500px
}
img.small{
width: 100px
}
Explanation:
Keep track of what image is large in your component state. Whenever an image is hovered on, update the state accordingly. After the hover event, return the state to its default state. The image will have the "large" class while its being hovered on, and when no image is being hovered on, the default large image will be large.
I have question about Vue.js. How can i fix this? I didn't find anything in documentation. I've got this error: "[vue/require-v-for-key]
Elements in iteration expect to have 'v-bind:key' directives."
And this: "Elements in iteration expect to have 'v-bind:key' directives."
I have this i my Roulette.vue
<template>
<div class="roulette">
<h1>Roulette</h1>
<div class="radio" v-for="genre in genres"> **here**
<input
#change="onGenrePick"
type="radio"
name="genre"
v-bind:id="genre.id"
v-bind:value="genre.id">
<label v-bind:for="genre.id">{{genre.name}}</label>
</div>
<Button class="btn" :onClick="roll">Roll</Button>
<MovieCard
v-if="!!movie"
v-bind:image="serviceGetImagePath(movie.poster_path)"
v-bind:title="movie.title"
v-bind:releaseDate="serviceFormatYear(movie.release_date)"
v-bind:id="movie.id"
v-bind:voteAverage="movie.vote_average"/>
</div>
</template>
<script>
import MovieCard from '../components/MovieCard'
import Button from '../components/Button'
import {movieService} from '../mixins/movieService'
export default {
name: 'Roulette',
components: {Button, MovieCard},
mixins: [movieService],
mounted: async function () {
this.genres = await this.serviceGetGenres()
},
data: () => ({
genres: [],
pickedGenres: new Set(),
movie: null
}),
methods: {
async roll() {
const genreIds = Array.from(this.pickedGenres)
const movies = await this.serviceGetMoviesByGenre(genreIds)
this.movie = movies[this.getRandom(movies.length)]
},
onGenrePick(e) {
this.pickedGenres.add(e.target.value)
},
getRandom(max) {
return Math.floor(Math.random() * max - 1)
}
}
}
</script>
<style scoped lang="scss">
.roulette {
margin: 40px;
}
.btn {
display: block;
min-width: 220px;
}
.radio {
display: inline-block;
margin: 20px 10px;
> label {
margin-left: 5px;
}
}
</style>
And in my UpcomingMovies.vue also
<template>
<div class="wrapper" v-if="movies.length">
<MovieCard
v-for="movie in movies" **here**
v-bind:image="serviceGetImagePath(movie.poster_path)"**here**
v-bind:title="movie.title"**here**
v-bind:releaseDate="serviceFormatYear(movie.release_date)"**here**
v-bind:id="movie.id"**here**
v-bind:voteAverage="movie.vote_average"/>**here**
<div class="loader">
<Button class="loader__btn" :onClick="loadNextPage">Load</Button>
</div>
<router-link :to="routes.roulette.path">
<div class="roulette">
<img src="../assets/roulette.png" alt="Roulette">
</div>
</router-link>
</div>
<Loader v-else/>
</template>
<script>
import Button from '../components/Button'
import MovieCard from '../components/MovieCard'
import Loader from '../components/Loader'
import { movieService } from '../mixins/movieService'
import routes from '../router/routes'
export default {
name: 'UpcomingMovies',
mixins: [movieService],
components: { Button, MovieCard, Loader },
data: () => ({
movies: [],
page: 1,
routes
}),
mounted() {
this.getMovies(this.page)
},
methods: {
getMovies: async function (page) {
const movies = await this.serviceGetMovies(page)
this.movies.push(...movies)
},
loadNextPage() {
this.getMovies(++this.page)
}
}
}
</script>
<style scoped lang="scss">
.wrapper {
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
margin-top: 40px;
margin-bottom: 40px;
}
.loader {
width: 100%;
text-align: center;
margin-top: 40px;
margin-bottom: 40px;
&__btn {
border: 5px dashed white;
background-color: transparent;
border-radius: 50%;
width: 80px;
height: 80px;
font-weight: bold;
text-transform: uppercase;
transition: border-radius 100ms ease-in-out, width 120ms ease-in-out 120ms;
&:hover {
border-radius: 0;
background-color: rgba(white, 0.1);
width: 200px;
}
}
}
.roulette {
cursor: pointer;
position: fixed;
right: 25px;
bottom: 25px;
> img {
opacity: .8;
animation: rotate 5s infinite;
width: 70px;
height: auto;
transition: opacity 220ms linear;
&:hover {
opacity: 1;
}
}
}
#keyframes rotate {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
</style>
Vue internally uses unique keys for each loop to determine which element to update at which point during an update process. Therefore every v-for needs a v-bind:key attribute to work properly. In your special case this would be as the following:
<div class="radio" v-for="genre in genres" v-bind:key="someUniqueId"> **here**
You can use the current loop index as ID or anything else.
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 am using ReactJS to create a collapse/extend list with animation. You can check out the code from https://codepen.io/zhaoyi0113/pen/qjRKXE. When the user click on the Extend button at the top, the list should be shown within 1 second and at the mean time the footer should be moved to the bottom. My current code doesn't work very well. First, the list items are not shown with the correct animation style. It seems that all items are rendered at the same time. Second, the footer component move to the bottom immediately after I click extend button. How can I implement the list correctly with reactjs animation?
See the Pen React Animation Demo by joey (#zhaoyi0113) on CodePen.
Below is the reactjs code I am using:
const ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
const Container = React.createClass({
getInitialState() {
return {
items: ['Click', 'To', 'Remove', 'An', 'Item'],
extend: false
};
},
render() {
return (
<div className="container">
<div className="animation-container">
<div onClick={() => this.expendItems()} className="item">
{this.state.extend?'Collapse':'Extend'}
</div>
<ReactCSSTransitionGroup transitionName="example">
{this.renderItems()}
</ReactCSSTransitionGroup>
<div>Footer</div>
</div>
</div>
);
},
renderItems() {
const items = this.state.extend? this.state.items: [];
console.log('render items ', items);
return items.map((item, i) => {
return (
<div key={item} className="item">
{item}
</div>
);
});
},
expendItems() {
this.setState({extend: !this.state.extend});
}
});
ReactDOM.render(<Container/>, document.body);
With the way ReactCSSTransitionGroup all components render at the same time.
Moreover, you had the wrong initial translation values, you want to transition from negative values to 0, so that it seems like the buttons are sliding down from the under the expand button
body {
font-family: 'Open Sans', sans-sarif;
padding: 25px;
background-color: $color6;
text-align: center;
}
.container {
text-align: center;
vertical-align: middle;
overflow: hidden;
}
.animation-container {
display: inline-block;
}
.item {
background-color: $color3;
width: 400px;
text-align: center;
padding: 10px 5px;
margin-top: 10px;
border-radius: 8px;
font-weight: 600;
color: mix(black, $color3, 60%);
position: relative;
&:hover {
cursor: pointer;
}
}
div > .item {
margin-top: 0px;
}
.example-enter {
top: -240px;
}
.example-enter.example-enter-active {
top: 0px;
transition: top 1s ease;
}
.example-leave {
top: 0px;
transition: top 1s ease;
}
.example-leave.example-leave-active {
top: -240px;
}
https://codepen.io/anon/pen/Gvrgqm