Created
August 26, 2018 07:25
-
-
Save mgenov/1c306fafa80e61995f7d9b2dce6b16cf to your computer and use it in GitHub Desktop.
StackMatch.js
This file contains hidden or 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
<MatchTabs> | |
<MatchTab | |
pathname="/home" | |
renderContent={props => { | |
return <RecursiveItem rootPath="/home" /> | |
}} | |
renderTab={({ isActive }) => ( | |
<Text style={{ color: isActive ? blue : null }}>Home</Text> | |
)} | |
/> | |
))} | |
<MatchTab | |
pathname="/notifications" | |
renderContent={props => ( | |
<View> | |
<Text style={{ fontSize: 30 }}>Notifications</Text> | |
</View> | |
)} | |
renderTab={({ isActive }) => ( | |
<Text style={{ color: isActive ? blue : null }}> | |
Notifications | |
</Text> | |
)} | |
/> | |
<MatchTab | |
pathname="/messages" | |
renderContent={props => <RecursiveItem rootPath="/messages" />} | |
renderTab={({ isActive }) => ( | |
<Text style={{ color: isActive ? blue : null }}>Messages</Text> | |
)} | |
/> | |
</MatchTabs> |
This file contains hidden or 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
class MatchTabs extends React.Component { | |
render() { | |
const { location } = this.props | |
return ( | |
<View style={{ flex: 1 }}> | |
<View style={{ flex: 1 }}>{this.props.children}</View> | |
<View | |
style={{ | |
flexDirection: 'row', | |
alignItems: 'center', | |
borderTopWidth: 1, | |
borderTopColor: '#ddd' | |
}} | |
> | |
{React.Children.map(this.props.children, child => ( | |
<Link | |
to={child.props.pathname} | |
component={TouchableOpacity} | |
style={{ flex: 1, padding: 20 }} | |
> | |
{child.props.renderTab({ | |
isActive: location.pathname === child.props.pathname | |
})} | |
</Link> | |
))} | |
</View> | |
</View> | |
) | |
} | |
} | |
class MatchTab extends React.Component { | |
render() { | |
const { renderContent, pathname } = this.props | |
return ( | |
<Route | |
path={pathname} | |
render={props => renderContent({ ...this.props, ...props })} | |
/> | |
) | |
} | |
} | |
const stuff = [ | |
{ path: 'one', label: 'One' }, | |
{ path: 'two', label: 'Two' }, | |
{ path: 'three', label: 'Three' }, | |
{ path: 'four', label: 'Four' } | |
] | |
const blue = 'hsl(200, 50%, 50%)' | |
class RecursiveItem extends Component { | |
render() { | |
const { pathname, rootPath, match } = this.props | |
const pattern = rootPath ? rootPath : `${match.path}/:id` | |
return ( | |
<StackMatch | |
isRoot={!!rootPath} | |
pattern={pattern} | |
renderTitle={({ match }) => ( | |
<Text | |
style={{ textAlign: 'center' }} | |
ellipsizeMode="middle" | |
numberOfLines={1} | |
> | |
{match.url} | |
</Text> | |
)} | |
renderContent={({ location }) => ( | |
<ScrollView style={{ flex: 1, backgroundColor: 'white' }}> | |
{stuff.map(thing => ( | |
<View | |
key={thing.path} | |
style={{ borderBottomWidth: 1, borderColor: '#ddd' }} | |
> | |
<Link | |
component={TouchableOpacity} | |
to={`${location.pathname}/${thing.path}`} | |
underlayColor="#f0f0f0" | |
> | |
<Text style={{ padding: 15 }}>{thing.label}</Text> | |
</Link> | |
</View> | |
))} | |
</ScrollView> | |
)} | |
renderChild={props => <RecursiveItem {...props} />} | |
/> | |
) | |
} | |
} | |
This file contains hidden or 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 } from 'react' | |
import PropTypes from 'prop-types' | |
import { | |
View, | |
Text, | |
Animated, | |
Dimensions, | |
TouchableOpacity | |
} from 'react-native' | |
import { Route, Redirect } from 'react-router' | |
import { Link } from 'react-router-native' | |
const rootStoredLocations = {} | |
class Stack extends Component { | |
state = { previousProps: null, currentProps: null } | |
animation = new Animated.Value(0) | |
static getDerivedStateFromProps = (props, state) => { | |
if (!state.currentProps) { | |
return { currentProps: props } | |
} | |
const isLocationChanged = props.location !== state.currentProps.location | |
if (isLocationChanged) { | |
return { | |
previousProps: state.currentProps, | |
currentProps: props | |
} | |
} | |
return null | |
} | |
componentDidUpdate(prevProps, prevState) { | |
const previousProps = prevState.previousProps | |
if (previousProps) { | |
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> | |
) | |
} | |
} | |
Stack.propTypes = { | |
title: PropTypes.any, | |
content: PropTypes.any, | |
backButton: PropTypes.any, | |
parentLocation: PropTypes.any, | |
location: PropTypes.any | |
} | |
const StackContext = React.createContext('stackContext') | |
class StackRootContainer extends Component { | |
state = { | |
title: null, | |
content: null, | |
parentLocation: null, | |
backButton: null, | |
direction: null | |
} | |
getChildContext() { | |
return { | |
stack: { | |
push: ({ direction, title, content, parentLocation }) => { | |
this.setState({ | |
direction, | |
title, | |
content, | |
parentLocation, | |
backButton: ( | |
<Link | |
replace={true} | |
component={TouchableOpacity} | |
to={parentLocation} | |
> | |
<Text style={{ padding: 10 }}><</Text> | |
</Link> | |
) | |
}) | |
} | |
} | |
} | |
} | |
componentWillUnmount() { | |
rootStoredLocations[this.props.pattern] = this.props.location | |
} | |
render() { | |
const { title, content, backButton, parentLocation, direction } = this.state | |
const { children, location } = this.props | |
return ( | |
<View style={{ flex: 1 }}> | |
<Stack | |
title={title} | |
content={content} | |
backButton={backButton} | |
parentLocation={parentLocation} | |
direction={direction} | |
location={location} | |
/> | |
{children} | |
</View> | |
) | |
} | |
} | |
StackRootContainer.childContextTypes = { | |
stack: PropTypes.any | |
} | |
StackRootContainer.propTypes = { | |
children: PropTypes.node, | |
location: PropTypes.object | |
} | |
class StackContainer extends Component { | |
getChildContext() { | |
return { | |
stack: { | |
...this.context.stack, | |
parentLocation: this.initialLocation | |
} | |
} | |
} | |
componentDidMount() { | |
this.initialLocation = { | |
...this.props.location, | |
pathname: this.props.location.pathname | |
} | |
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 | |
} | |
} | |
StackContainer.contextTypes = { | |
stack: PropTypes.any | |
} | |
StackContainer.childContextTypes = { | |
stack: PropTypes.any | |
} | |
class RedirectStack extends Component { | |
componentDidMount() { | |
delete rootStoredLocations[this.props.pattern] | |
} | |
render() { | |
return <Redirect to={this.props.to} /> | |
} | |
} | |
class StackMatch extends Component { | |
render() { | |
const { isRoot, pattern, ...rest } = this.props | |
return ( | |
<Route | |
path={pattern} | |
render={props => | |
isRoot ? ( | |
rootStoredLocations[pattern] ? ( | |
<RedirectStack | |
pattern={pattern} | |
to={rootStoredLocations[pattern]} | |
/> | |
) : ( | |
<StackRootContainer pattern={pattern} location={props.location}> | |
<StackContainer | |
{...rest} | |
{...props} | |
isExact={props.match.isExact} | |
/> | |
</StackRootContainer> | |
) | |
) : ( | |
<StackContainer | |
{...rest} | |
{...props} | |
isExact={props.match.isExact} | |
/> | |
) | |
} | |
/> | |
) | |
} | |
} | |
StackMatch.propTypes = { | |
pattern: PropTypes.string.isRequired, | |
renderTitle: PropTypes.any, | |
renderContent: PropTypes.any, | |
renderChild: PropTypes.any | |
} | |
export class StackScene extends Component { | |
render() { | |
return this.props.children | |
} | |
} | |
export default StackMatch |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment