How do I apply independent HeaderStyleInterpolators in react-navigation? - react-native

Using react-navigation-stack 1.10.3, applying a headerTransitionPreset of 'uikit' transitioned header titles from left-to-right while fading in/out whatever elements were in headerRight or headerLeft.
With react-navigation-stack 2.0.15, this transition is deprecated in favor of using headerStyleInterpolator.forUIKit, but that transition applies universally to the entire header, not just the title.
How do I replicate the previous behavior on 1.10.X with 2.0.X where I can use forFade on headerRight and headerLeft?
Example on 2.0.15:
Example on 1.10.3:
The main issue is the removal of the fade animation when going back in the stack and it's replacement with a left-to-right transition.

The forUIKit styles mimic the native iOS behavior. If you don't want the same behavior, you can use a custom style interpolator instead.
If you want to keep the same interpolator from forUIKit, but want to fade the left and right button instead of the default transition, you can override those styles with something like this:
function forCustomHeaderAnimation(options) {
const { progress } = options.current;
const styles = HeaderStyleInterpolators.forUIKit(options);
return {
...styles,
leftButtonStyle: { opacity: progress },
leftLabelStyle: {},
rightButtonStyle: { opacity: progress },
};
}
// ...
static navigationOptions = {
headerStyleInterpolator: forCustomHeaderAnimation
}
Source: https://github.com/react-navigation/navigation-ex/blob/2243b45cc1addf83727166d82736d214f181b1fb/packages/stack/src/TransitionConfigs/HeaderStyleInterpolators.tsx

Related

Fluent UI React - how to apply global component styles with Fluent ThemeProvider

I'm working with the theming code below. I'm able to apply a global Fluent theme with the ThemeProvider and createTheme utility, but when I add component specific themes, I'm not getting any typings, which makes theming very difficult.
So my question is: how do I apply global component-specific styles using Fluent ThemeProvider with strong typing.
If, for example, I wanted to add a box shadow to all Fluent PrimaryButtons, I wouldn't know what properties to access on the components key passed into createTheme.
If you've done any global component theming, please let me know what pattern you used and if I'm on the right track, thanks!
import { createTheme } from '#fluentui/react';
import { PartialTheme } from '#fluentui/react-theme-provider';
// Trying to add global component styles (not getting typings)
const customComponentStyles = {
PrimaryButton: {
styles: {
root: {
background: 'red'
}
}
}
};
export const fluentLightTheme: PartialTheme = createTheme({
components: customComponentStyles, // Want to apply component styles
palette: {
themePrimary: '#0078d4',
themeLighterAlt: '#eff6fc',
themeLighter: '#deecf9',
themeLight: '#c7e0f4',
themeTertiary: '#71afe5',
themeSecondary: '#2b88d8',
themeDarkAlt: '#106ebe',
themeDark: '#005a9e',
themeDarker: '#004578',
neutralLighterAlt: '#faf9f8',
neutralLighter: '#f3f2f1',
neutralLight: '#edebe9',
neutralQuaternaryAlt: '#e1dfdd',
neutralQuaternary: '#d0d0d0',
neutralTertiaryAlt: '#c8c6c4',
neutralTertiary: '#a19f9d',
neutralSecondary: '#605e5c',
neutralPrimaryAlt: '#3b3a39',
neutralPrimary: '#323130',
neutralDark: '#201f1e',
black: '#000000',
white: '#ffffff'
}
});
The problem here is, that components is just a Record<string, ComponentStyles>, where ComponentStyles then just is a quite unspecific object of the form { styles?: IStyleFunctionOrObject<any, any>}. They would have to add an entry for every possible I<Component>Styles interface to ComponentsStyles, which I guess would be too much work and errorprone (e.g. forgetting to add a new component here ...).
Since all the I<Component>Styles interfaces are exported by Fluent, I always define the styles for each component separately and than merge them in the components object:
const buttonStyles: IButtonStyles = {
root: {
backgroundColor: 'red'
}
};
export const fluentLightTheme: PartialTheme = createTheme({
components: { PrimaryButton: { styles: buttonStyles } },
});
Here is a codepen example I created by using one of Fluent UIs basic Button examples: https://codepen.io/alex3683/pen/WNjmdWo

Is there a way to animate the increased size of a View when new children are added?

