/** * Copyright (c) 2019 Paul Armstrong */ import * as Theme from '../theme'; import React from 'react'; import ReactDOM from 'react-dom'; import { AccessibilityRole, Dimensions, GestureResponderEvent, StyleSheet, TouchableOpacity, View } from 'react-native'; interface Props { accessibilityRole?: AccessibilityRole; children?: React.ReactElement | Array; onDismiss?: (event: GestureResponderEvent) => void; portalRootID?: string; relativeTo: React.RefObject; } const Menu = (props: Props): React.ReactElement => { const { accessibilityRole, children, onDismiss, portalRootID, relativeTo } = props; const [position, setPosition] = React.useState({ top: -999, left: 0 }); const portalRoot = portalRootID && document.getElementById(portalRootID); const ref = React.useRef(null); const handlePressOutside = React.useCallback( (event: GestureResponderEvent): void => { onDismiss && onDismiss(event); }, [onDismiss] ); React.useEffect(() => { if (ref.current && relativeTo.current) { const { width: windowWidth, height: windowHeight } = Dimensions.get('window'); ref.current.measure((_x: number, _y: number, menuWidth: number, menuHeight: number): void => { relativeTo.current.measureInWindow((x: number, y: number, _width: number, height: number): void => { let top = y + height; let left = x; // too far right if (left + menuWidth > windowWidth) { left = windowWidth - menuWidth; } // too far left else if (left < 0) { left = 0; } // too close to bottom if (top + menuHeight > windowHeight) { top = y - menuHeight; } ref.current && setPosition({ left, top }); }); }); } }, [relativeTo]); const menu = ( <> 0 && { top: position.top, left: position.left }, position.top > 0 && styles.show, ]} > {React.Children.toArray(children)} ); return portalRoot ? ReactDOM.createPortal(menu, portalRoot) : menu; }; const styles = StyleSheet.create({ root: { position: 'absolute', backgroundColor: Theme.Color.White, borderRadius: Theme.BorderRadius.Normal, shadowOffset: { width: 0, height: 2 }, shadowColor: Theme.Color.Black, shadowOpacity: 0.3, shadowRadius: 5, elevation: 10, width: 'auto', maxHeight: 400, minHeight: '2rem', minWidth: 200, transitionProperty: 'transform, opacity', transitionDuration: '0.1s', transitionTimingFunction: Theme.MotionTiming.Accelerate, transform: [{ scale: 0.85 }], opacity: 0, }, show: { transform: [{ scale: 1 }], opacity: 1, }, }); export default Menu;