react-native-gifted-chat 3.2.3 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +450 -156
- package/package.json +9 -8
- package/src/Bubble/index.tsx +34 -2
- package/src/Bubble/types.ts +17 -4
- package/src/Day/index.tsx +2 -2
- package/src/Day/types.ts +3 -2
- package/src/GiftedChat/index.tsx +109 -23
- package/src/GiftedChat/types.ts +9 -3
- package/src/InputToolbar.tsx +62 -8
- package/src/Message/index.tsx +181 -21
- package/src/Message/types.ts +4 -0
- package/src/MessageReply.tsx +160 -0
- package/src/MessagesContainer/components/Item/index.tsx +17 -3
- package/src/MessagesContainer/index.tsx +16 -11
- package/src/MessagesContainer/types.ts +26 -3
- package/src/Models.ts +3 -0
- package/src/Reply/index.ts +1 -0
- package/src/Reply/types.ts +80 -0
- package/src/ReplyPreview.tsx +132 -0
- package/src/Send.tsx +8 -3
- package/src/__tests__/MessageReply.test.tsx +54 -0
- package/src/__tests__/ReplyPreview.test.tsx +41 -0
- package/src/__tests__/__snapshots__/GiftedChat.test.tsx.snap +69 -42
- package/src/__tests__/__snapshots__/InputToolbar.test.tsx.snap +11 -15
- package/src/__tests__/__snapshots__/MessageImage.test.tsx.snap +24 -18
- package/src/__tests__/__snapshots__/MessageReply.test.tsx.snap +181 -0
- package/src/__tests__/__snapshots__/ReplyPreview.test.tsx.snap +403 -0
- package/src/__tests__/__snapshots__/Send.test.tsx.snap +3 -0
- package/src/components/MessageReply.tsx +156 -0
- package/src/components/ReplyPreview.tsx +230 -0
- package/src/index.ts +6 -1
- package/src/types.ts +17 -16
- package/src/utils.ts +11 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-gifted-chat",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "The most complete chat UI for React Native",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"android",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"src"
|
|
30
30
|
],
|
|
31
31
|
"scripts": {
|
|
32
|
-
"lint": "yarn eslint src",
|
|
33
|
-
"lint:fix": "yarn eslint --cache --fix",
|
|
32
|
+
"lint": "yarn eslint src example",
|
|
33
|
+
"lint:fix": "yarn eslint --cache --fix src example",
|
|
34
34
|
"prepublishOnly": "yarn lint && yarn test",
|
|
35
35
|
"start": "cd example && expo start",
|
|
36
36
|
"start:web": "cd example && expo start -w --dev",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"@types/lodash.isequal": "^4.5.8",
|
|
53
53
|
"dayjs": "^1.11.19",
|
|
54
54
|
"lodash.isequal": "^4.5.0",
|
|
55
|
-
"react-native-zoom-reanimated": "^1.
|
|
55
|
+
"react-native-zoom-reanimated": "^1.5.2"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@babel/core": "^7.28.5",
|
|
@@ -92,10 +92,11 @@
|
|
|
92
92
|
"react": "19.1.0",
|
|
93
93
|
"react-dom": "19.1.0",
|
|
94
94
|
"react-native": "0.81.5",
|
|
95
|
-
"react-native-gesture-handler": "
|
|
96
|
-
"react-native-keyboard-controller": "
|
|
97
|
-
"react-native-reanimated": "
|
|
98
|
-
"react-native-safe-area-context": "
|
|
95
|
+
"react-native-gesture-handler": "~2.28.0",
|
|
96
|
+
"react-native-keyboard-controller": "1.18.5",
|
|
97
|
+
"react-native-reanimated": "~4.1.1",
|
|
98
|
+
"react-native-safe-area-context": "~5.6.2",
|
|
99
|
+
"react-native-worklets": "0.5.1",
|
|
99
100
|
"react-test-renderer": "19.1.0",
|
|
100
101
|
"typescript": "^5.9.3"
|
|
101
102
|
},
|
package/src/Bubble/index.tsx
CHANGED
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
} from 'react-native'
|
|
6
6
|
|
|
7
7
|
import { Text } from 'react-native-gesture-handler'
|
|
8
|
+
|
|
9
|
+
import { MessageReply } from '../components/MessageReply'
|
|
8
10
|
import { useChatContext } from '../GiftedChatContext'
|
|
9
11
|
import { MessageAudio } from '../MessageAudio'
|
|
10
12
|
import { MessageImage } from '../MessageImage'
|
|
@@ -12,10 +14,8 @@ import { MessageText } from '../MessageText'
|
|
|
12
14
|
import { MessageVideo } from '../MessageVideo'
|
|
13
15
|
import { IMessage } from '../Models'
|
|
14
16
|
import { QuickReplies } from '../QuickReplies'
|
|
15
|
-
|
|
16
17
|
import { getStyleWithPosition } from '../styles'
|
|
17
18
|
import { Time } from '../Time'
|
|
18
|
-
|
|
19
19
|
import { isSameUser, isSameDay, renderComponentOrElement } from '../utils'
|
|
20
20
|
import styles from './styles'
|
|
21
21
|
import { BubbleProps, RenderMessageTextProps } from './types'
|
|
@@ -319,10 +319,41 @@ export const Bubble = <TMessage extends IMessage = IMessage>(props: BubbleProps<
|
|
|
319
319
|
return null
|
|
320
320
|
}, [props])
|
|
321
321
|
|
|
322
|
+
const renderMessageReply = useCallback(() => {
|
|
323
|
+
if (!currentMessage?.replyMessage)
|
|
324
|
+
return null
|
|
325
|
+
|
|
326
|
+
const { messageReply } = props
|
|
327
|
+
|
|
328
|
+
const messageReplyProps = {
|
|
329
|
+
replyMessage: currentMessage.replyMessage,
|
|
330
|
+
currentMessage,
|
|
331
|
+
position,
|
|
332
|
+
onPress: messageReply?.onPress,
|
|
333
|
+
containerStyle: position === 'left'
|
|
334
|
+
? messageReply?.containerStyleLeft ?? messageReply?.containerStyle
|
|
335
|
+
: messageReply?.containerStyleRight ?? messageReply?.containerStyle,
|
|
336
|
+
textStyle: position === 'left'
|
|
337
|
+
? messageReply?.textStyleLeft ?? messageReply?.textStyle
|
|
338
|
+
: messageReply?.textStyleRight ?? messageReply?.textStyle,
|
|
339
|
+
imageStyle: messageReply?.imageStyle,
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (messageReply?.renderMessageReply)
|
|
343
|
+
return renderComponentOrElement(messageReply.renderMessageReply, messageReplyProps)
|
|
344
|
+
|
|
345
|
+
return <MessageReply {...messageReplyProps} />
|
|
346
|
+
}, [
|
|
347
|
+
props,
|
|
348
|
+
currentMessage,
|
|
349
|
+
position,
|
|
350
|
+
])
|
|
351
|
+
|
|
322
352
|
const renderBubbleContent = useCallback(() => {
|
|
323
353
|
return (
|
|
324
354
|
<>
|
|
325
355
|
{!props.isCustomViewBottom && renderCustomView()}
|
|
356
|
+
{renderMessageReply()}
|
|
326
357
|
{renderMessageImage()}
|
|
327
358
|
{renderMessageVideo()}
|
|
328
359
|
{renderMessageAudio()}
|
|
@@ -331,6 +362,7 @@ export const Bubble = <TMessage extends IMessage = IMessage>(props: BubbleProps<
|
|
|
331
362
|
</>
|
|
332
363
|
)
|
|
333
364
|
}, [
|
|
365
|
+
renderMessageReply,
|
|
334
366
|
renderCustomView,
|
|
335
367
|
renderMessageImage,
|
|
336
368
|
renderMessageVideo,
|
package/src/Bubble/types.ts
CHANGED
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
TextStyle,
|
|
6
6
|
Pressable,
|
|
7
7
|
} from 'react-native'
|
|
8
|
+
|
|
9
|
+
import { MessageReplyProps } from '../components/MessageReply'
|
|
8
10
|
import { MessageImageProps } from '../MessageImage'
|
|
9
11
|
import { MessageTextProps } from '../MessageText'
|
|
10
12
|
import {
|
|
@@ -12,11 +14,13 @@ import {
|
|
|
12
14
|
IMessage,
|
|
13
15
|
LeftRightStyle,
|
|
14
16
|
Reply,
|
|
17
|
+
ReplyMessage,
|
|
15
18
|
Omit,
|
|
16
19
|
MessageVideoProps,
|
|
17
20
|
MessageAudioProps,
|
|
18
21
|
} from '../Models'
|
|
19
22
|
import { QuickRepliesProps } from '../QuickReplies'
|
|
23
|
+
import { MessageReplyStyleProps } from '../Reply'
|
|
20
24
|
import { TimeProps } from '../Time'
|
|
21
25
|
|
|
22
26
|
|
|
@@ -44,6 +48,13 @@ export type RenderMessageTextProps<TMessage extends IMessage> = Omit<
|
|
|
44
48
|
> &
|
|
45
49
|
MessageTextProps<TMessage>
|
|
46
50
|
|
|
51
|
+
/** Props for message reply functionality in bubble */
|
|
52
|
+
export interface BubbleReplyProps<TMessage extends IMessage> extends MessageReplyStyleProps {
|
|
53
|
+
/** Custom render for message reply; rendered on top of message content */
|
|
54
|
+
renderMessageReply?: (props: MessageReplyProps<TMessage>) => React.ReactNode
|
|
55
|
+
/** Callback when message reply is pressed */
|
|
56
|
+
onPress?: (replyMessage: ReplyMessage) => void
|
|
57
|
+
}
|
|
47
58
|
|
|
48
59
|
export interface BubbleProps<TMessage extends IMessage> {
|
|
49
60
|
user?: User
|
|
@@ -71,13 +82,13 @@ export interface BubbleProps<TMessage extends IMessage> {
|
|
|
71
82
|
onLongPressMessage?: (context?: unknown, message?: unknown) => void
|
|
72
83
|
onQuickReply?: (replies: Reply[]) => void
|
|
73
84
|
renderMessageImage?: (
|
|
74
|
-
props: RenderMessageImageProps<TMessage
|
|
85
|
+
props: RenderMessageImageProps<TMessage>
|
|
75
86
|
) => React.ReactNode
|
|
76
87
|
renderMessageVideo?: (
|
|
77
|
-
props: RenderMessageVideoProps<TMessage
|
|
88
|
+
props: RenderMessageVideoProps<TMessage>
|
|
78
89
|
) => React.ReactNode
|
|
79
90
|
renderMessageAudio?: (
|
|
80
|
-
props: RenderMessageAudioProps<TMessage
|
|
91
|
+
props: RenderMessageAudioProps<TMessage>
|
|
81
92
|
) => React.ReactNode
|
|
82
93
|
renderMessageText?: (props: RenderMessageTextProps<TMessage>) => React.ReactNode
|
|
83
94
|
renderCustomView?: (bubbleProps: BubbleProps<TMessage>) => React.ReactNode
|
|
@@ -86,6 +97,8 @@ export interface BubbleProps<TMessage extends IMessage> {
|
|
|
86
97
|
renderUsername?: (user?: TMessage['user']) => React.ReactNode
|
|
87
98
|
renderQuickReplySend?: () => React.ReactNode
|
|
88
99
|
renderQuickReplies?: (
|
|
89
|
-
quickReplies: QuickRepliesProps<TMessage
|
|
100
|
+
quickReplies: QuickRepliesProps<TMessage>
|
|
90
101
|
) => React.ReactNode
|
|
102
|
+
/** Message reply configuration */
|
|
103
|
+
messageReply?: BubbleReplyProps<TMessage>
|
|
91
104
|
}
|
package/src/Day/index.tsx
CHANGED
|
@@ -25,7 +25,7 @@ export function Day ({
|
|
|
25
25
|
createdAt,
|
|
26
26
|
containerStyle,
|
|
27
27
|
wrapperStyle,
|
|
28
|
-
|
|
28
|
+
textProps,
|
|
29
29
|
}: DayProps) {
|
|
30
30
|
const { getLocale } = useChatContext()
|
|
31
31
|
|
|
@@ -54,7 +54,7 @@ export function Day ({
|
|
|
54
54
|
return (
|
|
55
55
|
<View style={[stylesCommon.centerItems, styles.container, containerStyle]}>
|
|
56
56
|
<View style={[styles.wrapper, wrapperStyle]}>
|
|
57
|
-
<Text style={[styles.text,
|
|
57
|
+
<Text {...textProps} style={[styles.text, textProps?.style]}>
|
|
58
58
|
{dateStr}
|
|
59
59
|
</Text>
|
|
60
60
|
</View>
|
package/src/Day/types.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
StyleProp,
|
|
3
3
|
ViewStyle,
|
|
4
|
-
|
|
4
|
+
TextProps,
|
|
5
5
|
} from 'react-native'
|
|
6
6
|
|
|
7
7
|
export interface DayProps {
|
|
@@ -10,5 +10,6 @@ export interface DayProps {
|
|
|
10
10
|
dateFormatCalendar?: object
|
|
11
11
|
containerStyle?: StyleProp<ViewStyle>
|
|
12
12
|
wrapperStyle?: StyleProp<ViewStyle>
|
|
13
|
-
|
|
13
|
+
/** Props to pass to the Text component (e.g., style, allowFontScaling, numberOfLines) */
|
|
14
|
+
textProps?: Partial<TextProps>
|
|
14
15
|
}
|
package/src/GiftedChat/index.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import React, {
|
|
|
8
8
|
RefObject,
|
|
9
9
|
} from 'react'
|
|
10
10
|
import {
|
|
11
|
+
Platform,
|
|
11
12
|
View,
|
|
12
13
|
LayoutChangeEvent,
|
|
13
14
|
useColorScheme,
|
|
@@ -25,7 +26,7 @@ import { TEST_ID } from '../Constant'
|
|
|
25
26
|
import { GiftedChatContext } from '../GiftedChatContext'
|
|
26
27
|
import { InputToolbar } from '../InputToolbar'
|
|
27
28
|
import { MessagesContainer, AnimatedList } from '../MessagesContainer'
|
|
28
|
-
import { IMessage } from '../Models'
|
|
29
|
+
import { IMessage, ReplyMessage } from '../Models'
|
|
29
30
|
import stylesCommon from '../styles'
|
|
30
31
|
import { renderComponentOrElement } from '../utils'
|
|
31
32
|
import styles from './styles'
|
|
@@ -33,6 +34,23 @@ import { GiftedChatProps } from './types'
|
|
|
33
34
|
|
|
34
35
|
dayjs.extend(localizedFormat)
|
|
35
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Default keyboard vertical offset values (similar to Stream Chat SDK)
|
|
39
|
+
* iOS: Compensates for predictive/suggestion text bar (~44-50pt) and headers
|
|
40
|
+
* Android: Negative offset to account for navigation bar in edge-to-edge mode
|
|
41
|
+
*/
|
|
42
|
+
const DEFAULT_KEYBOARD_VERTICAL_OFFSET = Platform.select({
|
|
43
|
+
ios: 50,
|
|
44
|
+
android: 0,
|
|
45
|
+
default: 0,
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const DEFAULT_KEYBOARD_BEHAVIOR = Platform.select({
|
|
49
|
+
ios: 'padding' as const,
|
|
50
|
+
android: 'padding' as const,
|
|
51
|
+
default: 'padding' as const,
|
|
52
|
+
})
|
|
53
|
+
|
|
36
54
|
function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
37
55
|
props: GiftedChatProps<TMessage>
|
|
38
56
|
) {
|
|
@@ -56,8 +74,19 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
56
74
|
renderChatFooter,
|
|
57
75
|
renderInputToolbar,
|
|
58
76
|
isInverted = true,
|
|
77
|
+
|
|
78
|
+
// Reply props
|
|
79
|
+
reply,
|
|
59
80
|
} = props
|
|
60
81
|
|
|
82
|
+
// Extract reply props for internal use
|
|
83
|
+
const replyMessageProp = reply?.message
|
|
84
|
+
const onClearReply = reply?.onClear
|
|
85
|
+
const onSwipeToReply = reply?.swipe?.onSwipe
|
|
86
|
+
const renderReplyPreview = reply?.renderPreview
|
|
87
|
+
const replyPreviewContainerStyle = reply?.previewStyle?.containerStyle
|
|
88
|
+
const replyPreviewTextStyle = reply?.previewStyle?.textStyle
|
|
89
|
+
|
|
61
90
|
const systemColorScheme = useColorScheme()
|
|
62
91
|
const colorScheme = colorSchemeProp !== undefined ? colorSchemeProp : systemColorScheme
|
|
63
92
|
|
|
@@ -75,6 +104,10 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
75
104
|
|
|
76
105
|
const [isInitialized, setIsInitialized] = useState<boolean>(false)
|
|
77
106
|
const [text, setText] = useState<string | undefined>(() => props.text || '')
|
|
107
|
+
const [internalReplyMessage, setInternalReplyMessage] = useState<ReplyMessage | null>(null)
|
|
108
|
+
|
|
109
|
+
// Use controlled or uncontrolled reply state
|
|
110
|
+
const replyMessage = replyMessageProp !== undefined ? replyMessageProp : internalReplyMessage
|
|
78
111
|
|
|
79
112
|
const getTextFromProp = useCallback(
|
|
80
113
|
(fallback: string) => {
|
|
@@ -104,6 +137,31 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
104
137
|
[isInverted, messagesContainerRef]
|
|
105
138
|
)
|
|
106
139
|
|
|
140
|
+
const handleSwipeToReply = useCallback(
|
|
141
|
+
(message: TMessage) => {
|
|
142
|
+
if (replyMessageProp === undefined)
|
|
143
|
+
// Uncontrolled mode: manage state internally
|
|
144
|
+
setInternalReplyMessage({
|
|
145
|
+
_id: message._id,
|
|
146
|
+
text: message.text,
|
|
147
|
+
user: message.user,
|
|
148
|
+
image: message.image,
|
|
149
|
+
audio: message.audio,
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
onSwipeToReply?.(message)
|
|
153
|
+
},
|
|
154
|
+
[replyMessageProp, onSwipeToReply]
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
const clearReply = useCallback(() => {
|
|
158
|
+
if (replyMessageProp === undefined)
|
|
159
|
+
// Uncontrolled mode: manage state internally
|
|
160
|
+
setInternalReplyMessage(null)
|
|
161
|
+
|
|
162
|
+
onClearReply?.()
|
|
163
|
+
}, [replyMessageProp, onClearReply])
|
|
164
|
+
|
|
107
165
|
const renderMessages = useMemo(() => {
|
|
108
166
|
if (!isInitialized)
|
|
109
167
|
return null
|
|
@@ -118,6 +176,13 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
118
176
|
messages={messages}
|
|
119
177
|
forwardRef={messagesContainerRef}
|
|
120
178
|
isTyping={isTyping}
|
|
179
|
+
reply={{
|
|
180
|
+
...reply,
|
|
181
|
+
swipe: reply?.swipe ? {
|
|
182
|
+
...reply.swipe,
|
|
183
|
+
onSwipe: handleSwipeToReply,
|
|
184
|
+
} : undefined,
|
|
185
|
+
}}
|
|
121
186
|
/>
|
|
122
187
|
{renderComponentOrElement(renderChatFooter, {})}
|
|
123
188
|
</View>
|
|
@@ -130,6 +195,8 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
130
195
|
isInverted,
|
|
131
196
|
messagesContainerRef,
|
|
132
197
|
renderChatFooter,
|
|
198
|
+
reply,
|
|
199
|
+
handleSwipeToReply,
|
|
133
200
|
])
|
|
134
201
|
|
|
135
202
|
const notifyInputTextReset = useCallback(() => {
|
|
@@ -159,17 +226,22 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
159
226
|
user: user!,
|
|
160
227
|
createdAt: new Date(),
|
|
161
228
|
_id: messageIdGenerator?.(),
|
|
229
|
+
// Attach reply message if exists
|
|
230
|
+
...(replyMessage ? { replyMessage } : {}),
|
|
162
231
|
}
|
|
163
232
|
})
|
|
164
233
|
|
|
165
234
|
if (shouldResetInputToolbar === true)
|
|
166
235
|
resetInputToolbar()
|
|
167
236
|
|
|
237
|
+
// Clear reply after sending
|
|
238
|
+
clearReply()
|
|
239
|
+
|
|
168
240
|
onSend?.(newMessages)
|
|
169
241
|
|
|
170
242
|
setTimeout(() => scrollToBottom(), 10)
|
|
171
243
|
},
|
|
172
|
-
[messageIdGenerator, onSend, user, resetInputToolbar, scrollToBottom]
|
|
244
|
+
[messageIdGenerator, onSend, user, resetInputToolbar, scrollToBottom, replyMessage, clearReply]
|
|
173
245
|
)
|
|
174
246
|
|
|
175
247
|
const _onChangeText = useCallback(
|
|
@@ -214,6 +286,12 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
214
286
|
onChangeText: _onChangeText,
|
|
215
287
|
ref: textInputRef,
|
|
216
288
|
},
|
|
289
|
+
// Reply preview props
|
|
290
|
+
replyMessage,
|
|
291
|
+
onClearReply: clearReply,
|
|
292
|
+
renderReplyPreview,
|
|
293
|
+
replyPreviewContainerStyle,
|
|
294
|
+
replyPreviewTextStyle,
|
|
217
295
|
}
|
|
218
296
|
|
|
219
297
|
if (renderInputToolbar)
|
|
@@ -230,6 +308,11 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
230
308
|
textInputRef,
|
|
231
309
|
textInputProps,
|
|
232
310
|
_onChangeText,
|
|
311
|
+
replyMessage,
|
|
312
|
+
clearReply,
|
|
313
|
+
renderReplyPreview,
|
|
314
|
+
replyPreviewContainerStyle,
|
|
315
|
+
replyPreviewTextStyle,
|
|
233
316
|
])
|
|
234
317
|
|
|
235
318
|
const contextValues = useMemo(
|
|
@@ -254,29 +337,32 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
254
337
|
return (
|
|
255
338
|
<GiftedChatContext.Provider value={contextValues}>
|
|
256
339
|
<ActionSheetProvider ref={actionSheetRef}>
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
{...props.keyboardAvoidingViewProps}
|
|
340
|
+
<View
|
|
341
|
+
testID={TEST_ID.WRAPPER}
|
|
342
|
+
style={[stylesCommon.fill, styles.contentContainer]}
|
|
343
|
+
onLayout={onInitialLayoutViewLayout}
|
|
262
344
|
>
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
345
|
+
{/* @ts-expect-error */}
|
|
346
|
+
<KeyboardAvoidingView
|
|
347
|
+
behavior={DEFAULT_KEYBOARD_BEHAVIOR}
|
|
348
|
+
keyboardVerticalOffset={DEFAULT_KEYBOARD_VERTICAL_OFFSET}
|
|
349
|
+
style={stylesCommon.fill}
|
|
350
|
+
{...props.keyboardAvoidingViewProps}
|
|
267
351
|
>
|
|
268
|
-
{
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
352
|
+
<View style={stylesCommon.fill}>
|
|
353
|
+
{isInitialized
|
|
354
|
+
? (
|
|
355
|
+
<>
|
|
356
|
+
{renderMessages}
|
|
357
|
+
{inputToolbarFragment}
|
|
358
|
+
</>
|
|
359
|
+
)
|
|
360
|
+
: (
|
|
361
|
+
renderComponentOrElement(renderLoading, {})
|
|
362
|
+
)}
|
|
363
|
+
</View>
|
|
364
|
+
</KeyboardAvoidingView>
|
|
365
|
+
</View>
|
|
280
366
|
</ActionSheetProvider>
|
|
281
367
|
</GiftedChatContext.Provider>
|
|
282
368
|
)
|
package/src/GiftedChat/types.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
ActionSheetOptions,
|
|
10
10
|
} from '@expo/react-native-action-sheet'
|
|
11
11
|
import { KeyboardProvider, KeyboardAvoidingViewProps } from 'react-native-keyboard-controller'
|
|
12
|
+
|
|
12
13
|
import { ActionsProps } from '../Actions'
|
|
13
14
|
import { AvatarProps } from '../Avatar'
|
|
14
15
|
import { BubbleProps } from '../Bubble'
|
|
@@ -25,11 +26,12 @@ import {
|
|
|
25
26
|
User,
|
|
26
27
|
} from '../Models'
|
|
27
28
|
import { QuickRepliesProps } from '../QuickReplies'
|
|
29
|
+
import { ReplyProps } from '../Reply'
|
|
28
30
|
import { SendProps } from '../Send'
|
|
29
31
|
import { SystemMessageProps } from '../SystemMessage'
|
|
30
32
|
import { TimeProps } from '../Time'
|
|
31
33
|
|
|
32
|
-
export interface GiftedChatProps<TMessage extends IMessage> extends Partial<MessagesContainerProps<TMessage>> {
|
|
34
|
+
export interface GiftedChatProps<TMessage extends IMessage> extends Partial<Omit<MessagesContainerProps<TMessage>, 'messageReplyContainerStyle'>> {
|
|
33
35
|
/* Messages container ref */
|
|
34
36
|
messagesContainerRef?: RefObject<AnimatedList<TMessage>>
|
|
35
37
|
/* text input ref */
|
|
@@ -85,7 +87,7 @@ export interface GiftedChatProps<TMessage extends IMessage> extends Partial<Mess
|
|
|
85
87
|
actionSheet?: () => {
|
|
86
88
|
showActionSheetWithOptions: (
|
|
87
89
|
options: ActionSheetOptions,
|
|
88
|
-
callback: (buttonIndex: number) => void | Promise<void
|
|
90
|
+
callback: (buttonIndex: number) => void | Promise<void>
|
|
89
91
|
) => void
|
|
90
92
|
}
|
|
91
93
|
/* Callback when a message avatar is tapped */
|
|
@@ -140,11 +142,15 @@ export interface GiftedChatProps<TMessage extends IMessage> extends Partial<Mess
|
|
|
140
142
|
/* Extra props to be passed to the MessageText component */
|
|
141
143
|
messageTextProps?: Partial<MessageTextProps<TMessage>>
|
|
142
144
|
renderQuickReplies?: (
|
|
143
|
-
quickReplies: QuickRepliesProps<TMessage
|
|
145
|
+
quickReplies: QuickRepliesProps<TMessage>
|
|
144
146
|
) => React.ReactNode
|
|
145
147
|
renderQuickReplySend?: () => React.ReactNode
|
|
146
148
|
keyboardProviderProps?: React.ComponentProps<typeof KeyboardProvider>
|
|
149
|
+
/** Props for KeyboardAvoidingView. Use `keyboardVerticalOffset` to account for headers or iOS predictive text bar (~44pt). */
|
|
147
150
|
keyboardAvoidingViewProps?: KeyboardAvoidingViewProps
|
|
148
151
|
/** Enable animated day label that appears on scroll; default is true */
|
|
149
152
|
isDayAnimationEnabled?: boolean
|
|
153
|
+
|
|
154
|
+
/** Reply functionality configuration */
|
|
155
|
+
reply?: ReplyProps<TMessage>
|
|
150
156
|
}
|
package/src/InputToolbar.tsx
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import React, { useMemo } from 'react'
|
|
2
|
-
import { StyleSheet, View, StyleProp, ViewStyle } from 'react-native'
|
|
1
|
+
import React, { useCallback, useMemo } from 'react'
|
|
2
|
+
import { StyleSheet, View, StyleProp, ViewStyle, TextStyle } from 'react-native'
|
|
3
3
|
|
|
4
4
|
import { Actions, ActionsProps } from './Actions'
|
|
5
5
|
import { Color } from './Color'
|
|
6
|
+
import { ReplyPreview, ReplyPreviewProps } from './components/ReplyPreview'
|
|
6
7
|
import { Composer, ComposerProps } from './Composer'
|
|
7
8
|
import { useColorScheme } from './hooks/useColorScheme'
|
|
8
|
-
import { IMessage } from './Models'
|
|
9
|
+
import { IMessage, ReplyMessage } from './Models'
|
|
9
10
|
import { Send, SendProps } from './Send'
|
|
10
|
-
import { getColorSchemeStyle } from './styles'
|
|
11
11
|
import { renderComponentOrElement } from './utils'
|
|
12
12
|
|
|
13
|
+
export type { ReplyPreviewProps } from './components/ReplyPreview'
|
|
14
|
+
|
|
13
15
|
export interface InputToolbarProps<TMessage extends IMessage> {
|
|
14
16
|
actions?: Array<{ title: string, action: () => void }>
|
|
15
17
|
actionSheetOptionTintColor?: string
|
|
@@ -22,6 +24,16 @@ export interface InputToolbarProps<TMessage extends IMessage> {
|
|
|
22
24
|
onPressActionButton?: () => void
|
|
23
25
|
icon?: () => React.ReactNode
|
|
24
26
|
wrapperStyle?: StyleProp<ViewStyle>
|
|
27
|
+
/** Reply message to show in preview */
|
|
28
|
+
replyMessage?: ReplyMessage | null
|
|
29
|
+
/** Callback to clear reply */
|
|
30
|
+
onClearReply?: () => void
|
|
31
|
+
/** Custom render for reply preview */
|
|
32
|
+
renderReplyPreview?: (props: ReplyPreviewProps) => React.ReactNode
|
|
33
|
+
/** Style for reply preview container */
|
|
34
|
+
replyPreviewContainerStyle?: StyleProp<ViewStyle>
|
|
35
|
+
/** Style for reply preview text */
|
|
36
|
+
replyPreviewTextStyle?: StyleProp<TextStyle>
|
|
25
37
|
}
|
|
26
38
|
|
|
27
39
|
export function InputToolbar<TMessage extends IMessage = IMessage> (
|
|
@@ -38,10 +50,26 @@ export function InputToolbar<TMessage extends IMessage = IMessage> (
|
|
|
38
50
|
icon,
|
|
39
51
|
wrapperStyle,
|
|
40
52
|
containerStyle,
|
|
53
|
+
replyMessage,
|
|
54
|
+
onClearReply,
|
|
55
|
+
renderReplyPreview: renderReplyPreviewProp,
|
|
56
|
+
replyPreviewContainerStyle,
|
|
57
|
+
replyPreviewTextStyle,
|
|
41
58
|
} = props
|
|
42
59
|
|
|
43
60
|
const colorScheme = useColorScheme()
|
|
44
61
|
|
|
62
|
+
const containerStyles = useMemo(() => [
|
|
63
|
+
styles.container,
|
|
64
|
+
colorScheme === 'dark' && styles.container_dark,
|
|
65
|
+
containerStyle,
|
|
66
|
+
], [colorScheme, containerStyle])
|
|
67
|
+
|
|
68
|
+
const primaryStyles = useMemo(() => [
|
|
69
|
+
styles.primary,
|
|
70
|
+
props.primaryStyle,
|
|
71
|
+
], [props.primaryStyle])
|
|
72
|
+
|
|
45
73
|
const actionsFragment = useMemo(() => {
|
|
46
74
|
const actionsProps = {
|
|
47
75
|
onPressActionButton,
|
|
@@ -92,11 +120,37 @@ export function InputToolbar<TMessage extends IMessage = IMessage> (
|
|
|
92
120
|
return renderComponentOrElement(renderAccessory, props)
|
|
93
121
|
}, [renderAccessory, props])
|
|
94
122
|
|
|
123
|
+
const handleClearReply = useCallback(() => {
|
|
124
|
+
onClearReply?.()
|
|
125
|
+
}, [onClearReply])
|
|
126
|
+
|
|
127
|
+
const replyPreviewFragment = useMemo(() => {
|
|
128
|
+
if (!replyMessage)
|
|
129
|
+
return null
|
|
130
|
+
|
|
131
|
+
const replyPreviewProps: ReplyPreviewProps = {
|
|
132
|
+
replyMessage,
|
|
133
|
+
onClearReply: handleClearReply,
|
|
134
|
+
containerStyle: replyPreviewContainerStyle,
|
|
135
|
+
textStyle: replyPreviewTextStyle,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (renderReplyPreviewProp)
|
|
139
|
+
return renderComponentOrElement(renderReplyPreviewProp, replyPreviewProps)
|
|
140
|
+
|
|
141
|
+
return <ReplyPreview {...replyPreviewProps} />
|
|
142
|
+
}, [
|
|
143
|
+
replyMessage,
|
|
144
|
+
handleClearReply,
|
|
145
|
+
renderReplyPreviewProp,
|
|
146
|
+
replyPreviewContainerStyle,
|
|
147
|
+
replyPreviewTextStyle,
|
|
148
|
+
])
|
|
149
|
+
|
|
95
150
|
return (
|
|
96
|
-
<View
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
<View style={[getColorSchemeStyle(styles, 'primary', colorScheme), props.primaryStyle]}>
|
|
151
|
+
<View style={containerStyles}>
|
|
152
|
+
{replyPreviewFragment}
|
|
153
|
+
<View style={primaryStyles}>
|
|
100
154
|
{actionsFragment}
|
|
101
155
|
{composerFragment}
|
|
102
156
|
{sendFragment}
|