Last active
May 11, 2019 20:16
-
-
Save KennyHammerlund/00ad73394ccc6f514da5962a70d92997 to your computer and use it in GitHub Desktop.
React Native Modal with a toggleable content display/peeker
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 { Modal, TouchableHighlight, TouchableWithoutFeedback, View, TextStyle, Animated } from 'react-native'; | |
import style from './Modal.style'; | |
import Loading from '../LoadingDots/LoadingDots'; | |
import Ripple from 'react-native-material-ripple'; | |
import Text from '../Text'; | |
import Glyphicon from '../Glyphicon'; | |
import COLORS from '../../styles/constants/colors'; | |
interface ModalProps { | |
onDeny?: () => void; | |
onConfirm?: () => void; | |
denyText?: string | null; | |
confirmText?: string | null; | |
loading?: boolean; | |
peekerComponent?: React.Component; | |
} | |
export const Title: React.FC<{ children: string, style: TextStyle }> = ({ | |
children, | |
style: inputStyle, | |
}) => { | |
return ( | |
<Text numberOfLines={3} style={[style.title, inputStyle]}> | |
{children} | |
</Text> | |
); | |
}; | |
export const styles = style; | |
export const SubTitle: React.FC<{ children: string }> = ({ children }) => { | |
return ( | |
<Text numberOfLines={3} style={style.subTitle}> | |
{children} | |
</Text> | |
); | |
}; | |
export const Option: React.FC<{ | |
selected: boolean, | |
onPress: () => void, | |
noIcon?: boolean, | |
disabled?: boolean, | |
}> = ({ children, selected, noIcon, disabled, ...rest }) => { | |
return ( | |
<Ripple {...rest} style={style.option} disabled={disabled}> | |
{!noIcon && ( | |
<Glyphicon | |
icon="ok" | |
style={{ | |
fontSize: 20, | |
marginRight: 15, | |
color: selected ? COLORS.blue : COLORS.lightGrey, | |
}} | |
/> | |
)} | |
{typeof children === 'string' ? <Text style={style.optionText}>{children}</Text> : children} | |
</Ripple> | |
); | |
}; | |
export class Main extends React.Component<ModalProps> { | |
state = { peeker: false }; | |
togglePeeker = () => { | |
this.setState({ peeker: !this.state.peeker }); | |
}; | |
render() { | |
const { peeker } = this.state; | |
const { peekerComponent } = this.props; | |
const [peekerContentStyle, contentStyle] = (() => { | |
if (!peekerComponent) return [style.noPeeker, style.modal]; | |
return peeker | |
? [style.expandedPeeker, style.shortModal] | |
: [style.shortPeeker, style.expandedModal]; | |
})(); | |
const content = ( | |
<View style={contentStyle} key="content" onTouchEnd={peeker ? this.togglePeeker : () => {}}> | |
<View style={peekerComponent ? style.peekerContentWrapper : style.contentWrapper}> | |
{this.props.children} | |
</View> | |
{!peeker && ( | |
<View style={style.buttonContainer}> | |
<Ripple | |
style={style.denyButton} | |
onPress={!this.props.loading ? this.props.onDeny : undefined} | |
> | |
{this.props.loading ? ( | |
<Loading width="40%" size={12} /> | |
) : ( | |
<Text style={style.denyText}>{this.props.denyText || 'Cancel'}</Text> | |
)} | |
</Ripple> | |
{this.props.onConfirm && ( | |
<Ripple | |
style={style.confirmButton} | |
onPress={!this.props.loading ? this.props.onConfirm : undefined} | |
> | |
{this.props.loading ? ( | |
<Loading width="40%" color="muted" size={12} /> | |
) : ( | |
<Text style={style.confirmText}>{this.props.confirmText || `Yes`}</Text> | |
)} | |
</Ripple> | |
)} | |
</View> | |
)} | |
</View> | |
); | |
const peekerContent = ( | |
<View | |
style={peekerContentStyle} | |
onTouchEnd={!peeker ? this.togglePeeker : () => {}} | |
key="peeker" | |
> | |
<View style={style.contentWrapper}>{peekerComponent}</View> | |
{peeker && ( | |
<View style={style.buttonContainer}> | |
<Ripple style={style.denyButton} onPress={this.togglePeeker}> | |
{this.props.loading ? ( | |
<Loading width="40%" size={12} /> | |
) : ( | |
<Text style={style.denyText}>{'Hide'}</Text> | |
)} | |
</Ripple> | |
</View> | |
)} | |
</View> | |
); | |
return ( | |
<Modal | |
animationType="fade" | |
transparent={true} | |
visible={true} | |
onRequestClose={() => this.props.onDeny && this.props.onDeny()} | |
> | |
<TouchableHighlight onPress={this.props.onDeny} style={style.container}> | |
<TouchableWithoutFeedback> | |
{peekerComponent ? ( | |
<View style={{ flex: 1, justifyContent: 'space-between' }}> | |
{peeker ? [peekerContent, content] : [content, peekerContent]} | |
</View> | |
) : ( | |
content | |
)} | |
</TouchableWithoutFeedback> | |
</TouchableHighlight> | |
</Modal> | |
); | |
} | |
} | |
export default Main; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment