import { Portal } from '@gorhom/portal' import React, { NamedExoticComponent, PropsWithChildren, memo, useEffect } from 'react' import { View, Dimensions, Platform } from 'react-native' import { MenuItem } from '../../molecules/MenuItem' import { useColorScheme } from '@/contexts' import { Box, TouchableProps, ScrollView, Pressable } from '@/design-system' import { useRef, useState, useMemo, useTheme, useCallback } from '@/hooks' type TriggerPosition = { x: number y: number width: number height: number } | null type TriggerState = { isOpen: boolean } export type MenuProps = { trigger: (props: TouchableProps, state: TriggerState) => JSX.Element onOpen?: () => void onClose?: () => void closeOnSelect?: boolean isOpen?: boolean placement?: | 'top' | 'topLeft' | 'topRight' | 'bottom' | 'bottomLeft' | 'bottomRight' | 'left' | 'right' scrollable?: boolean } & PropsWithChildren type MenuComposition = NamedExoticComponent & { Item: typeof MenuItem } const screenWidth = Dimensions.get('window').width const screenHeight = Dimensions.get('window').height const Menu = memo( ({ trigger, onOpen, onClose, closeOnSelect = true, scrollable = false, placement = 'bottomLeft', ...props }) => { const { colorScheme } = useColorScheme() const { shadows } = useTheme() const _triggerContainer = useRef(null) const [triggerPosition, setTriggerPosition] = useState(null) const [isOpen, setOpen] = useState(props.isOpen || false) const _measureTriggerPosition = useCallback(() => { _triggerContainer.current?.measureInWindow((x, y, width, height) => { switch (placement) { case 'top': setTriggerPosition({ x, y: y - height, width, height }) return case 'topLeft': setTriggerPosition({ x: x - width, y: y - height, width, height }) return case 'topRight': setTriggerPosition({ x: x + width, y: y - height, width, height }) return case 'bottom': setTriggerPosition({ x, y: y + height, width, height }) return case 'bottomLeft': setTriggerPosition({ x: x - width, y: y + height, width, height }) return case 'bottomRight': setTriggerPosition({ x: x + width, y: y + height, width, height }) return case 'left': setTriggerPosition({ x: x - width, y, width, height }) return case 'right': x = x + width setTriggerPosition({ x: x + width, y, width, height }) return default: // default is bottomLeft setTriggerPosition({ x, y: y + height, width, height }) } }) }, [placement]) useEffect(() => { if (isOpen && !triggerPosition && Platform.OS === 'ios') { _measureTriggerPosition() } }, [_measureTriggerPosition, isOpen, triggerPosition]) const handleOpen = useCallback(() => { setOpen(true) onOpen?.() }, [onOpen]) const handleClose = useCallback(() => { setOpen(false) onClose?.() }, [onClose]) const triggerTouchableProps = useMemo( () => ({ onPress: handleOpen, }), [handleOpen] ) return ( <> {trigger(triggerTouchableProps, { isOpen })} {isOpen && ( {triggerPosition && ( {React.Children.map(props.children, (child) => { if (React.isValidElement(child)) { return React.cloneElement(child, { ...child.props, onPress: () => { if (closeOnSelect) { handleClose() } child.props.onPress?.() }, }) } return child })} )} )} ) } ) as MenuComposition Menu.Item = MenuItem export { Menu }