From 98dac168619fd9133300f0403ad57e1aab93ceb1 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Wed, 10 Jan 2024 13:06:44 +0100 Subject: [PATCH 1/4] migrate ButtonWithDropdownMenu to TypeScript --- ...downMenu.js => ButtonWithDropdownMenu.tsx} | 148 +++++++++--------- src/components/PopoverMenu.tsx | 2 +- 2 files changed, 75 insertions(+), 75 deletions(-) rename src/components/{ButtonWithDropdownMenu.js => ButtonWithDropdownMenu.tsx} (54%) diff --git a/src/components/ButtonWithDropdownMenu.js b/src/components/ButtonWithDropdownMenu.tsx similarity index 54% rename from src/components/ButtonWithDropdownMenu.js rename to src/components/ButtonWithDropdownMenu.tsx index 4d3ec8796a31..8cd1086a9392 100644 --- a/src/components/ButtonWithDropdownMenu.js +++ b/src/components/ButtonWithDropdownMenu.tsx @@ -1,89 +1,89 @@ -import PropTypes from 'prop-types'; +import type {RefObject} from 'react'; import React, {useEffect, useRef, useState} from 'react'; +import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; -import _ from 'underscore'; +import type {SvgProps} from 'react-native-svg'; +import type {ValueOf} from 'type-fest'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import type {AnchorPosition} from '@styles/index'; import CONST from '@src/CONST'; import Button from './Button'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; -import sourcePropTypes from './Image/sourcePropTypes'; +import type {AnchorAlignment} from './Popover/types'; import PopoverMenu from './PopoverMenu'; -const propTypes = { +type DropdownOption = { + value: string; + text: string; + icon: React.FC; + iconWidth?: number; + iconHeight?: number; + iconDescription?: string; +}; + +type ButtonWithDropdownMenuProps = { /** Text to display for the menu header */ - menuHeaderText: PropTypes.string, + menuHeaderText?: string; /** Callback to execute when the main button is pressed */ - onPress: PropTypes.func.isRequired, + onPress: (event: GestureResponderEvent | KeyboardEvent | undefined, value: string) => void; /** Call the onPress function on main button when Enter key is pressed */ - pressOnEnter: PropTypes.bool, + pressOnEnter?: boolean; /** Whether we should show a loading state for the main button */ - isLoading: PropTypes.bool, + isLoading?: boolean; /** The size of button size */ - buttonSize: PropTypes.oneOf(_.values(CONST.DROPDOWN_BUTTON_SIZE)), + buttonSize: ValueOf; /** Should the confirmation button be disabled? */ - isDisabled: PropTypes.bool, + isDisabled?: boolean; /** Additional styles to add to the component */ - style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), + style?: StyleProp; /** Menu options to display */ /** e.g. [{text: 'Pay with Expensify', icon: Wallet}] */ - options: PropTypes.arrayOf( - PropTypes.shape({ - value: PropTypes.string.isRequired, - text: PropTypes.string.isRequired, - icon: sourcePropTypes, - iconWidth: PropTypes.number, - iconHeight: PropTypes.number, - iconDescription: PropTypes.string, - }), - ).isRequired, + options: DropdownOption[]; /** The anchor alignment of the popover menu */ - anchorAlignment: PropTypes.shape({ - horizontal: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL)), - vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)), - }), + anchorAlignment?: AnchorAlignment; /* ref for the button */ - buttonRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + buttonRef: RefObject; }; -const defaultProps = { - isLoading: false, - isDisabled: false, - pressOnEnter: false, - menuHeaderText: '', - style: [], - buttonSize: CONST.DROPDOWN_BUTTON_SIZE.MEDIUM, - anchorAlignment: { +function ButtonWithDropdownMenu({ + isLoading = false, + isDisabled = false, + pressOnEnter = false, + menuHeaderText = '', + style = [], + buttonSize = CONST.DROPDOWN_BUTTON_SIZE.MEDIUM, + anchorAlignment = { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, // we assume that popover menu opens below the button, anchor is at TOP }, - buttonRef: () => {}, -}; - -function ButtonWithDropdownMenu(props) { + buttonRef, + onPress, + options, +}: ButtonWithDropdownMenuProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const [selectedItemIndex, setSelectedItemIndex] = useState(0); const [isMenuVisible, setIsMenuVisible] = useState(false); - const [popoverAnchorPosition, setPopoverAnchorPosition] = useState(null); + const [popoverAnchorPosition, setPopoverAnchorPosition] = useState(null); const {windowWidth, windowHeight} = useWindowDimensions(); - const caretButton = useRef(null); - const selectedItem = props.options[selectedItemIndex] || _.first(props.options); - const innerStyleDropButton = StyleUtils.getDropDownButtonHeight(props.buttonSize); - const isButtonSizeLarge = props.buttonSize === CONST.DROPDOWN_BUTTON_SIZE.LARGE; + const caretButton = useRef(null); + const selectedItem = options[selectedItemIndex] || options[0]; + const innerStyleDropButton = StyleUtils.getDropDownButtonHeight(buttonSize); + const isButtonSizeLarge = buttonSize === CONST.DROPDOWN_BUTTON_SIZE.LARGE; useEffect(() => { if (!caretButton.current) { @@ -92,29 +92,31 @@ function ButtonWithDropdownMenu(props) { if (!isMenuVisible) { return; } - caretButton.current.measureInWindow((x, y, w, h) => { - setPopoverAnchorPosition({ - horizontal: x + w, - vertical: - props.anchorAlignment.vertical === CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP - ? y + h + CONST.MODAL.POPOVER_MENU_PADDING // if vertical anchorAlignment is TOP, menu will open below the button and we need to add the height of button and padding - : y - CONST.MODAL.POPOVER_MENU_PADDING, // if it is BOTTOM, menu will open above the button so NO need to add height but DO subtract padding + if ('measureInWindow' in caretButton.current) { + caretButton.current.measureInWindow((x, y, w, h) => { + setPopoverAnchorPosition({ + horizontal: x + w, + vertical: + anchorAlignment.vertical === CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP + ? y + h + CONST.MODAL.POPOVER_MENU_PADDING // if vertical anchorAlignment is TOP, menu will open below the button and we need to add the height of button and padding + : y - CONST.MODAL.POPOVER_MENU_PADDING, // if it is BOTTOM, menu will open above the button so NO need to add height but DO subtract padding + }); }); - }); - }, [windowWidth, windowHeight, isMenuVisible, props.anchorAlignment.vertical]); + } + }, [windowWidth, windowHeight, isMenuVisible, anchorAlignment.vertical]); return ( - {props.options.length > 1 ? ( - + {options.length > 1 ? ( +