Skip to content

Commit

Permalink
fix: performance fixes slide and overlay
Browse files Browse the repository at this point in the history
  • Loading branch information
surajahmed committed Dec 20, 2021
1 parent 44b4678 commit 08eaabb
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 82 deletions.
Expand Up @@ -19,6 +19,7 @@ export const Example = () => {
h="100%"
// alignItems="flex-start"
justifyContent="center"
// overflow={'hidden'}
>
<VStack space={3} w="100%">
<HStack alignItems="flex-end">
Expand Down
19 changes: 19 additions & 0 deletions src/components/composites/Actionsheet/ActionsheetContent.tsx
Expand Up @@ -16,11 +16,20 @@ const ActionsheetContent = (
'ActionsheetContent',
props
);

// useEffect(() => {
// console.log('action sheet content');
// }, []);

// return null;
const { handleClose } = React.useContext(ModalContext);
const { hideDragIndicator } = React.useContext(ActionSheetContext);
const pan = React.useRef(new Animated.ValueXY()).current;
const sheetHeight = React.useRef(0);

// useEffect(() => {

// }, [])
const panResponder = React.useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => true,
Expand All @@ -42,6 +51,14 @@ const ActionsheetContent = (
duration: 150,
useNativeDriver: true,
}).start(handleClose);

