How to add an indicator under the active bottom tab? - react-native

I need to add an indicator for the active tab I tried to add a borderBottom with tabStyle but we can't check focused with that.
Using react-navigation v5 and createBottomTabNavigator for bottom tabs.
Here's my code:
<BottomTab.Navigator
tabBarOptions={{
activeTintColor: colors.brown,
labelPosition: 'below-icon',
}}>
<BottomTab.Screen
name="Home"
component={HomeTabNav}
options={{
tabBarLabel: 'Home',
tabBarIcon: ({focused}) => {
return focused ? (
<HomeSelectedIcon height={ms(24)} width={ms(24)} />
) : (
<HomeIcon height={ms(24)} width={ms(24)} />
);
},
}}
/>
...
</BottomTab.Navigator>
);
};
Thanks in advance!

I figured it out myself by making a custom tabbar icon if someone needs to achieve this using the bottom-tab bar only.
Here's the code.
<BottomTab.Navigator
tabBarOptions={{
activeTintColor: colors.brown,
showLabel: false,
tabStyle: styles.tabStyle,
style: styles.tabContainerStyle,
}}>
<BottomTab.Screen
name="Home"
component={HomeTabNav}
options={{
tabBarLabel: 'Home',
tabBarIcon: ({focused}) => {
return focused ? (
<View style={styles.labelFocusedContainer}>
<HomeSelectedIcon height={24} width={24} />
<Text style={styles.labelFocusedStyle}>Home</Text>
</View>
) : (
<View style={styles.labelContainer}>
<HomeIcon height={24} width={24} />
<Text style={styles.labelStyle}>Home</Text>
</View>
);
},
}}
/>
...
</BottomTab.Navigator>
const styles = StyleSheet.create({
labelContainer: {
alignItems: 'center',
width: '100%',
},
labelFocusedContainer: {
alignItems: 'center',
width: '100%',
borderBottomWidth: 3,
borderBottomColor: colors.brown,
},
labelFocusedStyle: {
textAlign: 'center',
marginVertical: 8,
color: colors.brown,
backgroundColor: 'transparent',
fontSize: 10,
},
labelStyle: {
textAlign: 'center',
marginVertical: 8,
color: colors.veryDarkgray,
backgroundColor: 'transparent',
fontSize: 10,
},
});
But the best and easy way to do this is by using createMaterialTopTabNavigator and using these props.
tabBarPosition="bottom"
tabBarOptions={{
showIcon: true,
pressOpacity: 1,
iconStyle: styles.iconStyle,
showLabel: true,
activeTintColor: colors.brown,
indicatorStyle: {
borderWidth: 2,
borderColor: colors.brown,
},

This does not seem to be possible / easily achievable with bottom-tabs, but you could use the material version - #react-navigation/material-top-tabs and configure it to match your needs, specifically using tabBarPosition="bottom" and tabBarOptions={{ indicatorStyle: { backgroundColor } }}.
You can check more options in the docs: https://reactnavigation.org/docs/material-top-tab-navigator/#tabbaroptions
import * as React from 'react';
import { Text, View } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createMaterialTopTabNavigator } from '#react-navigation/material-top-tabs';
const Tabs = createMaterialTopTabNavigator();
export default function App() {
return (
<NavigationContainer>
<Tabs.Navigator tabBarPosition="bottom" tabBarOptions={{ indicatorStyle: { backgroundColor: 'red' } }}>
<Tabs.Screen name="screen 1" component={View} />
<Tabs.Screen name="screen 2" component={View} />
</Tabs.Navigator>
</NavigationContainer>
);
}

The best answer would be to use the tabBarButton prop to override and add your own custom styles to the container of the tab button.
https://reactnavigation.org/docs/bottom-tab-navigator#tabbarbutton
const CustomTabButton = (props) => (
<Pressable
{...props}
style={
props.accessibilityState.selected
? [props.style, styles.activeTab]
: props.style
}
/>
)
styles.activeTab is the custom style you want to add, be careful to spread the props.style to get the default styles from the library like width, padding, height etc
props.accessibilityState.selected will add styles according to condition if you want styles for all the tabs you can remove the condition.
Inside screeenOptions prop on navigator or the option props of each screen.
tabBarButton: CustomTabButton
Using material top tab is not a good solution because it does not support well with a keyboard. But bottom tabs do work well with the keyboard.

Related

How to modify "selected tab" bottom border color on TopTabNavigator

I'm working on a react-native app using react-navigations createMaterialTopTabNavigator. I've read through the docs, but I cannot seem to find what props I need to modify to change the bottom border color of the active tab.
Does anyone have any advice?
FWIW here is my current Navigator code:
const TopTab = createMaterialTopTabNavigator()
function ProgressNavigator() {
const themes = useThemes()
return (
<TopTab.Navigator
screenOptions={{
tabBarStyle: {
backgroundColor: themes.lightBackground,
},
tabBarLabelStyle: {
paddingTop: 10,
fontSize: 14,
fontWeight: "bold",
},
tabBarActiveTintColor: themes.secondary.color,
tabBarInactiveTintColor: "#FFF",
}}
>
<TopTab.Screen name="Table" component={ProgressTable} />
<TopTab.Screen name="Chart" component={ProgressChart} />
</TopTab.Navigator>
)
}
Use tabBarIndicatorStyle to style an indicator. Below example sets label (and possible icon) and indicator to red.
<Tab.Navigator
screenOptions={{
tabBarIndicatorStyle: {
backgroundColor: 'red'
},
tabBarActiveTintColor: 'red',
}}>
...
</Tab.Navigator>

How to center tabs in MaterialTopTabNavigator in ReactNative

I added tabStyle: { width: "auto" } to fit the tabs of MaterialTopTabNavigator to the width of the text, but by adding this, the tabs are moved to the left side as shown in the image below.
Tabs are too far to the left.
The answer to this question says to remove tabStyle, but if I remove tabStyle, all tabs will have the same width.
I thought about using my own component in the tabBar, but I gave up because I couldn't reproduce the animation when moving the tab.
I thought about using paddingHorizontal to force it to be centered, but I decided against it because it might not be centered if the font size is changed in the OS.
If you know more about this, please let me know how to solve it.
The design I want to implement (edited in Paint)
import { View, Text, StyleSheet, Button, SafeAreaView } from "react-native";
import React, { useRef } from "react";
import { createMaterialTopTabNavigator } from "#react-navigation/material-top-tabs";
const Tab = createMaterialTopTabNavigator();
const Page = () => {
return (
<Tab.Navigator
tabBarOptions={{
labelStyle: {
fontSize: 14,
fontWeight: "bold",
},
activeTintColor: "#ffffff",
inactiveTintColor: "#000000",
pressOpacity: 1,
indicatorStyle: {
backgroundColor: "black",
height: 30,
borderRadius: 30,
top: 9,
},
tabStyle: {
width: "auto",
},
}}
>
<Tab.Screen name="Home" component={Home} />
<Tab.Screen name="Message" component={Message} />
<Tab.Screen name="Store" component={Store} />
</Tab.Navigator>
);
};
export default Page;
I think this should definitely work. If not, let me know.
import { View, Text, StyleSheet, Button, SafeAreaView } from "react-native";
import React, { useRef } from "react";
import { createMaterialTopTabNavigator } from "#react-navigation/material-top-tabs";
const Tab = createMaterialTopTabNavigator();
const Page = () => {
return (
<Tab.Navigator
tabBarOptions={{
labelStyle: {
fontSize: 14,
fontWeight: "bold",
},
activeTintColor: "#ffffff",
inactiveTintColor: "#000000",
pressOpacity: 1,
indicatorStyle: {
backgroundColor: "black",
height: 30,
borderRadius: 30,
top: 9,
},
tabStyle: {
width: "auto",
},
}}
>
<View style={{flexDirection:"row", justifyContent:"center"}}>
<View>
<Tab.Screen name="Home" component={Home} />
<Tab.Screen name="Message" component={Message} />
<Tab.Screen name="Store" component={Store} />
</View>
</View>
// If the above does not work than use the below one and comment the above part
// <View style={{flexDirection:"row", justifyContent:"center"}}>
// <Tab.Screen name="Home" component={Home} />
// <Tab.Screen name="Message" component={Message} />
// <Tab.Screen name="Store" component={Store} />
// </View>
</Tab.Navigator>
);
};
You can add tabBarContentContainerStyle attribute below, it works well for me:
<Tab.Navigator
initialRouteName="Attention"
screenOptions={{
lazy: true,
tabBarPressOpacity: 1,
tabBarPressColor: 'rgba(0,0,0,0)',
tabBarInactiveTintColor: Style.unActiveText,
tabBarActiveTintColor: Style.mainColor,
tabBarContentContainerStyle: {
alignItems: 'center',
justifyContent: 'center',
},
tabBarIndicatorStyle: {
display: 'none',
},
tabBarItemStyle: {
width: 70,
paddingHorizontal: 0,
position: 'relative',
padding: 0,
height: 45,
},
tabBarLabelStyle: {
fontSize: 15,
},
tabBarStyle: {
width: 'auto',
height: 45,
borderBottomColor: 'red',
backgroundColor: '#fff',
},
}}>
</Tab.Navigator>
Just apply those styles to screenOptions parameter in your navigator and you're done:
tabBarStyle: {
alignSelf: "center",
flexDirection: "row",
},
tabBarItemStyle: {
width: "auto",
}

Change the color of top navigation bar in react-native

I am new to react-native and I am working on developing a login application. The functionalities work completely fine. I want to change the color of the Navigation header(as shown in picture) from white to some other color. I looked but couldn't find a way to do the same. Can anyone guide me to correct pointer to achieve the same.
Here is the stackNavigation code that I am using :
const Login = createStackNavigator();
const LoginStack = () => {
return (
<Login.Navigator
initialRouteName="Welcome"
headerMode="float"
screenOptions={() => ({
headerTintColor: AppStyles.colorSet.mainBackgroundColor,
headerTitleStyle: styles.headerTitleStyle,
headerRight: () => <View />,
cardStyle: { backgroundColor: '#275362', }
})}
>
<Login.Screen name="Login" component={LoginScreen} />
<Login.Screen
options={{ headerShown: false }}
name="Welcome"
component={WelcomeScreen}
/>
</Login.Navigator>
);
};
const styles = StyleSheet.create({
headerTitleStyle: {
fontWeight: 'bold',
textAlign: 'center',
alignSelf: 'center',
color: 'red',
flex: 1,
},
});
If you want to change the header's background color for all the screens in your navigator:
<Login.Navigator
initialRouteName="Welcome"
headerMode="float"
screenOptions={{
headerStyle: {
backgroundColor: 'blue',
}
}}
>
If you only want to change the header's background color of your login screen:
<Login.Screen
name="Login"
component={LoginScreen}
options={{
headerStyle: {
backgroundColor: 'red',
}
}}
/>

How to horizontally center TopTabNavigator in React Native?

I've been struggling to apply the proper styling so that my Top Tab Navigator is horizontally centered on the screen. I've tried applying AlignItems: center to the different style props but that does not seem to work. Any tips?
Here is the documentation I have been following: https://reactnavigation.org/docs/material-top-tab-navigator/
import React from "react";
import { createMaterialTopTabNavigator } from "#react-navigation/material-top-tabs";
import Creators from "../../screens/Creators/Creators";
import Feed from "../../screens/Feed/Feed";
import Profile from "../../screens/Profile/Profile";
const Tab = createMaterialTopTabNavigator();
function TabNav() {
return (
<Tab.Navigator
tabBarOptions={{
labelStyle: {
fontSize: 12,
color: "white",
},
tabStyle: {
width: 100,
height: 40,
marginTop: 50,
},
indicatorStyle: {
backgroundColor: "white",
},
style: {
backgroundColor: "#03182d",
},
}}
>
<Tab.Screen name="Creators" component={Creators} />
<Tab.Screen name="Feed" component={Feed} />
<Tab.Screen name="Profile" component={Profile} />
</Tab.Navigator>
);
}
export default TabNav;
remove tabStyle and it will work perfectly like this
<Tab.Navigator
tabBarOptions={{
labelStyle: {
fontSize: 12,
color: "white",
},
//remove this
//tabStyle: {
// width: 100,
// height: 40,
// marginTop: 50,
},
indicatorStyle: {
backgroundColor: "white",
},
style: {
backgroundColor: "#03182d",
},
}}
>
<Tab.Screen name="Creators" component={Creators} />
<Tab.Screen name="Feed" component={Feed} />
<Tab.Screen name="Profile" component={Profile} />
</Tab.Navigator>
or remove only width as it is smudging your tabs

