UNPKG

@td-design/react-native

Version:

react-native UI组件库

206 lines (204 loc) 7.19 kB
import React, { useMemo } from 'react'; import { Image, Pressable, StyleSheet } from 'react-native'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import Animated, { useAnimatedStyle, useDerivedValue, useSharedValue, withTiming } from 'react-native-reanimated'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useTheme } from '@shopify/restyle'; import { useSafeState } from '@td-design/rn-hooks'; import Box from '../box'; import Flex from '../flex'; import helpers from '../helpers'; import Text from '../text'; import PlusIcon from './PlusIcon'; export default function FloatButton(_ref) { let { customActionButton, items, itemHeight = 60, position = 'bottomRight', containerStyle, draggable = false, actionButtonProps } = _ref; const insets = useSafeAreaInsets(); const theme = useTheme(); const width = useSharedValue((actionButtonProps === null || actionButtonProps === void 0 ? void 0 : actionButtonProps.width) || itemHeight); const height = useSharedValue((actionButtonProps === null || actionButtonProps === void 0 ? void 0 : actionButtonProps.height) || itemHeight); const borderRadius = useSharedValue((actionButtonProps === null || actionButtonProps === void 0 ? void 0 : actionButtonProps.borderRadius) || itemHeight); const isOpen = useSharedValue(false); const [opened, setOpened] = useSafeState(false); const progress = useDerivedValue(() => isOpen.value ? withTiming(1) : withTiming(0)); const scale = useDerivedValue(() => isOpen.value ? withTiming(0) : withTiming(1)); const handlePress = () => { if (!isOpen.value) { width.value = withTiming(200); height.value = withTiming((items.length + 1) * itemHeight); borderRadius.value = withTiming(12); } else { width.value = withTiming((actionButtonProps === null || actionButtonProps === void 0 ? void 0 : actionButtonProps.width) || itemHeight); height.value = withTiming((actionButtonProps === null || actionButtonProps === void 0 ? void 0 : actionButtonProps.height) || itemHeight); borderRadius.value = withTiming((actionButtonProps === null || actionButtonProps === void 0 ? void 0 : actionButtonProps.borderRadius) || itemHeight); } isOpen.value = !isOpen.value; setOpened(opened => !opened); }; const iconStyle = { width: itemHeight / 2, height: itemHeight / 2 }; const positionX = useSharedValue(0); const positionY = useSharedValue(0); const context = useSharedValue({ x: 0, y: 0 }); const panGesture = Gesture.Pan().enabled(draggable && !opened).onStart(() => { context.value = { x: positionX.value, y: positionY.value }; }).onUpdate(e => { positionX.value = e.translationX + context.value.x; positionY.value = e.translationY + context.value.y; }).onEnd(() => { 'worklet'; // 滑动结束后,判断位置,自动滑到左边或右边 // 同时需要根据position,如果一开始位置在左侧,那么滑动结束后,如果超过屏幕一半,自动滑到右边 // 如果一开始位置在右侧,那么滑动结束后,如果超过屏幕一半,自动滑到左边 if (position.includes('Left')) { if (positionX.value > (helpers.deviceWidth - itemHeight) / 2) { // 超过屏幕一半,自动滑到右边 positionX.value = withTiming(helpers.deviceWidth - itemHeight - 32); } else { positionX.value = withTiming(0); } } else { if (positionX.value < -(helpers.deviceWidth - itemHeight) / 2) { // 超过屏幕一半,自动滑到左边 positionX.value = withTiming(-helpers.deviceWidth + itemHeight + 32); } else { positionX.value = withTiming(0); } } }); const animatedStyle = useAnimatedStyle(() => { return { width: width.value, height: height.value, borderRadius: borderRadius.value, transform: [{ translateX: positionX.value }, { translateY: positionY.value }] }; }); /** 根据position确定绝对定位的初始位置 */ const positionStyle = useMemo(() => { switch (position) { case 'topLeft': return { top: 0, left: 0 }; case 'topRight': return { top: 0, right: 0 }; case 'bottomLeft': return { bottom: insets.bottom, left: 0 }; case 'bottomRight': return { bottom: insets.bottom, right: 0 }; default: return { top: 0, left: 0 }; } }, [position]); const mainButtonStyle = useAnimatedStyle(() => { return { opacity: scale.value, transform: [{ scale: scale.value }] }; }); const closeButtonStyle = useAnimatedStyle(() => { return { opacity: progress.value, transform: [{ scale: progress.value }] }; }); return /*#__PURE__*/React.createElement(GestureDetector, { gesture: panGesture }, /*#__PURE__*/React.createElement(Animated.View, { style: [{ backgroundColor: theme.colors.primary200 }, styles.container, positionStyle, containerStyle, animatedStyle] }, !opened ? /*#__PURE__*/React.createElement(Animated.View, { style: mainButtonStyle }, /*#__PURE__*/React.createElement(Pressable, { onPress: handlePress }, customActionButton ? customActionButton(progress, handlePress) : /*#__PURE__*/React.createElement(Animated.View, { style: [styles.iconContainer, { width: itemHeight, height: itemHeight, borderRadius: itemHeight }] }, /*#__PURE__*/React.createElement(PlusIcon, null)))) : /*#__PURE__*/React.createElement(Animated.View, { style: [closeButtonStyle] }, /*#__PURE__*/React.createElement(Pressable, { onPress: handlePress }, /*#__PURE__*/React.createElement(Animated.View, { style: [styles.iconContainer, { width: itemHeight, height: itemHeight, transform: [{ rotate: '-45deg' }] }] }, /*#__PURE__*/React.createElement(PlusIcon, null)))), /*#__PURE__*/React.createElement(Box, null, items.map((item, index) => /*#__PURE__*/React.createElement(Pressable, { key: index, onPress: async () => { await item.onPress(); handlePress(); } }, /*#__PURE__*/React.createElement(Flex, { alignItems: 'center', height: itemHeight, paddingHorizontal: 'x3', style: item.style }, /*#__PURE__*/React.createElement(Box, { marginRight: 'x2' }, typeof item.icon === 'string' ? /*#__PURE__*/React.createElement(Image, { source: { uri: item.icon }, style: iconStyle }) : item.icon), /*#__PURE__*/React.createElement(Box, null, typeof item.label === 'string' ? /*#__PURE__*/React.createElement(Text, { variant: 'p1', color: "white" }, item.label) : item.label))))))); } const styles = StyleSheet.create({ container: { position: 'absolute', overflow: 'hidden', margin: 16 }, iconContainer: { justifyContent: 'center', alignItems: 'center' } }); //# sourceMappingURL=index.js.map