Modify card style when closing a React Navigation stack card - react-native

My goal is to reproduce a stack card style that exists in the ios Spotify app in my react native application.
In Spotify, the currently playing song screen is a full screen modal:
When closing down the modal, the style of the card is modified to show a border top radius:
I am using React Navigation. For now I only achieved to style the card permanently with a border radius using the React Navigation 'options' prop to style the card. My goal is to show the border radius only while the stack card is getting closed down.
<Stack.Screen
name="ScreenName"
component={ScreenComponentName}
options={{
cardStyle: {
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
},
}}
/>
Any idea how I could modify the card style to add border radius only when the card is getting closed?

You may need to provide a header in your Screen, rather than using the header provided by the Navigator. Then, you can set the borderRadius on your header View.
Your Navigator:
<Stack.Screen
name="MyScreen"
component={MyScreen}
options={{
headerShown: false,
}}
/>
Your Screen:
function MyScreen() {
const insets = useSafeAreaInsets();
return (
<>
<View
style={[
styles.header,
{ borderTopLeftRadius: 38, borderTopRightRadius: 38 },
{ paddingTop: insets.top },
]}
>
<TouchableOpacity>
<Icon source={require('../assets/images/my-icon.png')}/>
</TouchableOpacity>
<TouchableOpacity>
<Icon source={require('../assets/images/my-icon.png')}/>
</TouchableOpacity>
</View>
<View style={styles.body}>
<Text>Foo</Text>
</View>
</>
)
}
const styles = StyleSheet.create({
header: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
paddingVertical: 20,
},
body: {
flex: 1,
}
});

Related

Can't align 2 views vertically and horizontally in React-Native

I have 2 Views One view on the left that have 2 items aligned next to each other, and the other is on the right like this, and on IOS it's worse
The problem as you can see here the name is not align to the arrow, I can't figure what the reason is. Here is my code:
<View style={styles.header}>
<View style={styles.leftHeader}>
<TouchableOpacity onPress={() => navigation.goBack()}>
<ArrowLeft />
</TouchableOpacity>
<View>
<Text style={styles.username}>{user && user.username}</Text>
</View>
<TouchableOpacity
onPress={() => setMoreModalVisible(true)}
style={styles.twoDots}>
<TwoDots />
</TouchableOpacity>
</View>
and here is the styling:
header: {
paddingHorizontal: wp(3),
flexDirection: 'row',
marginTop: hp(2.5),
marginBottom: hp(1),
justifyContent: 'space-between',
alignItems: 'center',
},
leftHeader: {
flexDirection: 'row',
justifyContent: 'flex-start',
textAlignVertical: 'center',
},
username: {
fontFamily: semiBoldFont,
fontSize: 15,
paddingLeft: wp(3),
},
Check the style of <ArrowLeft /> component. There might be some padding or margin applied there.
Or provide us with a snack for you code so we can be able to help you.
You can't change the height of the header because the headerStyle prop doesn't have a height prop.
You can follow the docs reference on this link:
https://reactnavigation.org/docs/headers#adjusting-header-styles
Another way to modify the header style you can set the headerShown prop to false and build your own header in the component where you can define the view height of your custom header, but be carefully because if you turn of the header in react-navigation then you have to use the safeareaview from react-native-safe-area-context because your screen will be slip up. On Android you don't take care about this.
A little code snipett for the example:
<Stack.Screen
name="example"
component={Component}
options={{headerShown: false}}
/>
function Component() {
const headerHeight = Platform.OS === 'ios' ? 44 : 20
return (
<SafeAreaView edges={["top"]}>
<View style={{height: headerHeight}}>
// your custom header content
</View>
</SafeAreaView>
)
}

tab bar icons not perfectly centered when screen changes

