diff --git a/assets/images/simple-illustrations/simple-illustration__bigrocket.svg b/assets/images/simple-illustrations/simple-illustration__bigrocket.svg
new file mode 100644
index 000000000000..1afd5f66b6ea
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__bigrocket.svg
@@ -0,0 +1,100 @@
+
diff --git a/assets/images/simple-illustrations/simple-illustration__handcard.svg b/assets/images/simple-illustrations/simple-illustration__handcard.svg
new file mode 100644
index 000000000000..7419b33d425c
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__handcard.svg
@@ -0,0 +1,41 @@
+
+
+
diff --git a/assets/images/simple-illustrations/simple-illustration__hotdogstand.svg b/assets/images/simple-illustrations/simple-illustration__hotdogstand.svg
index 732c16a75a2b..471b978bb97e 100644
--- a/assets/images/simple-illustrations/simple-illustration__hotdogstand.svg
+++ b/assets/images/simple-illustrations/simple-illustration__hotdogstand.svg
@@ -1 +1,98 @@
-
\ No newline at end of file
+
diff --git a/assets/images/simple-illustrations/simple-illustration__mailbox.svg b/assets/images/simple-illustrations/simple-illustration__mailbox.svg
new file mode 100644
index 000000000000..81b1f508fb52
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__mailbox.svg
@@ -0,0 +1,71 @@
+
+
+
diff --git a/assets/images/simple-illustrations/simple-illustration__smallrocket.svg b/assets/images/simple-illustrations/simple-illustration__smallrocket.svg
new file mode 100644
index 000000000000..0f8f166c849f
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__smallrocket.svg
@@ -0,0 +1,45 @@
+
diff --git a/src/CONST.ts b/src/CONST.ts
index dcb74695006a..0fc684347243 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -1297,6 +1297,12 @@ const CONST = {
USE_EXPENSIFY_FEES: 'use.expensify.com/fees',
},
+ LAYOUT_WIDTH: {
+ WIDE: 'wide',
+ NARROW: 'narrow',
+ NONE: 'none',
+ },
+
ICON_TYPE_ICON: 'icon',
ICON_TYPE_AVATAR: 'avatar',
ICON_TYPE_WORKSPACE: 'workspace',
diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts
index c8a14c7aba03..b0690211d6c5 100644
--- a/src/components/Icon/Illustrations.ts
+++ b/src/components/Icon/Illustrations.ts
@@ -27,6 +27,7 @@ import TadaBlue from '@assets/images/product-illustrations/tada--blue.svg';
import TadaYellow from '@assets/images/product-illustrations/tada--yellow.svg';
import ToddBehindCloud from '@assets/images/product-illustrations/todd-behind-cloud.svg';
import BankArrow from '@assets/images/simple-illustrations/simple-illustration__bank-arrow.svg';
+import BigRocket from '@assets/images/simple-illustrations/simple-illustration__bigrocket.svg';
import PinkBill from '@assets/images/simple-illustrations/simple-illustration__bill.svg';
import ChatBubbles from '@assets/images/simple-illustrations/simple-illustration__chatbubbles.svg';
import CoffeeMug from '@assets/images/simple-illustrations/simple-illustration__coffeemug.svg';
@@ -34,11 +35,13 @@ import ConciergeBubble from '@assets/images/simple-illustrations/simple-illustra
import ConciergeNew from '@assets/images/simple-illustrations/simple-illustration__concierge.svg';
import CreditCardsNew from '@assets/images/simple-illustrations/simple-illustration__credit-cards.svg';
import EmailAddress from '@assets/images/simple-illustrations/simple-illustration__email-address.svg';
+import HandCard from '@assets/images/simple-illustrations/simple-illustration__handcard.svg';
import HandEarth from '@assets/images/simple-illustrations/simple-illustration__handearth.svg';
import HotDogStand from '@assets/images/simple-illustrations/simple-illustration__hotdogstand.svg';
import InvoiceBlue from '@assets/images/simple-illustrations/simple-illustration__invoice.svg';
import LockOpen from '@assets/images/simple-illustrations/simple-illustration__lockopen.svg';
import Luggage from '@assets/images/simple-illustrations/simple-illustration__luggage.svg';
+import Mailbox from '@assets/images/simple-illustrations/simple-illustration__mailbox.svg';
import MoneyReceipts from '@assets/images/simple-illustrations/simple-illustration__money-receipts.svg';
import MoneyBadge from '@assets/images/simple-illustrations/simple-illustration__moneybadge.svg';
import MoneyIntoWallet from '@assets/images/simple-illustrations/simple-illustration__moneyintowallet.svg';
@@ -46,6 +49,7 @@ import MoneyWings from '@assets/images/simple-illustrations/simple-illustration_
import OpenSafe from '@assets/images/simple-illustrations/simple-illustration__opensafe.svg';
import SanFrancisco from '@assets/images/simple-illustrations/simple-illustration__sanfrancisco.svg';
import ShieldYellow from '@assets/images/simple-illustrations/simple-illustration__shield.svg';
+import SmallRocket from '@assets/images/simple-illustrations/simple-illustration__smallrocket.svg';
import ThumbsUpStars from '@assets/images/simple-illustrations/simple-illustration__thumbsupstars.svg';
import TrackShoe from '@assets/images/simple-illustrations/simple-illustration__track-shoe.svg';
import TreasureChest from '@assets/images/simple-illustrations/simple-illustration__treasurechest.svg';
@@ -55,12 +59,14 @@ export {
BankArrowPink,
BankMouseGreen,
BankUserGreen,
+ BigRocket,
ChatBubbles,
CoffeeMug,
ConciergeBlue,
ConciergeExclamation,
CreditCardsBlue,
EmailAddress,
+ HandCard,
HotDogStand,
InvoiceOrange,
JewelBoxBlue,
@@ -69,6 +75,7 @@ export {
JewelBoxPink,
JewelBoxYellow,
MagicCode,
+ Mailbox,
MoneyEnvelopeBlue,
MoneyMousePink,
ReceiptsSearchYellow,
@@ -77,6 +84,7 @@ export {
RocketOrange,
SanFrancisco,
SafeBlue,
+ SmallRocket,
TadaYellow,
TadaBlue,
ToddBehindCloud,
diff --git a/src/components/MentionSuggestions.tsx b/src/components/MentionSuggestions.tsx
index 72dfd177dd1e..a20cdcff4e10 100644
--- a/src/components/MentionSuggestions.tsx
+++ b/src/components/MentionSuggestions.tsx
@@ -100,7 +100,7 @@ function MentionSuggestions({prefix, mentions, highlightedMentionIndex = 0, onSe
{text}
@@ -119,7 +119,7 @@ function MentionSuggestions({prefix, mentions, highlightedMentionIndex = 0, onSe
styles.flexShrink1,
styles.flex1,
styles.mentionSuggestionsDisplayName,
- styles.mentionSuggestionsHandle,
+ styles.textSupporting,
theme.success,
StyleUtils,
],
diff --git a/src/components/PopoverMenu/index.js b/src/components/PopoverMenu/index.js
index 9cf890410017..597105173b4c 100644
--- a/src/components/PopoverMenu/index.js
+++ b/src/components/PopoverMenu/index.js
@@ -18,21 +18,9 @@ const propTypes = {
...createMenuPropTypes,
...windowDimensionsPropTypes,
- /** The horizontal and vertical anchors points for the popover */
- anchorPosition: PropTypes.shape({
- horizontal: PropTypes.number.isRequired,
- vertical: PropTypes.number.isRequired,
- }).isRequired,
-
/** Ref of the anchor */
anchorRef: refPropTypes,
- /** Where the popover should be positioned relative to the anchor points. */
- anchorAlignment: PropTypes.shape({
- horizontal: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL)),
- vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)),
- }),
-
withoutOverlay: PropTypes.bool,
/** Should we announce the Modal visibility changes? */
diff --git a/src/components/PopoverMenu/popoverMenuPropTypes.js b/src/components/PopoverMenu/popoverMenuPropTypes.js
index 092c2f2703c8..53eeb63b05e7 100644
--- a/src/components/PopoverMenu/popoverMenuPropTypes.js
+++ b/src/components/PopoverMenu/popoverMenuPropTypes.js
@@ -1,4 +1,5 @@
import PropTypes from 'prop-types';
+import _ from 'underscore';
import sourcePropTypes from '@components/Image/sourcePropTypes';
import CONST from '@src/CONST';
@@ -34,6 +35,12 @@ const propTypes = {
left: PropTypes.number,
}).isRequired,
+ /** Where the popover should be positioned relative to the anchor points. */
+ anchorAlignment: PropTypes.shape({
+ horizontal: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL)),
+ vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)),
+ }),
+
/** The anchor reference of the CreateMenu popover */
anchorRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]).isRequired,
diff --git a/src/languages/en.ts b/src/languages/en.ts
index b2c08ac80974..3cdb7cf2bf98 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -1474,6 +1474,7 @@ export default {
invoices: 'Invoices',
travel: 'Travel',
members: 'Members',
+ plan: 'Plan',
bankAccount: 'Bank account',
connectBankAccount: 'Connect bank account',
testTransactions: 'Test transactions',
@@ -1488,6 +1489,11 @@ export default {
workspaceAvatar: 'Workspace avatar',
mustBeOnlineToViewMembers: 'You must be online in order to view members of this workspace.',
},
+ type: {
+ free: 'Free',
+ control: 'Control',
+ collect: 'Collect',
+ },
emptyWorkspace: {
title: 'Create a workspace',
subtitle: 'Manage business expenses, issue cards, send invoices, and more.',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index dfd6dab6c388..d11211ca9325 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -1497,6 +1497,7 @@ export default {
invoices: 'Enviar facturas',
travel: 'Viajes',
members: 'Miembros',
+ plan: 'Plan',
bankAccount: 'Cuenta bancaria',
connectBankAccount: 'Conectar cuenta bancaria',
testTransactions: 'Transacciones de prueba',
@@ -1511,6 +1512,11 @@ export default {
workspaceAvatar: 'Espacio de trabajo avatar',
mustBeOnlineToViewMembers: 'Debes estar en línea para poder ver los miembros de este espacio de trabajo.',
},
+ type: {
+ free: 'Gratis',
+ control: 'Control',
+ collect: 'Recolectar',
+ },
emptyWorkspace: {
title: 'Crea un espacio de trabajo',
subtitle: 'Administra gastos de empresa, emite tarjetas, envía facturas y mucho más.',
diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts
index 8bb4ac0aea3e..a5a033797c7b 100644
--- a/src/libs/PersonalDetailsUtils.ts
+++ b/src/libs/PersonalDetailsUtils.ts
@@ -20,7 +20,11 @@ Onyx.connect({
* @param [defaultValue] optional default display name value
*/
function getDisplayNameOrDefault(displayName?: string, defaultValue = ''): string {
- return displayName ?? defaultValue ?? Localize.translateLocal('common.hidden');
+ // Not using nullish coalescing as it differs from OR for strings. For example:
+ // '' || 'A' === 'A'
+ // '' ?? 'A' === ''
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ return displayName || defaultValue || Localize.translateLocal('common.hidden');
}
/**
diff --git a/src/pages/workspace/WorkspacesListRow.tsx b/src/pages/workspace/WorkspacesListRow.tsx
new file mode 100755
index 000000000000..1a28ff47bbde
--- /dev/null
+++ b/src/pages/workspace/WorkspacesListRow.tsx
@@ -0,0 +1,177 @@
+import React, {useMemo} from 'react';
+import {View} from 'react-native';
+import {ValueOf} from 'type-fest';
+import Avatar from '@components/Avatar';
+import Icon from '@components/Icon';
+import * as Illustrations from '@components/Icon/Illustrations';
+import {MenuItemProps} from '@components/MenuItem';
+import Text from '@components/Text';
+import ThreeDotsMenu from '@components/ThreeDotsMenu';
+import withCurrentUserPersonalDetails, {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
+import {AvatarSource} from '@libs/UserUtils';
+import variables from '@styles/variables';
+import CONST from '@src/CONST';
+import IconAsset from '@src/types/utils/IconAsset';
+
+type WorkspacesListRowProps = WithCurrentUserPersonalDetailsProps & {
+ /** Name of the workspace */
+ title: string;
+
+ /** Account ID of the workspace's owner */
+ ownerAccountID?: number;
+
+ /** Type of workspace. Type personal is not valid in this context so it's omitted */
+ workspaceType: typeof CONST.POLICY.TYPE.FREE | typeof CONST.POLICY.TYPE.CORPORATE | typeof CONST.POLICY.TYPE.TEAM;
+
+ /** Icon to show next to the workspace name */
+ workspaceIcon?: AvatarSource;
+
+ /** Icon to be used when workspaceIcon is not present */
+ fallbackWorkspaceIcon?: AvatarSource;
+
+ /** Items for the three dots menu */
+ menuItems: MenuItemProps[];
+
+ /** Renders the component using big screen layout or small screen layout. When layoutWidth === WorkspaceListRowLayout.NONE,
+ * component will return null to prevent layout from jumping on initial render and when parent width changes. */
+ layoutWidth?: ValueOf;
+};
+
+const workspaceTypeIcon = (workspaceType: WorkspacesListRowProps['workspaceType']): IconAsset => {
+ switch (workspaceType) {
+ case CONST.POLICY.TYPE.FREE:
+ return Illustrations.HandCard;
+ case CONST.POLICY.TYPE.CORPORATE:
+ return Illustrations.ShieldYellow;
+ case CONST.POLICY.TYPE.TEAM:
+ return Illustrations.Mailbox;
+ default:
+ throw new Error(`Don't know which icon to serve for workspace type`);
+ }
+};
+
+function WorkspacesListRow({
+ title,
+ menuItems,
+ workspaceIcon,
+ fallbackWorkspaceIcon,
+ ownerAccountID,
+ workspaceType,
+ currentUserPersonalDetails,
+ layoutWidth = CONST.LAYOUT_WIDTH.NONE,
+}: WorkspacesListRowProps) {
+ const styles = useThemeStyles();
+ const {translate} = useLocalize();
+
+ const ownerDetails = ownerAccountID && PersonalDetailsUtils.getPersonalDetailsByIDs([ownerAccountID], currentUserPersonalDetails.accountID)[0];
+
+ const userFriendlyWorkspaceType = useMemo(() => {
+ switch (workspaceType) {
+ case CONST.POLICY.TYPE.FREE:
+ return translate('workspace.type.free');
+ case CONST.POLICY.TYPE.CORPORATE:
+ return translate('workspace.type.control');
+ case CONST.POLICY.TYPE.TEAM:
+ return translate('workspace.type.collect');
+ default:
+ throw new Error(`Don't know a friendly workspace name for this workspace type`);
+ }
+ }, [workspaceType, translate]);
+
+ if (layoutWidth === CONST.LAYOUT_WIDTH.NONE) {
+ // To prevent layout from jumping or rendering for a split second, when
+ // isWide is undefined we don't assume anything and simply return null.
+ return null;
+ }
+
+ const isWide = layoutWidth === CONST.LAYOUT_WIDTH.WIDE;
+ const isNarrow = layoutWidth === CONST.LAYOUT_WIDTH.NARROW;
+
+ return (
+
+
+
+
+ {title}
+
+ {isNarrow && (
+
+ )}
+
+
+ {!!ownerDetails && (
+ <>
+
+
+
+ {PersonalDetailsUtils.getDisplayNameOrDefault(ownerDetails.displayName)}
+
+
+ {ownerDetails.login}
+
+
+ >
+ )}
+
+
+
+
+
+ {userFriendlyWorkspaceType}
+
+
+ {translate('workspace.common.plan')}
+
+
+
+ {isWide && (
+
+ )}
+
+ );
+}
+
+WorkspacesListRow.displayName = 'WorkspacesListRow';
+
+export default withCurrentUserPersonalDetails(WorkspacesListRow);
diff --git a/src/styles/index.ts b/src/styles/index.ts
index b87d7ed5070d..cfd9b797de0b 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -296,7 +296,7 @@ const styles = (theme: ThemeColors) =>
fontWeight: fontWeightBold,
},
- mentionSuggestionsHandle: {
+ textSupporting: {
color: theme.textSupporting,
},
@@ -1148,6 +1148,13 @@ const styles = (theme: ThemeColors) =>
noOutline: addOutlineWidth(theme, {}, 0),
+ labelStrong: {
+ fontFamily: fontFamily.EXP_NEUE,
+ fontWeight: 'bold',
+ fontSize: variables.fontSizeLabel,
+ lineHeight: variables.lineHeightNormal,
+ },
+
textLabelSupporting: {
fontFamily: fontFamily.EXP_NEUE,
fontSize: variables.fontSizeLabel,
@@ -2874,6 +2881,14 @@ const styles = (theme: ThemeColors) =>
bottom: -4,
},
+ workspaceOwnerAvatarWrapper: {
+ margin: 6,
+ },
+
+ workspaceTypeWrapper: {
+ margin: 3,
+ },
+
autoGrowHeightMultilineInput: {
maxHeight: 115,
},
diff --git a/src/styles/utils/borders.ts b/src/styles/utils/borders.ts
index 5d5110f858e4..9cd02dcd22ae 100644
--- a/src/styles/utils/borders.ts
+++ b/src/styles/utils/borders.ts
@@ -12,6 +12,10 @@ export default {
borderRadius: 8,
},
+ br3: {
+ borderRadius: 12,
+ },
+
br4: {
borderRadius: 16,
},
diff --git a/src/styles/variables.ts b/src/styles/variables.ts
index 3bf83545bc4a..9bc467615706 100644
--- a/src/styles/variables.ts
+++ b/src/styles/variables.ts
@@ -178,6 +178,7 @@ export default {
reportActionItemImagesMoreCornerTriangleWidth: 40,
bankCardWidth: 40,
bankCardHeight: 26,
+ workspaceTypeIconWidth: 34,
// The height of the empty list is 14px (2px for borders and 12px for vertical padding)
// This is calculated based on the values specified in the 'getGoogleListViewStyle' function of the 'StyleUtils' utility