import React, {PropsWithChildren, useMemo, useRef} from 'react' import { Dimensions, GestureResponderEvent, StyleProp, StyleSheet, TouchableOpacity, TouchableWithoutFeedback, useWindowDimensions, View, ViewStyle, } from 'react-native' import Animated, {FadeIn, FadeInDown, FadeInUp} from 'react-native-reanimated' import RootSiblings from 'react-native-root-siblings' import {IconProp} from '@fortawesome/fontawesome-svg-core' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {HITSLOP_10} from 'lib/constants' import {usePalette} from 'lib/hooks/usePalette' import {colors} from 'lib/styles' import {useTheme} from 'lib/ThemeContext' import {isWeb} from 'platform/detection' import {native} from '#/alf' import {FullWindowOverlay} from '#/components/FullWindowOverlay' import {Text} from '../text/Text' import {Button, ButtonType} from './Button' const ESTIMATED_BTN_HEIGHT = 50 const ESTIMATED_SEP_HEIGHT = 16 const ESTIMATED_HEADING_HEIGHT = 60 export interface DropdownItemButton { testID?: string icon?: IconProp label: string onPress: () => void } export interface DropdownItemSeparator { sep: true } export interface DropdownItemHeading { heading: true label: string } export type DropdownItem = | DropdownItemButton | DropdownItemSeparator | DropdownItemHeading type MaybeDropdownItem = DropdownItem | false | undefined export type DropdownButtonType = ButtonType | 'bare' interface DropdownButtonProps { testID?: string type?: DropdownButtonType style?: StyleProp items: MaybeDropdownItem[] label?: string menuWidth?: number children?: React.ReactNode openToRight?: boolean openUpwards?: boolean rightOffset?: number bottomOffset?: number accessibilityLabel?: string accessibilityHint?: string } export function DropdownButton({ testID, type = 'bare', style, items, label, menuWidth, children, openToRight = false, openUpwards = false, rightOffset = 0, bottomOffset = 0, accessibilityLabel, }: PropsWithChildren) { const {_} = useLingui() const ref1 = useRef(null) const ref2 = useRef(null) const onPress = (e: GestureResponderEvent) => { const ref = ref1.current || ref2.current const {height: winHeight} = Dimensions.get('window') const pressY = e.nativeEvent.pageY ref?.measure( ( _x: number, _y: number, width: number, _height: number, pageX: number, pageY: number, ) => { if (!menuWidth) { menuWidth = 200 } let estimatedMenuHeight = 0 for (const item of items) { if (item && isSep(item)) { estimatedMenuHeight += ESTIMATED_SEP_HEIGHT } else if (item && isBtn(item)) { estimatedMenuHeight += ESTIMATED_BTN_HEIGHT } else if (item && isHeading(item)) { estimatedMenuHeight += ESTIMATED_HEADING_HEIGHT } } const newX = openToRight ? pageX + width + rightOffset : pageX + width - menuWidth // Add a bit of additional room let newY = pressY + bottomOffset + 20 if (openUpwards || newY + estimatedMenuHeight > winHeight) { newY -= estimatedMenuHeight } createDropdownMenu( newX, newY, pageY, menuWidth, items.filter(v => !!v) as DropdownItem[], openUpwards, ) }, ) } const numItems = useMemo( () => items.filter(item => { if (item === undefined || item === false) { return false } return isBtn(item) }).length, [items], ) if (type === 'bare') { return ( {children} ) } return ( ) } function createDropdownMenu( x: number, y: number, pageY: number, width: number, items: DropdownItem[], opensUpwards = false, ): RootSiblings { const onPressItem = (index: number) => { sibling.destroy() const item = items[index] if (isBtn(item)) { item.onPress() } } const onOuterPress = () => sibling.destroy() const sibling = new RootSiblings( ( ), ) return sibling } type DropDownItemProps = { onOuterPress: () => void x: number y: number pageY: number width: number items: DropdownItem[] onPressItem: (index: number) => void openUpwards: boolean } const DropdownItems = ({ onOuterPress, x, y, pageY, width, items, onPressItem, openUpwards, }: DropDownItemProps) => { const pal = usePalette('default') const theme = useTheme() const {_} = useLingui() const {height: screenHeight} = useWindowDimensions() const dropDownBackgroundColor = theme.colorScheme === 'dark' ? pal.btn : pal.view const separatorColor = theme.colorScheme === 'dark' ? pal.borderDark : pal.border const numItems = items.filter(isBtn).length // TODO: Refactor dropdown components to: // - (On web, if not handled by React Native) use semantic