From d4095f6bf317e7b34523276d1e246cf2a6db37e2 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Thu, 26 Oct 2023 11:18:59 +0200 Subject: [PATCH 1/3] [TS migration] Migrate 'EmojiSuggestions.js' component --- ...ojiSuggestions.js => EmojiSuggestions.tsx} | 70 ++++++----------- src/components/Text.tsx | 7 ++ src/libs/EmojiTrie.ts | 77 ++++++++++--------- src/styles/StyleUtils.ts | 2 +- 4 files changed, 73 insertions(+), 83 deletions(-) rename src/components/{EmojiSuggestions.js => EmojiSuggestions.tsx} (54%) diff --git a/src/components/EmojiSuggestions.js b/src/components/EmojiSuggestions.tsx similarity index 54% rename from src/components/EmojiSuggestions.js rename to src/components/EmojiSuggestions.tsx index d7f7a8d6091a..beba3fc6f617 100644 --- a/src/components/EmojiSuggestions.js +++ b/src/components/EmojiSuggestions.tsx @@ -1,84 +1,64 @@ -import React from 'react'; +import React, {ReactElement} from 'react'; import {View} from 'react-native'; -import PropTypes from 'prop-types'; -import _ from 'underscore'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; import * as EmojiUtils from '../libs/EmojiUtils'; import Text from './Text'; import getStyledTextArray from '../libs/GetStyledTextArray'; import AutoCompleteSuggestions from './AutoCompleteSuggestions'; +import type {SimpleEmoji} from '../libs/EmojiTrie'; -const propTypes = { +type MeasureParentContainerCallback = (x: number, y: number, width: number) => void; + +type EmojiSuggestionsProps = { /** The index of the highlighted emoji */ - highlightedEmojiIndex: PropTypes.number, + highlightedEmojiIndex?: number; /** Array of suggested emoji */ - emojis: PropTypes.arrayOf( - PropTypes.shape({ - /** The emoji code */ - code: PropTypes.string.isRequired, - - /** The name of the emoji */ - name: PropTypes.string.isRequired, - - /** Array of different skin tone variants. - * If provided, it will be indexed with props.preferredSkinToneIndex */ - types: PropTypes.arrayOf(PropTypes.string.isRequired), - }), - ).isRequired, + emojis: SimpleEmoji[]; /** Fired when the user selects an emoji */ - onSelect: PropTypes.func.isRequired, + onSelect: (index: number) => void; /** Emoji prefix that follows the colon */ - prefix: PropTypes.string.isRequired, + prefix: string; /** Show that we can use large emoji picker. Depending on available space * and whether the input is expanded, we can have a small or large emoji * 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. */ - isEmojiPickerLarge: PropTypes.bool.isRequired, + isEmojiPickerLarge: boolean; /** Stores user's preferred skin tone */ - preferredSkinToneIndex: PropTypes.number.isRequired, + preferredSkinToneIndex: number; /** Meaures the parent container's position and dimensions. */ - measureParentContainer: PropTypes.func, -}; - -const defaultProps = { - highlightedEmojiIndex: 0, - measureParentContainer: () => {}, + measureParentContainer: (callback: MeasureParentContainerCallback) => void; }; /** * Create unique keys for each emoji item - * @param {Object} item - * @param {Number} index - * @returns {String} */ -const keyExtractor = (item, index) => `${item.name}+${index}}`; +const keyExtractor = (item: SimpleEmoji, index: number): string => `${item.name}+${index}}`; -function EmojiSuggestions(props) { +function EmojiSuggestions({emojis, onSelect, prefix, isEmojiPickerLarge, preferredSkinToneIndex, highlightedEmojiIndex = 0, measureParentContainer = () => {}}: EmojiSuggestionsProps) { /** * Render an emoji suggestion menu item component. - * @param {Object} item - * @returns {JSX.Element} */ - const renderSuggestionMenuItem = (item) => { - const styledTextArray = getStyledTextArray(item.name, props.prefix); + const renderSuggestionMenuItem = (item: SimpleEmoji): ReactElement => { + const styledTextArray = getStyledTextArray(item.name, prefix); return ( - {EmojiUtils.getEmojiCodeWithSkinColor(item, props.preferredSkinToneIndex)} + {EmojiUtils.getEmojiCodeWithSkinColor(item, preferredSkinToneIndex)} : - {_.map(styledTextArray, ({text, isColored}, i) => ( + {styledTextArray.map(({text, isColored}, i) => ( @@ -93,20 +73,18 @@ function EmojiSuggestions(props) { return ( ); } -EmojiSuggestions.propTypes = propTypes; -EmojiSuggestions.defaultProps = defaultProps; EmojiSuggestions.displayName = 'EmojiSuggestions'; export default EmojiSuggestions; diff --git a/src/components/Text.tsx b/src/components/Text.tsx index 60a59aae1520..2588bb4ba31e 100644 --- a/src/components/Text.tsx +++ b/src/components/Text.tsx @@ -13,6 +13,13 @@ type TextProps = { /** The size of the text */ fontSize?: number; + /** + * Used to truncate the text with an ellipsis after computing the text + * layout, including line wrapping, such that the total number of lines + * does not exceed this number. + */ + numberOfLines?: number; + /** The alignment of the text */ textAlign?: 'left' | 'right' | 'auto' | 'center' | 'justify'; diff --git a/src/libs/EmojiTrie.ts b/src/libs/EmojiTrie.ts index d0a53acf29c9..67659429a5d6 100644 --- a/src/libs/EmojiTrie.ts +++ b/src/libs/EmojiTrie.ts @@ -5,14 +5,20 @@ import Trie from './Trie'; import Timing from './actions/Timing'; import CONST from '../CONST'; -type Emoji = { +type HeaderEmoji = { code: string; - header?: boolean; - icon?: React.FC; - name?: string; + header: boolean; + icon: React.FC; +}; + +type SimpleEmoji = { + code: string; + name: string; types?: string[]; }; +type Emoji = HeaderEmoji | SimpleEmoji; + type LocalizedEmoji = { name?: string; keywords: string[]; @@ -48,7 +54,7 @@ type EmojiTrie = { * @param name The localized name of the emoji. * @param shouldPrependKeyword Prepend the keyword (instead of append) to the suggestions */ -function addKeywordsToTrie(trie: Trie, keywords: string[], item: Emoji, name: string, shouldPrependKeyword = false) { +function addKeywordsToTrie(trie: Trie, keywords: string[], item: SimpleEmoji, name: string, shouldPrependKeyword = false) { keywords.forEach((keyword) => { const keywordNode = trie.search(keyword); if (!keywordNode) { @@ -81,37 +87,35 @@ function createTrie(lang: SupportedLanguage = CONST.LOCALES.DEFAULT): Trie { - if (!item.name) { - return; - } - - const englishName = item.name; - const localeName = langEmojis?.[item.code]?.name ?? englishName; - - const node = trie.search(localeName); - if (!node) { - trie.add(localeName, {code: item.code, types: item.types, name: localeName, suggestions: []}); - } else { - trie.update(localeName, {code: item.code, types: item.types, name: localeName, suggestions: node.metaData.suggestions}); - } - - const nameParts = getNameParts(localeName).slice(1); // We remove the first part because we already index the full name. - addKeywordsToTrie(trie, nameParts, item, localeName); - - // Add keywords for both the locale language and English to enable users to search using either language. - const keywords = (langEmojis?.[item.code]?.keywords ?? []).concat(isDefaultLocale ? [] : defaultLangEmojis?.[item.code]?.keywords ?? []); - addKeywordsToTrie(trie, keywords, item, localeName); - - /** - * If current language isn't the default, prepend the English name of the emoji in the suggestions as well. - * We do this because when the user types the english name of the emoji, we want to show the emoji in the suggestions before all the others. - */ - if (!isDefaultLocale) { - const englishNameParts = getNameParts(englishName); - addKeywordsToTrie(trie, englishNameParts, item, localeName, true); - } - }); + emojis + .filter((item: Emoji): item is SimpleEmoji => !(item as HeaderEmoji).header) + .forEach((item: SimpleEmoji) => { + const englishName = item.name; + const localeName = langEmojis?.[item.code]?.name ?? englishName; + + const node = trie.search(localeName); + if (!node) { + trie.add(localeName, {code: item.code, types: item.types, name: localeName, suggestions: []}); + } else { + trie.update(localeName, {code: item.code, types: item.types, name: localeName, suggestions: node.metaData.suggestions}); + } + + const nameParts = getNameParts(localeName).slice(1); // We remove the first part because we already index the full name. + addKeywordsToTrie(trie, nameParts, item, localeName); + + // Add keywords for both the locale language and English to enable users to search using either language. + const keywords = (langEmojis?.[item.code]?.keywords ?? []).concat(isDefaultLocale ? [] : defaultLangEmojis?.[item.code]?.keywords ?? []); + addKeywordsToTrie(trie, keywords, item, localeName); + + /** + * If current language isn't the default, prepend the English name of the emoji in the suggestions as well. + * We do this because when the user types the english name of the emoji, we want to show the emoji in the suggestions before all the others. + */ + if (!isDefaultLocale) { + const englishNameParts = getNameParts(englishName); + addKeywordsToTrie(trie, englishNameParts, item, localeName, true); + } + }); return trie; } @@ -121,3 +125,4 @@ const emojiTrie: EmojiTrie = supportedLanguages.reduce((prev, cur) => ({...prev, Timing.end(CONST.TIMING.TRIE_INITIALIZATION); export default emojiTrie; +export type {SimpleEmoji}; diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index 48100b2efb60..518689de8aad 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -1012,7 +1012,7 @@ function getAutoCompleteSuggestionContainerStyle(itemsHeight: number): ViewStyle /** * Select the correct color for text. */ -function getColoredBackgroundStyle(isColored: boolean): ViewStyle { +function getColoredBackgroundStyle(isColored: boolean): TextStyle { return {backgroundColor: isColored ? themeColors.link : undefined}; } From 8b38fe88edbc4d2f4f9e2e740bc5fb66c0e7aed8 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Mon, 30 Oct 2023 09:26:46 +0100 Subject: [PATCH 2/3] Run linter after merging main --- src/components/EmojiSuggestions.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/EmojiSuggestions.tsx b/src/components/EmojiSuggestions.tsx index beba3fc6f617..162e07fb7a43 100644 --- a/src/components/EmojiSuggestions.tsx +++ b/src/components/EmojiSuggestions.tsx @@ -1,12 +1,12 @@ import React, {ReactElement} from 'react'; import {View} from 'react-native'; -import styles from '../styles/styles'; -import * as StyleUtils from '../styles/StyleUtils'; -import * as EmojiUtils from '../libs/EmojiUtils'; -import Text from './Text'; -import getStyledTextArray from '../libs/GetStyledTextArray'; +import type {SimpleEmoji} from '@libs/EmojiTrie'; +import * as EmojiUtils from '@libs/EmojiUtils'; +import getStyledTextArray from '@libs/GetStyledTextArray'; +import styles from '@styles/styles'; +import * as StyleUtils from '@styles/StyleUtils'; import AutoCompleteSuggestions from './AutoCompleteSuggestions'; -import type {SimpleEmoji} from '../libs/EmojiTrie'; +import Text from './Text'; type MeasureParentContainerCallback = (x: number, y: number, width: number) => void; From e4245e4b081f3b1059113f837d86a2a05a6d5f01 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Mon, 13 Nov 2023 18:17:45 +0100 Subject: [PATCH 3/3] Update text key --- src/components/EmojiSuggestions.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/EmojiSuggestions.tsx b/src/components/EmojiSuggestions.tsx index 162e07fb7a43..8ab20cf13ad6 100644 --- a/src/components/EmojiSuggestions.tsx +++ b/src/components/EmojiSuggestions.tsx @@ -56,10 +56,9 @@ function EmojiSuggestions({emojis, onSelect, prefix, isEmojiPickerLarge, preferr style={styles.emojiSuggestionsText} > : - {styledTextArray.map(({text, isColored}, i) => ( + {styledTextArray.map(({text, isColored}) => ( {text}