From 9072dbc094572a6a59c8eb1342fb551eef3b35e9 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 27 Dec 2023 17:03:34 +0100 Subject: [PATCH 1/6] [TS migration] Migrate 'Section.js' component to TypeScript --- src/components/MenuItem.tsx | 2 +- src/components/MenuItemList.js | 62 --------- src/components/MenuItemList.tsx | 65 ++++++++++ src/components/Section/IconSection.js | 41 ------ src/components/Section/IconSection.tsx | 30 +++++ src/components/Section/index.js | 122 ------------------ src/components/Section/index.tsx | 104 +++++++++++++++ .../ContextMenu/ReportActionContextMenu.ts | 6 +- 8 files changed, 203 insertions(+), 229 deletions(-) delete mode 100644 src/components/MenuItemList.js create mode 100644 src/components/MenuItemList.tsx delete mode 100644 src/components/Section/IconSection.js create mode 100644 src/components/Section/IconSection.tsx delete mode 100644 src/components/Section/index.js create mode 100644 src/components/Section/index.tsx diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index db150d55f0d2..9e5d84166c0e 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -220,7 +220,7 @@ type MenuItemProps = (ResponsiveProps | UnresponsiveProps) & furtherDetails?: string; /** The function that should be called when this component is LongPressed or right-clicked. */ - onSecondaryInteraction: () => void; + onSecondaryInteraction?: (event: GestureResponderEvent | MouseEvent) => void; /** Array of objects that map display names to their corresponding tooltip */ titleWithTooltips: DisplayNameWithTooltip[]; diff --git a/src/components/MenuItemList.js b/src/components/MenuItemList.js deleted file mode 100644 index c9eee8e888e1..000000000000 --- a/src/components/MenuItemList.js +++ /dev/null @@ -1,62 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import _ from 'underscore'; -import useSingleExecution from '@hooks/useSingleExecution'; -import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; -import CONST from '@src/CONST'; -import MenuItem from './MenuItem'; -import menuItemPropTypes from './menuItemPropTypes'; - -const propTypes = { - /** An array of props that are pass to individual MenuItem components */ - menuItems: PropTypes.arrayOf(PropTypes.shape(menuItemPropTypes)), - - /** Whether or not to use the single execution hook */ - shouldUseSingleExecution: PropTypes.bool, -}; -const defaultProps = { - menuItems: [], - shouldUseSingleExecution: false, -}; - -function MenuItemList(props) { - let popoverAnchor; - const {isExecuting, singleExecution} = useSingleExecution(); - - /** - * Handle the secondary interaction for a menu item. - * - * @param {*} link the menu item link or function to get the link - * @param {Event} e the interaction event - */ - const secondaryInteraction = (link, e) => { - if (typeof link === 'function') { - link().then((url) => ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, e, url, popoverAnchor)); - } else if (!_.isEmpty(link)) { - ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, e, link, popoverAnchor); - } - }; - - return ( - <> - {_.map(props.menuItems, (menuItemProps) => ( - secondaryInteraction(menuItemProps.link, e) : undefined} - ref={(el) => (popoverAnchor = el)} - shouldBlockSelection={Boolean(menuItemProps.link)} - // eslint-disable-next-line react/jsx-props-no-spreading - {...menuItemProps} - disabled={menuItemProps.disabled || isExecuting} - onPress={props.shouldUseSingleExecution ? singleExecution(menuItemProps.onPress) : menuItemProps.onPress} - /> - ))} - - ); -} - -MenuItemList.displayName = 'MenuItemList'; -MenuItemList.propTypes = propTypes; -MenuItemList.defaultProps = defaultProps; - -export default MenuItemList; diff --git a/src/components/MenuItemList.tsx b/src/components/MenuItemList.tsx new file mode 100644 index 000000000000..2e1015d092ca --- /dev/null +++ b/src/components/MenuItemList.tsx @@ -0,0 +1,65 @@ +import React, {useRef} from 'react'; +import {GestureResponderEvent, View} from 'react-native'; +import useSingleExecution from '@hooks/useSingleExecution'; +import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; +import CONST from '@src/CONST'; +import MenuItem, {MenuItemProps} from './MenuItem'; + +type MenuItemWithLink = MenuItemProps & { + /** The link to open when the menu item is clicked */ + link: string | (() => Promise); +}; + +type MenuItemListProps = { + /** An array of props that are pass to individual MenuItem components */ + menuItems: MenuItemWithLink[]; + + /** Whether or not to use the single execution hook */ + shouldUseSingleExecution?: boolean; +}; + +function MenuItemList({menuItems = [], shouldUseSingleExecution = false}: MenuItemListProps) { + const popoverAnchor = useRef(null); + const {isExecuting, singleExecution} = useSingleExecution(); + + /** + * Handle the secondary interaction for a menu item. + * + * @param link the menu item link or function to get the link + * @param e the interaction event + */ + const secondaryInteraction = (link: MenuItemWithLink['link'], event: GestureResponderEvent | MouseEvent) => { + if (typeof link === 'function') { + link().then((url) => ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, event, url, popoverAnchor.current)); + } else if (link) { + ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, event, link, popoverAnchor.current); + } + }; + + return ( + <> + {menuItems.map((menuItemProps) => { + const onPress = menuItemProps.onPress ?? (() => {}); + + return ( + secondaryInteraction(menuItemProps.link, e) : undefined} + ref={popoverAnchor} + shouldBlockSelection={!!menuItemProps.link} + // eslint-disable-next-line react/jsx-props-no-spreading + {...menuItemProps} + disabled={menuItemProps.disabled ?? isExecuting} + onPress={shouldUseSingleExecution ? singleExecution(onPress) : onPress} + interactive + /> + ); + })} + + ); +} + +MenuItemList.displayName = 'MenuItemList'; + +export type {MenuItemWithLink}; +export default MenuItemList; diff --git a/src/components/Section/IconSection.js b/src/components/Section/IconSection.js deleted file mode 100644 index 307331aa36d6..000000000000 --- a/src/components/Section/IconSection.js +++ /dev/null @@ -1,41 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import {View} from 'react-native'; -import Icon from '@components/Icon'; -import sourcePropTypes from '@components/Image/sourcePropTypes'; -import useThemeStyles from '@hooks/useThemeStyles'; - -const iconSectionPropTypes = { - icon: sourcePropTypes, - IconComponent: PropTypes.IconComponent, - iconContainerStyles: PropTypes.iconContainerStyles, -}; - -const defaultIconSectionPropTypes = { - icon: null, - IconComponent: null, - iconContainerStyles: [], -}; - -function IconSection({icon, IconComponent, iconContainerStyles}) { - const styles = useThemeStyles(); - - return ( - - {Boolean(icon) && ( - - )} - {Boolean(IconComponent) && } - - ); -} - -IconSection.displayName = 'IconSection'; -IconSection.propTypes = iconSectionPropTypes; -IconSection.defaultProps = defaultIconSectionPropTypes; - -export default IconSection; diff --git a/src/components/Section/IconSection.tsx b/src/components/Section/IconSection.tsx new file mode 100644 index 000000000000..ddadbcdb1d77 --- /dev/null +++ b/src/components/Section/IconSection.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import {StyleProp, View, ViewStyle} from 'react-native'; +import Icon from '@components/Icon'; +import useThemeStyles from '@hooks/useThemeStyles'; +import IconAsset from '@src/types/utils/IconAsset'; + +type IconSectionProps = { + icon?: IconAsset; + iconContainerStyles?: StyleProp; +}; + +function IconSection({icon, iconContainerStyles}: IconSectionProps) { + const styles = useThemeStyles(); + + return ( + + {!!icon && ( + + )} + + ); +} + +IconSection.displayName = 'IconSection'; + +export default IconSection; diff --git a/src/components/Section/index.js b/src/components/Section/index.js deleted file mode 100644 index 50576abef025..000000000000 --- a/src/components/Section/index.js +++ /dev/null @@ -1,122 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import {View} from 'react-native'; -import sourcePropTypes from '@components/Image/sourcePropTypes'; -import MenuItemList from '@components/MenuItemList'; -import menuItemPropTypes from '@components/menuItemPropTypes'; -import Text from '@components/Text'; -import useThemeStyles from '@hooks/useThemeStyles'; -import IconSection from './IconSection'; - -const CARD_LAYOUT = { - ICON_ON_TOP: 'iconOnTop', - ICON_ON_RIGHT: 'iconOnRight', -}; - -const propTypes = { - /** An array of props that are pass to individual MenuItem components */ - menuItems: PropTypes.arrayOf(PropTypes.shape(menuItemPropTypes)), - - /** The text to display in the title of the section */ - title: PropTypes.string.isRequired, - - /** The text to display in the subtitle of the section */ - subtitle: PropTypes.string, - - /** The icon to display along with the title */ - icon: sourcePropTypes, - - /** Icon component */ - IconComponent: PropTypes.func, - - /** Card layout that affects icon positioning, margins, sizes. */ - // eslint-disable-next-line rulesdir/prefer-underscore-method - cardLayout: PropTypes.oneOf(Object.values(CARD_LAYOUT)), - - /** Contents to display inside the section */ - children: PropTypes.node, - - /** Customize the Section container */ - // eslint-disable-next-line react/forbid-prop-types - containerStyles: PropTypes.arrayOf(PropTypes.object), - - /** Customize the Section container */ - // eslint-disable-next-line react/forbid-prop-types - titleStyles: PropTypes.arrayOf(PropTypes.object), - - /** Customize the Section container */ - // eslint-disable-next-line react/forbid-prop-types - subtitleStyles: PropTypes.arrayOf(PropTypes.object), - - /** Whether the subtitle should have a muted style */ - subtitleMuted: PropTypes.bool, - - /** Customize the Section container */ - // eslint-disable-next-line react/forbid-prop-types - childrenStyles: PropTypes.arrayOf(PropTypes.object), - - /** Customize the Icon container */ - // eslint-disable-next-line react/forbid-prop-types - iconContainerStyles: PropTypes.arrayOf(PropTypes.object), -}; - -const defaultProps = { - menuItems: null, - children: null, - icon: null, - IconComponent: null, - cardLayout: CARD_LAYOUT.ICON_ON_RIGHT, - containerStyles: [], - iconContainerStyles: [], - titleStyles: [], - subtitleStyles: [], - subtitleMuted: false, - childrenStyles: [], - subtitle: null, -}; - -function Section({children, childrenStyles, containerStyles, icon, IconComponent, cardLayout, iconContainerStyles, menuItems, subtitle, subtitleStyles, subtitleMuted, title, titleStyles}) { - const styles = useThemeStyles(); - - return ( - <> - - {cardLayout === CARD_LAYOUT.ICON_ON_TOP && ( - - )} - - - {title} - - {cardLayout === CARD_LAYOUT.ICON_ON_RIGHT && ( - - )} - - - {Boolean(subtitle) && ( - - {subtitle} - - )} - - {children} - - {Boolean(menuItems) && } - - - ); -} -Section.displayName = 'Section'; -Section.propTypes = propTypes; -Section.defaultProps = defaultProps; - -export {CARD_LAYOUT}; -export default Section; diff --git a/src/components/Section/index.tsx b/src/components/Section/index.tsx new file mode 100644 index 000000000000..7f458d2ed8a5 --- /dev/null +++ b/src/components/Section/index.tsx @@ -0,0 +1,104 @@ +import React from 'react'; +import {StyleProp, View, ViewStyle} from 'react-native'; +import {ValueOf} from 'type-fest'; +import MenuItemList, {MenuItemWithLink} from '@components/MenuItemList'; +import Text from '@components/Text'; +import useThemeStyles from '@hooks/useThemeStyles'; +import ChildrenProps from '@src/types/utils/ChildrenProps'; +import IconAsset from '@src/types/utils/IconAsset'; +import IconSection from './IconSection'; + +const CARD_LAYOUT = { + ICON_ON_TOP: 'iconOnTop', + ICON_ON_RIGHT: 'iconOnRight', +} as const; + +type SectionProps = ChildrenProps & { + /** An array of props that are passed to individual MenuItem components */ + menuItems?: MenuItemWithLink[]; + + /** The text to display in the title of the section */ + title: string; + + /** The text to display in the subtitle of the section */ + subtitle?: string; + + /** The icon to display along with the title */ + icon?: IconAsset; + + /** Card layout that affects icon positioning, margins, sizes. */ + cardLayout?: ValueOf; + + /** Whether the subtitle should have a muted style */ + subtitleMuted?: boolean; + + /** Customize the Section container */ + containerStyles?: StyleProp; + + /** Customize the Section container */ + titleStyles?: StyleProp; + + /** Customize the Section container */ + subtitleStyles?: StyleProp; + + /** Customize the Section container */ + childrenStyles?: StyleProp; + + /** Customize the Icon container */ + iconContainerStyles?: StyleProp; +}; + +function Section({ + children, + childrenStyles, + containerStyles, + icon, + cardLayout = CARD_LAYOUT.ICON_ON_RIGHT, + iconContainerStyles, + menuItems, + subtitle, + subtitleStyles, + subtitleMuted = false, + title, + titleStyles, +}: SectionProps) { + const styles = useThemeStyles(); + + return ( + <> + + {cardLayout === CARD_LAYOUT.ICON_ON_TOP && ( + + )} + + + {title} + + {cardLayout === CARD_LAYOUT.ICON_ON_RIGHT && ( + + )} + + + {!!subtitle && ( + + {subtitle} + + )} + + {children} + + {!!menuItems && } + + + ); +} +Section.displayName = 'Section'; + +export {CARD_LAYOUT}; +export default Section; diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts b/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts index b269bc276b55..c7e712afc559 100644 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts @@ -1,5 +1,5 @@ import React from 'react'; -import {GestureResponderEvent, Text as RNText} from 'react-native'; +import {GestureResponderEvent, Text as RNText, View} from 'react-native'; import {OnyxEntry} from 'react-native-onyx'; import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; @@ -17,7 +17,7 @@ type ShowContextMenu = ( type: ContextMenuType, event: GestureResponderEvent | MouseEvent, selection: string, - contextMenuAnchor: RNText | null, + contextMenuAnchor: View | RNText | null, reportID?: string, reportActionID?: string, originalReportID?: string, @@ -94,7 +94,7 @@ function showContextMenu( type: ContextMenuType, event: GestureResponderEvent | MouseEvent, selection: string, - contextMenuAnchor: RNText | null, + contextMenuAnchor: View | RNText | null, reportID = '0', reportActionID = '0', originalReportID = '0', From fe574012fb26f2d2be3567ea1a69b0cc79a9e02a Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 27 Dec 2023 18:07:23 +0100 Subject: [PATCH 2/6] Adjust MenuItem and MenuItemList --- package.json | 2 +- src/components/MenuItem.tsx | 241 +++++++++++++------------- src/components/MenuItemList.tsx | 39 ++--- src/components/Section/index.tsx | 2 +- src/hooks/useSingleExecution/index.ts | 4 +- 5 files changed, 138 insertions(+), 150 deletions(-) diff --git a/package.json b/package.json index df30c549c515..43b74ab0de55 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "private": true, "scripts": { "configure-mapbox": "scripts/setup-mapbox-sdk-walkthrough.sh", - "setupNewDotWebForEmulators": "scripts/setup-newdot-web-emulators.sh", + "setupNewDotWebForEmulators": "scripts/psetup-newdot-web-emulators.sh", "startAndroidEmulator": "scripts/start-android.sh", "postinstall": "scripts/postInstall.sh", "clean": "npx react-native clean-project-auto", diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index 9e5d84166c0e..9ac65e438a48 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -33,20 +33,6 @@ import RenderHTML from './RenderHTML'; import SelectCircle from './SelectCircle'; import Text from './Text'; -type ResponsiveProps = { - /** Function to fire when component is pressed */ - onPress: (event: GestureResponderEvent | KeyboardEvent) => void; - - interactive?: true; -}; - -type UnresponsiveProps = { - onPress?: undefined; - - /** Whether the menu item should be interactive at all */ - interactive: false; -}; - type IconProps = { /** Flag to choose between avatar image or an icon */ iconType: typeof CONST.ICON_TYPE_ICON; @@ -67,170 +53,175 @@ type NoIcon = { icon?: undefined; }; -type MenuItemProps = (ResponsiveProps | UnresponsiveProps) & - (IconProps | AvatarProps | NoIcon) & { - /** Text to be shown as badge near the right end. */ - badgeText?: string; +type MenuItemProps = (IconProps | AvatarProps | NoIcon) & { + /** Function to fire when component is pressed */ + onPress?: (event: GestureResponderEvent | KeyboardEvent) => void; - /** Used to apply offline styles to child text components */ - style?: ViewStyle; + /** Whether the menu item should be interactive at all */ + interactive?: boolean; - /** Any additional styles to apply */ - wrapperStyle?: StyleProp; + /** Text to be shown as badge near the right end. */ + badgeText?: string; - /** Any additional styles to apply on the outer element */ - containerStyle?: StyleProp; + /** Used to apply offline styles to child text components */ + style?: ViewStyle; - /** Used to apply styles specifically to the title */ - titleStyle?: ViewStyle; + /** Any additional styles to apply */ + wrapperStyle?: StyleProp; - /** Any adjustments to style when menu item is hovered or pressed */ - hoverAndPressStyle: StyleProp>; + /** Any additional styles to apply on the outer element */ + containerStyle?: StyleProp; - /** Additional styles to style the description text below the title */ - descriptionTextStyle?: StyleProp; + /** Used to apply styles specifically to the title */ + titleStyle?: ViewStyle; - /** The fill color to pass into the icon. */ - iconFill?: string; + /** Any adjustments to style when menu item is hovered or pressed */ + hoverAndPressStyle: StyleProp>; - /** Secondary icon to display on the left side of component, right of the icon */ - secondaryIcon?: IconAsset; + /** Additional styles to style the description text below the title */ + descriptionTextStyle?: StyleProp; - /** The fill color to pass into the secondary icon. */ - secondaryIconFill?: string; + /** The fill color to pass into the icon. */ + iconFill?: string; - /** Icon Width */ - iconWidth?: number; + /** Secondary icon to display on the left side of component, right of the icon */ + secondaryIcon?: IconAsset; - /** Icon Height */ - iconHeight?: number; + /** The fill color to pass into the secondary icon. */ + secondaryIconFill?: string; - /** Any additional styles to pass to the icon container. */ - iconStyles?: StyleProp; + /** Icon Width */ + iconWidth?: number; - /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */ - fallbackIcon?: IconAsset; + /** Icon Height */ + iconHeight?: number; - /** An icon to display under the main item */ - furtherDetailsIcon?: IconAsset; + /** Any additional styles to pass to the icon container. */ + iconStyles?: StyleProp; - /** Boolean whether to display the title right icon */ - shouldShowTitleIcon?: boolean; + /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */ + fallbackIcon?: IconAsset; - /** Icon to display at right side of title */ - titleIcon?: IconAsset; + /** An icon to display under the main item */ + furtherDetailsIcon?: IconAsset; - /** Boolean whether to display the right icon */ - shouldShowRightIcon?: boolean; + /** Boolean whether to display the title right icon */ + shouldShowTitleIcon?: boolean; - /** Overrides the icon for shouldShowRightIcon */ - iconRight?: IconAsset; + /** Icon to display at right side of title */ + titleIcon?: IconAsset; - /** Should render component on the right */ - shouldShowRightComponent?: boolean; + /** Boolean whether to display the right icon */ + shouldShowRightIcon?: boolean; - /** Component to be displayed on the right */ - rightComponent?: ReactNode; + /** Overrides the icon for shouldShowRightIcon */ + iconRight?: IconAsset; - /** A description text to show under the title */ - description?: string; + /** Should render component on the right */ + shouldShowRightComponent?: boolean; - /** Should the description be shown above the title (instead of the other way around) */ - shouldShowDescriptionOnTop?: boolean; + /** Component to be displayed on the right */ + rightComponent?: ReactNode; - /** Error to display below the title */ - error?: string; + /** A description text to show under the title */ + description?: string; - /** Error to display at the bottom of the component */ - errorText?: string; + /** Should the description be shown above the title (instead of the other way around) */ + shouldShowDescriptionOnTop?: boolean; - /** A boolean flag that gives the icon a green fill if true */ - success?: boolean; + /** Error to display below the title */ + error?: string; - /** Whether item is focused or active */ - focused?: boolean; + /** Error to display at the bottom of the component */ + errorText?: string; - /** Should we disable this menu item? */ - disabled?: boolean; + /** A boolean flag that gives the icon a green fill if true */ + success?: boolean; - /** Text that appears above the title */ - label?: string; + /** Whether item is focused or active */ + focused?: boolean; - /** Label to be displayed on the right */ - rightLabel?: string; + /** Should we disable this menu item? */ + disabled?: boolean; - /** Text to display for the item */ - title?: string; + /** Text that appears above the title */ + label?: string; - /** A right-aligned subtitle for this menu option */ - subtitle?: string | number; + /** Label to be displayed on the right */ + rightLabel?: string; - /** Should the title show with normal font weight (not bold) */ - shouldShowBasicTitle?: boolean; + /** Text to display for the item */ + title?: string; - /** Should we make this selectable with a checkbox */ - shouldShowSelectedState?: boolean; + /** A right-aligned subtitle for this menu option */ + subtitle?: string | number; - /** Whether this item is selected */ - isSelected?: boolean; + /** Should the title show with normal font weight (not bold) */ + shouldShowBasicTitle?: boolean; - /** Prop to identify if we should load avatars vertically instead of diagonally */ - shouldStackHorizontally: boolean; + /** Should we make this selectable with a checkbox */ + shouldShowSelectedState?: boolean; - /** Prop to represent the size of the avatar images to be shown */ - avatarSize?: (typeof CONST.AVATAR_SIZE)[keyof typeof CONST.AVATAR_SIZE]; + /** Whether this item is selected */ + isSelected?: boolean; - /** Avatars to show on the right of the menu item */ - floatRightAvatars?: IconType[]; + /** Prop to identify if we should load avatars vertically instead of diagonally */ + shouldStackHorizontally: boolean; - /** Prop to represent the size of the float right avatar images to be shown */ - floatRightAvatarSize?: ValueOf; + /** Prop to represent the size of the avatar images to be shown */ + avatarSize?: (typeof CONST.AVATAR_SIZE)[keyof typeof CONST.AVATAR_SIZE]; - /** Affects avatar size */ - viewMode?: ValueOf; + /** Avatars to show on the right of the menu item */ + floatRightAvatars?: IconType[]; - /** Used to truncate the text with an ellipsis after computing the text layout */ - numberOfLinesTitle?: number; + /** Prop to represent the size of the float right avatar images to be shown */ + floatRightAvatarSize?: ValueOf; - /** Whether we should use small avatar subscript sizing the for menu item */ - isSmallAvatarSubscriptMenu?: boolean; + /** Affects avatar size */ + viewMode?: ValueOf; - /** The type of brick road indicator to show. */ - brickRoadIndicator?: ValueOf; + /** Used to truncate the text with an ellipsis after computing the text layout */ + numberOfLinesTitle?: number; - /** Should render the content in HTML format */ - shouldRenderAsHTML?: boolean; + /** Whether we should use small avatar subscript sizing the for menu item */ + isSmallAvatarSubscriptMenu?: boolean; - /** Should we grey out the menu item when it is disabled? */ - shouldGreyOutWhenDisabled?: boolean; + /** The type of brick road indicator to show. */ + brickRoadIndicator?: ValueOf; - /** The action accept for anonymous user or not */ - isAnonymousAction?: boolean; + /** Should render the content in HTML format */ + shouldRenderAsHTML?: boolean; - /** Flag to indicate whether or not text selection should be disabled from long-pressing the menu item. */ - shouldBlockSelection?: boolean; + /** Should we grey out the menu item when it is disabled? */ + shouldGreyOutWhenDisabled?: boolean; - /** Whether should render title as HTML or as Text */ - shouldParseTitle?: false; + /** The action accept for anonymous user or not */ + isAnonymousAction?: boolean; - /** Should check anonymous user in onPress function */ - shouldCheckActionAllowedOnPress?: boolean; + /** Flag to indicate whether or not text selection should be disabled from long-pressing the menu item. */ + shouldBlockSelection?: boolean; - /** Text to display under the main item */ - furtherDetails?: string; + /** Whether should render title as HTML or as Text */ + shouldParseTitle?: false; - /** The function that should be called when this component is LongPressed or right-clicked. */ - onSecondaryInteraction?: (event: GestureResponderEvent | MouseEvent) => void; + /** Should check anonymous user in onPress function */ + shouldCheckActionAllowedOnPress?: boolean; - /** Array of objects that map display names to their corresponding tooltip */ - titleWithTooltips: DisplayNameWithTooltip[]; + /** Text to display under the main item */ + furtherDetails?: string; - /** Icon should be displayed in its own color */ - displayInDefaultIconColor?: boolean; + /** The function that should be called when this component is LongPressed or right-clicked. */ + onSecondaryInteraction?: (event: GestureResponderEvent | MouseEvent) => void; - /** Determines how the icon should be resized to fit its container */ - contentFit?: ImageContentFit; - }; + /** Array of objects that map display names to their corresponding tooltip */ + titleWithTooltips: DisplayNameWithTooltip[]; + + /** Icon should be displayed in its own color */ + displayInDefaultIconColor?: boolean; + + /** Determines how the icon should be resized to fit its container */ + contentFit?: ImageContentFit; +}; function MenuItem( { diff --git a/src/components/MenuItemList.tsx b/src/components/MenuItemList.tsx index 2e1015d092ca..82097309c580 100644 --- a/src/components/MenuItemList.tsx +++ b/src/components/MenuItemList.tsx @@ -5,9 +5,11 @@ import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportA import CONST from '@src/CONST'; import MenuItem, {MenuItemProps} from './MenuItem'; +type MenuItemLink = string | (() => Promise); + type MenuItemWithLink = MenuItemProps & { /** The link to open when the menu item is clicked */ - link: string | (() => Promise); + link: MenuItemLink; }; type MenuItemListProps = { @@ -19,16 +21,16 @@ type MenuItemListProps = { }; function MenuItemList({menuItems = [], shouldUseSingleExecution = false}: MenuItemListProps) { - const popoverAnchor = useRef(null); + const popoverAnchor = useRef(null); const {isExecuting, singleExecution} = useSingleExecution(); /** * Handle the secondary interaction for a menu item. * * @param link the menu item link or function to get the link - * @param e the interaction event + * @param event the interaction event */ - const secondaryInteraction = (link: MenuItemWithLink['link'], event: GestureResponderEvent | MouseEvent) => { + const secondaryInteraction = (link: MenuItemLink, event: GestureResponderEvent | MouseEvent) => { if (typeof link === 'function') { link().then((url) => ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, event, url, popoverAnchor.current)); } else if (link) { @@ -38,23 +40,18 @@ function MenuItemList({menuItems = [], shouldUseSingleExecution = false}: MenuIt return ( <> - {menuItems.map((menuItemProps) => { - const onPress = menuItemProps.onPress ?? (() => {}); - - return ( - secondaryInteraction(menuItemProps.link, e) : undefined} - ref={popoverAnchor} - shouldBlockSelection={!!menuItemProps.link} - // eslint-disable-next-line react/jsx-props-no-spreading - {...menuItemProps} - disabled={menuItemProps.disabled ?? isExecuting} - onPress={shouldUseSingleExecution ? singleExecution(onPress) : onPress} - interactive - /> - ); - })} + {menuItems.map((menuItemProps) => ( + secondaryInteraction(menuItemProps.link, e) : undefined} + ref={popoverAnchor} + shouldBlockSelection={!!menuItemProps.link} + // eslint-disable-next-line react/jsx-props-no-spreading + {...menuItemProps} + disabled={!!menuItemProps.disabled || isExecuting} + onPress={shouldUseSingleExecution ? singleExecution(menuItemProps.onPress) : menuItemProps.onPress} + /> + ))} ); } diff --git a/src/components/Section/index.tsx b/src/components/Section/index.tsx index 7f458d2ed8a5..47f7605d278b 100644 --- a/src/components/Section/index.tsx +++ b/src/components/Section/index.tsx @@ -26,7 +26,7 @@ type SectionProps = ChildrenProps & { /** The icon to display along with the title */ icon?: IconAsset; - /** Card layout that affects icon positioning, margins, sizes. */ + /** Card layout that affects icon positioning, margins, sizes */ cardLayout?: ValueOf; /** Whether the subtitle should have a muted style */ diff --git a/src/hooks/useSingleExecution/index.ts b/src/hooks/useSingleExecution/index.ts index c37087d27c5f..0522303d553a 100644 --- a/src/hooks/useSingleExecution/index.ts +++ b/src/hooks/useSingleExecution/index.ts @@ -9,9 +9,9 @@ type Action = (...params: T) => void | Promise; */ export default function useSingleExecution() { const singleExecution = useCallback( - (action: Action) => + (action?: Action) => (...params: T) => { - action(...params); + action?.(...params); }, [], ); From 2a50e51d96ad58f2894d0a0345c191fe1492d391 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 27 Dec 2023 18:07:54 +0100 Subject: [PATCH 3/6] Fix package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 43b74ab0de55..df30c549c515 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "private": true, "scripts": { "configure-mapbox": "scripts/setup-mapbox-sdk-walkthrough.sh", - "setupNewDotWebForEmulators": "scripts/psetup-newdot-web-emulators.sh", + "setupNewDotWebForEmulators": "scripts/setup-newdot-web-emulators.sh", "startAndroidEmulator": "scripts/start-android.sh", "postinstall": "scripts/postInstall.sh", "clean": "npx react-native clean-project-auto", From 60e0c990e48bf3fa9ebf1804557f329047332c87 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 2 Jan 2024 14:52:59 +0100 Subject: [PATCH 4/6] Rerun checks From 4cebe6a365e8791f435a17f06c6aee651a693fff Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 4 Jan 2024 09:47:42 +0100 Subject: [PATCH 5/6] Fix typecheck --- src/components/MenuItem.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index db3a77ccee35..368a3148d0b4 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -78,7 +78,7 @@ type MenuItemProps = (IconProps | AvatarProps | NoIcon) & { titleStyle?: ViewStyle; /** Any adjustments to style when menu item is hovered or pressed */ - hoverAndPressStyle: StyleProp>; + hoverAndPressStyle?: StyleProp>; /** Additional styles to style the description text below the title */ descriptionTextStyle?: StyleProp; @@ -168,7 +168,7 @@ type MenuItemProps = (IconProps | AvatarProps | NoIcon) & { isSelected?: boolean; /** Prop to identify if we should load avatars vertically instead of diagonally */ - shouldStackHorizontally: boolean; + shouldStackHorizontally?: boolean; /** Prop to represent the size of the avatar images to be shown */ avatarSize?: (typeof CONST.AVATAR_SIZE)[keyof typeof CONST.AVATAR_SIZE]; @@ -216,7 +216,7 @@ type MenuItemProps = (IconProps | AvatarProps | NoIcon) & { onSecondaryInteraction?: (event: GestureResponderEvent | MouseEvent) => void; /** Array of objects that map display names to their corresponding tooltip */ - titleWithTooltips: DisplayNameWithTooltip[]; + titleWithTooltips?: DisplayNameWithTooltip[]; /** Icon should be displayed in its own color */ displayInDefaultIconColor?: boolean; From 09a65e6b1e8416d7e5c764ac4615d9109ad120cd Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 4 Jan 2024 09:59:35 +0100 Subject: [PATCH 6/6] Fix type imports --- src/components/MenuItemList.tsx | 5 +++-- src/components/Section/IconSection.tsx | 5 +++-- src/components/Section/index.tsx | 12 +++++++----- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/components/MenuItemList.tsx b/src/components/MenuItemList.tsx index 82097309c580..f83f173a644f 100644 --- a/src/components/MenuItemList.tsx +++ b/src/components/MenuItemList.tsx @@ -1,9 +1,10 @@ import React, {useRef} from 'react'; -import {GestureResponderEvent, View} from 'react-native'; +import type {GestureResponderEvent, View} from 'react-native'; import useSingleExecution from '@hooks/useSingleExecution'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; -import MenuItem, {MenuItemProps} from './MenuItem'; +import type {MenuItemProps} from './MenuItem'; +import MenuItem from './MenuItem'; type MenuItemLink = string | (() => Promise); diff --git a/src/components/Section/IconSection.tsx b/src/components/Section/IconSection.tsx index ddadbcdb1d77..cc42c6b7ace5 100644 --- a/src/components/Section/IconSection.tsx +++ b/src/components/Section/IconSection.tsx @@ -1,8 +1,9 @@ import React from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import Icon from '@components/Icon'; import useThemeStyles from '@hooks/useThemeStyles'; -import IconAsset from '@src/types/utils/IconAsset'; +import type IconAsset from '@src/types/utils/IconAsset'; type IconSectionProps = { icon?: IconAsset; diff --git a/src/components/Section/index.tsx b/src/components/Section/index.tsx index 47f7605d278b..f24316a5f1bb 100644 --- a/src/components/Section/index.tsx +++ b/src/components/Section/index.tsx @@ -1,11 +1,13 @@ import React from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; -import {ValueOf} from 'type-fest'; -import MenuItemList, {MenuItemWithLink} from '@components/MenuItemList'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; +import type {ValueOf} from 'type-fest'; +import type {MenuItemWithLink} from '@components/MenuItemList'; +import MenuItemList from '@components/MenuItemList'; import Text from '@components/Text'; import useThemeStyles from '@hooks/useThemeStyles'; -import ChildrenProps from '@src/types/utils/ChildrenProps'; -import IconAsset from '@src/types/utils/IconAsset'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; +import type IconAsset from '@src/types/utils/IconAsset'; import IconSection from './IconSection'; const CARD_LAYOUT = {