@td-design/react-native
Version:
react-native UI组件库
206 lines (204 loc) • 7.19 kB
JavaScript
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