This commit is contained in:
satyajit.happy
2019-05-16 16:49:13 +02:00
parent 0799b43046
commit e4e009fb33
9 changed files with 112 additions and 469 deletions

View File

@@ -1,31 +0,0 @@
/* @flow */
/* eslint-disable import/no-commonjs */
module.exports = {
/**
* Navigators
*/
get createBottomTabNavigator() {
return require('./navigators/createBottomTabNavigator').default;
},
get createMaterialTopTabNavigator() {
return require('./navigators/createMaterialTopTabNavigator').default;
},
/**
* Views
*/
get BottomTabBar() {
return require('./views/BottomTabBar').default;
},
get MaterialTopTabBar() {
return require('./views/MaterialTopTabBar').default;
},
/**
* Utils
*/
get createTabNavigator() {
return require('./utils/createTabNavigator').default;
},
};

View File

@@ -0,0 +1,20 @@
/**
* Navigators
*/
export {
default as createBottomTabNavigator,
} from './navigators/createBottomTabNavigator';
export {
default as createMaterialTopTabNavigator,
} from './navigators/createMaterialTopTabNavigator';
/**
* Views
*/
export { default as BottomTabBar } from './views/BottomTabBar';
export { default as MaterialTopTabBar } from './views/MaterialTopTabBar';
/**
* Utils
*/
export { default as createTabNavigator } from './utils/createTabNavigator';

View File

