diff --git a/src/CONST.js b/src/CONST.js index 3326bfd69bb6e..c3b71c534e9cd 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -715,7 +715,7 @@ const CONST = { EMOJI_PICKER_ITEM_HEIGHT: 32, EMOJI_PICKER_HEADER_HEIGHT: 32, RECIPIENT_LOCAL_TIME_HEIGHT: 25, - EMOJI_SUGGESTER: { + AUTO_COMPLETE_SUGGESTER: { SUGGESTER_PADDING: 6, ITEM_HEIGHT: 36, SMALL_CONTAINER_HEIGHT_FACTOR: 2.5, diff --git a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.js b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.js new file mode 100644 index 0000000000000..f548303e82383 --- /dev/null +++ b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.js @@ -0,0 +1,85 @@ +import React from 'react'; +import {View, Pressable} from 'react-native'; + +// We take FlatList from this package to properly handle the scrolling of AutoCompleteSuggestions in chats since one scroll is nested inside another +import {FlatList} from 'react-native-gesture-handler'; +import styles from '../../styles/styles'; +import * as StyleUtils from '../../styles/StyleUtils'; +import CONST from '../../CONST'; +import {propTypes} from './autoCompleteSuggestionsPropTypes'; + +/** + * @param {Number} numRows + * @param {Boolean} isSuggestionPickerLarge + * @returns {Number} + */ +const measureHeightOfSuggestionRows = (numRows, isSuggestionPickerLarge) => { + if (isSuggestionPickerLarge) { + return numRows * CONST.AUTO_COMPLETE_SUGGESTER.ITEM_HEIGHT; + } + if (numRows > 2) { + // On small screens, we display a scrollable window with a height of 2.5 items, indicating that there are more items available beyond what is currently visible + return CONST.AUTO_COMPLETE_SUGGESTER.SMALL_CONTAINER_HEIGHT_FACTOR * CONST.AUTO_COMPLETE_SUGGESTER.ITEM_HEIGHT; + } + return numRows * CONST.AUTO_COMPLETE_SUGGESTER.ITEM_HEIGHT; +}; + +const BaseAutoCompleteSuggestions = (props) => { + /** + * Render a suggestion menu item component. + * @param {Object} params + * @param {Object} params.item + * @param {Number} params.index + * @returns {JSX.Element} + */ + const renderSuggestionMenuItem = ({item, index}) => ( + StyleUtils.getAutoCompleteSuggestionItemStyle( + props.highlightedSuggestionIndex, + CONST.AUTO_COMPLETE_SUGGESTER.ITEM_HEIGHT, + hovered, + index, + )} + onMouseDown={e => e.preventDefault()} + onPress={() => props.onSelect(index)} + onLongPress={() => {}} + > + {props.renderSuggestionMenuItem(item, index)} + + ); + + const rowHeight = measureHeightOfSuggestionRows( + props.suggestions.length, + props.isSuggestionPickerLarge, + ); + + return ( + + + + ); +}; + +BaseAutoCompleteSuggestions.propTypes = propTypes; +BaseAutoCompleteSuggestions.displayName = 'BaseAutoCompleteSuggestions'; + +export default React.forwardRef((props, ref) => ( + // eslint-disable-next-line react/jsx-props-no-spreading + +)); diff --git a/src/components/AutoCompleteSuggestions/autoCompleteSuggestionsPropTypes.js b/src/components/AutoCompleteSuggestions/autoCompleteSuggestionsPropTypes.js new file mode 100644 index 0000000000000..eed9368e534d8 --- /dev/null +++ b/src/components/AutoCompleteSuggestions/autoCompleteSuggestionsPropTypes.js @@ -0,0 +1,31 @@ +import PropTypes from 'prop-types'; + +const propTypes = { + /** Array of suggestions */ + // eslint-disable-next-line react/forbid-prop-types + suggestions: PropTypes.arrayOf(PropTypes.object).isRequired, + + /** Function used to render each suggestion, returned JSX will be enclosed inside a Pressable component */ + renderSuggestionMenuItem: PropTypes.func.isRequired, + + /** Create unique keys for each suggestion item */ + keyExtractor: PropTypes.func.isRequired, + + /** The index of the highlighted suggestion */ + highlightedSuggestionIndex: PropTypes.number.isRequired, + + /** Fired when the user selects a suggestion */ + onSelect: PropTypes.func.isRequired, + + /** Show that we can use large auto-complete suggestion picker. + * Depending on available space and whether the input is expanded, we can have a small or large mention suggester. + * When this value is false, the suggester will have a height of 2.5 items. When this value is true, the height can be up to 5 items. */ + isSuggestionPickerLarge: PropTypes.bool.isRequired, + + /** Show that we should include ReportRecipientLocalTime view height */ + shouldIncludeReportRecipientLocalTimeHeight: PropTypes.bool.isRequired, +}; + +const defaultProps = {}; + +export {propTypes, defaultProps}; diff --git a/src/components/EmojiSuggestions/index.js b/src/components/AutoCompleteSuggestions/index.js similarity index 53% rename from src/components/EmojiSuggestions/index.js rename to src/components/AutoCompleteSuggestions/index.js index 6a3b52f27dd95..a17c869f1a87a 100644 --- a/src/components/EmojiSuggestions/index.js +++ b/src/components/AutoCompleteSuggestions/index.js @@ -1,16 +1,16 @@ import React from 'react'; -import BaseEmojiSuggestions from './BaseEmojiSuggestions'; +import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions'; import * as DeviceCapabilities from '../../libs/DeviceCapabilities'; -import {propTypes, defaultProps} from './emojiSuggestionsPropTypes'; +import {propTypes} from './autoCompleteSuggestionsPropTypes'; /** - * On the mobile-web platform, when long-pressing on emoji suggestions, - * we need to prevent focus shifting to avoid blurring the main input which makes the emoji picker close and adds the emoji to the composer. + * On the mobile-web platform, when long-pressing on auto-complete suggestions, + * we need to prevent focus shifting to avoid blurring the main input (which makes the suggestions picker close and fires the onSelect callback). * The desired pattern for all platforms is to do nothing on long-press. - * On the native platform, tapping on emoji suggestions will not blur the main input. + * On the native platform, tapping on auto-complete suggestions will not blur the main input. */ -const EmojiSuggestions = (props) => { +const AutoCompleteSuggestions = (props) => { const containerRef = React.useRef(null); React.useEffect(() => { const container = containerRef.current; @@ -28,12 +28,11 @@ const EmojiSuggestions = (props) => { return ( // eslint-disable-next-line react/jsx-props-no-spreading - + ); }; -EmojiSuggestions.propTypes = propTypes; -EmojiSuggestions.defaultProps = defaultProps; -EmojiSuggestions.displayName = 'EmojiSuggestions'; +AutoCompleteSuggestions.propTypes = propTypes; +AutoCompleteSuggestions.displayName = 'AutoCompleteSuggestions'; -export default EmojiSuggestions; +export default AutoCompleteSuggestions; diff --git a/src/components/AutoCompleteSuggestions/index.native.js b/src/components/AutoCompleteSuggestions/index.native.js new file mode 100644 index 0000000000000..9332478a3cd98 --- /dev/null +++ b/src/components/AutoCompleteSuggestions/index.native.js @@ -0,0 +1,13 @@ +import React from 'react'; +import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions'; +import {propTypes} from './autoCompleteSuggestionsPropTypes'; + +const AutoCompleteSuggestions = props => ( + // eslint-disable-next-line react/jsx-props-no-spreading + +); + +AutoCompleteSuggestions.propTypes = propTypes; +AutoCompleteSuggestions.displayName = 'AutoCompleteSuggestions'; + +export default AutoCompleteSuggestions; diff --git a/src/components/EmojiSuggestions.js b/src/components/EmojiSuggestions.js new file mode 100644 index 0000000000000..9f5269efe846c --- /dev/null +++ b/src/components/EmojiSuggestions.js @@ -0,0 +1,101 @@ +import React from 'react'; +import {View} from 'react-native'; +import PropTypes from 'prop-types'; +import _ from 'underscore'; +import styles from '../styles/styles'; +import * as StyleUtils from '../styles/StyleUtils'; +import * as EmojiUtils from '../libs/EmojiUtils'; +import Text from './Text'; +import getStyledTextArray from '../libs/GetStyledTextArray'; +import AutoCompleteSuggestions from +'./AutoCompleteSuggestions'; + +const propTypes = { + /** The index of the highlighted emoji */ + highlightedEmojiIndex: PropTypes.number, + + /** Array of suggested emoji */ + emojis: PropTypes.arrayOf(PropTypes.shape({ + /** The emoji code */ + code: PropTypes.string.isRequired, + + /** The name of the emoji */ + name: PropTypes.string.isRequired, + + /** Array of different skin tone variants. + * If provided, it will be indexed with props.preferredSkinToneIndex */ + types: PropTypes.arrayOf(PropTypes.string.isRequired), + })).isRequired, + + /** Fired when the user selects an emoji */ + onSelect: PropTypes.func.isRequired, + + /** Emoji prefix that follows the colon */ + prefix: PropTypes.string.isRequired, + + /** Show that we can use large emoji picker. Depending on available space + * and whether the input is expanded, we can have a small or large emoji + * suggester. When this value is false, the suggester will have a height of + * 2.5 items. When this value is true, the height can be up to 5 items. */ + isEmojiPickerLarge: PropTypes.bool.isRequired, + + /** Show that we should include ReportRecipientLocalTime view height */ + shouldIncludeReportRecipientLocalTimeHeight: PropTypes.bool.isRequired, + + /** Stores user's preferred skin tone */ + preferredSkinToneIndex: PropTypes.number.isRequired, +}; + +const defaultProps = {highlightedEmojiIndex: 0}; + +/** + * Create unique keys for each emoji item + * @param {Object} item + * @param {Number} index + * @returns {String} + */ +const keyExtractor = (item, index) => `${item.name}+${index}}`; + +const EmojiSuggestions = (props) => { + /** + * Render an emoji suggestion menu item component. + * @param {Object} item + * @returns {JSX.Element} + */ + const renderSuggestionMenuItem = (item) => { + const styledTextArray = getStyledTextArray(item.name, props.prefix); + + return ( + + {EmojiUtils.getEmojiCodeWithSkinColor(item, props.preferredSkinToneIndex)} + + : + {_.map(styledTextArray, ({text, isColored}, i) => ( + + {text} + + ))} + : + + + ); + }; + + return ( + + ); +}; + +EmojiSuggestions.propTypes = propTypes; +EmojiSuggestions.defaultProps = defaultProps; +EmojiSuggestions.displayName = 'EmojiSuggestions'; + +export default EmojiSuggestions; diff --git a/src/components/EmojiSuggestions/BaseEmojiSuggestions.js b/src/components/EmojiSuggestions/BaseEmojiSuggestions.js deleted file mode 100644 index 6491b2c0d0850..0000000000000 --- a/src/components/EmojiSuggestions/BaseEmojiSuggestions.js +++ /dev/null @@ -1,112 +0,0 @@ -import React from 'react'; -import {View, Pressable} from 'react-native'; -import _ from 'underscore'; - -// We take FlatList from this package to properly handle the scrolling of EmojiSuggestions in chats since one scroll is nested inside another -import {FlatList} from 'react-native-gesture-handler'; -import styles from '../../styles/styles'; -import * as StyleUtils from '../../styles/StyleUtils'; -import * as EmojiUtils from '../../libs/EmojiUtils'; -import Text from '../Text'; -import CONST from '../../CONST'; -import getStyledTextArray from '../../libs/GetStyledTextArray'; -import {propTypes, defaultProps} from './emojiSuggestionsPropTypes'; - -/** - * @param {Number} numRows - * @param {Boolean} isEmojiPickerLarge - * @returns {Number} - */ -const measureHeightOfEmojiRows = (numRows, isEmojiPickerLarge) => { - if (isEmojiPickerLarge) { - return numRows * CONST.EMOJI_SUGGESTER.ITEM_HEIGHT; - } - if (numRows > 2) { - // on small screens, we display a scrollable window with a height of 2.5 items, indicating that there are more items available beyond what is currently visible - return CONST.EMOJI_SUGGESTER.SMALL_CONTAINER_HEIGHT_FACTOR * CONST.EMOJI_SUGGESTER.ITEM_HEIGHT; - } - return numRows * CONST.EMOJI_SUGGESTER.ITEM_HEIGHT; -}; - -/** - * Create unique keys for each emoji item - * @param {Object} item - * @param {Number} index - * @returns {String} - */ -const keyExtractor = (item, index) => `${item.name}+${index}}`; - -const BaseEmojiSuggestions = (props) => { - /** - * Render a suggestion menu item component. - * @param {Object} params.item - * @param {Number} params.index - * @returns {JSX.Element} - */ - const renderSuggestionMenuItem = ({item, index}) => { - const styledTextArray = getStyledTextArray(item.name, props.prefix); - - return ( - StyleUtils.getEmojiSuggestionItemStyle( - props.highlightedEmojiIndex, - CONST.EMOJI_SUGGESTER.ITEM_HEIGHT, - hovered, - index, - )} - onMouseDown={e => e.preventDefault()} - onPress={() => props.onSelect(index)} - onLongPress={() => {}} - > - - {EmojiUtils.getEmojiCodeWithSkinColor(item, props.preferredSkinToneIndex)} - - : - {_.map(styledTextArray, ({text, isColored}, i) => ( - - {text} - - ))} - : - - - - ); - }; - - const rowHeight = measureHeightOfEmojiRows( - props.emojis.length, - props.isEmojiPickerLarge, - ); - - return ( - - - - ); -}; - -BaseEmojiSuggestions.propTypes = propTypes; -BaseEmojiSuggestions.defaultProps = defaultProps; -BaseEmojiSuggestions.displayName = 'BaseEmojiSuggestions'; - -export default React.forwardRef((props, ref) => ( - // eslint-disable-next-line react/jsx-props-no-spreading - -)); diff --git a/src/components/EmojiSuggestions/emojiSuggestionsPropTypes.js b/src/components/EmojiSuggestions/emojiSuggestionsPropTypes.js deleted file mode 100644 index 58344bc00d780..0000000000000 --- a/src/components/EmojiSuggestions/emojiSuggestionsPropTypes.js +++ /dev/null @@ -1,41 +0,0 @@ -import PropTypes from 'prop-types'; - -const propTypes = { - /** The index of the highlighted emoji */ - highlightedEmojiIndex: PropTypes.number, - - /** Array of suggested emoji */ - emojis: PropTypes.arrayOf(PropTypes.shape({ - /** The emoji code */ - code: PropTypes.string, - - /** The name of the emoji */ - name: PropTypes.string, - })).isRequired, - - /** Fired when the user selects an emoji */ - onSelect: PropTypes.func.isRequired, - - /** Emoji prefix that follows the colon */ - prefix: PropTypes.string.isRequired, - - /** Show that we can use large emoji picker. - * Depending on available space and whether the input is expanded, we can have a small or large emoji suggester. - * When this value is false, the suggester will have a height of 2.5 items. When this value is true, the height can be up to 5 items. */ - isEmojiPickerLarge: PropTypes.bool.isRequired, - - /** Show that we should include ReportRecipientLocalTime view height */ - shouldIncludeReportRecipientLocalTimeHeight: PropTypes.bool.isRequired, - - /** Stores user's preferred skin tone */ - preferredSkinToneIndex: PropTypes.number.isRequired, - - /** A ref to forward to the suggestion container */ - forwardedRef: PropTypes.object, -}; - -const defaultProps = { - highlightedEmojiIndex: 0, -}; - -export {propTypes, defaultProps}; diff --git a/src/components/EmojiSuggestions/index.native.js b/src/components/EmojiSuggestions/index.native.js deleted file mode 100644 index 30cc58b8c675b..0000000000000 --- a/src/components/EmojiSuggestions/index.native.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import BaseEmojiSuggestions from './BaseEmojiSuggestions'; -import {propTypes, defaultProps} from './emojiSuggestionsPropTypes'; - -const EmojiSuggestions = props => ( - // eslint-disable-next-line react/jsx-props-no-spreading - -); - -EmojiSuggestions.propTypes = propTypes; -EmojiSuggestions.defaultProps = defaultProps; -EmojiSuggestions.displayName = 'EmojiSuggestions'; - -export default EmojiSuggestions; diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index 0e984f5a0aa84..88e5a11e5cb43 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -152,8 +152,8 @@ const defaultProps = { const getMaxArrowIndex = (numRows, isEmojiPickerLarge) => { // EmojiRowCount is number of emoji suggestions. For small screen we can fit 3 items and for large we show up to 5 items const emojiRowCount = isEmojiPickerLarge - ? Math.max(numRows, CONST.EMOJI_SUGGESTER.MAX_AMOUNT_OF_ITEMS) - : Math.max(numRows, CONST.EMOJI_SUGGESTER.MIN_AMOUNT_OF_ITEMS); + ? Math.max(numRows, CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_ITEMS) + : Math.max(numRows, CONST.AUTO_COMPLETE_SUGGESTER.MIN_AMOUNT_OF_ITEMS); // -1 because we start at 0 return emojiRowCount - 1; diff --git a/src/styles/StyleUtils.js b/src/styles/StyleUtils.js index 1df9d5beccd75..b08b7cf22dd85 100644 --- a/src/styles/StyleUtils.js +++ b/src/styles/StyleUtils.js @@ -860,7 +860,7 @@ function getReportWelcomeContainerStyle(isSmallScreenWidth) { } /** - * Gets styles for Emoji Suggestion row + * Gets styles for AutoCompleteSuggestion row * * @param {Number} highlightedEmojiIndex * @param {Number} rowHeight @@ -868,7 +868,7 @@ function getReportWelcomeContainerStyle(isSmallScreenWidth) { * @param {Number} currentEmojiIndex * @returns {Object} */ -function getEmojiSuggestionItemStyle( +function getAutoCompleteSuggestionItemStyle( highlightedEmojiIndex, rowHeight, hovered, @@ -894,18 +894,18 @@ function getEmojiSuggestionItemStyle( } /** - * Gets the correct position for emoji suggestion container + * Gets the correct position for auto complete suggestion container * * @param {Number} itemsHeight * @param {Boolean} shouldIncludeReportRecipientLocalTimeHeight * @returns {Object} */ -function getEmojiSuggestionContainerStyle( +function getAutoCompleteSuggestionContainerStyle( itemsHeight, shouldIncludeReportRecipientLocalTimeHeight, ) { const optionalPadding = shouldIncludeReportRecipientLocalTimeHeight ? CONST.RECIPIENT_LOCAL_TIME_HEIGHT : 0; - const padding = CONST.EMOJI_SUGGESTER.SUGGESTER_PADDING - optionalPadding; + const padding = CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING - optionalPadding; // The suggester is positioned absolutely within the component that includes the input and RecipientLocalTime view (for non-expanded mode only). To position it correctly, // we need to shift it by the suggester's height plus its padding and, if applicable, the height of the RecipientLocalTime view. @@ -1045,8 +1045,8 @@ export { getReportWelcomeBackgroundImageStyle, getReportWelcomeTopMarginStyle, getReportWelcomeContainerStyle, - getEmojiSuggestionItemStyle, - getEmojiSuggestionContainerStyle, + getAutoCompleteSuggestionItemStyle, + getAutoCompleteSuggestionContainerStyle, getColoredBackgroundStyle, getDefaultWorkspaceAvatarColor, getAvatarBorderRadius, diff --git a/src/styles/styles.js b/src/styles/styles.js index afde881c94454..b5c12d395aeee 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -169,7 +169,7 @@ const styles = { flexBasis: '48%', }, - emojiSuggestionsContainer: { + autoCompleteSuggestionsContainer: { backgroundColor: themeColors.appBG, borderRadius: 8, borderWidth: 1, @@ -180,7 +180,8 @@ const styles = { left: 0, right: 0, }, - emojiSuggestionContainer: { + + autoCompleteSuggestionContainer: { flexDirection: 'row', alignItems: 'center', },