Skip to content

Instantly share code, notes, and snippets.

@tkh44
Last active February 27, 2017 23:26
Show Gist options
  • Select an option

  • Save tkh44/8db3dcd0161e7209fec116047cf3d392 to your computer and use it in GitHub Desktop.

Select an option

Save tkh44/8db3dcd0161e7209fec116047cf3d392 to your computer and use it in GitHub Desktop.
Attempting to simplify react-motion's TransitionMotion component api
import { cloneElement, createElement as h, PropTypes, PureComponent } from 'react'
import { TransitionMotion } from 'react-motion'
export default class Eccentric extends PureComponent {
static defaultProps = {
component: h('div'),
data: []
};
static propTypes = {
// wrapper element ex: <div/>
component: PropTypes.element,
// array of data
data: PropTypes.array,
// (key, data, style) => ReactElement
// called once render for each item in `data` with the key from `getKey`, `data` from `data[index]`, and
// `style`, the result of: `onComponentMount`, `onRender`, `onRemount`, or `onUnmount`
//
// If an array is provided it will render the resulting elements in order.
// This is useful for creating animated layers.
render: PropTypes.oneOf([PropTypes.func, PropTypes.arrayOf(PropTypes.func)]),
// (data, i) => string
// help identify which items have been changed, added, or are removed
// https://facebook.github.io/react/docs/lists-and-keys.html
getKey: PropTypes.func,
// (data, i) => Style object
// data === props.data[i]
// called when `component` mounts
// do not wrap values in springs
onComponentMount: PropTypes.func,
// (data, i) => Style object
// data === props.data[i]
// called when `component` mounts
// do not wrap values in springs
onRender: PropTypes.func,
// (data, i) => Style object
// data === props.data[i]
// do not wrap values in springs
onRemount: PropTypes.func,
// (data, i) => Style object
// data === props.data[i]
// do not wrap values in springs
onUnmount: PropTypes.func
}
render () {
return h(TransitionMotion, {
defaultStyles: this.getDefaultStyles(),
styles: this.getStyles(),
willEnter: this.willEnter,
willLeave: this.willLeave,
children: this.renderCurrentStyles
})
}
renderCurrentStyles = currentStyles => {
const { component, render } = this.props
let children
if (Array.isArray(render)) {
// If render is an array, children becomes and array of arrays.
// This is useful if you want to create layers of animation
children = new Array(render.length)
for (let i = 0; i < render.length; ++i) {
children[i] = new Array(currentStyles.length)
for (let j = 0; j < currentStyles.length; ++j) {
const { key, data, style } = currentStyles[j]
children[i][j] = render[i](key, data, style, j)
}
}
} else {
children = new Array(currentStyles.length)
for (let j = 0; j < currentStyles.length; ++j) {
const { key, data, style } = currentStyles[j]
children[j] = render(key, data, style, j)
}
}
return cloneElement(component, { children })
};
getDefaultStyles = () => {
const {
getKey,
onComponentMount,
data
} = this.props
let mappedData = new Array(data.length)
for (let i = 0; i < data.length; ++i) {
mappedData[i] = {
key: getKey(data[i], i),
data: data[i],
style: onComponentMount(data[i], i)
}
}
return mappedData
};
getStyles = () => {
const {
getKey,
onRender,
data
} = this.props
let mappedData = new Array(data.length)
for (let i = 0; i < data.length; ++i) {
mappedData[i] = {
key: getKey(data[i], i),
data: data[i],
style: onRender(data[i], i)
}
}
return mappedData
};
willEnter = config => this.props.onRemount(config);
willLeave = config => this.props.onUnmount(config);
}
import { createElement as h, PureComponent } from 'react'
import { spring } from 'react-motion'
import Eccentric from 'ui/Eccentric'
export default class TimestampBarContainer extends PureComponent {
render () {
return h(Eccentric, {
data: this.props.data,
component: <ul />,
getKey: data => {
return data.name
},
onComponentMount: data => {
// no springs
return {
top: data.top,
left: data.left,
opacity: data.top > 500 ? 0 : 1,
translateY: data.offsetY
}
},
onRender: (data, i) => {
// springs
return {
top: spring(i * data.top),
left: spring(data.left),
opacity: data.top > 500 ? 0 : 1, // you don't have to use them,
translateY: spring(data.offsetY)
}
},
onRemount: ({ key, data, style }) => {
// no springs
// key, data, style come from the config generated by `onRender`.
// When the piece of data is remounted this function is called to override the initial position of the rendered element
// We are creating a starting point to our animation here
// *from* onRemount styles *to* onRender styles
// be careful using values from `style` they will be `spring` objects
return {
top: data.top,
left: data.left,
opacity: data.top > 500 ? 0 : 1,
translateY: data.offsetY
}
},
onUnmount: ({ key, data, style }) => {
// springs
// Where to animate *to* when removing this data item
return {
top: spring(data.top),
left: spring(data.left),
opacity: data.top > 500 ? 0 : 1, // you don't have to use them,
translateY: spring(data.offsetY)
}
},
render: (key, data, style) => {
// called for each data item with its key, data and style
return (
<li
key={key}
style={{
position: 'absolute',
top: style.top,
left: style.left,
opacity: style.opacity,
transform: `translateY(${style.translateY})`
}}
onClick={this.handleClick.bind(this, key, data, style)}
>
{data.name} | { data.username}
</li>
)
}
})
}
handleClick = (key, data, style, evt) => {
console.log(key, data, style)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment