Responsive React-native-web layout - react-native

I'm trying to create a dashboard layout from a react-native project. The idea is to keep most of the code similar for Android, iOS and Web only the layout or navigation style will change. But I find making this type of layout in web is easy but to make it responsive without re-rendering is difficult.
I have achieved this by manually calculating the windows heigh and width by following the code
Dimensions.get('window').width
Dimensions.get('window').height
and by eventListener keep updating the state so that it re-renders the whole page again and again.
Dimensions.addEventListener("change", this.updateScreen);
Is there a way I can simply use some % value to fill up the screen. Right now if I use % it squeezed to a child View size. I even tried flex:1 with alignSelf:stretch, alignItem:stretch, width:'100%' etc but no luck.
For a while, let's talk about center row (image attached) it contain 3 columns. I want left and right block (Menu & Call to Action) to be 300px each. Now if I'm on 1000px width monitor my Content block should be (1000 - (300+300)) 400px. if monitor is 1200px then Content block will be (1200 - (300+300)) 600px.

I hope this doesn't come too late.
<View style={{ flex: 1 }}>
<View style={{ height: 100, backgroundColor: 'red' }} />
<View style={{ flex: 1, backgroundColor: 'gray', flexDirection: 'row' }}>
<View style={{ width: 100, backgroundColor: 'green' }} />
<View style={{ flex: 1, backgroundColor: 'blue' }} />
<View style={{ width: 100, backgroundColor: 'green' }} />
</View>
<View style={{ height: 100, backgroundColor: 'red' }} />
</View>
This is the result from the above code.
You don't need to do percentage calculations at all; just structure it in 2 layers of flex layout.
For the components that should not stretch, state their width. For the rest, specify the flex value.
If you insist on using 1 layer to handle it all, then we shall discuss again.
Cheers.

I made this exactly for the same reasons:
https://www.npmjs.com/package/react-native-animated-layout
Demos:
Expo snack: https://snack.expo.dev/#mehmetkaplan/react-native-animated-layout
Web demo: https://mehmetkaplan.github.io/react-native-animated-layout/
It takes layouts as inputs and using the Width / Height ratio of the screen, decides which layout to use.
And within each layout, you define top, right, bottom, left of each screen using the coordinates between 0 and 1. (Because browser resizing changes the coordinates of each View and you want views to stay exact relative places.)
import React, { useState, useEffect } from 'react';
import { Text, View } from 'react-native';
import ReactNativeAnimatedLayout from 'react-native-animated-layout';
export default function App() {
const [rerender, setRerender] = useState(0);
const redView = <View style={{ backgroundColor: 'red', height: '100%', width: '100%', }}><Text>{"I am the red view"}</Text></View>;
const greenView = <View style={{ backgroundColor: 'green', height: '100%', width: '100%', overflow: 'hidden'}}>
<Text>{"I am the green view and my contents overflow. Because of the overflow prop, the over-flown content is hidden."}</Text><Text>{"0"}</Text><Text>{"1"}</Text><Text>{"2"}</Text><Text>{"3"}</Text><Text>{"4"}</Text><Text>{"5"}</Text><Text>{"6"}</Text><Text>{"7"}</Text><Text>{"8"}</Text><Text>{"9"}</Text><Text>{"10"}</Text><Text>{"11"}</Text><Text>{"12"}</Text><Text>{"13"}</Text><Text>{"14"}</Text><Text>{"15"}</Text><Text>{"16"}</Text><Text>{"17"}</Text><Text>{"18"}</Text><Text>{"19"}</Text><Text>{"20"}</Text><Text>{"21"}</Text><Text>{"22"}</Text><Text>{"23"}</Text><Text>{"24"}</Text><Text>{"25"}</Text><Text>{"26"}</Text><Text>{"27"}</Text><Text>{"28"}</Text><Text>{"29"}</Text>
</View>;
const blueView = <View style={{ backgroundColor: 'blue', height: '100%', width: '100%', }}><Text>{"I am the blue view"}</Text></View>;
const layouts = [
{
validAfterWHRatio: 0,
validBeforeWHRatio: 0.9,
views: [
{ top: 0, bottom: 0.5, left: 0, right: 1, children: redView },
{ top: 0.75, bottom: 1, left: 0, right: 1, children: blueView },
{ top: 0.5, bottom: 0.75, left: 0, right: 1, children: greenView },
]
},
{
validAfterWHRatio: 1 / 0.62,
validBeforeWHRatio: 999, // infinity
views: [
{ top: 0, bottom: 1, left: 0, right: 0.5, children: redView },
{ top: 0.5, bottom: 1, left: 0.5, right: 1, children: blueView },
{ top: 0, bottom: 0.5, left: 0.5, right: 1, children: greenView },
]
},
{
defaultFlag: true,
views: [
{ top: 0.16, bottom: 0.84, left: 0.16, right: 0.5, children: redView },
{ top: 0.50, bottom: 0.84, left: 0.5, right: 0.84, children: blueView },
{ top: 0.16, bottom: 0.50, left: 0.5, right: 0.84, children: greenView },
]
},
];
useEffect(() => {
let nextRerender = rerender + 1;
setRerender(nextRerender);
}, []); // when we want to re-render
return <ReactNativeAnimatedLayout
layouts={layouts}
rerender={rerender}
/>;
}
Note: This approach may be useful especially for PWAs. If you need to use scroll bars, they should be inside one of the views, probably "the" content view.

