Skip to content

Instantly share code, notes, and snippets.

@sturmenta
Last active Jun 6, 2022
Embed
What would you like to do?
react native collapse animation dynamic height
import React, {useEffect, useImperativeHandle, useState} from 'react';
import {Keyboard, View} from 'react-native';
import {useTheme} from '@react-navigation/native';
import {useSpring, animated} from '@react-spring/native';
import {MyThemeInterfaceColors} from '_styles';
import {GetDimensions} from '_atoms';
import {MultilineTextInput, Button, TextInputRef} from '_molecules';
import {getPercentageInHex, themedStyleSheet} from '_utils';
import {usePrevious} from '_hooks';
export interface FeedbackProps {
sectionName: string;
}
export interface FeedbackRef {
open(): void;
close(): void;
isOpen(): boolean;
}
export const Feedback = React.forwardRef<FeedbackRef, FeedbackProps>(
({sectionName}: FeedbackProps, ref) => {
const styles = useStyles();
const colors = useTheme().colors as MyThemeInterfaceColors;
const textInputRef = React.createRef<TextInputRef>();
const [isVisible, setIsVisible] = useState(false);
const previousVisible = usePrevious(isVisible);
const [feedbackText, setFeedbackText] = useState('');
const [dynamicHeight, setDynamicHeight] = useState<Number>(0);
const {height, y} = useSpring({
from: {height: 0, opacity: 0, y: 0},
to: {
height: isVisible ? dynamicHeight : 0,
opacity: isVisible ? 1 : 0,
y: isVisible ? 0 : 10,
},
});
useEffect(() => {
if (isVisible) textInputRef.current?.focus();
}, [isVisible, textInputRef, sectionName]);
const open = () => {
setFeedbackText('');
setIsVisible(true);
};
const close = () => {
Keyboard.dismiss();
setIsVisible(false);
};
const isOpen = () => isVisible;
useImperativeHandle(ref, () => ({open, close, isOpen})); // https://stackoverflow.com/a/64491870
// ─────────────────────────────────────────────────────────────────
const onSendFeedback = () => {
console.warn('send feedback -', sectionName);
close();
// TODO: fullscreen animation with background fade thanks for comments
};
// ─────────────────────────────────────────────────────────────────
const InsideAnimationContainer = React.memo(() => (
<>
<View style={styles.inputTextContainer}>
<MultilineTextInput
inputRef={textInputRef}
value={feedbackText}
onChangeText={setFeedbackText}
returnKeyType="send"
placeholder="¿Alguna idea sobre como mejorar esta parte de la app?"
onSubmitEditing={onSendFeedback}
/>
</View>
<View style={styles.buttonsContainer}>
<Button
text="Cancelar"
size="small"
onPress={close}
style={{
backgroundColor: `#ffffff${getPercentageInHex(10)}`,
...styles.button,
}}
textScale="caption"
/>
<Button
text="Enviar comentarios"
size="small"
onPress={onSendFeedback}
style={{
backgroundColor: colors.bg_green,
...styles.button,
}}
textScale="caption"
/>
</View>
</>
));
return (
<animated.View
style={{
...styles.touchableContainer,
...({
height:
isVisible && previousVisible === isVisible ? 'auto' : height,
top: y,
} || {}),
}}>
<GetDimensions
component={<InsideAnimationContainer />}
onDimensions={({height: _height}) =>
dynamicHeight !== _height ? setDynamicHeight(_height) : null
}
/>
<InsideAnimationContainer />
</animated.View>
);
},
);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const useStyles = themedStyleSheet((colors: MyThemeInterfaceColors) => ({
touchableContainer: {
backgroundColor: `#000000${getPercentageInHex(20)}`,
},
inputTextContainer: {
marginTop: 15,
paddingHorizontal: 25,
},
button: {
borderRadius: 4,
height: 'auto',
padding: 5,
paddingHorizontal: 15,
},
buttonsContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: 25,
paddingTop: 6,
marginBottom: 10,
},
}));
// https://codesandbox.io/s/github/pmndrs/react-spring/tree/master/demo/src/sandboxes/tree
// https://react-spring-visualizer.com/
import React, {useEffect, useState} from 'react';
import {View, ViewProps} from 'react-native';
interface GetDimensionsProps extends ViewProps {
component: React.ReactChild[] | React.ReactChild;
onDimensions: ({width, height}: {width: Number; height: Number}) => void;
}
// render the component once (10 milliseconds) & return dimensions
export const GetDimensions: React.FC<GetDimensionsProps> = ({
component,
onDimensions,
...props
}: GetDimensionsProps) => {
const [isTimeToRenderNull, setIsTimeToRenderNull] = useState(false);
useEffect(() => {
setTimeout(() => setIsTimeToRenderNull(true), 10);
}, []);
if (isTimeToRenderNull) return null;
return (
<View
onLayout={event =>
onDimensions({
width: Math.round(event.nativeEvent.layout.width),
height: Math.round(event.nativeEvent.layout.height),
})
}
style={{position: 'absolute', width: '100%'}}
{...props}>
{component}
</View>
);
};
// ────────────────────────────────────────────────────────────────────────────────
// usage
// <GetDimensions
// component={<View />}
// onDimensions={({width, height}) => console.log(width, height)} />
/* eslint-disable no-void */
/* eslint-disable no-return-assign */
import {useEffect, useRef} from 'react';
export function usePrevious<T>(value: T) {
const ref = useRef<T>();
useEffect(() => void (ref.current = value), [value]);
return ref.current;
}
@sturmenta
Copy link
Author

sturmenta commented May 18, 2022

gif example

@kingkongqn4444
Copy link

kingkongqn4444 commented May 30, 2022

look good. thank you for sharing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment