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