diff --git a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.js b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx similarity index 77% rename from src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.js rename to src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx index ec53507d4d8e..3e5e7b4fdd9a 100644 --- a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.js +++ b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx @@ -1,5 +1,6 @@ import {FlashList} from '@shopify/flash-list'; -import React, {useCallback, useEffect, useRef} from 'react'; +import React, {ForwardedRef, forwardRef, ReactElement, useCallback, useEffect, useRef} from 'react'; +import {View} from 'react-native'; // We take ScrollView from this package to properly handle the scrolling of AutoCompleteSuggestions in chats since one scroll is nested inside another import {ScrollView} from 'react-native-gesture-handler'; import Animated, {Easing, FadeOutDown, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; @@ -7,14 +8,10 @@ import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import * as StyleUtils from '@styles/StyleUtils'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; -import {propTypes} from './autoCompleteSuggestionsPropTypes'; +import viewForwardedRef from '@src/types/utils/viewForwardedRef'; +import type {AutoCompleteSuggestionsProps, RenderSuggestionMenuItemProps} from './types'; -/** - * @param {Number} numRows - * @param {Boolean} isSuggestionPickerLarge - * @returns {Number} - */ -const measureHeightOfSuggestionRows = (numRows, isSuggestionPickerLarge) => { +const measureHeightOfSuggestionRows = (numRows: number, isSuggestionPickerLarge: boolean): number => { if (isSuggestionPickerLarge) { if (numRows > CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_VISIBLE_SUGGESTIONS_IN_CONTAINER) { // On large screens, if there are more than 5 suggestions, we display a scrollable window with a height of 5 items, indicating that there are more items available @@ -29,28 +26,26 @@ const measureHeightOfSuggestionRows = (numRows, isSuggestionPickerLarge) => { return numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; }; -function BaseAutoCompleteSuggestions({ - highlightedSuggestionIndex, - onSelect, - renderSuggestionMenuItem, - suggestions, - accessibilityLabelExtractor, - keyExtractor, - isSuggestionPickerLarge, - forwardedRef, -}) { +function BaseAutoCompleteSuggestions( + { + highlightedSuggestionIndex, + onSelect, + accessibilityLabelExtractor, + renderSuggestionMenuItem, + suggestions, + isSuggestionPickerLarge, + keyExtractor, + }: AutoCompleteSuggestionsProps, + ref: ForwardedRef, +) { const styles = useThemeStyles(); const rowHeight = useSharedValue(0); - const scrollRef = useRef(null); + const scrollRef = useRef>(null); /** * Render a suggestion menu item component. - * @param {Object} params - * @param {Object} params.item - * @param {Number} params.index - * @returns {JSX.Element} */ const renderItem = useCallback( - ({item, index}) => ( + ({item, index}: RenderSuggestionMenuItemProps): ReactElement => ( StyleUtils.getAutoCompleteSuggestionItemStyle(highlightedSuggestionIndex, CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT, hovered, index)} hoverDimmingValue={1} @@ -84,7 +79,7 @@ function BaseAutoCompleteSuggestions({ return ( @@ -104,17 +99,6 @@ function BaseAutoCompleteSuggestions({ ); } -BaseAutoCompleteSuggestions.propTypes = propTypes; BaseAutoCompleteSuggestions.displayName = 'BaseAutoCompleteSuggestions'; -const BaseAutoCompleteSuggestionsWithRef = React.forwardRef((props, ref) => ( - -)); - -BaseAutoCompleteSuggestionsWithRef.displayName = 'BaseAutoCompleteSuggestionsWithRef'; - -export default BaseAutoCompleteSuggestionsWithRef; +export default forwardRef(BaseAutoCompleteSuggestions); diff --git a/src/components/AutoCompleteSuggestions/autoCompleteSuggestionsPropTypes.js b/src/components/AutoCompleteSuggestions/autoCompleteSuggestionsPropTypes.js deleted file mode 100644 index 8c6dca1902c5..000000000000 --- a/src/components/AutoCompleteSuggestions/autoCompleteSuggestionsPropTypes.js +++ /dev/null @@ -1,36 +0,0 @@ -import PropTypes from 'prop-types'; - -const propTypes = { - /** Array of suggestions */ - // eslint-disable-next-line react/forbid-prop-types - suggestions: PropTypes.arrayOf(PropTypes.object).isRequired, - - /** Function used to render each suggestion, returned JSX will be enclosed inside a Pressable component */ - renderSuggestionMenuItem: PropTypes.func.isRequired, - - /** Create unique keys for each suggestion item */ - keyExtractor: PropTypes.func.isRequired, - - /** The index of the highlighted suggestion */ - highlightedSuggestionIndex: PropTypes.number.isRequired, - - /** Fired when the user selects a suggestion */ - onSelect: PropTypes.func.isRequired, - - /** Show that we can use large auto-complete suggestion picker. - * Depending on available space and whether the input is expanded, we can have a small or large mention suggester. - * When this value is false, the suggester will have a height of 2.5 items. When this value is true, the height can be up to 5 items. */ - isSuggestionPickerLarge: PropTypes.bool.isRequired, - - /** create accessibility label for each item */ - accessibilityLabelExtractor: PropTypes.func.isRequired, - - /** Meaures the parent container's position and dimensions. */ - measureParentContainer: PropTypes.func, -}; - -const defaultProps = { - measureParentContainer: () => {}, -}; - -export {propTypes, defaultProps}; diff --git a/src/components/AutoCompleteSuggestions/index.native.js b/src/components/AutoCompleteSuggestions/index.native.tsx similarity index 61% rename from src/components/AutoCompleteSuggestions/index.native.js rename to src/components/AutoCompleteSuggestions/index.native.tsx index 439fa45eae78..fbfa7d953581 100644 --- a/src/components/AutoCompleteSuggestions/index.native.js +++ b/src/components/AutoCompleteSuggestions/index.native.tsx @@ -1,18 +1,17 @@ import {Portal} from '@gorhom/portal'; import React from 'react'; -import {propTypes} from './autoCompleteSuggestionsPropTypes'; import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions'; +import type {AutoCompleteSuggestionsProps} from './types'; -function AutoCompleteSuggestions({measureParentContainer, ...props}) { +function AutoCompleteSuggestions({measureParentContainer, ...props}: AutoCompleteSuggestionsProps) { return ( {/* eslint-disable-next-line react/jsx-props-no-spreading */} - + {...props} /> ); } -AutoCompleteSuggestions.propTypes = propTypes; AutoCompleteSuggestions.displayName = 'AutoCompleteSuggestions'; export default AutoCompleteSuggestions; diff --git a/src/components/AutoCompleteSuggestions/index.js b/src/components/AutoCompleteSuggestions/index.tsx similarity index 76% rename from src/components/AutoCompleteSuggestions/index.js rename to src/components/AutoCompleteSuggestions/index.tsx index 30654caf5708..24b846c265a9 100644 --- a/src/components/AutoCompleteSuggestions/index.js +++ b/src/components/AutoCompleteSuggestions/index.tsx @@ -4,8 +4,8 @@ import {View} from 'react-native'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as StyleUtils from '@styles/StyleUtils'; -import {propTypes} from './autoCompleteSuggestionsPropTypes'; import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions'; +import type {AutoCompleteSuggestionsProps} from './types'; /** * On the mobile-web platform, when long-pressing on auto-complete suggestions, @@ -14,8 +14,8 @@ import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions'; * On the native platform, tapping on auto-complete suggestions will not blur the main input. */ -function AutoCompleteSuggestions({measureParentContainer, ...props}) { - const containerRef = React.useRef(null); +function AutoCompleteSuggestions({measureParentContainer = () => {}, ...props}: AutoCompleteSuggestionsProps) { + const containerRef = React.useRef(null); const {windowHeight, windowWidth} = useWindowDimensions(); const [{width, left, bottom}, setContainerState] = React.useState({ width: 0, @@ -25,7 +25,7 @@ function AutoCompleteSuggestions({measureParentContainer, ...props}) { React.useEffect(() => { const container = containerRef.current; if (!container) { - return; + return () => {}; } container.onpointerdown = (e) => { if (DeviceCapabilities.hasHoverSupport()) { @@ -44,20 +44,20 @@ function AutoCompleteSuggestions({measureParentContainer, ...props}) { }, [measureParentContainer, windowHeight, windowWidth]); const componentToRender = ( - // eslint-disable-next-line react/jsx-props-no-spreading {...props} ref={containerRef} /> ); + const bodyElement = document.querySelector('body'); + return ( - Boolean(width) && - ReactDOM.createPortal({componentToRender}, document.querySelector('body')) + !!width && bodyElement && ReactDOM.createPortal({componentToRender}, bodyElement) ); } -AutoCompleteSuggestions.propTypes = propTypes; AutoCompleteSuggestions.displayName = 'AutoCompleteSuggestions'; export default AutoCompleteSuggestions; diff --git a/src/components/AutoCompleteSuggestions/types.ts b/src/components/AutoCompleteSuggestions/types.ts new file mode 100644 index 000000000000..9130f5139d71 --- /dev/null +++ b/src/components/AutoCompleteSuggestions/types.ts @@ -0,0 +1,38 @@ +import {ReactElement} from 'react'; + +type MeasureParentContainerCallback = (x: number, y: number, width: number) => void; + +type RenderSuggestionMenuItemProps = { + item: TSuggestion; + index: number; +}; + +type AutoCompleteSuggestionsProps = { + /** Array of suggestions */ + suggestions: TSuggestion[]; + + /** Function used to render each suggestion, returned JSX will be enclosed inside a Pressable component */ + renderSuggestionMenuItem: (item: TSuggestion, index: number) => ReactElement; + + /** Create unique keys for each suggestion item */ + keyExtractor: (item: TSuggestion, index: number) => string; + + /** The index of the highlighted suggestion */ + highlightedSuggestionIndex: number; + + /** Fired when the user selects a suggestion */ + onSelect: (index: number) => void; + + /** Show that we can use large auto-complete suggestion picker. + * Depending on available space and whether the input is expanded, we can have a small or large mention suggester. + * When this value is false, the suggester will have a height of 2.5 items. When this value is true, the height can be up to 5 items. */ + isSuggestionPickerLarge: boolean; + + /** create accessibility label for each item */ + accessibilityLabelExtractor: (item: TSuggestion, index: number) => string; + + /** Meaures the parent container's position and dimensions. */ + measureParentContainer?: (callback: MeasureParentContainerCallback) => void; +}; + +export type {AutoCompleteSuggestionsProps, RenderSuggestionMenuItemProps}; diff --git a/src/types/utils/viewForwardedRef.ts b/src/types/utils/viewForwardedRef.ts new file mode 100644 index 000000000000..3bc18495b153 --- /dev/null +++ b/src/types/utils/viewForwardedRef.ts @@ -0,0 +1,6 @@ +import {ForwardedRef} from 'react'; +import {View} from 'react-native'; + +const viewForwardedRef = (ref: ForwardedRef) => ref as ForwardedRef; + +export default viewForwardedRef;