diff --git a/packages/bottom-tabs/src/index.js b/packages/bottom-tabs/src/index.js deleted file mode 100644 index dd4a92c8..00000000 --- a/packages/bottom-tabs/src/index.js +++ /dev/null @@ -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; - }, -}; diff --git a/packages/bottom-tabs/src/index.tsx b/packages/bottom-tabs/src/index.tsx new file mode 100644 index 00000000..027fb6da --- /dev/null +++ b/packages/bottom-tabs/src/index.tsx @@ -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'; diff --git a/packages/bottom-tabs/src/navigators/createBottomTabNavigator.js b/packages/bottom-tabs/src/navigators/createBottomTabNavigator.tsx similarity index 81% rename from packages/bottom-tabs/src/navigators/createBottomTabNavigator.js rename to packages/bottom-tabs/src/navigators/createBottomTabNavigator.tsx index adf180fa..0c55c622 100644 --- a/packages/bottom-tabs/src/navigators/createBottomTabNavigator.js +++ b/packages/bottom-tabs/src/navigators/createBottomTabNavigator.tsx @@ -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; + tabBarOptions?: TabBarOptions; + navigation: NavigationProp; + descriptors: { [key: string]: SceneDescriptor }; + screenProps?: unknown; }; type State = { - loaded: number[], + loaded: number[]; }; class TabNavigationView extends React.PureComponent { 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 { 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 { return options.tabBarButtonComponent; } - return null; + return undefined; }; _renderTabBar = () => { diff --git a/packages/bottom-tabs/src/navigators/createMaterialTopTabNavigator.js b/packages/bottom-tabs/src/navigators/createMaterialTopTabNavigator.js deleted file mode 100644 index 076b98c7..00000000 --- a/packages/bottom-tabs/src/navigators/createMaterialTopTabNavigator.js +++ /dev/null @@ -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 { - _renderLazyPlaceholder = props => { - const { lazyPlaceholderComponent: LazyPlaceholder } = this.props; - - if (LazyPlaceholder != null) { - return ; - } - - 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 ( - - ); - }; - - 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 ( - - ); - } -} - -export default createTabNavigator(MaterialTabView); diff --git a/packages/bottom-tabs/src/types.tsx b/packages/bottom-tabs/src/types.tsx index 1f1aa985..95198f3e 100644 --- a/packages/bottom-tabs/src/types.tsx +++ b/packages/bottom-tabs/src/types.tsx @@ -38,6 +38,7 @@ export type NavigationProp = { 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; }; export type SceneDescriptor = { diff --git a/packages/bottom-tabs/src/views/BottomTabBar.js b/packages/bottom-tabs/src/views/BottomTabBar.tsx similarity index 82% rename from packages/bottom-tabs/src/views/BottomTabBar.js rename to packages/bottom-tabs/src/views/BottomTabBar.tsx index e4935842..08c0849d 100644 --- a/packages/bottom-tabs/src/views/BottomTabBar.js +++ b/packages/bottom-tabs/src/views/BottomTabBar.tsx @@ -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; + tabStyle: StyleProp; + labelPosition?: LabelPosition; + adaptive?: boolean; + style: StyleProp; }; 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 | 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['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 +> { render() { const { onPress, @@ -103,7 +128,9 @@ class TabBarBottom extends React.Component { 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 { 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 { }); }; - _renderLabel = ({ route, focused }) => { + _renderLabel = ({ route, focused }: { route: Route; focused: boolean }) => { const { activeTintColor, inactiveTintColor, @@ -203,7 +230,6 @@ class TabBarBottom extends React.Component { if (typeof label === 'function') { return label({ - route, focused, tintColor, orientation: horizontal ? 'horizontal' : 'vertical', @@ -213,9 +239,8 @@ class TabBarBottom extends React.Component { 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 { { return false; } + // @ts-ignore if (Platform.isPad) { let maxTabItemWidth = DEFAULT_MAX_TAB_ITEM_WIDTH; @@ -315,6 +340,7 @@ class TabBarBottom extends React.Component { 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: { diff --git a/packages/bottom-tabs/src/views/CrossFadeIcon.js b/packages/bottom-tabs/src/views/CrossFadeIcon.js deleted file mode 100644 index a881581f..00000000 --- a/packages/bottom-tabs/src/views/CrossFadeIcon.js +++ /dev/null @@ -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 { - 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 ( - - - {renderIcon({ - route, - focused: true, - horizontal, - tintColor: activeTintColor, - })} - - - {renderIcon({ - route, - focused: false, - horizontal, - tintColor: inactiveTintColor, - })} - - - ); - } -} - -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, - }, -}); diff --git a/packages/bottom-tabs/src/views/MaterialTopTabBar.js b/packages/bottom-tabs/src/views/MaterialTopTabBar.js deleted file mode 100644 index 87b98e94..00000000 --- a/packages/bottom-tabs/src/views/MaterialTopTabBar.js +++ /dev/null @@ -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, - 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 { - 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 ( - - {upperCaseLabel ? label.toUpperCase() : label} - - ); - } - - 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 ( - - {renderIcon({ - route, - focused, - tintColor: color, - })} - - ); - }; - - 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 ( - - ); - } -} - -const styles = StyleSheet.create({ - icon: { - height: 24, - width: 24, - }, - label: { - textAlign: 'center', - fontSize: 13, - margin: 4, - backgroundColor: 'transparent', - }, -}); diff --git a/packages/bottom-tabs/src/views/ResourceSavingScene.js b/packages/bottom-tabs/src/views/ResourceSavingScene.tsx similarity index 94% rename from packages/bottom-tabs/src/views/ResourceSavingScene.js rename to packages/bottom-tabs/src/views/ResourceSavingScene.tsx index 719d2e9b..7bcc400a 100644 --- a/packages/bottom-tabs/src/views/ResourceSavingScene.js +++ b/packages/bottom-tabs/src/views/ResourceSavingScene.tsx @@ -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 { render() { if (screensEnabled && screensEnabled()) { const { isVisible, ...rest } = this.props; + // @ts-ignore return ; }