SafeAreaView padding from style prop (#2889)

* SafeAreaView now adds padding from style object. If height is specified, inset padding is added to height.

* Header now only accepts headerStyle prop, backgroundColor works as expected.

* TabBarBottom now only accepts style prop, backgroundColor works as expected. Fixes top inset bug.

* Update snapshot.

* Add clarifying comment.

* Support padding with percentage.
This commit is contained in:
Dave Pack
2017-11-02 18:14:15 -07:00
parent 261587436e
commit cb4933d19f
10 changed files with 240 additions and 228 deletions

View File

@@ -135,10 +135,6 @@ Style object for the header
Style object for the title component
### `headerBackgroundColor`
Color string that overrides default background color.
#### `headerBackTitleStyle`
Style object for the back title

View File

@@ -106,7 +106,6 @@ Several options get passed to the underlying router to modify navigation logic:
- `inactiveBackgroundColor` - Background color of the inactive tab.
- `showLabel` - Whether to show label for tab, default is true.
- `style` - Style object for the tab bar.
- `backgroundColor` - Color string that overrides default backgroundColor.
- `labelStyle` - Style object for the tab label.
- `tabStyle` - Style object for the tab.
- `allowFontScaling` - Whether label font should scale to respect Text Size accessibility settings, default is true.

View File

@@ -360,7 +360,6 @@ export type NavigationStackScreenOptions = {|
headerPressColorAndroid?: string,
headerRight?: React.Node,
headerStyle?: ViewStyleProp,
headerBackgroundColor?: string,
gesturesEnabled?: boolean,
gestureResponseDistance?: { vertical?: number, horizontal?: number },
|};

View File

@@ -94,15 +94,12 @@ exports[`DrawerNavigator renders successfully 1`] = `
<View
onLayout={[Function]}
style={
Array [
undefined,
Object {
"paddingBottom": 0,
"paddingLeft": 0,
"paddingRight": 0,
"paddingTop": 20,
},
]
Object {
"paddingBottom": 0,
"paddingLeft": 0,
"paddingRight": 0,
"paddingTop": 20,
}
}
>
<View
@@ -142,17 +139,13 @@ exports[`DrawerNavigator renders successfully 1`] = `
<View
onLayout={[Function]}
style={
Array [
Object {
"backgroundColor": "rgba(0, 0, 0, .04)",
},
Object {
"paddingBottom": 0,
"paddingLeft": 0,
"paddingRight": 0,
"paddingTop": 20,
},
]
Object {
"backgroundColor": "rgba(0, 0, 0, .04)",
"paddingBottom": 0,
"paddingLeft": 0,
"paddingRight": 0,
"paddingTop": 20,
}
}
>
<View

View File

@@ -75,77 +75,65 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
/>
</View>
<View
onLayout={[Function]}
style={
Array [
Array [
Object {
"borderBottomColor": "rgba(0, 0, 0, .3)",
"borderBottomWidth": 0.5,
},
Object {
"backgroundColor": "#F7F7F7",
},
],
cardStyle={undefined}
collapsable={undefined}
getScreenDetails={[Function]}
headerMode={undefined}
index={0}
layout={
Object {
"height": 0,
"initHeight": 0,
"initWidth": 0,
"isMeasured": false,
"width": 0,
}
}
mode="float"
navigation={
Object {
"dispatch": [Function],
"goBack": [Function],
"navigate": [Function],
"setParams": [Function],
"state": Object {
"index": 0,
"routes": Array [
Object {
"key": "Init-id-0-1",
"routeName": "Home",
},
],
},
}
}
router={
Object {
"getActionForPathAndParams": [Function],
"getComponentForRouteName": [Function],
"getComponentForState": [Function],
"getPathAndParamsForState": [Function],
"getScreenConfig": [Function],
"getScreenOptions": [Function],
"getStateForAction": [Function],
}
}
transitionConfig={undefined}
>
<View
onLayout={[Function]}
style={
Object {
"backgroundColor": "#F7F7F7",
"borderBottomColor": "rgba(0, 0, 0, .3)",
"borderBottomWidth": 0.5,
"height": 64,
"paddingBottom": 0,
"paddingLeft": 0,
"paddingRight": 0,
"paddingTop": 20,
},
]
}
>
<View
cardStyle={undefined}
collapsable={undefined}
getScreenDetails={[Function]}
headerMode={undefined}
index={0}
layout={
Object {
"height": 0,
"initHeight": 0,
"initWidth": 0,
"isMeasured": false,
"width": 0,
}
}
mode="float"
navigation={
Object {
"dispatch": [Function],
"goBack": [Function],
"navigate": [Function],
"setParams": [Function],
"state": Object {
"index": 0,
"routes": Array [
Object {
"key": "Init-id-0-1",
"routeName": "Home",
},
],
},
}
}
router={
Object {
"getActionForPathAndParams": [Function],
"getComponentForRouteName": [Function],
"getComponentForState": [Function],
"getPathAndParamsForState": [Function],
"getScreenConfig": [Function],
"getScreenOptions": [Function],
"getStateForAction": [Function],
}
}
style={
Object {
"height": 44,
}
}
transitionConfig={undefined}
>
<View
style={
@@ -315,77 +303,65 @@ exports[`StackNavigator renders successfully 1`] = `
/>
</View>
<View
onLayout={[Function]}
style={
Array [
Array [
Object {
"borderBottomColor": "rgba(0, 0, 0, .3)",
"borderBottomWidth": 0.5,
},
Object {
"backgroundColor": "#F7F7F7",
},
],
cardStyle={undefined}
collapsable={undefined}
getScreenDetails={[Function]}
headerMode={undefined}
index={0}
layout={
Object {
"height": 0,
"initHeight": 0,
"initWidth": 0,
"isMeasured": false,
"width": 0,
}
}
mode="float"
navigation={
Object {
"dispatch": [Function],
"goBack": [Function],
"navigate": [Function],
"setParams": [Function],
"state": Object {
"index": 0,
"routes": Array [
Object {
"key": "Init-id-0-0",
"routeName": "Home",
},
],
},
}
}
router={
Object {
"getActionForPathAndParams": [Function],
"getComponentForRouteName": [Function],
"getComponentForState": [Function],
"getPathAndParamsForState": [Function],
"getScreenConfig": [Function],
"getScreenOptions": [Function],
"getStateForAction": [Function],
}
}
transitionConfig={undefined}
>
<View
onLayout={[Function]}
style={
Object {
"backgroundColor": "#F7F7F7",
"borderBottomColor": "rgba(0, 0, 0, .3)",
"borderBottomWidth": 0.5,
"height": 64,
"paddingBottom": 0,
"paddingLeft": 0,
"paddingRight": 0,
"paddingTop": 20,
},
]
}
>
<View
cardStyle={undefined}
collapsable={undefined}
getScreenDetails={[Function]}
headerMode={undefined}
index={0}
layout={
Object {
"height": 0,
"initHeight": 0,
"initWidth": 0,
"isMeasured": false,
"width": 0,
}
}
mode="float"
navigation={
Object {
"dispatch": [Function],
"goBack": [Function],
"navigate": [Function],
"setParams": [Function],
"state": Object {
"index": 0,
"routes": Array [
Object {
"key": "Init-id-0-0",
"routeName": "Home",
},
],
},
}
}
router={
Object {
"getActionForPathAndParams": [Function],
"getComponentForRouteName": [Function],
"getComponentForState": [Function],
"getPathAndParamsForState": [Function],
"getScreenConfig": [Function],
"getScreenOptions": [Function],
"getStateForAction": [Function],
}
}
style={
Object {
"height": 44,
}
}
transitionConfig={undefined}
>
<View
style={

View File

@@ -65,33 +65,21 @@ exports[`TabNavigator renders successfully 1`] = `
</View>
</View>
<View
onLayout={[Function]}
style={
Array [
Array [
Object {
"borderTopColor": "rgba(0, 0, 0, .3)",
"borderTopWidth": 0.5,
},
Object {
"backgroundColor": "#F7F7F7",
},
],
collapsable={undefined}
>
<View
onLayout={[Function]}
style={
Object {
"backgroundColor": "#F7F7F7",
"borderTopColor": "rgba(0, 0, 0, .3)",
"borderTopWidth": 0.5,
"flexDirection": "row",
"height": 49,
"paddingBottom": 0,
"paddingLeft": 0,
"paddingRight": 0,
"paddingTop": 20,
},
]
}
>
<View
collapsable={undefined}
style={
Object {
"flexDirection": "row",
"height": 49,
"paddingTop": 0,
}
}
>

View File

@@ -302,12 +302,10 @@ class Header extends React.PureComponent<Props, State> {
} = this.props;
const { options } = this.props.getScreenDetails(scene);
const {
headerStyle,
headerBackgroundColor = Platform.OS === 'ios' ? '#F7F7F7' : '#FFF',
} = options;
const { headerStyle } = options;
const appBarHeight = Platform.OS === 'ios' ? (isLandscape ? 32 : 44) : 56;
const containerStyles = [
styles.container,
{
height: appBarHeight,
},
@@ -315,14 +313,14 @@ class Header extends React.PureComponent<Props, State> {
];
return (
<SafeAreaView
style={[styles.container, { backgroundColor: headerBackgroundColor }]}
forceInset={{ top: 'always', bottom: 'never' }}
>
<Animated.View {...rest} style={containerStyles}>
<Animated.View {...rest}>
<SafeAreaView
style={containerStyles}
forceInset={{ top: 'always', bottom: 'never' }}
>
<View style={styles.appBar}>{appBar}</View>
</Animated.View>
</SafeAreaView>
</SafeAreaView>
</Animated.View>
);
}
}
@@ -347,6 +345,7 @@ if (Platform.OS === 'ios') {
const styles = StyleSheet.create({
container: {
backgroundColor: Platform.OS === 'ios' ? '#F7F7F7' : '#FFF',
...platformContainerStyles,
},
appBar: {

View File

@@ -6,6 +6,7 @@ import {
NativeModules,
Platform,
SafeAreaView,
StyleSheet,
View,
} from 'react-native';
import withOrientation from './withOrientation';
@@ -62,6 +63,18 @@ const statusBarHeight = isLandscape => {
return isLandscape ? 0 : 20;
};
const doubleFromPercentString = percent => {
if (!percent.includes('%')) {
return 0;
}
const dbl = parseFloat(percent) / 100;
if (isNaN(dbl)) return 0;
return dbl;
};
class SafeView extends Component {
state = {
touchesTop: true,
@@ -69,6 +82,8 @@ class SafeView extends Component {
touchesLeft: true,
touchesRight: true,
orientation: null,
viewWidth: 0,
viewHeight: 0,
};
componentDidMount() {
@@ -88,17 +103,13 @@ class SafeView extends Component {
return <View style={style}>{this.props.children}</View>;
}
if (!forceInset && minor >= 50) {
return <SafeAreaView style={style}>{this.props.children}</SafeAreaView>;
}
const safeAreaStyle = this._getSafeAreaStyle();
return (
<View
ref={c => (this.view = c)}
onLayout={this._onLayout}
style={[style, safeAreaStyle]}
style={safeAreaStyle}
>
{this.props.children}
</View>
@@ -145,6 +156,8 @@ class SafeView extends Component {
touchesLeft,
touchesRight,
orientation: newOrientation,
viewWidth: winWidth,
viewHeight: winHeight,
});
});
};
@@ -153,7 +166,16 @@ class SafeView extends Component {
const { touchesTop, touchesBottom, touchesLeft, touchesRight } = this.state;
const { forceInset, isLandscape } = this.props;
const {
paddingTop,
paddingBottom,
paddingLeft,
paddingRight,
viewStyle,
} = this._getViewStyles();
const style = {
...viewStyle,
paddingTop: touchesTop ? this._getInset('top') : 0,
paddingBottom: touchesBottom ? this._getInset('bottom') : 0,
paddingLeft: touchesLeft ? this._getInset('left') : 0,
@@ -195,9 +217,64 @@ class SafeView extends Component {
});
}
// new height/width should only include padding from insets
// height/width should not be affected by padding from style obj
if (style.height && typeof style.height === 'number') {
style.height += style.paddingTop + style.paddingBottom;
}
if (style.width && typeof style.width === 'number') {
style.width += style.paddingLeft + style.paddingRight;
}
style.paddingTop += paddingTop;
style.paddingBottom += paddingBottom;
style.paddingLeft += paddingLeft;
style.paddingRight += paddingRight;
return style;
};
_getViewStyles = () => {
const { viewWidth } = this.state;
// get padding values from style to add back in after insets are determined
// default precedence: padding[Side] -> vertical | horizontal -> padding -> 0
let {
padding = 0,
paddingVertical = padding,
paddingHorizontal = padding,
paddingTop = paddingVertical,
paddingBottom = paddingVertical,
paddingLeft = paddingHorizontal,
paddingRight = paddingHorizontal,
...viewStyle
} = StyleSheet.flatten(this.props.style || {});
if (typeof paddingTop !== 'number') {
paddingTop = doubleFromPercentString(paddingTop) * viewWidth;
}
if (typeof paddingBottom !== 'number') {
paddingBottom = doubleFromPercentString(paddingBottom) * viewWidth;
}
if (typeof paddingLeft !== 'number') {
paddingLeft = doubleFromPercentString(paddingLeft) * viewWidth;
}
if (typeof paddingRight !== 'number') {
paddingRight = doubleFromPercentString(paddingRight) * viewWidth;
}
return {
paddingTop,
paddingBottom,
paddingLeft,
paddingRight,
viewStyle,
};
};
_getInset = key => {
const { isLandscape } = this.props;
switch (key) {

View File

@@ -40,7 +40,6 @@ type Props = {
getTestIDProps: (scene: TabScene) => (scene: TabScene) => any,
renderIcon: (scene: TabScene) => React.Node,
style?: ViewStyleProp,
backgroundColor?: string,
labelStyle?: TextStyleProp,
tabStyle?: ViewStyleProp,
showIcon?: boolean,
@@ -165,7 +164,6 @@ class TabBarBottom extends React.PureComponent<Props> {
inactiveBackgroundColor,
style,
tabStyle,
backgroundColor = '#F7F7F7', // Default background color in iOS 10
isLandscape,
} = this.props;
const { routes } = navigation.state;
@@ -181,11 +179,11 @@ class TabBarBottom extends React.PureComponent<Props> {
];
return (
<SafeAreaView
style={[styles.tabBarContainer, { backgroundColor }]}
forceInset={{ bottom: 'always' }}
>
<Animated.View style={tabBarStyle}>
<Animated.View>
<SafeAreaView
style={tabBarStyle}
forceInset={{ bottom: 'always', top: 'never' }}
>
{routes.map((route: NavigationRoute, index: number) => {
const focused = index === navigation.state.index;
const scene = { route, index, focused };
@@ -228,8 +226,8 @@ class TabBarBottom extends React.PureComponent<Props> {
</TouchableWithoutFeedback>
);
})}
</Animated.View>
</SafeAreaView>
</SafeAreaView>
</Animated.View>
);
}
}
@@ -237,11 +235,10 @@ class TabBarBottom extends React.PureComponent<Props> {
const LABEL_LEFT_MARGIN = 20;
const LABEL_TOP_MARGIN = 15;
const styles = StyleSheet.create({
tabBarContainer: {
tabBar: {
backgroundColor: '#F7F7F7', // Default background color in iOS 10
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: 'rgba(0, 0, 0, .3)',
},
tabBar: {
flexDirection: 'row',
},
tabBarLandscape: {

View File

@@ -21,33 +21,21 @@ exports[`TabBarBottom renders successfully 1`] = `
}
>
<View
onLayout={[Function]}
style={
Array [
Array [
Object {
"borderTopColor": "rgba(0, 0, 0, .3)",
"borderTopWidth": 0.5,
},
Object {
"backgroundColor": "#F7F7F7",
},
],
collapsable={undefined}
>
<View
onLayout={[Function]}
style={
Object {
"backgroundColor": "#F7F7F7",
"borderTopColor": "rgba(0, 0, 0, .3)",
"borderTopWidth": 0.5,
"flexDirection": "row",
"height": 49,
"paddingBottom": 0,
"paddingLeft": 0,
"paddingRight": 0,
"paddingTop": 20,
},
]
}
>
<View
collapsable={undefined}
style={
Object {
"flexDirection": "row",
"height": 49,
"paddingTop": 0,
}
}
>