import React, { Component } from "react"const listData = [...Array(3).keys()]const ListItem = ({ index, onClick }) => (<div className="listItem" onClick={() => onClick(index)}><div className="listItemContent"><div className="avatar" /><div className="description">{listData.map(i => (<div key={i} />))}</div></div></div>)const ExpandedListItem = ({ index, onClick }) => (<div className="expandedListItem" onClick={() => onClick(index)}><div className="expandedListItemContent"><div className="avatar avatarExpanded" /><div className="description">{listData.map(i => (<div key={i} />))}</div><div className="additional-content">{listData.map(i => (<div key={i} />))}</div></div></div>)export default class AnimatedList extends Component {state = { focused: null }onClick = index =>this.setState({focused: this.state.focused === index ? null : index})render() {return (<div className="staggered-list-content"><ul className="list">{listData.map(index => {return (<li key={index}>{index === this.state.focused ? (<ExpandedListItemindex={this.state.focused}onClick={this.onClick}/>) : (<ListItem index={index} key={index} onClick={this.onClick} />)}</li>)})}</ul></div>)}}
ListItem
import React, { Component } from "react"import { Flipper, Flipped } from "react-flip-toolkit"const listData = [...Array(3).keys()]const ListItem = ({ index, onClick }) => (<Flipped flipId={`listItem-${index}`}><div className="listItem" onClick={() => onClick(index)}><div className="listItemContent"><div className="avatar" /><div className="description">{listData.map(i => (<div key={i} />))}</div></div></div></Flipped>)const ExpandedListItem = ({ index, onClick }) => (<Flipped flipId={`listItem-${index}`}><div className="expandedListItem" onClick={() => onClick(index)}><div className="expandedListItemContent"><div className="avatar avatarExpanded" /><div className="description">{listData.map(i => (<div key={i} />))}</div><div className="additional-content">{listData.map(i => (<div key={i} />))}</div></div></div></Flipped>)export default class AnimatedList extends Component {state = { focused: null }onClick = index =>this.setState({focused: this.state.focused === index ? null : index})render() {return (<FlipperflipKey={this.state.focused}className="staggered-list-content"spring="gentle"><ul className="list">{listData.map(index => {return (<li key={index}>{index === this.state.focused ? (<ExpandedListItemindex={this.state.focused}onClick={this.onClick}/>) : (<ListItem index={index} key={index} onClick={this.onClick} />)}</li>)})}</ul></Flipper>)}}
Flipper
and Flipped
componentsListItem
and ExpandedListItem
will automatically be tweened.
import React, { Component } from "react"import { Flipper, Flipped } from "react-flip-toolkit"const listData = [...Array(3).keys()]const ListItem = ({ index, onClick }) => (<Flipped flipId={`listItem-${index}`}><div className="listItem" onClick={() => onClick(index)}><Flipped inverseFlipId={`listItem-${index}`}><div className="listItemContent"><div className="avatar" /><div className="description">{listData.map(i => (<div />))}</div></div></Flipped></div></Flipped>)const ExpandedListItem = ({ index, onClick }) => (<Flipped flipId={`listItem-${index}`}><div className="expandedListItem" onClick={() => onClick(index)}><Flipped inverseFlipId={`listItem-${index}`}><div className="expandedListItemContent"><div className="avatar avatarExpanded" /><div className="description">{listData.map(i => (<div />))}</div><div className="additional-content">{listData.map(i => (<div key={i} />))}</div></div></Flipped></div></Flipped>)export default class AnimatedList extends Component {state = { focused: null }onClick = index =>this.setState({focused: this.state.focused === index ? null : index})render() {return (<FlipperflipKey={this.state.focused}className="staggered-list-content"spring="gentle"><ul className="list">{listData.map(index => {return (<li key={index}>{index === this.state.focused ? (<ExpandedListItemindex={this.state.focused}onClick={this.onClick}/>) : (<ListItem index={index} key={index} onClick={this.onClick} />)}</li>)})}</ul></Flipper>)}}
Flipped
component to wrap the inner contents of ListItem
inverseFlipId
prop, we've totally canceled the effects of the parent transform on the children
import React, { Component } from 'react'import { Flipper, Flipped } from 'react-flip-toolkit'const listData = [...Array(3).keys()]const ListItem = ({ index, onClick }) => (<Flipped flipId={`listItem-${index}`}><div className="listItem" onClick={() => onClick(index)}><Flipped inverseFlipId={`listItem-${index}`}><div className="listItemContent"><Flipped flipId={`avatar-${index}`}><div className="avatar" /></Flipped><div className="description">{listData.map(i => (<Flipped flipId={`description-${index}-${i}`} key={i}><div /></Flipped>))}</div></div></Flipped></div></Flipped>)const ExpandedListItem = ({ index, onClick }) => (<Flipped flipId={`listItem-${index}`}><div className="expandedListItem" onClick={() => onClick(index)}><Flipped inverseFlipId={`listItem-${index}`}><div className="expandedListItemContent"><Flipped flipId={`avatar-${index}`}><div className="avatar avatarExpanded" /></Flipped><div className="description">{listData.map(i => (<Flipped flipId={`description-${index}-${i}`} key={i}><div /></Flipped>))}</div><div className="additional-content">{listData.map(i => (<div key={i} />))}</div></div></Flipped></div></Flipped>)export default class AnimatedList extends Component {state = { focused: null }onClick = index =>this.setState({focused: this.state.focused === index ? null : index})render() {return (<FlipperflipKey={this.state.focused}className="staggered-list-content"spring="gentle"><ul className="list">{listData.map(index => {return (<li key={index}>{index === this.state.focused ? (<ExpandedListItemindex={this.state.focused}onClick={this.onClick}/>) : (<ListItem index={index} key={index} onClick={this.onClick} />)}</li>)})}</ul></Flipper>)}}
ListItem
in their own Flipped
componentsFlipped
component to only animate in certain situations
import React, { Component } from "react"import { Flipper, Flipped } from "react-flip-toolkit"const listData = [...Array(3).keys()]const shouldFlip = index => (prevDecisionData, currentDecisionData) =>index === prevDecisionData || index === currentDecisionDataconst ListItem = ({ index, onClick }) => (<Flipped flipId={`listItem-${index}`} shouldInvert={shouldFlip(index)}><div className="listItem" onClick={() => onClick(index)}><Flipped inverseFlipId={`listItem-${index}`}><div className="listItemContent"><Flipped flipId={`avatar-${index}`} shouldFlip={shouldFlip(index)}><div className="avatar" /></Flipped><div className="description">{listData.map(i => (<FlippedflipId={`description-${index}-${i}`}shouldFlip={shouldFlip(index)}><div /></Flipped>))}</div></div></Flipped></div></Flipped>)const ExpandedListItem = ({ index, onClick }) => (<Flipped flipId={`listItem-${index}`}><div className="expandedListItem" onClick={() => onClick(index)}><Flipped inverseFlipId={`listItem-${index}`}><div className="expandedListItemContent"><Flipped flipId={`avatar-${index}`}><div className="avatar avatarExpanded" /></Flipped><div className="description">{listData.map(i => (<Flipped flipId={`description-${index}-${i}`} key={i}><div /></Flipped>))}</div><div className="additional-content">{listData.map(i => (<div key={i} />))}</div></div></Flipped></div></Flipped>)export default class AnimatedList extends Component {state = { focused: null }onClick = index =>this.setState({focused: this.state.focused === index ? null : index})render() {return (<FlipperflipKey={this.state.focused}className="staggered-list-content"spring="gentle"decisionData={this.state.focused}><ul className="list">{listData.map(index => {return (<li key={index}>{index === this.state.focused ? (<ExpandedListItemindex={this.state.focused}onClick={this.onClick}/>) : (<ListItem index={index} key={index} onClick={this.onClick} />)}</li>)})}</ul></Flipper>)}}
decisionData
prop to Flipper
with some data that will help Flipped
components decide whether to animate
import React, { Component } from "react"import { Flipper, Flipped } from "react-flip-toolkit"const listData = [...Array(3).keys()]const shouldFlip = index => (prev, current) =>index === prev || index === currentconst ListItem = ({ index, onClick }) => (<FlippedflipId={`listItem-${index}`}stagger="card"shouldInvert={shouldFlip(index)}><div className="listItem" onClick={() => onClick(index)}><Flipped inverseFlipId={`listItem-${index}`}><div className="listItemContent"><FlippedflipId={`avatar-${index}`}stagger="card-content"shouldFlip={shouldFlip(index)}><div className="avatar" /></Flipped><div className="description">{listData.slice(0, 3).map(i => (<FlippedflipId={`description-${index}-${i}`}stagger="card-content"shouldFlip={shouldFlip(index)}><div /></Flipped>))}</div></div></Flipped></div></Flipped>)const ExpandedListItem = ({ index, onClick }) => (<Flipped flipId={`listItem-${index}`} stagger="card"><div className="expandedListItem" onClick={() => onClick(index)}><Flipped inverseFlipId={`listItem-${index}`}><div className="expandedListItemContent"><Flipped flipId={`avatar-${index}`} stagger="card-content"><div className="avatar avatarExpanded" /></Flipped><div className="description">{listData.slice(0, 3).map(i => (<FlippedflipId={`description-${index}-${i}`}stagger="card-content"><div /></Flipped>))}</div><div className="additional-content">{listData.slice(0, 3).map(i => (<div key={i} />))}</div></div></Flipped></div></Flipped>)export default class AnimatedList extends Component {state = { focused: null }onClick = index =>this.setState({focused: this.state.focused === index ? null : index})render() {return (<FlipperflipKey={this.state.focused}className="staggered-list-content"spring="gentle"staggerConfig={{card: {reverse: this.state.focused !== null,speed: 0.5}}}decisionData={this.state.focused}><ul className="list">{listData.map(index => {return (<li key={index}>{index === this.state.focused ? (<ExpandedListItemindex={this.state.focused}onClick={this.onClick}/>) : (<ListItem index={index} key={index} onClick={this.onClick} />)}</li>)})}</ul></Flipper>)}}
Flipped
components with unique string ids identifying which stagger group they belong toExpandedListItem
import React, { Component } from "react"import { Flipper, Flipped } from "react-flip-toolkit"const listData = [...Array(3).keys()]const shouldFlip = index => (prev, current) =>index === prev || index === currentconst ListItem = ({ index, onClick }) => (<FlippedflipId={`listItem-${index}`}stagger="card"shouldInvert={shouldFlip(index)}><div className="listItem" onClick={() => onClick(index)}><Flipped inverseFlipId={`listItem-${index}`}><div className="listItemContent"><FlippedflipId={`avatar-${index}`}stagger="card-content"shouldFlip={shouldFlip(index)}><div className="avatar" /></Flipped><div className="description">{listData.slice(0, 3).map(i => (<FlippedflipId={`description-${index}-${i}`}stagger="card-content"shouldFlip={shouldFlip(index)}><div /></Flipped>))}</div></div></Flipped></div></Flipped>)const ExpandedListItem = ({ index, onClick }) => (<FlippedflipId={`listItem-${index}`}stagger="card"onStart={el => {setTimeout(() => {el.classList.add("animated-in")}, 600)}}><div className="expandedListItem" onClick={() => onClick(index)}><Flipped inverseFlipId={`listItem-${index}`}><div className="expandedListItemContent"><Flipped flipId={`avatar-${index}`} stagger="card-content"><div className="avatar avatarExpanded" /></Flipped><div className="description">{listData.slice(0, 3).map(i => (<FlippedflipId={`description-${index}-${i}`}stagger="card-content"><div /></Flipped>))}</div><div className="additional-content">{listData.slice(0, 3).map(i => (<div key={i} />))}</div></div></Flipped></div></Flipped>)export default class AnimatedList extends Component {state = { focused: null }onClick = index =>this.setState({focused: this.state.focused === index ? null : index})render() {return (<FlipperflipKey={this.state.focused}className="staggered-list-content"spring="gentle"staggerConfig={{card: {reverse: this.state.focused !== null,speed: 0.5}}}decisionData={this.state.focused}><ul className="list">{listData.map(index => {return (<li key={index}>{index === this.state.focused ? (<ExpandedListItemindex={this.state.focused}onClick={this.onClick}/>) : (<ListItem index={index} key={index} onClick={this.onClick} />)}</li>)})}</ul></Flipper>)}}
onStart
callback on the Flipped
component allows us to do some work at the beginning of a FLIP animationYou can check out the final code in this Code Sandbox