Skip to content

Commit

Permalink
Merge pull request #31065 from VickyStash/ts-migration/autoCompleteSu…
Browse files Browse the repository at this point in the history
…ggestions-component

[TS migration] Migrate 'AutoCompleteSuggestions' component to TypeScript
  • Loading branch information
MariaHCD authored Nov 27, 2023
2 parents d985a0e + 1292c40 commit 6904fb1
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 85 deletions.
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
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';
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
Expand All @@ -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<TSuggestion>(
{
highlightedSuggestionIndex,
onSelect,
accessibilityLabelExtractor,
renderSuggestionMenuItem,
suggestions,
isSuggestionPickerLarge,
keyExtractor,
}: AutoCompleteSuggestionsProps<TSuggestion>,
ref: ForwardedRef<View | HTMLDivElement>,
) {
const styles = useThemeStyles();
const rowHeight = useSharedValue(0);
const scrollRef = useRef(null);
const scrollRef = useRef<FlashList<TSuggestion>>(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<TSuggestion>): ReactElement => (
<PressableWithFeedback
style={({hovered}) => StyleUtils.getAutoCompleteSuggestionItemStyle(highlightedSuggestionIndex, CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT, hovered, index)}
hoverDimmingValue={1}
Expand Down Expand Up @@ -84,7 +79,7 @@ function BaseAutoCompleteSuggestions({

return (
<Animated.View
ref={forwardedRef}
ref={viewForwardedRef(ref)}
style={[styles.autoCompleteSuggestionsContainer, animatedStyles]}
exiting={FadeOutDown.duration(100).easing(Easing.inOut(Easing.ease))}
>
Expand All @@ -104,17 +99,6 @@ function BaseAutoCompleteSuggestions({
);
}

BaseAutoCompleteSuggestions.propTypes = propTypes;
BaseAutoCompleteSuggestions.displayName = 'BaseAutoCompleteSuggestions';

const BaseAutoCompleteSuggestionsWithRef = React.forwardRef((props, ref) => (
<BaseAutoCompleteSuggestions
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
forwardedRef={ref}
/>
));

BaseAutoCompleteSuggestionsWithRef.displayName = 'BaseAutoCompleteSuggestionsWithRef';

export default BaseAutoCompleteSuggestionsWithRef;
export default forwardRef(BaseAutoCompleteSuggestions);

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<TSuggestion>({measureParentContainer, ...props}: AutoCompleteSuggestionsProps<TSuggestion>) {
return (
<Portal hostName="suggestions">
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<BaseAutoCompleteSuggestions {...props} />
<BaseAutoCompleteSuggestions<TSuggestion> {...props} />
</Portal>
);
}

AutoCompleteSuggestions.propTypes = propTypes;
AutoCompleteSuggestions.displayName = 'AutoCompleteSuggestions';

export default AutoCompleteSuggestions;
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<TSuggestion>({measureParentContainer = () => {}, ...props}: AutoCompleteSuggestionsProps<TSuggestion>) {
const containerRef = React.useRef<HTMLDivElement>(null);
const {windowHeight, windowWidth} = useWindowDimensions();
const [{width, left, bottom}, setContainerState] = React.useState({
width: 0,
Expand All @@ -25,7 +25,7 @@ function AutoCompleteSuggestions({measureParentContainer, ...props}) {
React.useEffect(() => {
const container = containerRef.current;
if (!container) {
return;
return () => {};
}
container.onpointerdown = (e) => {
if (DeviceCapabilities.hasHoverSupport()) {
Expand All @@ -44,20 +44,20 @@ function AutoCompleteSuggestions({measureParentContainer, ...props}) {
}, [measureParentContainer, windowHeight, windowWidth]);

const componentToRender = (
<BaseAutoCompleteSuggestions
<BaseAutoCompleteSuggestions<TSuggestion>
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
ref={containerRef}
/>
);

const bodyElement = document.querySelector('body');

return (
Boolean(width) &&
ReactDOM.createPortal(<View style={StyleUtils.getBaseAutoCompleteSuggestionContainerStyle({left, width, bottom})}>{componentToRender}</View>, document.querySelector('body'))
!!width && bodyElement && ReactDOM.createPortal(<View style={StyleUtils.getBaseAutoCompleteSuggestionContainerStyle({left, width, bottom})}>{componentToRender}</View>, bodyElement)
);
}

AutoCompleteSuggestions.propTypes = propTypes;
AutoCompleteSuggestions.displayName = 'AutoCompleteSuggestions';

export default AutoCompleteSuggestions;
38 changes: 38 additions & 0 deletions src/components/AutoCompleteSuggestions/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {ReactElement} from 'react';

type MeasureParentContainerCallback = (x: number, y: number, width: number) => void;

type RenderSuggestionMenuItemProps<TSuggestion> = {
item: TSuggestion;
index: number;
};

type AutoCompleteSuggestionsProps<TSuggestion> = {
/** 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};
6 changes: 6 additions & 0 deletions src/types/utils/viewForwardedRef.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {ForwardedRef} from 'react';
import {View} from 'react-native';

const viewForwardedRef = (ref: ForwardedRef<View | HTMLDivElement>) => ref as ForwardedRef<View>;

export default viewForwardedRef;

0 comments on commit 6904fb1

Please sign in to comment.