-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ui-components: integration of modal in native
Integration of a simple modal component in the native part, so it can be later reused in the different apps. Fixes #16
- Loading branch information
Showing
8 changed files
with
416 additions
and
2 deletions.
There are no files selected for viewing
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export { default as Alert } from './src/Alert' | ||
export { default as Icon } from './src/Icon' | ||
export { default as Modal } from './src/Modal' |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import React from 'react' | ||
import { Modal as RNModal, View, ScrollView, TouchableOpacity } from 'react-native' | ||
import PropTypes from 'prop-types' | ||
import Icon from '../Icon' | ||
|
||
const Body = ({ children }) => children | ||
|
||
Body.propTypes = { | ||
children: PropTypes.any.isRequired | ||
} | ||
|
||
class Trigger extends React.Component { | ||
render() { | ||
return React.Children.map(this.props.children, child => { | ||
const onPressCall = child.props.onPress | ||
return React.cloneElement(child, { | ||
onPress: () => { | ||
if (typeof onPressCall === 'function') { | ||
if (onPressCall.then !== undefined) { | ||
onPressCall() | ||
.then(result => { | ||
this.props.onShowDialog() | ||
return Promise.resolve(result) | ||
}) | ||
.catch(error => { | ||
return Promise.reject(error) | ||
}) | ||
} else { | ||
onPressCall() | ||
this.props.onShowDialog() | ||
} | ||
} else { | ||
this.props.onShowDialog() | ||
} | ||
} | ||
}) | ||
}) | ||
} | ||
} | ||
Trigger.propTypes = { | ||
children: PropTypes.any.isRequired, | ||
onShowDialog: PropTypes.func | ||
} | ||
|
||
class Modal extends React.Component { | ||
static Body = Body | ||
static Trigger = Trigger | ||
|
||
constructor(props) { | ||
super(props) | ||
this.state = { | ||
visible: false | ||
} | ||
} | ||
|
||
open() { | ||
this.setState({ visible: true }) | ||
} | ||
|
||
close() { | ||
this.setState({ visible: false }) | ||
} | ||
|
||
componentWillReceiveProps(newProps) { | ||
if (newProps.visible !== this.props.visible) { | ||
this.setState({ visible: newProps.visible }) | ||
} | ||
} | ||
|
||
render() { | ||
const triggerButton = React.Children.map(this.props.children, child => { | ||
if (child.type === Trigger) { | ||
return React.cloneElement(child, { | ||
onShowDialog: () => { | ||
this.open() | ||
} | ||
}) | ||
} | ||
}) | ||
|
||
const children = React.Children.map(this.props.children, child => { | ||
if (child.type !== Trigger) { | ||
return React.cloneElement(child) | ||
} | ||
}) | ||
|
||
const { visible } = this.state | ||
return ( | ||
<View> | ||
<RNModal | ||
animationType={'fade'} | ||
transparent={true} | ||
visible={visible} | ||
onRequestClose={this.close} | ||
> | ||
<View style={this.props.containerStyles}> | ||
<View style={this.props.bodyStyles}> | ||
{this.props.hasCloseHeader && ( | ||
<View style={this.props.headerStyles}> | ||
<TouchableOpacity onPress={() => this.close()}> | ||
<Icon | ||
name={'close'} | ||
/> | ||
</TouchableOpacity> | ||
</View> | ||
)} | ||
<ScrollView>{children}</ScrollView> | ||
</View> | ||
</View> | ||
</RNModal> | ||
{triggerButton} | ||
</View> | ||
) | ||
} | ||
} | ||
|
||
Modal.propTypes = { | ||
containerStyles: PropTypes.any, | ||
bodyStyles: PropTypes.any, | ||
children: PropTypes.any, | ||
headerStyles: PropTypes.any, | ||
hasCloseHeader: PropTypes.bool.isRequired, | ||
visible: PropTypes.bool | ||
} | ||
|
||
Modal.defaultProps = { | ||
visible: false, | ||
hasCloseHeader: false, | ||
headerStyles: { | ||
flexDirection: 'row', | ||
alignItems: 'flex-end', | ||
margin: 0, | ||
padding: 0, | ||
borderBottomWidth: 0 | ||
} | ||
} | ||
|
||
export default Modal |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import React from 'react' | ||
import { TouchableOpacity, Text } from 'react-native' | ||
import { shallow } from 'enzyme' | ||
import toJson from 'enzyme-to-json' | ||
import Modal from './Modal' | ||
|
||
describe('(Modal) rendering', () => { | ||
it('should render Modal component', () => { | ||
const wrapper = shallow( | ||
<Modal> | ||
<Modal.Trigger> | ||
<TouchableOpacity onPress={() => {}}> | ||
<Text>trigger</Text> | ||
</TouchableOpacity> | ||
</Modal.Trigger> | ||
<Modal.Body> | ||
<Text>body</Text> | ||
</Modal.Body> | ||
</Modal> | ||
) | ||
expect(toJson(wrapper)).toMatchSnapshot() | ||
}) | ||
|
||
it('should render Modal component with header', () => { | ||
const wrapper = shallow( | ||
<Modal hasCloseHeader={true} > | ||
<Modal.Trigger> | ||
<TouchableOpacity onPress={() => {}}> | ||
<Text>trigger</Text> | ||
</TouchableOpacity> | ||
</Modal.Trigger> | ||
<Modal.Body> | ||
<Text>body</Text> | ||
</Modal.Body> | ||
</Modal> | ||
) | ||
expect(toJson(wrapper)).toMatchSnapshot() | ||
}) | ||
|
||
it('should hide modal when close header is pressed', () => { | ||
const wrapper = shallow( | ||
<Modal hasCloseHeader={true} /> | ||
) | ||
wrapper.setState({ visible: true }) | ||
wrapper | ||
.find(TouchableOpacity) | ||
.first() | ||
.props() | ||
.onPress() | ||
expect(wrapper.state().visible).toBe(false) | ||
}) | ||
|
||
it('should open modal dialog on press trigger', () => { | ||
const wrapper = shallow( | ||
<Modal> | ||
<Modal.Trigger> | ||
<TouchableOpacity onPress={() => {}}> | ||
<Text>test</Text> | ||
</TouchableOpacity> | ||
</Modal.Trigger> | ||
</Modal> | ||
) | ||
wrapper.setState({ visible: false }) | ||
wrapper | ||
.find(Modal.Trigger) | ||
.first() | ||
.props() | ||
.onShowDialog() | ||
expect(wrapper.state().visible).toBe(true) | ||
}) | ||
}) |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import React from 'react' | ||
import Modal from './Modal' | ||
import { View, TouchableOpacity, Text, StyleSheet } from 'react-native' | ||
import { storiesOf } from '@storybook/react-native' | ||
import { action } from '@storybook/addon-actions' | ||
import { linkTo } from '@storybook/addon-links' | ||
|
||
storiesOf('Modal', module) | ||
.add('Example Modal with Trigger', () => ( | ||
<Modal | ||
containerStyles={styles.containerStyles} | ||
bodyStyles={styles.bodyStyles} | ||
hasCloseHeader={true} | ||
> | ||
<Modal.Trigger> | ||
<TouchableOpacity style={styles.trigger} onPress={() => {}}> | ||
<Text style={styles.triggerText} >Open</Text> | ||
</TouchableOpacity> | ||
</Modal.Trigger> | ||
<Modal.Body> | ||
<Text>body</Text> | ||
</Modal.Body> | ||
</Modal> | ||
)) | ||
.add('Example Modal with reference', () => ( | ||
<View> | ||
<Modal | ||
containerStyles={styles.containerStyles} | ||
bodyStyles={styles.bodyStyles} | ||
ref={ref => (this.modal = ref)} | ||
> | ||
<Modal.Body> | ||
<Text>body</Text> | ||
<TouchableOpacity style={styles.trigger} onPress={() => this.modal.close()}> | ||
<Text style={styles.triggerText} >Close</Text> | ||
</TouchableOpacity> | ||
</Modal.Body> | ||
</Modal> | ||
<View style={styles.containerColumn}> | ||
<TouchableOpacity style={styles.trigger} onPress={() => this.modal.show()}> | ||
<Text style={styles.triggerText} >Open</Text> | ||
</TouchableOpacity> | ||
</View> | ||
</View> | ||
)) | ||
|
||
|
||
const styles = StyleSheet.create({ | ||
containerColumn: { | ||
flex: 1, | ||
alignItems: 'center', | ||
marginTop: 'auto', | ||
marginBottom: 'auto' | ||
}, | ||
containerStyles: { | ||
flex: 1, | ||
backgroundColor: 'rgb(236, 240, 241)' | ||
}, | ||
bodyStyles: { | ||
marginTop: 'auto', | ||
marginBottom: 'auto', | ||
marginLeft: 20, | ||
marginRight: 20, | ||
backgroundColor: 'rgb(255, 255, 255)', | ||
borderTopWidth: 5, | ||
borderRadius: 5, | ||
borderColor: 'rgb(34, 167, 240)', | ||
alignItems: 'center' | ||
}, | ||
trigger: { | ||
width: 100, | ||
height: 50, | ||
alignItems: 'center', | ||
justifyContent: 'space-around', | ||
margin: 5, | ||
borderRadius: 5, | ||
backgroundColor: 'rgb(34, 167, 240)' | ||
}, | ||
triggerText: { | ||
color: 'rgb(228, 241, 254)' | ||
} | ||
}) | ||
|
||
export default styles |
Oops, something went wrong.