Created
September 15, 2019 16:50
-
-
Save Landerson352/494af30f21af5b02838523002878bd63 to your computer and use it in GitHub Desktop.
A React-Native component to simplify screen layout.
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, { useContext, useRef } from 'react'; | |
import { KeyboardAvoidingView, StatusBar, StyleSheet, View } from 'react-native'; | |
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; | |
import { SafeAreaView } from 'react-navigation'; | |
import withMeasurements from '../utilities/withMeasurements'; | |
import { utils } from '../theme'; | |
import ActivityIndicator from './ActivityIndicator'; | |
export const ScreenViewContext = React.createContext({}); | |
// ScreenView: A component for wrapping all screens | |
// and providing flexible options for common use cases around | |
// status-bar/notch avoidance, keyboard avoidance, scrolling, | |
// layout, and styling. | |
// | |
// Props: | |
// | |
// - `safeArea`: If true, the screen will be wrapped with a `SafeArea`, adding space for the status bar and notch. | |
// If it is an object, the properties will be assigned to the `SafeArea`. | |
// Default: `false`. | |
// - `statusBar`: If a string, will set the `StatusBar` shade. Allowed values are "dark" and "light". | |
// If an object, properties will be assigned to the `StatusBar`. | |
// Default: `dark`. | |
// - `scrollView`. If an object, properties will be assigned to the `KeyboardAwareScrollView`. | |
// If false, will disabled the `KeyboardAwareScrollView` and use a `KeyboardAvoidingView` instead. | |
// Default: `true`. | |
// - `noScroll`: If true, will disabled the `KeyboardAwareScrollView` and use a `KeyboardAvoidingView` instead. | |
// Default: `false`. | |
// - `contentContainerStyle`: Style object that will be assigned | |
// to either the `KeyboardAwareScrollView` or `KeyboardAvoidingView`. | |
// Default: `undefined`. | |
// - `center`: If true, will center the children using flex properties. | |
// (Subject to style overrides from `contentContainerStyle`.) | |
// Default: `false`. | |
// - `fill`: If true, will make the content layout fill the screen vertically. | |
// (Subject to style overrides from `contentContainerStyle`.) | |
// Default: `true`. | |
// - `padded`: If true, apply standard content padding on all sides. | |
// (Subject to style overrides from `contentContainerStyle`.) | |
// Default: `false`. | |
// | |
// Example: <ScreenView safeArea={true} statusBar="dark" noScroll center padded>...</ScreenView> | |
const styles = StyleSheet.create({ | |
centerContent: { | |
alignItems: 'center', | |
justifyContent: 'center', | |
}, | |
flexFill: { | |
flexGrow: 1, | |
flexShrink: 0, | |
}, | |
}); | |
const DEFAULT_BAR_CONTENT = 'dark'; | |
const DEFAULT_FORCE_INSET = { top: 'always' }; // This seems to help Android, especially with notches | |
const DEFAULT_RESET_SCROLL_TO_COORDS = { x: 0, y: 0 }; | |
const nullIfNotObject = (o) => (typeof o === 'object' ? o : null); | |
const ScreenView = ({ | |
style, | |
safeArea = false, // assume nav header will provide one | |
statusBar, | |
scrollView = true, // assume we want KeyboardAvoidingScrollView | |
noScroll, | |
center, | |
fill = true, // TODO: remove default after we get a global spinner solution | |
padded, | |
children, | |
elementToMeasure, | |
py, | |
header, | |
footer, | |
activityIndicator | |
}) => { | |
const keyboardAwareScrollViewRef = useRef(); | |
const scrollToTop = () => setTimeout(() => { | |
if (keyboardAwareScrollViewRef.current) { | |
keyboardAwareScrollViewRef.current.scrollToPosition(0, 0); | |
} | |
}, 1); | |
const statusBarElement = ( | |
<StatusBar | |
barStyle={`${typeof statusBar === 'string' ? statusBar : DEFAULT_BAR_CONTENT}-content`} | |
{...nullIfNotObject(statusBar)} | |
/> | |
); | |
const allContentContainerStyles = [ | |
fill ? styles.flexFill : null, | |
center ? styles.centerContent : null, | |
padded ? utils.pscreen : null, | |
]; | |
const contentWrapperElement = (scrollView && !noScroll) ? ( | |
<KeyboardAwareScrollView | |
ref={keyboardAwareScrollViewRef} | |
alwaysBounceVertical={false} | |
contentContainerStyle={styles.flexFill} | |
enableOnAndroid | |
keyboardOpeningTime={0} | |
overScrollMode="never" | |
resetScrollToCoords={DEFAULT_RESET_SCROLL_TO_COORDS} | |
{...nullIfNotObject(scrollView)} | |
> | |
<View | |
style={allContentContainerStyles} | |
> | |
{children} | |
</View> | |
</KeyboardAwareScrollView> | |
) : ( | |
<KeyboardAvoidingView | |
behavior="padding" | |
keyboardVerticalOffset={py} | |
style={[...allContentContainerStyles, styles.flexFill]} | |
> | |
{children} | |
</KeyboardAvoidingView> | |
); | |
const activityIndicatorElement = activityIndicator ? ( | |
<ActivityIndicator overlay title={activityIndicator} /> | |
) : null; | |
const allStyles = [ | |
utils.flex1, | |
style, | |
]; | |
return ( | |
<ScreenViewContext.Provider value={{ scrollToTop }}> | |
{safeArea ? ( | |
<SafeAreaView | |
forceInset={DEFAULT_FORCE_INSET} | |
style={allStyles} | |
{...nullIfNotObject(safeArea)} | |
> | |
{statusBarElement} | |
{header} | |
{contentWrapperElement} | |
{footer} | |
{activityIndicatorElement} | |
</SafeAreaView> | |
) : ( | |
<View | |
style={allStyles} | |
{...elementToMeasure} | |
> | |
{statusBarElement} | |
{header} | |
{contentWrapperElement} | |
{footer} | |
{activityIndicatorElement} | |
</View> | |
)} | |
</ScreenViewContext.Provider> | |
); | |
}; | |
export const useScreenView = () => { | |
return useContext(ScreenViewContext); | |
}; | |
export default withMeasurements(ScreenView); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment