import React, {PropsWithChildren, useMemo, useRef} from 'react' import { Dimensions, StyleProp, StyleSheet, TouchableOpacity, TouchableWithoutFeedback, View, ViewStyle, } from 'react-native' import {IconProp} from '@fortawesome/fontawesome-svg-core' import RootSiblings from 'react-native-root-siblings' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {Text} from '../text/Text' import {Button, ButtonType} from './Button' import {colors} from 'lib/styles' import {usePalette} from 'lib/hooks/usePalette' import {useTheme} from 'lib/ThemeContext' import {HITSLOP_10} from 'lib/constants' import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' 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 ref1 = useRef(null) const ref2 = useRef(null) const onPress = () => { const ref = ref1.current || ref2.current ref?.measure( ( _x: number, _y: number, width: number, height: number, pageX: number, pageY: number, ) => { if (!menuWidth) { menuWidth = 200 } const winHeight = Dimensions.get('window').height 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 let newY = pageY + height + bottomOffset if (openUpwards || newY + estimatedMenuHeight > winHeight) { newY -= estimatedMenuHeight } createDropdownMenu( newX, newY, menuWidth, items.filter(v => !!v) as DropdownItem[], ) }, ) } 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, width: number, items: DropdownItem[], ): 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 width: number items: DropdownItem[] onPressItem: (index: number) => void } const DropdownItems = ({ onOuterPress, x, y, width, items, onPressItem, }: DropDownItemProps) => { const pal = usePalette('default') const theme = useTheme() const {_} = useLingui() 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