react-native-trays
Version:
Production-grade, Family inspired, React Native Tray System library
177 lines (174 loc) • 6.46 kB
JavaScript
"use strict";
/**
* TrayRenderer.tsx
*
* Renders a single tray component with animation and keyboard adjustment support.
* Handles tray position, animation, and keyboard-aware behavior for tray UI.
*/
import { Gesture, GestureDetector, Directions } from 'react-native-gesture-handler';
import React, { useEffect, useMemo } from 'react';
import { Platform, Keyboard, StyleSheet, View } from 'react-native';
import Animated, { useAnimatedStyle, useSharedValue, withTiming, Easing, LinearTransition, SlideInDown, SlideOutDown, FadeInDown, FadeOutDown, runOnJS } from 'react-native-reanimated';
import { DEVICE_HEIGHT } from "./constants.js";
import { calculateKeyboardAdjustments } from "./utils/index.js";
/**
* Props for TrayRenderer component.
* @property trayKey - Unique key for the tray instance.
* @property trayProps - Props to pass to the tray component.
* @property config - Tray stack configuration.
* @property TrayComponent - The tray component to render.
* @property insets - Safe area insets for proper positioning.
*/
import { jsx as _jsx } from "react/jsx-runtime";
/**
* TrayRenderer
*
* Renders the given tray component with animation and keyboard-aware adjustments.
* Handles tray position, entry/exit animations, and safe area insets.
*/
export const TrayRenderer = ({
trayKey,
trayProps,
config,
TrayComponent,
insets,
onDismiss
}) => {
const translateY = useSharedValue(0);
const trayHeight = useSharedValue(0);
const trayBottom = useSharedValue(insets.bottom);
const isIOS = Platform.OS === 'ios';
const maxAllowedHeight = useMemo(() => Math.round(DEVICE_HEIGHT) - (Math.round(insets.top) + Math.round(insets.bottom)), [insets.bottom, insets.top]);
const maxHeight = useSharedValue(maxAllowedHeight);
const keyboardBehavior = useMemo(() => ({
adjustForKeyboard: config.adjustForKeyboard ?? false,
clipMaxHeightToSafeArea: config.clipMaxHeightToSafeArea ?? false
}), [config.adjustForKeyboard, config.clipMaxHeightToSafeArea]);
useEffect(() => {
const handleKeyboardShow = e => {
const keyboardHeight = e.endCoordinates.height;
const adjustments = calculateKeyboardAdjustments(keyboardHeight, keyboardBehavior, maxAllowedHeight, insets.bottom);
trayBottom.value = withTiming(adjustments.bottom, {
duration: isIOS ? 60 : 250,
easing: Easing.out(Easing.ease)
});
maxHeight.value = withTiming(adjustments.maxHeight, {
duration: isIOS ? 60 : 10,
easing: Easing.out(Easing.ease)
});
};
const handleKeyboardHide = () => {
trayBottom.value = withTiming(insets.bottom, {
duration: isIOS ? 90 : 200,
easing: Easing.out(Easing.ease)
});
maxHeight.value = withTiming(maxAllowedHeight, {
duration: isIOS ? 90 : 0,
easing: Easing.out(Easing.ease)
});
};
const showSub = isIOS ? Keyboard.addListener('keyboardWillShow', handleKeyboardShow) : Keyboard.addListener('keyboardDidShow', handleKeyboardShow);
const hideSub = isIOS ? Keyboard.addListener('keyboardWillHide', handleKeyboardHide) : Keyboard.addListener('keyboardDidHide', handleKeyboardHide);
return () => {
showSub.remove();
hideSub.remove();
};
}, [config.adjustForKeyboard, config.clipMaxHeightToSafeArea, insets.bottom, isIOS, keyboardBehavior, maxAllowedHeight, maxHeight, trayBottom]);
const dynamicTrayStyle = useMemo(() => ({
backgroundColor: config.customTheming ? undefined : '#fff',
shadowColor: config.customTheming ? undefined : '#000'
}), [config.customTheming]);
const trayAnimatedStyle = useAnimatedStyle(() => {
const style = {
transform: [{
translateY: translateY.value
}]
};
if (config.stickToTop) {
style.top = insets.top + (typeof config.trayStyles?.top === 'number' ? config.trayStyles?.top : 0);
} else {
style.bottom = trayBottom.value + (typeof config.trayStyles?.bottom === 'number' ? config.trayStyles?.bottom : 0);
}
return style;
}, [config.stickToTop, config.trayStyles, insets.top, trayBottom.value]);
const {
enteringAnimation = SlideInDown,
exitingAnimation = SlideOutDown,
horizontalSpacing = 20
} = config;
const gesture = Gesture.Fling().direction(config.stickToTop ? Directions.UP : Directions.DOWN).onEnd((_event, success) => {
if (success) {
runOnJS(onDismiss)();
}
});
const trayAnimatedHeight = useAnimatedStyle(() => {
'worklet';
return {
maxHeight: maxHeight.value
};
}, []);
return /*#__PURE__*/_jsx(Animated.View, {
style: [styles.tray, {
left: insets.left + horizontalSpacing,
right: insets.right + horizontalSpacing
}, dynamicTrayStyle, config.trayStyles, trayAnimatedStyle, config.clipMaxHeightToSafeArea ? trayAnimatedHeight : undefined],
layout: config.disableLayoutAnimation ? undefined : config.customLayoutAnimation ?? LinearTransition.easing(Easing.out(Easing.ease)).duration(150),
entering: enteringAnimation,
exiting: exitingAnimation,
children: /*#__PURE__*/_jsx(View, {
style: styles.content,
onLayout: e => {
trayHeight.value = e.nativeEvent.layout.height;
},
children: config.enableSwipeToClose ? /*#__PURE__*/_jsx(GestureDetector, {
gesture: config.enableSwipeToClose ? gesture : Gesture.Pan(),
children: /*#__PURE__*/_jsx(Animated.View, {
collapsable: false,
entering: FadeInDown.duration(180),
exiting: FadeOutDown.duration(120),
children: /*#__PURE__*/_jsx(TrayComponent, {
...(trayProps ?? {})
})
}, trayKey)
}) : /*#__PURE__*/_jsx(Animated.View, {
entering: FadeInDown.duration(180),
exiting: FadeOutDown.duration(120),
children: /*#__PURE__*/_jsx(TrayComponent, {
...(trayProps ?? {})
})
}, trayKey)
})
});
};
const styles = StyleSheet.create({
tray: {
position: 'absolute',
borderRadius: 30,
shadowOffset: {
width: 0,
height: 5
},
shadowOpacity: 0.25,
shadowRadius: 40,
elevation: 10,
overflow: 'hidden',
zIndex: 999
},
content: {
position: 'relative',
flex: 1
},
closeBtnWrapper: {
position: 'absolute',
zIndex: 1,
top: 20,
right: 20,
overflow: 'hidden'
},
closeBtn: {
width: 30,
height: 30,
zIndex: 2
}
});
//# sourceMappingURL=TrayRenderer.js.map