I’m currently using LayoutAnimation to animate a view when children are added. However, since LayoutAnimation causes everything to be animated, globally, and I can’t easily use built-in Animated library to fit my use-case, I’m wondering if react-native-reanimated is able to help.
Here's a snack of my current solution:
https://snack.expo.io/#insats/height-adapation
This is what the result of that looks like:
Is there a way to achieve the same thing without using LayoutAnimation? I've looked through all exampled in react-native-reanimated, and I've read through the docs but I'm still not sure if this is possible to do or how I should get started. I've thought about using Animated to move the item-wrapper out of the viewable area and "scroll" it upwards (using transform translateY) when items are added, but that would require fixed height, which I don't have.
I have 2 approaches that I can suggest out of my mind:
You can configure your LayoutAnimation only when your desired state changed. If you use hooks it would be too easy:
const [state,setState] = useState([]);
useEffect(()=>{
/*rest code*/
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
},[state])
Or if you use class component you can catch your desired state change in componentDidUpdate:
componentDidUpdate(prevProps,prevState){
if(prevState.items!==state.items){
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
}
}
You can use onLayout function of view:
addItem = () => {
this.setState({
items: [...this.state.items, {title:'An item',isNew:true}]
})
};
renderItems = () => {
return this.state.items.map((item, index) => {
let opacity = new Animated.Value(0);
return (
<Animated.View onLayout={({nativeEvent})=>{
if(this.state.item.isNew){
// here you got the height from nativeEvent.layout.height
// Then you have to store the height animate height and opacity to its precise value
// PS I used opacity:0 to calculate the height
}
}} key={index} style={[styles.item,{opacity}>
<Text>{item.title}</Text>
</View>
)
});
};
When it comes to react-native-reanimated I regard it as more faster version of react-native's Animated library. So either way you will have to calculate the height!

Detect Swipe direction using React Native Gesture Handler and Reanimated

We have already developed a swiper using React Native PanResponder, (before somebody comment) - we cannot use any other swiper library here. In the swiper we developed, we are facing an issue when a user ends the swipe(meaning releases the Pan) there is a lag when starting a spring animation.
So to fix that, we are trying to move from React Native Animated API to Reanimated Libary which could solve the problem of lag the user is facing.
But while developing with React Native Gesture Handler (PanGestureHandler) and
Reanimated Library, we're unable to detect swipe direction in the
gesture handler.
I'm adding the part of code which we used to detect swipe direction in PanResponder
onPanMoving = (evt, {dx}) => {
this.draggedEnd = dx < -this.swipeThreshold; //You are moving towards right screen
this.draggedStart = dx > this.swipeThreshold; //You are moving towards left screen
}
As you see, detecting the direction while the pan is moving was so easy in PanResponder.
Here comes the problem with Gesture Handler,
I'm unable to detect swipe while the gesture state is active
I have already searched for issues in Gesture Handler and I found this.
There were two workaround suggested in the issue
First one is by Sonaye which has two handler and detects the
swipe direction in onHandlerStateChange for both the handlers
which was not helpful while using reanimated because it only sets swipe direction
when handlers state changes.
Second one is by Satya which actually detects the swipe when
the State is ACTIVE but he uses translationX property, the
translationX property can also negative for us and can move in
either direction similar to swiper.
Both the workarounds doesn't solve our problem.
So is there any other way to find direction using PanGestureHandler and Reanimated. I tried using PanResponder with Reanimated with dx value but ended up with error message i.e nativeEvent properties are only support as dx is from gestureState param from PanResponder.
Note: We cannot use FlingGestureHandler and Swipeables because we need to find direction in Fling still onHandlerStateChange and Swipeables doesn't use Reanimated.
You can detect direction when swipe horizontal via property velocityX
Example :
const handleGesture = (evt) => {
const { nativeEvent } = evt;
if (nativeEvent.velocityX > 0) {
console.log('Swipe right');
} else {
console.log('Swipe left');
}
};
return (
<PanGestureHandler onGestureEvent={handleGesture}>
// child component
</PanGestureHandler>
);
For those who want to use double FlingGestureHandler to have vertical or horizontal fling gesture here is the solution:
First you have to initialize the stateHandlers for the direction you want. In my case, i was trying to implement a vertical flingGestureHandler.
const onDownFlingHandlerStateChange = ({
nativeEvent,
}: FlingGestureHandlerStateChangeEvent) => {
if (nativeEvent.oldState === State.ACTIVE) {
progressHeight.value = withTiming(COLLAPSED_HEIGHT, {
duration: ANIMATION_DURATION,
});
}
};
const onUpFlingHandlerStateChange = ({
nativeEvent,
}: FlingGestureHandlerStateChangeEvent) => {
if (nativeEvent.oldState === State.ACTIVE) {
progressHeight.value = withTiming(
DEVICE_HEIGHT - TOP_PADDING_FOR_EXPANDED,
{
duration: ANIMATION_DURATION,
},
);
}
};
and then you have to bind those handlers to 2 FlingGestureHandler component of RNGH
<FlingGestureHandler
onHandlerStateChange={onUpFlingHandlerStateChange}
onEnded={() => {
onPressChecklistIcon();
}}
direction={Directions.UP}>
<FlingGestureHandler
direction={Directions.DOWN}
onEnded={() => {
onPressChecklistIcon();
}}
onHandlerStateChange={onDownFlingHandlerStateChange}>
<Animated.View style={[styles.container, reanimatedStyle]}>
<View style={styles.closerContainer}>
<View style={styles.closer} />
</View>
{renderChecklistContent()}
</Animated.View>
</FlingGestureHandler>
</FlingGestureHandler>
Use translationX property rather than velocityX property by nativeEvent:
const handleGesture = (evt) => {
const { nativeEvent } = evt;
if (nativeEvent.translationX > 0) {
Alert.alert('Swipe right');
} else {
Alert.alert('Swipe left');
}
};
In my case I have used double FlingGestureHandler to detect the direction. It's worked for me.

React native detect screen rotation

I'm using onLayout to detect screen orientation and it's working fine inside my root view, but when I implemented inside the drawer it didn't work, any reason why this happens ?
code :
import Drawer from 'react-native-drawer'
...
onLayout(e) {
console.log('onLayout');
}
<Drawer onLayout={this.onLayout}
It didn't log any thing when orientation changed!
This is because the Drawer component doesn't take onLayout as a prop. You can see in the source code that the rendered View does use onLayout, but it's not pulling from something like this.props.onLayout.
I'm not exactly sure what you're looking to do, but maybe this issue will help you. As it shows, you can pass a function into openDrawerOffset instead of an integer or a ratio in order to be a little more dynamic with how you set your offset:
openDrawerOffset={(viewport) => {
if (viewport.width < 400) {
return viewport.width * 0.1;
}
return viewport.width - 400;
}}
You might also benefit from the Event handlers that react-native-drawer has to offer.

How to implement a user selectable theme / style in react native?

If user has selected light-theme, and switches to dark-theme, then all scenes will immediately render to using the dark-theme.
I am using react-native-router-flux if this helps.
Thanks in advance,
Different approaches are possible. One of them is to use React context. Generally it should be used with caution but theming is one of the official examples where it is suitable.
Theming is a good example of when you might want an entire subtree to have access to some piece of information
So the example might look like
class App extends Component {
getChildContext() {
return {theme: { primaryColor: "purple" }};
}
...
}
App.childContextTypes = {
theme: React.PropTypes.object
};
where you set the context for rest of your application and then you use it in your components
class Button extends Component {
render() {
return <TouchableHighlight underlayColor={this.context.theme.primaryColor}>...
}
}
Button.contextTypes = {
theme: React.PropTypes.object
};
If you want to switch theme, you can set context based on your state/props that can be based on user selection.
Currently we are dealing with same questions and therefore we have starting prototyping library called react-native-themeable.
The idea is to use context to store theme and (re)implement RN components so they can replace original components but supports theming.
Example of theme switching you can find in https://github.com/instea/react-native-themeable/blob/master/examples/src/SwitchTheme.js
You can start by creating one JS file containing two objects that have the colors for each theme. Then just use one as a default and if the user picks another theme save it in the database or using local storage and pull the relevant object.
export const theme1 = {
blue: '#fddd',
red: '#ddddd',
buttonColor: '#fff'
}
export const theme2 = {
blue: '#fddd',
red: '#ddddd'
buttonColor: '#fff'
}
Then just import the relevant file:
import * as themes from 'Themes'
const currentTheme = this.props.currentTheme ? this.props.currentTheme : 'theme1'
const styles = StyleSheet.create({
button: {
color: themes[currentTheme].buttonColor
},
})
Wouldn’t it be great if you could import a colors object directly, and modify the object every time you change the theme and have those new colors propagate to the entire app? Then your components wouldn’t need to have any special theme logic. I was able to achieve this using a combination of AsyncStorage, forced app restart, and async module loading.