Related

How to use BottomSheet inside model

I want to add bottom sheet in Modal. I am using reanimated-bottom-sheet and react native modal.
But when I use BottomSheet inside Modal gesture not work and can't closeBottomSheet with swiping down.
I searched and I found this is RCGH problem.
So I decide to use View instead of Modal.
but View doesn't cover Header.
This is bottomSheet code:
<View style={BottomSheetStyles.container}>
<TouchableWithoutFeedback onPress={handleOutsidePress}>
<Animated.View style={[BottomSheetStyles.dropShadow, { opacity }]} />
</TouchableWithoutFeedback>
<Animated.Code exec={onChange(position, [cond(eq(position, 1), call([], onClose))])} />
<BottomSheet
ref={sheet}
initialSnap={zeroIndex}
snapPoints={snapPoints}
callbackNode={position}
renderHeader={() => <View style={BottomSheetStyles.header} />}
renderContent={() => (
<View style={[BottomSheetStyles.content, { height: "100%" }]}>{children}</View>
)}
/>
</View>
and this is styles:
const BottomSheetStyles = StyleSheet.create({
container: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
flex: 1,
zIndex: 100,
},
dropShadow: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
flex: 1,
zIndex: 100,
backgroundColor: "rgba(0, 0, 0, 0.3)",
},
header: {
backgroundColor: Colors.greyC4C,
width: 90,
height: 7,
margin: 5,
alignSelf: "center",
borderRadius: 11,
},
content: {
backgroundColor: Colors.whiteFFF,
paddingTop: 18,
borderTopLeftRadius: 22,
borderTopRightRadius: 22,
},
});
So problem can be solve either whit solving gesture in Modal or do something to cover Header with View.
I am using React Navigation.

react-native trasparent overlay exposing some component

