import * as React from 'react' ; import { StyleProp , ViewStyle } from 'react-native' ; import { camelCase } from 'lodash' ; import { NavigationContainer } from '@react-navigation/native' ; import { BackBehavior } from '@react-navigation/routers/src/TabRouter' ; import { createStackNavigator , CardStyleInterpolators } from '@react-navigation/stack' ; import { StackHeaderOptions , StackCardMode , StackHeaderMode } from '@react-navigation/stack/src/types' ; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs' ; import { navigationRef } from './rootNavigation' ; import CustomTabBar from './view/TabBar' ; import HeadTitle from './view/HeadTitle' ; import BackButton from './view/BackButton' ; import { getTabItemConfig , getTabVisible , setTabConfig , getTabInitRoute , handleUrl } from './utils/index' ; interface WindowConfig { pageOrientation ? : 'auto' | 'portrait' | 'landscape' ; pullRefresh ? : 'YES' | 'NO' | boolean ; allowsBounceVertical ? : 'YES' | 'NO' ; navigationBarBackgroundColor ? : string ; navigationBarTextStyle ? : 'white' | 'black' ; navigationStyle ? : 'default' | 'custom' ; navigationBarTitleText ? : string ; backgroundTextStyle ? : 'dark' | 'light' ; enablePullDownRefresh ? : boolean ; onReachBottomDistance ? : number ; } interface ITabBarItem { pagePath : string ; text : string ; iconPath ? : string ; selectedIconPath ? : string ; } interface ITabBar { color ? : string ; selectedColor ? : string ; backgroundColor ? : string ; borderStyle ? : 'black' | 'white' ; list : ITabBarItem [ ] ; position ? : 'bottom' | 'top' ; custom ? : boolean ; } interface PageItem { name : string ; component : any ; pagePath : string ; } interface RNConfig { initialRouteName ? : string ; linking ? : string [ ] ; screenOptions ? : Record < string , any > ; tabBarOptions ? : Record < string , any > ; options ? : Record < string , any > ; tabProps ? : { backBehavior ? : BackBehavior ; lazy ? : boolean ; detachInactiveScreens ? : boolean ; sceneContainerStyle ? : StyleProp < ViewStyle > ; } ; stackProps ? : { keyboardHandlingEnabled ? : boolean ; mode ? : StackCardMode ; headerMode ? : StackHeaderMode ; detachInactiveScreens ? : boolean ; } ; } export interface RouterConfig { pages : PageItem [ ] ; tabBar ? : ITabBar ; window ? : WindowConfig ; linkPrefix ? : string [ ] ; rnConfig ? : RNConfig ; initParams ? : Record < string , any > ; initPath ? : string ; entryPagePath ? : string ; } export function createRouter ( config ) { if ( config ?. tabBar ?. list ?. length ) { return createTabNavigate ( config ) ; } else { return createStackNavigate ( config ) ; } } function getTabNames ( config ) { let tabNames = [ ] ; const tabBar = config ?. tabBar ; if ( ! tabBar ) return tabNames ; tabNames = tabBar . list . map ( item => { const pagePath = item . pagePath . startsWith ( '/' ) ? item . pagePath : `/${ item . pagePath }` ; return camelCase ( pagePath ) ; } ) ; return tabNames ; } function getPageList ( config ) { const tabBar = config . tabBar ; const pageList = config . pages ; if ( ! tabBar ) return pageList ; const tabNames = getTabNames ( config ) ; return pageList . filter ( item => tabNames . indexOf ( item . name ) === - 1 ) ; } function getTabItemOptions ( item , index ) { return { tabBarLabel : getTabItemConfig ( index , 'tabBarLabel' ) || item . text , tabBarBadge : getTabItemConfig ( index , 'tabBarBadge' ) , tabBarVisible : getTabVisible ( ) , tabBarTestID : `tabbar-${ index }` } ; } function getHeaderView ( title , color , props ) { return React . createElement ( HeadTitle , { label : title , color , headerProps : props } , null ) ; } function getStackOptions ( config ) { const windowOptions = config . window || { } ; const title = '' ; const headColor = windowOptions . navigationBarTextStyle || 'white' ; const bgColor = windowOptions . navigationBarBackgroundColor || '#000000' ; const headerTitleAlign = 'center' ; const defaultOptions = { title : title , headerShown : windowOptions . navigationStyle !== 'custom' , headerTitle : ( props ) => getHeaderView ( title , headColor , props ) , headerStyle : { backgroundColor : bgColor , shadowOffset : { width : 0 , height : 0 } , borderWidth : 0 , elevation : 0 , shadowOpacity : 1 , borderBottomWidth : 0 } , headerTintColor : headColor , cardStyleInterpolator : CardStyleInterpolators . forHorizontalIOS , headerBackTitleVisible : false , headerPressColorAndroid : 'rgba(255,255,255,0)' , headerTitleAlign , headerBackImage : ( { tintColor } ) => { return React . createElement ( BackButton , { tintColor } , null ) ; } } ; const rnConfig = config . rnConfig || { } ; const screenOptions = rnConfig ?. screenOptions || { } ; return Object . assign ( { } , defaultOptions , screenOptions ) ; } function getTabItem ( config , tabName ) { const tabBar = config . tabBar ; const pageList = config . pages ; if ( ! tabBar ) return pageList ; let tabItem = { name : '' , component : { } , pagePath : '' } ; pageList . forEach ( item => { if ( item . name === tabName ) { tabItem = item ; } } ) ; return tabItem ; } let initRoute ; function getInitRouteName ( config ) { if ( initRoute ) return initRoute ; const initPath = config . initPath || '' ; const rn = config . rnConfig || { } ; if ( initPath ) { initRoute = handleUrl ( initPath ) . pageName ; } else if ( rn ?. initialRouteName ) { initRoute = camelCase ( rn . initialRouteName ) ; } else if ( config . entryPagePath ) { const entryPagePath = config . entryPagePath . startsWith ( '/' ) ? config . entryPagePath : `/${ config . entryPagePath }` ; initRoute = config . pages . find ( p => p . pagePath === entryPagePath ) ?. name ; } else { initRoute = config . pages [ 0 ] . name ; } return initRoute ; } function getInitTabRoute ( config ) { const pageList = config . pages ; const tabNames = getTabNames ( config ) ; const initPath = config . initPath || '' ; let initTabName = '' ; if ( initPath ) { const route = handleUrl ( initPath ) . pageName ; for ( let i = 0 ; i < tabNames . length ; i ++ ) { if ( route === tabNames [ i ] ) { initTabName = tabNames [ i ] ; break ; } } } if ( ! initTabName ) { for ( let i = 0 ; i < pageList . length ; i ++ ) { const item = pageList [ i ] ; if ( tabNames . indexOf ( item . name ) !== - 1 ) { initTabName = item . name ; break ; } } } return initTabName ; } function getInitParams ( config , pageName ) { let params = { } ; const initRouteName = getInitRouteName ( config ) ; if ( initRouteName === pageName ) { const initPath = config . initPath || '' ; params = handleUrl ( initPath ) . params ; params = Object . assign ( { } , params , config . initParams ) ; } return params ; } function createTabStack ( config , parentProps ) { const Tab = createBottomTabNavigator ( ) ; const tabBar = config . tabBar ; const rnConfig = config . rnConfig ; const tabList = [ ] ; const userOptions = rnConfig ?. options || { } ; tabBar ?. list . forEach ( ( item , index ) => { const defaultOptions = Object . assign ( { } , { tabBarVisible : config . tabBar ?. custom ? false : getTabVisible ( ) } , getTabItemOptions ( item , index ) ) ; const tabItemOptions = Object . assign ( { } , defaultOptions , userOptions ) ; setTabConfig ( 'tabBarVisible' , tabItemOptions . tabBarVisible ) ; const path = item . pagePath . startsWith ( '/' ) ? item . pagePath : `/${ item . pagePath }` ; const tabName = camelCase ( path ) ; const tabPage = getTabItem ( config , tabName ) as PageItem ; const initParams = getInitParams ( config , tabName ) ; const tabNode = React . createElement ( Tab . Screen , { key : `tab${ tabName }` , name : `${ tabPage . name }` , options : tabItemOptions , component : tabPage . component , initialParams : initParams , ... parentProps } ) ; tabList . push ( tabNode ) ; } ) ; const userTabBarOptions = rnConfig ?. tabBarOptions || { } ; const tabBarOptions = Object . assign ( { backBehavior : 'none' , activeTintColor : tabBar ?. selectedColor || '#3cc51f' , inactiveTintColor : tabBar ?. color || '#7A7E83' , activeBackgroundColor : tabBar ?. backgroundColor || '#ffffff' , inactiveBackgroundColor : tabBar ?. backgroundColor || '#ffffff' , style : tabBar ?. borderStyle ? { backgroundColor : tabBar ?. backgroundColor , borderTopColor : ( tabBar ?. borderStyle === 'black' ? '#000000' : '#ffffff' ) } : { } } , userTabBarOptions ) ; const tabNames = getTabNames ( config ) ; const tabProps = config . rnConfig ?. tabProps || { } ; const tabInitRouteName = getTabInitRoute ( ) || getInitTabRoute ( config ) || tabNames [ 0 ] ; return React . createElement ( Tab . Navigator , { ... tabProps , tabBarOptions : tabBarOptions , tabBar : ( props ) => createTabBar ( props , userOptions ) , initialRouteName : tabInitRouteName , children : tabList } , tabList ) ; } function createTabBar ( props , userOptions ) { return React . createElement ( CustomTabBar , { ... props , userOptions } ) ; } function getLinkingConfig ( config ) { const prefixes = config ?. rnConfig ?. linking || config . linkPrefix || [ ] ; const screens = { } ; const pageList = getPageList ( config ) ; pageList . forEach ( item => { const path = item . pagePath . startsWith ( '/' ) ? item . pagePath . substr ( 1 ) : item . pagePath ; screens [ `${ item . name }` ] = path ; } ) ; let tabScreen = { } ; if ( config . tabBar ) { const tabs = { } ; const tabBarList = config . tabBar ?. list || [ ] ; tabBarList . forEach ( ( item ) => { const tabPath = item . pagePath . startsWith ( '/' ) ? item . pagePath : `/${ item . pagePath }` ; const tabName = camelCase ( tabPath ) ; tabs [ `${ tabName }` ] = tabPath ; } ) ; tabScreen = { tabNav : { path : '' , screens : tabs } } ; } return { prefixes : prefixes , config : { screens : { ... tabScreen , ... screens } } } ; } function createTabNavigate ( config ) { const screeList = [ ] ; const Stack = createStackNavigator ( ) ; const tabScreen = React . createElement ( Stack . Screen , { name : 'tabNav' , key : 'tabScreen' , children : ( props ) => createTabStack ( config , props ) } , ( props ) => createTabStack ( config , props ) ) ; screeList . push ( tabScreen ) ; const pageList = getPageList ( config ) ; pageList . forEach ( item => { const initParams = getInitParams ( config , item . name ) ; const screenNode = React . createElement ( Stack . Screen , { key : `${ item . name }` , name : `${ item . name }` , component : item . component , initialParams : initParams } , null ) ; screeList . push ( screenNode ) ; } ) ; const linking = getLinkingConfig ( config ) ; const stackProps = config . rnConfig ?. stackProps ; const tabStack = React . createElement ( Stack . Navigator , { ... stackProps , screenOptions : ( ) => { const options = getCurrentOptions ( ) ; const defaultOptions = getStackOptions ( config ) ; return Object . assign ( { } , defaultOptions , options ) ; } , initialRouteName : getInitRouteName ( config ) , children : screeList } , screeList ) ; return React . createElement ( NavigationContainer , { ref : navigationRef , linking : linking , children : tabStack } , tabStack ) ; } function createStackNavigate ( config ) { const Stack = createStackNavigator ( ) ; const pageList = getPageList ( config ) ; if ( pageList . length <= 0 ) return null ; const screenChild = [ ] ; pageList . forEach ( item => { const initParams = getInitParams ( config , item . name ) ; const screenNode = React . createElement ( Stack . Screen , { key : `${ item . name }` , name : `${ item . name }` , component : item . component , initialParams : initParams } , null ) ; screenChild . push ( screenNode ) ; } ) ; const linking = getLinkingConfig ( config ) ; const stackProps = config . rnConfig ?. stackProps ; const stackNav = React . createElement ( Stack . Navigator , { ... stackProps , screenOptions : getStackOptions ( config ) , children : screenChild , initialRouteName : getInitRouteName ( config ) } , screenChild ) ; return React . createElement ( NavigationContainer , { ref : navigationRef , linking : linking , children : stackNav } , stackNav ) ; } function getCurrentOptions ( ) { const options = navigationRef . current ?. getCurrentOptions ( ) || { } ; const params = navigationRef . current ?. getCurrentRoute ( ) ?. params || { } ; const navParams = params ?. navigateConfig || { } ; return Object . assign ( { } , options , navParams ) ; }