Skip to content

Commit 697c90e

Browse files
committed
feat: implement portal-style overlay
1 parent 0afc592 commit 697c90e

File tree

13 files changed

+383
-221
lines changed

13 files changed

+383
-221
lines changed

examples/SampleApp/ios/Podfile.lock

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3165,6 +3165,65 @@ PODS:
31653165
- ReactCommon/turbomodule/core
31663166
- SocketRocket
31673167
- Yoga
3168+
- Teleport (0.5.3):
3169+
- boost
3170+
- DoubleConversion
3171+
- fast_float
3172+
- fmt
3173+
- glog
3174+
- hermes-engine
3175+
- RCT-Folly
3176+
- RCT-Folly/Fabric
3177+
- RCTRequired
3178+
- RCTTypeSafety
3179+
- React-Core
3180+
- React-debug
3181+
- React-Fabric
3182+
- React-featureflags
3183+
- React-graphics
3184+
- React-hermes
3185+
- React-ImageManager
3186+
- React-jsi
3187+
- React-NativeModulesApple
3188+
- React-RCTFabric
3189+
- React-renderercss
3190+
- React-rendererdebug
3191+
- React-utils
3192+
- ReactCodegen
3193+
- ReactCommon/turbomodule/bridging
3194+
- ReactCommon/turbomodule/core
3195+
- SocketRocket
3196+
- Teleport/common (= 0.5.3)
3197+
- Yoga
3198+
- Teleport/common (0.5.3):
3199+
- boost
3200+
- DoubleConversion
3201+
- fast_float
3202+
- fmt
3203+
- glog
3204+
- hermes-engine
3205+
- RCT-Folly
3206+
- RCT-Folly/Fabric
3207+
- RCTRequired
3208+
- RCTTypeSafety
3209+
- React-Core
3210+
- React-debug
3211+
- React-Fabric
3212+
- React-featureflags
3213+
- React-graphics
3214+
- React-hermes
3215+
- React-ImageManager
3216+
- React-jsi
3217+
- React-NativeModulesApple
3218+
- React-RCTFabric
3219+
- React-renderercss
3220+
- React-rendererdebug
3221+
- React-utils
3222+
- ReactCodegen
3223+
- ReactCommon/turbomodule/bridging
3224+
- ReactCommon/turbomodule/core
3225+
- SocketRocket
3226+
- Yoga
31683227
- Yoga (0.0.0)
31693228

31703229
DEPENDENCIES:
@@ -3269,6 +3328,7 @@ DEPENDENCIES:
32693328
- RNWorklets (from `../node_modules/react-native-worklets`)
32703329
- SocketRocket (~> 0.7.1)
32713330
- stream-chat-react-native (from `../node_modules/stream-chat-react-native`)
3331+
- Teleport (from `../node_modules/react-native-teleport`)
32723332
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
32733333

32743334
SPEC REPOS:
@@ -3490,6 +3550,8 @@ EXTERNAL SOURCES:
34903550
:path: "../node_modules/react-native-worklets"
34913551
stream-chat-react-native:
34923552
:path: "../node_modules/stream-chat-react-native"
3553+
Teleport:
3554+
:path: "../node_modules/react-native-teleport"
34933555
Yoga:
34943556
:path: "../node_modules/react-native/ReactCommon/yoga"
34953557

@@ -3522,7 +3584,7 @@ SPEC CHECKSUMS:
35223584
op-sqlite: b9a4028bef60145d7b98fbbc4341c83420cdcfd5
35233585
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
35243586
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
3525-
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
3587+
RCT-Folly: 59ec0ac1f2f39672a0c6e6cecdd39383b764646f
35263588
RCTDeprecation: 300c5eb91114d4339b0bb39505d0f4824d7299b7
35273589
RCTRequired: e0446b01093475b7082fbeee5d1ef4ad1fe20ac4
35283590
RCTTypeSafety: cb974efcdc6695deedf7bf1eb942f2a0603a063f
@@ -3612,6 +3674,7 @@ SPEC CHECKSUMS:
36123674
SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d
36133675
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
36143676
stream-chat-react-native: 7a042480d22a8a87aaee6186bf2f1013af017d3a
3677+
Teleport: 98d5a14f7f1a7b47b0c0541b00c697a59ac2682d
36153678
Yoga: ce248fb32065c9b00451491b06607f1c25b2f1ed
36163679

36173680
PODFILE CHECKSUM: 4f662370295f8f9cee909f1a4c59a614999a209d

examples/SampleApp/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"react-native-screens": "^4.11.1",
5656
"react-native-share": "^12.0.11",
5757
"react-native-svg": "^15.12.0",
58+
"react-native-teleport": "^0.5.3",
5859
"react-native-video": "^6.16.1",
5960
"react-native-worklets": "^0.4.1",
6061
"stream-chat-react-native": "link:../../package/native-package",

examples/SampleApp/yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7729,6 +7729,11 @@ react-native-svg@^15.12.0:
77297729
css-tree "^1.1.3"
77307730
warn-once "0.1.1"
77317731

7732+
react-native-teleport@^0.5.3:
7733+
version "0.5.3"
7734+
resolved "https://registry.yarnpkg.com/react-native-teleport/-/react-native-teleport-0.5.3.tgz#c29fb09f4f0faf1d6d6aa479b1a0792f3a9373b6"
7735+
integrity sha512-aWAui0yH0UqqC3Z3wnY3VLXZw30ST4Ikdx9ZzF7YyeVJdhfcDO/JjQb2D3uHnbarttLQojdblQLYoQzeAO07sg==
7736+
77327737
react-native-url-polyfill@^2.0.0:
77337738
version "2.0.0"
77347739
resolved "https://registry.yarnpkg.com/react-native-url-polyfill/-/react-native-url-polyfill-2.0.0.tgz#db714520a2985cff1d50ab2e66279b9f91ffd589"

package/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@
157157
"react-native-reanimated": "3.18.0",
158158
"react-native-safe-area-context": "^5.6.1",
159159
"react-native-svg": "15.12.0",
160+
"react-native-teleport": "^0.5.3",
160161
"react-test-renderer": "19.1.0",
161162
"rimraf": "^6.0.1",
162163
"typescript": "5.8.3",

package/src/components/Message/Message.tsx

Lines changed: 95 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1-
import React, { useMemo, useState } from 'react';
2-
import { GestureResponderEvent, Keyboard, StyleProp, View, ViewStyle } from 'react-native';
1+
import React, { useMemo, useRef, useState } from 'react';
2+
import {
3+
findNodeHandle,
4+
GestureResponderEvent,
5+
Keyboard,
6+
Pressable,
7+
StyleProp,
8+
TouchableWithoutFeedback,
9+
UIManager,
10+
View,
11+
ViewStyle,
12+
} from 'react-native';
13+
14+
import { Portal } from 'react-native-teleport';
315

416
import type { Attachment, LocalMessage, UserResponse } from 'stream-chat';
517

@@ -25,6 +37,7 @@ import {
2537
useMessageComposerAPIContext,
2638
} from '../../contexts/messageComposerContext/MessageComposerAPIContext';
2739
import { MessageContextValue, MessageProvider } from '../../contexts/messageContext/MessageContext';
40+
import { useMessageListItemContext } from '../../contexts/messageListItemContext/MessageListItemContext';
2841
import {
2942
MessagesContextValue,
3043
useMessagesContext,
@@ -60,6 +73,15 @@ export type TouchableEmitter =
6073

6174
export type TextMentionTouchableHandlerAdditionalInfo = { user?: UserResponse };
6275

76+
function measureInWindow(node: any): Promise<{ x: number; y: number; w: number; h: number }> {
77+
return new Promise((resolve, reject) => {
78+
const handle = findNodeHandle(node);
79+
if (!handle) return reject(new Error('No native handle'));
80+
81+
UIManager.measureInWindow(handle, (x, y, w, h) => resolve({ x, y, w, h }));
82+
});
83+
}
84+
6385
export type TextMentionTouchableHandlerPayload = {
6486
emitter: 'textMention';
6587
additionalInfo?: TextMentionTouchableHandlerAdditionalInfo;
@@ -219,7 +241,12 @@ export type MessagePropsWithContext = Pick<
219241
* each individual Message component.
220242
*/
221243
const MessageWithContext = (props: MessagePropsWithContext) => {
222-
const [messageOverlayVisible, setMessageOverlayVisible] = useState(false);
244+
const [messageOverlayVisible, setMessageOverlayVisible] = useState<{
245+
x: number;
246+
y: number;
247+
w: number;
248+
h: number;
249+
} | null>(null);
223250
const [isErrorInMessage, setIsErrorInMessage] = useState(false);
224251
const [showMessageReactions, setShowMessageReactions] = useState(true);
225252
const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false);
@@ -293,21 +320,25 @@ const MessageWithContext = (props: MessagePropsWithContext) => {
293320
const { client } = chatContext;
294321
const {
295322
theme: {
296-
colors: { targetedMessageBackground, bg_gradient_start },
323+
colors: { targetedMessageBackground, bg_gradient_start, overlay },
297324
messageSimple: { targetedMessageContainer, unreadUnderlayColor = bg_gradient_start, wrapper },
298325
screenPadding,
299326
},
300327
} = useTheme();
301328

302329
const showMessageOverlay = async (showMessageReactions = false, selectedReaction?: string) => {
303330
await dismissKeyboard();
331+
const layout = await measureInWindow(messageWrapperRef.current);
304332
setShowMessageReactions(showMessageReactions);
305-
setMessageOverlayVisible(true);
333+
setMessageOverlayVisible(layout);
306334
setSelectedReaction(selectedReaction);
307335
};
308336

337+
const { setNativeScrollability } = useMessageListItemContext();
338+
309339
const dismissOverlay = () => {
310-
setMessageOverlayVisible(false);
340+
setNativeScrollability(true);
341+
setMessageOverlayVisible(null);
311342
};
312343

313344
const actionsEnabled =
@@ -620,7 +651,10 @@ const MessageWithContext = (props: MessagePropsWithContext) => {
620651
unpinMessage: handleTogglePinMessage,
621652
};
622653

654+
const messageWrapperRef = useRef<View>(null);
655+
623656
const onLongPress = () => {
657+
setNativeScrollability(false);
624658
if (hasAttachmentActions || isBlockedMessage(message) || !enableLongPress) {
625659
return;
626660
}
@@ -759,20 +793,64 @@ const MessageWithContext = (props: MessagePropsWithContext) => {
759793
]}
760794
testID='message-wrapper'
761795
>
762-
<MessageSimple />
763-
{isBounceDialogOpen ? (
764-
<MessageBounce setIsBounceDialogOpen={setIsBounceDialogOpen} />
765-
) : null}
766796
{messageOverlayVisible ? (
767-
<MessageMenu
768-
dismissOverlay={dismissOverlay}
769-
handleReaction={ownCapabilities.sendReaction ? handleReaction : undefined}
770-
messageActions={messageActions}
771-
selectedReaction={selectedReaction}
772-
showMessageReactions={showMessageReactions}
773-
visible={messageOverlayVisible}
797+
<View
798+
style={{
799+
width: messageOverlayVisible.w,
800+
height: messageOverlayVisible.h,
801+
}}
774802
/>
775803
) : null}
804+
<Portal hostName={messageOverlayVisible ? 'overlay' : undefined}>
805+
<View
806+
style={
807+
messageOverlayVisible
808+
? [
809+
{
810+
flex: 1,
811+
justifyContent: 'flex-end',
812+
},
813+
{ backgroundColor: overlay },
814+
]
815+
: null
816+
}
817+
>
818+
{messageOverlayVisible ? (
819+
<TouchableWithoutFeedback onPress={dismissOverlay} style={{ flex: 1 }}>
820+
<View style={{ flex: 1 }} />
821+
</TouchableWithoutFeedback>
822+
) : null}
823+
<View
824+
style={
825+
messageOverlayVisible
826+
? {
827+
position: 'absolute',
828+
top: messageOverlayVisible.y,
829+
...(isMyMessage
830+
? { right: messageOverlayVisible.x }
831+
: { left: messageOverlayVisible.x }),
832+
}
833+
: null
834+
}
835+
>
836+
<MessageSimple ref={messageWrapperRef} />
837+
</View>
838+
</View>
839+
</Portal>
840+
{isBounceDialogOpen ? (
841+
<MessageBounce setIsBounceDialogOpen={setIsBounceDialogOpen} />
842+
) : null}
843+
{/*{messageOverlayVisible ? (*/}
844+
{/*<MessageMenu*/}
845+
{/* dismissOverlay={dismissOverlay}*/}
846+
{/* handleReaction={ownCapabilities.sendReaction ? handleReaction : undefined}*/}
847+
{/* layout={messageOverlayVisible}*/}
848+
{/* messageActions={messageActions}*/}
849+
{/* selectedReaction={selectedReaction}*/}
850+
{/* showMessageReactions={showMessageReactions}*/}
851+
{/* visible={!!messageOverlayVisible}*/}
852+
{/*/>*/}
853+
{/*) : null}*/}
776854
</View>
777855
</View>
778856
</MessageProvider>

package/src/components/Message/MessageSimple/MessageContent.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useMemo } from 'react';
1+
import React, { useMemo, useState } from 'react';
22
import {
33
AnimatableNumericValue,
44
ColorValue,
@@ -121,6 +121,7 @@ export type MessageContentPropsWithContext = Pick<
121121
* Child of MessageSimple that displays a message's content
122122
*/
123123
const MessageContentWithContext = (props: MessageContentPropsWithContext) => {
124+
const [longPressFired, setLongPressFired] = useState(false);
124125
const {
125126
additionalPressableProps,
126127
alignment,
@@ -243,6 +244,7 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => {
243244
<Pressable
244245
disabled={preventPress}
245246
onLongPress={(event) => {
247+
setLongPressFired(true);
246248
if (onLongPress) {
247249
onLongPress({
248250
emitter: 'messageContent',
@@ -266,7 +268,10 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => {
266268
});
267269
}
268270
}}
269-
style={({ pressed }) => [{ opacity: pressed ? 0.5 : 1 }, container]}
271+
onPressOut={() => {
272+
setLongPressFired(false);
273+
}}
274+
style={({ pressed }) => [{ opacity: pressed && !longPressFired ? 0.5 : 1 }, container]}
270275
{...additionalPressableProps}
271276
>
272277
<View onLayout={onLayout} style={wrapper}>

0 commit comments

Comments
 (0)