From b0678c607f62b915efbd85d4260544f99bac3024 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Thu, 22 Feb 2024 12:18:43 +0300 Subject: [PATCH 01/53] migrate EmojiPicker to ts --- .../{EmojiPicker.js => EmojiPicker.tsx} | 102 ++++++++++-------- 1 file changed, 60 insertions(+), 42 deletions(-) rename src/components/EmojiPicker/{EmojiPicker.js => EmojiPicker.tsx} (68%) diff --git a/src/components/EmojiPicker/EmojiPicker.js b/src/components/EmojiPicker/EmojiPicker.tsx similarity index 68% rename from src/components/EmojiPicker/EmojiPicker.js rename to src/components/EmojiPicker/EmojiPicker.tsx index 410d600dcce2..952df1f14776 100644 --- a/src/components/EmojiPicker/EmojiPicker.js +++ b/src/components/EmojiPicker/EmojiPicker.tsx @@ -1,7 +1,7 @@ -import PropTypes from 'prop-types'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; +import type {ForwardedRef, RefObject} from 'react'; import {Dimensions} from 'react-native'; -import _ from 'underscore'; +import type {View} from 'react-native'; import PopoverWithMeasuredContent from '@components/PopoverWithMeasuredContent'; import withViewportOffsetTop from '@components/withViewportOffsetTop'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -10,6 +10,9 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import * as Browser from '@libs/Browser'; import calculateAnchorPosition from '@libs/calculateAnchorPosition'; import CONST from '@src/CONST'; +import type {OnModalHideValue, OnEmojiSelected, EmojiPopoverAnchor, AnchorOrigin, OnWillShowPicker} from '@libs/actions/EmojiPickerAction'; +import type {Emoji} from '@assets/emojis/types'; +import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import EmojiPickerMenu from './EmojiPickerMenu'; const DEFAULT_ANCHOR_ORIGIN = { @@ -17,11 +20,28 @@ const DEFAULT_ANCHOR_ORIGIN = { vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, }; -const propTypes = { - viewportOffsetTop: PropTypes.number.isRequired, +type EmojiPickerProps = { + viewportOffsetTop: number, +} + +type EmojiPickerRef = { + showEmojiPicker: ( + onModalHideValue: OnModalHideValue, + onEmojiSelectedValue: OnEmojiSelected, + emojiPopoverAnchor: EmojiPopoverAnchor, + anchorOrigin?: AnchorOrigin, + onWillShow?: OnWillShowPicker, + id?: string, + activeEmoji?: string, + ) => void; + isActive: (id: string) => boolean; + clearActive: () => void; + hideEmojiPicker: (isNavigating?: boolean) => void; + isEmojiPickerVisible: boolean; + resetEmojiPopoverAnchor: () => void; }; -const EmojiPicker = forwardRef((props, ref) => { +function EmojiPicker (props: EmojiPickerProps, ref: ForwardedRef) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const [isEmojiPickerVisible, setIsEmojiPickerVisible] = useState(false); @@ -29,17 +49,17 @@ const EmojiPicker = forwardRef((props, ref) => { horizontal: 0, vertical: 0, }); - const [emojiPopoverAnchorOrigin, setEmojiPopoverAnchorOrigin] = useState(DEFAULT_ANCHOR_ORIGIN); - const [activeID, setActiveID] = useState(); - const emojiPopoverAnchorRef = useRef(null); + const [emojiPopoverAnchorOrigin, setEmojiPopoverAnchorOrigin] = useState(DEFAULT_ANCHOR_ORIGIN); + const [activeID, setActiveID] = useState(); + const emojiPopoverAnchorRef = useRef(); const emojiAnchorDimension = useRef({ width: 0, height: 0, }); const onModalHide = useRef(() => {}); - const onEmojiSelected = useRef(() => {}); - const activeEmoji = useRef(); - const emojiSearchInput = useRef(); + const onEmojiSelected = useRef(() => {}); + const activeEmoji = useRef(); + const emojiSearchInput = useRef(); const {isSmallScreenWidth, windowHeight} = useWindowDimensions(); /** @@ -50,31 +70,31 @@ const EmojiPicker = forwardRef((props, ref) => { * * Don't directly get the ref from emojiPopoverAnchorRef, instead use getEmojiPopoverAnchor() */ - const getEmojiPopoverAnchor = useCallback(() => emojiPopoverAnchorRef.current || emojiPopoverAnchorRef, []); + const getEmojiPopoverAnchor = useCallback(() => emojiPopoverAnchorRef.current ?? emojiPopoverAnchorRef, []); /** * Show the emoji picker menu. * - * @param {Function} [onModalHideValue=() => {}] - Run a callback when Modal hides. - * @param {Function} [onEmojiSelectedValue=() => {}] - Run a callback when Emoji selected. - * @param {React.MutableRefObject} emojiPopoverAnchorValue - Element to which Popover is anchored - * @param {Object} [anchorOrigin=DEFAULT_ANCHOR_ORIGIN] - Anchor origin for Popover - * @param {Function} [onWillShow] - Run a callback when Popover will show - * @param {String} id - Unique id for EmojiPicker - * @param {String} activeEmojiValue - Selected emoji to be highlighted + * @param [onModalHideValue=() => {}] - Run a callback when Modal hides. + * @param [onEmojiSelectedValue=() => {}] - Run a callback when Emoji selected. + * @param emojiPopoverAnchorValue - Element to which Popover is anchored + * @param [anchorOrigin=DEFAULT_ANCHOR_ORIGIN] - Anchor origin for Popover + * @param [onWillShow] - Run a callback when Popover will show + * @param id - Unique id for EmojiPicker + * @param activeEmojiValue - Selected emoji to be highlighted */ - const showEmojiPicker = (onModalHideValue, onEmojiSelectedValue, emojiPopoverAnchorValue, anchorOrigin, onWillShow, id, activeEmojiValue) => { + const showEmojiPicker = (onModalHideValue: OnModalHideValue, onEmojiSelectedValue: OnEmojiSelected, emojiPopoverAnchorValue: EmojiPopoverAnchor, anchorOrigin?: AnchorOrigin, onWillShow: OnWillShowPicker, id?: string, activeEmojiValue?: string) => { onModalHide.current = onModalHideValue; onEmojiSelected.current = onEmojiSelectedValue; activeEmoji.current = activeEmojiValue; emojiPopoverAnchorRef.current = emojiPopoverAnchorValue; const emojiPopoverAnchor = getEmojiPopoverAnchor(); - if (emojiPopoverAnchor.current && emojiPopoverAnchor.current.blur) { + // if ((emojiPopoverAnchor?.current as View)?.blur) { // Drop focus to avoid blue focus ring. - emojiPopoverAnchor.current.blur(); - } + (emojiPopoverAnchor?.current as View)?.blur?.(); + // } - const anchorOriginValue = anchorOrigin || DEFAULT_ANCHOR_ORIGIN; + const anchorOriginValue = anchorOrigin ?? DEFAULT_ANCHOR_ORIGIN; calculateAnchorPosition(emojiPopoverAnchor.current, anchorOriginValue).then((value) => { // eslint-disable-next-line es/no-optional-chaining @@ -96,9 +116,9 @@ const EmojiPicker = forwardRef((props, ref) => { /** * Hide the emoji picker menu. * - * @param {Boolean} isNavigating + * @param isNavigating */ - const hideEmojiPicker = (isNavigating) => { + const hideEmojiPicker = (isNavigating?: boolean) => { if (isNavigating) { onModalHide.current = () => {}; } @@ -107,7 +127,7 @@ const EmojiPicker = forwardRef((props, ref) => { if (currOnModalHide) { currOnModalHide(); } - emojiPopoverAnchorRef.current = null; + emojiPopoverAnchorRef.current = undefined; }; setIsEmojiPickerVisible(false); }; @@ -119,16 +139,16 @@ const EmojiPicker = forwardRef((props, ref) => { if (!emojiSearchInput.current) { return; } - emojiSearchInput.current.focus(); + emojiSearchInput.current?.focus(); }; /** * Callback for the emoji picker to add whatever emoji is chosen into the main input * - * @param {String} emoji - * @param {Object} emojiObject + * @param emoji + * @param emojiObject */ - const selectEmoji = (emoji, emojiObject) => { + const selectEmoji = (emoji: string, emojiObject: Emoji) => { // Prevent fast click / multiple emoji selection; // The first click will hide the emoji picker by calling the hideEmojiPicker() function if (!isEmojiPickerVisible) { @@ -136,7 +156,7 @@ const EmojiPicker = forwardRef((props, ref) => { } hideEmojiPicker(false); - if (_.isFunction(onEmojiSelected.current)) { + if (typeof onEmojiSelected.current === 'function') { onEmojiSelected.current(emoji, emojiObject); } }; @@ -144,14 +164,13 @@ const EmojiPicker = forwardRef((props, ref) => { /** * Whether emoji picker is active for the given id. * - * @param {String} id - * @return {Boolean} + * @param id */ - const isActive = (id) => Boolean(id) && id === activeID; + const isActive = (id: string) => !!id && id === activeID; - const clearActive = () => setActiveID(null); + const clearActive = () => setActiveID(''); - const resetEmojiPopoverAnchor = () => (emojiPopoverAnchorRef.current = null); + const resetEmojiPopoverAnchor = () => (emojiPopoverAnchorRef.current = undefined); useImperativeHandle(ref, () => ({showEmojiPicker, isActive, clearActive, hideEmojiPicker, isEmojiPickerVisible, resetEmojiPopoverAnchor})); @@ -165,7 +184,7 @@ const EmojiPicker = forwardRef((props, ref) => { } return; } - calculateAnchorPosition(emojiPopoverAnchor.current, emojiPopoverAnchorOrigin).then((value) => { + calculateAnchorPosition(emojiPopoverAnchor.current as View, emojiPopoverAnchorOrigin).then((value) => { setEmojiPopoverAnchorPosition({ horizontal: value.horizontal, vertical: value.vertical, @@ -201,7 +220,7 @@ const EmojiPicker = forwardRef((props, ref) => { vertical: emojiPopoverAnchorPosition.vertical, horizontal: emojiPopoverAnchorPosition.horizontal, }} - anchorRef={getEmojiPopoverAnchor()} + anchorRef={getEmojiPopoverAnchor() as RefObject} withoutOverlay popoverDimensions={{ width: CONST.EMOJI_PICKER_SIZE.WIDTH, @@ -223,8 +242,7 @@ const EmojiPicker = forwardRef((props, ref) => { /> ); -}); +}; -EmojiPicker.propTypes = propTypes; EmojiPicker.displayName = 'EmojiPicker'; -export default withViewportOffsetTop(EmojiPicker); +export default withViewportOffsetTop(forwardRef(EmojiPicker)); From 19b1beddc8c684dcd199586c3523295c16c8b63d Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Thu, 22 Feb 2024 12:19:43 +0300 Subject: [PATCH 02/53] rename emojiPickerMenuPropTypes to types.ts --- .../EmojiPickerMenu/emojiPickerMenuPropTypes.js | 8 -------- src/components/EmojiPicker/EmojiPickerMenu/types.ts | 8 ++++++++ 2 files changed, 8 insertions(+), 8 deletions(-) delete mode 100644 src/components/EmojiPicker/EmojiPickerMenu/emojiPickerMenuPropTypes.js create mode 100644 src/components/EmojiPicker/EmojiPickerMenu/types.ts diff --git a/src/components/EmojiPicker/EmojiPickerMenu/emojiPickerMenuPropTypes.js b/src/components/EmojiPicker/EmojiPickerMenu/emojiPickerMenuPropTypes.js deleted file mode 100644 index ae345f6fcf56..000000000000 --- a/src/components/EmojiPicker/EmojiPickerMenu/emojiPickerMenuPropTypes.js +++ /dev/null @@ -1,8 +0,0 @@ -import PropTypes from 'prop-types'; - -const emojiPickerMenuPropTypes = { - /** Function to add the selected emoji to the main compose text input */ - onEmojiSelected: PropTypes.func.isRequired, -}; - -export default emojiPickerMenuPropTypes; diff --git a/src/components/EmojiPicker/EmojiPickerMenu/types.ts b/src/components/EmojiPicker/EmojiPickerMenu/types.ts new file mode 100644 index 000000000000..7f6776008fe7 --- /dev/null +++ b/src/components/EmojiPicker/EmojiPickerMenu/types.ts @@ -0,0 +1,8 @@ +import type {Emoji} from "@assets/emojis/types"; + +type EmojiPickerMenuProps = { + /** Function to add the selected emoji to the main compose text input */ + onEmojiSelected: (emoji: string, emojiObject: Emoji) => void, +}; + +export default EmojiPickerMenuProps; From df5af683e2313ebb9f1c9595802deb164853d883 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Thu, 22 Feb 2024 12:20:52 +0300 Subject: [PATCH 03/53] remove unnecessary type definition --- src/libs/calculateAnchorPosition.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/libs/calculateAnchorPosition.ts b/src/libs/calculateAnchorPosition.ts index 365bd1ece28b..ba4054119069 100644 --- a/src/libs/calculateAnchorPosition.ts +++ b/src/libs/calculateAnchorPosition.ts @@ -1,14 +1,9 @@ /* eslint-disable no-restricted-imports */ -import type {ValueOf} from 'type-fest'; + import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; import type {AnchorDimensions, AnchorPosition} from '@src/styles'; - -type AnchorOrigin = { - horizontal: ValueOf; - vertical: ValueOf; - shiftVertical?: number; -}; +import type {AnchorOrigin} from './actions/EmojiPickerAction'; /** * Gets the x,y position of the passed in component for the purpose of anchoring another component to it. From 9ed4b4feca784e278880ad9ee1aabad9dcc994ae Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Thu, 22 Feb 2024 12:22:43 +0300 Subject: [PATCH 04/53] export types --- src/libs/actions/EmojiPickerAction.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/EmojiPickerAction.ts b/src/libs/actions/EmojiPickerAction.ts index a54095ad9c23..9f01f736ed6f 100644 --- a/src/libs/actions/EmojiPickerAction.ts +++ b/src/libs/actions/EmojiPickerAction.ts @@ -9,11 +9,12 @@ import type CONST from '@src/CONST'; type AnchorOrigin = { horizontal: ValueOf; vertical: ValueOf; + shiftVertical?: number }; type EmojiPopoverAnchor = MutableRefObject; -type OnWillShowPicker = (callback: CloseContextMenuCallback) => void; +type OnWillShowPicker = (callback?: CloseContextMenuCallback) => void; type OnModalHideValue = () => void; @@ -112,4 +113,4 @@ function resetEmojiPopoverAnchor() { } export {emojiPickerRef, showEmojiPicker, hideEmojiPicker, isActive, clearActive, isEmojiPickerVisible, resetEmojiPopoverAnchor}; -export type {AnchorOrigin, EmojiPickerRef}; +export type {AnchorOrigin, EmojiPickerRef, OnModalHideValue, OnEmojiSelected, EmojiPopoverAnchor, OnWillShowPicker}; From 0b8f22825b52470d1fc71bda5567035ac7116c98 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Thu, 22 Feb 2024 17:23:17 +0300 Subject: [PATCH 05/53] migrate useEmojiPickerMenu to ts --- .../{useEmojiPickerMenu.js => useEmojiPickerMenu.ts} | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) rename src/components/EmojiPicker/EmojiPickerMenu/{useEmojiPickerMenu.js => useEmojiPickerMenu.ts} (87%) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.js b/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts similarity index 87% rename from src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.js rename to src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts index c6f9f601f4df..3dcf83af973e 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts @@ -1,8 +1,9 @@ import {useCallback, useEffect, useMemo, useState} from 'react'; import {useAnimatedRef} from 'react-native-reanimated'; -import _ from 'underscore'; import emojis from '@assets/emojis'; import {useFrequentlyUsedEmojis} from '@components/OnyxProvider'; +import type {PickerEmojis} from '@assets/emojis/types'; +import type {SupportedLanguage} from '@libs/EmojiTrie'; import useLocalize from '@hooks/useLocalize'; import usePreferredEmojiSkinTone from '@hooks/usePreferredEmojiSkinTone'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -14,8 +15,8 @@ const useEmojiPickerMenu = () => { const frequentlyUsedEmojis = useFrequentlyUsedEmojis(); // eslint-disable-next-line react-hooks/exhaustive-deps const allEmojis = useMemo(() => EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis), [frequentlyUsedEmojis]); - const headerEmojis = useMemo(() => EmojiUtils.getHeaderEmojis(allEmojis), [allEmojis]); - const headerRowIndices = useMemo(() => _.map(headerEmojis, (headerEmoji) => headerEmoji.index), [headerEmojis]); + const headerEmojis = useMemo(() => EmojiUtils.getHeaderEmojis(allEmojis as PickerEmojis), [allEmojis]); + const headerRowIndices = useMemo(() => headerEmojis.map((headerEmoji) => headerEmoji.index), [headerEmojis]); const spacersIndexes = useMemo(() => EmojiUtils.getSpacersIndexes(allEmojis), [allEmojis]); const [filteredEmojis, setFilteredEmojis] = useState(allEmojis); const [headerIndices, setHeaderIndices] = useState(headerRowIndices); @@ -45,9 +46,9 @@ const useEmojiPickerMenu = () => { * @returns {[String, Array]} */ const suggestEmojis = useCallback( - (searchTerm) => { + (searchTerm: string) => { const normalizedSearchTerm = searchTerm.toLowerCase().trim().replaceAll(':', ''); - const emojisSuggestions = EmojiUtils.suggestEmojis(`:${normalizedSearchTerm}`, preferredLocale, allEmojis.length); + const emojisSuggestions = EmojiUtils.suggestEmojis(`:${normalizedSearchTerm}`, preferredLocale as keyof SupportedLanguage, allEmojis.length); return [normalizedSearchTerm, emojisSuggestions]; }, From 5f7e85b03ed64c525bb5e1db3ac8516c7e436a72 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Fri, 23 Feb 2024 13:42:28 +0300 Subject: [PATCH 06/53] wip --- .../EmojiPicker/EmojiPickerMenu/index.js | 2 +- .../{index.native.js => index.native.tsx} | 27 +++++++++---------- .../EmojiPickerMenu/useEmojiPickerMenu.ts | 3 ++- 3 files changed, 16 insertions(+), 16 deletions(-) rename src/components/EmojiPicker/EmojiPickerMenu/{index.native.js => index.native.tsx} (84%) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.js b/src/components/EmojiPicker/EmojiPickerMenu/index.js index 926903be18d1..bcb61625a271 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.js @@ -20,7 +20,7 @@ import isEnterWhileComposition from '@libs/KeyboardShortcut/isEnterWhileComposit import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import BaseEmojiPickerMenu from './BaseEmojiPickerMenu'; -import emojiPickerMenuPropTypes from './emojiPickerMenuPropTypes'; +import emojiPickerMenuPropTypes from './types'; import useEmojiPickerMenu from './useEmojiPickerMenu'; const propTypes = { diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.js b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx similarity index 84% rename from src/components/EmojiPicker/EmojiPickerMenu/index.native.js rename to src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx index 67f0542fb82c..99ed4a91e831 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx @@ -1,8 +1,11 @@ import React, {useCallback} from 'react'; +import type {ForwardedRef} from 'react'; +import lodashDebounce from 'lodash/debounce'; import {View} from 'react-native'; import {runOnUI, scrollTo} from 'react-native-reanimated'; -import _ from 'underscore'; import EmojiPickerMenuItem from '@components/EmojiPicker/EmojiPickerMenuItem'; +import type { BaseTextInputRef } from '@components/TextInput/BaseTextInput/types'; +import type {TranslationPaths} from '@src/languages/types'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; @@ -13,12 +16,10 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import * as EmojiUtils from '@libs/EmojiUtils'; import CONST from '@src/CONST'; import BaseEmojiPickerMenu from './BaseEmojiPickerMenu'; -import emojiPickerMenuPropTypes from './emojiPickerMenuPropTypes'; +import type EmojiPickerMenuProps from './types'; import useEmojiPickerMenu from './useEmojiPickerMenu'; -const propTypes = emojiPickerMenuPropTypes; - -function EmojiPickerMenu({onEmojiSelected, activeEmoji}) { +function EmojiPickerMenu(onEmojiSelected: EmojiPickerMenuProps, activeEmoji: ForwardedRef) { const styles = useThemeStyles(); const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); @@ -44,11 +45,11 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}) { * * @param {String} searchTerm */ - const filterEmojis = _.debounce((searchTerm) => { + const filterEmojis = lodashDebounce((searchTerm) => { const [normalizedSearchTerm, newFilteredEmojiList] = suggestEmojis(searchTerm); if (emojiListRef.current) { - emojiListRef.current.scrollToOffset({offset: 0, animated: false}); + emojiListRef?.current?.scrollToOffset({offset: 0, animated: false}); } if (normalizedSearchTerm === '') { @@ -62,7 +63,7 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}) { setHeaderIndices([]); }, 300); - const scrollToHeader = (headerIndex) => { + const scrollToHeader = (headerIndex: number) => { const calculatedOffset = Math.floor(headerIndex / CONST.EMOJI_NUM_PER_ROW) * CONST.EMOJI_PICKER_HEADER_HEIGHT; runOnUI(() => { 'worklet'; @@ -76,8 +77,7 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}) { * Items with the code "SPACER" return nothing and are used to fill rows up to 8 * so that the sticky headers function properly. * - * @param {Object} item - * @returns {*} + * @param item */ const renderItem = useCallback( ({item, target}) => { @@ -89,17 +89,17 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}) { if (item.header) { return ( - {translate(`emojiPicker.headers.${code}`)} + {translate(`emojiPicker.headers.${code}` as TranslationPaths)} ); } - const emojiCode = types && types[preferredSkinTone] ? types[preferredSkinTone] : code; + const emojiCode = types?.[preferredSkinTone as number] ? types[preferredSkinTone as number] : code; const shouldEmojiBeHighlighted = Boolean(activeEmoji) && EmojiUtils.getRemovedSkinToneEmoji(emojiCode) === EmojiUtils.getRemovedSkinToneEmoji(activeEmoji); return ( onEmojiSelected(emoji, item))} + onPress={singleExecution((emoji: string) => onEmojiSelected(emoji, item))} emoji={emojiCode} isHighlighted={shouldEmojiBeHighlighted} /> @@ -141,7 +141,6 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}) { } EmojiPickerMenu.displayName = 'EmojiPickerMenu'; -EmojiPickerMenu.propTypes = propTypes; const EmojiPickerMenuWithRef = React.forwardRef((props, ref) => ( { - const emojiListRef = useAnimatedRef(); + const emojiListRef = useAnimatedRef>(); const frequentlyUsedEmojis = useFrequentlyUsedEmojis(); // eslint-disable-next-line react-hooks/exhaustive-deps const allEmojis = useMemo(() => EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis), [frequentlyUsedEmojis]); From b249860d3dcc9ad59c1bcfc452a918813b3ad319 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Sun, 25 Feb 2024 22:27:01 +0300 Subject: [PATCH 07/53] migrate CategoryShortcutBar to ts --- ...ShortcutBar.js => CategoryShortcutBar.tsx} | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) rename src/components/EmojiPicker/{CategoryShortcutBar.js => CategoryShortcutBar.tsx} (59%) diff --git a/src/components/EmojiPicker/CategoryShortcutBar.js b/src/components/EmojiPicker/CategoryShortcutBar.tsx similarity index 59% rename from src/components/EmojiPicker/CategoryShortcutBar.js rename to src/components/EmojiPicker/CategoryShortcutBar.tsx index d9d5e8f12465..bedadcb8210f 100644 --- a/src/components/EmojiPicker/CategoryShortcutBar.js +++ b/src/components/EmojiPicker/CategoryShortcutBar.tsx @@ -1,33 +1,26 @@ -import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; -import _ from 'underscore'; -import sourcePropTypes from '@components/Image/sourcePropTypes'; +import type {HeaderIndice} from '@libs/EmojiUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import CategoryShortcutButton from './CategoryShortcutButton'; -const propTypes = { +type CategoryShortcutBarProps = { /** The function to call when an emoji is selected */ - onPress: PropTypes.func.isRequired, + onPress: (index: number) => void, /** The emojis consisting emoji code and indices that the icons should link to */ - headerEmojis: PropTypes.arrayOf( - PropTypes.shape({ - code: PropTypes.string.isRequired, - index: PropTypes.number.isRequired, - icon: sourcePropTypes.isRequired, - }), - ).isRequired, -}; + headerEmojis: HeaderIndice[] +} -function CategoryShortcutBar(props) { +function CategoryShortcutBar(props: CategoryShortcutBarProps) { const styles = useThemeStyles(); return ( - {_.map(props.headerEmojis, (headerEmoji, i) => ( + {props.headerEmojis.map((headerEmoji, i) => ( props.onPress(headerEmoji.index)} + // eslint-disable-next-line react/no-array-index-key key={`categoryShortcut${i}`} code={headerEmoji.code} /> @@ -36,7 +29,6 @@ function CategoryShortcutBar(props) { ); } -CategoryShortcutBar.propTypes = propTypes; CategoryShortcutBar.displayName = 'CategoryShortcutBar'; export default CategoryShortcutBar; From 38d4c3aa227fb20bb36210b5a8c0635b1101a0e1 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Sun, 25 Feb 2024 23:26:37 +0300 Subject: [PATCH 08/53] migrate CategoryShortcutButton to ts --- .../EmojiPicker/CategoryShortcutButton.tsx | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/components/EmojiPicker/CategoryShortcutButton.tsx diff --git a/src/components/EmojiPicker/CategoryShortcutButton.tsx b/src/components/EmojiPicker/CategoryShortcutButton.tsx new file mode 100644 index 000000000000..049d27877006 --- /dev/null +++ b/src/components/EmojiPicker/CategoryShortcutButton.tsx @@ -0,0 +1,59 @@ +import React, {useState} from 'react'; +import Icon from '@components/Icon'; +import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; +import Tooltip from '@components/Tooltip'; +import useLocalize from '@hooks/useLocalize'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import getButtonState from '@libs/getButtonState'; +import variables from '@styles/variables'; +import CONST from '@src/CONST'; +import type IconAsset from '@src/types/utils/IconAsset'; +import type {TranslationPaths} from '@src/languages/types'; + +type CategoryShortcutButtonProps = { + /** The emoji code of the category header */ + code: string, + + /** The icon representation of the category that this button links to */ + icon: IconAsset, + + /** The function to call when an emoji is selected */ + onPress: () => void, +} + +function CategoryShortcutButton(props: CategoryShortcutButtonProps) { + const theme = useTheme(); + const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); + const {translate} = useLocalize(); + const [isHighlighted, setIsHighlighted] = useState(false); + + return ( + + setIsHighlighted(true)} + onHoverOut={() => setIsHighlighted(false)} + style={({pressed}) => [StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)), styles.categoryShortcutButton, isHighlighted && styles.emojiItemHighlighted]} + accessibilityLabel={`emojiPicker.headers.${props.code}`} + role={CONST.ROLE.BUTTON} + > + + + + ); +} + +CategoryShortcutButton.displayName = 'CategoryShortcutButton'; +export default React.memo(CategoryShortcutButton); From 918b173e089eec537625b460d82bc030d950508b Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Sun, 25 Feb 2024 23:26:59 +0300 Subject: [PATCH 09/53] wip migrate BaseEmojiPickerMenu to ts --- .../EmojiPickerMenu/BaseEmojiPickerMenu.tsx | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx diff --git a/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx b/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx new file mode 100644 index 000000000000..dcbd3a5e2a9e --- /dev/null +++ b/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx @@ -0,0 +1,155 @@ +import {FlashList} from '@shopify/flash-list'; +import React, {useMemo} from 'react'; +import type {ForwardedRef} from 'react'; +import {StyleSheet, View} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; +import CategoryShortcutBar from '@components/EmojiPicker/CategoryShortcutBar'; +import EmojiSkinToneList from '@components/EmojiPicker/EmojiSkinToneList'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import CONST from '@src/CONST'; + +type EmojiPropTypes = { + /** The code of the item */ + code: string, + + /** Whether the item is a header or not */ + header: boolean, + + /** Whether the item is a spacer or not */ + spacer: boolean, + + /** Types of an emoji - e.g. different skin types */ + types: string[], +} + +type BaseEmojiPickerMenuProps = { + /** Indicates if the emoji list is filtered or not */ + isFiltered: boolean, + + /** Array of header emojis */ + headerEmojis: EmojiPropTypes[], + + /** Function to scroll to a specific header in the emoji list */ + scrollToHeader: (headerIndex: number) => void, + + /** Style to be applied to the list wrapper */ + listWrapperStyle?: StyleProp, + + /** The data for the emoji list */ + data: EmojiPropTypes[], + + /** Function to render each item in the list */ + renderItem: () => void, + + /** Extra data to be passed to the list for re-rendering */ + // eslint-disable-next-line react/forbid-prop-types + extraData?: Record, + + /** Array of indices for the sticky headers */ + stickyHeaderIndices?: number[], + + /** Whether the list should always bounce vertically */ + alwaysBounceVertical?: boolean, +} + +/** + * Improves FlashList's recycling when there are different types of items + * @param {Object} item + * @returns {String} + */ +const getItemType = (item) => { + // item is undefined only when list is empty + if (!item) { + return; + } + + if (item.name) { + return CONST.EMOJI_PICKER_ITEM_TYPES.EMOJI; + } + if (item.header) { + return CONST.EMOJI_PICKER_ITEM_TYPES.HEADER; + } + + return CONST.EMOJI_PICKER_ITEM_TYPES.SPACER; +}; + +/** + * Return a unique key for each emoji item + * + * @param {Object} item + * @param {Number} index + * @returns {String} + */ +const keyExtractor = (item, index) => `emoji_picker_${item.code}_${index}`; + +/** + * Renders the list empty component + * @returns {React.Component} + */ +function ListEmptyComponent() { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + return {translate('common.noResultsFound')}; +} + +function BaseEmojiPickerMenu({headerEmojis, scrollToHeader, isFiltered, listWrapperStyle, data, renderItem, stickyHeaderIndices, extraData, alwaysBounceVertical}: BaseEmojiPickerMenuProps, forwardedRef: ForwardedRef>) { + const styles = useThemeStyles(); + const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); + + // Estimated list size should be a whole integer to avoid floating point precision errors + // More info: https://github.com/Expensify/App/issues/34522 + const listWidth = isSmallScreenWidth ? Math.floor(windowWidth) : CONST.EMOJI_PICKER_SIZE.WIDTH; + + const flattenListWrapperStyle = useMemo(() => StyleSheet.flatten(listWrapperStyle), [listWrapperStyle]); + + return ( + <> + {!isFiltered && ( + + )} + + + + + + ); +} + +BaseEmojiPickerMenu.propTypes = propTypes; +BaseEmojiPickerMenu.defaultProps = defaultProps; +BaseEmojiPickerMenu.displayName = 'BaseEmojiPickerMenu'; + +const BaseEmojiPickerMenuWithRef = React.forwardRef((props, ref) => ( + +)); + +BaseEmojiPickerMenuWithRef.displayName = 'BaseEmojiPickerMenuWithRef'; + +export default BaseEmojiPickerMenuWithRef; From 5bf07bf59c36fe4efccee5989d9b622e2ef79018 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Sun, 25 Feb 2024 23:27:54 +0300 Subject: [PATCH 10/53] remove CategoryShortcutButton.js --- .../EmojiPicker/CategoryShortcutButton.js | 59 ------------------- 1 file changed, 59 deletions(-) delete mode 100644 src/components/EmojiPicker/CategoryShortcutButton.js diff --git a/src/components/EmojiPicker/CategoryShortcutButton.js b/src/components/EmojiPicker/CategoryShortcutButton.js deleted file mode 100644 index e7db8161cea1..000000000000 --- a/src/components/EmojiPicker/CategoryShortcutButton.js +++ /dev/null @@ -1,59 +0,0 @@ -import PropTypes from 'prop-types'; -import React, {useState} from 'react'; -import Icon from '@components/Icon'; -import sourcePropTypes from '@components/Image/sourcePropTypes'; -import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; -import Tooltip from '@components/Tooltip'; -import useLocalize from '@hooks/useLocalize'; -import useStyleUtils from '@hooks/useStyleUtils'; -import useTheme from '@hooks/useTheme'; -import useThemeStyles from '@hooks/useThemeStyles'; -import getButtonState from '@libs/getButtonState'; -import variables from '@styles/variables'; -import CONST from '@src/CONST'; - -const propTypes = { - /** The emoji code of the category header */ - code: PropTypes.string.isRequired, - - /** The icon representation of the category that this button links to */ - icon: sourcePropTypes.isRequired, - - /** The function to call when an emoji is selected */ - onPress: PropTypes.func.isRequired, -}; - -function CategoryShortcutButton(props) { - const theme = useTheme(); - const styles = useThemeStyles(); - const StyleUtils = useStyleUtils(); - const {translate} = useLocalize(); - const [isHighlighted, setIsHighlighted] = useState(false); - - return ( - - setIsHighlighted(true)} - onHoverOut={() => setIsHighlighted(false)} - style={({pressed}) => [StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)), styles.categoryShortcutButton, isHighlighted && styles.emojiItemHighlighted]} - accessibilityLabel={`emojiPicker.headers.${props.code}`} - role={CONST.ROLE.BUTTON} - > - - - - ); -} -CategoryShortcutButton.propTypes = propTypes; -CategoryShortcutButton.displayName = 'CategoryShortcutButton'; -export default React.memo(CategoryShortcutButton); From e12a1f46d8dda0c873983e139a8ce03ba80aae5d Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Sun, 25 Feb 2024 23:35:37 +0300 Subject: [PATCH 11/53] migrate getSkinToneEmojiFromIndex to ts --- .../EmojiPicker/getSkinToneEmojiFromIndex.js | 13 ------------- .../EmojiPicker/getSkinToneEmojiFromIndex.ts | 11 +++++++++++ 2 files changed, 11 insertions(+), 13 deletions(-) delete mode 100644 src/components/EmojiPicker/getSkinToneEmojiFromIndex.js create mode 100644 src/components/EmojiPicker/getSkinToneEmojiFromIndex.ts diff --git a/src/components/EmojiPicker/getSkinToneEmojiFromIndex.js b/src/components/EmojiPicker/getSkinToneEmojiFromIndex.js deleted file mode 100644 index 48f9e2177a73..000000000000 --- a/src/components/EmojiPicker/getSkinToneEmojiFromIndex.js +++ /dev/null @@ -1,13 +0,0 @@ -import _ from 'underscore'; -import * as Emojis from '@assets/emojis'; - -/** - * Fetch the emoji code of selected skinTone - * @param {Number} skinToneIndex - * @returns {String} - */ -function getSkinToneEmojiFromIndex(skinToneIndex) { - return _.find(Emojis.skinTones, (emoji) => emoji.skinTone === skinToneIndex) || Emojis.skinTones[0]; -} - -export default getSkinToneEmojiFromIndex; diff --git a/src/components/EmojiPicker/getSkinToneEmojiFromIndex.ts b/src/components/EmojiPicker/getSkinToneEmojiFromIndex.ts new file mode 100644 index 000000000000..6b98e3d188ea --- /dev/null +++ b/src/components/EmojiPicker/getSkinToneEmojiFromIndex.ts @@ -0,0 +1,11 @@ +import * as Emojis from '@assets/emojis'; + +/** + * Fetch the emoji code of selected skinTone + * @param skinToneIndex + */ +function getSkinToneEmojiFromIndex(skinToneIndex: number) { + return Emojis.skinTones.find((emoji) => emoji.skinTone === skinToneIndex) ?? Emojis.skinTones[0]; +} + +export default getSkinToneEmojiFromIndex; From 40457135d247e0c1c9e91dac5481b27eeab271ce Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Sun, 25 Feb 2024 23:38:45 +0300 Subject: [PATCH 12/53] delete BaseEmojiPickerMenu.js --- .../EmojiPickerMenu/BaseEmojiPickerMenu.js | 175 ------------------ 1 file changed, 175 deletions(-) delete mode 100644 src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.js diff --git a/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.js b/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.js deleted file mode 100644 index a9bc1eeac8b6..000000000000 --- a/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.js +++ /dev/null @@ -1,175 +0,0 @@ -import {FlashList} from '@shopify/flash-list'; -import PropTypes from 'prop-types'; -import React, {useMemo} from 'react'; -import {StyleSheet, View} from 'react-native'; -import CategoryShortcutBar from '@components/EmojiPicker/CategoryShortcutBar'; -import EmojiSkinToneList from '@components/EmojiPicker/EmojiSkinToneList'; -import refPropTypes from '@components/refPropTypes'; -import Text from '@components/Text'; -import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; -import useWindowDimensions from '@hooks/useWindowDimensions'; -import stylePropTypes from '@styles/stylePropTypes'; -import CONST from '@src/CONST'; - -const emojiPropTypes = { - /** The code of the item */ - code: PropTypes.string.isRequired, - - /** Whether the item is a header or not */ - header: PropTypes.bool, - - /** Whether the item is a spacer or not */ - spacer: PropTypes.bool, - - /** Types of an emoji - e.g. different skin types */ - types: PropTypes.arrayOf(PropTypes.string), -}; - -const propTypes = { - /** Indicates if the emoji list is filtered or not */ - isFiltered: PropTypes.bool.isRequired, - - /** Array of header emojis */ - headerEmojis: PropTypes.arrayOf(PropTypes.shape(emojiPropTypes)).isRequired, - - /** Function to scroll to a specific header in the emoji list */ - scrollToHeader: PropTypes.func.isRequired, - - /** Style to be applied to the list wrapper */ - listWrapperStyle: stylePropTypes, - - /** Reference to the emoji list */ - forwardedRef: refPropTypes, - - /** The data for the emoji list */ - data: PropTypes.arrayOf(PropTypes.shape(emojiPropTypes)).isRequired, - - /** Function to render each item in the list */ - renderItem: PropTypes.func.isRequired, - - /** Extra data to be passed to the list for re-rendering */ - // eslint-disable-next-line react/forbid-prop-types - extraData: PropTypes.any, - - /** Array of indices for the sticky headers */ - stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number), - - /** Whether the list should always bounce vertically */ - alwaysBounceVertical: PropTypes.bool, -}; - -const defaultProps = { - listWrapperStyle: [], - forwardedRef: () => {}, - extraData: [], - stickyHeaderIndices: [], - alwaysBounceVertical: false, -}; - -/** - * Improves FlashList's recycling when there are different types of items - * @param {Object} item - * @returns {String} - */ -const getItemType = (item) => { - // item is undefined only when list is empty - if (!item) { - return; - } - - if (item.name) { - return CONST.EMOJI_PICKER_ITEM_TYPES.EMOJI; - } - if (item.header) { - return CONST.EMOJI_PICKER_ITEM_TYPES.HEADER; - } - - return CONST.EMOJI_PICKER_ITEM_TYPES.SPACER; -}; - -/** - * Return a unique key for each emoji item - * - * @param {Object} item - * @param {Number} index - * @returns {String} - */ -const keyExtractor = (item, index) => `emoji_picker_${item.code}_${index}`; - -/** - * Renders the list empty component - * @returns {React.Component} - */ -function ListEmptyComponent() { - const styles = useThemeStyles(); - const {translate} = useLocalize(); - - return {translate('common.noResultsFound')}; -} - -function BaseEmojiPickerMenu({headerEmojis, scrollToHeader, isFiltered, listWrapperStyle, forwardedRef, data, renderItem, stickyHeaderIndices, extraData, alwaysBounceVertical}) { - const styles = useThemeStyles(); - const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); - - // Estimated list size should be a whole integer to avoid floating point precision errors - // More info: https://github.com/Expensify/App/issues/34522 - const listWidth = isSmallScreenWidth ? Math.floor(windowWidth) : CONST.EMOJI_PICKER_SIZE.WIDTH; - - const flattenListWrapperStyle = useMemo(() => StyleSheet.flatten(listWrapperStyle), [listWrapperStyle]); - - return ( - <> - {!isFiltered && ( - - )} - - - - - - ); -} - -BaseEmojiPickerMenu.propTypes = propTypes; -BaseEmojiPickerMenu.defaultProps = defaultProps; -BaseEmojiPickerMenu.displayName = 'BaseEmojiPickerMenu'; - -const BaseEmojiPickerMenuWithRef = React.forwardRef((props, ref) => ( - -)); - -BaseEmojiPickerMenuWithRef.displayName = 'BaseEmojiPickerMenuWithRef'; - -export default BaseEmojiPickerMenuWithRef; From d93dba4ab8150ba7fe997cecf950a9010619f6bc Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Wed, 28 Feb 2024 12:56:35 +0300 Subject: [PATCH 13/53] wip migrate EmojiPickerButton to ts --- ...iPickerButton.js => EmojiPickerButton.tsx} | 51 ++++++++----------- 1 file changed, 20 insertions(+), 31 deletions(-) rename src/components/EmojiPicker/{EmojiPickerButton.js => EmojiPickerButton.tsx} (67%) diff --git a/src/components/EmojiPicker/EmojiPickerButton.js b/src/components/EmojiPicker/EmojiPickerButton.tsx similarity index 67% rename from src/components/EmojiPicker/EmojiPickerButton.js rename to src/components/EmojiPicker/EmojiPickerButton.tsx index 438deb7e53d9..ea5ffc7e2e1c 100644 --- a/src/components/EmojiPicker/EmojiPickerButton.js +++ b/src/components/EmojiPicker/EmojiPickerButton.tsx @@ -1,77 +1,68 @@ -import PropTypes from 'prop-types'; import React, {memo, useEffect, useRef} from 'react'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import Tooltip from '@components/Tooltip/PopoverAnchorTooltip'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import withNavigationFocus from '@components/withNavigationFocus'; +import type {WithNavigationFocusProps} from '@components/withNavigationFocus'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import getButtonState from '@libs/getButtonState'; import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; import CONST from '@src/CONST'; +import useLocalize from '@hooks/useLocalize'; -const propTypes = { +type EmojiPickerButtonProps = WithNavigationFocusProps & { /** Flag to disable the emoji picker button */ - isDisabled: PropTypes.bool, + isDisabled?: boolean, /** Id to use for the emoji picker button */ - id: PropTypes.string, + id?: string, /** Unique id for emoji picker */ - emojiPickerID: PropTypes.string, + emojiPickerID?: string, /** Emoji popup anchor offset shift vertical */ - shiftVertical: PropTypes.number, - - ...withLocalizePropTypes, -}; - -const defaultProps = { - isDisabled: false, - id: '', - emojiPickerID: '', - shiftVertical: 0, -}; + shiftVertical?: number, +} -function EmojiPickerButton(props) { +function EmojiPickerButton({isDisabled, id, emojiPickerID, shiftVertical, isFocused}: EmojiPickerButtonProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const emojiPopoverAnchor = useRef(null); + const {translate} = useLocalize(); useEffect(() => EmojiPickerAction.resetEmojiPopoverAnchor, []); return ( - + [styles.chatItemEmojiButton, StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed))]} - disabled={props.isDisabled} + disabled={isDisabled} onPress={() => { - if (!props.isFocused) { + if (!isFocused) { return; } if (!EmojiPickerAction.emojiPickerRef.current.isEmojiPickerVisible) { EmojiPickerAction.showEmojiPicker( - props.onModalHide, - props.onEmojiSelected, + onModalHide, + onEmojiSelected, emojiPopoverAnchor, { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, - shiftVertical: props.shiftVertical, + shiftVertical: shiftVertical, }, () => {}, - props.emojiPickerID, + emojiPickerID, ); } else { EmojiPickerAction.emojiPickerRef.current.hideEmojiPicker(); } }} - id={props.id} - accessibilityLabel={props.translate('reportActionCompose.emoji')} + id={id} + accessibilityLabel={translate('reportActionCompose.emoji')} > {({hovered, pressed}) => ( Date: Wed, 28 Feb 2024 12:57:10 +0300 Subject: [PATCH 14/53] wip migrate EmojiPickerButtonDropdown to ts --- ...pdown.js => EmojiPickerButtonDropdown.tsx} | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) rename src/components/EmojiPicker/{EmojiPickerButtonDropdown.js => EmojiPickerButtonDropdown.tsx} (78%) diff --git a/src/components/EmojiPicker/EmojiPickerButtonDropdown.js b/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx similarity index 78% rename from src/components/EmojiPicker/EmojiPickerButtonDropdown.js rename to src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx index 24c99988e4f0..1fe8c59a0ac5 100644 --- a/src/components/EmojiPicker/EmojiPickerButtonDropdown.js +++ b/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx @@ -6,28 +6,24 @@ import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip/PopoverAnchorTooltip'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import getButtonState from '@libs/getButtonState'; import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; import CONST from '@src/CONST'; +import useLocalize from '@hooks/useLocalize'; -const propTypes = { +type EmojiPickerButtonDropdownProps = { /** Flag to disable the emoji picker button */ - isDisabled: PropTypes.bool, - - ...withLocalizePropTypes, -}; - -const defaultProps = { - isDisabled: false, -}; + isDisabled?: boolean, +} -function EmojiPickerButtonDropdown(props) { +function EmojiPickerButtonDropdown({isDisabled}: EmojiPickerButtonDropdownProps, ) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const emojiPopoverAnchor = useRef(null); + const {translate} = useLocalize(); + useEffect(() => EmojiPickerAction.resetEmojiPopoverAnchor, []); const onPress = () => { if (EmojiPickerAction.isEmojiPickerVisible()) { @@ -51,11 +47,11 @@ function EmojiPickerButtonDropdown(props) { }; return ( - + ( - -)); +// const EmojiPickerButtonDropdownWithRef = React.forwardRef((props, ref) => ( +// +// )); -EmojiPickerButtonDropdownWithRef.displayName = 'EmojiPickerButtonDropdownWithRef'; +// EmojiPickerButtonDropdownWithRef.displayName = 'EmojiPickerButtonDropdownWithRef'; -export default withLocalize(EmojiPickerButtonDropdownWithRef); +export default React.forwardRef(EmojiPickerButtonDropdown); From 43d4aa9dfa1adcd371cdf1b1008245d4d968d4a6 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Wed, 28 Feb 2024 12:57:28 +0300 Subject: [PATCH 15/53] wip --- .../EmojiPicker/EmojiPickerMenuItem/index.js | 44 ++++++++++++------- ...iSkinToneList.js => EmojiSkinToneList.tsx} | 2 +- 2 files changed, 29 insertions(+), 17 deletions(-) rename src/components/EmojiPicker/{EmojiSkinToneList.js => EmojiSkinToneList.tsx} (98%) diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js index 52d4a0db8812..1a3d7454daef 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import React, {PureComponent} from 'react'; +import React, {PureComponent, useEffect, useRef, useState} from 'react'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import Text from '@components/Text'; import withStyleUtils, {withStyleUtilsPropTypes} from '@components/withStyleUtils'; @@ -37,23 +37,35 @@ const propTypes = { ...withStyleUtilsPropTypes, }; -class EmojiPickerMenuItem extends PureComponent { - constructor(props) { - super(props); - - this.ref = null; - this.focusAndScroll = this.focusAndScroll.bind(this); - this.state = { - isHovered: false, - }; - } - - componentDidMount() { - if (!this.props.isFocused) { +function EmojiPickerMenuItem(props) { + +// class EmojiPickerMenuItem extends PureComponent { + // constructor(props) { + // super(props); + + // this.ref = null; + // this.focusAndScroll = this.focusAndScroll.bind(this); + // this.state = { + // isHovered: false, + // }; + // } + const [isHovered, setIsHovered] = useState(false); + const ref = useRef(null); + + // componentDidMount() { + // if (!this.props.isFocused) { + // return; + // } + // this.focusAndScroll(); + // } + + useEffect(() => { + if(!props.isFocused) { return; } - this.focusAndScroll(); - } + focusAndScroll(); + }, [props.isFocused]) + componentDidUpdate(prevProps) { if (prevProps.isFocused === this.props.isFocused) { diff --git a/src/components/EmojiPicker/EmojiSkinToneList.js b/src/components/EmojiPicker/EmojiSkinToneList.tsx similarity index 98% rename from src/components/EmojiPicker/EmojiSkinToneList.js rename to src/components/EmojiPicker/EmojiSkinToneList.tsx index 7f36d929025f..c34c8270f47c 100644 --- a/src/components/EmojiPicker/EmojiSkinToneList.js +++ b/src/components/EmojiPicker/EmojiSkinToneList.tsx @@ -57,7 +57,7 @@ function EmojiSkinToneList() { )} {isSkinToneListVisible && ( - {_.map(Emojis.skinTones, (skinToneEmoji) => ( + {Emojis.skinTones.map((skinToneEmoji) => ( updateSelectedSkinTone(skinToneEmoji)} onHoverIn={() => setHighlightedIndex(skinToneEmoji.skinTone)} From 0abcb72ddd777d4b94b6b408e87b47d03946e60e Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Thu, 29 Feb 2024 10:16:57 +0300 Subject: [PATCH 16/53] migrate index.js to functional component --- .../EmojiPicker/EmojiPickerMenuItem/index.js | 69 +++++++------------ 1 file changed, 25 insertions(+), 44 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js index 1a3d7454daef..29fc030388e8 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import React, {PureComponent, useEffect, useRef, useState} from 'react'; +import React, {useEffect, useRef, useState} from 'react'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import Text from '@components/Text'; import withStyleUtils, {withStyleUtilsPropTypes} from '@components/withStyleUtils'; @@ -43,7 +43,7 @@ function EmojiPickerMenuItem(props) { // constructor(props) { // super(props); - // this.ref = null; + // ref = null; // this.focusAndScroll = this.focusAndScroll.bind(this); // this.state = { // isHovered: false, @@ -52,12 +52,10 @@ function EmojiPickerMenuItem(props) { const [isHovered, setIsHovered] = useState(false); const ref = useRef(null); - // componentDidMount() { - // if (!this.props.isFocused) { - // return; - // } - // this.focusAndScroll(); - // } + const focusAndScroll = () => { + ref.focus({preventScroll: true}); + ref.scrollIntoView({block: 'nearest'}); + } useEffect(() => { if(!props.isFocused) { @@ -66,61 +64,44 @@ function EmojiPickerMenuItem(props) { focusAndScroll(); }, [props.isFocused]) - - componentDidUpdate(prevProps) { - if (prevProps.isFocused === this.props.isFocused) { - return; - } - if (!this.props.isFocused) { - return; - } - - this.focusAndScroll(); - } - - focusAndScroll() { - this.ref.focus({preventScroll: true}); - this.ref.scrollIntoView({block: 'nearest'}); - } - - render() { + return ( this.props.onPress(this.props.emoji)} + onPress={() => props.onPress(props.emoji)} // In order to prevent haptic feedback, pass empty callback as onLongPress props. Please refer https://github.com/necolas/react-native-web/issues/2349#issuecomment-1195564240 onLongPress={Browser.isMobileChrome() ? () => {} : undefined} - onPressOut={Browser.isMobile() ? this.props.onHoverOut : undefined} + onPressOut={Browser.isMobile() ? props.onHoverOut : undefined} onHoverIn={() => { - if (this.props.onHoverIn) { - this.props.onHoverIn(); + if (props.onHoverIn) { + props.onHoverIn(); } - this.setState({isHovered: true}); + setIsHovered(true); }} onHoverOut={() => { - if (this.props.onHoverOut) { - this.props.onHoverOut(); + if (props.onHoverOut) { + props.onHoverOut(); } - this.setState({isHovered: false}); + setIsHovered(false); }} - onFocus={this.props.onFocus} - onBlur={this.props.onBlur} - ref={(ref) => (this.ref = ref)} + onFocus={props.onFocus} + onBlur={props.onBlur} + ref={(el) => (ref.current = el)} style={({pressed}) => [ - this.props.isFocused ? this.props.themeStyles.emojiItemKeyboardHighlighted : {}, - this.state.isHovered || this.props.isHighlighted ? this.props.themeStyles.emojiItemHighlighted : {}, - Browser.isMobile() && this.props.StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)), - this.props.themeStyles.emojiItem, + props.isFocused ? props.themeStyles.emojiItemKeyboardHighlighted : {}, + isHovered || props.isHighlighted ? props.themeStyles.emojiItemHighlighted : {}, + Browser.isMobile() && props.StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)), + props.themeStyles.emojiItem, ]} - accessibilityLabel={this.props.emoji} + accessibilityLabel={props.emoji} role={CONST.ROLE.BUTTON} > - {this.props.emoji} + {props.emoji} ); - } + } EmojiPickerMenuItem.propTypes = propTypes; From 0c6d6e7ac33483c682c369226ee71cad17350013 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Thu, 29 Feb 2024 10:22:31 +0300 Subject: [PATCH 17/53] migrate index.native to functional component --- .../EmojiPicker/EmojiPickerMenuItem/index.js | 11 --- .../EmojiPickerMenuItem/index.native.js | 73 ++++++++----------- 2 files changed, 29 insertions(+), 55 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js index 29fc030388e8..f93688ed46e6 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js @@ -38,17 +38,6 @@ const propTypes = { }; function EmojiPickerMenuItem(props) { - -// class EmojiPickerMenuItem extends PureComponent { - // constructor(props) { - // super(props); - - // ref = null; - // this.focusAndScroll = this.focusAndScroll.bind(this); - // this.state = { - // isHovered: false, - // }; - // } const [isHovered, setIsHovered] = useState(false); const ref = useRef(null); diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js index 1726ff5b6543..dae858d36e4f 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import React, {PureComponent} from 'react'; +import React, {useEffect, useRef} from 'react'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import Text from '@components/Text'; import withStyleUtils, {withStyleUtilsPropTypes} from '@components/withStyleUtils'; @@ -39,53 +39,38 @@ const propTypes = { ...withStyleUtilsPropTypes, }; -class EmojiPickerMenuItem extends PureComponent { - constructor(props) { - super(props); +function EmojiPickerMenuItem(props) { - this.ref = null; - } + const ref = useRef(null); - componentDidMount() { - if (!this.props.isFocused) { + useEffect(() => { + if(!props.isFocused) { return; } - this.ref.focus(); - } - - componentDidUpdate(prevProps) { - if (prevProps.isFocused === this.props.isFocused) { - return; - } - if (!this.props.isFocused) { - return; - } - this.ref.focus(); - } - - render() { - return ( - this.props.onPress(this.props.emoji)} - onHoverIn={this.props.onHoverIn} - onHoverOut={this.props.onHoverOut} - onFocus={this.props.onFocus} - onBlur={this.props.onBlur} - ref={(ref) => (this.ref = ref)} - style={({pressed}) => [ - this.props.StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)), - this.props.isHighlighted && this.props.isUsingKeyboardMovement ? this.props.themeStyles.emojiItemKeyboardHighlighted : {}, - this.props.isHighlighted && !this.props.isUsingKeyboardMovement ? this.props.themeStyles.emojiItemHighlighted : {}, - this.props.themeStyles.emojiItem, - ]} - accessibilityLabel={this.props.emoji} - role={CONST.ROLE.BUTTON} - > - {this.props.emoji} - - ); - } + ref.focus(); + }, [props.isFocused]) + + return ( + props.onPress(props.emoji)} + onHoverIn={props.onHoverIn} + onHoverOut={props.onHoverOut} + onFocus={props.onFocus} + onBlur={props.onBlur} + ref={(el) => (ref.current = el)} + style={({pressed}) => [ + props.StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)), + props.isHighlighted && props.isUsingKeyboardMovement ? props.themeStyles.emojiItemKeyboardHighlighted : {}, + props.isHighlighted && !props.isUsingKeyboardMovement ? props.themeStyles.emojiItemHighlighted : {}, + props.themeStyles.emojiItem, + ]} + accessibilityLabel={props.emoji} + role={CONST.ROLE.BUTTON} + > + {props.emoji} + + ); } EmojiPickerMenuItem.propTypes = propTypes; From 9dcdfd67e0b9e11eb9e6b5ad9e9232f3016df52b Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Thu, 29 Feb 2024 11:57:32 +0300 Subject: [PATCH 18/53] create types file for EmojiPickerMenuItem --- .../EmojiPicker/EmojiPickerMenuItem/types.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/components/EmojiPicker/EmojiPickerMenuItem/types.ts diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/types.ts b/src/components/EmojiPicker/EmojiPickerMenuItem/types.ts new file mode 100644 index 000000000000..81ad982cbc79 --- /dev/null +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/types.ts @@ -0,0 +1,27 @@ +type EmojiPickerMenuItemProps = { + /** The unicode that is used to display the emoji */ + emoji: string, + + /** The function to call when an emoji is selected */ + onPress: (emoji?: string) => void, + + /** Handles what to do when we hover over this item with our cursor */ + onHoverIn?: () => void, + + /** Handles what to do when the hover is out */ + onHoverOut?: () => void, + + /** Handles what to do when the pressable is focused */ + onFocus?: () => void, + + /** Handles what to do when the pressable is blurred */ + onBlur?: () => void, + + /** Whether this menu item is currently focused or not */ + isFocused?: boolean, + + /** Whether the menu item should be highlighted or not */ + isHighlighted?: boolean, +} + +export default EmojiPickerMenuItemProps; \ No newline at end of file From 7c8bceb1a48ecc8e7f53b5546f5599877728d0df Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Thu, 29 Feb 2024 11:58:00 +0300 Subject: [PATCH 19/53] migrate EmojiPickerMenuItem(index) to ts --- .../EmojiPicker/EmojiPickerMenuItem/index.js | 115 ------------------ .../EmojiPicker/EmojiPickerMenuItem/index.tsx | 71 +++++++++++ 2 files changed, 71 insertions(+), 115 deletions(-) delete mode 100644 src/components/EmojiPicker/EmojiPickerMenuItem/index.js create mode 100644 src/components/EmojiPicker/EmojiPickerMenuItem/index.tsx diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js deleted file mode 100644 index f93688ed46e6..000000000000 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js +++ /dev/null @@ -1,115 +0,0 @@ -import PropTypes from 'prop-types'; -import React, {useEffect, useRef, useState} from 'react'; -import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; -import Text from '@components/Text'; -import withStyleUtils, {withStyleUtilsPropTypes} from '@components/withStyleUtils'; -import withThemeStyles, {withThemeStylesPropTypes} from '@components/withThemeStyles'; -import * as Browser from '@libs/Browser'; -import getButtonState from '@libs/getButtonState'; -import CONST from '@src/CONST'; - -const propTypes = { - /** The unicode that is used to display the emoji */ - emoji: PropTypes.string.isRequired, - - /** The function to call when an emoji is selected */ - onPress: PropTypes.func.isRequired, - - /** Handles what to do when we hover over this item with our cursor */ - onHoverIn: PropTypes.func, - - /** Handles what to do when the hover is out */ - onHoverOut: PropTypes.func, - - /** Handles what to do when the pressable is focused */ - onFocus: PropTypes.func, - - /** Handles what to do when the pressable is blurred */ - onBlur: PropTypes.func, - - /** Whether this menu item is currently focused or not */ - isFocused: PropTypes.bool, - - /** Whether the menu item should be highlighted or not */ - isHighlighted: PropTypes.bool, - - ...withThemeStylesPropTypes, - ...withStyleUtilsPropTypes, -}; - -function EmojiPickerMenuItem(props) { - const [isHovered, setIsHovered] = useState(false); - const ref = useRef(null); - - const focusAndScroll = () => { - ref.focus({preventScroll: true}); - ref.scrollIntoView({block: 'nearest'}); - } - - useEffect(() => { - if(!props.isFocused) { - return; - } - focusAndScroll(); - }, [props.isFocused]) - - - return ( - props.onPress(props.emoji)} - // In order to prevent haptic feedback, pass empty callback as onLongPress props. Please refer https://github.com/necolas/react-native-web/issues/2349#issuecomment-1195564240 - onLongPress={Browser.isMobileChrome() ? () => {} : undefined} - onPressOut={Browser.isMobile() ? props.onHoverOut : undefined} - onHoverIn={() => { - if (props.onHoverIn) { - props.onHoverIn(); - } - - setIsHovered(true); - }} - onHoverOut={() => { - if (props.onHoverOut) { - props.onHoverOut(); - } - - setIsHovered(false); - }} - onFocus={props.onFocus} - onBlur={props.onBlur} - ref={(el) => (ref.current = el)} - style={({pressed}) => [ - props.isFocused ? props.themeStyles.emojiItemKeyboardHighlighted : {}, - isHovered || props.isHighlighted ? props.themeStyles.emojiItemHighlighted : {}, - Browser.isMobile() && props.StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)), - props.themeStyles.emojiItem, - ]} - accessibilityLabel={props.emoji} - role={CONST.ROLE.BUTTON} - > - {props.emoji} - - ); - -} - -EmojiPickerMenuItem.propTypes = propTypes; -EmojiPickerMenuItem.defaultProps = { - isFocused: false, - isHighlighted: false, - onHoverIn: () => {}, - onHoverOut: () => {}, - onFocus: () => {}, - onBlur: () => {}, -}; - -// Significantly speeds up re-renders of the EmojiPickerMenu's FlatList -// by only re-rendering at most two EmojiPickerMenuItems that are highlighted/un-highlighted per user action. -export default withThemeStyles( - withStyleUtils( - React.memo( - EmojiPickerMenuItem, - (prevProps, nextProps) => prevProps.isFocused === nextProps.isFocused && prevProps.isHighlighted === nextProps.isHighlighted && prevProps.emoji === nextProps.emoji, - ), - ), -); diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.tsx b/src/components/EmojiPicker/EmojiPickerMenuItem/index.tsx new file mode 100644 index 000000000000..3c6faf3ddba8 --- /dev/null +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.tsx @@ -0,0 +1,71 @@ +import React, {useEffect, useRef, useState} from 'react'; +import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; +import Text from '@components/Text'; +import * as Browser from '@libs/Browser'; +import getButtonState from '@libs/getButtonState'; +import CONST from '@src/CONST'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; +import type EmojiPickerMenuItemProps from './types'; + +function EmojiPickerMenuItem({emoji, onPress, onHoverIn, onHoverOut, onFocus, onBlur, isFocused, isHighlighted}: EmojiPickerMenuItemProps) { + const [isHovered, setIsHovered] = useState(false); + const ref = useRef(null); + const StyleUtils = useStyleUtils(); + const themeStyles = useThemeStyles(); + + const focusAndScroll = () => { + ref?.current?.focus({preventScroll: true}); + ref?.current?.scrollIntoView({block: 'nearest'}); + } + + useEffect(() => { + if(!isFocused) { + return; + } + focusAndScroll(); + }, [isFocused]) + + + return ( + onPress(emoji)} + // In order to prevent haptic feedback, pass empty callback as onLongPress Please refer https://github.com/necolas/react-native-web/issues/2349#issuecomment-1195564240 + onLongPress={Browser.isMobileChrome() ? () => {} : undefined} + onPressOut={Browser.isMobile() ? onHoverOut : undefined} + onHoverIn={() => { + if (onHoverIn) { + onHoverIn(); + } + + setIsHovered(true); + }} + onHoverOut={() => { + if (onHoverOut) { + onHoverOut(); + } + + setIsHovered(false); + }} + onFocus={onFocus} + onBlur={onBlur} + ref={ref} + style={({pressed}) => [ + isFocused ? themeStyles.emojiItemKeyboardHighlighted : {}, + isHovered || isHighlighted ? themeStyles.emojiItemHighlighted : {}, + Browser.isMobile() && StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)), + themeStyles.emojiItem, + ]} + accessibilityLabel={emoji} + role={CONST.ROLE.BUTTON} + > + {emoji} + + ); + +} + +// Significantly speeds up re-renders of the EmojiPickerMenu's FlatList +// by only re-rendering at most two EmojiPickerMenuItems that are highlighted/un-highlighted per user action. +export default React.memo(EmojiPickerMenuItem); From 22a872fce5b2f9c38fa879dda67efeab277aa20d Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Thu, 29 Feb 2024 12:06:24 +0300 Subject: [PATCH 20/53] migrate EmojiPickerMenuItem(index.native) to ts --- .../EmojiPickerMenuItem/index.native.js | 97 ------------------- .../EmojiPickerMenuItem/index.native.tsx | 49 ++++++++++ .../EmojiPicker/EmojiPickerMenuItem/types.ts | 3 + 3 files changed, 52 insertions(+), 97 deletions(-) delete mode 100644 src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js create mode 100644 src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js deleted file mode 100644 index dae858d36e4f..000000000000 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js +++ /dev/null @@ -1,97 +0,0 @@ -import PropTypes from 'prop-types'; -import React, {useEffect, useRef} from 'react'; -import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; -import Text from '@components/Text'; -import withStyleUtils, {withStyleUtilsPropTypes} from '@components/withStyleUtils'; -import withThemeStyles, {withThemeStylesPropTypes} from '@components/withThemeStyles'; -import getButtonState from '@libs/getButtonState'; -import CONST from '@src/CONST'; - -const propTypes = { - /** The unicode that is used to display the emoji */ - emoji: PropTypes.string.isRequired, - - /** The function to call when an emoji is selected */ - onPress: PropTypes.func.isRequired, - - /** Handles what to do when we hover over this item with our cursor */ - onHoverIn: PropTypes.func, - - /** Handles what to do when the hover is out */ - onHoverOut: PropTypes.func, - - /** Handles what to do when the pressable is focused */ - onFocus: PropTypes.func, - - /** Handles what to do when the pressable is blurred */ - onBlur: PropTypes.func, - - /** Whether this menu item is currently highlighted or not */ - isHighlighted: PropTypes.bool, - - /** Whether this menu item is currently focused or not */ - isFocused: PropTypes.bool, - - /** Whether the emoji is highlighted by the keyboard/mouse */ - isUsingKeyboardMovement: PropTypes.bool, - - ...withThemeStylesPropTypes, - ...withStyleUtilsPropTypes, -}; - -function EmojiPickerMenuItem(props) { - - const ref = useRef(null); - - useEffect(() => { - if(!props.isFocused) { - return; - } - ref.focus(); - }, [props.isFocused]) - - return ( - props.onPress(props.emoji)} - onHoverIn={props.onHoverIn} - onHoverOut={props.onHoverOut} - onFocus={props.onFocus} - onBlur={props.onBlur} - ref={(el) => (ref.current = el)} - style={({pressed}) => [ - props.StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)), - props.isHighlighted && props.isUsingKeyboardMovement ? props.themeStyles.emojiItemKeyboardHighlighted : {}, - props.isHighlighted && !props.isUsingKeyboardMovement ? props.themeStyles.emojiItemHighlighted : {}, - props.themeStyles.emojiItem, - ]} - accessibilityLabel={props.emoji} - role={CONST.ROLE.BUTTON} - > - {props.emoji} - - ); -} - -EmojiPickerMenuItem.propTypes = propTypes; -EmojiPickerMenuItem.defaultProps = { - isHighlighted: false, - isFocused: false, - isUsingKeyboardMovement: false, - onHoverIn: () => {}, - onHoverOut: () => {}, - onFocus: () => {}, - onBlur: () => {}, -}; - -// Significantly speeds up re-renders of the EmojiPickerMenu's FlatList -// by only re-rendering at most two EmojiPickerMenuItems that are highlighted/un-highlighted per user action. -export default withThemeStyles( - withStyleUtils( - React.memo( - EmojiPickerMenuItem, - (prevProps, nextProps) => - prevProps.isHighlighted === nextProps.isHighlighted && prevProps.emoji === nextProps.emoji && prevProps.isUsingKeyboardMovement === nextProps.isUsingKeyboardMovement, - ), - ), -); diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx new file mode 100644 index 000000000000..b77d6e69d854 --- /dev/null +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx @@ -0,0 +1,49 @@ +import React, {useEffect, useRef} from 'react'; +import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; +import Text from '@components/Text'; +import getButtonState from '@libs/getButtonState'; +import CONST from '@src/CONST'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; +import type EmojiPickerMenuItemProps from './types' + +function EmojiPickerMenuItem({emoji, onPress, onHoverIn, onHoverOut, onFocus, onBlur, isFocused, isHighlighted, isUsingKeyboardMovement}: EmojiPickerMenuItemProps) { + + const ref = useRef(null); + const StyleUtils = useStyleUtils(); + const themeStyles = useThemeStyles(); + + useEffect(() => { + if(!isFocused) { + return; + } + + ref?.current?.focus(); + }, [isFocused]) + + return ( + onPress(emoji)} + onHoverIn={onHoverIn} + onHoverOut={onHoverOut} + onFocus={onFocus} + onBlur={onBlur} + ref={ref} + style={({pressed}) => [ + StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)), + isHighlighted && isUsingKeyboardMovement ? themeStyles.emojiItemKeyboardHighlighted : {}, + isHighlighted && !isUsingKeyboardMovement ? themeStyles.emojiItemHighlighted : {}, + themeStyles.emojiItem, + ]} + accessibilityLabel={emoji} + role={CONST.ROLE.BUTTON} + > + {emoji} + + ); +} + +// Significantly speeds up re-renders of the EmojiPickerMenu's FlatList +// by only re-rendering at most two EmojiPickerMenuItems that are highlighted/un-highlighted per user action. +export default React.memo(EmojiPickerMenuItem); diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/types.ts b/src/components/EmojiPicker/EmojiPickerMenuItem/types.ts index 81ad982cbc79..a957751cda74 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/types.ts +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/types.ts @@ -22,6 +22,9 @@ type EmojiPickerMenuItemProps = { /** Whether the menu item should be highlighted or not */ isHighlighted?: boolean, + + /** Whether the emoji is highlighted by the keyboard/mouse */ + isUsingKeyboardMovement?: boolean, } export default EmojiPickerMenuItemProps; \ No newline at end of file From 4395ce7e5afdfb20a412dea5f225cc8d603abddb Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Fri, 1 Mar 2024 13:41:30 +0300 Subject: [PATCH 21/53] wip --- .../EmojiPicker/CategoryShortcutBar.tsx | 6 +- .../EmojiPicker/CategoryShortcutButton.tsx | 10 +-- .../EmojiPicker/EmojiPickerButtonDropdown.tsx | 12 +-- .../EmojiPickerMenu/BaseEmojiPickerMenu.tsx | 62 +++++---------- .../EmojiPickerMenu/index.native.tsx | 44 +++++------ .../EmojiPickerMenu/{index.js => index.tsx} | 75 +++++++------------ .../EmojiPicker/EmojiPickerMenu/types.ts | 20 ++++- .../EmojiPickerMenu/useEmojiPickerMenu.ts | 14 ++-- .../EmojiPickerMenuItem/index.native.tsx | 3 +- .../EmojiPicker/EmojiPickerMenuItem/index.tsx | 3 +- .../EmojiPicker/EmojiPickerMenuItem/types.ts | 2 +- src/libs/EmojiUtils.ts | 2 + 12 files changed, 108 insertions(+), 145 deletions(-) rename src/components/EmojiPicker/EmojiPickerMenu/{index.js => index.tsx} (88%) diff --git a/src/components/EmojiPicker/CategoryShortcutBar.tsx b/src/components/EmojiPicker/CategoryShortcutBar.tsx index bedadcb8210f..e9ac8c11a298 100644 --- a/src/components/EmojiPicker/CategoryShortcutBar.tsx +++ b/src/components/EmojiPicker/CategoryShortcutBar.tsx @@ -12,14 +12,14 @@ type CategoryShortcutBarProps = { headerEmojis: HeaderIndice[] } -function CategoryShortcutBar(props: CategoryShortcutBarProps) { +function CategoryShortcutBar({onPress, headerEmojis}: CategoryShortcutBarProps) { const styles = useThemeStyles(); return ( - {props.headerEmojis.map((headerEmoji, i) => ( + {headerEmojis.map((headerEmoji, i) => ( props.onPress(headerEmoji.index)} + onPress={() => onPress(headerEmoji.index)} // eslint-disable-next-line react/no-array-index-key key={`categoryShortcut${i}`} code={headerEmoji.code} diff --git a/src/components/EmojiPicker/CategoryShortcutButton.tsx b/src/components/EmojiPicker/CategoryShortcutButton.tsx index 049d27877006..75bd49c50704 100644 --- a/src/components/EmojiPicker/CategoryShortcutButton.tsx +++ b/src/components/EmojiPicker/CategoryShortcutButton.tsx @@ -23,7 +23,7 @@ type CategoryShortcutButtonProps = { onPress: () => void, } -function CategoryShortcutButton(props: CategoryShortcutButtonProps) { +function CategoryShortcutButton({code, icon, onPress}: CategoryShortcutButtonProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -32,21 +32,21 @@ function CategoryShortcutButton(props: CategoryShortcutButtonProps) { return ( setIsHighlighted(true)} onHoverOut={() => setIsHighlighted(false)} style={({pressed}) => [StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)), styles.categoryShortcutButton, isHighlighted && styles.emojiItemHighlighted]} - accessibilityLabel={`emojiPicker.headers.${props.code}`} + accessibilityLabel={`emojiPicker.headers.${code}`} role={CONST.ROLE.BUTTON} > diff --git a/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx b/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx index 1fe8c59a0ac5..c632a0dc1853 100644 --- a/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx +++ b/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx @@ -16,6 +16,8 @@ import useLocalize from '@hooks/useLocalize'; type EmojiPickerButtonDropdownProps = { /** Flag to disable the emoji picker button */ isDisabled?: boolean, + + } function EmojiPickerButtonDropdown({isDisabled}: EmojiPickerButtonDropdownProps, ) { @@ -85,14 +87,4 @@ function EmojiPickerButtonDropdown({isDisabled}: EmojiPickerButtonDropdownProps, EmojiPickerButtonDropdown.displayName = 'EmojiPickerButtonDropdown'; -// const EmojiPickerButtonDropdownWithRef = React.forwardRef((props, ref) => ( -// -// )); - -// EmojiPickerButtonDropdownWithRef.displayName = 'EmojiPickerButtonDropdownWithRef'; - export default React.forwardRef(EmojiPickerButtonDropdown); diff --git a/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx b/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx index dcbd3a5e2a9e..0e779bce2f1d 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx @@ -1,6 +1,7 @@ import {FlashList} from '@shopify/flash-list'; +import type {ListRenderItem} from '@shopify/flash-list'; import React, {useMemo} from 'react'; -import type {ForwardedRef} from 'react'; +import type {LegacyRef} from 'react'; import {StyleSheet, View} from 'react-native'; import type {StyleProp, ViewStyle} from 'react-native'; import CategoryShortcutBar from '@components/EmojiPicker/CategoryShortcutBar'; @@ -10,20 +11,9 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import CONST from '@src/CONST'; - -type EmojiPropTypes = { - /** The code of the item */ - code: string, - - /** Whether the item is a header or not */ - header: boolean, - - /** Whether the item is a spacer or not */ - spacer: boolean, - - /** Types of an emoji - e.g. different skin types */ - types: string[], -} +import type {Emoji, HeaderEmoji, PickerEmoji, PickerEmojis} from '@assets/emojis/types'; +import type {OnyxValue} from '@src/ONYXKEYS'; +import type {EmojiPropTypes, RenderItemProps} from './types'; type BaseEmojiPickerMenuProps = { /** Indicates if the emoji list is filtered or not */ @@ -39,14 +29,14 @@ type BaseEmojiPickerMenuProps = { listWrapperStyle?: StyleProp, /** The data for the emoji list */ - data: EmojiPropTypes[], + data: PickerEmoji[], /** Function to render each item in the list */ - renderItem: () => void, + renderItem: ({item, target}: RenderItemProps) => void, /** Extra data to be passed to the list for re-rendering */ // eslint-disable-next-line react/forbid-prop-types - extraData?: Record, + extraData?: Array | ((skinTone: number) => void)>, /** Array of indices for the sticky headers */ stickyHeaderIndices?: number[], @@ -55,12 +45,14 @@ type BaseEmojiPickerMenuProps = { alwaysBounceVertical?: boolean, } +type GetItemTypeProps = Partial & Partial + /** * Improves FlashList's recycling when there are different types of items - * @param {Object} item - * @returns {String} + * @param item + * @returns */ -const getItemType = (item) => { +const getItemType = (item: GetItemTypeProps): string | undefined => { // item is undefined only when list is empty if (!item) { return; @@ -79,15 +71,13 @@ const getItemType = (item) => { /** * Return a unique key for each emoji item * - * @param {Object} item - * @param {Number} index - * @returns {String} + * @param item + * @param index */ -const keyExtractor = (item, index) => `emoji_picker_${item.code}_${index}`; +const keyExtractor = (item: PickerEmoji, index: number): string => `emoji_picker_${item.code}_${index}`; /** * Renders the list empty component - * @returns {React.Component} */ function ListEmptyComponent() { const styles = useThemeStyles(); @@ -96,7 +86,7 @@ function ListEmptyComponent() { return {translate('common.noResultsFound')}; } -function BaseEmojiPickerMenu({headerEmojis, scrollToHeader, isFiltered, listWrapperStyle, data, renderItem, stickyHeaderIndices, extraData, alwaysBounceVertical}: BaseEmojiPickerMenuProps, forwardedRef: ForwardedRef>) { +function BaseEmojiPickerMenu({headerEmojis, scrollToHeader, isFiltered, listWrapperStyle, data, renderItem, stickyHeaderIndices, extraData, alwaysBounceVertical}: BaseEmojiPickerMenuProps, forwardedRef: LegacyRef>) { const styles = useThemeStyles(); const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); @@ -120,14 +110,14 @@ function BaseEmojiPickerMenu({headerEmojis, scrollToHeader, isFiltered, listWrap keyboardShouldPersistTaps="handled" data={data} drawDistance={CONST.EMOJI_DRAW_AMOUNT} - renderItem={renderItem} + renderItem={renderItem as ListRenderItem} keyExtractor={keyExtractor} numColumns={CONST.EMOJI_NUM_PER_ROW} stickyHeaderIndices={stickyHeaderIndices} ListEmptyComponent={ListEmptyComponent} alwaysBounceVertical={alwaysBounceVertical} estimatedItemSize={CONST.EMOJI_PICKER_ITEM_HEIGHT} - estimatedListSize={{height: flattenListWrapperStyle.height, width: listWidth}} + estimatedListSize={{height: flattenListWrapperStyle.height as number, width: listWidth}} contentContainerStyle={styles.ph4} extraData={extraData} getItemType={getItemType} @@ -138,18 +128,6 @@ function BaseEmojiPickerMenu({headerEmojis, scrollToHeader, isFiltered, listWrap ); } -BaseEmojiPickerMenu.propTypes = propTypes; -BaseEmojiPickerMenu.defaultProps = defaultProps; BaseEmojiPickerMenu.displayName = 'BaseEmojiPickerMenu'; -const BaseEmojiPickerMenuWithRef = React.forwardRef((props, ref) => ( - -)); - -BaseEmojiPickerMenuWithRef.displayName = 'BaseEmojiPickerMenuWithRef'; - -export default BaseEmojiPickerMenuWithRef; +export default React.forwardRef(BaseEmojiPickerMenu); diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx index 99ed4a91e831..9f64df68fac8 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx @@ -1,11 +1,9 @@ import React, {useCallback} from 'react'; -import type {ForwardedRef} from 'react'; -import lodashDebounce from 'lodash/debounce'; import {View} from 'react-native'; import {runOnUI, scrollTo} from 'react-native-reanimated'; +import lodashDebounce from 'lodash/debounce'; +import type {PickerEmojis} from '@assets/emojis/types'; import EmojiPickerMenuItem from '@components/EmojiPicker/EmojiPickerMenuItem'; -import type { BaseTextInputRef } from '@components/TextInput/BaseTextInput/types'; -import type {TranslationPaths} from '@src/languages/types'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; @@ -15,11 +13,12 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as EmojiUtils from '@libs/EmojiUtils'; import CONST from '@src/CONST'; -import BaseEmojiPickerMenu from './BaseEmojiPickerMenu'; -import type EmojiPickerMenuProps from './types'; +import type {TranslationPaths} from '@src/languages/types'; +import type {EmojiPickerMenuProps, RenderItemProps} from './types'; import useEmojiPickerMenu from './useEmojiPickerMenu'; +import BaseEmojiPickerMenu from './BaseEmojiPickerMenu'; -function EmojiPickerMenu(onEmojiSelected: EmojiPickerMenuProps, activeEmoji: ForwardedRef) { +function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps) { const styles = useThemeStyles(); const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); @@ -45,21 +44,21 @@ function EmojiPickerMenu(onEmojiSelected: EmojiPickerMenuProps, activeEmoji: For * * @param {String} searchTerm */ - const filterEmojis = lodashDebounce((searchTerm) => { + const filterEmojis = lodashDebounce((searchTerm: string) => { const [normalizedSearchTerm, newFilteredEmojiList] = suggestEmojis(searchTerm); if (emojiListRef.current) { - emojiListRef?.current?.scrollToOffset({offset: 0, animated: false}); + emojiListRef.current.scrollToOffset({offset: 0, animated: false}); } if (normalizedSearchTerm === '') { - setFilteredEmojis(allEmojis); + setFilteredEmojis(allEmojis as PickerEmojis); setHeaderIndices(headerRowIndices); return; } - setFilteredEmojis(newFilteredEmojiList); + setFilteredEmojis(newFilteredEmojiList as PickerEmojis); setHeaderIndices([]); }, 300); @@ -77,10 +76,11 @@ function EmojiPickerMenu(onEmojiSelected: EmojiPickerMenuProps, activeEmoji: For * Items with the code "SPACER" return nothing and are used to fill rows up to 8 * so that the sticky headers function properly. * - * @param item + * @param {Object} item + * @returns {*} */ const renderItem = useCallback( - ({item, target}) => { + ({item, target}: RenderItemProps) => { const {code, types} = item; if (item.spacer) { return null; @@ -95,11 +95,12 @@ function EmojiPickerMenu(onEmojiSelected: EmojiPickerMenuProps, activeEmoji: For } const emojiCode = types?.[preferredSkinTone as number] ? types[preferredSkinTone as number] : code; - const shouldEmojiBeHighlighted = Boolean(activeEmoji) && EmojiUtils.getRemovedSkinToneEmoji(emojiCode) === EmojiUtils.getRemovedSkinToneEmoji(activeEmoji); + const shouldEmojiBeHighlighted = !!activeEmoji && EmojiUtils.getRemovedSkinToneEmoji(emojiCode) === EmojiUtils.getRemovedSkinToneEmoji(activeEmoji); return ( onEmojiSelected(emoji, item))} + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + onPress={singleExecution((emoji) => onEmojiSelected(emoji!, item))} emoji={emojiCode} isHighlighted={shouldEmojiBeHighlighted} /> @@ -141,15 +142,4 @@ function EmojiPickerMenu(onEmojiSelected: EmojiPickerMenuProps, activeEmoji: For } EmojiPickerMenu.displayName = 'EmojiPickerMenu'; - -const EmojiPickerMenuWithRef = React.forwardRef((props, ref) => ( - -)); - -EmojiPickerMenuWithRef.displayName = 'EmojiPickerMenuWithRef'; - -export default EmojiPickerMenuWithRef; +export default React.forwardRef(EmojiPickerMenu); \ No newline at end of file diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.js b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx similarity index 88% rename from src/components/EmojiPicker/EmojiPickerMenu/index.js rename to src/components/EmojiPicker/EmojiPickerMenu/index.tsx index bcb61625a271..203e5ff5ed12 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx @@ -1,9 +1,9 @@ import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import type {ForwardedRef} from 'react'; import {View} from 'react-native'; import {scrollTo} from 'react-native-reanimated'; -import _ from 'underscore'; +import lodashDebounce from 'lodash/debounce'; import EmojiPickerMenuItem from '@components/EmojiPicker/EmojiPickerMenuItem'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; @@ -19,23 +19,16 @@ import * as EmojiUtils from '@libs/EmojiUtils'; import isEnterWhileComposition from '@libs/KeyboardShortcut/isEnterWhileComposition'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; +import type {Emoji, PickerEmojis} from '@assets/emojis/types'; +import type { TranslationPaths } from '@src/languages/types'; +import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; +import type {EmojiPickerMenuProps, RenderItemProps} from './types'; import BaseEmojiPickerMenu from './BaseEmojiPickerMenu'; -import emojiPickerMenuPropTypes from './types'; import useEmojiPickerMenu from './useEmojiPickerMenu'; -const propTypes = { - /** The ref to the search input (may be null on small screen widths) */ - forwardedRef: PropTypes.func, - ...emojiPickerMenuPropTypes, -}; - -const defaultProps = { - forwardedRef: () => {}, -}; - const throttleTime = Browser.isMobile() ? 200 : 50; -function EmojiPickerMenu({forwardedRef, onEmojiSelected, activeEmoji}) { +function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, forwardedRef: ForwardedRef) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {isSmallScreenWidth, windowWidth} = useWindowDimensions(); @@ -58,7 +51,7 @@ function EmojiPickerMenu({forwardedRef, onEmojiSelected, activeEmoji}) { } = useEmojiPickerMenu(); // Ref for the emoji search input - const searchInputRef = useRef(null); + const searchInputRef = useRef(null); // We want consistent auto focus behavior on input between native and mWeb so we have some auto focus management code that will // prevent auto focus when open picker for mobile device @@ -78,7 +71,7 @@ function EmojiPickerMenu({forwardedRef, onEmojiSelected, activeEmoji}) { }, [arePointerEventsDisabled]); const onFocusedIndexChange = useCallback( - (newIndex) => { + (newIndex: number) => { if (filteredEmojis.length === 0) { return; } @@ -92,8 +85,8 @@ function EmojiPickerMenu({forwardedRef, onEmojiSelected, activeEmoji}) { } // If the input is not focused and the new index is out of range, focus the input - if (newIndex < 0 && !searchInputRef.current.isFocused() && shouldFocusInputOnScreenFocus) { - searchInputRef.current.focus(); + if (newIndex < 0 && !searchInputRef.current?.isFocused() && shouldFocusInputOnScreenFocus) { + searchInputRef.current?.focus(); } }, [filteredEmojis.length, highlightFirstEmoji, isUsingKeyboardMovement, shouldFocusInputOnScreenFocus], @@ -115,7 +108,7 @@ function EmojiPickerMenu({forwardedRef, onEmojiSelected, activeEmoji}) { allowNegativeIndexes: true, }); - const filterEmojis = _.throttle((searchTerm) => { + const filterEmojis = lodashDebounce((searchTerm: string) => { const [normalizedSearchTerm, newFilteredEmojiList] = suggestEmojis(searchTerm); if (emojiListRef.current) { @@ -123,21 +116,21 @@ function EmojiPickerMenu({forwardedRef, onEmojiSelected, activeEmoji}) { } if (normalizedSearchTerm === '') { // There are no headers when searching, so we need to re-make them sticky when there is no search term - setFilteredEmojis(allEmojis); + setFilteredEmojis(allEmojis as PickerEmojis); setHeaderIndices(headerRowIndices); setFocusedIndex(-1); setHighlightEmoji(false); return; } // Remove sticky header indices. There are no headers while searching and we don't want to make emojis sticky - setFilteredEmojis(newFilteredEmojiList); + setFilteredEmojis(newFilteredEmojiList as PickerEmojis); setHeaderIndices([]); setHighlightFirstEmoji(true); setIsUsingKeyboardMovement(false); }, throttleTime); const keyDownHandler = useCallback( - (keyBoardEvent) => { + (keyBoardEvent: KeyboardEvent) => { if (keyBoardEvent.key.startsWith('Arrow')) { if (!isFocused || keyBoardEvent.key === 'ArrowUp' || keyBoardEvent.key === 'ArrowDown') { keyBoardEvent.preventDefault(); @@ -158,7 +151,7 @@ function EmojiPickerMenu({forwardedRef, onEmojiSelected, activeEmoji}) { return; } const emoji = lodashGet(item, ['types', preferredSkinTone], item.code); - onEmojiSelected(emoji, item); + onEmojiSelected(emoji, item as Emoji); // On web, avoid this Enter default input action; otherwise, it will add a new line in the subsequently focused composer. keyBoardEvent.preventDefault(); // On mWeb, avoid propagating this Enter keystroke to Pressable child component; otherwise, it will trigger the onEmojiSelected callback again. @@ -214,7 +207,7 @@ function EmojiPickerMenu({forwardedRef, onEmojiSelected, activeEmoji}) { // get a ref to the inner textInput element e.g. if we do // this.textInput = el} /> this will not // return a ref to the component, but rather the HTML element by default - if (shouldFocusInputOnScreenFocus && forwardedRef && _.isFunction(forwardedRef)) { + if (shouldFocusInputOnScreenFocus && forwardedRef && typeof forwardedRef === 'function') { forwardedRef(searchInputRef.current); } @@ -226,7 +219,7 @@ function EmojiPickerMenu({forwardedRef, onEmojiSelected, activeEmoji}) { }, [forwardedRef, shouldFocusInputOnScreenFocus, cleanupEventHandlers, setupEventHandlers]); const scrollToHeader = useCallback( - (headerIndex) => { + (headerIndex: number) => { if (!emojiListRef.current) { return; } @@ -242,12 +235,11 @@ function EmojiPickerMenu({forwardedRef, onEmojiSelected, activeEmoji}) { * Items with the code "SPACER" return nothing and are used to fill rows up to 8 * so that the sticky headers function properly. * - * @param {Object} item - * @param {Number} index - * @returns {*} + * @param item + * @param index */ const renderItem = useCallback( - ({item, index, target}) => { + ({item, index, target}: RenderItemProps) => { const {code, types} = item; if (item.spacer) { return null; @@ -256,12 +248,12 @@ function EmojiPickerMenu({forwardedRef, onEmojiSelected, activeEmoji}) { if (item.header) { return ( - {translate(`emojiPicker.headers.${code}`)} + {translate(`emojiPicker.headers.${code}` as TranslationPaths)} ); } - const emojiCode = types && types[preferredSkinTone] ? types[preferredSkinTone] : code; + const emojiCode = types?.[preferredSkinTone as number] ? types[preferredSkinTone as number] : code; const isEmojiFocused = index === focusedIndex && isUsingKeyboardMovement; const shouldEmojiBeHighlighted = @@ -270,7 +262,8 @@ function EmojiPickerMenu({forwardedRef, onEmojiSelected, activeEmoji}) { return ( onEmojiSelected(emoji, item))} + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + onPress={singleExecution((emoji) => onEmojiSelected(emoji!, item))} onHoverIn={() => { setHighlightEmoji(false); setHighlightFirstEmoji(false); @@ -280,7 +273,8 @@ function EmojiPickerMenu({forwardedRef, onEmojiSelected, activeEmoji}) { setIsUsingKeyboardMovement(false); }} emoji={emojiCode} - onFocus={() => setFocusedIndex(index)} + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + onFocus={() => setFocusedIndex(index!)} isFocused={isEmojiFocused} isHighlighted={shouldFirstEmojiBeHighlighted || shouldEmojiBeHighlighted} /> @@ -347,17 +341,4 @@ function EmojiPickerMenu({forwardedRef, onEmojiSelected, activeEmoji}) { } EmojiPickerMenu.displayName = 'EmojiPickerMenu'; -EmojiPickerMenu.propTypes = propTypes; -EmojiPickerMenu.defaultProps = defaultProps; - -const EmojiPickerMenuWithRef = React.forwardRef((props, ref) => ( - -)); - -EmojiPickerMenuWithRef.displayName = 'EmojiPickerMenuWithRef'; - -export default EmojiPickerMenuWithRef; +export default React.forwardRef(EmojiPickerMenu); diff --git a/src/components/EmojiPicker/EmojiPickerMenu/types.ts b/src/components/EmojiPicker/EmojiPickerMenu/types.ts index 7f6776008fe7..94c5e83c1013 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/types.ts +++ b/src/components/EmojiPicker/EmojiPickerMenu/types.ts @@ -1,8 +1,26 @@ import type {Emoji} from "@assets/emojis/types"; +import type IconAsset from "@src/types/utils/IconAsset"; type EmojiPickerMenuProps = { /** Function to add the selected emoji to the main compose text input */ onEmojiSelected: (emoji: string, emojiObject: Emoji) => void, + + activeEmoji: string, }; -export default EmojiPickerMenuProps; +type RenderItemProps = { + + item: Emoji & { + spacer?: boolean, + + header?: boolean, + } + + target: string, + + index?: number +} + +type EmojiPropTypes ={code: string; index: number; icon: IconAsset}; + +export type {EmojiPickerMenuProps, RenderItemProps, EmojiPropTypes}; diff --git a/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts b/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts index 764acafd4dcf..4647fd7fc460 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts +++ b/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts @@ -2,24 +2,23 @@ import {useCallback, useEffect, useMemo, useState} from 'react'; import {useAnimatedRef} from 'react-native-reanimated'; import emojis from '@assets/emojis'; import {useFrequentlyUsedEmojis} from '@components/OnyxProvider'; -import type {PickerEmojis} from '@assets/emojis/types'; -import type {SupportedLanguage} from '@libs/EmojiTrie'; -import type {FlashList} from '@shopify/flash-list'; import useLocalize from '@hooks/useLocalize'; import usePreferredEmojiSkinTone from '@hooks/usePreferredEmojiSkinTone'; import useStyleUtils from '@hooks/useStyleUtils'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as EmojiUtils from '@libs/EmojiUtils'; +import type {PickerEmoji, PickerEmojis} from '@assets/emojis/types'; +import type {FlashList} from '@shopify/flash-list'; const useEmojiPickerMenu = () => { - const emojiListRef = useAnimatedRef>(); + const emojiListRef = useAnimatedRef>(); const frequentlyUsedEmojis = useFrequentlyUsedEmojis(); // eslint-disable-next-line react-hooks/exhaustive-deps const allEmojis = useMemo(() => EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis), [frequentlyUsedEmojis]); const headerEmojis = useMemo(() => EmojiUtils.getHeaderEmojis(allEmojis as PickerEmojis), [allEmojis]); const headerRowIndices = useMemo(() => headerEmojis.map((headerEmoji) => headerEmoji.index), [headerEmojis]); const spacersIndexes = useMemo(() => EmojiUtils.getSpacersIndexes(allEmojis), [allEmojis]); - const [filteredEmojis, setFilteredEmojis] = useState(allEmojis); + const [filteredEmojis, setFilteredEmojis] = useState(allEmojis as PickerEmojis); const [headerIndices, setHeaderIndices] = useState(headerRowIndices); const isListFiltered = allEmojis.length !== filteredEmojis.length; const {preferredLocale} = useLocalize(); @@ -34,7 +33,7 @@ const useEmojiPickerMenu = () => { const listStyle = StyleUtils.getEmojiPickerListHeight(isListFiltered, windowHeight * 0.95); useEffect(() => { - setFilteredEmojis(allEmojis); + setFilteredEmojis(allEmojis as PickerEmojis); }, [allEmojis]); useEffect(() => { @@ -49,7 +48,7 @@ const useEmojiPickerMenu = () => { const suggestEmojis = useCallback( (searchTerm: string) => { const normalizedSearchTerm = searchTerm.toLowerCase().trim().replaceAll(':', ''); - const emojisSuggestions = EmojiUtils.suggestEmojis(`:${normalizedSearchTerm}`, preferredLocale as keyof SupportedLanguage, allEmojis.length); + const emojisSuggestions = EmojiUtils.suggestEmojis(`:${normalizedSearchTerm}`, preferredLocale, allEmojis.length); return [normalizedSearchTerm, emojisSuggestions]; }, @@ -74,3 +73,4 @@ const useEmojiPickerMenu = () => { }; export default useEmojiPickerMenu; + diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx index b77d6e69d854..966bfdf62b8c 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx @@ -36,7 +36,8 @@ function EmojiPickerMenuItem({emoji, onPress, onHoverIn, onHoverOut, onFocus, on isHighlighted && !isUsingKeyboardMovement ? themeStyles.emojiItemHighlighted : {}, themeStyles.emojiItem, ]} - accessibilityLabel={emoji} + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + accessibilityLabel={emoji!} role={CONST.ROLE.BUTTON} > {emoji} diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.tsx b/src/components/EmojiPicker/EmojiPickerMenuItem/index.tsx index 3c6faf3ddba8..c950df4f112e 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.tsx @@ -57,7 +57,8 @@ function EmojiPickerMenuItem({emoji, onPress, onHoverIn, onHoverOut, onFocus, on Browser.isMobile() && StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)), themeStyles.emojiItem, ]} - accessibilityLabel={emoji} + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + accessibilityLabel={emoji!} role={CONST.ROLE.BUTTON} > {emoji} diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/types.ts b/src/components/EmojiPicker/EmojiPickerMenuItem/types.ts index a957751cda74..f7a1de623622 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/types.ts +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/types.ts @@ -1,6 +1,6 @@ type EmojiPickerMenuItemProps = { /** The unicode that is used to display the emoji */ - emoji: string, + emoji?: string, /** The function to call when an emoji is selected */ onPress: (emoji?: string) => void, diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index 05f6fbd17503..85992835ed35 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -568,6 +568,8 @@ function getSpacersIndexes(allEmojis: EmojiPickerList): number[] { return spacersIndexes; } +export type {HeaderIndice}; + export { findEmojiByName, findEmojiByCode, From aa7b6fd109aa27b0381bfdb1431678ac1b62cd68 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Fri, 1 Mar 2024 23:33:53 +0300 Subject: [PATCH 22/53] EmojiPicker migration done --- src/components/EmojiPicker/EmojiPicker.tsx | 48 +++++++--------------- 1 file changed, 14 insertions(+), 34 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPicker.tsx b/src/components/EmojiPicker/EmojiPicker.tsx index 952df1f14776..9734df992eb6 100644 --- a/src/components/EmojiPicker/EmojiPicker.tsx +++ b/src/components/EmojiPicker/EmojiPicker.tsx @@ -10,7 +10,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import * as Browser from '@libs/Browser'; import calculateAnchorPosition from '@libs/calculateAnchorPosition'; import CONST from '@src/CONST'; -import type {OnModalHideValue, OnEmojiSelected, EmojiPopoverAnchor, AnchorOrigin, OnWillShowPicker} from '@libs/actions/EmojiPickerAction'; +import type {OnModalHideValue, OnEmojiSelected, EmojiPopoverAnchor, AnchorOrigin, OnWillShowPicker, EmojiPickerRef} from '@libs/actions/EmojiPickerAction'; import type {Emoji} from '@assets/emojis/types'; import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import EmojiPickerMenu from './EmojiPickerMenu'; @@ -24,24 +24,7 @@ type EmojiPickerProps = { viewportOffsetTop: number, } -type EmojiPickerRef = { - showEmojiPicker: ( - onModalHideValue: OnModalHideValue, - onEmojiSelectedValue: OnEmojiSelected, - emojiPopoverAnchor: EmojiPopoverAnchor, - anchorOrigin?: AnchorOrigin, - onWillShow?: OnWillShowPicker, - id?: string, - activeEmoji?: string, - ) => void; - isActive: (id: string) => boolean; - clearActive: () => void; - hideEmojiPicker: (isNavigating?: boolean) => void; - isEmojiPickerVisible: boolean; - resetEmojiPopoverAnchor: () => void; -}; - -function EmojiPicker (props: EmojiPickerProps, ref: ForwardedRef) { +function EmojiPicker ({viewportOffsetTop}: EmojiPickerProps, ref: ForwardedRef) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const [isEmojiPickerVisible, setIsEmojiPickerVisible] = useState(false); @@ -50,15 +33,15 @@ function EmojiPicker (props: EmojiPickerProps, ref: ForwardedRef vertical: 0, }); const [emojiPopoverAnchorOrigin, setEmojiPopoverAnchorOrigin] = useState(DEFAULT_ANCHOR_ORIGIN); - const [activeID, setActiveID] = useState(); - const emojiPopoverAnchorRef = useRef(); + const [activeID, setActiveID] = useState(); + const emojiPopoverAnchorRef = useRef(null); const emojiAnchorDimension = useRef({ width: 0, height: 0, }); const onModalHide = useRef(() => {}); const onEmojiSelected = useRef(() => {}); - const activeEmoji = useRef(); + const activeEmoji = useRef(); const emojiSearchInput = useRef(); const {isSmallScreenWidth, windowHeight} = useWindowDimensions(); @@ -83,21 +66,18 @@ function EmojiPicker (props: EmojiPickerProps, ref: ForwardedRef * @param id - Unique id for EmojiPicker * @param activeEmojiValue - Selected emoji to be highlighted */ - const showEmojiPicker = (onModalHideValue: OnModalHideValue, onEmojiSelectedValue: OnEmojiSelected, emojiPopoverAnchorValue: EmojiPopoverAnchor, anchorOrigin?: AnchorOrigin, onWillShow: OnWillShowPicker, id?: string, activeEmojiValue?: string) => { + const showEmojiPicker = (onModalHideValue: OnModalHideValue, onEmojiSelectedValue: OnEmojiSelected, emojiPopoverAnchorValue: EmojiPopoverAnchor, anchorOrigin?: AnchorOrigin, onWillShow?: OnWillShowPicker, id?: string, activeEmojiValue?: string) => { onModalHide.current = onModalHideValue; onEmojiSelected.current = onEmojiSelectedValue; activeEmoji.current = activeEmojiValue; emojiPopoverAnchorRef.current = emojiPopoverAnchorValue; const emojiPopoverAnchor = getEmojiPopoverAnchor(); - // if ((emojiPopoverAnchor?.current as View)?.blur) { - // Drop focus to avoid blue focus ring. - (emojiPopoverAnchor?.current as View)?.blur?.(); - // } + // Drop focus to avoid blue focus ring. + ((emojiPopoverAnchor?.current) as HTMLDivElement)?.blur(); const anchorOriginValue = anchorOrigin ?? DEFAULT_ANCHOR_ORIGIN; - calculateAnchorPosition(emojiPopoverAnchor.current, anchorOriginValue).then((value) => { - // eslint-disable-next-line es/no-optional-chaining + calculateAnchorPosition(emojiPopoverAnchor.current as View, anchorOriginValue).then((value) => { onWillShow?.(); setIsEmojiPickerVisible(true); setEmojiPopoverAnchorPosition({ @@ -127,7 +107,7 @@ function EmojiPicker (props: EmojiPickerProps, ref: ForwardedRef if (currOnModalHide) { currOnModalHide(); } - emojiPopoverAnchorRef.current = undefined; + emojiPopoverAnchorRef.current = null; }; setIsEmojiPickerVisible(false); }; @@ -139,7 +119,7 @@ function EmojiPicker (props: EmojiPickerProps, ref: ForwardedRef if (!emojiSearchInput.current) { return; } - emojiSearchInput.current?.focus(); + emojiSearchInput.current.focus(); }; /** @@ -168,9 +148,9 @@ function EmojiPicker (props: EmojiPickerProps, ref: ForwardedRef */ const isActive = (id: string) => !!id && id === activeID; - const clearActive = () => setActiveID(''); + const clearActive = () => setActiveID(null); - const resetEmojiPopoverAnchor = () => (emojiPopoverAnchorRef.current = undefined); + const resetEmojiPopoverAnchor = () => (emojiPopoverAnchorRef.current = null); useImperativeHandle(ref, () => ({showEmojiPicker, isActive, clearActive, hideEmojiPicker, isEmojiPickerVisible, resetEmojiPopoverAnchor})); @@ -227,7 +207,7 @@ function EmojiPicker (props: EmojiPickerProps, ref: ForwardedRef height: CONST.EMOJI_PICKER_SIZE.HEIGHT, }} anchorAlignment={emojiPopoverAnchorOrigin} - outerStyle={StyleUtils.getOuterModalStyle(windowHeight, props.viewportOffsetTop)} + outerStyle={StyleUtils.getOuterModalStyle(windowHeight, viewportOffsetTop)} innerContainerStyle={styles.popoverInnerContainer} anchorDimensions={emojiAnchorDimension.current} avoidKeyboard From ea27a477b8dc4919791bec62ef24edafeda5a051 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Fri, 1 Mar 2024 23:38:26 +0300 Subject: [PATCH 23/53] remove unnecessary type definition --- src/components/Reactions/AddReactionBubble.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Reactions/AddReactionBubble.tsx b/src/components/Reactions/AddReactionBubble.tsx index 6b7aa12b742e..268cd16c86ce 100644 --- a/src/components/Reactions/AddReactionBubble.tsx +++ b/src/components/Reactions/AddReactionBubble.tsx @@ -16,7 +16,7 @@ import type {AnchorOrigin} from '@userActions/EmojiPickerAction'; import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; import type {ReportAction} from '@src/types/onyx'; -import type {CloseContextMenuCallback, OpenPickerCallback, PickerRefElement} from './QuickEmojiReactions/types'; +import type {OpenPickerCallback, PickerRefElement} from './QuickEmojiReactions/types'; type AddReactionBubbleProps = { /** Whether it is for context menu so we can modify its style */ @@ -32,7 +32,7 @@ type AddReactionBubbleProps = { /** * Will get called the moment before the picker opens. */ - onWillShowPicker?: (callback: CloseContextMenuCallback) => void; + onWillShowPicker?: () => void; /** * Called when the user selects an emoji. From 616e4ac098d132986e4f17f3912f03b3b04d29d7 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Fri, 1 Mar 2024 23:40:35 +0300 Subject: [PATCH 24/53] minor changes --- src/components/EmojiPicker/EmojiPickerMenu/index.tsx | 2 +- src/components/EmojiPicker/EmojiPickerMenu/types.ts | 2 +- src/libs/EmojiUtils.ts | 2 +- src/libs/actions/EmojiPickerAction.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx index 203e5ff5ed12..9bbe8b27c958 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx @@ -150,7 +150,7 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, f if (!item) { return; } - const emoji = lodashGet(item, ['types', preferredSkinTone], item.code); + const emoji = lodashGet(item, ['types', preferredSkinTone], item.code); onEmojiSelected(emoji, item as Emoji); // On web, avoid this Enter default input action; otherwise, it will add a new line in the subsequently focused composer. keyBoardEvent.preventDefault(); diff --git a/src/components/EmojiPicker/EmojiPickerMenu/types.ts b/src/components/EmojiPicker/EmojiPickerMenu/types.ts index 94c5e83c1013..48bf416fdc22 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/types.ts +++ b/src/components/EmojiPicker/EmojiPickerMenu/types.ts @@ -5,7 +5,7 @@ type EmojiPickerMenuProps = { /** Function to add the selected emoji to the main compose text input */ onEmojiSelected: (emoji: string, emojiObject: Emoji) => void, - activeEmoji: string, + activeEmoji?: string, }; type RenderItemProps = { diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index 85992835ed35..3901d0d82324 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -554,7 +554,7 @@ const getEmojiReactionDetails = (emojiName: string, reaction: ReportActionReacti /** * Given an emoji code, returns an base emoji code without skin tone */ -const getRemovedSkinToneEmoji = (emoji: string) => emoji.replace(CONST.REGEX.EMOJI_SKIN_TONES, ''); +const getRemovedSkinToneEmoji = (emoji?: string) => emoji?.replace(CONST.REGEX.EMOJI_SKIN_TONES, ''); function getSpacersIndexes(allEmojis: EmojiPickerList): number[] { const spacersIndexes: number[] = []; diff --git a/src/libs/actions/EmojiPickerAction.ts b/src/libs/actions/EmojiPickerAction.ts index 9f01f736ed6f..61601c03dbb1 100644 --- a/src/libs/actions/EmojiPickerAction.ts +++ b/src/libs/actions/EmojiPickerAction.ts @@ -113,4 +113,4 @@ function resetEmojiPopoverAnchor() { } export {emojiPickerRef, showEmojiPicker, hideEmojiPicker, isActive, clearActive, isEmojiPickerVisible, resetEmojiPopoverAnchor}; -export type {AnchorOrigin, EmojiPickerRef, OnModalHideValue, OnEmojiSelected, EmojiPopoverAnchor, OnWillShowPicker}; +export type {AnchorOrigin, OnModalHideValue, OnEmojiSelected, EmojiPopoverAnchor, OnWillShowPicker, EmojiPickerRef}; From 13357f0c6df909a7363d4728c8bbff0f5c2f8514 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Fri, 1 Mar 2024 23:41:16 +0300 Subject: [PATCH 25/53] migration of EmojiPickerButton done --- .../EmojiPicker/EmojiPickerButton.tsx | 10 ++++--- .../EmojiPicker/EmojiPickerButtonDropdown.tsx | 27 ++++++++++++------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerButton.tsx b/src/components/EmojiPicker/EmojiPickerButton.tsx index ea5ffc7e2e1c..a1e14f679dd2 100644 --- a/src/components/EmojiPicker/EmojiPickerButton.tsx +++ b/src/components/EmojiPicker/EmojiPickerButton.tsx @@ -24,9 +24,13 @@ type EmojiPickerButtonProps = WithNavigationFocusProps & { /** Emoji popup anchor offset shift vertical */ shiftVertical?: number, + + onModalHide: EmojiPickerAction.OnModalHideValue, + + onEmojiSelected: EmojiPickerAction.OnEmojiSelected, } -function EmojiPickerButton({isDisabled, id, emojiPickerID, shiftVertical, isFocused}: EmojiPickerButtonProps) { +function EmojiPickerButton({isDisabled, id, emojiPickerID, shiftVertical, isFocused, onModalHide, onEmojiSelected}: EmojiPickerButtonProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const emojiPopoverAnchor = useRef(null); @@ -44,7 +48,7 @@ function EmojiPickerButton({isDisabled, id, emojiPickerID, shiftVertical, isFocu if (!isFocused) { return; } - if (!EmojiPickerAction.emojiPickerRef.current.isEmojiPickerVisible) { + if (!EmojiPickerAction.emojiPickerRef?.current?.isEmojiPickerVisible) { EmojiPickerAction.showEmojiPicker( onModalHide, onEmojiSelected, @@ -52,7 +56,7 @@ function EmojiPickerButton({isDisabled, id, emojiPickerID, shiftVertical, isFocu { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, - shiftVertical: shiftVertical, + shiftVertical, }, () => {}, emojiPickerID, diff --git a/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx b/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx index c632a0dc1853..240a05cfc608 100644 --- a/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx +++ b/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx @@ -1,6 +1,6 @@ -import PropTypes from 'prop-types'; import React, {useEffect, useRef} from 'react'; import {View} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; @@ -17,10 +17,19 @@ type EmojiPickerButtonDropdownProps = { /** Flag to disable the emoji picker button */ isDisabled?: boolean, - + onModalHide: EmojiPickerAction.OnModalHideValue, + + onInputChange: (emoji: string) => void, + + value?: string + + disabled?: boolean + + style: StyleProp + } -function EmojiPickerButtonDropdown({isDisabled}: EmojiPickerButtonDropdownProps, ) { +function EmojiPickerButtonDropdown({isDisabled, onModalHide, onInputChange, value, disabled, style}: EmojiPickerButtonDropdownProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const emojiPopoverAnchor = useRef(null); @@ -34,8 +43,8 @@ function EmojiPickerButtonDropdown({isDisabled}: EmojiPickerButtonDropdownProps, } EmojiPickerAction.showEmojiPicker( - props.onModalHide, - (emoji) => props.onInputChange(emoji), + onModalHide, + (emoji) => onInputChange(emoji), emojiPopoverAnchor, { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, @@ -44,7 +53,7 @@ function EmojiPickerButtonDropdown({isDisabled}: EmojiPickerButtonDropdownProps, }, () => {}, undefined, - props.value, + value, ); }; @@ -52,7 +61,7 @@ function EmojiPickerButtonDropdown({isDisabled}: EmojiPickerButtonDropdownProps, - {props.value || ( + {value ?? ( )} - + Date: Fri, 1 Mar 2024 23:42:10 +0300 Subject: [PATCH 26/53] migrate EmojiSkinTone --- .../EmojiPicker/EmojiSkinToneList.tsx | 17 +++++++++++------ .../EmojiPicker/getSkinToneEmojiFromIndex.ts | 1 - 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/components/EmojiPicker/EmojiSkinToneList.tsx b/src/components/EmojiPicker/EmojiSkinToneList.tsx index c34c8270f47c..34df03b4ee84 100644 --- a/src/components/EmojiPicker/EmojiSkinToneList.tsx +++ b/src/components/EmojiPicker/EmojiSkinToneList.tsx @@ -1,6 +1,5 @@ import React, {useCallback, useEffect, useState} from 'react'; import {View} from 'react-native'; -import _ from 'underscore'; import * as Emojis from '@assets/emojis'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import Text from '@components/Text'; @@ -11,9 +10,13 @@ import CONST from '@src/CONST'; import EmojiPickerMenuItem from './EmojiPickerMenuItem'; import getSkinToneEmojiFromIndex from './getSkinToneEmojiFromIndex'; +type SkinToneEmoji = { + skinTone: number, +} + function EmojiSkinToneList() { const styles = useThemeStyles(); - const [highlightedIndex, setHighlightedIndex] = useState(null); + const [highlightedIndex, setHighlightedIndex] = useState(null); const [isSkinToneListVisible, setIsSkinToneListVisible] = useState(false); const {translate} = useLocalize(); const [preferredSkinTone, setPreferredSkinTone] = usePreferredEmojiSkinTone(); @@ -24,11 +27,13 @@ function EmojiSkinToneList() { /** * Set the preferred skin tone in Onyx and close the skin tone picker - * @param {object} skinToneEmoji + * @param skinToneEmoji */ - function updateSelectedSkinTone(skinToneEmoji) { + function updateSelectedSkinTone(skinToneEmoji: SkinToneEmoji) { setHighlightedIndex(skinToneEmoji.skinTone); - setPreferredSkinTone(skinToneEmoji.skinTone); + if(typeof setPreferredSkinTone === 'function') { + setPreferredSkinTone(skinToneEmoji.skinTone); + } } useEffect(() => { @@ -39,7 +44,7 @@ function EmojiSkinToneList() { // eslint-disable-next-line react-hooks/exhaustive-deps -- only run when preferredSkinTone updates }, [preferredSkinTone]); - const currentSkinTone = getSkinToneEmojiFromIndex(preferredSkinTone); + const currentSkinTone = getSkinToneEmojiFromIndex(preferredSkinTone as number); return ( {!isSkinToneListVisible && ( diff --git a/src/components/EmojiPicker/getSkinToneEmojiFromIndex.ts b/src/components/EmojiPicker/getSkinToneEmojiFromIndex.ts index 6b98e3d188ea..4e9beb615303 100644 --- a/src/components/EmojiPicker/getSkinToneEmojiFromIndex.ts +++ b/src/components/EmojiPicker/getSkinToneEmojiFromIndex.ts @@ -2,7 +2,6 @@ import * as Emojis from '@assets/emojis'; /** * Fetch the emoji code of selected skinTone - * @param skinToneIndex */ function getSkinToneEmojiFromIndex(skinToneIndex: number) { return Emojis.skinTones.find((emoji) => emoji.skinTone === skinToneIndex) ?? Emojis.skinTones[0]; From 5026a281b4cbb699d7c6081eecf3c3262a1c1727 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Fri, 1 Mar 2024 23:53:40 +0300 Subject: [PATCH 27/53] run prettier --- .../EmojiPicker/CategoryShortcutBar.tsx | 8 ++--- .../EmojiPicker/CategoryShortcutButton.tsx | 10 +++---- src/components/EmojiPicker/EmojiPicker.tsx | 26 +++++++++++------ .../EmojiPicker/EmojiPickerButton.tsx | 18 ++++++------ .../EmojiPicker/EmojiPickerButtonDropdown.tsx | 17 +++++------ .../EmojiPickerMenu/BaseEmojiPickerMenu.tsx | 29 ++++++++++--------- .../EmojiPickerMenu/index.native.tsx | 6 ++-- .../EmojiPicker/EmojiPickerMenu/index.tsx | 12 ++++---- .../EmojiPicker/EmojiPickerMenu/types.ts | 23 +++++++-------- .../EmojiPickerMenu/useEmojiPickerMenu.ts | 5 ++-- .../EmojiPickerMenuItem/index.native.tsx | 11 ++++--- .../EmojiPicker/EmojiPickerMenuItem/index.tsx | 12 ++++---- .../EmojiPicker/EmojiPickerMenuItem/types.ts | 22 +++++++------- .../EmojiPicker/EmojiSkinToneList.tsx | 6 ++-- src/libs/actions/EmojiPickerAction.ts | 2 +- 15 files changed, 106 insertions(+), 101 deletions(-) diff --git a/src/components/EmojiPicker/CategoryShortcutBar.tsx b/src/components/EmojiPicker/CategoryShortcutBar.tsx index e9ac8c11a298..2fbe4efca6b7 100644 --- a/src/components/EmojiPicker/CategoryShortcutBar.tsx +++ b/src/components/EmojiPicker/CategoryShortcutBar.tsx @@ -1,16 +1,16 @@ import React from 'react'; import {View} from 'react-native'; -import type {HeaderIndice} from '@libs/EmojiUtils'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {HeaderIndice} from '@libs/EmojiUtils'; import CategoryShortcutButton from './CategoryShortcutButton'; type CategoryShortcutBarProps = { /** The function to call when an emoji is selected */ - onPress: (index: number) => void, + onPress: (index: number) => void; /** The emojis consisting emoji code and indices that the icons should link to */ - headerEmojis: HeaderIndice[] -} + headerEmojis: HeaderIndice[]; +}; function CategoryShortcutBar({onPress, headerEmojis}: CategoryShortcutBarProps) { const styles = useThemeStyles(); diff --git a/src/components/EmojiPicker/CategoryShortcutButton.tsx b/src/components/EmojiPicker/CategoryShortcutButton.tsx index 75bd49c50704..7ffff218d1fe 100644 --- a/src/components/EmojiPicker/CategoryShortcutButton.tsx +++ b/src/components/EmojiPicker/CategoryShortcutButton.tsx @@ -9,19 +9,19 @@ import useThemeStyles from '@hooks/useThemeStyles'; import getButtonState from '@libs/getButtonState'; import variables from '@styles/variables'; import CONST from '@src/CONST'; -import type IconAsset from '@src/types/utils/IconAsset'; import type {TranslationPaths} from '@src/languages/types'; +import type IconAsset from '@src/types/utils/IconAsset'; type CategoryShortcutButtonProps = { /** The emoji code of the category header */ - code: string, + code: string; /** The icon representation of the category that this button links to */ - icon: IconAsset, + icon: IconAsset; /** The function to call when an emoji is selected */ - onPress: () => void, -} + onPress: () => void; +}; function CategoryShortcutButton({code, icon, onPress}: CategoryShortcutButtonProps) { const theme = useTheme(); diff --git a/src/components/EmojiPicker/EmojiPicker.tsx b/src/components/EmojiPicker/EmojiPicker.tsx index 9734df992eb6..b38a17e8c5f9 100644 --- a/src/components/EmojiPicker/EmojiPicker.tsx +++ b/src/components/EmojiPicker/EmojiPicker.tsx @@ -2,17 +2,17 @@ import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, import type {ForwardedRef, RefObject} from 'react'; import {Dimensions} from 'react-native'; import type {View} from 'react-native'; +import type {Emoji} from '@assets/emojis/types'; import PopoverWithMeasuredContent from '@components/PopoverWithMeasuredContent'; +import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import withViewportOffsetTop from '@components/withViewportOffsetTop'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as Browser from '@libs/Browser'; +import type {AnchorOrigin, EmojiPickerRef, EmojiPopoverAnchor, OnEmojiSelected, OnModalHideValue, OnWillShowPicker} from '@libs/actions/EmojiPickerAction'; import calculateAnchorPosition from '@libs/calculateAnchorPosition'; import CONST from '@src/CONST'; -import type {OnModalHideValue, OnEmojiSelected, EmojiPopoverAnchor, AnchorOrigin, OnWillShowPicker, EmojiPickerRef} from '@libs/actions/EmojiPickerAction'; -import type {Emoji} from '@assets/emojis/types'; -import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import EmojiPickerMenu from './EmojiPickerMenu'; const DEFAULT_ANCHOR_ORIGIN = { @@ -21,10 +21,10 @@ const DEFAULT_ANCHOR_ORIGIN = { }; type EmojiPickerProps = { - viewportOffsetTop: number, -} + viewportOffsetTop: number; +}; -function EmojiPicker ({viewportOffsetTop}: EmojiPickerProps, ref: ForwardedRef) { +function EmojiPicker({viewportOffsetTop}: EmojiPickerProps, ref: ForwardedRef) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const [isEmojiPickerVisible, setIsEmojiPickerVisible] = useState(false); @@ -66,14 +66,22 @@ function EmojiPicker ({viewportOffsetTop}: EmojiPickerProps, ref: ForwardedRef { + const showEmojiPicker = ( + onModalHideValue: OnModalHideValue, + onEmojiSelectedValue: OnEmojiSelected, + emojiPopoverAnchorValue: EmojiPopoverAnchor, + anchorOrigin?: AnchorOrigin, + onWillShow?: OnWillShowPicker, + id?: string, + activeEmojiValue?: string, + ) => { onModalHide.current = onModalHideValue; onEmojiSelected.current = onEmojiSelectedValue; activeEmoji.current = activeEmojiValue; emojiPopoverAnchorRef.current = emojiPopoverAnchorValue; const emojiPopoverAnchor = getEmojiPopoverAnchor(); // Drop focus to avoid blue focus ring. - ((emojiPopoverAnchor?.current) as HTMLDivElement)?.blur(); + (emojiPopoverAnchor?.current as HTMLDivElement)?.blur(); const anchorOriginValue = anchorOrigin ?? DEFAULT_ANCHOR_ORIGIN; @@ -222,7 +230,7 @@ function EmojiPicker ({viewportOffsetTop}: EmojiPickerProps, ref: ForwardedRef ); -}; +} EmojiPicker.displayName = 'EmojiPicker'; export default withViewportOffsetTop(forwardRef(EmojiPicker)); diff --git a/src/components/EmojiPicker/EmojiPickerButton.tsx b/src/components/EmojiPicker/EmojiPickerButton.tsx index a1e14f679dd2..e62c6cef80cc 100644 --- a/src/components/EmojiPicker/EmojiPickerButton.tsx +++ b/src/components/EmojiPicker/EmojiPickerButton.tsx @@ -5,30 +5,30 @@ import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeed import Tooltip from '@components/Tooltip/PopoverAnchorTooltip'; import withNavigationFocus from '@components/withNavigationFocus'; import type {WithNavigationFocusProps} from '@components/withNavigationFocus'; +import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import getButtonState from '@libs/getButtonState'; import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; import CONST from '@src/CONST'; -import useLocalize from '@hooks/useLocalize'; type EmojiPickerButtonProps = WithNavigationFocusProps & { /** Flag to disable the emoji picker button */ - isDisabled?: boolean, + isDisabled?: boolean; /** Id to use for the emoji picker button */ - id?: string, + id?: string; /** Unique id for emoji picker */ - emojiPickerID?: string, + emojiPickerID?: string; /** Emoji popup anchor offset shift vertical */ - shiftVertical?: number, + shiftVertical?: number; - onModalHide: EmojiPickerAction.OnModalHideValue, - - onEmojiSelected: EmojiPickerAction.OnEmojiSelected, -} + onModalHide: EmojiPickerAction.OnModalHideValue; + + onEmojiSelected: EmojiPickerAction.OnEmojiSelected; +}; function EmojiPickerButton({isDisabled, id, emojiPickerID, shiftVertical, isFocused, onModalHide, onEmojiSelected}: EmojiPickerButtonProps) { const styles = useThemeStyles(); diff --git a/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx b/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx index 240a05cfc608..1b438333576d 100644 --- a/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx +++ b/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx @@ -6,28 +6,27 @@ import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip/PopoverAnchorTooltip'; +import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import getButtonState from '@libs/getButtonState'; import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; import CONST from '@src/CONST'; -import useLocalize from '@hooks/useLocalize'; type EmojiPickerButtonDropdownProps = { /** Flag to disable the emoji picker button */ - isDisabled?: boolean, + isDisabled?: boolean; - onModalHide: EmojiPickerAction.OnModalHideValue, + onModalHide: EmojiPickerAction.OnModalHideValue; - onInputChange: (emoji: string) => void, + onInputChange: (emoji: string) => void; - value?: string + value?: string; - disabled?: boolean + disabled?: boolean; - style: StyleProp - -} + style: StyleProp; +}; function EmojiPickerButtonDropdown({isDisabled, onModalHide, onInputChange, value, disabled, style}: EmojiPickerButtonDropdownProps) { const styles = useThemeStyles(); diff --git a/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx b/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx index 0e779bce2f1d..bd10f2ad9227 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx @@ -4,6 +4,7 @@ import React, {useMemo} from 'react'; import type {LegacyRef} from 'react'; import {StyleSheet, View} from 'react-native'; import type {StyleProp, ViewStyle} from 'react-native'; +import type {Emoji, HeaderEmoji, PickerEmoji, PickerEmojis} from '@assets/emojis/types'; import CategoryShortcutBar from '@components/EmojiPicker/CategoryShortcutBar'; import EmojiSkinToneList from '@components/EmojiPicker/EmojiSkinToneList'; import Text from '@components/Text'; @@ -11,41 +12,40 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import CONST from '@src/CONST'; -import type {Emoji, HeaderEmoji, PickerEmoji, PickerEmojis} from '@assets/emojis/types'; import type {OnyxValue} from '@src/ONYXKEYS'; import type {EmojiPropTypes, RenderItemProps} from './types'; type BaseEmojiPickerMenuProps = { /** Indicates if the emoji list is filtered or not */ - isFiltered: boolean, + isFiltered: boolean; /** Array of header emojis */ - headerEmojis: EmojiPropTypes[], + headerEmojis: EmojiPropTypes[]; /** Function to scroll to a specific header in the emoji list */ - scrollToHeader: (headerIndex: number) => void, + scrollToHeader: (headerIndex: number) => void; /** Style to be applied to the list wrapper */ - listWrapperStyle?: StyleProp, + listWrapperStyle?: StyleProp; /** The data for the emoji list */ - data: PickerEmoji[], + data: PickerEmoji[]; /** Function to render each item in the list */ - renderItem: ({item, target}: RenderItemProps) => void, + renderItem: ({item, target}: RenderItemProps) => void; /** Extra data to be passed to the list for re-rendering */ // eslint-disable-next-line react/forbid-prop-types - extraData?: Array | ((skinTone: number) => void)>, + extraData?: Array | ((skinTone: number) => void)>; /** Array of indices for the sticky headers */ - stickyHeaderIndices?: number[], + stickyHeaderIndices?: number[]; /** Whether the list should always bounce vertically */ - alwaysBounceVertical?: boolean, -} + alwaysBounceVertical?: boolean; +}; -type GetItemTypeProps = Partial & Partial +type GetItemTypeProps = Partial & Partial; /** * Improves FlashList's recycling when there are different types of items @@ -86,7 +86,10 @@ function ListEmptyComponent() { return {translate('common.noResultsFound')}; } -function BaseEmojiPickerMenu({headerEmojis, scrollToHeader, isFiltered, listWrapperStyle, data, renderItem, stickyHeaderIndices, extraData, alwaysBounceVertical}: BaseEmojiPickerMenuProps, forwardedRef: LegacyRef>) { +function BaseEmojiPickerMenu( + {headerEmojis, scrollToHeader, isFiltered, listWrapperStyle, data, renderItem, stickyHeaderIndices, extraData, alwaysBounceVertical}: BaseEmojiPickerMenuProps, + forwardedRef: LegacyRef>, +) { const styles = useThemeStyles(); const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx index 9f64df68fac8..fbcf9c34c3ca 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx @@ -1,7 +1,7 @@ +import lodashDebounce from 'lodash/debounce'; import React, {useCallback} from 'react'; import {View} from 'react-native'; import {runOnUI, scrollTo} from 'react-native-reanimated'; -import lodashDebounce from 'lodash/debounce'; import type {PickerEmojis} from '@assets/emojis/types'; import EmojiPickerMenuItem from '@components/EmojiPicker/EmojiPickerMenuItem'; import Text from '@components/Text'; @@ -14,9 +14,9 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import * as EmojiUtils from '@libs/EmojiUtils'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; +import BaseEmojiPickerMenu from './BaseEmojiPickerMenu'; import type {EmojiPickerMenuProps, RenderItemProps} from './types'; import useEmojiPickerMenu from './useEmojiPickerMenu'; -import BaseEmojiPickerMenu from './BaseEmojiPickerMenu'; function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps) { const styles = useThemeStyles(); @@ -142,4 +142,4 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps) { } EmojiPickerMenu.displayName = 'EmojiPickerMenu'; -export default React.forwardRef(EmojiPickerMenu); \ No newline at end of file +export default React.forwardRef(EmojiPickerMenu); diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx index 9bbe8b27c958..9547f38d7278 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx @@ -1,12 +1,14 @@ +import lodashDebounce from 'lodash/debounce'; import lodashGet from 'lodash/get'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import type {ForwardedRef} from 'react'; import {View} from 'react-native'; import {scrollTo} from 'react-native-reanimated'; -import lodashDebounce from 'lodash/debounce'; +import type {Emoji, PickerEmojis} from '@assets/emojis/types'; import EmojiPickerMenuItem from '@components/EmojiPicker/EmojiPickerMenuItem'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; +import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; import useLocalize from '@hooks/useLocalize'; import useSingleExecution from '@hooks/useSingleExecution'; @@ -19,11 +21,9 @@ import * as EmojiUtils from '@libs/EmojiUtils'; import isEnterWhileComposition from '@libs/KeyboardShortcut/isEnterWhileComposition'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; -import type {Emoji, PickerEmojis} from '@assets/emojis/types'; -import type { TranslationPaths } from '@src/languages/types'; -import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; -import type {EmojiPickerMenuProps, RenderItemProps} from './types'; +import type {TranslationPaths} from '@src/languages/types'; import BaseEmojiPickerMenu from './BaseEmojiPickerMenu'; +import type {EmojiPickerMenuProps, RenderItemProps} from './types'; import useEmojiPickerMenu from './useEmojiPickerMenu'; const throttleTime = Browser.isMobile() ? 200 : 50; @@ -150,7 +150,7 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, f if (!item) { return; } - const emoji = lodashGet(item, ['types', preferredSkinTone], item.code); + const emoji = lodashGet(item, ['types', preferredSkinTone], item.code); onEmojiSelected(emoji, item as Emoji); // On web, avoid this Enter default input action; otherwise, it will add a new line in the subsequently focused composer. keyBoardEvent.preventDefault(); diff --git a/src/components/EmojiPicker/EmojiPickerMenu/types.ts b/src/components/EmojiPicker/EmojiPickerMenu/types.ts index 48bf416fdc22..e6cbabc95c07 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/types.ts +++ b/src/components/EmojiPicker/EmojiPickerMenu/types.ts @@ -1,26 +1,25 @@ -import type {Emoji} from "@assets/emojis/types"; -import type IconAsset from "@src/types/utils/IconAsset"; +import type {Emoji} from '@assets/emojis/types'; +import type IconAsset from '@src/types/utils/IconAsset'; type EmojiPickerMenuProps = { /** Function to add the selected emoji to the main compose text input */ - onEmojiSelected: (emoji: string, emojiObject: Emoji) => void, + onEmojiSelected: (emoji: string, emojiObject: Emoji) => void; - activeEmoji?: string, + activeEmoji?: string; }; type RenderItemProps = { - item: Emoji & { - spacer?: boolean, + spacer?: boolean; - header?: boolean, - } + header?: boolean; + }; - target: string, + target: string; - index?: number -} + index?: number; +}; -type EmojiPropTypes ={code: string; index: number; icon: IconAsset}; +type EmojiPropTypes = {code: string; index: number; icon: IconAsset}; export type {EmojiPickerMenuProps, RenderItemProps, EmojiPropTypes}; diff --git a/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts b/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts index 4647fd7fc460..c6a48baba29c 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts +++ b/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts @@ -1,14 +1,14 @@ +import type {FlashList} from '@shopify/flash-list'; import {useCallback, useEffect, useMemo, useState} from 'react'; import {useAnimatedRef} from 'react-native-reanimated'; import emojis from '@assets/emojis'; +import type {PickerEmoji, PickerEmojis} from '@assets/emojis/types'; import {useFrequentlyUsedEmojis} from '@components/OnyxProvider'; import useLocalize from '@hooks/useLocalize'; import usePreferredEmojiSkinTone from '@hooks/usePreferredEmojiSkinTone'; import useStyleUtils from '@hooks/useStyleUtils'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as EmojiUtils from '@libs/EmojiUtils'; -import type {PickerEmoji, PickerEmojis} from '@assets/emojis/types'; -import type {FlashList} from '@shopify/flash-list'; const useEmojiPickerMenu = () => { const emojiListRef = useAnimatedRef>(); @@ -73,4 +73,3 @@ const useEmojiPickerMenu = () => { }; export default useEmojiPickerMenu; - diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx index 966bfdf62b8c..2ea4a09bd645 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx @@ -1,25 +1,24 @@ import React, {useEffect, useRef} from 'react'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import Text from '@components/Text'; -import getButtonState from '@libs/getButtonState'; -import CONST from '@src/CONST'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; -import type EmojiPickerMenuItemProps from './types' +import getButtonState from '@libs/getButtonState'; +import CONST from '@src/CONST'; +import type EmojiPickerMenuItemProps from './types'; function EmojiPickerMenuItem({emoji, onPress, onHoverIn, onHoverOut, onFocus, onBlur, isFocused, isHighlighted, isUsingKeyboardMovement}: EmojiPickerMenuItemProps) { - const ref = useRef(null); const StyleUtils = useStyleUtils(); const themeStyles = useThemeStyles(); useEffect(() => { - if(!isFocused) { + if (!isFocused) { return; } ref?.current?.focus(); - }, [isFocused]) + }, [isFocused]); return ( { ref?.current?.focus({preventScroll: true}); ref?.current?.scrollIntoView({block: 'nearest'}); - } + }; useEffect(() => { - if(!isFocused) { + if (!isFocused) { return; } focusAndScroll(); - }, [isFocused]) + }, [isFocused]); - return ( {emoji} ); - } // Significantly speeds up re-renders of the EmojiPickerMenu's FlatList diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/types.ts b/src/components/EmojiPicker/EmojiPickerMenuItem/types.ts index f7a1de623622..a524a503a230 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/types.ts +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/types.ts @@ -1,30 +1,30 @@ type EmojiPickerMenuItemProps = { /** The unicode that is used to display the emoji */ - emoji?: string, + emoji?: string; /** The function to call when an emoji is selected */ - onPress: (emoji?: string) => void, + onPress: (emoji?: string) => void; /** Handles what to do when we hover over this item with our cursor */ - onHoverIn?: () => void, + onHoverIn?: () => void; /** Handles what to do when the hover is out */ - onHoverOut?: () => void, + onHoverOut?: () => void; /** Handles what to do when the pressable is focused */ - onFocus?: () => void, + onFocus?: () => void; /** Handles what to do when the pressable is blurred */ - onBlur?: () => void, + onBlur?: () => void; /** Whether this menu item is currently focused or not */ - isFocused?: boolean, + isFocused?: boolean; /** Whether the menu item should be highlighted or not */ - isHighlighted?: boolean, + isHighlighted?: boolean; /** Whether the emoji is highlighted by the keyboard/mouse */ - isUsingKeyboardMovement?: boolean, -} + isUsingKeyboardMovement?: boolean; +}; -export default EmojiPickerMenuItemProps; \ No newline at end of file +export default EmojiPickerMenuItemProps; diff --git a/src/components/EmojiPicker/EmojiSkinToneList.tsx b/src/components/EmojiPicker/EmojiSkinToneList.tsx index 34df03b4ee84..3932f5ff241d 100644 --- a/src/components/EmojiPicker/EmojiSkinToneList.tsx +++ b/src/components/EmojiPicker/EmojiSkinToneList.tsx @@ -11,8 +11,8 @@ import EmojiPickerMenuItem from './EmojiPickerMenuItem'; import getSkinToneEmojiFromIndex from './getSkinToneEmojiFromIndex'; type SkinToneEmoji = { - skinTone: number, -} + skinTone: number; +}; function EmojiSkinToneList() { const styles = useThemeStyles(); @@ -31,7 +31,7 @@ function EmojiSkinToneList() { */ function updateSelectedSkinTone(skinToneEmoji: SkinToneEmoji) { setHighlightedIndex(skinToneEmoji.skinTone); - if(typeof setPreferredSkinTone === 'function') { + if (typeof setPreferredSkinTone === 'function') { setPreferredSkinTone(skinToneEmoji.skinTone); } } diff --git a/src/libs/actions/EmojiPickerAction.ts b/src/libs/actions/EmojiPickerAction.ts index 61601c03dbb1..dcb98eb8bb60 100644 --- a/src/libs/actions/EmojiPickerAction.ts +++ b/src/libs/actions/EmojiPickerAction.ts @@ -9,7 +9,7 @@ import type CONST from '@src/CONST'; type AnchorOrigin = { horizontal: ValueOf; vertical: ValueOf; - shiftVertical?: number + shiftVertical?: number; }; type EmojiPopoverAnchor = MutableRefObject; From 7db7ee9bce198bb7bc875b6c6d332b4129ddf2bc Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Tue, 5 Mar 2024 20:41:40 +0300 Subject: [PATCH 28/53] fix ts issues --- .../EmojiPicker/EmojiPickerMenu/index.native.tsx | 3 +-- src/components/EmojiPicker/EmojiPickerMenu/index.tsx | 7 +++---- src/components/Reactions/AddReactionBubble.tsx | 4 ++-- src/components/Reactions/QuickEmojiReactions/types.ts | 4 ++-- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx index fbcf9c34c3ca..a4e56af68677 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx @@ -76,8 +76,7 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps) { * Items with the code "SPACER" return nothing and are used to fill rows up to 8 * so that the sticky headers function properly. * - * @param {Object} item - * @returns {*} + * @param item */ const renderItem = useCallback( ({item, target}: RenderItemProps) => { diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx index 9547f38d7278..ad6585982751 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx @@ -1,5 +1,4 @@ import lodashDebounce from 'lodash/debounce'; -import lodashGet from 'lodash/get'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import type {ForwardedRef} from 'react'; import {View} from 'react-native'; @@ -146,12 +145,12 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, f indexToSelect = 0; } - const item = filteredEmojis[indexToSelect]; + const item = filteredEmojis[indexToSelect] as Emoji; if (!item) { return; } - const emoji = lodashGet(item, ['types', preferredSkinTone], item.code); - onEmojiSelected(emoji, item as Emoji); + const emoji = item?.types?.[preferredSkinTone as number] ?? item.code; + onEmojiSelected(emoji, item); // On web, avoid this Enter default input action; otherwise, it will add a new line in the subsequently focused composer. keyBoardEvent.preventDefault(); // On mWeb, avoid propagating this Enter keystroke to Pressable child component; otherwise, it will trigger the onEmojiSelected callback again. diff --git a/src/components/Reactions/AddReactionBubble.tsx b/src/components/Reactions/AddReactionBubble.tsx index 268cd16c86ce..8364a6658270 100644 --- a/src/components/Reactions/AddReactionBubble.tsx +++ b/src/components/Reactions/AddReactionBubble.tsx @@ -16,7 +16,7 @@ import type {AnchorOrigin} from '@userActions/EmojiPickerAction'; import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; import type {ReportAction} from '@src/types/onyx'; -import type {OpenPickerCallback, PickerRefElement} from './QuickEmojiReactions/types'; +import type {CloseContextMenuCallback, OpenPickerCallback, PickerRefElement} from './QuickEmojiReactions/types'; type AddReactionBubbleProps = { /** Whether it is for context menu so we can modify its style */ @@ -32,7 +32,7 @@ type AddReactionBubbleProps = { /** * Will get called the moment before the picker opens. */ - onWillShowPicker?: () => void; + onWillShowPicker?: (callback?: CloseContextMenuCallback) => void; /** * Called when the user selects an emoji. diff --git a/src/components/Reactions/QuickEmojiReactions/types.ts b/src/components/Reactions/QuickEmojiReactions/types.ts index 7b6558364a55..0021f33ce2c0 100644 --- a/src/components/Reactions/QuickEmojiReactions/types.ts +++ b/src/components/Reactions/QuickEmojiReactions/types.ts @@ -18,7 +18,7 @@ type BaseReactionsProps = { /** * Will be called when the emoji picker is about to show. */ - onWillShowPicker?: (callback: CloseContextMenuCallback) => void; + onWillShowPicker?: (callback?: CloseContextMenuCallback) => void; /** * Callback to fire when the "open emoji picker" button is pressed. @@ -55,7 +55,7 @@ type QuickEmojiReactionsProps = BaseReactionsProps & { * Function that can be called to close the context menu * in which this component is rendered. */ - closeContextMenu: (callback: CloseContextMenuCallback) => void; + closeContextMenu: (callback?: CloseContextMenuCallback) => void; setIsEmojiPickerActive?: (state: boolean) => void; }; From f4f7229e0bba6398e0e4e8e919ab1b5a47b98e34 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Tue, 5 Mar 2024 22:36:43 +0300 Subject: [PATCH 29/53] fix lint and ts issues --- src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx | 5 ++++- src/libs/actions/EmojiPickerAction.ts | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx b/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx index 1b438333576d..c91f5bbf0d18 100644 --- a/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx +++ b/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx @@ -1,9 +1,11 @@ import React, {useEffect, useRef} from 'react'; +import type {ForwardedRef} from 'react'; import {View} from 'react-native'; import type {StyleProp, ViewStyle} from 'react-native'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; +import type {AnimatedTextInputRef} from '@components/RNTextInput'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip/PopoverAnchorTooltip'; import useLocalize from '@hooks/useLocalize'; @@ -28,7 +30,8 @@ type EmojiPickerButtonDropdownProps = { style: StyleProp; }; -function EmojiPickerButtonDropdown({isDisabled, onModalHide, onInputChange, value, disabled, style}: EmojiPickerButtonDropdownProps) { +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function EmojiPickerButtonDropdown({isDisabled, onModalHide, onInputChange, value, disabled, style}: EmojiPickerButtonDropdownProps, forwardedRef: ForwardedRef) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const emojiPopoverAnchor = useRef(null); diff --git a/src/libs/actions/EmojiPickerAction.ts b/src/libs/actions/EmojiPickerAction.ts index dcb98eb8bb60..787f105e4939 100644 --- a/src/libs/actions/EmojiPickerAction.ts +++ b/src/libs/actions/EmojiPickerAction.ts @@ -18,7 +18,6 @@ type OnWillShowPicker = (callback?: CloseContextMenuCallback) => void; type OnModalHideValue = () => void; -// TODO: Move this type to src/components/EmojiPicker/EmojiPicker.js once it is converted to TS type EmojiPickerRef = { showEmojiPicker: ( onModalHideValue: OnModalHideValue, From 8559b7b688206cfe1755d48e00a852d9ab99c03b Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Wed, 6 Mar 2024 00:23:19 +0300 Subject: [PATCH 30/53] resolve confilict --- .../EmojiPickerMenu/BaseEmojiPickerMenu.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx b/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx index bd10f2ad9227..446891610153 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx @@ -35,7 +35,6 @@ type BaseEmojiPickerMenuProps = { renderItem: ({item, target}: RenderItemProps) => void; /** Extra data to be passed to the list for re-rendering */ - // eslint-disable-next-line react/forbid-prop-types extraData?: Array | ((skinTone: number) => void)>; /** Array of indices for the sticky headers */ @@ -50,7 +49,6 @@ type GetItemTypeProps = Partial & Partial; /** * Improves FlashList's recycling when there are different types of items * @param item - * @returns */ const getItemType = (item: GetItemTypeProps): string | undefined => { // item is undefined only when list is empty @@ -124,6 +122,14 @@ function BaseEmojiPickerMenu( contentContainerStyle={styles.ph4} extraData={extraData} getItemType={getItemType} + overrideProps={{ + // scrollPaddingTop set to consider sticky header while scrolling, https://github.com/Expensify/App/issues/36883 + style: { + minHeight: 1, + minWidth: 1, + scrollPaddingTop: isFiltered ? 0 : CONST.EMOJI_PICKER_ITEM_HEIGHT, + }, + }} /> From 95628430a3be0c820f01214c6bb233d3c2a6ffb5 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Wed, 6 Mar 2024 01:09:28 +0300 Subject: [PATCH 31/53] code cleanup --- src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx | 2 +- .../EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx index a4e56af68677..ade8916708ad 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx @@ -42,7 +42,7 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps) { /** * Filter the entire list of emojis to only emojis that have the search term in their keywords * - * @param {String} searchTerm + * @param searchTerm */ const filterEmojis = lodashDebounce((searchTerm: string) => { const [normalizedSearchTerm, newFilteredEmojiList] = suggestEmojis(searchTerm); diff --git a/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts b/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts index c6a48baba29c..c96440e17426 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts +++ b/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts @@ -42,8 +42,7 @@ const useEmojiPickerMenu = () => { /** * Suggest emojis based on the search term - * @param {String} searchTerm - * @returns {[String, Array]} + * @param searchTerm */ const suggestEmojis = useCallback( (searchTerm: string) => { From b2932a2698457c70dbf19a7021025f76d73dbfa3 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Fri, 8 Mar 2024 01:04:24 +0300 Subject: [PATCH 32/53] code changes based on review --- src/components/EmojiPicker/CategoryShortcutButton.tsx | 5 ++--- .../EmojiPicker/EmojiPickerMenu/index.native.tsx | 3 +-- src/components/EmojiPicker/EmojiPickerMenu/index.tsx | 7 +++---- .../EmojiPicker/EmojiPickerMenuItem/index.native.tsx | 5 ++--- src/components/EmojiPicker/EmojiPickerMenuItem/index.tsx | 5 ++--- src/components/EmojiPicker/EmojiPickerMenuItem/types.ts | 4 ++-- src/components/EmojiPicker/EmojiSkinToneList.tsx | 4 +--- src/hooks/usePreferredEmojiSkinTone.ts | 2 +- 8 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/components/EmojiPicker/CategoryShortcutButton.tsx b/src/components/EmojiPicker/CategoryShortcutButton.tsx index 7ffff218d1fe..148fc6670e53 100644 --- a/src/components/EmojiPicker/CategoryShortcutButton.tsx +++ b/src/components/EmojiPicker/CategoryShortcutButton.tsx @@ -9,12 +9,11 @@ import useThemeStyles from '@hooks/useThemeStyles'; import getButtonState from '@libs/getButtonState'; import variables from '@styles/variables'; import CONST from '@src/CONST'; -import type {TranslationPaths} from '@src/languages/types'; import type IconAsset from '@src/types/utils/IconAsset'; type CategoryShortcutButtonProps = { /** The emoji code of the category header */ - code: string; + code: 'frequentlyUsed' | 'smileysAndEmotion' | 'peopleAndBody' | 'foodAndDrink' | 'travelAndPlaces' | 'activities' | 'objects' | 'symbols' | 'flags'; /** The icon representation of the category that this button links to */ icon: IconAsset; @@ -32,7 +31,7 @@ function CategoryShortcutButton({code, icon, onPress}: CategoryShortcutButtonPro return ( onEmojiSelected(emoji!, item))} + onPress={singleExecution((emoji) => onEmojiSelected(emoji, item))} emoji={emojiCode} isHighlighted={shouldEmojiBeHighlighted} /> diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx index ad6585982751..89c2caf25cd1 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx @@ -1,4 +1,4 @@ -import lodashDebounce from 'lodash/debounce'; +import throttle from 'lodash/throttle'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import type {ForwardedRef} from 'react'; import {View} from 'react-native'; @@ -107,7 +107,7 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, f allowNegativeIndexes: true, }); - const filterEmojis = lodashDebounce((searchTerm: string) => { + const filterEmojis = throttle((searchTerm: string) => { const [normalizedSearchTerm, newFilteredEmojiList] = suggestEmojis(searchTerm); if (emojiListRef.current) { @@ -261,8 +261,7 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, f return ( onEmojiSelected(emoji!, item))} + onPress={singleExecution((emoji) => onEmojiSelected(emoji, item))} onHoverIn={() => { setHighlightEmoji(false); setHighlightFirstEmoji(false); diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx index 2ea4a09bd645..6b1762d0cdda 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx @@ -7,7 +7,7 @@ import getButtonState from '@libs/getButtonState'; import CONST from '@src/CONST'; import type EmojiPickerMenuItemProps from './types'; -function EmojiPickerMenuItem({emoji, onPress, onHoverIn, onHoverOut, onFocus, onBlur, isFocused, isHighlighted, isUsingKeyboardMovement}: EmojiPickerMenuItemProps) { +function EmojiPickerMenuItem({emoji, onPress, onHoverIn = () => {}, onHoverOut = () => {}, onFocus = () => {}, onBlur = () => {}, isFocused = false, isHighlighted = false, isUsingKeyboardMovement = false}: EmojiPickerMenuItemProps) { const ref = useRef(null); const StyleUtils = useStyleUtils(); const themeStyles = useThemeStyles(); @@ -35,8 +35,7 @@ function EmojiPickerMenuItem({emoji, onPress, onHoverIn, onHoverOut, onFocus, on isHighlighted && !isUsingKeyboardMovement ? themeStyles.emojiItemHighlighted : {}, themeStyles.emojiItem, ]} - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - accessibilityLabel={emoji!} + accessibilityLabel={emoji} role={CONST.ROLE.BUTTON} > {emoji} diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.tsx b/src/components/EmojiPicker/EmojiPickerMenuItem/index.tsx index 6ce2c8ebf203..f3c24009dc30 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.tsx @@ -8,7 +8,7 @@ import getButtonState from '@libs/getButtonState'; import CONST from '@src/CONST'; import type EmojiPickerMenuItemProps from './types'; -function EmojiPickerMenuItem({emoji, onPress, onHoverIn, onHoverOut, onFocus, onBlur, isFocused, isHighlighted}: EmojiPickerMenuItemProps) { +function EmojiPickerMenuItem({emoji, onPress, onHoverIn = () => {}, onHoverOut = () => {}, onFocus = () => {}, onBlur = () => {}, isFocused = false, isHighlighted = false}: EmojiPickerMenuItemProps) { const [isHovered, setIsHovered] = useState(false); const ref = useRef(null); const StyleUtils = useStyleUtils(); @@ -56,8 +56,7 @@ function EmojiPickerMenuItem({emoji, onPress, onHoverIn, onHoverOut, onFocus, on Browser.isMobile() && StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)), themeStyles.emojiItem, ]} - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - accessibilityLabel={emoji!} + accessibilityLabel={emoji} role={CONST.ROLE.BUTTON} > {emoji} diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/types.ts b/src/components/EmojiPicker/EmojiPickerMenuItem/types.ts index a524a503a230..ca5a4d12a988 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/types.ts +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/types.ts @@ -1,9 +1,9 @@ type EmojiPickerMenuItemProps = { /** The unicode that is used to display the emoji */ - emoji?: string; + emoji: string; /** The function to call when an emoji is selected */ - onPress: (emoji?: string) => void; + onPress: (emoji: string) => void; /** Handles what to do when we hover over this item with our cursor */ onHoverIn?: () => void; diff --git a/src/components/EmojiPicker/EmojiSkinToneList.tsx b/src/components/EmojiPicker/EmojiSkinToneList.tsx index 3932f5ff241d..a7b25f57e696 100644 --- a/src/components/EmojiPicker/EmojiSkinToneList.tsx +++ b/src/components/EmojiPicker/EmojiSkinToneList.tsx @@ -31,9 +31,7 @@ function EmojiSkinToneList() { */ function updateSelectedSkinTone(skinToneEmoji: SkinToneEmoji) { setHighlightedIndex(skinToneEmoji.skinTone); - if (typeof setPreferredSkinTone === 'function') { - setPreferredSkinTone(skinToneEmoji.skinTone); - } + setPreferredSkinTone(skinToneEmoji.skinTone); } useEffect(() => { diff --git a/src/hooks/usePreferredEmojiSkinTone.ts b/src/hooks/usePreferredEmojiSkinTone.ts index 6eeecdb16617..3e536d3dc041 100644 --- a/src/hooks/usePreferredEmojiSkinTone.ts +++ b/src/hooks/usePreferredEmojiSkinTone.ts @@ -16,5 +16,5 @@ export default function usePreferredEmojiSkinTone() { [preferredSkinTone], ); - return [preferredSkinTone, updatePreferredSkinTone]; + return [preferredSkinTone, updatePreferredSkinTone] as const; } From 2215fe3b607c8ba397534879452d4702de2f4589 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Fri, 8 Mar 2024 01:20:47 +0300 Subject: [PATCH 33/53] resolve conflict --- src/libs/calculateAnchorPosition.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libs/calculateAnchorPosition.ts b/src/libs/calculateAnchorPosition.ts index ba4054119069..bee34c1c1e96 100644 --- a/src/libs/calculateAnchorPosition.ts +++ b/src/libs/calculateAnchorPosition.ts @@ -1,5 +1,3 @@ -/* eslint-disable no-restricted-imports */ - import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; import type {AnchorDimensions, AnchorPosition} from '@src/styles'; From 795e3e65ae1b2f0723f2f830a7bc5ad2705f7028 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire <39636266+HezekielT@users.noreply.github.com> Date: Fri, 8 Mar 2024 01:22:47 +0300 Subject: [PATCH 34/53] remove params without description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Błażej Kustra <46095609+blazejkustra@users.noreply.github.com> --- src/components/EmojiPicker/EmojiSkinToneList.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/EmojiPicker/EmojiSkinToneList.tsx b/src/components/EmojiPicker/EmojiSkinToneList.tsx index a7b25f57e696..43ffb1de796b 100644 --- a/src/components/EmojiPicker/EmojiSkinToneList.tsx +++ b/src/components/EmojiPicker/EmojiSkinToneList.tsx @@ -27,7 +27,6 @@ function EmojiSkinToneList() { /** * Set the preferred skin tone in Onyx and close the skin tone picker - * @param skinToneEmoji */ function updateSelectedSkinTone(skinToneEmoji: SkinToneEmoji) { setHighlightedIndex(skinToneEmoji.skinTone); From ae9f2136420389237a1511e9d2911b4aac892166 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire <39636266+HezekielT@users.noreply.github.com> Date: Fri, 8 Mar 2024 01:25:16 +0300 Subject: [PATCH 35/53] Update style conditions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Błażej Kustra <46095609+blazejkustra@users.noreply.github.com> --- .../EmojiPicker/EmojiPickerMenuItem/index.native.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx index 6b1762d0cdda..ce997346d66b 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx @@ -31,8 +31,8 @@ function EmojiPickerMenuItem({emoji, onPress, onHoverIn = () => {}, onHoverOut = ref={ref} style={({pressed}) => [ StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)), - isHighlighted && isUsingKeyboardMovement ? themeStyles.emojiItemKeyboardHighlighted : {}, - isHighlighted && !isUsingKeyboardMovement ? themeStyles.emojiItemHighlighted : {}, + isHighlighted && isUsingKeyboardMovement && themeStyles.emojiItemKeyboardHighlighted, + isHighlighted && !isUsingKeyboardMovement && themeStyles.emojiItemHighlighted, themeStyles.emojiItem, ]} accessibilityLabel={emoji} From 4305596142e22abb928f41a57efec022a639385b Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire <39636266+HezekielT@users.noreply.github.com> Date: Fri, 8 Mar 2024 01:28:34 +0300 Subject: [PATCH 36/53] Update src/components/EmojiPicker/EmojiPickerMenu/types.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Błażej Kustra <46095609+blazejkustra@users.noreply.github.com> --- src/components/EmojiPicker/EmojiPickerMenu/types.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/types.ts b/src/components/EmojiPicker/EmojiPickerMenu/types.ts index e6cbabc95c07..2babe0dcf2b0 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/types.ts +++ b/src/components/EmojiPicker/EmojiPickerMenu/types.ts @@ -11,12 +11,9 @@ type EmojiPickerMenuProps = { type RenderItemProps = { item: Emoji & { spacer?: boolean; - header?: boolean; }; - target: string; - index?: number; }; From bfcc90795d13eb3d124f4ba7af4f2daa116586ae Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire <39636266+HezekielT@users.noreply.github.com> Date: Fri, 8 Mar 2024 01:29:08 +0300 Subject: [PATCH 37/53] Update src/components/EmojiPicker/EmojiPickerMenu/index.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Błażej Kustra <46095609+blazejkustra@users.noreply.github.com> --- src/components/EmojiPicker/EmojiPickerMenu/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx index 89c2caf25cd1..3e79b638c4d3 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx @@ -234,8 +234,6 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, f * Items with the code "SPACER" return nothing and are used to fill rows up to 8 * so that the sticky headers function properly. * - * @param item - * @param index */ const renderItem = useCallback( ({item, index, target}: RenderItemProps) => { From cd2f62086e7e56394a6a8020a7c4fd829312a626 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire <39636266+HezekielT@users.noreply.github.com> Date: Fri, 8 Mar 2024 01:33:00 +0300 Subject: [PATCH 38/53] Update src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Błażej Kustra <46095609+blazejkustra@users.noreply.github.com> --- .../EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx b/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx index 446891610153..a8890a426051 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx @@ -69,8 +69,6 @@ const getItemType = (item: GetItemTypeProps): string | undefined => { /** * Return a unique key for each emoji item * - * @param item - * @param index */ const keyExtractor = (item: PickerEmoji, index: number): string => `emoji_picker_${item.code}_${index}`; From 280a84943cd07bb548c5107e944467ce7101f211 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Fri, 8 Mar 2024 02:00:12 +0300 Subject: [PATCH 39/53] run prettier --- .../EmojiPicker/EmojiPickerMenuItem/index.native.tsx | 12 +++++++++++- .../EmojiPicker/EmojiPickerMenuItem/index.tsx | 11 ++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx index ce997346d66b..09a053719b19 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx @@ -7,7 +7,17 @@ import getButtonState from '@libs/getButtonState'; import CONST from '@src/CONST'; import type EmojiPickerMenuItemProps from './types'; -function EmojiPickerMenuItem({emoji, onPress, onHoverIn = () => {}, onHoverOut = () => {}, onFocus = () => {}, onBlur = () => {}, isFocused = false, isHighlighted = false, isUsingKeyboardMovement = false}: EmojiPickerMenuItemProps) { +function EmojiPickerMenuItem({ + emoji, + onPress, + onHoverIn = () => {}, + onHoverOut = () => {}, + onFocus = () => {}, + onBlur = () => {}, + isFocused = false, + isHighlighted = false, + isUsingKeyboardMovement = false, +}: EmojiPickerMenuItemProps) { const ref = useRef(null); const StyleUtils = useStyleUtils(); const themeStyles = useThemeStyles(); diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.tsx b/src/components/EmojiPicker/EmojiPickerMenuItem/index.tsx index f3c24009dc30..2e3578d23417 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.tsx @@ -8,7 +8,16 @@ import getButtonState from '@libs/getButtonState'; import CONST from '@src/CONST'; import type EmojiPickerMenuItemProps from './types'; -function EmojiPickerMenuItem({emoji, onPress, onHoverIn = () => {}, onHoverOut = () => {}, onFocus = () => {}, onBlur = () => {}, isFocused = false, isHighlighted = false}: EmojiPickerMenuItemProps) { +function EmojiPickerMenuItem({ + emoji, + onPress, + onHoverIn = () => {}, + onHoverOut = () => {}, + onFocus = () => {}, + onBlur = () => {}, + isFocused = false, + isHighlighted = false, +}: EmojiPickerMenuItemProps) { const [isHovered, setIsHovered] = useState(false); const ref = useRef(null); const StyleUtils = useStyleUtils(); From bb66611df9a5782b367464e2c004d823c6d6411e Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Fri, 8 Mar 2024 02:05:59 +0300 Subject: [PATCH 40/53] remove params without description --- src/components/EmojiPicker/EmojiPicker.tsx | 7 ------- .../EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx | 1 - .../EmojiPicker/EmojiPickerMenu/index.native.tsx | 4 ---- 3 files changed, 12 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPicker.tsx b/src/components/EmojiPicker/EmojiPicker.tsx index b38a17e8c5f9..8c48aceff760 100644 --- a/src/components/EmojiPicker/EmojiPicker.tsx +++ b/src/components/EmojiPicker/EmojiPicker.tsx @@ -103,8 +103,6 @@ function EmojiPicker({viewportOffsetTop}: EmojiPickerProps, ref: ForwardedRef { if (isNavigating) { @@ -132,9 +130,6 @@ function EmojiPicker({viewportOffsetTop}: EmojiPickerProps, ref: ForwardedRef { // Prevent fast click / multiple emoji selection; @@ -151,8 +146,6 @@ function EmojiPicker({viewportOffsetTop}: EmojiPickerProps, ref: ForwardedRef !!id && id === activeID; diff --git a/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx b/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx index a8890a426051..68c9dfd488ee 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx @@ -48,7 +48,6 @@ type GetItemTypeProps = Partial & Partial; /** * Improves FlashList's recycling when there are different types of items - * @param item */ const getItemType = (item: GetItemTypeProps): string | undefined => { // item is undefined only when list is empty diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx index 5b6003e344ec..75c05e0cfc96 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx @@ -41,8 +41,6 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps) { /** * Filter the entire list of emojis to only emojis that have the search term in their keywords - * - * @param searchTerm */ const filterEmojis = lodashDebounce((searchTerm: string) => { const [normalizedSearchTerm, newFilteredEmojiList] = suggestEmojis(searchTerm); @@ -75,8 +73,6 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps) { * Given an emoji item object, render a component based on its type. * Items with the code "SPACER" return nothing and are used to fill rows up to 8 * so that the sticky headers function properly. - * - * @param item */ const renderItem = useCallback( ({item, target}: RenderItemProps) => { From abc8a60d57d396ec245d534499587fa328db7ed1 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Fri, 8 Mar 2024 12:25:26 +0300 Subject: [PATCH 41/53] improve code --- .../EmojiPickerMenu/BaseEmojiPickerMenu.tsx | 13 +++++++------ .../EmojiPicker/EmojiPickerMenu/index.tsx | 6 +++--- .../EmojiPickerMenu/useEmojiPickerMenu.ts | 9 ++++----- src/libs/EmojiUtils.ts | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx b/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx index 68c9dfd488ee..e02063e524ff 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx @@ -4,13 +4,14 @@ import React, {useMemo} from 'react'; import type {LegacyRef} from 'react'; import {StyleSheet, View} from 'react-native'; import type {StyleProp, ViewStyle} from 'react-native'; -import type {Emoji, HeaderEmoji, PickerEmoji, PickerEmojis} from '@assets/emojis/types'; +import type {Emoji, HeaderEmoji} from '@assets/emojis/types'; import CategoryShortcutBar from '@components/EmojiPicker/CategoryShortcutBar'; import EmojiSkinToneList from '@components/EmojiPicker/EmojiSkinToneList'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import type {EmojiPickerList, EmojiSpacer} from '@libs/EmojiUtils'; import CONST from '@src/CONST'; import type {OnyxValue} from '@src/ONYXKEYS'; import type {EmojiPropTypes, RenderItemProps} from './types'; @@ -29,13 +30,13 @@ type BaseEmojiPickerMenuProps = { listWrapperStyle?: StyleProp; /** The data for the emoji list */ - data: PickerEmoji[]; + data: EmojiPickerList; /** Function to render each item in the list */ renderItem: ({item, target}: RenderItemProps) => void; /** Extra data to be passed to the list for re-rendering */ - extraData?: Array | ((skinTone: number) => void)>; + extraData?: Array | ((skinTone: number) => void)>; /** Array of indices for the sticky headers */ stickyHeaderIndices?: number[]; @@ -69,7 +70,7 @@ const getItemType = (item: GetItemTypeProps): string | undefined => { * Return a unique key for each emoji item * */ -const keyExtractor = (item: PickerEmoji, index: number): string => `emoji_picker_${item.code}_${index}`; +const keyExtractor = (item: Emoji | HeaderEmoji | EmojiSpacer, index: number): string => `emoji_picker_${item.code}_${index}`; /** * Renders the list empty component @@ -83,7 +84,7 @@ function ListEmptyComponent() { function BaseEmojiPickerMenu( {headerEmojis, scrollToHeader, isFiltered, listWrapperStyle, data, renderItem, stickyHeaderIndices, extraData, alwaysBounceVertical}: BaseEmojiPickerMenuProps, - forwardedRef: LegacyRef>, + forwardedRef: LegacyRef>, ) { const styles = useThemeStyles(); const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); @@ -108,7 +109,7 @@ function BaseEmojiPickerMenu( keyboardShouldPersistTaps="handled" data={data} drawDistance={CONST.EMOJI_DRAW_AMOUNT} - renderItem={renderItem as ListRenderItem} + renderItem={renderItem as ListRenderItem} keyExtractor={keyExtractor} numColumns={CONST.EMOJI_NUM_PER_ROW} stickyHeaderIndices={stickyHeaderIndices} diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx index 3e79b638c4d3..946ef02e4513 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx @@ -3,7 +3,7 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import type {ForwardedRef} from 'react'; import {View} from 'react-native'; import {scrollTo} from 'react-native-reanimated'; -import type {Emoji, PickerEmojis} from '@assets/emojis/types'; +import type {Emoji} from '@assets/emojis/types'; import EmojiPickerMenuItem from '@components/EmojiPicker/EmojiPickerMenuItem'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; @@ -115,14 +115,14 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, f } if (normalizedSearchTerm === '') { // There are no headers when searching, so we need to re-make them sticky when there is no search term - setFilteredEmojis(allEmojis as PickerEmojis); + setFilteredEmojis(allEmojis); setHeaderIndices(headerRowIndices); setFocusedIndex(-1); setHighlightEmoji(false); return; } // Remove sticky header indices. There are no headers while searching and we don't want to make emojis sticky - setFilteredEmojis(newFilteredEmojiList as PickerEmojis); + setFilteredEmojis(newFilteredEmojiList as EmojiUtils.EmojiPickerList); setHeaderIndices([]); setHighlightFirstEmoji(true); setIsUsingKeyboardMovement(false); diff --git a/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts b/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts index c96440e17426..c05e6c0bbb2a 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts +++ b/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts @@ -2,7 +2,7 @@ import type {FlashList} from '@shopify/flash-list'; import {useCallback, useEffect, useMemo, useState} from 'react'; import {useAnimatedRef} from 'react-native-reanimated'; import emojis from '@assets/emojis'; -import type {PickerEmoji, PickerEmojis} from '@assets/emojis/types'; +import type {Emoji, HeaderEmoji, PickerEmojis} from '@assets/emojis/types'; import {useFrequentlyUsedEmojis} from '@components/OnyxProvider'; import useLocalize from '@hooks/useLocalize'; import usePreferredEmojiSkinTone from '@hooks/usePreferredEmojiSkinTone'; @@ -11,14 +11,14 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import * as EmojiUtils from '@libs/EmojiUtils'; const useEmojiPickerMenu = () => { - const emojiListRef = useAnimatedRef>(); + const emojiListRef = useAnimatedRef>(); const frequentlyUsedEmojis = useFrequentlyUsedEmojis(); // eslint-disable-next-line react-hooks/exhaustive-deps const allEmojis = useMemo(() => EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis), [frequentlyUsedEmojis]); const headerEmojis = useMemo(() => EmojiUtils.getHeaderEmojis(allEmojis as PickerEmojis), [allEmojis]); const headerRowIndices = useMemo(() => headerEmojis.map((headerEmoji) => headerEmoji.index), [headerEmojis]); const spacersIndexes = useMemo(() => EmojiUtils.getSpacersIndexes(allEmojis), [allEmojis]); - const [filteredEmojis, setFilteredEmojis] = useState(allEmojis as PickerEmojis); + const [filteredEmojis, setFilteredEmojis] = useState(allEmojis); const [headerIndices, setHeaderIndices] = useState(headerRowIndices); const isListFiltered = allEmojis.length !== filteredEmojis.length; const {preferredLocale} = useLocalize(); @@ -33,7 +33,7 @@ const useEmojiPickerMenu = () => { const listStyle = StyleUtils.getEmojiPickerListHeight(isListFiltered, windowHeight * 0.95); useEffect(() => { - setFilteredEmojis(allEmojis as PickerEmojis); + setFilteredEmojis(allEmojis); }, [allEmojis]); useEffect(() => { @@ -42,7 +42,6 @@ const useEmojiPickerMenu = () => { /** * Suggest emojis based on the search term - * @param searchTerm */ const suggestEmojis = useCallback( (searchTerm: string) => { diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index 3901d0d82324..844be6eb66ce 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -568,7 +568,7 @@ function getSpacersIndexes(allEmojis: EmojiPickerList): number[] { return spacersIndexes; } -export type {HeaderIndice}; +export type {HeaderIndice, EmojiPickerList, EmojiSpacer}; export { findEmojiByName, From e1927748872514c91838924f7d8d383076f4eeb5 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Fri, 8 Mar 2024 12:46:38 +0300 Subject: [PATCH 42/53] run prettier --- src/components/EmojiPicker/CategoryShortcutButton.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/EmojiPicker/CategoryShortcutButton.tsx b/src/components/EmojiPicker/CategoryShortcutButton.tsx index 148fc6670e53..7ffff218d1fe 100644 --- a/src/components/EmojiPicker/CategoryShortcutButton.tsx +++ b/src/components/EmojiPicker/CategoryShortcutButton.tsx @@ -9,11 +9,12 @@ import useThemeStyles from '@hooks/useThemeStyles'; import getButtonState from '@libs/getButtonState'; import variables from '@styles/variables'; import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; import type IconAsset from '@src/types/utils/IconAsset'; type CategoryShortcutButtonProps = { /** The emoji code of the category header */ - code: 'frequentlyUsed' | 'smileysAndEmotion' | 'peopleAndBody' | 'foodAndDrink' | 'travelAndPlaces' | 'activities' | 'objects' | 'symbols' | 'flags'; + code: string; /** The icon representation of the category that this button links to */ icon: IconAsset; @@ -31,7 +32,7 @@ function CategoryShortcutButton({code, icon, onPress}: CategoryShortcutButtonPro return ( Date: Fri, 8 Mar 2024 16:23:42 +0300 Subject: [PATCH 43/53] add default props --- src/components/EmojiPicker/EmojiPickerButton.tsx | 2 +- src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx | 7 +++++-- .../EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerButton.tsx b/src/components/EmojiPicker/EmojiPickerButton.tsx index e62c6cef80cc..3912184ba7b5 100644 --- a/src/components/EmojiPicker/EmojiPickerButton.tsx +++ b/src/components/EmojiPicker/EmojiPickerButton.tsx @@ -30,7 +30,7 @@ type EmojiPickerButtonProps = WithNavigationFocusProps & { onEmojiSelected: EmojiPickerAction.OnEmojiSelected; }; -function EmojiPickerButton({isDisabled, id, emojiPickerID, shiftVertical, isFocused, onModalHide, onEmojiSelected}: EmojiPickerButtonProps) { +function EmojiPickerButton({isDisabled = false, id = '', emojiPickerID = '', shiftVertical = 0, isFocused, onModalHide, onEmojiSelected}: EmojiPickerButtonProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const emojiPopoverAnchor = useRef(null); diff --git a/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx b/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx index c91f5bbf0d18..1a853f599fce 100644 --- a/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx +++ b/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx @@ -30,8 +30,11 @@ type EmojiPickerButtonDropdownProps = { style: StyleProp; }; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -function EmojiPickerButtonDropdown({isDisabled, onModalHide, onInputChange, value, disabled, style}: EmojiPickerButtonDropdownProps, forwardedRef: ForwardedRef) { +function EmojiPickerButtonDropdown( + {isDisabled = false, onModalHide, onInputChange, value, disabled, style}: EmojiPickerButtonDropdownProps, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + forwardedRef: ForwardedRef, +) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const emojiPopoverAnchor = useRef(null); diff --git a/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx b/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx index e02063e524ff..24f8f35d935a 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx @@ -83,7 +83,7 @@ function ListEmptyComponent() { } function BaseEmojiPickerMenu( - {headerEmojis, scrollToHeader, isFiltered, listWrapperStyle, data, renderItem, stickyHeaderIndices, extraData, alwaysBounceVertical}: BaseEmojiPickerMenuProps, + {headerEmojis, scrollToHeader, isFiltered, listWrapperStyle = [], data, renderItem, stickyHeaderIndices = [], extraData = [], alwaysBounceVertical = false}: BaseEmojiPickerMenuProps, forwardedRef: LegacyRef>, ) { const styles = useThemeStyles(); From 22fbbe8bf7fcb778a366463a2cb16e953412f8e2 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire <39636266+HezekielT@users.noreply.github.com> Date: Fri, 8 Mar 2024 16:24:50 +0300 Subject: [PATCH 44/53] Update src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Błażej Kustra <46095609+blazejkustra@users.noreply.github.com> --- .../EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx b/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx index 24f8f35d935a..78f2e722753f 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx @@ -33,7 +33,7 @@ type BaseEmojiPickerMenuProps = { data: EmojiPickerList; /** Function to render each item in the list */ - renderItem: ({item, target}: RenderItemProps) => void; + renderItem: (item: RenderItemProps) => void; /** Extra data to be passed to the list for re-rendering */ extraData?: Array | ((skinTone: number) => void)>; From 4423c09b533031c6374bc2213986e14f2d1e7a5b Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Sun, 10 Mar 2024 23:40:22 +0300 Subject: [PATCH 45/53] code cleanup --- src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx index 75c05e0cfc96..925296876cfb 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx @@ -2,7 +2,6 @@ import lodashDebounce from 'lodash/debounce'; import React, {useCallback} from 'react'; import {View} from 'react-native'; import {runOnUI, scrollTo} from 'react-native-reanimated'; -import type {PickerEmojis} from '@assets/emojis/types'; import EmojiPickerMenuItem from '@components/EmojiPicker/EmojiPickerMenuItem'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; @@ -50,13 +49,13 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps) { } if (normalizedSearchTerm === '') { - setFilteredEmojis(allEmojis as PickerEmojis); + setFilteredEmojis(allEmojis); setHeaderIndices(headerRowIndices); return; } - setFilteredEmojis(newFilteredEmojiList as PickerEmojis); + setFilteredEmojis(newFilteredEmojiList as EmojiUtils.EmojiPickerList); setHeaderIndices([]); }, 300); From 9085bc8e7121c39ac0946aad92e672d9ff79d655 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Tue, 12 Mar 2024 21:18:50 +0300 Subject: [PATCH 46/53] code changes based on review --- src/components/EmojiPicker/EmojiPickerButton.tsx | 10 +++++----- .../EmojiPicker/EmojiPickerButtonDropdown.tsx | 5 +++-- .../EmojiPickerMenu/BaseEmojiPickerMenu.tsx | 6 +++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerButton.tsx b/src/components/EmojiPicker/EmojiPickerButton.tsx index 3912184ba7b5..20bc0fb807c4 100644 --- a/src/components/EmojiPicker/EmojiPickerButton.tsx +++ b/src/components/EmojiPicker/EmojiPickerButton.tsx @@ -3,16 +3,15 @@ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import Tooltip from '@components/Tooltip/PopoverAnchorTooltip'; -import withNavigationFocus from '@components/withNavigationFocus'; -import type {WithNavigationFocusProps} from '@components/withNavigationFocus'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; +import {useIsFocused} from '@react-navigation/native'; import getButtonState from '@libs/getButtonState'; import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; import CONST from '@src/CONST'; -type EmojiPickerButtonProps = WithNavigationFocusProps & { +type EmojiPickerButtonProps = { /** Flag to disable the emoji picker button */ isDisabled?: boolean; @@ -30,11 +29,12 @@ type EmojiPickerButtonProps = WithNavigationFocusProps & { onEmojiSelected: EmojiPickerAction.OnEmojiSelected; }; -function EmojiPickerButton({isDisabled = false, id = '', emojiPickerID = '', shiftVertical = 0, isFocused, onModalHide, onEmojiSelected}: EmojiPickerButtonProps) { +function EmojiPickerButton({isDisabled = false, id = '', emojiPickerID = '', shiftVertical = 0, onModalHide, onEmojiSelected}: EmojiPickerButtonProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const emojiPopoverAnchor = useRef(null); const {translate} = useLocalize(); + const isFocused = useIsFocused(); useEffect(() => EmojiPickerAction.resetEmojiPopoverAnchor, []); @@ -80,4 +80,4 @@ function EmojiPickerButton({isDisabled = false, id = '', emojiPickerID = '', shi } EmojiPickerButton.displayName = 'EmojiPickerButton'; -export default withNavigationFocus(memo(EmojiPickerButton)); +export default memo(EmojiPickerButton); diff --git a/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx b/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx index 1a853f599fce..4cb6fe4a513a 100644 --- a/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx +++ b/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx @@ -33,7 +33,7 @@ type EmojiPickerButtonDropdownProps = { function EmojiPickerButtonDropdown( {isDisabled = false, onModalHide, onInputChange, value, disabled, style}: EmojiPickerButtonDropdownProps, // eslint-disable-next-line @typescript-eslint/no-unused-vars - forwardedRef: ForwardedRef, + ref: ForwardedRef, ) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -79,7 +79,8 @@ function EmojiPickerButtonDropdown( style={styles.emojiPickerButtonDropdownIcon} numberOfLines={1} > - {value ?? ( + {// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + value || ( >, + ref: ForwardedRef>, ) { const styles = useThemeStyles(); const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); @@ -105,7 +105,7 @@ function BaseEmojiPickerMenu( )} Date: Wed, 13 Mar 2024 16:38:47 +0300 Subject: [PATCH 47/53] code changes based on review --- .../EmojiPicker/EmojiPickerButton.tsx | 2 +- .../EmojiPicker/EmojiPickerButtonDropdown.tsx | 16 +++--- .../EmojiPickerMenu/BaseEmojiPickerMenu.tsx | 22 ++++---- .../EmojiPickerMenu/index.native.tsx | 36 +++++++++---- .../EmojiPicker/EmojiPickerMenu/index.tsx | 53 ++++++++++++------- .../EmojiPicker/EmojiPickerMenu/types.ts | 14 +---- .../EmojiPickerMenu/useEmojiPickerMenu.ts | 6 +-- .../EmojiPicker/EmojiSkinToneList.tsx | 2 +- .../EmojiPicker/getSkinToneEmojiFromIndex.ts | 3 +- src/libs/EmojiUtils.ts | 5 +- 10 files changed, 88 insertions(+), 71 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerButton.tsx b/src/components/EmojiPicker/EmojiPickerButton.tsx index 20bc0fb807c4..6e0944e5a913 100644 --- a/src/components/EmojiPicker/EmojiPickerButton.tsx +++ b/src/components/EmojiPicker/EmojiPickerButton.tsx @@ -1,3 +1,4 @@ +import {useIsFocused} from '@react-navigation/native'; import React, {memo, useEffect, useRef} from 'react'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -6,7 +7,6 @@ import Tooltip from '@components/Tooltip/PopoverAnchorTooltip'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; -import {useIsFocused} from '@react-navigation/native'; import getButtonState from '@libs/getButtonState'; import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; import CONST from '@src/CONST'; diff --git a/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx b/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx index 4cb6fe4a513a..96a08709b800 100644 --- a/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx +++ b/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx @@ -79,13 +79,15 @@ function EmojiPickerButtonDropdown( style={styles.emojiPickerButtonDropdownIcon} numberOfLines={1} > - {// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - value || ( - - )} + { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + value || ( + + ) + } void; @@ -33,7 +31,7 @@ type BaseEmojiPickerMenuProps = { data: EmojiPickerList; /** Function to render each item in the list */ - renderItem: (item: RenderItemProps) => void; + renderItem: ListRenderItem; /** Extra data to be passed to the list for re-rendering */ extraData?: Array | ((skinTone: number) => void)>; @@ -45,21 +43,19 @@ type BaseEmojiPickerMenuProps = { alwaysBounceVertical?: boolean; }; -type GetItemTypeProps = Partial & Partial; - /** * Improves FlashList's recycling when there are different types of items */ -const getItemType = (item: GetItemTypeProps): string | undefined => { +const getItemType = (item: EmojiPickerListItem): string | undefined => { // item is undefined only when list is empty if (!item) { return; } - if (item.name) { + if ('name' in item && item.name) { return CONST.EMOJI_PICKER_ITEM_TYPES.EMOJI; } - if (item.header) { + if ('header' in item && item.header) { return CONST.EMOJI_PICKER_ITEM_TYPES.HEADER; } @@ -70,7 +66,7 @@ const getItemType = (item: GetItemTypeProps): string | undefined => { * Return a unique key for each emoji item * */ -const keyExtractor = (item: Emoji | HeaderEmoji | EmojiSpacer, index: number): string => `emoji_picker_${item.code}_${index}`; +const keyExtractor = (item: EmojiPickerListItem, index: number): string => `emoji_picker_${item.code}_${index}`; /** * Renders the list empty component @@ -84,7 +80,7 @@ function ListEmptyComponent() { function BaseEmojiPickerMenu( {headerEmojis, scrollToHeader, isFiltered, listWrapperStyle = [], data, renderItem, stickyHeaderIndices = [], extraData = [], alwaysBounceVertical = false}: BaseEmojiPickerMenuProps, - ref: ForwardedRef>, + ref: ForwardedRef>, ) { const styles = useThemeStyles(); const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); @@ -109,7 +105,7 @@ function BaseEmojiPickerMenu( keyboardShouldPersistTaps="handled" data={data} drawDistance={CONST.EMOJI_DRAW_AMOUNT} - renderItem={renderItem as ListRenderItem} + renderItem={renderItem} keyExtractor={keyExtractor} numColumns={CONST.EMOJI_NUM_PER_ROW} stickyHeaderIndices={stickyHeaderIndices} diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx index 925296876cfb..2125e16ed168 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx @@ -1,10 +1,13 @@ +import type {ListRenderItem} from '@shopify/flash-list'; import lodashDebounce from 'lodash/debounce'; import React, {useCallback} from 'react'; +import type {ForwardedRef} from 'react'; import {View} from 'react-native'; import {runOnUI, scrollTo} from 'react-native-reanimated'; import EmojiPickerMenuItem from '@components/EmojiPicker/EmojiPickerMenuItem'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; +import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import useLocalize from '@hooks/useLocalize'; import useSingleExecution from '@hooks/useSingleExecution'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -14,10 +17,11 @@ import * as EmojiUtils from '@libs/EmojiUtils'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import BaseEmojiPickerMenu from './BaseEmojiPickerMenu'; -import type {EmojiPickerMenuProps, RenderItemProps} from './types'; +import type EmojiPickerMenuProps from './types'; import useEmojiPickerMenu from './useEmojiPickerMenu'; -function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps) { +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, ref: ForwardedRef) { const styles = useThemeStyles(); const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); @@ -55,7 +59,7 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps) { return; } - setFilteredEmojis(newFilteredEmojiList as EmojiUtils.EmojiPickerList); + setFilteredEmojis(newFilteredEmojiList ?? []); setHeaderIndices([]); }, 300); @@ -73,14 +77,21 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps) { * Items with the code "SPACER" return nothing and are used to fill rows up to 8 * so that the sticky headers function properly. */ - const renderItem = useCallback( - ({item, target}: RenderItemProps) => { - const {code, types} = item; - if (item.spacer) { + const renderItem: ListRenderItem = useCallback( + ({item, target}) => { + let code: string; + let types: readonly string[] | undefined; + if ('types' in item) { + ({code, types} = item); + } else { + ({code} = item); + } + + if ('spacer' in item && item.spacer) { return null; } - if (item.header) { + if ('header' in item && item.header) { return ( {translate(`emojiPicker.headers.${code}` as TranslationPaths)} @@ -88,12 +99,17 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps) { ); } - const emojiCode = types?.[preferredSkinTone as number] ? types[preferredSkinTone as number] : code; + const emojiCode = typeof preferredSkinTone === 'number' && types?.[preferredSkinTone] ? types?.[preferredSkinTone] : code; const shouldEmojiBeHighlighted = !!activeEmoji && EmojiUtils.getRemovedSkinToneEmoji(emojiCode) === EmojiUtils.getRemovedSkinToneEmoji(activeEmoji); return ( onEmojiSelected(emoji, item))} + onPress={singleExecution((emoji) => { + if (!('name' in item)) { + return; + } + onEmojiSelected(emoji, item); + })} emoji={emojiCode} isHighlighted={shouldEmojiBeHighlighted} /> diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx index 946ef02e4513..f25e58ce9fa0 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx @@ -1,9 +1,9 @@ +import type {ListRenderItem} from '@shopify/flash-list'; import throttle from 'lodash/throttle'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import type {ForwardedRef} from 'react'; import {View} from 'react-native'; import {scrollTo} from 'react-native-reanimated'; -import type {Emoji} from '@assets/emojis/types'; import EmojiPickerMenuItem from '@components/EmojiPicker/EmojiPickerMenuItem'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; @@ -22,12 +22,12 @@ import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import BaseEmojiPickerMenu from './BaseEmojiPickerMenu'; -import type {EmojiPickerMenuProps, RenderItemProps} from './types'; +import type EmojiPickerMenuProps from './types'; import useEmojiPickerMenu from './useEmojiPickerMenu'; const throttleTime = Browser.isMobile() ? 200 : 50; -function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, forwardedRef: ForwardedRef) { +function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, ref: ForwardedRef) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {isSmallScreenWidth, windowWidth} = useWindowDimensions(); @@ -122,7 +122,7 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, f return; } // Remove sticky header indices. There are no headers while searching and we don't want to make emojis sticky - setFilteredEmojis(newFilteredEmojiList as EmojiUtils.EmojiPickerList); + setFilteredEmojis(newFilteredEmojiList ?? []); setHeaderIndices([]); setHighlightFirstEmoji(true); setIsUsingKeyboardMovement(false); @@ -145,12 +145,14 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, f indexToSelect = 0; } - const item = filteredEmojis[indexToSelect] as Emoji; + const item = filteredEmojis[indexToSelect]; if (!item) { return; } - const emoji = item?.types?.[preferredSkinTone as number] ?? item.code; - onEmojiSelected(emoji, item); + if ('types' in item) { + const emoji = typeof preferredSkinTone === 'number' && item?.types?.[preferredSkinTone] ? item?.types?.[preferredSkinTone] : item.code; + onEmojiSelected(emoji, item); + } // On web, avoid this Enter default input action; otherwise, it will add a new line in the subsequently focused composer. keyBoardEvent.preventDefault(); // On mWeb, avoid propagating this Enter keystroke to Pressable child component; otherwise, it will trigger the onEmojiSelected callback again. @@ -206,8 +208,8 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, f // get a ref to the inner textInput element e.g. if we do // this.textInput = el} /> this will not // return a ref to the component, but rather the HTML element by default - if (shouldFocusInputOnScreenFocus && forwardedRef && typeof forwardedRef === 'function') { - forwardedRef(searchInputRef.current); + if (shouldFocusInputOnScreenFocus && ref && typeof ref === 'function') { + ref(searchInputRef.current); } setupEventHandlers(); @@ -215,7 +217,7 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, f return () => { cleanupEventHandlers(); }; - }, [forwardedRef, shouldFocusInputOnScreenFocus, cleanupEventHandlers, setupEventHandlers]); + }, [ref, shouldFocusInputOnScreenFocus, cleanupEventHandlers, setupEventHandlers]); const scrollToHeader = useCallback( (headerIndex: number) => { @@ -235,14 +237,21 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, f * so that the sticky headers function properly. * */ - const renderItem = useCallback( - ({item, index, target}: RenderItemProps) => { - const {code, types} = item; - if (item.spacer) { + const renderItem: ListRenderItem = useCallback( + ({item, index, target}) => { + let code: string; + let types: readonly string[] | undefined; + if ('types' in item) { + ({code, types} = item); + } else { + ({code} = item); + } + + if ('spacer' in item && item.spacer) { return null; } - if (item.header) { + if ('header' in item && item.header) { return ( {translate(`emojiPicker.headers.${code}` as TranslationPaths)} @@ -250,16 +259,21 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, f ); } - const emojiCode = types?.[preferredSkinTone as number] ? types[preferredSkinTone as number] : code; + const emojiCode = typeof preferredSkinTone === 'number' && types?.[preferredSkinTone] ? types[preferredSkinTone] : code; const isEmojiFocused = index === focusedIndex && isUsingKeyboardMovement; const shouldEmojiBeHighlighted = - (index === focusedIndex && highlightEmoji) || (Boolean(activeEmoji) && EmojiUtils.getRemovedSkinToneEmoji(emojiCode) === EmojiUtils.getRemovedSkinToneEmoji(activeEmoji)); + (index === focusedIndex && highlightEmoji) || (!!activeEmoji && EmojiUtils.getRemovedSkinToneEmoji(emojiCode) === EmojiUtils.getRemovedSkinToneEmoji(activeEmoji)); const shouldFirstEmojiBeHighlighted = index === 0 && highlightFirstEmoji; return ( onEmojiSelected(emoji, item))} + onPress={singleExecution((emoji) => { + if (!('name' in item)) { + return; + } + onEmojiSelected(emoji, item); + })} onHoverIn={() => { setHighlightEmoji(false); setHighlightFirstEmoji(false); @@ -269,8 +283,7 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, f setIsUsingKeyboardMovement(false); }} emoji={emojiCode} - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - onFocus={() => setFocusedIndex(index!)} + onFocus={() => setFocusedIndex(index)} isFocused={isEmojiFocused} isHighlighted={shouldFirstEmojiBeHighlighted || shouldEmojiBeHighlighted} /> diff --git a/src/components/EmojiPicker/EmojiPickerMenu/types.ts b/src/components/EmojiPicker/EmojiPickerMenu/types.ts index 2babe0dcf2b0..b821cc89aeea 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/types.ts +++ b/src/components/EmojiPicker/EmojiPickerMenu/types.ts @@ -1,5 +1,4 @@ import type {Emoji} from '@assets/emojis/types'; -import type IconAsset from '@src/types/utils/IconAsset'; type EmojiPickerMenuProps = { /** Function to add the selected emoji to the main compose text input */ @@ -8,15 +7,4 @@ type EmojiPickerMenuProps = { activeEmoji?: string; }; -type RenderItemProps = { - item: Emoji & { - spacer?: boolean; - header?: boolean; - }; - target: string; - index?: number; -}; - -type EmojiPropTypes = {code: string; index: number; icon: IconAsset}; - -export type {EmojiPickerMenuProps, RenderItemProps, EmojiPropTypes}; +export default EmojiPickerMenuProps; diff --git a/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts b/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts index c05e6c0bbb2a..56078c622393 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts +++ b/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts @@ -2,7 +2,7 @@ import type {FlashList} from '@shopify/flash-list'; import {useCallback, useEffect, useMemo, useState} from 'react'; import {useAnimatedRef} from 'react-native-reanimated'; import emojis from '@assets/emojis'; -import type {Emoji, HeaderEmoji, PickerEmojis} from '@assets/emojis/types'; +import type {PickerEmojis} from '@assets/emojis/types'; import {useFrequentlyUsedEmojis} from '@components/OnyxProvider'; import useLocalize from '@hooks/useLocalize'; import usePreferredEmojiSkinTone from '@hooks/usePreferredEmojiSkinTone'; @@ -11,7 +11,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import * as EmojiUtils from '@libs/EmojiUtils'; const useEmojiPickerMenu = () => { - const emojiListRef = useAnimatedRef>(); + const emojiListRef = useAnimatedRef>(); const frequentlyUsedEmojis = useFrequentlyUsedEmojis(); // eslint-disable-next-line react-hooks/exhaustive-deps const allEmojis = useMemo(() => EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis), [frequentlyUsedEmojis]); @@ -48,7 +48,7 @@ const useEmojiPickerMenu = () => { const normalizedSearchTerm = searchTerm.toLowerCase().trim().replaceAll(':', ''); const emojisSuggestions = EmojiUtils.suggestEmojis(`:${normalizedSearchTerm}`, preferredLocale, allEmojis.length); - return [normalizedSearchTerm, emojisSuggestions]; + return [normalizedSearchTerm, emojisSuggestions] as const; }, [allEmojis, preferredLocale], ); diff --git a/src/components/EmojiPicker/EmojiSkinToneList.tsx b/src/components/EmojiPicker/EmojiSkinToneList.tsx index 43ffb1de796b..fb798f1c02c4 100644 --- a/src/components/EmojiPicker/EmojiSkinToneList.tsx +++ b/src/components/EmojiPicker/EmojiSkinToneList.tsx @@ -41,7 +41,7 @@ function EmojiSkinToneList() { // eslint-disable-next-line react-hooks/exhaustive-deps -- only run when preferredSkinTone updates }, [preferredSkinTone]); - const currentSkinTone = getSkinToneEmojiFromIndex(preferredSkinTone as number); + const currentSkinTone = getSkinToneEmojiFromIndex(preferredSkinTone); return ( {!isSkinToneListVisible && ( diff --git a/src/components/EmojiPicker/getSkinToneEmojiFromIndex.ts b/src/components/EmojiPicker/getSkinToneEmojiFromIndex.ts index 4e9beb615303..6bae46edca7b 100644 --- a/src/components/EmojiPicker/getSkinToneEmojiFromIndex.ts +++ b/src/components/EmojiPicker/getSkinToneEmojiFromIndex.ts @@ -1,9 +1,10 @@ +import type {OnyxEntry} from 'react-native-onyx'; import * as Emojis from '@assets/emojis'; /** * Fetch the emoji code of selected skinTone */ -function getSkinToneEmojiFromIndex(skinToneIndex: number) { +function getSkinToneEmojiFromIndex(skinToneIndex: OnyxEntry) { return Emojis.skinTones.find((emoji) => emoji.skinTone === skinToneIndex) ?? Emojis.skinTones[0]; } diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index 844be6eb66ce..a8d798fe9e55 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -13,7 +13,8 @@ import type IconAsset from '@src/types/utils/IconAsset'; type HeaderIndice = {code: string; index: number; icon: IconAsset}; type EmojiSpacer = {code: string; spacer: boolean}; -type EmojiPickerList = Array; +type EmojiPickerListItem = EmojiSpacer | Emoji | HeaderEmoji; +type EmojiPickerList = EmojiPickerListItem[]; type ReplacedEmoji = {text: string; emojis: Emoji[]; cursorPosition?: number}; let frequentlyUsedEmojis: FrequentlyUsedEmoji[] = []; @@ -568,7 +569,7 @@ function getSpacersIndexes(allEmojis: EmojiPickerList): number[] { return spacersIndexes; } -export type {HeaderIndice, EmojiPickerList, EmojiSpacer}; +export type {HeaderIndice, EmojiPickerList, EmojiSpacer, EmojiPickerListItem}; export { findEmojiByName, From 393243303457d45753aae1f21c112c1f1a1a657f Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Tue, 19 Mar 2024 23:00:59 +0300 Subject: [PATCH 48/53] code changes based on review --- src/components/EmojiPicker/EmojiPicker.tsx | 8 ++++---- .../EmojiPicker/EmojiPickerMenu/index.native.tsx | 9 ++------- src/components/EmojiPicker/EmojiPickerMenu/index.tsx | 11 +++-------- .../EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts | 3 +-- src/libs/EmojiUtils.ts | 2 +- .../report/ContextMenu/ReportActionContextMenu.ts | 4 ++-- 6 files changed, 13 insertions(+), 24 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPicker.tsx b/src/components/EmojiPicker/EmojiPicker.tsx index 8c48aceff760..c5b970860609 100644 --- a/src/components/EmojiPicker/EmojiPicker.tsx +++ b/src/components/EmojiPicker/EmojiPicker.tsx @@ -53,7 +53,7 @@ function EmojiPicker({viewportOffsetTop}: EmojiPickerProps, ref: ForwardedRef emojiPopoverAnchorRef.current ?? emojiPopoverAnchorRef, []); + const getEmojiPopoverAnchor = useCallback(() => emojiPopoverAnchorRef.current ?? emojiPopoverAnchorRef?.current, []); /** * Show the emoji picker menu. @@ -85,7 +85,7 @@ function EmojiPicker({viewportOffsetTop}: EmojiPickerProps, ref: ForwardedRef { + calculateAnchorPosition(emojiPopoverAnchor?.current, anchorOriginValue).then((value) => { onWillShow?.(); setIsEmojiPickerVisible(true); setEmojiPopoverAnchorPosition({ @@ -158,14 +158,14 @@ function EmojiPicker({viewportOffsetTop}: EmojiPickerProps, ref: ForwardedRef { const emojiPopoverDimensionListener = Dimensions.addEventListener('change', () => { const emojiPopoverAnchor = getEmojiPopoverAnchor(); - if (!emojiPopoverAnchor.current) { + if (!emojiPopoverAnchor?.current) { // In small screen width, the window size change might be due to keyboard open/hide, we should avoid hide EmojiPicker in those cases if (isEmojiPickerVisible && !isSmallScreenWidth) { hideEmojiPicker(); } return; } - calculateAnchorPosition(emojiPopoverAnchor.current as View, emojiPopoverAnchorOrigin).then((value) => { + calculateAnchorPosition(emojiPopoverAnchor?.current, emojiPopoverAnchorOrigin).then((value) => { setEmojiPopoverAnchorPosition({ horizontal: value.horizontal, vertical: value.vertical, diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx index 2125e16ed168..5b7dda1c7ab1 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx @@ -79,13 +79,8 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, r */ const renderItem: ListRenderItem = useCallback( ({item, target}) => { - let code: string; - let types: readonly string[] | undefined; - if ('types' in item) { - ({code, types} = item); - } else { - ({code} = item); - } + const code = item.code; + const types = 'types' in item ? item.types : undefined; if ('spacer' in item && item.spacer) { return null; diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx index f25e58ce9fa0..b8c31a61ecbf 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx @@ -149,7 +149,7 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, r if (!item) { return; } - if ('types' in item) { + if ('types' in item || 'name' in item) { const emoji = typeof preferredSkinTone === 'number' && item?.types?.[preferredSkinTone] ? item?.types?.[preferredSkinTone] : item.code; onEmojiSelected(emoji, item); } @@ -239,13 +239,8 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, r */ const renderItem: ListRenderItem = useCallback( ({item, index, target}) => { - let code: string; - let types: readonly string[] | undefined; - if ('types' in item) { - ({code, types} = item); - } else { - ({code} = item); - } + const code = item.code; + const types = 'types' in item ? item.types : undefined; if ('spacer' in item && item.spacer) { return null; diff --git a/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts b/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts index 56078c622393..7cbfc4f1e2bc 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts +++ b/src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts @@ -2,7 +2,6 @@ import type {FlashList} from '@shopify/flash-list'; import {useCallback, useEffect, useMemo, useState} from 'react'; import {useAnimatedRef} from 'react-native-reanimated'; import emojis from '@assets/emojis'; -import type {PickerEmojis} from '@assets/emojis/types'; import {useFrequentlyUsedEmojis} from '@components/OnyxProvider'; import useLocalize from '@hooks/useLocalize'; import usePreferredEmojiSkinTone from '@hooks/usePreferredEmojiSkinTone'; @@ -15,7 +14,7 @@ const useEmojiPickerMenu = () => { const frequentlyUsedEmojis = useFrequentlyUsedEmojis(); // eslint-disable-next-line react-hooks/exhaustive-deps const allEmojis = useMemo(() => EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis), [frequentlyUsedEmojis]); - const headerEmojis = useMemo(() => EmojiUtils.getHeaderEmojis(allEmojis as PickerEmojis), [allEmojis]); + const headerEmojis = useMemo(() => EmojiUtils.getHeaderEmojis(allEmojis), [allEmojis]); const headerRowIndices = useMemo(() => headerEmojis.map((headerEmoji) => headerEmoji.index), [headerEmojis]); const spacersIndexes = useMemo(() => EmojiUtils.getSpacersIndexes(allEmojis), [allEmojis]); const [filteredEmojis, setFilteredEmojis] = useState(allEmojis); diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index a8d798fe9e55..3b189dbb084f 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -156,7 +156,7 @@ function containsOnlyEmojis(message: string): boolean { /** * Get the header emojis with their code, icon and index */ -function getHeaderEmojis(emojis: PickerEmojis): HeaderIndice[] { +function getHeaderEmojis(emojis: EmojiPickerList): HeaderIndice[] { const headerIndices: HeaderIndice[] = []; emojis.forEach((emoji, index) => { if (!('header' in emoji)) { diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts b/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts index 04b4d5cc1850..f984e88b85b2 100644 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts @@ -1,7 +1,7 @@ import React from 'react'; import type {RefObject} from 'react'; // eslint-disable-next-line no-restricted-imports -import type {GestureResponderEvent, Text as RNText, View} from 'react-native'; +import type {GestureResponderEvent, Text as RNText, TextInput, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; @@ -16,7 +16,7 @@ type OnCancel = () => void; type ContextMenuType = ValueOf; -type ContextMenuAnchor = View | RNText | HTMLDivElement | null | undefined; +type ContextMenuAnchor = View | RNText | TextInput | HTMLDivElement | null | undefined; type ShowContextMenu = ( type: ContextMenuType, From da9e52f2d7cd037233734998e3704850d0dd7432 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Wed, 20 Mar 2024 22:05:09 +0300 Subject: [PATCH 49/53] remove type assertion --- src/components/EmojiPicker/EmojiPicker.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/EmojiPicker/EmojiPicker.tsx b/src/components/EmojiPicker/EmojiPicker.tsx index c5b970860609..d5f417bc2e05 100644 --- a/src/components/EmojiPicker/EmojiPicker.tsx +++ b/src/components/EmojiPicker/EmojiPicker.tsx @@ -81,7 +81,7 @@ function EmojiPicker({viewportOffsetTop}: EmojiPickerProps, ref: ForwardedRef Date: Sun, 24 Mar 2024 16:41:13 +0300 Subject: [PATCH 50/53] fix ts errors --- src/components/EmojiPicker/EmojiPicker.tsx | 2 +- src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx | 7 ++++++- .../EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx | 4 ++-- src/components/Form/types.ts | 4 +++- src/pages/home/report/ReportActionItem.tsx | 2 +- src/pages/settings/Profile/CustomStatus/StatusPage.tsx | 4 +++- 6 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPicker.tsx b/src/components/EmojiPicker/EmojiPicker.tsx index d5f417bc2e05..8c7b4df3264f 100644 --- a/src/components/EmojiPicker/EmojiPicker.tsx +++ b/src/components/EmojiPicker/EmojiPicker.tsx @@ -9,8 +9,8 @@ import withViewportOffsetTop from '@components/withViewportOffsetTop'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import * as Browser from '@libs/Browser'; import type {AnchorOrigin, EmojiPickerRef, EmojiPopoverAnchor, OnEmojiSelected, OnModalHideValue, OnWillShowPicker} from '@libs/actions/EmojiPickerAction'; +import * as Browser from '@libs/Browser'; import calculateAnchorPosition from '@libs/calculateAnchorPosition'; import CONST from '@src/CONST'; import EmojiPickerMenu from './EmojiPickerMenu'; diff --git a/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx b/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx index 96a08709b800..c736d09ceee1 100644 --- a/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx +++ b/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx @@ -19,6 +19,10 @@ type EmojiPickerButtonDropdownProps = { /** Flag to disable the emoji picker button */ isDisabled?: boolean; + accessibilityLabel?: string; + + role?: string; + onModalHide: EmojiPickerAction.OnModalHideValue; onInputChange: (emoji: string) => void; @@ -31,7 +35,8 @@ type EmojiPickerButtonDropdownProps = { }; function EmojiPickerButtonDropdown( - {isDisabled = false, onModalHide, onInputChange, value, disabled, style}: EmojiPickerButtonDropdownProps, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + {isDisabled = false, onModalHide, onInputChange, value, disabled, style, ...otherProps}: EmojiPickerButtonDropdownProps, // eslint-disable-next-line @typescript-eslint/no-unused-vars ref: ForwardedRef, ) { diff --git a/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx b/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx index 71729962002c..8e1112e7844c 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.tsx @@ -4,6 +4,7 @@ import React, {useMemo} from 'react'; import type {ForwardedRef} from 'react'; import {StyleSheet, View} from 'react-native'; import type {StyleProp, ViewStyle} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import CategoryShortcutBar from '@components/EmojiPicker/CategoryShortcutBar'; import EmojiSkinToneList from '@components/EmojiPicker/EmojiSkinToneList'; import Text from '@components/Text'; @@ -12,7 +13,6 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import type {EmojiPickerList, EmojiPickerListItem, HeaderIndice} from '@libs/EmojiUtils'; import CONST from '@src/CONST'; -import type {OnyxValue} from '@src/ONYXKEYS'; type BaseEmojiPickerMenuProps = { /** Indicates if the emoji list is filtered or not */ @@ -34,7 +34,7 @@ type BaseEmojiPickerMenuProps = { renderItem: ListRenderItem; /** Extra data to be passed to the list for re-rendering */ - extraData?: Array | ((skinTone: number) => void)>; + extraData?: Array | ((skinTone: number) => void)>; /** Array of indices for the sticky headers */ stickyHeaderIndices?: number[]; diff --git a/src/components/Form/types.ts b/src/components/Form/types.ts index 93da67331c9f..0957e9d67e0e 100644 --- a/src/components/Form/types.ts +++ b/src/components/Form/types.ts @@ -9,6 +9,7 @@ import type AmountTextInput from '@components/AmountTextInput'; import type CheckboxWithLabel from '@components/CheckboxWithLabel'; import type CountrySelector from '@components/CountrySelector'; import type DatePicker from '@components/DatePicker'; +import type EmojiPickerButtonDropdown from '@components/EmojiPicker/EmojiPickerButtonDropdown'; import type Picker from '@components/Picker'; import type RadioButtons from '@components/RadioButtons'; import type RoomNameInput from '@components/RoomNameInput'; @@ -47,7 +48,8 @@ type ValidInputs = | typeof RadioButtons | typeof AmountPicker | typeof TextPicker - | typeof AddPlaidBankAccount; + | typeof AddPlaidBankAccount + | typeof EmojiPickerButtonDropdown; type ValueTypeKey = 'string' | 'boolean' | 'date' | 'country'; type ValueTypeMap = { diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 02d7a14f4b0e..e3e32f398fb6 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -187,7 +187,7 @@ function ReportActionItem({ const reactionListRef = useContext(ReactionListContext); const {updateHiddenAttachments} = useContext(ReportAttachmentsContext); const textInputRef = useRef(); - const popoverAnchorRef = useRef(null); + const popoverAnchorRef = useRef>(null); const downloadedPreviews = useRef([]); const prevDraftMessage = usePrevious(draftMessage); const originalReportID = ReportUtils.getOriginalReportID(report.reportID, action); diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx index bb7be4a6866c..5dc1c1832d39 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.tsx +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.tsx @@ -162,11 +162,13 @@ function StatusPage({draftStatus, currentUserPersonalDetails}: StatusPageProps) {}} + // eslint-disable-next-line @typescript-eslint/no-unused-vars + onInputChange={(emoji: string): void => {}} /> Date: Mon, 25 Mar 2024 20:11:59 +0300 Subject: [PATCH 51/53] code changes based on review --- src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx | 7 ------- src/components/Form/types.ts | 3 --- 2 files changed, 10 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx b/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx index c736d09ceee1..e0f97d63738d 100644 --- a/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx +++ b/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx @@ -18,19 +18,12 @@ import CONST from '@src/CONST'; type EmojiPickerButtonDropdownProps = { /** Flag to disable the emoji picker button */ isDisabled?: boolean; - accessibilityLabel?: string; - role?: string; - onModalHide: EmojiPickerAction.OnModalHideValue; - onInputChange: (emoji: string) => void; - value?: string; - disabled?: boolean; - style: StyleProp; }; diff --git a/src/components/Form/types.ts b/src/components/Form/types.ts index 0957e9d67e0e..331f1c943b30 100644 --- a/src/components/Form/types.ts +++ b/src/components/Form/types.ts @@ -27,9 +27,6 @@ import type {BaseForm} from '@src/types/form/Form'; /** * This type specifies all the inputs that can be used with `InputWrapper` component. Make sure to update it * when adding new inputs or removing old ones. - * - * TODO: Add remaining inputs here once these components are migrated to Typescript: - * EmojiPickerButtonDropdown */ type ValidInputs = | typeof TextInput From 27a2dd567e837a4ba07e3ed07a7a5eb5d8129d89 Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Fri, 29 Mar 2024 20:21:09 +0300 Subject: [PATCH 52/53] replaced array index usage by headerEmoji.index --- src/components/EmojiPicker/CategoryShortcutBar.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/EmojiPicker/CategoryShortcutBar.tsx b/src/components/EmojiPicker/CategoryShortcutBar.tsx index 2fbe4efca6b7..25c1ff85f6a3 100644 --- a/src/components/EmojiPicker/CategoryShortcutBar.tsx +++ b/src/components/EmojiPicker/CategoryShortcutBar.tsx @@ -16,12 +16,11 @@ function CategoryShortcutBar({onPress, headerEmojis}: CategoryShortcutBarProps) const styles = useThemeStyles(); return ( - {headerEmojis.map((headerEmoji, i) => ( + {headerEmojis.map((headerEmoji) => ( onPress(headerEmoji.index)} - // eslint-disable-next-line react/no-array-index-key - key={`categoryShortcut${i}`} + key={`categoryShortcut${headerEmoji.index}`} code={headerEmoji.code} /> ))} From dd6327f076cdcecfa5c1c7d4bea5b78d0ec0fd9c Mon Sep 17 00:00:00 2001 From: Hezekiel Tamire Date: Wed, 3 Apr 2024 13:27:08 +0300 Subject: [PATCH 53/53] fix cursor issue --- .../EmojiPickerMenuItem/index.native.tsx | 6 +++++- .../EmojiPicker/EmojiPickerMenuItem/index.tsx | 21 ++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx index 09a053719b19..1336654cdf2f 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx @@ -55,4 +55,8 @@ function EmojiPickerMenuItem({ // Significantly speeds up re-renders of the EmojiPickerMenu's FlatList // by only re-rendering at most two EmojiPickerMenuItems that are highlighted/un-highlighted per user action. -export default React.memo(EmojiPickerMenuItem); +export default React.memo( + EmojiPickerMenuItem, + (prevProps, nextProps) => + prevProps.isHighlighted === nextProps.isHighlighted && prevProps.emoji === nextProps.emoji && prevProps.isUsingKeyboardMovement === nextProps.isUsingKeyboardMovement, +); diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.tsx b/src/components/EmojiPicker/EmojiPickerMenuItem/index.tsx index 2e3578d23417..8aaf4a14e560 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.tsx @@ -1,4 +1,6 @@ import React, {useEffect, useRef, useState} from 'react'; +// eslint-disable-next-line no-restricted-imports +import type {Text as RNText, View} from 'react-native'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import Text from '@components/Text'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -19,13 +21,17 @@ function EmojiPickerMenuItem({ isHighlighted = false, }: EmojiPickerMenuItemProps) { const [isHovered, setIsHovered] = useState(false); - const ref = useRef(null); + const ref = useRef(null); const StyleUtils = useStyleUtils(); const themeStyles = useThemeStyles(); const focusAndScroll = () => { - ref?.current?.focus({preventScroll: true}); - ref?.current?.scrollIntoView({block: 'nearest'}); + if (ref.current && 'focus' in ref.current) { + ref.current.focus({preventScroll: true}); + } + if (ref.current && 'scrollIntoView' in ref.current) { + ref.current.scrollIntoView({block: 'nearest'}); + } }; useEffect(() => { @@ -58,7 +64,9 @@ function EmojiPickerMenuItem({ }} onFocus={onFocus} onBlur={onBlur} - ref={ref} + ref={(el) => { + ref.current = el ?? null; + }} style={({pressed}) => [ isFocused ? themeStyles.emojiItemKeyboardHighlighted : {}, isHovered || isHighlighted ? themeStyles.emojiItemHighlighted : {}, @@ -75,4 +83,7 @@ function EmojiPickerMenuItem({ // Significantly speeds up re-renders of the EmojiPickerMenu's FlatList // by only re-rendering at most two EmojiPickerMenuItems that are highlighted/un-highlighted per user action. -export default React.memo(EmojiPickerMenuItem); +export default React.memo( + EmojiPickerMenuItem, + (prevProps, nextProps) => prevProps.isFocused === nextProps.isFocused && prevProps.isHighlighted === nextProps.isHighlighted && prevProps.emoji === nextProps.emoji, +);