UNPKG

react-native-simple-accordian

Version:
208 lines (192 loc) 5.66 kB
import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { Animated, Easing, View,ViewPropTypes } from 'react-native'; const ANIMATED_EASING_PREFIXES = ['easeInOut', 'easeOut', 'easeIn']; export default class Collapsible extends Component { static propTypes = { align: PropTypes.oneOf(['top', 'center', 'bottom']), collapsed: PropTypes.bool, collapsedHeight: PropTypes.number, duration: PropTypes.number, easing: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), style: ViewPropTypes.style, children: PropTypes.node, }; static defaultProps = { align: 'top', collapsed: true, collapsedHeight: 0, duration: 300, easing: 'easeOutCubic', }; constructor(props) { super(props); this.state = { measuring: false, measured: false, height: new Animated.Value(props.collapsedHeight), contentHeight: 0, animating: false, }; } componentWillReceiveProps(nextProps) { if (!nextProps.collapsed && !this.props.collapsed) { this.setState({ measured: false }, () => this._componentWillReceiveProps(nextProps) ); } else { this._componentWillReceiveProps(nextProps); } } _componentWillReceiveProps(nextProps) { if (nextProps.collapsed !== this.props.collapsed) { this._toggleCollapsed(nextProps.collapsed); } else if ( nextProps.collapsed && nextProps.collapsedHeight !== this.props.collapsedHeight ) { this.state.height.setValue(nextProps.collapsedHeight); } } contentHandle = null; _handleRef = ref => { this.contentHandle = ref; }; _measureContent(callback) { this.setState( { measuring: true, }, () => { requestAnimationFrame(() => { if (!this.contentHandle) { this.setState( { measuring: false, }, () => callback(this.props.collapsedHeight) ); } else { this.contentHandle.getNode().measure((x, y, width, height) => { this.setState( { measuring: false, measured: true, contentHeight: height, }, () => callback(height) ); }); } }); } ); } _toggleCollapsed(collapsed) { if (collapsed) { this._transitionToHeight(this.props.collapsedHeight); } else if (!this.contentHandle) { if (this.state.measured) { this._transitionToHeight(this.state.contentHeight); } return; } else { this._measureContent(contentHeight => { this._transitionToHeight(contentHeight); }); } } _transitionToHeight(height) { const { duration } = this.props; let easing = this.props.easing; if (typeof easing === 'string') { let prefix; let found = false; for (let i = 0; i < ANIMATED_EASING_PREFIXES.length; i++) { prefix = ANIMATED_EASING_PREFIXES[i]; if (easing.substr(0, prefix.length) === prefix) { easing = easing.substr(prefix.length, 1).toLowerCase() + easing.substr(prefix.length + 1); prefix = prefix.substr(4, 1).toLowerCase() + prefix.substr(5); easing = Easing[prefix](Easing[easing || 'ease']); found = true; break; } } if (!found) { easing = Easing[easing]; } if (!easing) { throw new Error('Invalid easing type "' + this.props.easing + '"'); } } if (this._animation) { this._animation.stop(); } this.setState({ animating: true }); this._animation = Animated.timing(this.state.height, { toValue: height, duration, easing, }).start(() => this.setState({ animating: false })); } _handleLayoutChange = event => { const contentHeight = event.nativeEvent.layout.height; if ( this.state.animating || this.props.collapsed || this.state.measuring || this.state.contentHeight === contentHeight ) { return; } this.state.height.setValue(contentHeight); this.setState({ contentHeight }); }; render() { const { collapsed } = this.props; const { height, contentHeight, measuring, measured } = this.state; const hasKnownHeight = !measuring && (measured || collapsed); const style = hasKnownHeight && { overflow: 'hidden', height: height, }; const contentStyle = {}; if (measuring) { contentStyle.position = 'absolute'; contentStyle.opacity = 0; } else if (this.props.align === 'center') { contentStyle.transform = [ { translateY: height.interpolate({ inputRange: [0, contentHeight], outputRange: [contentHeight / -2, 0], }), }, ]; } else if (this.props.align === 'bottom') { contentStyle.transform = [ { translateY: height.interpolate({ inputRange: [0, contentHeight], outputRange: [-contentHeight, 0], }), }, ]; } return ( <Animated.View style={style} pointerEvents={collapsed ? 'none' : 'auto'}> <Animated.View ref={this._handleRef} style={[ contentStyle]} onLayout={this.state.animating ? undefined : this._handleLayoutChange} > <View style={{ height: measured ? contentHeight : null }}> {this.props.children} </View> </Animated.View> </Animated.View> ); } }