setTimeout(() => {
Animated.spring(pan, {
toValue: { x: 0, y: 0 },
overshootClamping: true,
useNativeDriver: true,
}).start();
});
} else {
Animated.spring(pan, {
toValue: { x: 0, y: 0 },
Expand All @@ -56,6 +73,8 @@ const ActionsheetContent = (
if (useHasResponsiveProps(props)) {
return null;
}

// console.log('action sheet render ');
return (
<Animated.View
style={{
Expand Down
2 changes: 2 additions & 0 deletions src/components/composites/Modal/Modal.tsx
Expand Up @@ -61,6 +61,8 @@ const Modal = (
if (useHasResponsiveProps(rest)) {
return null;
}

// console.log('visible here', visible);
return (
<Overlay
isOpen={visible}
Expand Down
10 changes: 4 additions & 6 deletions src/components/composites/Transitions/PresenceTransition.tsx
Expand Up @@ -8,27 +8,25 @@ const PresenceTransition = (
{ visible = false, onTransitionComplete, ...rest }: IPresenceTransitionProps,
ref: any
) => {
const [animationExited, setAnimationExited] = React.useState(!visible);
// const [animationExited, setAnimationExited] = React.useState(!visible);

const { setExited } = React.useContext(ExitAnimationContext);
//TODO: refactor for responsive prop
if (useHasResponsiveProps(rest)) {
return null;
}

if (!visible && animationExited) {
return null;
}
// if (!visible && animationExited) {
// return null;
// }

return (
<Transition
visible={visible}
onTransitionComplete={(state) => {
if (state === 'exited') {
setAnimationExited(true);
setExited(true);
} else {
setAnimationExited(false);
setExited(false);
}
onTransitionComplete && onTransitionComplete(state);
Expand Down
172 changes: 100 additions & 72 deletions src/components/composites/Transitions/Transition.tsx
@@ -1,6 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
import React, { forwardRef } from 'react';
import { Animated } from 'react-native';
import { useHasResponsiveProps } from '../../../hooks/useHasResponsiveProps';
import type {
ISupportedTransitions,
ITransitionConfig,
Expand Down Expand Up @@ -80,119 +80,147 @@ export const Transition = forwardRef(
) => {
const animateValue = React.useRef(new Animated.Value(0)).current;

const [displayState, setDisplayState] = React.useState('flex');
const Component = React.useMemo(() => {
if (as) {
return Animated.createAnimatedComponent(as);
}
return Animated.View;
}, [as]);

const [animationState, setAnimationState] = React.useState(
visible ? 'entering' : 'exited'
);
const [animationState, setAnimationState] = React.useState('');

const prevVisible = React.useRef(visible);

React.useEffect(
function startEntryTransition() {
// React.useEffect(() => {

// }, [visible]);

React.useEffect(() => {
if (animationState === 'entering' || animationState === 'exiting') {
// setDisplayState('flex');

// if (animationState === 'entering') {
// setTimeout(() => {
setDisplayState('flex');
// });
// return;
// }

// setTimeout(() => {
const entryTransition = {
...defaultTransitionConfig,
...animate?.transition,
};
const exitTransition = {
...defaultTransitionConfig,
...exit?.transition,
};

if (visible) {
Animated.sequence([
// @ts-ignore - delay is present in defaultTransitionConfig
Animated.delay(entryTransition.delay),
Animated[entryTransition.type ?? 'timing'](animateValue, {
toValue: 1,
useNativeDriver: true,
...entryTransition,
}),
]).start(() => {
const startAnimation = animationState === 'entering' ? 1 : 0;

const transition = startAnimation ? entryTransition : exitTransition;

// console.log(
// 'Transition state',
// transition,
// animationState,
// defaultTransitionConfig
// );
Animated.sequence([
// @ts-ignore - delay is present in defaultTransitionConfig
Animated.delay(transition.delay),
Animated[transition.type ?? 'timing'](animateValue, {
toValue: startAnimation,
useNativeDriver: true,
...transition,
}),
]).start(() => {
if (animationState === 'entering') {
setAnimationState('entered');
});
}
},
[visible, onTransitionComplete, animateValue, animate]
);
} else if (animationState === 'exiting') {
setAnimationState('exited');
setTimeout(() => {
setDisplayState('none');
});
}
});
// });
}

if (animationState === 'exited') {
onTransitionComplete && onTransitionComplete('exited');
// setDisplayState('none');

// setInitialState({ ...exit });
} else if (animationState === 'entered') {
onTransitionComplete && onTransitionComplete('entered');
// setInitialState({ ...initial });
}
// if (animationState === 'entering') {
// //
// }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [animationState, onTransitionComplete]);

React.useEffect(() => {
// Exit request
// if (!visible) {
if (prevVisible.current !== visible && !visible) {
setAnimationState('exiting');
}

if (visible) {
setAnimationState('entering');
}
prevVisible.current = visible;
// }
}, [visible]);

React.useEffect(
function startExitTransition() {
const exitTransition = {
...defaultTransitionConfig,
...exit?.transition,
};

if (animationState === 'exiting') {
Animated.sequence([
// @ts-ignore - delay is present in defaultTransitionConfig
Animated.delay(exitTransition.delay),
Animated[exitTransition.type ?? 'timing'](animateValue, {
toValue: 0,
useNativeDriver: true,
...exitTransition,
}),
]).start(() => {
setAnimationState('exited');
});
}
},
[
exit,
onTransitionComplete,
setAnimationState,
animationState,
animateValue,
]
);

// If exit animation is present and state is exiting, we replace 'initial' with 'exit' animation
initial =
animationState === 'exiting' && exit

// const initialState = { ...defaultStyles, ...initial };
const initialState =
animationState === 'exited' && exit
? { ...defaultStyles, ...exit }
: { ...defaultStyles, ...initial };
// const initialState = { ...defaultStyles, ...initial };

// initial =
// animationState === 'exited'
// ? { ...defaultStyles, ...exit }
// : { ...defaultStyles, ...initial };

animate = { ...defaultStyles, ...animate };
const animateState = { ...defaultStyles, ...animate };

// const [initialState, setInitialState] = React.useState({
// ...defaultStyles,
// ...initial,
// });
// console.log('Initial state ', initial);

// const [animateState] = React.useState({ ...defaultStyles, ...animate });
const styles = React.useMemo(() => {
// console.log('display state here', initial);
return [
getAnimatedStyles(animateValue)(
initial as ISupportedTransitions,
animate as ISupportedTransitions
initialState as ISupportedTransitions,
animateState as ISupportedTransitions
),
style,
];
}, [animateValue, initial, animate, style]);

React.useEffect(() => {
if (animationState === 'exited') {
onTransitionComplete && onTransitionComplete('exited');
} else if (animationState === 'entered') {
onTransitionComplete && onTransitionComplete('entered');
}
}, [animationState, onTransitionComplete]);
//TODO: refactor for responsive prop
if (useHasResponsiveProps(rest)) {
return null;
}

return (
<Component
pointerEvents="box-none"
// pointerEvents="box-none"
pointerEvents={!visible ? 'none' : 'box-none'}
// https://github.com/facebook/react-native/issues/23090#issuecomment-710803743
needsOffscreenAlphaCompositing
style={styles}
// needsOffscreenAlphaCompositing
// style={[styles]}
style={[styles, { display: displayState }]}
ref={ref}
{...rest}
// style={{ }}
>
{children}
</Component>
Expand Down
2 changes: 2 additions & 0 deletions src/components/composites/Transitions/types.tsx
Expand Up @@ -81,6 +81,8 @@ export interface ITransitionProps extends ViewProps {
* Determines whether to start the animation
*/
visible?: boolean;

animationExited?: boolean;
children?: any;
as?: any;
}
Expand Down
14 changes: 10 additions & 4 deletions src/components/primitives/Overlay/Overlay.tsx
Expand Up @@ -28,9 +28,9 @@ export function Overlay({
callback: onRequestClose ? onRequestClose : () => {},
});

if (exited && !isOpen) {
return null;
}
// if (exited && !isOpen) {
// return null;
// }

// Android handles multiple Modal in RN and is better for accessibility as it shifts accessibility focus on mount, however it may not needed in case of tooltips, toast where one doesn't need to shift accessibility focus
if (Platform.OS === 'android' && useRNModalOnAndroid) {
Expand All @@ -44,11 +44,17 @@ export function Overlay({
}

// Since OverlayContainer mounts children in NativeBaseProvider using Context, we need to pass the context by wrapping children

// return <> {children}</>;
return (
<OverlayContainer>
<OverlayContainer
style={{ display: exited && !isOpen ? 'none' : 'contents' }}
>
<ExitAnimationContext.Provider value={{ exited, setExited }}>
{children}
</ExitAnimationContext.Provider>
</OverlayContainer>

// </Box>
);
}

0 comments on commit 08eaabb

Please sign in to comment.