Adding a custom 'Add' button to createMaterialBottomTabNavigator in react navigation 5

I'm making a project (react native, expo, react navigation 5) where I wanted to add a custom 'add' button to the bottom tabs, but since...
A navigator can only contain 'Screen' components as its direct
children
...I needed to find a way to pass my custom component.
Seemed easy enough, I mean there are docs:
https://reactnavigation.org/docs/material-bottom-tab-navigator
https://reactnavigation.org/docs/bottom-tab-navigator
https://reactnavigation.org/docs/custom-navigators/
...but in looking at these and the questions from others I either only found muy complicado examples or examples of how to achieve this in earlier versions.
In the end I found a simple solution that so far works like a charm (fully grateful to any suggestions as to why this might be a terrible idea).
Figured I post my solution if anyone is in a similar pickle. See answer below.
Place the component outside the navigator, and position it above the tabs with css. Adjust the icons of the tabs to the left and right as seen in the example.
Like I said above, suggestions on how to achieve this in a different way warmly welcome, but haven't encountered any issues yet (knock on wood).
Here's what it looks like:
And here's the bunny:
import React from 'react';
import { createMaterialBottomTabNavigator } from '#react-navigation/material-bottom-tabs';
import { Ionicons, MaterialIcons, FontAwesome } from '#expo/vector-icons';
import AddButton from '../../components/UI/AddButton';
import SpotlightProductsScreen from './SpotlightProductsScreen';
import ProductsScreen from './ProductsScreen';
import UserSpotlightScreen from './../user/UserSpotlightScreen';
import UserProductsScreen from './../user/UserProductsScreen';
const ProductsOverviewScreen = props => {
const Tab = createMaterialBottomTabNavigator();
return (
<>
<AddButton
navigation={props.navigation}
style={{
position: 'absolute',
zIndex: 99,
bottom: 5,
alignSelf: 'center',
shadowColor: 'black',
shadowOpacity: 0.15,
shadowOffset: { width: 0, height: 2 },
shadowRadius: 8,
elevation: 3 //Because shadow only work on iOS, elevation is same thing but for android.
}}
/>
<Tab.Navigator
initialRouteName="Spotlight"
labeled={false}
shifting={true}
activeColor="#f0edf6"
inactiveColor="#3e2465"
barStyle={{ backgroundColor: 'rgba(127,63,191,.9)' }}
>
<Tab.Screen
name="Spotlight"
component={SpotlightProductsScreen}
options={{
tabBarIcon: ({ color }) => (
<Ionicons
name={
Platform.OS === 'android'
? 'md-notifications'
: 'ios-notifications'
}
color={color}
size={27}
style={{
marginLeft: -35
}}
/>
)
}}
/>
<Tab.Screen
name="Förråd"
component={ProductsScreen}
options={{
tabBarIcon: ({ color }) => (
<MaterialIcons
name={'file-download'}
color={color}
size={27}
style={{
marginLeft: -70
}}
/>
)
}}
/>
<Tab.Screen
name="Mitt Förråd"
component={UserProductsScreen}
options={{
tabBarIcon: ({ color }) => (
<MaterialIcons
name={'file-upload'}
color={color}
size={30}
style={{
marginRight: -70
}}
/>
)
}}
/>
<Tab.Screen
name="Min Sida"
component={UserSpotlightScreen}
options={{
tabBarBadge: 4,
tabBarIcon: ({ color }) => (
<FontAwesome
name={'user'}
color={color}
size={30}
style={{
marginRight: -35
}}
/>
)
}}
/>
</Tab.Navigator>
</>
);
};
export default ProductsOverviewScreen;
You can try this:
<Tab.Screen
name = "Button"
component={ScanStack}
options={{
tabBarButton:()=>
<View style={{position:'relative',bottom:35,alignItems:'center', justifyContent:'space-around',height:85}}>
<Icon
name="barcode-scan"
type = "material-community"
reverse
color={'yellow'}
reverseColor='black'
containerStyle={{padding:0,margin:0,elevation:5}}
onPress={()=>console.log('Hi')}
size={30}/>
<Text>Scan</Text>
</View>
}}/>
In the component it is necessary to use a valid react-component, I tried to use component={()=>null} but a warn appeared in the console
Result
I had the same problem, I needed to add a custom component, not related with a screen, to the tab navigator and everything I tried it failed. In my case I was trying with createMaterialTopTabNavigator.
Documentation of React Navigation 5 is a little rough and there isn't a lot of examples of React Navigation 5, but after many attempts I could create a custom component and style it myself so I can mix the tabs created by the routes and the custom buttons embedded in the tab navigator.
import * as React from 'react';
import { View } from 'react-native'
import {
NavigationHelpersContext,
useNavigationBuilder,
TabRouter,
TabActions,
createNavigatorFactory,
} from '#react-navigation/native';
import styled from 'styled-components'
import Colors from '../constants/Colors';
const customTabNavigator = ({
initialRouteName,
children,
screenOptions,
tabContainerStyle,
contentStyle,
leftIcon,
rightIcon
}) => {
const { state, navigation, descriptors } = useNavigationBuilder(TabRouter, {
children,
screenOptions,
initialRouteName,
});
return (
<NavigationHelpersContext.Provider value={navigation}>
<OuterWrapper style={tabContainerStyle}>
{ leftIcon }
<TabWrapper>
{state.routes.map((route, i) => {
return (
<Tab
key={route.key}
onPress={() => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!event.defaultPrevented) {
navigation.dispatch({
...TabActions.jumpTo(route.name),
target: state.key,
});
}
}}
style={descriptors[route.key].options.tabStyle}
>
{ descriptors[route.key].options.label ?? <Label active={state.index === i}>{descriptors[route.key].options.title || route.name}</Label> }
</Tab>
)
})}
</TabWrapper>
{ rightIcon }
</OuterWrapper>
<View style={[{ flex: 1 }, contentStyle]}>
{descriptors[state.routes[state.index].key].render()}
</View>
</NavigationHelpersContext.Provider>
);
}
const OuterWrapper = styled.View`
height: 55px;
flex-direction: row;
justify-content: space-between;
background-color: ${Colors.grey1};
`
const TabWrapper = styled.View`
flex: 1;
flex-direction: row;
justify-content: space-evenly;
`
const Tab = styled.TouchableOpacity`
padding: 0 24px;
justify-content: center;
height: 100%;
`
const Label = styled.Text`
font-family: Futura-Medium;
font-size: 26px;
color: ${({ active }) => active ? Colors.grey6 : Colors.grey3};
`
export default createNavigatorFactory(customTabNavigator)
import customTabNavigator from './customTabNavigator'
import * as React from 'react';
import { View, Image } from 'react-native'
import {
ProjectsScreen,
RenderScreen,
EventsScreen,
CameraScreen
} from '../screens';
import Colors from '../constants/Colors'
import logo from '../assets/images/icon.png'
import { Ionicons } from '#expo/vector-icons';
import { TouchableOpacity } from 'react-native-gesture-handler';
const TopTab = customTabNavigator();
const INITIAL_ROUTE_NAME = 'Home';
export default function MainNavigator({ navigation, route }) {
navigation.setOptions({ headerTitle: getHeaderTitle(route) });
return (
<TopTab.Navigator
initialRouteName={INITIAL_ROUTE_NAME}
leftIcon={(
<TouchableOpacity style={{ height: "100%", justifyContent: "center" }} onPress={() => alert("Whatever")}>
<Image source={logo} style={{ resizeMode: "center", width: 70, height: 40 }} />
</TouchableOpacity>
)}
>
<TopTab.Screen
name="Home"
component={ProjectsScreen}
options={{
title: 'Proyectos',
}}
/>
<TopTab.Screen
name="Preview"
component={EventsScreen}
options={{
title: 'Eventos',
}}
/>
<TopTab.Screen
name="Render"
component={RenderScreen}
options={{
title: 'Mi cuenta',
}}
/>
<TopTab.Screen
name="Camera"
component={CameraScreen}
options={{
title: "Camera",
label: (
<View style={{ width: 36, height: 32, backgroundColor: Colors.grey3, borderRadius: 3, alignItems: "center", justifyContent: "center" }}>
<Ionicons name="md-camera" style={{ color: Colors.grey5 }} size={25} />
</View>
),
tabStyle: { flexDirection: "row", alignItems: "center", justifyContent: "flex-end", flex: 1 }
}}
/>
</TopTab.Navigator>
);
}
function getHeaderTitle(route) {
const routeName = route.state?.routes[route.state.index]?.name ?? INITIAL_ROUTE_NAME;
switch (routeName) {
case 'Home':
return 'Montar vídeo';
case 'Preview':
return 'Previsualizar vídeo';
case 'Render':
return 'Renderizar';
case 'Gallery':
return 'Galería'
case 'Camera':
return 'Camera'
}
}
To the example of https://reactnavigation.org/docs/custom-navigators I added a different styling and two new props, leftIcon and rightIcon. This props receive a component for render it to the corresponding side of the tab wrapper. And this components can be TouchableWhatevers with a custom onPress not related with a screen :P
I hope it helps, I almost throw myself through the window until I made it work, haha
step-1:- In bottomTabNav.js to component-prop give a screen which returns nothing
import React from 'react'
const AddMoreScreen = () => {
return null
}
export default AddMoreScreen
//I created AddMoreScreen.js component
step-2:- As mentioned in step-1, I am giving a screen that renders nothing to component prop, later in options prop I will tap tabBarButton object & returns a custom button to it
...
<Tab.Screen
name={"Add"}
component={AddMoreScreen}
options={{
tabBarButton: ()=> <AddMoreModal />
}}
/>
...
step-3:- and finally our center button code, in my case, if I press button a modal has to appear from the bottom, just change the below codes for your requirements. AddMoreModal.js
export default function AddMoreModal() {
const [modalVisible, setModalVisible] = useState(false);
return (
<View style={{ marginTop: 15, marginLeft: 10, marginRight: 10, }}>
<TouchableOpacity
onPress={() => {
setModalVisible(true);
}}
>
<View style={styles.buttonStyle}>
<Image
source={icons.quickAddOutlined}
style={styles.bottomTabImage}
/>
<Text style={styles.bottomTabText}>Add</Text>
</View>
</TouchableOpacity>
<View style={styles.container}>
<Modal
backdropOpacity={0.3}
isVisible={modalVisible}
onBackdropPress={() => setModalVisible(false)}
style={styles.contentView}
>
<View style={styles.content}>
<Text style={styles.contentTitle}>Hi 👋!</Text>
<Text>Welcome to CRAZY MIDDLE BUTTON! forgotten by react-native navigation</Text>
</View>
</Modal>
</View>
</View>
);
}
const styles = StyleSheet.create({
content: {
backgroundColor: "white",
padding: 22,
justifyContent: "center",
alignItems: "center",
borderTopRightRadius: 17,
borderTopLeftRadius: 17,
},
contentTitle: {
fontSize: 20,
marginBottom: 12,
},
contentView: {
justifyContent: "flex-end",
margin: 0,
},
buttonStyle: {
marginBottom: 0,
alignItems:'center',
justifyContent:'center'
},
bottomTabImage: {
width: 25,
height: 25,
},
bottomTabText: {
marginTop: 4,
fontSize: FONTS.body3.fontSize,
fontFamily: FONTS.body3.fontFamily,
color: COLORS.fontGray90,
},
});
step-4:-