Created
December 6, 2016 19:20
-
-
Save ryanflorence/80979a51aef8e356b4d79733bd780a61 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component, PropTypes } from 'react' | |
import { | |
Text, | |
View, | |
TouchableHighlight, | |
ScrollView, | |
StatusBar, | |
Dimensions, | |
Animated | |
} from 'react-native' | |
import Match from 'react-router/Match' | |
import Redirect from 'react-router/Redirect' | |
import Nav from './Nav' | |
const { any, node } = PropTypes | |
class StackContainer extends Component { | |
static contextTypes = { | |
stack: any | |
} | |
static childContextTypes = { | |
stack: any | |
} | |
initialLocation = { | |
...this.props.location, | |
pathname: this.props.pathname | |
} | |
getChildContext() { | |
return { | |
stack: { | |
...this.context.stack, | |
parentLocation: this.initialLocation | |
} | |
} | |
} | |
componentWillMount() { | |
this.pushToStack('down') | |
} | |
componentDidUpdate(prevProps) { | |
const becameActive = ( | |
this.props.isExact === true && | |
prevProps.isExact === false | |
) | |
if (becameActive) { | |
this.pushToStack('up') | |
} | |
} | |
pushToStack(direction) { | |
const { isExact, renderTitle, renderContent, renderChild, ...rest } = this.props | |
if (isExact) { | |
this.context.stack.push({ | |
title: renderTitle(rest), | |
content: renderContent(rest), | |
parentLocation: this.context.stack.parentLocation, | |
direction | |
}) | |
} | |
} | |
render() { | |
const { isExact, renderTitle, renderContent, renderChild, ...rest } = this.props | |
return isExact ? null : renderChild ? renderChild(rest) : null | |
} | |
} | |
class AnimatedStack extends React.Component { | |
static propTypes = { | |
title: any, | |
content: any, | |
backButton: any, | |
parentLocation: any, | |
location: any | |
} | |
state = { previousProps: null } | |
animation = new Animated.Value(0) | |
componentWillReceiveProps(nextProps) { | |
if (nextProps.location !== this.props.location) { | |
this.setState({ | |
previousProps: this.props | |
}, () => { | |
const { animation } = this | |
animation.setValue(0) | |
Animated.timing(animation, { | |
toValue: 1, | |
duration: 300 | |
}).start(({ finished }) => { | |
this.setState({ previousProps: null }) | |
}) | |
}) | |
} | |
} | |
render() { | |
const { width, height } = Dimensions.get('window') | |
const { direction } = this.props | |
const animating = this.state.previousProps | |
const bothProps = [ this.props ] | |
if (animating) | |
bothProps.push(this.state.previousProps) | |
return ( | |
<View pointerEvents={animating ? 'none' : 'auto'} style={{ flex: 1 }}> | |
<View style={{ zIndex: 1, backgroundColor: '#f0f0f0', borderBottomColor: '#ccc', borderBottomWidth: 1, height: 40, alignItems: 'center' }}> | |
{bothProps.map((props, index, arr) => ( | |
<Animated.View | |
key={props.location.pathname} | |
style={{ | |
opacity: this.animation.interpolate({ | |
inputRange: [ 0, 1 ], | |
outputRange: arr.length > 1 && index === 0 ? ( | |
[ 0, 1 ] | |
) : index === 1 ? ( | |
[ 1, 0 ] | |
) : [ 1, 1 ] | |
}), | |
flexDirection: 'row', alignItems: 'center', position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 | |
}} | |
> | |
<View style={{ width: 30 }}> | |
{props.parentLocation ? props.backButton : <Text> </Text>} | |
</View> | |
<View style={{ flex: 1 }}> | |
{props.title} | |
</View> | |
<View style={{ width: 30 }}/> | |
</Animated.View> | |
))} | |
</View> | |
<View style={{ flex: 1, backgroundColor: '#ccc' }}> | |
{bothProps.map((props, index, arr) => ( | |
<Animated.View key={props.location.pathname} style={{ | |
left: this.animation.interpolate({ | |
inputRange: [ 0, 1 ], | |
outputRange: arr.length > 1 ? ( | |
index === 0 && direction === 'down' ? ( | |
[ width + 10, 0 ] | |
) : index === 1 && direction === 'down' ? ( | |
[ 0, -100 ] | |
) : index === 0 && direction === 'up' ? ( | |
[ -100, 0 ] | |
) : index === 1 && direction === 'up' ? ( | |
[ 0, width + 10 ] | |
) : [ 0, 0 ] | |
) : [ 0, 0 ] | |
}), | |
zIndex: arr.length > 1 ? ( | |
index === 0 && direction === 'down' ? ( | |
1 | |
) : index === 1 && direction === 'down' ? ( | |
0 | |
) : index === 0 && direction === 'up' ? ( | |
0 | |
) : index === 1 && direction === 'up' ? ( | |
1 | |
) : 1 | |
) : 1, | |
position: 'absolute', width, height, top: 0, | |
shadowColor: "#000000", | |
shadowOpacity: 0.25, | |
shadowRadius: 10, | |
opacity: this.animation.interpolate({ | |
inputRange: [ 0, 1 ], | |
outputRange: arr.length > 1 ? ( | |
index === 0 && direction === 'down' ? ( | |
[ 1 , 1 ] | |
) : index === 1 && direction === 'down' ? ( | |
[ 1, 0.5 ] | |
) : index === 0 && direction === 'up' ? ( | |
[ 0.5, 1 ] | |
) : index === 1 && direction === 'up' ? ( | |
[ 1, 1 ] | |
) : [ 1, 1 ] | |
) : [ 1, 1 ] | |
}) | |
}}> | |
{props.content} | |
</Animated.View> | |
))} | |
</View> | |
</View> | |
) | |
} | |
} | |
const rootStoredLocations = {} | |
class StackRootContainer extends Component { | |
static childContextTypes = { | |
stack: any, | |
} | |
static propTypes = { | |
children: PropTypes.node, | |
location: PropTypes.object | |
} | |
state = { | |
title: null, | |
content: null, | |
backLocation: null, | |
backButton: null, | |
direction: null | |
} | |
getChildContext() { | |
return { | |
stack: { | |
push: ({ direction, title, content, parentLocation }) => { | |
this.setState({ | |
direction, | |
title, | |
content, | |
parentLocation, | |
backButton: ( | |
<Nav replace={true} to={parentLocation}> | |
<Text style={{ padding: 10 }}><</Text> | |
</Nav> | |
) | |
}) | |
} | |
} | |
} | |
} | |
componentWillUnmount() { | |
rootStoredLocations[this.props.pattern] = this.props.location | |
} | |
render() { | |
const { title, content, backButton, parentLocation, direction } = this.state | |
const { children, location } = this.props | |
const { width, height } = Dimensions.get('window') | |
return ( | |
<View style={{ flex: 1 }}> | |
<AnimatedStack | |
title={title} | |
content={content} | |
backButton={backButton} | |
parentLocation={parentLocation} | |
direction={direction} | |
location={location} | |
/> | |
{children} | |
</View> | |
) | |
} | |
} | |
class RedirectStack extends Component { | |
componentWillMount() { | |
delete rootStoredLocations[this.props.pattern] | |
} | |
render() { | |
return <Redirect to={this.props.to}/> | |
} | |
} | |
class StackMatch extends Component { | |
static propTypes = { | |
pattern: any, | |
isRoot: any, | |
renderTitle: any, | |
renderContent: any, | |
renderChild: any | |
} | |
render() { | |
const { isRoot, pattern, ...rest } = this.props | |
return ( | |
<Match pattern={pattern} render={(props) => ( | |
isRoot ? ( | |
rootStoredLocations[pattern] ? ( | |
<RedirectStack pattern={pattern} to={rootStoredLocations[pattern]}/> | |
) : ( | |
<StackRootContainer pattern={pattern} location={props.location}> | |
<StackContainer {...rest} {...props}/> | |
</StackRootContainer> | |
) | |
) : ( | |
<StackContainer {...rest} {...props}/> | |
) | |
)}/> | |
) | |
} | |
} | |
export default StackMatch |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment