diff --git a/src/CONST.ts b/src/CONST.ts
index 7c8a6791d65b..4376594342f8 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -2932,8 +2932,8 @@ const CONST = {
// eslint-disable-next-line max-len, no-misleading-character-class
EMOJI: /[\p{Extended_Pictographic}\u200d\u{1f1e6}-\u{1f1ff}\u{1f3fb}-\u{1f3ff}\u{e0020}-\u{e007f}\u20E3\uFE0F]|[#*0-9]\uFE0F?\u20E3/gu,
- // eslint-disable-next-line max-len, no-misleading-character-class
- EMOJIS: /[\p{Extended_Pictographic}](\u200D[\p{Extended_Pictographic}]|[\u{1F3FB}-\u{1F3FF}]|[\u{E0020}-\u{E007F}]|\uFE0F|\u20E3)*|[\u{1F1E6}-\u{1F1FF}]{2}|[#*0-9]\uFE0F?\u20E3/gu,
+ // eslint-disable-next-line max-len, no-misleading-character-class, no-empty-character-class
+ EMOJIS: /[\p{Extended_Pictographic}](\u200D[\p{Extended_Pictographic}]|[\u{1F3FB}-\u{1F3FF}]|[\u{E0020}-\u{E007F}]|\uFE0F|\u20E3)*|[\u{1F1E6}-\u{1F1FF}]{2}|[#*0-9]\uFE0F?\u20E3/du,
// eslint-disable-next-line max-len, no-misleading-character-class
EMOJI_SKIN_TONES: /[\u{1f3fb}-\u{1f3ff}]/gu,
@@ -2970,6 +2970,10 @@ const CONST = {
return new RegExp(`[\\n\\s]|${this.SPECIAL_CHAR.source}|${this.EMOJI.source}`, 'gu');
},
+ get ALL_EMOJIS() {
+ return new RegExp(this.EMOJIS, this.EMOJIS.flags.concat('g'));
+ },
+
MERGED_ACCOUNT_PREFIX: /^(MERGED_\d+@)/,
ROUTES: {
VALIDATE_LOGIN: /\/v($|(\/\/*))/,
diff --git a/src/components/AccountSwitcher.tsx b/src/components/AccountSwitcher.tsx
index 9a90de17595d..ed2eae7a0a4c 100644
--- a/src/components/AccountSwitcher.tsx
+++ b/src/components/AccountSwitcher.tsx
@@ -10,6 +10,7 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import {clearDelegatorErrors, connect, disconnect} from '@libs/actions/Delegate';
+import * as EmojiUtils from '@libs/EmojiUtils';
import * as ErrorUtils from '@libs/ErrorUtils';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import variables from '@styles/variables';
@@ -46,6 +47,7 @@ function AccountSwitcher() {
const isActingAsDelegate = !!account?.delegatedAccess?.delegate ?? false;
const canSwitchAccounts = delegators.length > 0 || isActingAsDelegate;
+ const processedTextArray = EmojiUtils.splitTextWithEmojis(currentUserPersonalDetails?.displayName);
const createBaseMenuItem = (
personalDetails: PersonalDetails | undefined,
@@ -149,7 +151,9 @@ function AccountSwitcher() {
numberOfLines={1}
style={[styles.textBold, styles.textLarge, styles.flexShrink1]}
>
- {currentUserPersonalDetails?.displayName}
+ {processedTextArray.length !== 0
+ ? EmojiUtils.getProcessedText(processedTextArray, styles.initialSettingsUsernameEmoji)
+ : currentUserPersonalDetails?.displayName}
{!!canSwitchAccounts && (
diff --git a/src/components/Composer/implementation/index.tsx b/src/components/Composer/implementation/index.tsx
index be875790d75e..e71ade65e66d 100755
--- a/src/components/Composer/implementation/index.tsx
+++ b/src/components/Composer/implementation/index.tsx
@@ -19,6 +19,7 @@ import * as Browser from '@libs/Browser';
import * as EmojiUtils from '@libs/EmojiUtils';
import * as FileUtils from '@libs/fileDownload/FileUtils';
import isEnterWhileComposition from '@libs/KeyboardShortcut/isEnterWhileComposition';
+import variables from '@styles/variables';
import CONST from '@src/CONST';
const excludeNoStyles: Array = [];
@@ -70,6 +71,7 @@ function Composer(
start: selectionProp.start,
end: selectionProp.end,
});
+ const [hasMultipleLines, setHasMultipleLines] = useState(false);
const [isRendered, setIsRendered] = useState(false);
const isScrollBarVisible = useIsScrollBarVisible(textInput, value ?? '');
const [prevScroll, setPrevScroll] = useState();
@@ -328,10 +330,10 @@ function Composer(
scrollStyleMemo,
StyleUtils.getComposerMaxHeightStyle(maxLines, isComposerFullSize),
isComposerFullSize ? {height: '100%', maxHeight: 'none'} : undefined,
- textContainsOnlyEmojis ? styles.onlyEmojisTextLineHeight : {},
+ textContainsOnlyEmojis && hasMultipleLines ? styles.onlyEmojisTextLineHeight : {},
],
- [style, styles.rtlTextRenderForSafari, styles.onlyEmojisTextLineHeight, scrollStyleMemo, StyleUtils, maxLines, isComposerFullSize, textContainsOnlyEmojis],
+ [style, styles.rtlTextRenderForSafari, styles.onlyEmojisTextLineHeight, scrollStyleMemo, hasMultipleLines, StyleUtils, maxLines, isComposerFullSize, textContainsOnlyEmojis],
);
return (
@@ -350,6 +352,9 @@ function Composer(
/* eslint-disable-next-line react/jsx-props-no-spreading */
{...props}
onSelectionChange={addCursorPositionToSelectionChange}
+ onContentSizeChange={(e) => {
+ setHasMultipleLines(e.nativeEvent.contentSize.height > variables.componentSizeLarge);
+ }}
disabled={isDisabled}
onKeyPress={handleKeyPress}
addAuthTokenToImageURLCallback={addEncryptedAuthTokenToURL}
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx
index 31d092800d20..879684210825 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx
@@ -1,11 +1,22 @@
-import React from 'react';
+import React, {useMemo} from 'react';
+import type {TextStyle} from 'react-native';
import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html';
import EmojiWithTooltip from '@components/EmojiWithTooltip';
import useThemeStyles from '@hooks/useThemeStyles';
function EmojiRenderer({tnode, style: styleProp}: CustomRendererProps) {
const styles = useThemeStyles();
- const style = {...styleProp, ...('islarge' in tnode.attributes ? styles.onlyEmojisText : {})};
+ const style = useMemo(() => {
+ if ('islarge' in tnode.attributes) {
+ return [styleProp as TextStyle, styles.onlyEmojisText];
+ }
+
+ if ('ismedium' in tnode.attributes) {
+ return [styleProp as TextStyle, styles.emojisWithTextFontSize, styles.verticalAlignTopText];
+ }
+
+ return null;
+ }, [tnode.attributes, styles, styleProp]);
return (
{displayName}
diff --git a/src/components/TextInput/BaseTextInput/index.native.tsx b/src/components/TextInput/BaseTextInput/index.native.tsx
index 670126f8c6ec..127989b5faa0 100644
--- a/src/components/TextInput/BaseTextInput/index.native.tsx
+++ b/src/components/TextInput/BaseTextInput/index.native.tsx
@@ -182,9 +182,10 @@ function BaseTextInput(
}
const layout = event.nativeEvent.layout;
+ const HEIGHT_TO_FIT_EMOJIS = 1;
setWidth((prevWidth: number | null) => (autoGrowHeight ? layout.width : prevWidth));
- setHeight((prevHeight: number) => (!multiline ? layout.height : prevHeight));
+ setHeight((prevHeight: number) => (!multiline ? layout.height + HEIGHT_TO_FIT_EMOJIS : prevHeight));
},
[autoGrowHeight, multiline],
);
diff --git a/src/components/TextWithTooltip/index.native.tsx b/src/components/TextWithTooltip/index.native.tsx
index b857ded2588b..9f5f246ff9d3 100644
--- a/src/components/TextWithTooltip/index.native.tsx
+++ b/src/components/TextWithTooltip/index.native.tsx
@@ -1,14 +1,19 @@
import React from 'react';
import Text from '@components/Text';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as EmojiUtils from '@libs/EmojiUtils';
import type TextWithTooltipProps from './types';
function TextWithTooltip({text, style, numberOfLines = 1}: TextWithTooltipProps) {
+ const styles = useThemeStyles();
+ const processedTextArray = EmojiUtils.splitTextWithEmojis(text);
+
return (
- {text}
+ {processedTextArray.length !== 0 ? EmojiUtils.getProcessedText(processedTextArray, [style, styles.emojisFontFamily]) : text}
);
}
diff --git a/src/components/WorkspacesListRowDisplayName/index.native.tsx b/src/components/WorkspacesListRowDisplayName/index.native.tsx
new file mode 100644
index 000000000000..1a91e2857db3
--- /dev/null
+++ b/src/components/WorkspacesListRowDisplayName/index.native.tsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import Text from '@components/Text';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as EmojiUtils from '@libs/EmojiUtils';
+import type WorkspacesListRowDisplayNameProps from './types';
+
+function WorkspacesListRowDisplayName({isDeleted, ownerName}: WorkspacesListRowDisplayNameProps) {
+ const styles = useThemeStyles();
+ const processedOwnerName = EmojiUtils.splitTextWithEmojis(ownerName);
+
+ return (
+
+ {processedOwnerName.length !== 0
+ ? EmojiUtils.getProcessedText(processedOwnerName, [styles.labelStrong, isDeleted ? styles.offlineFeedback.deleted : {}, styles.emojisWithTextFontFamily])
+ : ownerName}
+
+ );
+}
+
+WorkspacesListRowDisplayName.displayName = 'WorkspacesListRowDisplayName';
+
+export default WorkspacesListRowDisplayName;
diff --git a/src/components/WorkspacesListRowDisplayName/index.tsx b/src/components/WorkspacesListRowDisplayName/index.tsx
new file mode 100644
index 000000000000..0d3acb736d2f
--- /dev/null
+++ b/src/components/WorkspacesListRowDisplayName/index.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import Text from '@components/Text';
+import useThemeStyles from '@hooks/useThemeStyles';
+import type WorkspacesListRowDisplayNameProps from './types';
+
+function WorkspacesListRowDisplayName({isDeleted, ownerName}: WorkspacesListRowDisplayNameProps) {
+ const styles = useThemeStyles();
+
+ return (
+
+ {ownerName}
+
+ );
+}
+
+WorkspacesListRowDisplayName.displayName = 'WorkspacesListRowDisplayName';
+
+export default WorkspacesListRowDisplayName;
diff --git a/src/components/WorkspacesListRowDisplayName/types.tsx b/src/components/WorkspacesListRowDisplayName/types.tsx
new file mode 100644
index 000000000000..0744ebc18fc1
--- /dev/null
+++ b/src/components/WorkspacesListRowDisplayName/types.tsx
@@ -0,0 +1,9 @@
+type WorkspacesListRowDisplayNameProps = {
+ /** Should the deleted style be applied */
+ isDeleted: boolean;
+
+ /** Workspace owner name */
+ ownerName: string;
+};
+
+export default WorkspacesListRowDisplayNameProps;
diff --git a/src/hooks/useMarkdownStyle.ts b/src/hooks/useMarkdownStyle.ts
index 2006ca85dd13..7b38cc12347f 100644
--- a/src/hooks/useMarkdownStyle.ts
+++ b/src/hooks/useMarkdownStyle.ts
@@ -10,7 +10,7 @@ const defaultEmptyArray: Array = [];
function useMarkdownStyle(message: string | null = null, excludeStyles: Array = defaultEmptyArray): MarkdownStyle {
const theme = useTheme();
const hasMessageOnlyEmojis = message != null && message.length > 0 && containsOnlyEmojis(message);
- const emojiFontSize = hasMessageOnlyEmojis ? variables.fontSizeOnlyEmojis : variables.fontSizeNormal;
+ const emojiFontSize = hasMessageOnlyEmojis ? variables.fontSizeOnlyEmojis : variables.fontSizeEmojisWithinText;
// this map is used to reset the styles that are not needed - passing undefined value can break the native side
const nonStylingDefaultValues: Record = useMemo(
@@ -38,6 +38,7 @@ function useMarkdownStyle(message: string | null = null, excludeStyles: Array Emojis.emojiNameTable[name];
@@ -148,7 +155,7 @@ function trimEmojiUnicode(emojiCode: string): string {
*/
function isFirstLetterEmoji(message: string): boolean {
const trimmedMessage = Str.replaceAll(message.replace(/ /g, ''), '\n', '');
- const match = trimmedMessage.match(CONST.REGEX.EMOJIS);
+ const match = trimmedMessage.match(CONST.REGEX.ALL_EMOJIS);
if (!match) {
return false;
@@ -162,7 +169,7 @@ function isFirstLetterEmoji(message: string): boolean {
*/
function containsOnlyEmojis(message: string): boolean {
const trimmedMessage = Str.replaceAll(message.replace(/ /g, ''), '\n', '');
- const match = trimmedMessage.match(CONST.REGEX.EMOJIS);
+ const match = trimmedMessage.match(CONST.REGEX.ALL_EMOJIS);
if (!match) {
return false;
@@ -285,7 +292,7 @@ function extractEmojis(text: string): Emoji[] {
}
// Parse Emojis including skin tones - Eg: ['👩🏻', '👩🏻', '👩🏼', '👩🏻', '👩🏼', '👩']
- const parsedEmojis = text.match(CONST.REGEX.EMOJIS);
+ const parsedEmojis = text.match(CONST.REGEX.ALL_EMOJIS);
if (!parsedEmojis) {
return [];
@@ -595,6 +602,75 @@ function getSpacersIndexes(allEmojis: EmojiPickerList): number[] {
return spacersIndexes;
}
+/** Splits the text with emojis into array if emojis exist in the text */
+function splitTextWithEmojis(text = ''): TextWithEmoji[] {
+ if (!text) {
+ return [];
+ }
+
+ const doesTextContainEmojis = new RegExp(CONST.REGEX.EMOJIS, CONST.REGEX.EMOJIS.flags.concat('g')).test(text);
+
+ if (!doesTextContainEmojis) {
+ return [];
+ }
+
+ // The regex needs to be cloned because `exec()` is a stateful operation and maintains the state inside
+ // the regex variable itself, so we must have an independent instance for each function's call.
+ const emojisRegex = new RegExp(CONST.REGEX.EMOJIS, CONST.REGEX.EMOJIS.flags.concat('g'));
+
+ const splitText: TextWithEmoji[] = [];
+ let regexResult: RegExpExecArray | null;
+ let lastMatchIndexEnd = 0;
+
+ do {
+ regexResult = emojisRegex.exec(text);
+
+ if (regexResult?.indices) {
+ const matchIndexStart = regexResult.indices[0][0];
+ const matchIndexEnd = regexResult.indices[0][1];
+
+ if (matchIndexStart > lastMatchIndexEnd) {
+ splitText.push({
+ text: text.slice(lastMatchIndexEnd, matchIndexStart),
+ isEmoji: false,
+ });
+ }
+
+ splitText.push({
+ text: text.slice(matchIndexStart, matchIndexEnd),
+ isEmoji: true,
+ });
+
+ lastMatchIndexEnd = matchIndexEnd;
+ }
+ } while (regexResult !== null);
+
+ if (lastMatchIndexEnd < text.length) {
+ splitText.push({
+ text: text.slice(lastMatchIndexEnd, text.length),
+ isEmoji: false,
+ });
+ }
+
+ return splitText;
+}
+
+function getProcessedText(processedTextArray: TextWithEmoji[], style: StyleProp): Array {
+ return processedTextArray.map(({text, isEmoji}, index) =>
+ isEmoji ? (
+
+ {text}
+
+ ) : (
+ text
+ ),
+ );
+}
+
export type {HeaderIndice, EmojiPickerList, EmojiSpacer, EmojiPickerListItem};
export {
@@ -602,6 +678,7 @@ export {
findEmojiByCode,
getEmojiName,
getLocalizedEmojiName,
+ getProcessedText,
getHeaderEmojis,
mergeEmojisWithFrequentlyUsedEmojis,
containsOnlyEmojis,
@@ -620,4 +697,5 @@ export {
hasAccountIDEmojiReacted,
getRemovedSkinToneEmoji,
getSpacersIndexes,
+ splitTextWithEmojis,
};
diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts
index 0367325db6b1..9c49c5f2ced6 100644
--- a/src/libs/ValidationUtils.ts
+++ b/src/libs/ValidationUtils.ts
@@ -49,7 +49,7 @@ function isValidAddress(value: FormValue): boolean {
return false;
}
- if (!CONST.REGEX.ANY_VALUE.test(value) || value.match(CONST.REGEX.EMOJIS)) {
+ if (!CONST.REGEX.ANY_VALUE.test(value) || value.match(CONST.REGEX.ALL_EMOJIS)) {
return false;
}
@@ -343,7 +343,7 @@ function isValidRoutingNumber(routingNumber: string): boolean {
* Checks that the provided name doesn't contain any emojis
*/
function isValidCompanyName(name: string) {
- return !name.match(CONST.REGEX.EMOJIS);
+ return !name.match(CONST.REGEX.ALL_EMOJIS);
}
function isValidReportName(name: string) {
diff --git a/src/pages/home/report/ReportActionItemFragment.tsx b/src/pages/home/report/ReportActionItemFragment.tsx
index 787904d72b81..05cb657b1e54 100644
--- a/src/pages/home/report/ReportActionItemFragment.tsx
+++ b/src/pages/home/report/ReportActionItemFragment.tsx
@@ -2,7 +2,6 @@ import React, {memo} from 'react';
import type {StyleProp, TextStyle} from 'react-native';
import RenderHTML from '@components/RenderHTML';
import Text from '@components/Text';
-import UserDetailsTooltip from '@components/UserDetailsTooltip';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
@@ -15,6 +14,7 @@ import type {Message} from '@src/types/onyx/ReportAction';
import type ReportActionName from '@src/types/onyx/ReportActionName';
import AttachmentCommentFragment from './comment/AttachmentCommentFragment';
import TextCommentFragment from './comment/TextCommentFragment';
+import ReportActionItemMessageHeaderSender from './ReportActionItemMessageHeaderSender';
type ReportActionItemFragmentProps = {
/** Users accountID */
@@ -160,18 +160,13 @@ function ReportActionItemFragment({
}
return (
-
-
- {fragment?.text}
-
-
+ fragmentText={fragment.text}
+ actorIcon={actorIcon}
+ isSingleLine={isSingleLine}
+ />
);
}
case 'LINK':
diff --git a/src/pages/home/report/ReportActionItemMessageHeaderSender/index.native.tsx b/src/pages/home/report/ReportActionItemMessageHeaderSender/index.native.tsx
new file mode 100644
index 000000000000..9a752c3a9007
--- /dev/null
+++ b/src/pages/home/report/ReportActionItemMessageHeaderSender/index.native.tsx
@@ -0,0 +1,30 @@
+import React, {useMemo} from 'react';
+import Text from '@components/Text';
+import UserDetailsTooltip from '@components/UserDetailsTooltip';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as EmojiUtils from '@libs/EmojiUtils';
+import type ReportActionItemMessageHeaderSenderProps from './types';
+
+function ReportActionItemMessageHeaderSender({fragmentText, accountID, delegateAccountID, actorIcon, isSingleLine}: ReportActionItemMessageHeaderSenderProps) {
+ const styles = useThemeStyles();
+ const processedTextArray = useMemo(() => EmojiUtils.splitTextWithEmojis(fragmentText), [fragmentText]);
+
+ return (
+
+
+ {processedTextArray.length !== 0 ? EmojiUtils.getProcessedText(processedTextArray, [styles.emojisWithTextFontSize, styles.emojisWithTextFontFamily]) : fragmentText}
+
+
+ );
+}
+
+ReportActionItemMessageHeaderSender.displayName = 'ReportActionItemMessageHeaderSender';
+
+export default ReportActionItemMessageHeaderSender;
diff --git a/src/pages/home/report/ReportActionItemMessageHeaderSender/index.tsx b/src/pages/home/report/ReportActionItemMessageHeaderSender/index.tsx
new file mode 100644
index 000000000000..d5602dbedfae
--- /dev/null
+++ b/src/pages/home/report/ReportActionItemMessageHeaderSender/index.tsx
@@ -0,0 +1,30 @@
+import React, {useMemo} from 'react';
+import Text from '@components/Text';
+import UserDetailsTooltip from '@components/UserDetailsTooltip';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as EmojiUtils from '@libs/EmojiUtils';
+import type ReportActionItemMessageHeaderSenderProps from './types';
+
+function ReportActionItemMessageHeaderSender({fragmentText, accountID, delegateAccountID, actorIcon, isSingleLine}: ReportActionItemMessageHeaderSenderProps) {
+ const styles = useThemeStyles();
+ const processedTextArray = useMemo(() => EmojiUtils.splitTextWithEmojis(fragmentText), [fragmentText]);
+
+ return (
+
+
+ {processedTextArray.length !== 0 ? EmojiUtils.getProcessedText(processedTextArray, styles.emojisWithTextFontSize) : fragmentText}
+
+
+ );
+}
+
+ReportActionItemMessageHeaderSender.displayName = 'ReportActionItemMessageHeaderSender';
+
+export default ReportActionItemMessageHeaderSender;
diff --git a/src/pages/home/report/ReportActionItemMessageHeaderSender/types.ts b/src/pages/home/report/ReportActionItemMessageHeaderSender/types.ts
new file mode 100644
index 000000000000..44a27de119e6
--- /dev/null
+++ b/src/pages/home/report/ReportActionItemMessageHeaderSender/types.ts
@@ -0,0 +1,20 @@
+import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
+
+type ReportActionItemMessageHeaderSenderProps = {
+ /** Text to display */
+ fragmentText: string;
+
+ /** Users accountID */
+ accountID: number;
+
+ /** Should this fragment be contained in a single line? */
+ isSingleLine?: boolean;
+
+ /** The accountID of the copilot who took this action on behalf of the user */
+ delegateAccountID?: number;
+
+ /** Actor icon */
+ actorIcon?: OnyxCommon.Icon;
+};
+
+export default ReportActionItemMessageHeaderSenderProps;
diff --git a/src/pages/home/report/comment/TextCommentFragment.tsx b/src/pages/home/report/comment/TextCommentFragment.tsx
index ab06a594a17f..913de87af4ac 100644
--- a/src/pages/home/report/comment/TextCommentFragment.tsx
+++ b/src/pages/home/report/comment/TextCommentFragment.tsx
@@ -1,6 +1,6 @@
import {Str} from 'expensify-common';
import isEmpty from 'lodash/isEmpty';
-import React, {memo, useEffect} from 'react';
+import React, {memo, useEffect, useMemo} from 'react';
import type {StyleProp, TextStyle} from 'react-native';
import Text from '@components/Text';
import ZeroWidthView from '@components/ZeroWidthView';
@@ -20,6 +20,7 @@ import type {OriginalMessageSource} from '@src/types/onyx/OriginalMessage';
import type {Message} from '@src/types/onyx/ReportAction';
import RenderCommentHTML from './RenderCommentHTML';
import shouldRenderAsText from './shouldRenderAsText';
+import TextWithEmojiFragment from './TextWithEmojiFragment';
type TextCommentFragmentProps = {
/** The reportAction's source */
@@ -52,6 +53,10 @@ function TextCommentFragment({fragment, styleAsDeleted, styleAsMuted = false, so
const {translate} = useLocalize();
const {shouldUseNarrowLayout} = useResponsiveLayout();
+ const message = isEmpty(iouMessage) ? text : iouMessage;
+
+ const processedTextArray = useMemo(() => EmojiUtils.splitTextWithEmojis(message), [message]);
+
useEffect(() => {
Performance.markEnd(CONST.TIMING.SEND_MESSAGE, {message: text});
Timing.end(CONST.TIMING.SEND_MESSAGE);
@@ -69,7 +74,10 @@ function TextCommentFragment({fragment, styleAsDeleted, styleAsMuted = false, so
if (containsOnlyEmojis) {
htmlContent = Str.replaceAll(htmlContent, '', '');
htmlContent = Str.replaceAll(htmlContent, '', '');
+ } else if (CONST.REGEX.ALL_EMOJIS.test(text ?? '')) {
+ htmlContent = Str.replaceAll(htmlWithDeletedTag, '', '');
}
+
let htmlWithTag = editedTag ? `${htmlContent}${editedTag}` : htmlContent;
if (styleAsMuted) {
@@ -84,26 +92,37 @@ function TextCommentFragment({fragment, styleAsDeleted, styleAsMuted = false, so
);
}
- const message = isEmpty(iouMessage) ? text : iouMessage;
-
return (
-
- {convertToLTR(message ?? '')}
-
+ {processedTextArray.length !== 0 && !containsOnlyEmojis ? (
+
+ ) : (
+
+ {convertToLTR(message ?? '')}
+
+ )}
{!!fragment?.isEdited && (
<>
EmojiUtils.splitTextWithEmojis(message), [message]);
+
+ return (
+
+ {processedTextArray.map(({text, isEmoji}, index) =>
+ isEmoji ? (
+
+ {text}
+
+ ) : (
+ convertToLTR(text)
+ ),
+ )}
+
+ );
+}
+
+TextWithEmojiFragment.displayName = 'TextWithEmojiFragment';
+
+export default TextWithEmojiFragment;
diff --git a/src/pages/home/report/comment/TextWithEmojiFragment/index.tsx b/src/pages/home/report/comment/TextWithEmojiFragment/index.tsx
new file mode 100644
index 000000000000..d19725da766d
--- /dev/null
+++ b/src/pages/home/report/comment/TextWithEmojiFragment/index.tsx
@@ -0,0 +1,33 @@
+import React, {useMemo} from 'react';
+import Text from '@components/Text';
+import useThemeStyles from '@hooks/useThemeStyles';
+import convertToLTR from '@libs/convertToLTR';
+import * as EmojiUtils from '@libs/EmojiUtils';
+import type TextWithEmojiFragmentProps from './types';
+
+function TextWithEmojiFragment({message = '', style}: TextWithEmojiFragmentProps) {
+ const styles = useThemeStyles();
+ const processedTextArray = useMemo(() => EmojiUtils.splitTextWithEmojis(message), [message]);
+
+ return (
+
+ {processedTextArray.map(({text, isEmoji}, index) =>
+ isEmoji ? (
+
+ {text}
+
+ ) : (
+ convertToLTR(text)
+ ),
+ )}
+
+ );
+}
+
+TextWithEmojiFragment.displayName = 'TextWithEmojiFragment';
+
+export default TextWithEmojiFragment;
diff --git a/src/pages/home/report/comment/TextWithEmojiFragment/types.ts b/src/pages/home/report/comment/TextWithEmojiFragment/types.ts
new file mode 100644
index 000000000000..243b02f1fd76
--- /dev/null
+++ b/src/pages/home/report/comment/TextWithEmojiFragment/types.ts
@@ -0,0 +1,11 @@
+import type {StyleProp, TextStyle} from 'react-native';
+
+type TextWithEmojiFragmentProps = {
+ /** The message to be displayed */
+ message?: string;
+
+ /** Any additional styles to apply */
+ style: StyleProp;
+};
+
+export default TextWithEmojiFragmentProps;
diff --git a/src/pages/settings/Profile/DisplayNamePage.tsx b/src/pages/settings/Profile/DisplayNamePage.tsx
index 90f7ca3abbd6..4c6211bc3e37 100644
--- a/src/pages/settings/Profile/DisplayNamePage.tsx
+++ b/src/pages/settings/Profile/DisplayNamePage.tsx
@@ -1,7 +1,6 @@
import React from 'react';
import {View} from 'react-native';
-import type {OnyxEntry} from 'react-native-onyx';
-import {withOnyx} from 'react-native-onyx';
+import {useOnyx} from 'react-native-onyx';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import type {FormInputErrors, FormOnyxValues} from '@components/Form/types';
@@ -22,11 +21,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import INPUT_IDS from '@src/types/form/DisplayNameForm';
-type DisplayNamePageOnyxProps = {
- isLoadingApp: OnyxEntry;
-};
-
-type DisplayNamePageProps = DisplayNamePageOnyxProps & WithCurrentUserPersonalDetailsProps;
+type DisplayNamePageProps = WithCurrentUserPersonalDetailsProps;
/**
* Submit form to update user's first and last name (and display name)
@@ -36,9 +31,10 @@ const updateDisplayName = (values: FormOnyxValues
@@ -114,6 +111,7 @@ function DisplayNamePage({isLoadingApp = true, currentUserPersonalDetails}: Disp
role={CONST.ROLE.PRESENTATION}
defaultValue={currentUserDetails.lastName ?? ''}
spellCheck={false}
+ isMarkdownEnabled
/>
@@ -124,10 +122,4 @@ function DisplayNamePage({isLoadingApp = true, currentUserPersonalDetails}: Disp
DisplayNamePage.displayName = 'DisplayNamePage';
-export default withCurrentUserPersonalDetails(
- withOnyx({
- isLoadingApp: {
- key: ONYXKEYS.IS_LOADING_APP,
- },
- })(DisplayNamePage),
-);
+export default withCurrentUserPersonalDetails(DisplayNamePage);
diff --git a/src/pages/workspace/WorkspacesListRow.tsx b/src/pages/workspace/WorkspacesListRow.tsx
index e202baf9b39d..5569d4fb3d70 100644
--- a/src/pages/workspace/WorkspacesListRow.tsx
+++ b/src/pages/workspace/WorkspacesListRow.tsx
@@ -12,6 +12,7 @@ import Text from '@components/Text';
import ThreeDotsMenu from '@components/ThreeDotsMenu';
import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails';
import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails';
+import WorkspacesListRowDisplayName from '@components/WorkspacesListRowDisplayName';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
@@ -213,12 +214,10 @@ function WorkspacesListRow({
containerStyles={styles.workspaceOwnerAvatarWrapper}
/>
-
- {PersonalDetailsUtils.getDisplayNameOrDefault(ownerDetails)}
-
+
textAlign: 'left',
},
+ verticalAlignTopText: {
+ verticalAlign: 'text-top',
+ },
verticalAlignTop: {
verticalAlign: 'top',
},
@@ -415,7 +418,7 @@ const styles = (theme: ThemeColors) =>
color: theme.text,
...FontUtils.fontFamily.platform.EXP_NEUE_BOLD,
fontSize: variables.fontSizeSmall,
- lineHeight: variables.lineHeightSmall,
+ lineHeight: variables.lineHeightNormal,
},
textMicroSupporting: {
@@ -1746,6 +1749,31 @@ const styles = (theme: ThemeColors) =>
lineHeight: variables.fontSizeOnlyEmojisHeight,
},
+ emojisWithTextFontSizeAligned: {
+ fontSize: variables.fontSizeEmojisWithinText,
+ marginVertical: -7,
+ },
+
+ emojisFontFamily: {
+ fontFamily: FontUtils.fontFamily.platform.SYSTEM.fontFamily,
+ },
+
+ emojisWithTextFontSize: {
+ fontSize: variables.fontSizeEmojisWithinText,
+ },
+
+ emojisWithTextFontFamily: {
+ fontFamily: FontUtils.fontFamily.platform.SYSTEM.fontFamily,
+ },
+
+ emojisWithTextLineHeight: {
+ lineHeight: variables.lineHeightXLarge,
+ },
+
+ initialSettingsUsernameEmoji: {
+ fontSize: variables.fontSizeUsernameEmoji,
+ },
+
createMenuPositionSidebar: (windowHeight: number) =>
({
horizontal: 18,
diff --git a/src/styles/utils/emojiDefaultStyles/index.ts b/src/styles/utils/emojiDefaultStyles/index.ts
index 88c42e7e95d1..45880b46005d 100644
--- a/src/styles/utils/emojiDefaultStyles/index.ts
+++ b/src/styles/utils/emojiDefaultStyles/index.ts
@@ -6,7 +6,7 @@ import type EmojiDefaultStyles from './types';
const emojiDefaultStyles: EmojiDefaultStyles = {
fontStyle: 'normal',
fontWeight: FontUtils.fontWeight.normal,
- ...display.dInlineFlex,
+ ...display.dInline,
};
export default emojiDefaultStyles;
diff --git a/src/styles/variables.ts b/src/styles/variables.ts
index 5a8927ede6d0..8318b58012a9 100644
--- a/src/styles/variables.ts
+++ b/src/styles/variables.ts
@@ -48,8 +48,6 @@ export default {
defaultAvatarPreviewSize: 360,
fabBottom: 25,
breadcrumbsFontSize: getValueUsingPixelRatio(19, 32),
- fontSizeOnlyEmojis: 30,
- fontSizeOnlyEmojisHeight: 35,
fontSizeSmall: getValueUsingPixelRatio(11, 17),
fontSizeExtraSmall: 9,
fontSizeLabel: getValueUsingPixelRatio(13, 19),
@@ -89,8 +87,6 @@ export default {
sidebarAvatarSize: 28,
iconHeader: 48,
iconSection: 68,
- emojiSize: 20,
- emojiLineHeight: 28,
iouAmountTextSize: 40,
extraSmallMobileResponsiveWidthBreakpoint: 320,
extraSmallMobileResponsiveHeightBreakpoint: 667,
@@ -218,6 +214,14 @@ export default {
onboardingModalWidth: 500,
fontSizeToWidthRatio: getValueUsingPixelRatio(0.8, 1),
+ // Emoji related variables
+ fontSizeOnlyEmojis: 30,
+ fontSizeOnlyEmojisHeight: 35,
+ emojiSize: 20,
+ emojiLineHeight: 28,
+ fontSizeUsernameEmoji: 19,
+ fontSizeEmojisWithinText: getValueUsingPixelRatio(17, 19),
+
// 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
googleEmptyListViewHeight: 14,