Created
August 26, 2018 07:25
-
-
Save mgenov/1c306fafa80e61995f7d9b2dce6b16cf to your computer and use it in GitHub Desktop.
Revisions
-
mgenov created this gist
Aug 26, 2018 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,32 @@ <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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,96 @@ 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,367 @@ 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