Last active
April 6, 2018 12:16
-
-
Save udfalkso/235a0f6876fad6fe09b6b2bc28cac70c to your computer and use it in GitHub Desktop.
"Portal" style modal that transfers content elsewhere without native code
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 ModalManager from "app/lib/ModalManager" | |
import styles from "./styles" | |
import React from "react" | |
import ReactNative from "react-native" | |
import { connect } from "react-redux" | |
const { StatusBar, View, StyleSheet, Platform } = ReactNative | |
class AppModal extends React.Component { | |
render() { | |
var user = dataStore.get("user", this.props.app.currentUserId) | |
return ( | |
<SafeAreaView | |
style={styles.modal} | |
{...this.props.contentRenderProps} | |
pointerEvents="box-none" | |
> | |
<View style={{ flex: 1 }}> | |
{this.props.contentRenderFn.bind(this.props.contentRenderProps)()} | |
</View> | |
</SafeAreaView> | |
) | |
} | |
} | |
AppModal = connect(mapStateToProps)(AppModal) | |
class App extends React.Component { | |
constructor(props) { | |
super(props) | |
this.state = { | |
modalShowing: false, | |
} | |
} | |
componentDidMount() { | |
ModalManager.rootModalOpener = this.openModal | |
ModalManager.rootModalRefresher = this.refreshModal | |
ModalManager.rootModalCloser = this.closeModal | |
} | |
refreshModal = (renderFn, childProps) => { | |
this.setState({ | |
modalShowing: true, | |
modalRenderFn: renderFn, | |
modalRenderProps: childProps, | |
}) | |
} | |
openModal = (renderFn, childProps) => { | |
this.setState({ | |
modalShowing: true, | |
modalRenderFn: renderFn, | |
modalRenderProps: childProps, | |
}) | |
setTimeout(() => { | |
//Have to wait for re-render before ref will be available | |
var cont = this.refs["app-modal-container"] | |
if ( | |
cont && | |
cont.refs | |
) { | |
ModalManager.refs = cont.refs | |
} | |
}) | |
} | |
closeModal = renderFn => { | |
this.setState({ | |
modalShowing: false, | |
modalRenderFn: null, | |
}) | |
ModalManager.refs = null | |
} | |
closeModal = renderFn => { | |
this.setState({ | |
modalShowing: false, | |
modalRenderFn: null, | |
}) | |
ModalManager.refs = null | |
} | |
render() { | |
return ( | |
<View style={{ flex: 1 }}> | |
<AppContent /> | |
{this.state.modalShowing && ( | |
<AppModal | |
ref="app-modal-container" | |
contentRenderFn={this.state.modalRenderFn} | |
contentRenderProps={this.state.modalRenderProps} | |
/> | |
)} | |
</View> | |
) | |
} | |
} | |
function mapStateToProps(state) { | |
return Object.assign( | |
{}, | |
{ | |
app: state.app, | |
} | |
) | |
} | |
export default connect(mapStateToProps)(App) |
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
class ModalManager {} | |
ModalManager.open = (renderFn, childProps) => { | |
if (ModalManager.rootModalOpener) { | |
ModalManager.rootModalOpener(renderFn, childProps) | |
} | |
} | |
ModalManager.refresh = (renderFn, childProps) => { | |
if (ModalManager.rootModalRefresher) { | |
ModalManager.rootModalRefresher(renderFn, childProps) | |
} | |
} | |
ModalManager.close = () => { | |
if (ModalManager.rootModalCloser) { | |
ModalManager.rootModalCloser() | |
} | |
} | |
export default ModalManager |
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 from "react" | |
import ReactNative from "react-native" | |
import { connect } from "react-redux" | |
import ModalManager from "app/lib/ModalManager" | |
import PropTypes from "prop-types" | |
const { View, StyleSheet, TouchableWithoutFeedback, StatusBar } = ReactNative | |
import * as Animatable from "react-native-animatable" | |
class MyModal extends React.Component { | |
static defaultProps = { | |
animation: null, | |
closeAnimation: null, | |
loading: false, | |
duration: undefined, | |
} | |
componentDidMount() { | |
if (this.props.visible) { | |
ModalManager.open(this.renderModalContent, this.props) | |
} | |
} | |
onBack = () => { | |
this.props.onBack && this.props.onBack() | |
return true | |
} | |
componentWillUnmount() { | |
ModalManager.close() | |
} | |
componentWillReceiveProps(nextProps) { | |
if (nextProps.visible && !this.props.visible) { | |
ModalManager.open(this.renderModalContent, this.props) | |
} else if (!nextProps.visible && this.props.visible) { | |
if (this.props.closeAnimation && this._modalContents) { | |
this._modalContents[this.props.closeAnimation](this.props.duration).then( | |
() => { | |
ModalManager.close() | |
}, | |
err => { | |
console.warn("Warning: unexpected ModalManager rejection in Myodal:", err) | |
ModalManager.close() | |
} | |
) | |
} else { | |
ModalManager.close() | |
ModalManager.close(this.props) | |
} | |
} else if (nextProps.visible) { | |
ModalManager.refresh(this.renderModalContent, this.props) | |
} | |
} | |
dismiss = () => { | |
this.props.dismiss && this.props.dismiss() | |
} | |
renderModalContent = () => { | |
if (this.props.loading) { | |
return <LoadingOverlay isLoading={true} /> | |
} | |
var Component = !!this.props.dismiss ? TouchableWithoutFeedback : View | |
return ( | |
<Component style={styles.modal} dismiss={this.dismiss}> | |
<Animatable.View | |
ref={c => { | |
this._modalContents = c | |
}} | |
animation={this.props.animation} | |
duration={this.props.duration} | |
style={[styles.grow, this.props.contentContainerStyle]} | |
> | |
{this.props.children} | |
</Animatable.View> | |
</Component> | |
) | |
} | |
render = () => { | |
return <View /> | |
} | |
} | |
export default connect(state => ({ | |
}))(MyModal) | |
var styles = StyleSheet.create({ | |
modal: { | |
position: "absolute", | |
top: 0, | |
right: 0, | |
bottom: 0, | |
left: 0, | |
}, | |
}) |
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 from "react" | |
import ReactNative from "react-native" | |
import MyModal from "app/components/MyModal" | |
const { StatusBar, View, StyleSheet, Platform, Text, TouchableOpacity } = ReactNative | |
export class SomeView extends React.Component { | |
constructor(props) { | |
super(props) | |
this.state = { | |
modalVisible: false, | |
} | |
} | |
toggleModal = () => { | |
this.setState({modalVisible: !this.state.modalVisible}) | |
} | |
renderMyModal() { | |
return ( | |
<MyModal | |
visible={ | |
this.state.modalVisible | |
} | |
animation="slideInUp" | |
duration={200} | |
closeAnimation="bounceOutDown" | |
> | |
<Text>This will show up inside MyModal as the contents</Text> | |
</MyModal> | |
) | |
} | |
render() { | |
return ( | |
<View style={{flex: 1}}> | |
<Text>I'm some random view that has a modal in it</Text> | |
<TouchableOpacity onPress={this.toggleModal}> | |
<Text>Tap me to show the modal</Text> | |
</TouchableOpacity> | |
{this.renderMyModal()} | |
</View> | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment