Skip to content

Instantly share code, notes, and snippets.

@Landerson352
Created September 15, 2019 16:50
Show Gist options
  • Save Landerson352/494af30f21af5b02838523002878bd63 to your computer and use it in GitHub Desktop.
Save Landerson352/494af30f21af5b02838523002878bd63 to your computer and use it in GitHub Desktop.
A React-Native component to simplify screen layout.
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