how can I create a transparent overlay exposing some component like buttons, icons? So that it can be used as a tutorial screen.
I want to achieve something like shown in the image.
You need a modal overlay. To achieve this, you must add a <View> with an opacity style over your current component. In that <View>, you must add your own custom images (with the arrows for example) and position them as you want.
To add an opacity, you can add a style with the opacity property, like this:
overlay: {
opacity: 0.5,
}
You can set the value of the opacity as you like (between 0 and 1).
If you want it to be as big as the screen and to be positioned over your component, you can add something like this:
overlay: {
position: 'absolute',
height: '100%',
width: '100%',
backgroundColor: '#fff',
opacity: 0.5,
}
or:
overlay: {
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0,
backgroundColor: '#fff',
opacity: 0.5,
}
Now, for the holes in the overlay, that can be achieved by properly using the z-index property. As long as a component has a higher value of z-index, it will be on top of a component that has a lower value.
To assign the z-index to a style, use it like this:
onTop: {
zIndex: 2
}
below: {
zIndex: 1
}
Complete example
This is a working example of a modal overlay, where the text This is always on top, is always on top:
import React, { Component } from 'react';
import { Image, StyleSheet, View, Text } from 'react-native';
export default class App extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.mainText}>This is always on top</Text>
<View style={styles.overlay}>
<Text>This is an overlay</Text>
<Text>This is an overlay</Text>
<Text>This is an overlay</Text>
<Text>This is an overlay</Text>
<Text>This is an overlay</Text>
<Text>This is an overlay</Text>
<Text>This is an overlay</Text>
<Text>This is an overlay</Text>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex:1,
backgroundColor: "#00ff00",
top: 0,
right: 0,
bottom: 0,
left: 0,
justifyContent: "center",
alignItems: "center",
zIndex: 1
},
mainText:{
fontSize: 30,
zIndex: 3,
backgroundColor: '#ff0000',
},
overlay: {
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0,
backgroundColor: '#fff',
color: '#000',
opacity: 0.5,
justifyContent: "center",
alignItems: "center",
fontSize: 24,
zIndex: 2
}
});
Apply these ideas to your working app, and you can achieve what you are looking for.

React Native rounded image with a border

I want to create a rounded image with a border. If I add borderColor: 'green', borderWidth:1, border is visible only in top left part of the rounded image.
<TouchableHighlight
style={[styles.profileImgContainer, { borderColor: 'green', borderWidth:1 }]}
>
<Image source={{ uri:"https://www.t-nation.com/system/publishing/articles/10005529/original/6-Reasons-You-Should-Never-Open-a-Gym.png" }} style={styles.profileImg} />
</TouchableHighlight>
export default styles = StyleSheet.create({
profileImgContainer: {
marginLeft: 8,
height: 80,
width: 80,
borderRadius: 40,
},
profileImg: {
height: 80,
width: 80,
borderRadius: 40,
},
});
overflow: 'hidden' for images container solves this issue.
Use the following styling, it's work for me.
image: {
width: 150,
height: 150,
borderRadius: 150 / 2,
overflow: "hidden",
borderWidth: 3,
borderColor: "red"
}
Worth to mention for Android...
I had to specifically set resizeMode="cover" for the borderRadius to take effect.
<Image
style={styles.image}
source={source}
resizeMode={"cover"} // <- needs to be "cover" for borderRadius to take effect on Android
/>
const styles = StyleSheet.create({
image: {
width: 150,
height: 150,
borderColor: 'red,
borderWidth: 2,
borderRadius: 75
},
});
Border width adds up to the size of the component that you added to. This makes your image bigger than the size of your container component. To solve this issue you can add the border width to the component sizes.
Example
const styles = StyleSheet.create({
profileImgContainer: {
marginLeft: 8,
height: 82,
width: 82,
borderRadius: 40,
borderWidth: 1
},
profileImg: {
height: 80,
width: 80,
borderRadius: 40,
},
});
If you want to show the image with rounded corner and with resizeMode={'contain'} to show the full image then wrap the image inside a view and then give border radius to the view. it will work
<View style={styles.carImageHolder}>
<Image
source={{
uri: each?.usercar_details?.car_model?.sized_photo,
}}
resizeMode={'contain'}
style={styles.carImage}
/>
</View>
and the style part
carImage: {
width: '100%',
aspectRatio: 1 / 1,
},
carImageHolder: {
width: '28.3%',
aspectRatio: 1 / 1,
backgroundColor: '#d8d8d8',
borderRadius: 25,
overflow: 'hidden',
},
The answers given here, are nice, but, from my experience, it's better to use percentages of your available screen height, as the width and height dimensions of image. This will help a lot with responsiveness. Do it like this
import RN from 'react-native';
const SCREEN_HEIGHT = RN.Dimensions.get('window').height;
Then apply the following as the dimensions styling, to get a nice, responsive rounded image.
style={[
//... your other previous styles
{
resizeMode: 'cover',
width: SCREEN_HEIGHT * 0.15,
height: SCREEN_HEIGHT * 0.15,
borderRadius: (SCREEN_HEIGHT * 0.15)/2,
}
]}
The (*0.15 i.e 15% of screen height) is just my choice of sample dimensions, you can use higher or lower, depending on how big you want your image to be.