I have a issue with my costume tab bar and that is when I select other screens icons lose their position a little bit and shift to for example right. for better understanding consider these two images:
you can see this compass icon is not centered perfectly:
but when I tap on screen this become perfectly centered:
here's my styles on custom bottom tab bar :
const TabComponent = ({label, accessibilityState, onPress}) => {
const focused = accessibilityState.selected;
const icon = focused ? ICONS[`${label}Active`] : ICONS[label]
return (
<TouchableWithoutFeedback onPress={onPress}>
<View focused={focused} style={[styles.container, focused ? styles.focused : null]}>
<Image style={styles.icon} source={icon} />
</View>
</TouchableWithoutFeedback>
)
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
marginHorizontal: 20
},
focused: {
backgroundColor: '#3170FF',
borderRadius: 25,
width: 46,
height: 75,
position: 'relative',
bottom: 20
},
icon: {
width: 24,
height: 24,
}
})
and below is my styles on bottom tab itself:
<Tab.Navigator tabBarOptions={{
style: {
borderTopLeftRadius: 23,
borderTopRightRadius: 23,
height: 64,
alignItems: 'center',
}
}} >
<Tab.Screen name="Home" component={Home} options={{
tabBarButton: (props) => <TabComponent label='home' {...props} />,
}}/>
...
how can I fix this issue? any help would be great.

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:-

How to add clients logo on bottom Tab Bar

i am creating an android based mobile app using react native. I want to add a logo on my bottom tab. The tab bar also contains 4 tabs. I need to display a logo on left corner of my device. Logo is not a clickable one, just an image.
For React Navigation 5.
You can show custom BottomTabBar with tabBar options.
Define Tab stack
import {createBottomTabNavigator} from '#react-navigation/bottom-tabs';
const Tab = createBottomTabNavigator();
function MyTabs() {
return (
<Tab.Navigator tabBar={props => <BottomTabBar {...props} />}>
<Tab.Screen name="Tab1" component={Screen3} />
<Tab.Screen name="Tab2" component={Screen2} />
<Tab.Screen name="Tab3" component={Screen2} />
<Tab.Screen name="Tab4" component={Screen2} />
</Tab.Navigator>
);
}
Create Custom BottomTabBar
import React from 'react';
import {View,Text,Image,Platform,TouchableOpacity,StyleSheet} from 'react-native';
const BottomTabBar = ({state, navigation, ...props}) => {
const {routes = [], index: activeIndex} = state;
return (
<View style={styles.container}>
<Image
source={require('../../assets/logo.png')}
style={styles.imageIcon}
/>
<View style={styles.tabContainer}>
{routes.map((it, index) => {
return (
<TouchableOpacity
onPress={() => {
navigation.jumpTo(it.name);
}}
style={[
styles.tabButton,
{
borderBottomWidth: activeIndex === index ? 1 : 0,
},
]}>
<Text>{it.name}</Text>
</TouchableOpacity>
);
})}
</View>
</View>
);
};
export default BottomTabBar;
const styles = StyleSheet.create({
tabButton: {
flex: 1,
height: 50,
justifyContent: 'center',
alignItems: 'center',
borderBottomColor: 'red',
},
tabContainer: {
flex: 1,
flexDirection: 'row',
justifyContent: 'space-evenly',
},
imageIcon: {
width: 50,
height: 50,
resizeMode: 'contain',
},
container: {
width: '100%',
flexDirection: 'row',
alignItems: 'center',
height: 64,
paddingHorizontal: 16,
backgroundColor: 'white',
paddingBottom: Platform.OS === 'ios' ? 15 : 0,
},
});
ScreenShot

How to make header bar for each TopTabs in react navigation V3

I am using React Navigation V3 for my project and I want a separate header bar for each screen of toptabs, not a common header bar for every screen of top tab.
I tried to make it but Header bar is coming under the toptabs. Actually my project requirement is to have different buttons and title on every screen of top tabs.
I have made a example on expo please review and give a solution.
https://snack.expo.io/#mudabbir/navigation-example
You can customize you header on each screen by using static navigationOptions like below (only class component)
static navigationOptions = {
title: 'Home',
headerTintColor: '#5F55B2',
headerStyle: { height: 60 },
};
or
static navigationOptions = ({ navigation }) => {
return {
headerTitle: (
<View style={{ alignItems: 'center' }}>
<Text style={{ fontSize: 18 }}>Home</Text >
<Text>header</Text>
</View >),
headerRight: (
<View style={{ flexDirection: 'row' }}>
<Button transparent>
<Icon name='ios-search' />
</Button>
<Button transparent>
<Icon name='ios-calendar' />
</Button>
</View>
),
headerStyle: { backgroundColor: 'green' }
}
}