diff --git a/src/CONST.js b/src/CONST.js index 86e819127c3b..7dbcb9dad56d 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -1109,6 +1109,10 @@ const CONST = { HEADER: 'header', MENTION_ICON: 'mention-icon', }, + AVATAR_ROW_SIZE: { + DEFAULT: 4, + LARGE_SCREEN: 8, + }, OPTION_MODE: { COMPACT: 'compact', DEFAULT: 'default', diff --git a/src/components/MultipleAvatars.js b/src/components/MultipleAvatars.js index c62fc387b815..2ac9724ccd14 100644 --- a/src/components/MultipleAvatars.js +++ b/src/components/MultipleAvatars.js @@ -1,4 +1,4 @@ -import React, {memo} from 'react'; +import React, {memo, useEffect, useState} from 'react'; import PropTypes from 'prop-types'; import {View} from 'react-native'; import _ from 'underscore'; @@ -32,6 +32,9 @@ const propTypes = { /** Prop to identify if we should load avatars vertically instead of diagonally */ shouldStackHorizontally: PropTypes.bool, + /** Prop to identify if we should display avatars in rows */ + shouldDisplayAvatarsInRows: PropTypes.bool, + /** Whether the avatars are hovered */ isHovered: PropTypes.bool, @@ -49,6 +52,9 @@ const propTypes = { /** Whether avatars are displayed with the highlighted background color instead of the app background color. This is primarily the case for IOU previews. */ shouldUseCardBackground: PropTypes.bool, + + /** Prop to limit the amount of avatars displayed horizontally */ + maxAvatarsInRow: PropTypes.number, }; const defaultProps = { @@ -57,20 +63,48 @@ const defaultProps = { secondAvatarStyle: [StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG)], fallbackIcon: undefined, shouldStackHorizontally: false, + shouldDisplayAvatarsInRows: false, isHovered: false, isPressed: false, isFocusMode: false, isInReportAction: false, shouldShowTooltip: true, shouldUseCardBackground: false, + maxAvatarsInRow: CONST.AVATAR_ROW_SIZE.DEFAULT, }; function MultipleAvatars(props) { + const [avatarRows, setAvatarRows] = useState([props.icons]); let avatarContainerStyles = props.size === CONST.AVATAR_SIZE.SMALL ? [styles.emptyAvatarSmall, styles.emptyAvatarMarginSmall] : [styles.emptyAvatar, styles.emptyAvatarMargin]; const singleAvatarStyles = props.size === CONST.AVATAR_SIZE.SMALL ? styles.singleAvatarSmall : styles.singleAvatar; const secondAvatarStyles = [props.size === CONST.AVATAR_SIZE.SMALL ? styles.secondAvatarSmall : styles.secondAvatar, ...props.secondAvatarStyle]; const tooltipTexts = props.shouldShowTooltip ? _.pluck(props.icons, 'name') : ['']; + const calculateAvatarRows = () => { + // If we're not displaying avatars in rows or the number of icons is less than or equal to the max avatars in a row, return a single row + if (!props.shouldDisplayAvatarsInRows || props.icons.length <= props.maxAvatarsInRow) { + setAvatarRows([props.icons]); + return; + } + + // Calculate the size of each row + const rowSize = Math.min(Math.ceil(props.icons.length / 2), props.maxAvatarsInRow); + + // Slice the icons array into two rows + const firstRow = props.icons.slice(rowSize); + const secondRow = props.icons.slice(0, rowSize); + + // Update the state with the two rows as an array + setAvatarRows([firstRow, secondRow]); + }; + + useEffect(() => { + calculateAvatarRows(); + + // The only dependencies of the effect are based on props, so we can safely disable the exhaustive-deps rule + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [props.icons, props.maxAvatarsInRow, props.shouldDisplayAvatarsInRows]); + if (!props.icons.length) { return null; } @@ -118,114 +152,126 @@ function MultipleAvatars(props) { } return ( - + <> {props.shouldStackHorizontally ? ( - <> - {_.map([...props.icons].splice(0, 4), (icon, index) => ( - - - - - - ))} - {props.icons.length > 4 && ( - - ( + + {_.map([...avatars].splice(0, props.maxAvatarsInRow), (icon, index) => ( + - {`+${props.icons.length - 4}`} - - - - )} - - ) : ( - - - {/* View is necessary for tooltip to show for multiple avatars in LHN */} - - - - - - {props.icons.length === 2 ? ( - - - ) : ( - - - props.maxAvatarsInRow && ( + + + - {`+${props.icons.length - 1}`} - + {`+${avatars.length - props.maxAvatarsInRow}`} + )} + )) + ) : ( + + + + {/* View is necessary for tooltip to show for multiple avatars in LHN */} + + + + + + {props.icons.length === 2 ? ( + + + + + + ) : ( + + + + {`+${props.icons.length - 1}`} + + + + )} + + )} - + ); } diff --git a/src/pages/home/report/ReportActionItemCreated.js b/src/pages/home/report/ReportActionItemCreated.js index 3c68a366bd0a..095b6ac350c3 100644 --- a/src/pages/home/report/ReportActionItemCreated.js +++ b/src/pages/home/report/ReportActionItemCreated.js @@ -4,7 +4,6 @@ import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; import ONYXKEYS from '../../../ONYXKEYS'; -import RoomHeaderAvatars from '../../../components/RoomHeaderAvatars'; import ReportWelcomeText from '../../../components/ReportWelcomeText'; import participantPropTypes from '../../../components/participantPropTypes'; import * as ReportUtils from '../../../libs/ReportUtils'; @@ -18,6 +17,7 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '../../../componen import compose from '../../../libs/compose'; import withLocalize from '../../../components/withLocalize'; import PressableWithoutFeedback from '../../../components/Pressable/PressableWithoutFeedback'; +import MultipleAvatars from '../../../components/MultipleAvatars'; import CONST from '../../../CONST'; const propTypes = { @@ -77,7 +77,13 @@ function ReportActionItemCreated(props) { accessibilityLabel={props.translate('common.details')} accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} > - + diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.js b/src/pages/workspace/WorkspaceInviteMessagePage.js index fe422dea20aa..12caf96f283a 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.js +++ b/src/pages/workspace/WorkspaceInviteMessagePage.js @@ -196,6 +196,7 @@ class WorkspaceInviteMessagePage extends React.Component { size={CONST.AVATAR_SIZE.LARGE} icons={OptionsListUtils.getAvatarsForAccountIDs(_.values(this.props.invitedEmailsToAccountIDsDraft), this.props.personalDetails)} shouldStackHorizontally + shouldDisplayAvatarsInRows secondAvatarStyle={[styles.secondAvatarInline]} />