Curved bottom on View

How can i add the curved bottom to a View in react-native? See curved example
I'f tried to a add an second view like this:
headerBottom: {
width: width / 2,
height: width / 2,
backgroundColor: 'red',
position: 'absolute',
bottom: -35,
left: width / 4 - 15,
borderRadius: width / 4,
transform: [
{scaleX: 2},
{scaleY: 0.25}
]
},
I've been able to get above but rather have a less bad solution right in the same view, not as in the example in a second view.
Here is the result. I used Dimensions (const window = Dimensions.get('window');) here to make it more dynamic to different screen sizes.
const styles = StyleSheet.create({
containerStyle: {
alignSelf: 'center',
width: window.width,
overflow: 'hidden',
height: window.width / 1.7
},
sliderContainerStyle: {
borderRadius: window.width,
width: window.width * 2,
height: window.width * 2,
marginLeft: -(window.width / 2),
position: 'absolute',
bottom: 0,
overflow: 'hidden'
},
slider: {
height: window.width / 1.7,
width: window.width,
position: 'absolute',
bottom: 0,
marginLeft: window.width / 2,
backgroundColor: '#9DD6EB'
}});
render() {
return(
<View style={styles.containerStyle} >
<View style={styles.sliderContainerStyle} >
<Slider/>
</View>
</View>
);
}
I ended up with using react-native-svg.:
<Circle
cx={screenWidth / 2}
cy={`-${898 - headerHeight + 2}`}
r="898.5"
fill="#EFF2F3"
stroke="#C5CACD"
strokeWidth="2"
/>
I don't know whether this is a proper way or not. However this works for me and hope this will help someone.
<View style={styles.parent}>
<View style={styles.child}>
<Text>Hello World</Text>
</View>
</View>
Insert your code to child View
const styles = StyleSheet.create({
parent : {
height : '80%',
width : '100%',
transform : [ { scaleX : 2 } ],
borderBottomStartRadius : 200,
borderBottomEndRadius : 200,
overflow : 'hidden',
},
child : {
flex : 1,
transform : [ { scaleX : 0.5 } ],
backgroundColor : 'yellow',
alignItems : 'center',
justifyContent : 'center'
}
})
i use border bottom radius. this is work.
class Home extends Component {
render() {
return (
<View style={styles.oval} />
)
}
}
const styles= StyleSheet.create({
oval: {
height: 100,
borderBottomLeftRadius: 50,
borderBottomRightRadius: 50,
backgroundColor: 'red'
},
});

React Native: How to set height of the child view same as parent view with position=absolute?

I want to show two children views inside a parent view. Both the children have position: "absolute" in style, as I want to apply some animations on the position of the views. I want to set the height of the views to be equal to the parent view. How can I achieve this in React native?
To have your children views that use absolute positions the same height as the parent, you can set their top and bottom position to 0.
Then you can still apply a margin or transform to further change their position.
var SampleApp = React.createClass({
render: function() {
return (
<View style={styles.container}>
<View style={styles.child1}/>
<View style={styles.child2}/>
</View>
);
}
});
var styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
child1: {
position: 'absolute',
backgroundColor: 'green',
right: 0,
top: 0,
bottom: 0,
width: 40,
},
child2: {
position: 'absolute',
backgroundColor: 'blue',
left: 0,
top: 0,
bottom: 0,
width: 40,
},
});
See it live: https://rnplay.org/apps/kSZnBg