import React, { useEffect, useRef, useState } from 'react'; import { Dimensions, Modal, StyleProp, StyleSheet, Text, TextStyle, TouchableOpacity, TouchableWithoutFeedback, View, ViewStyle } from 'react-native'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import { runOnJS } from 'react-native-reanimated'; interface MenuItem { label: string; svg?: React.ReactNode; onPress: () => void; textStyle?: StyleProp; } interface ContextMenuProps { children: React.ReactNode; items: MenuItem[]; menuStyle?: StyleProp; menuItemStyle?: StyleProp; menuTextStyle?: StyleProp; dividerStyle?: StyleProp; onOpen?: () => void; onClose?: () => void; longPressDuration?: number; activeOpacity?: number; cancel?: boolean; } const ContextMenu: React.FC = ({ children, items, menuStyle, menuItemStyle, menuTextStyle, dividerStyle, cancel, onOpen, onClose, longPressDuration = 500, activeOpacity = 0.8, }) => { const { width: screenWidth, height: screenHeight } = Dimensions.get('window'); const [menuVisible, setMenuVisible] = useState(false); const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 }); const containerRef = useRef(null); const showMenu = (x: number, y: number) => { setMenuPosition({ x, y }); setMenuVisible(true); onOpen?.(); }; const hideMenu = () => { setMenuVisible(false); onClose?.(); }; const handleItemPress = (onPress: () => void) => { onPress(); hideMenu(); }; const gesture = Gesture.LongPress() .minDuration(longPressDuration) .onStart((e) => { const absoluteX = e.absoluteX; const absoluteY = e.absoluteY; runOnJS(showMenu)(absoluteX, absoluteY); }); useEffect(() => { setMenuVisible(!cancel); }, [cancel]) return ( <> {children} screenWidth / 2 ? menuPosition.x - 150 : menuPosition.x, screenWidth - 160 ), }, menuStyle, ]} onStartShouldSetResponder={() => true} > {items.map((item, index) => ( handleItemPress(item.onPress)} activeOpacity={activeOpacity} > {item.svg} {item.label} {index < items.length - 1 && ( )} ))} ); }; const styles = StyleSheet.create({ modalOverlay: { ...StyleSheet.absoluteFillObject, backgroundColor: 'rgba(0, 0, 0, 0.1)', }, menu: { backgroundColor: 'white', borderRadius: 8, minWidth: 100, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.3, shadowRadius: 6, elevation: 10, zIndex: 1000, }, menuItem: { paddingVertical: 12, paddingHorizontal: 16, minWidth: 100, flexDirection: 'row', gap: 4, alignItems: 'center' }, menuText: { fontSize: 16, color: '#333', }, divider: { height: 1, backgroundColor: '#f0f0f0', marginHorizontal: 8, }, }); export default ContextMenu;