diff --git a/src/CONST.js b/src/CONST.js
index 3298d36b7880..4e034327cab1 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -703,6 +703,7 @@ const CONST = {
LARGE: 'large',
DEFAULT: 'default',
SMALL: 'small',
+ SMALLER: 'smaller',
SUBSCRIPT: 'subscript',
SMALL_SUBSCRIPT: 'small-subscript',
},
diff --git a/src/ROUTES.js b/src/ROUTES.js
index 0cd8b4164a8e..0dd0f61824e2 100644
--- a/src/ROUTES.js
+++ b/src/ROUTES.js
@@ -23,6 +23,7 @@ export default {
SETTINGS: 'settings',
SETTINGS_PROFILE: 'settings/profile',
SETTINGS_PREFERENCES: 'settings/preferences',
+ SETTINGS_WORKSPACES: 'settings/workspaces',
SETTINGS_SECURITY: 'settings/security',
SETTINGS_CLOSE: 'settings/security/closeAccount',
SETTINGS_PASSWORD: 'settings/security/password',
diff --git a/src/components/BlockingViews/BlockingView.js b/src/components/BlockingViews/BlockingView.js
index 279e4c86cf54..5241714bea22 100644
--- a/src/components/BlockingViews/BlockingView.js
+++ b/src/components/BlockingViews/BlockingView.js
@@ -35,7 +35,7 @@ const BlockingView = props => (
width={variables.iconSizeSuperLarge}
height={variables.iconSizeSuperLarge}
/>
- {props.title}
+ {props.title}
{props.subtitle}
);
diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js
index 0ea32095698f..a9cc1f057caf 100644
--- a/src/components/MenuItem.js
+++ b/src/components/MenuItem.js
@@ -16,6 +16,7 @@ import menuItemPropTypes from './menuItemPropTypes';
import SelectCircle from './SelectCircle';
import colors from '../styles/colors';
import variables from '../styles/variables';
+import MultipleAvatars from './MultipleAvatars';
const propTypes = {
...menuItemPropTypes,
@@ -46,6 +47,8 @@ const defaultProps = {
interactive: true,
fallbackIcon: Expensicons.FallbackAvatar,
brickRoadIndicator: '',
+ floatRightAvatars: [],
+ shouldStackHorizontally: false,
};
const MenuItem = (props) => {
@@ -150,8 +153,18 @@ const MenuItem = (props) => {
)}
+ {!_.isEmpty(props.floatRightAvatars) && (
+
+
+
+ )}
{Boolean(props.brickRoadIndicator) && (
-
+
{
@@ -39,12 +47,13 @@ const MultipleAvatars = (props) => {
props.size === CONST.AVATAR_SIZE.SMALL ? styles.secondAvatarSmall : styles.secondAvatar,
...props.secondAvatarStyle,
];
+ const horizontalStyles = [styles.horizontalStackedAvatar4, styles.horizontalStackedAvatar3, styles.horizontalStackedAvatar2, styles.horizontalStackedAvatar1];
if (!props.icons.length) {
return null;
}
- if (props.icons.length === 1) {
+ if (props.icons.length === 1 && !props.shouldStackHorizontally) {
return (
@@ -60,41 +69,67 @@ const MultipleAvatars = (props) => {
return (
-
-
-
-
-
- {props.icons.length === 2 ? (
-
-
-
- ) : (
-
+ {props.shouldStackHorizontally ? (
+ <>
+ {
+ _.map([...props.icons].splice(0, 4).reverse(), (icon, index) => (
-
- {`+${props.icons.length - 1}`}
-
+
-
+ ))
+ }
+ {props.icons.length > 4 && (
+
+
+ {`+${props.icons.length - 4}`}
+
+
)}
+ >
+ ) : (
+
+
+
+
+
+ {props.icons.length === 2 ? (
+
+
+
+ ) : (
+
+
+
+ {`+${props.icons.length - 1}`}
+
+
+
+ )}
+
-
+ )}
);
};
diff --git a/src/components/menuItemPropTypes.js b/src/components/menuItemPropTypes.js
index 7f38175d564e..949d1ca26f1a 100644
--- a/src/components/menuItemPropTypes.js
+++ b/src/components/menuItemPropTypes.js
@@ -76,8 +76,14 @@ const propTypes = {
/** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */
fallbackIcon: PropTypes.func,
+ /** Avatars to show on the right of the menu item */
+ floatRightAvatars: PropTypes.arrayOf(PropTypes.string),
+
/** The type of brick road indicator to show. */
brickRoadIndicator: PropTypes.oneOf([CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR, CONST.BRICK_ROAD_INDICATOR_STATUS.INFO, '']),
+
+ /** Prop to identify if we should load avatars vertically instead of diagonally */
+ shouldStackHorizontally: PropTypes.bool,
};
export default propTypes;
diff --git a/src/languages/en.js b/src/languages/en.js
index 939a60d94665..6b2ac17c596e 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -19,6 +19,7 @@ export default {
save: 'Save',
saveChanges: 'Save changes',
password: 'Password',
+ workspaces: 'Workspaces',
profile: 'Profile',
payments: 'Payments',
preferences: 'Preferences',
@@ -814,6 +815,10 @@ export default {
growlMessageOnDeleteError: 'This workspace cannot be deleted right now because reports are actively being processed',
unavailable: 'Unavailable workspace',
},
+ emptyWorkspace: {
+ title: 'Create a new workspace',
+ subtitle: 'Workspaces are where you\'ll chat with your team, reimburse expenses, issue cards, send invoices, pay bills, and more — all in one place.',
+ },
new: {
newWorkspace: 'New workspace',
getTheExpensifyCardAndMore: 'Get the Expensify Card and more',
diff --git a/src/languages/es.js b/src/languages/es.js
index 31f7161565b9..eb7420a24cf5 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -19,6 +19,7 @@ export default {
save: 'Guardar',
saveChanges: 'Guardar cambios',
password: 'Contraseña',
+ workspaces: 'Espacios de trabajo',
profile: 'Perfil',
payments: 'Pagos',
preferences: 'Preferencias',
@@ -816,6 +817,10 @@ export default {
growlMessageOnDeleteError: 'No se puede eliminar el espacio de trabajo porque tiene informes que están siendo procesados',
unavailable: 'Espacio de trabajo no disponible',
},
+ emptyWorkspace: {
+ title: 'Crear un nuevo espacio de trabajo',
+ subtitle: 'En los espacios de trabajo es donde puedes chatear con tu equipo, reembolsar gastos, emitir tarjetas, enviar y pagar facturas y mas — todo en un mismo lugar',
+ },
new: {
newWorkspace: 'Nuevo espacio de trabajo',
getTheExpensifyCardAndMore: 'Consigue la Tarjeta Expensify y más',
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
index 6702b90769d8..7a22f47572fc 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
@@ -203,6 +203,13 @@ const SettingsModalStackNavigator = createModalStackNavigator([
},
name: 'Settings_Root',
},
+ {
+ getComponent: () => {
+ const SettingsWorkspacesPage = require('../../../pages/workspace/WorkspacesListPage').default;
+ return SettingsWorkspacesPage;
+ },
+ name: 'Settings_Workspaces',
+ },
{
getComponent: () => {
const SettingsProfilePage = require('../../../pages/settings/Profile/ProfilePage').default;
diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js
index 23cfa9ea349c..6e8951bffbcf 100644
--- a/src/libs/Navigation/linkingConfig.js
+++ b/src/libs/Navigation/linkingConfig.js
@@ -36,6 +36,10 @@ export default {
Settings_Root: {
path: ROUTES.SETTINGS,
},
+ Settings_Workspaces: {
+ path: ROUTES.SETTINGS_WORKSPACES,
+ exact: true,
+ },
Settings_Preferences: {
path: ROUTES.SETTINGS_PREFERENCES,
exact: true,
diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js
index 564fc2d0b717..1f481e08802d 100755
--- a/src/pages/settings/InitialSettingsPage.js
+++ b/src/pages/settings/InitialSettingsPage.js
@@ -5,10 +5,8 @@ import _ from 'underscore';
import {withOnyx} from 'react-native-onyx';
import Str from 'expensify-common/lib/str';
import styles from '../../styles/styles';
-import themeColors from '../../styles/themes/default';
import Text from '../../components/Text';
import * as Session from '../../libs/actions/Session';
-import * as Policy from '../../libs/actions/Policy';
import ONYXKEYS from '../../ONYXKEYS';
import Tooltip from '../../components/Tooltip';
import Avatar from '../../components/Avatar';
@@ -24,14 +22,12 @@ import CONST from '../../CONST';
import Permissions from '../../libs/Permissions';
import * as App from '../../libs/actions/App';
import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes, withCurrentUserPersonalDetailsDefaultProps} from '../../components/withCurrentUserPersonalDetails';
-import * as PolicyUtils from '../../libs/PolicyUtils';
-import policyMemberPropType from '../policyMemberPropType';
import * as PaymentMethods from '../../libs/actions/PaymentMethods';
import bankAccountPropTypes from '../../components/bankAccountPropTypes';
import cardPropTypes from '../../components/cardPropTypes';
import * as Wallet from '../../libs/actions/Wallet';
-import OfflineWithFeedback from '../../components/OfflineWithFeedback';
import walletTermsPropTypes from '../EnablePayments/walletTermsPropTypes';
+import * as PolicyUtils from '../../libs/PolicyUtils';
const propTypes = {
/* Onyx Props */
@@ -60,9 +56,6 @@ const propTypes = {
pendingAction: PropTypes.oneOf(_.values(CONST.RED_BRICK_ROAD_PENDING_ACTION)),
})),
- /** List of policy members */
- policyMembers: PropTypes.objectOf(policyMemberPropType),
-
/** The user's wallet account */
userWallet: PropTypes.shape({
/** The user's current wallet balance */
@@ -92,32 +85,16 @@ const defaultProps = {
currentBalance: 0,
},
betas: [],
- policyMembers: {},
walletTerms: {},
...withCurrentUserPersonalDetailsDefaultProps,
};
-/**
- * Dismisses the errors on one item
- *
- * @param {string} policyID
- * @param {string} pendingAction
- */
-function dismissWorkspaceError(policyID, pendingAction) {
- if (pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) {
- Policy.clearDeleteWorkspaceError(policyID);
- return;
- }
- throw new Error('Not implemented');
-}
-
class InitialSettingsPage extends React.Component {
constructor(props) {
super(props);
this.getWalletBalance = this.getWalletBalance.bind(this);
this.getDefaultMenuItems = this.getDefaultMenuItems.bind(this);
- this.getMenuItemsList = this.getMenuItemsList.bind(this);
this.getMenuItem = this.getMenuItem.bind(this);
}
@@ -142,7 +119,28 @@ class InitialSettingsPage extends React.Component {
* @returns {Array} the default menu items
*/
getDefaultMenuItems() {
+ const policiesAvatars = _.chain(this.props.policies)
+ .filter(policy => policy
+ && policy.type === CONST.POLICY.TYPE.FREE
+ && policy.role === CONST.POLICY.ROLE.ADMIN
+ && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE)
+ .sortBy(policy => policy.name)
+ .pluck('avatar')
+ .value();
+ const policyBrickRoadIndicator = _.chain(this.props.policies)
+ .filter(policy => policy && policy.type === CONST.POLICY.TYPE.FREE && policy.role === CONST.POLICY.ROLE.ADMIN)
+ .find(policy => PolicyUtils.getPolicyBrickRoadIndicatorStatus(policy, this.props.policyMembers))
+ .value() ? 'error' : null;
+
return ([
+ {
+ translationKey: 'common.workspaces',
+ icon: Expensicons.Building,
+ action: () => { Navigation.navigate(ROUTES.SETTINGS_WORKSPACES); },
+ floatRightAvatars: policiesAvatars,
+ shouldStackHorizontally: true,
+ brickRoadIndicator: policyBrickRoadIndicator,
+ },
{
translationKey: 'common.profile',
icon: Expensicons.Profile,
@@ -178,65 +176,10 @@ class InitialSettingsPage extends React.Component {
]);
}
- /**
- * Add free policies (workspaces) to the list of menu items and returns the list of menu items
- * @returns {Array} the menu item list
- */
- getMenuItemsList() {
- const menuItems = _.chain(this.props.policies)
- .filter(policy => policy && policy.type === CONST.POLICY.TYPE.FREE && policy.role === CONST.POLICY.ROLE.ADMIN)
- .map(policy => ({
- title: policy.name,
- icon: policy.avatar ? policy.avatar : Expensicons.Building,
- iconType: policy.avatar ? CONST.ICON_TYPE_AVATAR : CONST.ICON_TYPE_ICON,
- action: () => Navigation.navigate(ROUTES.getWorkspaceInitialRoute(policy.id)),
- iconStyles: policy.avatar ? [] : [styles.popoverMenuIconEmphasized],
- iconFill: themeColors.iconReversed,
- fallbackIcon: Expensicons.FallbackWorkspaceAvatar,
- brickRoadIndicator: PolicyUtils.getPolicyBrickRoadIndicatorStatus(policy, this.props.policyMembers),
- pendingAction: policy.pendingAction,
- isPolicy: true,
- errors: policy.errors,
- dismissError: () => dismissWorkspaceError(policy.id, policy.pendingAction),
- disabled: policy.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
- }))
- .sortBy(policy => policy.title)
- .value();
- menuItems.push(...this.getDefaultMenuItems());
-
- return menuItems;
- }
-
getMenuItem(item, index) {
const keyTitle = item.translationKey ? this.props.translate(item.translationKey) : item.title;
const isPaymentItem = item.translationKey === 'common.payments';
- if (item.isPolicy) {
- return (
-
-
-
- );
- }
-
return (
);
}
@@ -301,7 +246,7 @@ class InitialSettingsPage extends React.Component {
)}
- {_.map(this.getMenuItemsList(), (item, index) => this.getMenuItem(item, index))}
+ {_.map(this.getDefaultMenuItems(), (item, index) => this.getMenuItem(item, index))}
diff --git a/src/pages/workspace/WorkspaceInitialPage.js b/src/pages/workspace/WorkspaceInitialPage.js
index fbaf9289979d..52f0ae69d830 100644
--- a/src/pages/workspace/WorkspaceInitialPage.js
+++ b/src/pages/workspace/WorkspaceInitialPage.js
@@ -138,17 +138,13 @@ class WorkspaceInitialPage extends React.Component {
Navigation.navigate(ROUTES.SETTINGS)}
+ onBackButtonPress={() => Navigation.navigate(ROUTES.SETTINGS_WORKSPACES)}
onCloseButtonPress={() => Navigation.dismissModal()}
shouldShowThreeDotsButton
shouldShowGetAssistanceButton
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_INITIAL}
threeDotsMenuItems={[
{
- icon: Expensicons.Plus,
- text: this.props.translate('workspace.new.newWorkspace'),
- onSelected: () => Policy.createWorkspace(),
- }, {
icon: Expensicons.Trashcan,
text: this.props.translate('workspace.common.delete'),
onSelected: () => this.setState({isDeleteModalOpen: true}),
diff --git a/src/pages/workspace/WorkspacesListPage.js b/src/pages/workspace/WorkspacesListPage.js
new file mode 100755
index 000000000000..4c9df5ee401a
--- /dev/null
+++ b/src/pages/workspace/WorkspacesListPage.js
@@ -0,0 +1,221 @@
+import React, {Component} from 'react';
+import {ScrollView} from 'react-native';
+import {withOnyx} from 'react-native-onyx';
+import PropTypes from 'prop-types';
+import _ from 'underscore';
+import HeaderWithCloseButton from '../../components/HeaderWithCloseButton';
+import Navigation from '../../libs/Navigation/Navigation';
+import ScreenWrapper from '../../components/ScreenWrapper';
+import ROUTES from '../../ROUTES';
+import ONYXKEYS from '../../ONYXKEYS';
+import CONST from '../../CONST';
+import styles from '../../styles/styles';
+import compose from '../../libs/compose';
+import OfflineWithFeedback from '../../components/OfflineWithFeedback';
+import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
+import * as Expensicons from '../../components/Icon/Expensicons';
+import themeColors from '../../styles/themes/default';
+import * as PolicyUtils from '../../libs/PolicyUtils';
+import MenuItem from '../../components/MenuItem';
+import * as Policy from '../../libs/actions/Policy';
+import policyMemberPropType from '../policyMemberPropType';
+import Permissions from '../../libs/Permissions';
+import Button from '../../components/Button';
+import FixedFooter from '../../components/FixedFooter';
+import BlockingView from '../../components/BlockingViews/BlockingView';
+
+const propTypes = {
+ /* Onyx Props */
+
+ /** The list of this user's policies */
+ policies: PropTypes.objectOf(PropTypes.shape({
+ /** The ID of the policy */
+ ID: PropTypes.string,
+
+ /** The name of the policy */
+ name: PropTypes.string,
+
+ /** The type of the policy */
+ type: PropTypes.string,
+
+ /** The user's role in the policy */
+ role: PropTypes.string,
+
+ /** The current action that is waiting to happen on the policy */
+ pendingAction: PropTypes.oneOf(_.values(CONST.RED_BRICK_ROAD_PENDING_ACTION)),
+ })),
+
+ /** List of policy members */
+ policyMembers: PropTypes.objectOf(policyMemberPropType),
+
+ /** The user's wallet account */
+ userWallet: PropTypes.shape({
+ /** The user's current wallet balance */
+ currentBalance: PropTypes.number,
+ }),
+
+ /** List of betas available to current user */
+ betas: PropTypes.arrayOf(PropTypes.string),
+
+ ...withLocalizePropTypes,
+};
+
+const defaultProps = {
+ policies: {},
+ policyMembers: {},
+ userWallet: {
+ currentBalance: 0,
+ },
+ betas: [],
+};
+
+/**
+ * Dismisses the errors on one item
+ *
+ * @param {string} policyID
+ * @param {string} pendingAction
+ */
+function dismissWorkspaceError(policyID, pendingAction) {
+ if (pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) {
+ Policy.clearDeleteWorkspaceError(policyID);
+ return;
+ }
+ throw new Error('Not implemented');
+}
+
+class WorkspacesListPage extends Component {
+ constructor(props) {
+ super(props);
+
+ this.getWalletBalance = this.getWalletBalance.bind(this);
+ this.getWorkspaces = this.getWorkspaces.bind(this);
+ this.getMenuItem = this.getMenuItem.bind(this);
+ }
+
+ /**
+ * @param {Boolean} isPaymentItem whether the item being rendered is the payments menu item
+ * @returns {Number} the user wallet balance
+ */
+ getWalletBalance(isPaymentItem) {
+ return (isPaymentItem && Permissions.canUseWallet(this.props.betas))
+ ? this.props.numberFormat(
+ this.props.userWallet.currentBalance / 100, // Divide by 100 because balance is in cents
+ {style: 'currency', currency: 'USD'},
+ ) : undefined;
+ }
+
+ /**
+ * Add free policies (workspaces) to the list of menu items and returns the list of menu items
+ * @returns {Array} the menu item list
+ */
+ getWorkspaces() {
+ return _.chain(this.props.policies)
+ .filter(policy => policy && policy.type === CONST.POLICY.TYPE.FREE && policy.role === CONST.POLICY.ROLE.ADMIN)
+ .map(policy => ({
+ title: policy.name,
+ icon: policy.avatar ? policy.avatar : Expensicons.Building,
+ iconType: policy.avatar ? CONST.ICON_TYPE_AVATAR : CONST.ICON_TYPE_ICON,
+ action: () => Navigation.navigate(ROUTES.getWorkspaceInitialRoute(policy.id)),
+ iconStyles: policy.avatar ? [] : [styles.popoverMenuIconEmphasized],
+ iconFill: themeColors.iconReversed,
+ fallbackIcon: Expensicons.FallbackWorkspaceAvatar,
+ brickRoadIndicator: PolicyUtils.getPolicyBrickRoadIndicatorStatus(policy, this.props.policyMembers),
+ pendingAction: policy.pendingAction,
+ isPolicy: true,
+ errors: policy.errors,
+ dismissError: () => dismissWorkspaceError(policy.id, policy.pendingAction),
+ disabled: policy.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
+ }))
+ .sortBy(policy => policy.title)
+ .value();
+ }
+
+ /**
+ * Gets the menu item for each workspace
+ *
+ * @param {Object} item
+ * @param {Number} index
+ * @returns {JSX}
+ */
+ getMenuItem(item, index) {
+ const keyTitle = item.translationKey ? this.props.translate(item.translationKey) : item.title;
+ const isPaymentItem = item.translationKey === 'common.payments';
+
+ return (
+
+
+
+ );
+ }
+
+ render() {
+ const workspaces = this.getWorkspaces();
+ return (
+
+ Navigation.navigate(ROUTES.SETTINGS)}
+ onCloseButtonPress={() => Navigation.dismissModal(true)}
+ />
+ {_.isEmpty(workspaces) ? (
+
+ ) : (
+
+ {_.map(workspaces, (item, index) => this.getMenuItem(item, index))}
+
+ )}
+
+
+
+ );
+ }
+}
+
+WorkspacesListPage.propTypes = propTypes;
+WorkspacesListPage.defaultProps = defaultProps;
+
+export default compose(
+ withLocalize,
+ withOnyx({
+ policies: {
+ key: ONYXKEYS.COLLECTION.POLICY,
+ },
+ policyMembers: {
+ key: ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST,
+ },
+ userWallet: {
+ key: ONYXKEYS.USER_WALLET,
+ },
+ betas: {
+ key: ONYXKEYS.BETAS,
+ },
+ }),
+)(WorkspacesListPage);
diff --git a/src/styles/StyleUtils.js b/src/styles/StyleUtils.js
index 568058123451..95ed56688335 100644
--- a/src/styles/StyleUtils.js
+++ b/src/styles/StyleUtils.js
@@ -19,6 +19,7 @@ function getAvatarSize(size) {
[CONST.AVATAR_SIZE.SMALL_SUBSCRIPT]: variables.avatarSizeSmallSubscript,
[CONST.AVATAR_SIZE.SUBSCRIPT]: variables.avatarSizeSubscript,
[CONST.AVATAR_SIZE.SMALL]: variables.avatarSizeSmall,
+ [CONST.AVATAR_SIZE.SMALLER]: variables.avatarSizeSmaller,
[CONST.AVATAR_SIZE.LARGE]: variables.avatarSizeLarge,
};
return AVATAR_SIZES[size];
diff --git a/src/styles/styles.js b/src/styles/styles.js
index 969ea07918e3..dcbe18680344 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -493,6 +493,10 @@ const styles = {
backgroundColor: themeColors.activeComponentBG,
},
+ fontWeightBold: {
+ fontWeight: fontWeightBold,
+ },
+
touchableButtonImage: {
alignItems: 'center',
height: variables.componentSizeNormal,
@@ -1571,6 +1575,14 @@ const styles = {
borderRadius: 24,
},
+ horizontalStackedAvatar: {
+ height: 28,
+ width: 28,
+ backgroundColor: colors.white,
+ borderRadius: 33,
+ paddingTop: 2,
+ },
+
singleSubscript: {
height: variables.iconSizeNormal,
width: variables.iconSizeNormal,
@@ -1701,6 +1713,43 @@ const styles = {
width: variables.avatarSizeSmall,
},
+ horizontalStackedAvatar1: {
+ left: -19,
+ top: -79,
+ zIndex: 2,
+ },
+
+ horizontalStackedAvatar2: {
+ left: 1,
+ top: -51,
+ zIndex: 3,
+ },
+
+ horizontalStackedAvatar3: {
+ left: 21,
+ top: -23,
+ zIndex: 4,
+ },
+
+ horizontalStackedAvatar4: {
+ top: 5,
+ left: 41,
+ zIndex: 5,
+ },
+
+ horizontalStackedAvatar4Overlay: {
+ top: -107,
+ left: 41,
+ height: 28,
+ width: 28,
+ borderWidth: 2,
+ borderStyle: 'solid',
+ borderColor: colors.white,
+ backgroundColor: themeColors.opaqueAvatar,
+ borderRadius: 24,
+ zIndex: 6,
+ },
+
modalViewContainer: {
alignItems: 'center',
flex: 1,
diff --git a/src/styles/themes/default.js b/src/styles/themes/default.js
index b5cf7a058ca4..0b64e64484bf 100644
--- a/src/styles/themes/default.js
+++ b/src/styles/themes/default.js
@@ -33,6 +33,7 @@ export default {
online: colors.green,
offline: colors.gray3,
sidebarButtonBG: 'rgba(198, 201, 202, 0.25)',
+ opaqueAvatar: 'rgba(011, 027, 052, 0.64)',
modalBackdrop: colors.gray3,
modalBackground: colors.gray2,
badgeDangerBG: colors.red,
diff --git a/src/styles/utilities/positioning.js b/src/styles/utilities/positioning.js
index f25c757fbfc3..51a151ea72af 100644
--- a/src/styles/utilities/positioning.js
+++ b/src/styles/utilities/positioning.js
@@ -18,6 +18,9 @@ export default {
l0: {
left: 0,
},
+ l4: {
+ left: 4,
+ },
r0: {
right: 0,
},
diff --git a/src/styles/variables.js b/src/styles/variables.js
index fd5193a736a0..785f9775eeeb 100644
--- a/src/styles/variables.js
+++ b/src/styles/variables.js
@@ -11,6 +11,7 @@ export default {
avatarSizeLarge: 80,
avatarSizeNormal: 40,
avatarSizeSmall: 28,
+ avatarSizeSmaller: 24,
avatarSizeSubscript: 20,
avatarSizeSmallSubscript: 14,
fontSizeOnlyEmojis: 30,