react-stonecutter
Version:
Animated grid layout component for React
139 lines (119 loc) • 3.78 kB
JSX
import React from 'react';
import { TransitionMotion, spring } from 'react-motion';
import stripStyle from 'react-motion/lib/stripStyle';
import { buildTransform, positionToProperties } from '../utils/transformHelpers';
import isEqual from 'lodash.isequal';
import { commonPropTypes, commonDefaultProps } from '../utils/commonProps';
export default React.createClass({
propTypes: {
...commonPropTypes,
springConfig: React.PropTypes.shape({
stiffness: React.PropTypes.number,
damping: React.PropTypes.number,
precision: React.PropTypes.number
})
},
getDefaultProps() {
return {
...commonDefaultProps,
springConfig: { stiffness: 60, damping: 14, precision: 0.1 }
};
},
componentWillMount() {
this.setState(this.doLayout(this.props));
},
componentWillReceiveProps(nextProps) {
if (!isEqual(nextProps, this.props)) {
this.setState(this.doLayout(nextProps));
}
},
doLayout(props) {
const items = React.Children.toArray(props.children)
.map(element => ({
key: element.key,
data: {
element
}
}));
const { positions, gridWidth, gridHeight } =
props.layout(items.map(item => ({
...item.data.element.props,
key: item.data.element.key
})), props);
const styles = positions.map((position, i) => ({
...items[i],
style: {
...items[i].style,
zIndex: 2,
...springify(props.entered(items[i].data.element.props,
props, { gridWidth, gridHeight }), props.springConfig),
...springify(positionToProperties(position), props.springConfig)
}
}));
return { styles, gridWidth, gridHeight };
},
willEnter(transitionStyle) {
const { gridWidth, gridHeight } = this.state;
return {
...stripStyle(transitionStyle.style),
zIndex: 1,
...this.props.enter(transitionStyle.data.element.props,
this.props, { gridWidth, gridHeight })
};
},
willLeave(transitionStyle) {
const { gridWidth, gridHeight } = this.state;
const exitStyle = this.props.exit(transitionStyle.data.element.props,
this.props, { gridWidth, gridHeight });
return {
...transitionStyle.style,
zIndex: 0,
...springify(exitStyle, this.props.springConfig)
};
},
render() {
const { component, style, perspective, lengthUnit, angleUnit, ...rest } = this.props;
return (
<TransitionMotion
styles={this.state.styles}
willEnter={this.willEnter}
willLeave={this.willLeave}
>
{interpolatedStyles =>
React.createElement(component, {
style: {
position: 'relative',
...style,
width: `${this.state.gridWidth}${lengthUnit}`,
height: `${this.state.gridHeight}${lengthUnit}`
},
...rest
}, interpolatedStyles.map(config => {
const { style: { opacity, zIndex }, data } = config;
const transform = buildTransform(config.style, perspective, {
length: lengthUnit, angle: angleUnit
});
return React.cloneElement(data.element, {
style: {
...data.element.props.style,
position: 'absolute',
top: 0,
left: 0,
zIndex,
opacity,
transform,
WebkitTransform: transform,
msTransform: transform
}
});
}))}
</TransitionMotion>
);
}
});
function springify(style, springConfig) {
return Object.keys(style).reduce((obj, key) => {
obj[key] = spring(style[key], springConfig);
return obj;
}, {});
}