import React from 'react'; import { TouchableWithoutFeedback } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import type { ReactElement, ReactNode } from 'react'; import type { StyleProp, ViewStyle, ViewProps } from 'react-native'; import Icon from '../Icon'; import { isHeroIcon } from '../Icon/utils'; import { BottomBar, BottomBarItem, BottomBarWrapper, BottomNavigationContainer, BottomNavigationTab, ContentWrapper, StyledBottomBarText, } from './StyledBottomNavigation'; import { isIOS } from '../../utils/helpers'; import type { IconName } from '../Icon'; import Count from '../Badge/Count'; export type BottomNavigationTabType = { key: string; title?: string; icon: IconName; component: ReactNode; testID?: string; /** * Badge to display on the tab icon. */ badge?: { /** The number to display on the badge. */ count: number; /** Maximum number before showing `${max}+`. Defaults to 99. */ max?: number; /** Testing id for the badge wrapper. */ testID?: string; }; }; interface BottomNavigationProps extends ViewProps { /** * Callback which is called on tab press, receiving key of upcoming active Tab. */ onTabPress: (key: string) => void; /** * Whether inactive tabs should be removed and unmounted in React. * Defaults to `false`. */ renderActiveTabOnly?: boolean; /** * Current selected tab key. */ selectedTabKey: string; /** * List of Tabs to be rendered. Each Tab must have an unique key. */ tabs: BottomNavigationTabType[]; /** * Additional style. */ style?: StyleProp; /** * Testing id of the component. */ testID?: string; } const getInactiveIcon = (icon: IconName): IconName => { const inactiveIcon = `${icon}-outlined`; return isHeroIcon(inactiveIcon) ? inactiveIcon : icon; }; const BottomNavigation = ({ onTabPress, renderActiveTabOnly = false, selectedTabKey, tabs, ...nativeProps }: BottomNavigationProps): ReactElement => { const insets = useSafeAreaInsets(); /** * List of loaded tabs, tabs will be loaded when navigated to. */ const [loaded, setLoaded] = React.useState([selectedTabKey]); if (!loaded.includes(selectedTabKey)) { // Set the current tab to be loaded if it was not loaded before setLoaded((loadedState) => [...loadedState, selectedTabKey]); } return ( {tabs.map((tab) => { const { key, component, testID } = tab; const active = selectedTabKey === key; if (renderActiveTabOnly && !active) { return null; } if (!loaded.includes(key)) { // Don't render a screen if we've never navigated to it return null; } return ( {component} ); })} {tabs.map((tab) => { const { key, icon, title, testID, badge } = tab; const active = selectedTabKey === key; const inactiveIcon = getInactiveIcon(icon); return ( onTabPress(key)} testID={testID} > 0} max={badge?.max} testID={badge?.testID ?? `bottom-nav-badge-${key}`} > {!!title && ( {title} )} ); })} ); }; export default BottomNavigation;