diff --git a/packages/bottom-tabs/src/navigators/createBottomTabNavigator.tsx b/packages/bottom-tabs/src/navigators/createBottomTabNavigator.tsx index 0c55c622..2985d06b 100644 --- a/packages/bottom-tabs/src/navigators/createBottomTabNavigator.tsx +++ b/packages/bottom-tabs/src/navigators/createBottomTabNavigator.tsx @@ -1,5 +1,10 @@ import * as React from 'react'; -import { View, StyleSheet } from 'react-native'; +import { + View, + StyleSheet, + AccessibilityRole, + AccessibilityState, +} from 'react-native'; // eslint-disable-next-line import/no-unresolved import { ScreenContainer } from 'react-native-screens'; @@ -7,18 +12,29 @@ import { ScreenContainer } from 'react-native-screens'; import createTabNavigator, { NavigationViewProps, } from '../utils/createTabNavigator'; -import BottomTabBar, { TabBarOptions } from '../views/BottomTabBar'; +import BottomTabBar from '../views/BottomTabBar'; import ResourceSavingScene from '../views/ResourceSavingScene'; -import { NavigationProp, Route, SceneDescriptor } from '../types'; +import { + NavigationProp, + Route, + SceneDescriptor, + NavigationBottomTabOptions, + BottomTabBarOptions, +} from '../types'; type Props = NavigationViewProps & { - getAccessibilityRole: (props: { route: any }) => string; - getAccessibilityStates: (props: { route: any }) => string[]; + getAccessibilityRole: (props: { + route: Route; + }) => AccessibilityRole | undefined; + getAccessibilityStates: (props: { + route: Route; + focused: boolean; + }) => AccessibilityState[]; lazy?: boolean; tabBarComponent?: React.ComponentType; - tabBarOptions?: TabBarOptions; + tabBarOptions?: BottomTabBarOptions; navigation: NavigationProp; - descriptors: { [key: string]: SceneDescriptor }; + descriptors: { [key: string]: SceneDescriptor }; screenProps?: unknown; }; @@ -29,9 +45,12 @@ type State = { class TabNavigationView extends React.PureComponent { static defaultProps = { lazy: true, - getAccessibilityRole: () => 'button', - getAccessibilityStates: ({ focused }: { focused: boolean }) => - focused ? ['selected'] : [], + getAccessibilityRole: (): AccessibilityRole => 'button', + getAccessibilityStates: ({ + focused, + }: { + focused: boolean; + }): AccessibilityState[] => (focused ? ['selected'] : []), }; static getDerivedStateFromProps(nextProps: Props, prevState: State) { @@ -157,4 +176,6 @@ const styles = StyleSheet.create({ }, }); -export default createTabNavigator(TabNavigationView); +export default createTabNavigator( + TabNavigationView +); diff --git a/packages/bottom-tabs/src/types.tsx b/packages/bottom-tabs/src/types.tsx index 95198f3e..dc046f49 100644 --- a/packages/bottom-tabs/src/types.tsx +++ b/packages/bottom-tabs/src/types.tsx @@ -1,4 +1,13 @@ import * as React from 'react'; +import { + AccessibilityRole, + AccessibilityState, + StyleProp, + TextStyle, + ViewStyle, +} from 'react-native'; +import SafeAreaView from 'react-native-safe-area-view'; +import Animated from 'react-native-reanimated'; export type Route = { key: string; @@ -36,18 +45,127 @@ export type NavigationProp = { dangerouslyGetParent(): NavigationProp | undefined; }; -export type NavigationTabOptions = { +export type Orientation = 'horizontal' | 'vertical'; + +export type LabelPosition = 'beside-icon' | 'below-icon'; + +export type BottomTabBarOptions = { + keyboardHidesTabBar: boolean; + activeTintColor?: string; + inactiveTintColor?: string; + activeBackgroundColor?: string; + inactiveBackgroundColor?: string; + allowFontScaling: boolean; + showLabel: boolean; + showIcon: boolean; + labelStyle: StyleProp; + tabStyle: StyleProp; + labelPosition?: + | LabelPosition + | ((options: { deviceOrientation: Orientation }) => LabelPosition); + adaptive?: boolean; + style: StyleProp; +}; + +export type BottomTabBarProps = BottomTabBarOptions & { + 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; + focused: boolean; + }) => AccessibilityState[]; + getButtonComponent: (props: { + route: Route; + }) => React.ComponentType | undefined; + getLabelText: (props: { + route: Route; + }) => + | ((scene: { + focused: boolean; + tintColor?: string; + orientation?: 'horizontal' | 'vertical'; + }) => string | undefined) + | string + | undefined; + getTestID: (props: { route: Route }) => string | undefined; + renderIcon: (props: { + route: Route; + focused: boolean; + tintColor?: string; + horizontal?: boolean; + }) => React.ReactNode; + dimensions: { width: number; height: number }; + isLandscape: boolean; + safeAreaInset: React.ComponentProps['forceInset']; +}; + +export type MaterialTabBarOptions = { + activeTintColor?: string; + allowFontScaling?: boolean; + bounces?: boolean; + inactiveTintColor?: string; + pressColor?: string; + pressOpacity?: number; + scrollEnabled?: boolean; + showIcon?: boolean; + showLabel?: boolean; + upperCaseLabel?: boolean; + tabStyle?: StyleProp; + indicatorStyle?: StyleProp; + iconStyle?: StyleProp; + labelStyle?: StyleProp; + contentContainerStyle?: StyleProp; + style?: StyleProp; +}; + +export type MaterialTabBarProps = MaterialTabBarOptions & { + layout: { + width: number; + height: number; + }; + position: Animated.Node; + jumpTo: (key: string) => void; + getLabelText: (scene: { + route: Route; + }) => + | ((scene: { focused: boolean; tintColor: string }) => string | undefined) + | string + | undefined; + getAccessible?: (scene: { route: Route }) => boolean | undefined; + getAccessibilityLabel: (scene: { route: Route }) => string | undefined; + getTestID: (scene: { route: Route }) => string | undefined; + renderIcon: (scene: { + route: Route; + focused: boolean; + tintColor: string; + horizontal?: boolean; + }) => React.ReactNode; + renderBadge?: (scene: { route: Route }) => React.ReactNode; + onTabPress?: (scene: { route: Route }) => void; + onTabLongPress?: (scene: { route: Route }) => void; + tabBarPosition?: 'top' | 'bottom'; + screenProps: unknown; + navigation: NavigationProp; +}; + +export type NavigationCommonTabOptions = { title?: string; - tabBarVisible?: boolean; tabBarLabel?: React.ReactNode; + tabBarVisible?: boolean; + tabBarAccessibilityLabel?: string; + tabBarTestID?: string; tabBarIcon?: | React.ReactNode | ((props: { focused: boolean; - tintColor: string; - horizontal: boolean; + tintColor?: string; + horizontal?: boolean; }) => React.ReactNode); - tabBarTestID?: string; tabBarOnPress?: (props: { navigation: NavigationProp; defaultHandler: () => void; @@ -56,19 +174,28 @@ export type NavigationTabOptions = { navigation: NavigationProp; defaultHandler: () => void; }) => void; - tabBarAccessibilityLabel?: string; - tabBarButtonComponent?: React.ComponentType; }; -export type SceneDescriptor = { +export type NavigationBottomTabOptions = NavigationCommonTabOptions & { + tabBarButtonComponent?: React.ComponentType; +}; + +export type NavigationMaterialTabOptions = NavigationCommonTabOptions & { + tabBarButtonComponent?: React.ComponentType; + swipeEnabled?: boolean | ((state: NavigationState) => boolean); +}; + +export type SceneDescriptor = { key: string; - options: NavigationTabOptions; + options: Options; navigation: NavigationProp; getComponent(): React.ComponentType; }; -export type Screen = React.ComponentType & { - navigationOptions?: NavigationTabOptions & { +export type Screen< + Options extends NavigationCommonTabOptions +> = React.ComponentType & { + navigationOptions?: Options & { [key: string]: any; }; }; diff --git a/packages/bottom-tabs/src/views/BottomTabBar.tsx b/packages/bottom-tabs/src/views/BottomTabBar.tsx index 08c0849d..0e7d3b15 100644 --- a/packages/bottom-tabs/src/views/BottomTabBar.tsx +++ b/packages/bottom-tabs/src/views/BottomTabBar.tsx @@ -6,74 +6,13 @@ import { View, Keyboard, Platform, - StyleProp, - TextStyle, - ViewStyle, LayoutChangeEvent, - AccessibilityRole, - AccessibilityState, } from 'react-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'; -type LabelPosition = - | Position - | ((options: { deviceOrientation: Orientation }) => Position); - -export type TabBarOptions = { - 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: 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']; -}; +import { Route, BottomTabBarProps } from '../types'; type State = { layout: { height: number; width: number }; @@ -88,7 +27,9 @@ const isIOS11 = majorVersion >= 11 && isIos; const DEFAULT_MAX_TAB_ITEM_WIDTH = 125; class TouchableWithoutFeedbackWrapper extends React.Component< - React.ComponentProps + React.ComponentProps & { + children: React.ReactNode; + } > { render() { const { @@ -117,7 +58,7 @@ class TouchableWithoutFeedbackWrapper extends React.Component< } } -class TabBarBottom extends React.Component { +class TabBarBottom extends React.Component { static defaultProps = { keyboardHidesTabBar: true, activeTintColor: '#007AFF',