callstack / react-native-paper

Material Design for React Native (Android & iOS)
https://reactnativepaper.com
MIT License
12.5k stars 2.05k forks source link

How can I change icon position using BottomNavigation.Bar in a Tab.Navigator #4438

Open liebestraumm opened 2 weeks ago

liebestraumm commented 2 weeks ago

Hello, I would like to change to icon position at the bottom navbar to be centred. Currently, the icon is not vertically aligned (see image 1). I debugged it on react-devtools and the culprit is a "top" property which is set to 4 (see image 2) and in order for the icon to be centred I need to change it to top: 0. Does anybody know which props and in which component should I do this? I've tried in Tab.Screen, Tab.Navigator and BottomNavigation.Bar components with no success. The icon doesn't look vertically aligned in both Android Emulator and physical devices. Here's what I got in my navigator:

import { BottomTabScreenProps, createBottomTabNavigator } from "@react-navigation/bottom-tabs"
import { CompositeScreenProps, CommonActions } from "@react-navigation/native"
import React from "react"
import { ViewStyle } from "react-native"
import { HomeScreen, MessagesScreen, SearchScreen, MyProfileScreen, TestScreen } from "../screens"
import { colors } from "../theme"
import { AppStackParamList, AppStackScreenProps } from "./AppNavigator"
import { observer } from "mobx-react-lite"
import { createNativeStackNavigator, NativeStackNavigationProp } from "@react-navigation/native-stack"
import HeaderBar from "../components/HeaderBar"
import { BottomNavigation, Icon } from 'react-native-paper';
import { ICONREGISTRY } from "app/const/iconRegistry"

export type MobileBottomNavigatorParamList = {
  Home: undefined
  Search: undefined
  Messages: undefined
  MyProfile: undefined
  Test: undefined
  HomeStack: undefined
  SearchStack: undefined
  MessagesStack: undefined
  MyProfileStack: undefined
}

export type MobileBottomTabScreenProps<T extends keyof MobileBottomNavigatorParamList> = CompositeScreenProps<
  BottomTabScreenProps<MobileBottomNavigatorParamList, T>,
  AppStackScreenProps<keyof AppStackParamList>
>
export type HeaderNavigation = NativeStackNavigationProp<MobileBottomNavigatorParamList>

const Tab = createBottomTabNavigator<MobileBottomNavigatorParamList>()
const Stack = createNativeStackNavigator<MobileBottomNavigatorParamList>()

const HomeStack = observer(function HomeStack() {
  return (
    <Stack.Navigator screenOptions={{ title:"Home", header: (props: any) => <HeaderBar {...props} /> }} initialRouteName="Home">
      <Stack.Screen name="Home" component={HomeScreen} />
      <Stack.Screen name="Test" component={TestScreen} />
    </Stack.Navigator>
  )
})

const SearchStack = observer(function SearchStack() {
  return (
    <Stack.Navigator screenOptions={{ title:"Search", header: (props: any) => <HeaderBar {...props} /> }} initialRouteName="Search">
      <Stack.Screen name="Search" component={SearchScreen} />
    </Stack.Navigator>
  )
})

const MessagesStack = observer(function MessagesStack() {
  return (
    <Stack.Navigator screenOptions={{ title: "Messages", header: (props: any) => <HeaderBar {...props} /> }} initialRouteName="Messages">
      <Stack.Screen name="Messages" component={MessagesScreen} />
    </Stack.Navigator>
  )
})

const MyProfileStack = observer(function MyProfileStack() {
  return (
    <Stack.Navigator screenOptions={{ title: "My Profile", header: (props: any) => <HeaderBar {...props} /> }} initialRouteName="MyProfile">
      <Stack.Screen name="MyProfile" component={MyProfileScreen} />
    </Stack.Navigator>
  )
})

export const MobileBottomNavigator = () => {
  return (
    <Tab.Navigator
      screenOptions={{
        headerShown: false
      }}
      tabBar={({ navigation, state, descriptors, insets }: any) => (
        <BottomNavigation.Bar
          theme={{ colors: { secondaryContainer: "#6B8FDF" } }}
          activeColor="white"
          inactiveColor="white"
          navigationState={state}
          safeAreaInsets={insets}
          style={$tabBar}
          onTabPress={({ route, preventDefault }: any) => {
            const event = navigation.emit({
              type: 'tabPress',
              target: route.key,
              canPreventDefault: true,
            });

            if (event.defaultPrevented) {
              preventDefault();
            } else {
              navigation.dispatch({
                ...CommonActions.navigate(route.name, route.params),
                target: state.key,
              });
            }
          }}
          renderIcon={({ route, focused, color }) => {
            const { options } = descriptors[route.key];
            if (options.tabBarIcon) {
              return options.tabBarIcon({ focused, color, size: 24 });
            }

            return null;
          }}
          getLabelText={({ route }) => {
            const { options } = descriptors[route.key];
            const label =
              options.tabBarLabel !== undefined
                ? options.tabBarLabel
                : options.title !== undefined
                  ? options.title
                  : route.title;

            return label;
          }}
        />
      )}
    >
      <Tab.Screen
        name="HomeStack"
        component={HomeStack}
        options={{
          tabBarLabel: "Home",
          title: "Home",
          tabBarIcon: ({ focused }) => (
            <Icon source={ICONREGISTRY.home} color={focused ? "white" : "white"} size={30} />
          ),
        }}
      />

      <Tab.Screen
        name="SearchStack"
        component={SearchStack}
        options={{
          tabBarLabel: "Search",
          title: "Search",
          tabBarIcon: ({ focused }) => (
            <Icon source={ICONREGISTRY.search} color={focused ? "white" : "white"} size={30} />
          ),
        }}
      />

      <Tab.Screen
        name="MessagesStack"
        component={MessagesStack}
        options={{
          tabBarLabel: "Messages",
          title: "Messages",
          tabBarIcon: ({ focused }) => (
            <Icon source={ICONREGISTRY.chatBubble} color={focused ? "white" : "white"} size={30} />
          ),
        }}
      />

      <Tab.Screen
        name="MyProfileStack"
        component={MyProfileStack}
        options={{
          tabBarLabel: "My Profile",
          title: "My Profile",
          tabBarIcon: ({ focused }) => (
            <Icon source={ICONREGISTRY.person} color={focused ? "white" : "white"} size={30} />
          ),
        }}
      />
    </Tab.Navigator>
  )
}

const $tabBar: ViewStyle = {
  backgroundColor: "#355CA8",
  borderTopColor: colors.transparent,

}

Change_Icon_Position_1

Change_Icon_Position_2

seb-zabielski commented 1 week ago

Hey, I could be wrong, but it looks like there is no easy way to solve your problem. I haven't found a way to override the top style. I think in your case it is also problematic that the icon container has a fixed height (it is based on the Material Design specification). Therefore, the icon is not aligned correctly because your icon size is 30dp, while in the Material specification is 24dp. To have an aligned icon, you can either set the icon size to 24dp or try adjusting the position with a negative value for the top style if the icon, for example :

<Tab.Screen
  name="Home"
  component={HomeScreen}
  options={{
    tabBarIcon: ({ color, size }) => {
      return (
        <Icon 
          name="home" 
          size={size} 
          color={color} 
          style={{ top: -4 }} // <- THIS
        />
      );
    },
  }}
/>

However, I cannot guarantee that this will not cause other unforeseen problems

I know that these are not the best solutions. Maybe at least one of them will at least be enough until someone else provides a better one?