@@ -1,5 +1,3 @@
/* @flow */
import * as React from 'react';
import { View, StyleSheet } from 'react-native';
@@ -7,31 +5,36 @@ import { View, StyleSheet } from 'react-native';
import { ScreenContainer } from 'react-native-screens';
import createTabNavigator, {
type InjectedProps,
NavigationViewProps,
} from '../utils/createTabNavigator';
import BottomTabBar, { type TabBarOptions } from '../views/BottomTabBar';
import BottomTabBar, { TabBarOptions } from '../views/BottomTabBar';
import ResourceSavingScene from '../views/ResourceSavingScene';
import { NavigationProp, Route, SceneDescriptor } from '../types';
type Props = InjectedProps & {
getAccessibilityRole: (props: { route: any }) => string,
getAccessibilityStates: (props: { route: any }) => string[],
lazy?: boolean,
tabBarComponent?: React.ComponentType<*>,
tabBarOptions?: TabBarOptions,
type Props = NavigationViewProps & {
getAccessibilityRole: (props: { route: any }) => string;
getAccessibilityStates: (props: { route: any }) => string[];
lazy?: boolean;
tabBarComponent?: React.ComponentType<any>;
tabBarOptions?: TabBarOptions;
navigation: NavigationProp;
descriptors: { [key: string]: SceneDescriptor };
screenProps?: unknown;
};
type State = {
loaded: number[],
loaded: number[];
};
class TabNavigationView extends React.PureComponent<Props, State> {
static defaultProps = {
lazy: true,
getAccessibilityRole: () => 'button',
getAccessibilityStates: ({ focused }) => (focused ? ['selected'] : []),
getAccessibilityStates: ({ focused }: { focused: boolean }) =>
focused ? ['selected'] : [],
};
static getDerivedStateFromProps(nextProps, prevState) {
static getDerivedStateFromProps(nextProps: Props, prevState: State) {
const { index } = nextProps.navigation.state;
return {
@@ -46,7 +49,7 @@ class TabNavigationView extends React.PureComponent<Props, State> {
loaded: [this.props.navigation.state.index],
};
_getButtonComponent = ({ route }) => {
_getButtonComponent = ({ route }: { route: Route }) => {
const { descriptors } = this.props;
const descriptor = descriptors[route.key];
const options = descriptor.options;
@@ -55,7 +58,7 @@ class TabNavigationView extends React.PureComponent<Props, State> {
return options.tabBarButtonComponent;
}
return null;
return undefined;
};
_renderTabBar = () => {

View File

@@ -1,138 +0,0 @@
/* @flow */
import * as React from 'react';
import { TabView } from 'react-native-tab-view';
import type { ViewStyleProp } from 'react-native/Libraries/StyleSheet/StyleSheet';
import createTabNavigator, {
type InjectedProps,
} from '../utils/createTabNavigator';
import MaterialTopTabBar, {
type TabBarOptions,
} from '../views/MaterialTopTabBar';
type Route = {
key: string,
routeName: string,
};
type Props = {|
...InjectedProps,
keyboardDismissMode?: 'none' | 'on-drag',
swipeEnabled?: boolean,
swipeDistanceThreshold?: number,
swipeVelocityThreshold?: number,
onSwipeStart?: () => mixed,
onSwipeEnd?: () => mixed,
initialLayout?: { width?: number, height?: number },
lazy?: boolean,
lazyPlaceholderComponent?: React.ComponentType<{ route: Route }>,
tabBarComponent?: React.ComponentType<*>,
tabBarOptions?: TabBarOptions,
tabBarPosition?: 'top' | 'bottom',
sceneContainerStyle?: ViewStyleProp,
style?: ViewStyleProp,
|};
class MaterialTabView extends React.PureComponent<Props> {
_renderLazyPlaceholder = props => {
const { lazyPlaceholderComponent: LazyPlaceholder } = this.props;
if (LazyPlaceholder != null) {
return <LazyPlaceholder {...props} />;
}
return null;
};
_renderTabBar = props => {
const { state } = this.props.navigation;
const route = state.routes[state.index];
const { descriptors } = this.props;
const descriptor = descriptors[route.key];
const options = descriptor.options;
const tabBarVisible =
options.tabBarVisible == null ? true : options.tabBarVisible;
const {
navigation,
getLabelText,
getAccessibilityLabel,
getTestID,
renderIcon,
onTabPress,
onTabLongPress,
tabBarComponent: TabBarComponent = MaterialTopTabBar,
tabBarPosition,
tabBarOptions,
screenProps,
} = this.props;
if (TabBarComponent === null || !tabBarVisible) {
return null;
}
return (
<TabBarComponent
{...tabBarOptions}
{...props}
tabBarPosition={tabBarPosition}
screenProps={screenProps}
navigation={navigation}
getLabelText={getLabelText}
getAccessibilityLabel={getAccessibilityLabel}
getTestID={getTestID}
renderIcon={renderIcon}
onTabPress={onTabPress}
onTabLongPress={onTabLongPress}
/>
);
};
render() {
const {
/* eslint-disable no-unused-vars */
getLabelText,
getAccessibilityLabel,
getTestID,
renderIcon,
onTabPress,
onTabLongPress,
screenProps,
lazyPlaceholderComponent,
tabBarComponent,
tabBarOptions,
/* eslint-enable no-unused-vars */
navigation,
descriptors,
...rest
} = this.props;
const { state } = navigation;
const route = state.routes[state.index];
const descriptor = descriptors[route.key];
const options = descriptor.options;
let swipeEnabled =
options.swipeEnabled == null
? this.props.swipeEnabled
: options.swipeEnabled;
if (typeof swipeEnabled === 'function') {
swipeEnabled = swipeEnabled(state);
}
return (
<TabView
{...rest}
navigationState={navigation.state}
swipeEnabled={swipeEnabled}
renderTabBar={this._renderTabBar}
renderLazyPlaceholder={this._renderLazyPlaceholder}
/>
);
}
}
export default createTabNavigator(MaterialTabView);

View File

@@ -38,6 +38,7 @@ export type NavigationProp<RouteName = string, Params = object> = {
export type NavigationTabOptions = {
title?: string;
tabBarVisible?: boolean;
tabBarLabel?: React.ReactNode;
tabBarIcon?:
| React.ReactNode
@@ -56,6 +57,7 @@ export type NavigationTabOptions = {
defaultHandler: () => void;
}) => void;
tabBarAccessibilityLabel?: string;
tabBarButtonComponent?: React.ComponentType<any>;
};
export type SceneDescriptor = {

View File

@@ -1,5 +1,3 @@
/* @flow */
import React from 'react';
import {
Animated,
@@ -8,11 +6,18 @@ import {
View,
Keyboard,
Platform,
StyleProp,
TextStyle,
ViewStyle,
LayoutChangeEvent,
AccessibilityRole,
AccessibilityState,
} from 'react-native';
import { SafeAreaView } from '@react-navigation/native';
import SafeAreaView from 'react-native-safe-area-view';
import CrossFadeIcon from './CrossFadeIcon';
import withDimensions from '../utils/withDimensions';
import { Route, NavigationProp } from '../types';
type Orientation = 'horizontal' | 'vertical';
type Position = 'beside-icon' | 'below-icon';
@@ -21,50 +26,70 @@ type LabelPosition =
| ((options: { deviceOrientation: Orientation }) => Position);
export type TabBarOptions = {
keyboardHidesTabBar: boolean,
activeTintColor?: string,
inactiveTintColor?: string,
activeBackgroundColor?: string,
inactiveBackgroundColor?: string,
allowFontScaling: boolean,
showLabel: boolean,
showIcon: boolean,
labelStyle: any,
tabStyle: any,
labelPosition?: LabelPosition,
adaptive?: boolean,
style: any,
keyboardHidesTabBar: boolean;
activeTintColor?: string;
inactiveTintColor?: string;
activeBackgroundColor?: string;
inactiveBackgroundColor?: string;
allowFontScaling: boolean;
showLabel: boolean;
showIcon: boolean;
labelStyle: StyleProp<TextStyle>;
tabStyle: StyleProp<ViewStyle>;
labelPosition?: LabelPosition;
adaptive?: boolean;
style: StyleProp<ViewStyle>;
};
type Props = TabBarOptions & {
navigation: any,
onTabPress: any,
onTabLongPress: any,
getAccessibilityLabel: (props: { route: any }) => string,
getAccessibilityRole: (props: { route: any }) => string,
getAccessibilityStates: (props: { route: any }) => string[],
getButtonComponent: ({ route: any }) => any,
getLabelText: ({ route: any }) => any,
getTestID: (props: { route: any }) => string,
renderIcon: any,
dimensions: { width: number, height: number },
isLandscape: boolean,
safeAreaInset: { top: string, right: string, bottom: string, left: string },
navigation: NavigationProp;
onTabPress: (props: { route: Route }) => void;
onTabLongPress: (props: { route: Route }) => void;
getAccessibilityLabel: (props: { route: Route }) => string | undefined;
getAccessibilityRole: (props: {
route: Route;
}) => AccessibilityRole | undefined;
getAccessibilityStates: (props: { route: Route }) => AccessibilityState[];
getButtonComponent: (props: {
route: Route;
}) => React.ComponentType<any> | undefined;
getLabelText: (props: {
route: Route;
}) =>
| ((scene: {
focused: boolean;
tintColor?: string;
orientation: 'horizontal' | 'vertical';
}) => React.ReactNode)
| string
| undefined;
getTestID: (props: { route: Route }) => string;
renderIcon: (props: {
route: Route;
focused: boolean;
tintColor?: string;
horizontal?: boolean;
}) => React.ReactNode;
dimensions: { width: number; height: number };
isLandscape: boolean;
safeAreaInset: React.ComponentProps<typeof SafeAreaView>['forceInset'];
};
type State = {
layout: { height: number, width: number },
keyboard: boolean,
visible: Animated.Value,
layout: { height: number; width: number };
keyboard: boolean;
visible: Animated.Value;
};
const majorVersion = parseInt(Platform.Version, 10);
const majorVersion = parseInt(Platform.Version as string, 10);
const isIos = Platform.OS === 'ios';
const isIOS11 = majorVersion >= 11 && isIos;
const DEFAULT_MAX_TAB_ITEM_WIDTH = 125;
class TouchableWithoutFeedbackWrapper extends React.Component<*> {
class TouchableWithoutFeedbackWrapper extends React.Component<
React.ComponentProps<typeof TouchableWithoutFeedback>
> {
render() {
const {
onPress,
@@ -103,7 +128,9 @@ class TabBarBottom extends React.Component<Props, State> {
showIcon: true,
allowFontScaling: true,
adaptive: isIOS11,
safeAreaInset: { bottom: 'always', top: 'never' },
safeAreaInset: { bottom: 'always', top: 'never' } as React.ComponentProps<
typeof SafeAreaView
>['forceInset'],
};
state = {
@@ -150,7 +177,7 @@ class TabBarBottom extends React.Component<Props, State> {
this.setState({ keyboard: false });
});
_handleLayout = e => {
_handleLayout = (e: LayoutChangeEvent) => {
const { layout } = this.state;
const { height, width } = e.nativeEvent.layout;
@@ -166,7 +193,7 @@ class TabBarBottom extends React.Component<Props, State> {
});
};
_renderLabel = ({ route, focused }) => {
_renderLabel = ({ route, focused }: { route: Route; focused: boolean }) => {
const {
activeTintColor,
inactiveTintColor,
@@ -203,7 +230,6 @@ class TabBarBottom extends React.Component<Props, State> {
if (typeof label === 'function') {
return label({
route,
focused,
tintColor,
orientation: horizontal ? 'horizontal' : 'vertical',
@@ -213,9 +239,8 @@ class TabBarBottom extends React.Component<Props, State> {
return label;
};
_renderIcon = ({ route, focused }) => {
_renderIcon = ({ route, focused }: { route: Route; focused: boolean }) => {
const {
navigation,
activeTintColor,
inactiveTintColor,
renderIcon,
@@ -235,7 +260,6 @@ class TabBarBottom extends React.Component<Props, State> {
<CrossFadeIcon
route={route}
horizontal={horizontal}
navigation={navigation}
activeOpacity={activeOpacity}
inactiveOpacity={inactiveOpacity}
activeTintColor={activeTintColor}
@@ -279,6 +303,7 @@ class TabBarBottom extends React.Component<Props, State> {
return false;
}
// @ts-ignore
if (Platform.isPad) {
let maxTabItemWidth = DEFAULT_MAX_TAB_ITEM_WIDTH;
@@ -315,6 +340,7 @@ class TabBarBottom extends React.Component<Props, State> {
const tabBarStyle = [
styles.tabBar,
// @ts-ignore
this._shouldUseHorizontalLabels() && !Platform.isPad
? styles.tabBarCompact
: styles.tabBarRegular,
@@ -444,6 +470,7 @@ const styles = StyleSheet.create({
flex: 1,
},
iconWithExplicitHeight: {
// @ts-ignore
height: Platform.isPad ? DEFAULT_HEIGHT : COMPACT_HEIGHT,
},
label: {

View File

@@ -1,70 +0,0 @@
/* @flow */
import React from 'react';
import { View, StyleSheet } from 'react-native';
import Animated from 'react-native-reanimated';
type Props = {
route: any,
horizontal?: boolean,
activeOpacity: any,
inactiveOpacity: any,
activeTintColor: any,
inactiveTintColor: any,
renderIcon: any,
style: any,
};
export default class TabBarIcon extends React.Component<Props> {
render() {
const {
route,
activeOpacity,
inactiveOpacity,
activeTintColor,
inactiveTintColor,
renderIcon,
horizontal,
style,
} = this.props;
// We render the icon twice at the same position on top of each other:
// active and inactive one, so we can fade between them.
return (
<View style={style}>
<Animated.View style={[styles.icon, { opacity: activeOpacity }]}>
{renderIcon({
route,
focused: true,
horizontal,
tintColor: activeTintColor,
})}
</Animated.View>
<Animated.View style={[styles.icon, { opacity: inactiveOpacity }]}>
{renderIcon({
route,
focused: false,
horizontal,
tintColor: inactiveTintColor,
})}
</Animated.View>
</View>
);
}
}
const styles = StyleSheet.create({
icon: {
// We render the icon twice at the same position on top of each other:
// active and inactive one, so we can fade between them:
// Cover the whole iconContainer:
position: 'absolute',
alignSelf: 'center',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
width: '100%',
// Workaround for react-native >= 0.54 layout bug
minWidth: 25,
},
});

View File

@@ -1,169 +0,0 @@
/* @flow */
import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { TabBar } from 'react-native-tab-view';
import Animated from 'react-native-reanimated';
import type {
ViewStyleProp,
TextStyleProp,
} from 'react-native/Libraries/StyleSheet/StyleSheet';
type Route = {
key: string,
routeName: string,
};
type Layout = {|
width: number,
height: number,
|};
export type TabBarOptions = {|
activeTintColor?: string,
allowFontScaling?: boolean,
bounces?: boolean,
inactiveTintColor?: string,
pressColor?: string,
pressOpacity?: number,
scrollEnabled?: boolean,
showIcon?: boolean,
showLabel?: boolean,
upperCaseLabel?: boolean,
tabStyle?: ViewStyleProp,
indicatorStyle?: ViewStyleProp,
iconStyle?: any,
labelStyle?: TextStyleProp,
contentContainerStyle?: ViewStyleProp,
style?: ViewStyleProp,
|};
type Props = {|
...TabBarOptions,
layout: Layout,
position: Animated.Node<number>,
jumpTo: (key: string) => void,
getLabelText: (scene: { route: Route }) => ?string,
getAccessible?: (scene: { route: Route }) => ?boolean,
getAccessibilityLabel: (scene: { route: Route }) => ?string,
getTestID: (scene: { route: Route }) => ?string,
renderIcon: (scene: {
route: Route,
focused: boolean,
tintColor: string,
horizontal?: boolean,
}) => React.Node,
renderBadge?: (scene: { route: Route }) => React.Node,
onTabPress?: (scene: { route: Route }) => mixed,
onTabLongPress?: (scene: { route: Route }) => mixed,
tabBarPosition: 'top' | 'bottom',
navigationState: any,
screenProps: any,
navigation: any,
|};
export default class TabBarTop extends React.PureComponent<Props> {
static defaultProps = {
activeTintColor: 'rgba(255, 255, 255, 1)',
inactiveTintColor: 'rgba(255, 255, 255, 0.7)',
showIcon: false,
showLabel: true,
upperCaseLabel: true,
allowFontScaling: true,
};
_renderLabel = ({ route, focused, color }) => {
const {
showLabel,
upperCaseLabel,
labelStyle,
allowFontScaling,
} = this.props;
if (showLabel === false) {
return null;
}
const label = this.props.getLabelText({ route });
if (typeof label === 'string') {
return (
<Animated.Text
style={[styles.label, { color }, labelStyle]}
allowFontScaling={allowFontScaling}
>
{upperCaseLabel ? label.toUpperCase() : label}
</Animated.Text>
);
}
if (typeof label === 'function') {
return label({ focused, tintColor: color });
}
return label;
};
_renderIcon = ({ route, focused, color }) => {
const { renderIcon, showIcon, iconStyle } = this.props;
if (showIcon === false) {
return null;
}
return (
<View style={[styles.icon, iconStyle]}>
{renderIcon({
route,
focused,
tintColor: color,
})}
</View>
);
};
render() {
const {
navigation,
activeTintColor,
inactiveTintColor,
/* eslint-disable no-unused-vars */
renderIcon,
getLabelText,
allowFontScaling,
showLabel,
showIcon,
upperCaseLabel,
tabBarPosition,
navigationState,
screenProps,
iconStyle,
/* eslint-enable no-unused-vars */
...rest
} = this.props;
return (
<TabBar
{...rest}
activeColor={activeTintColor}
inactiveColor={inactiveTintColor}
navigationState={navigation.state}
renderIcon={this._renderIcon}
renderLabel={this._renderLabel}
/>
);
}
}
const styles = StyleSheet.create({
icon: {
height: 24,
width: 24,
},
label: {
textAlign: 'center',
fontSize: 13,
margin: 4,
backgroundColor: 'transparent',
},
});

View File

@@ -1,5 +1,3 @@
/* @flow */
import * as React from 'react';
import { Platform, StyleSheet, View } from 'react-native';
@@ -7,9 +5,9 @@ import { Platform, StyleSheet, View } from 'react-native';
import { Screen, screensEnabled } from 'react-native-screens';
type Props = {
isVisible: boolean,
children: React.Node,
style?: any,
isVisible: boolean;
children: React.ReactNode;
style?: any;
};
const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view out of its container
@@ -18,6 +16,7 @@ export default class ResourceSavingScene extends React.Component<Props> {
render() {
if (screensEnabled && screensEnabled()) {
const { isVisible, ...rest } = this.props;
// @ts-ignore
return <Screen active={isVisible ? 1 : 0} {...rest} />;
}