forked from Expensify/App
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request Expensify#31479 from TMisiukiewicz/feat/emojipicke…
…r-flashlist-migration Migrate EmojiPickerMenu to FlashList
- Loading branch information
Showing
12 changed files
with
443 additions
and
355 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
161 changes: 161 additions & 0 deletions
161
src/components/EmojiPicker/EmojiPickerMenu/BaseEmojiPickerMenu.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import {FlashList} from '@shopify/flash-list'; | ||
import PropTypes from 'prop-types'; | ||
import React, {useMemo} from 'react'; | ||
import {StyleSheet, Text, View} from 'react-native'; | ||
import CategoryShortcutBar from '@components/EmojiPicker/CategoryShortcutBar'; | ||
import EmojiSkinToneList from '@components/EmojiPicker/EmojiSkinToneList'; | ||
import refPropTypes from '@components/refPropTypes'; | ||
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 <Text style={[styles.textLabel, styles.colorMuted]}>{translate('common.noResultsFound')}</Text>; | ||
} | ||
|
||
function BaseEmojiPickerMenu({headerEmojis, scrollToHeader, isFiltered, listWrapperStyle, forwardedRef, data, renderItem, stickyHeaderIndices, extraData, alwaysBounceVertical}) { | ||
const styles = useThemeStyles(); | ||
const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); | ||
|
||
const flattenListWrapperStyle = useMemo(() => StyleSheet.flatten(listWrapperStyle), [listWrapperStyle]); | ||
|
||
return ( | ||
<> | ||
{!isFiltered && ( | ||
<CategoryShortcutBar | ||
headerEmojis={headerEmojis} | ||
onPress={scrollToHeader} | ||
/> | ||
)} | ||
<View style={listWrapperStyle}> | ||
<FlashList | ||
ref={forwardedRef} | ||
keyboardShouldPersistTaps="handled" | ||
data={data} | ||
renderItem={renderItem} | ||
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: isSmallScreenWidth ? windowWidth : CONST.EMOJI_PICKER_SIZE.WIDTH}} | ||
contentContainerStyle={styles.ph4} | ||
extraData={extraData} | ||
getItemType={getItemType} | ||
/> | ||
</View> | ||
<EmojiSkinToneList /> | ||
</> | ||
); | ||
} | ||
|
||
BaseEmojiPickerMenu.propTypes = propTypes; | ||
BaseEmojiPickerMenu.defaultProps = defaultProps; | ||
BaseEmojiPickerMenu.displayName = 'BaseEmojiPickerMenu'; | ||
|
||
const BaseEmojiPickerMenuWithRef = React.forwardRef((props, ref) => ( | ||
<BaseEmojiPickerMenu | ||
// eslint-disable-next-line react/jsx-props-no-spreading | ||
{...props} | ||
forwardedRef={ref} | ||
/> | ||
)); | ||
|
||
BaseEmojiPickerMenuWithRef.displayName = 'BaseEmojiPickerMenuWithRef'; | ||
|
||
export default BaseEmojiPickerMenuWithRef; |
8 changes: 8 additions & 0 deletions
8
src/components/EmojiPicker/EmojiPickerMenu/emojiPickerMenuPropTypes.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
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; |
Oops, something went wrong.