I use the react-native-orientation-locker package and I would like to change the style when I rotate the smartphone
I use the state to initialize my style, but this solution I don't think is optimal
here is an example of my code
const [styles, setStyles] = useState(Styles(theme, layout))
const changeStyles = (o: OrientationType): void => {
layout = getDeviceVideoLayout(o) // return 'PORTRAIT or 'LANDSCAPE-LEFT' or 'LANDSCAPE-RIGHT'
// change the rotation of the phone
const { height, width } = Dimensions.get('window')
//I update width and height
theme.layout.width = width
theme.layout.height = height
// I update the status
setStyles(Styles(theme, layout))
}
useDeviceOrientationChange((o) => {
changeStyles(o)
})
I don't like this solution of the style in the state
an example of my style
const StyleCommon = (theme: Theme, bottomSpace: number): StyleGeneral => StyleSheet.create({
container: {
height: theme.layout.height,
width: '100%',
backgroundColor: theme.colors.black
},
actionButtonContainer: {
width: 50,
backgroundColor: 'rgba(242, 241, 240, 0.3)',
borderRadius: 12,
marginBottom: 20
}
})
const StyleLayoutPortrait = (theme: Theme, bottomSpace: number): StyleLayout => StyleSheet.create({
actionContainer: {
position: 'absolute',
bottom: theme.spacing.sm + theme.layout.insets.bottom + 40 + bottomSpace,
right: theme.spacing.sm
}
})
const StyleLayoutLandscapeLeft = (theme: Theme): StyleLayout => StyleSheet.create({
actionContainer: {
position: 'absolute',
top: theme.spacing.sm,
right: theme.spacing.sm
}
})
const StyleLayoutLandscapeRight = (theme: Theme): StyleLayout => StyleSheet.create({
actionContainer: {
position: 'absolute',
right: theme.spacing.sm + theme.layout.insets.top,
top: theme.spacing.sm
}
})
const Styles = (theme: Theme, layout: VideoLayoutType, bottomSpace: number): Style => {
if (layout === 'LANDSCAPE-RIGHT') {
return ({
...StyleCommon(theme, bottomSpace),
...StyleLayoutLandscapeRight(theme)
})
} else if (layout === 'LANDSCAPE-LEFT') {
return ({
...StyleCommon(theme, bottomSpace),
...StyleLayoutLandscapeLeft(theme)
})
} else {
return ({
...StyleCommon(theme, bottomSpace),
...StyleLayoutPortrait(theme, bottomSpace)
})
}
}
I do not think this solution is optimal, how could I do?
indicatorContainerProps:{
position: 'fixed',
right: '0px',
bottom: '0%',
marginBottom: '40%',
display:'flex',
justifyContent:'center',
flexDirection:'row',
alignItems:'center',
"#media (orientation:landscape)": {
marginBottom: '0px'
}
This example is for Jsx or ReactJs.
You just need to specify the some class name like text
text:{
position: 'fixed',
right: '0px',
bottom: '0%',
marginBottom: '40%',
display:'flex',
justifyContent:'center',
flexDirection:'row',
alignItems:'center',
"#media (orientation:landscape)": { //here you can change based on orientation
marginBottom: '0px'
}
Related
Hello i have issue with range slider it goes out of bounds when I move the thumbs.
I spend a lot time to find what is going wrong but i can't find solution.
unexpected result:
issue result
const RangeSlider = ({ sliderWidth, min, max, step }: RangeSliderProps) => {
const position = useSharedValue(0);
const position2 = useSharedValue(sliderWidth);
const zIndex = useSharedValue(0);
const zIndex2 = useSharedValue(0);
const gestureHandler = useAnimatedGestureHandler<
PanGestureHandlerGestureEvent,
{ x: number; y: number }
>({
onStart: (_, ctx) => {
ctx.x = position.value;
},
onActive: (e, ctx) => {
if (ctx.x + e.translationX < 0) {
position.value = 0;
} else if (ctx.x + e.translationX > position2.value) {
position.value = position2.value;
zIndex.value = 1;
zIndex2.value = 0;
} else {
position.value = ctx.x + e.translationX
}
},
onEnd: () => {
},
});
const gestureHandler2 = useAnimatedGestureHandler<
PanGestureHandlerGestureEvent,
{ x: number; y: number }
>({
onStart: (_, ctx) => {
ctx.x = position2.value;
},
onActive: (e, ctx) => {
if (ctx.x + e.translationX > sliderWidth) {
position2.value = sliderWidth;
} else if (ctx.x + e.translationX < position.value) {
position2.value = position.value;
zIndex.value = 0;
zIndex2.value = 1;
} else {
position2.value = ctx.x + e.translationX;
}
},
onEnd: () => {
},
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: position.value }],
zIndex: zIndex.value,
}));
const animatedStyle2 = useAnimatedStyle(() => ({
transform: [{ translateX: position2.value }],
zIndex: zIndex2.value,
}));
const minLabelText: any = useAnimatedProps(() => {
return {
text: `${min +
Math.floor(position.value / (sliderWidth / ((max - min) / step))) * step
}`,
};
});
const maxLabelText: any = useAnimatedProps(() => ({
text: `${min +
Math.floor(position2.value / (sliderWidth / ((max - min) / step))) *
step
}`,
}));
const sliderStyle2 = useAnimatedStyle(() => {
return {
width: position2.value - position.value,
transform: [{ translateX: position.value }]
}
});
return (
<View style={[styles.sliderContainer, { width: sliderWidth }]} >
<Animated.View style={[styles.label]}>
<AnimatedTextInput
style={styles.labelText}
editable={false}
animatedProps={minLabelText}
/>
</Animated.View>
<View style={[styles.sliderBack, { width: sliderWidth }]} />
<Animated.View style={[styles.sliderFront, sliderStyle2]} />
<PanGestureHandler onGestureEvent={gestureHandler}>
<Animated.View style={[animatedStyle, styles.thumb]} />
</PanGestureHandler>
<PanGestureHandler onGestureEvent={gestureHandler2} >
<Animated.View style={[animatedStyle2, styles.thumb]} >
</Animated.View>
</PanGestureHandler>
<Animated.View style={[styles.label]}>
<AnimatedTextInput
style={styles.labelText}
editable={false}
animatedProps={maxLabelText}
/>
</Animated.View>
</View>
);
};
const styles = StyleSheet.create({
sliderContainer: {
justifyContent: 'center',
alignSelf: 'center',
},
sliderBack: {
height: 4,
backgroundColor: '#444444',
borderRadius: 20,
},
sliderFront: {
height: 4,
backgroundColor: '#0088D1',
borderRadius: 20,
position: 'absolute',
},
thumb: {
left: -10,
width: 13,
height: 13,
position: 'absolute',
backgroundColor: '#0088D1',
borderColor: 'white',
borderWidth: 2,
borderRadius: 10,
},
label: {
width: 35,
backgroundColor: "#444444",
borderRadius: 5,
alignSelf: 'center',
justifyContent: 'center',
alignItems: 'center',
marginLeft: 4,
marginRight: 4
},
labelText: {
textAlign: "right",
color: '#EEEEEE',
padding: 5,
fontWeight: '500',
lineHeight: 14,
fontSize: 12,
width: '100%',
},
});
export default RangeSlider;
possible issue
const sliderStyle2 = useAnimatedStyle(() => {
return {
transform: [{ translateX: position.value }],
width: position2.value - position.value,
}
}, []);
expected result: expected image
Slider not goes out of bounds when I move the slider
overflow: 'hidden' not working as expected.
The text inside my MaterialUI Stepper // StepLabel sometimes wraps over multiple lines.
I need to keep the vertical StepConnectors attached the StepIcons regardless of the number of lines of text in the label.
I've tried other solutions such as using CSS pseudo tags, but I hit a wall every time I try to work those changes into our existing solution.
Massive thanks in advance to anyone who can help.
Sandbox
https://codesandbox.io/s/practical-chebyshev-4hktl?file=/src/App.js
Current Screenshot
Existing ThemeOptions
import {
ThemeOptions,
createTheme,
ThemeProvider,
CssBaseline
} from "#material-ui/core";
export const themeOptions: ThemeOptions = {
overrides: {
MuiStepper: {
root: {
backgroundColor: "transparent" // remove set background
}
},
MuiStepConnector: {
vertical: {
padding: 0,
width: 5,
marginLeft: 8 // half icon
},
lineVertical: {
top: "calc(-50%)",
bottom: "calc(50%)",
borderLeftWidth: "2px",
marginLeft: "-1px", // center (1/2 width)
marginTop: "-6px", // add -ve margin to top and bottom ...
marginBottom: "-6px", // ... to hide gap due to smaller icon
borderColor: "lightgrey",
"$active &, $completed &": {
borderLeftWidth: "4px",
marginLeft: "-2px",
borderColor: "blue"
}
}
},
MuiStepLabel: {
label: {
textAlign: "left",
fontSize: "1.25rem",
"&$active": {
fontWeight: 400
},
"&$completed": {
fontWeight: 400
}
},
iconContainer: {
paddingRight: 12
}
},
MuiStepIcon: {
root: {
display: "block",
fontSize: "1rem",
color: "lightgrey",
"&$completed": {
color: "blue"
},
"&$active": {
color: "blue"
}
}
}
}
};
Just in case anyone finds this in the future, we compromised on the implementation to deliver the task.
Instead of having a variable height on the MuiStepLabel, it was given a fixed height to keep the StepIcons the same distance apart. If you imagine the below screenshot with a different font size + spacing, it ended up looking OK, but not ideal.
Before
// src/Theme/index.tsx
export const themeOptions: ThemeOptions = {
overrides: {
MuiStepConnector: {
marginTop: "-6px",
marginBottom: "-6px",
}
MuiStepLabel: {}
}
}
After
// src/Theme/index.tsx
export const themeOptions: ThemeOptions = {
overrides: {
MuiStepConnector: {
marginTop: "-2px",
marginBottom: "-4px",
minHeight: "calc(24px + 0.5rem)",
},
MuiStepLabel: {
height: "1.25rem",
lineHeight: "1.25rem",
}
}
}
Sandbox
https://codesandbox.io/s/epic-bohr-0p7fj?file=/src/Theme/index.ts
I have an issue with using react testing library and its fireEvent function.
I want to test my component and its style after hovering.
Here it is my component written in tsx:
import React from 'react'
import { makeStyles } from '#material-ui/core'
import Paper, { PaperProps } from '#material-ui/core/Paper'
import Box from '#material-ui/core/Box'
import posed from 'react-pose'
import grey from '#material-ui/core/colors/grey'
import green from '#material-ui/core/colors/green'
import lightBlue from '#material-ui/core/colors/lightBlue'
import teal from '#material-ui/core/colors/teal'
function styleProducer(variant: BigTileProps['variant'] = 'default', color: BigTileProps['color'] = 'default'): Function {
type colrMapType = {
[k: string]: { tile: { [pr: string]: string } }
}
const colorMap: colrMapType = {
default: { tile: {} },
grey: {
tile: { background: grey[700], color: 'white' }
},
green: {
tile: { background: green[300] }
},
teal: {
tile: { background: teal[400], color: 'white' }
},
blue: {
tile: { background: lightBlue[200] }
},
}
interface variantsKeys {
small: (theme: any) => object
default: (theme: any) => object
big: (theme: any) => object
large: (theme: any) => object
}
type variantsType = {
[k in keyof variantsKeys]: variantsKeys[k]
}
const variants: variantsType = {
small: (theme: any) => ({
tile: Object.assign({
minWidth: theme.spacing(10),
height: theme.spacing(10),
background: grey[500],
position: 'relative',
'&:hover': {
cursor: 'pointer'
}
}, colorMap[color].tile),
content: {
fontSize: '2rem',
fontWeight: 'bold'
},
title: {
textTransform: 'uppercase'
},
icon: {
position: 'absolute',
top: 0,
left: 0
}
}),
default: (theme: any) => ({
tile: Object.assign({
minWidth: theme.spacing(15),
height: theme.spacing(15),
position: 'relative',
'&:hover': {
cursor: 'pointer'
}
}, colorMap[color].tile),
content: {
fontSize: '2rem',
fontWeight: 'bold'
},
title: {
textTransform: 'uppercase'
},
icon: {
position: 'absolute',
top: 0,
left: 0
}
}),
big: (theme: any) => ({
tile: Object.assign({
minWidth: theme.spacing(20),
height: theme.spacing(20),
position: 'relative',
'&:hover': {
cursor: 'pointer'
}
}, colorMap[color].tile),
content: {
fontSize: '2rem',
fontWeight: 'bold'
},
title: {
textTransform: 'uppercase'
},
icon: {
position: 'absolute',
top: 0,
left: 0
}
}),
large: (theme: any) => ({
tile: Object.assign({
minWidth: theme.spacing(25),
height: theme.spacing(25),
position: 'relative',
'&:hover': {
cursor: 'pointer'
}
}, colorMap[color].tile),
content: {
fontSize: '2rem',
fontWeight: 'bold'
},
title: {
textTransform: 'uppercase'
},
icon: {
position: 'absolute',
top: 0,
left: 0
}
})
}
return makeStyles(variants[variant])
}
type BigTileProps = {
color?: 'default' | 'grey' | 'green' | 'blue' | 'teal',
variant?: 'small' | 'default' | 'big' | 'large',
width?: string, // 15px , 10rem etc
height?: string, // 15px , 10rem etc
title?: string,
content?: string
icon?: React.FC | React.ReactElement
}
const PosedPaper = posed(Paper)({
onHover: {
scale: 1.05
},
none: {
scale: 1
}
})
const BigTile: React.FC<BigTileProps> = ({
variant = 'default',
color = 'default',
width,
height,
children,
title,
content,
icon,
...props
}) => {
const [hover, setHover] = React.useState(false)
const useStyles = styleProducer(variant, color)
const classes = useStyles()
const onHoverHandle = (bool: boolean) => () => {
setHover(bool)
}
const style = {height, width}
if (!height) delete style['height']
if (!width) delete style['width']
return (
<PosedPaper className={classes.tile} style={{ height, width }} pose={hover ? 'onHover' : 'none'} onMouseEnter={onHoverHandle(true)} onMouseLeave={onHoverHandle(false)}>
<Box className={classes.icon} p={1}>
{icon}
</Box>
<Box height="100%" width="100%" display="flex" justifyContent="center" alignItems="center" flexDirection="column">
<Box mb={1} className={classes.content}>{children ? children : content}</Box>
<div className={classes.title}>{title}</div>
</Box>
</PosedPaper>
)
}
export default BigTile
And here is my test :
it('BigTile hover check', ()=>{
const { container } = render(<BigTile />)
const elem = container.querySelector<HTMLElement>('.MuiPaper-root')
if (!elem) fail("Element is null.")
fireEvent.mouseOver(elem);
const elemAfterHover = container.querySelector<HTMLElement>('.MuiPaper-root')
if (!elemAfterHover) fail("Element after hover is null.")
console.log(elemAfterHover.style)
})
Here i cant see changed style after mouseover event. Console log shows me only transform:none
when it should be something like transform: scale(1.05).
Please help me with firing this event properly and if you have some advise to the code itself it would be great to take some advices.
//Answer
The event was firing every time i started the test.
To check it i just put some console.log(..) inside onHoverHandle which is firing after moseenter/leave.
Of course i didn't need to find element once again after fireEvent, becouse i had this element before. Main idea is that i needed to wait for event to end before checking for style change. So waitFor is good for that.
I read somewhere than using userEvent from '#testing-library/user-event' is better than fireEvent , so i did.
Final solution :
it('BigTile hover check.', async ()=>{
const { container } = render(<BigTile />)
const elem = container.firstChild as HTMLElement
if (!elem) fail("Element is null.")
userEvent.hover(elem);
await waitFor(() => {
expect(elem.style.transform).toEqual("scale(1.05) translateZ(0)")
})
})
The goal of this current app is to have the "POSITIVE" mood prediction link to a sunny day background, and "NEGATIVE" mood prediction link to a moving cloudy day background. Below is the crux of my code. My question is more specifically about the logic. If somebody can explain how state would work in this situation would be the best. How to tie the "css based background in JS" element to the state and how to make a method to handle the changing background and how to return the method. Let me know if the question is unclear.
enter code here
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
theme: "",
};
}
handleBackground = () => {
if (!this.state.mood) {
return startBackground;
} else if (this.state.mood === "NEGATIVE") {
return cloudFromCss;
} else {
return sunFromCss;
}
};
render() {
const isLoading = this.state.isLoading;
const formData = this.state.formData;
const mood = this.state.mood;
const sunFromCss = {
".sun": {
position: "absolute",
margin: "auto",
borderRadius: "50%",
top: "0",
right: "0",
bottom: "80%",
left: "60%",
backgroundColor: "rgb(252, 174, 7)",
height: "50px",
width: "50px",
animationName: "beat",
animationDuration: "3s",
animationIterationCount: "infinite",
transform: "scale(2.5)",
opacity: "0.8",
},
"#keyframes backdiv": { "50%": { background: "#bde6f3" } },
"#keyframes beat": {
"0%": { transform: "scale(2.5) rotate(-45deg)" },
"50%": { transform: "scale(2) rotate(-45deg)" },
},
};
const startBackground = {
"#clouds": { padding: "100px 0", background: "#c9dbe9" },
};
const cloudFromCss = {
"#clouds": { padding: "100px 0", background: "#c9dbe9" },
".cloud": {
width: "200px",
height: "60px",
background: "#fff",
borderRadius: "200px",
animationName: "moveclouds",
animationIterationCount: "infinite",
position: "relative",
},
".cloud:before,\n.cloud:after": {
content: '""',
position: ["absolute", "absolute"],
background: "#fff",
width: "100px",
height: "80px",
top: "-15px",
left: "10px",
borderRadius: "100px",
transform: "rotate(30deg)",
},
".cloud:after": {
width: "120px",
height: "120px",
top: "-55px",
left: "auto",
right: "15px",
},
".x1": { opacity: "0.8", animationDuration: "15s" },
"#keyframes moveclouds": {
"0%": { marginLeft: "1000px" },
"100%": { marginLeft: "-1000px" },
},
};
return (
{() => this.handleBackground()}
)
Goal: create an OptionFan button that when pressed, rotates on its Z axis, and FanItems release from behind the main button and travel along their own respective vectors.
OptionFan.js:
import React, { useState, useEffect } from 'react';
import { Image, View, Animated, StyleSheet, TouchableOpacity, Dimensions } from 'react-native';
import EStyleSheet from 'react-native-extended-stylesheet';
import FanItem from './FanItem';
const { height, width } = Dimensions.get('window');
export default class OptionFan extends React.Component {
constructor (props) {
super(props);
this.state = {
animatedRotate: new Animated.Value(0),
expanded: false
};
}
handlePress = () => {
if (this.state.expanded) {
// button is opened
Animated.spring(this.state.animatedRotate, {
toValue: 0
}).start();
this.refs.option.collapse();
this.setState({ expanded: !this.state.expanded });
} else {
// button is collapsed
Animated.spring(this.state.animatedRotate, {
toValue: 1
}).start();
this.refs.option.expand();
this.setState({ expanded: !this.state.expanded });
}
};
render () {
const animatedRotation = this.state.animatedRotate.interpolate({
inputRange: [ 0, 0.5, 1 ],
outputRange: [ '0deg', '90deg', '180deg' ]
});
return (
<View>
<View style={{ position: 'absolute', left: 2, top: 2 }}>
{this.props.options.map((item, index) => (
<FanItem ref={'option'} icon={item.icon} onPress={item.onPress} index={index} />
))}
</View>
<TouchableOpacity style={styles.container} onPress={() => this.handlePress()}>
<Animated.Image
resizeMode={'contain'}
source={require('./src/assets/img/arrow-up.png')}
style={{ transform: [ { rotateZ: animatedRotation } ], ...styles.icon }}
/>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center',
borderRadius: 30,
backgroundColor: '#E06363',
elevation: 15,
shadowOffset: {
height: 3,
width: 3
},
shadowColor: '#333',
shadowOpacity: 0.5,
shadowRadius: 5,
height: width * 0.155,
width: width * 0.155
},
icon: {
height: width * 0.06,
width: width * 0.06
},
optContainer: {
justifyContent: 'center',
alignItems: 'center',
borderRadius: 30,
backgroundColor: '#219F75',
elevation: 5,
shadowOffset: {
height: 3,
width: 3
},
shadowColor: '#333',
shadowOpacity: 0.5,
shadowRadius: 5,
height: width * 0.13,
width: width * 0.13,
position: 'absolute'
}
});
FanItem.js:
import React, { useState } from 'react';
import { Image, Animated, StyleSheet, TouchableOpacity, Dimensions } from 'react-native';
import EStyleSheet from 'react-native-extended-stylesheet';
const { width } = Dimensions.get('window');
export default class FanItem extends React.Component {
constructor (props) {
super(props);
this.state = {
animatedOffset: new Animated.ValueXY(0),
animatedOpacity: new Animated.Value(0)
};
}
expand () {
let offset = { x: 0, y: 0 };
switch (this.props.index) {
case 0:
offset = { x: -50, y: 20 };
break;
case 1:
offset = { x: -20, y: 50 };
break;
case 2:
offset = { x: 20, y: 50 };
break;
case 3:
offset = { x: 75, y: -20 };
break;
}
Animated.parallel([
Animated.spring(this.state.animatedOffset, { toValue: offset }),
Animated.timing(this.state.animatedOpacity, { toValue: 1, duration: 600 })
]).start();
}
collapse () {
Animated.parallel([
Animated.spring(this.state.animatedOffset, { toValue: 0 }),
Animated.timing(this.state.animatedOpacity, { toValue: 0, duration: 600 })
]).start();
}
render () {
return (
<Animated.View
style={
(this.props.style,
{
left: this.state.animatedOffset.x,
top: this.state.animatedOffset.y,
opacity: this.state.animatedOpacity
})
}
>
<TouchableOpacity style={styles.container} onPress={this.props.onPress}>
<Image resizeMode={'contain'} source={this.props.icon} style={styles.icon} />
</TouchableOpacity>
</Animated.View>
);
}
}
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center',
borderRadius: 30,
backgroundColor: '#219F75',
elevation: 5,
shadowOffset: {
height: 3,
width: 3
},
shadowColor: '#333',
shadowOpacity: 0.5,
shadowRadius: 5,
height: width * 0.13,
width: width * 0.13,
position: 'absolute'
},
icon: {
height: width * 0.08,
width: width * 0.08
}
});
Implementation:
import React from 'react';
import { StyleSheet, View, Dimensions } from 'react-native';
import Component from './Component';
const { height, width } = Dimensions.get('window');
const testArr = [
{
icon: require('./src/assets/img/chat.png'),
onPress: () => alert('start chat')
},
{
icon: require('./src/assets/img/white_video.png'),
onPress: () => alert('video chat')
},
{
icon: require('./src/assets/img/white_voice.png'),
onPress: () => alert('voice chat')
},
{
icon: require('./src/assets/img/camera.png'),
onPress: () => alert('request selfie')
}
];
const App = () => {
return (
<View style={styles.screen}>
<Component options={testArr} />
</View>
);
};
const styles = StyleSheet.create({
screen: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#E6E6E6'
}
});
export default App;
Problem: The issue is, only the last FanItem item runs its animation. (opacity, and vector translation). before implementing the opacity animation I could tell the first three FanItems did in fact render behind the main button, because I could see them when pressing the main button, as the opacity temporarily changes for the duration of the button click.
My question is 1) why are the first three mapped items not animating? and 2) how to resolve this?
You are storing ref of FanItem in option. but, ref gets overridden in each iteration of map. so, at the end it only stores ref of last FanItem in option. So, first declare one array in constructor to store ref of each FanItem:
constructor(props) {
super(props);
// your other code
this.refOptions = [];
}
Store ref of each FanItem separately like this:
{this.props.options.map((item, index) => (
<FanItem ref={(ref) => this.refOptions[index] = ref} icon={item.icon} onPress={item.onPress} index={index} />
))}
and then to animate each FanItem:
for(var i = 0; i < this.refOptions.length; i++){
this.refOptions[i].expand(); //call 'expand' or 'collapse' as required
}
This is expo snack link for your reference:
https://snack.expo.io/BygobuL3JL