react-native-gifted-chat 3.2.3 → 3.3.2
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 +487 -165
- 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 +87 -24
- package/src/GiftedChat/styles.ts +3 -0
- 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 +87 -45
- package/src/__tests__/__snapshots__/InputToolbar.test.tsx.snap +10 -36
- package/src/__tests__/__snapshots__/MessageImage.test.tsx.snap +18 -102
- package/src/__tests__/__snapshots__/MessageReply.test.tsx.snap +181 -0
- package/src/__tests__/__snapshots__/ReplyPreview.test.tsx.snap +349 -0
- package/src/__tests__/__snapshots__/Send.test.tsx.snap +0 -63
- 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 +16 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-gifted-chat",
|
|
3
|
-
"version": "3.2
|
|
3
|
+
"version": "3.3.2",
|
|
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.30.0",
|
|
96
|
+
"react-native-keyboard-controller": "1.20.6",
|
|
97
|
+
"react-native-reanimated": "~4.2.1",
|
|
98
|
+
"react-native-safe-area-context": "~5.6.2",
|
|
99
|
+
"react-native-worklets": "0.7.2",
|
|
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
|
@@ -20,12 +20,12 @@ import dayjs from 'dayjs'
|
|
|
20
20
|
import localizedFormat from 'dayjs/plugin/localizedFormat'
|
|
21
21
|
import { GestureHandlerRootView, TextInput } from 'react-native-gesture-handler'
|
|
22
22
|
import { KeyboardAvoidingView, KeyboardProvider } from 'react-native-keyboard-controller'
|
|
23
|
-
import { SafeAreaProvider } from 'react-native-safe-area-context'
|
|
23
|
+
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
24
24
|
import { TEST_ID } from '../Constant'
|
|
25
25
|
import { GiftedChatContext } from '../GiftedChatContext'
|
|
26
26
|
import { InputToolbar } from '../InputToolbar'
|
|
27
27
|
import { MessagesContainer, AnimatedList } from '../MessagesContainer'
|
|
28
|
-
import { IMessage } from '../Models'
|
|
28
|
+
import { IMessage, ReplyMessage } from '../Models'
|
|
29
29
|
import stylesCommon from '../styles'
|
|
30
30
|
import { renderComponentOrElement } from '../utils'
|
|
31
31
|
import styles from './styles'
|
|
@@ -56,13 +56,26 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
56
56
|
renderChatFooter,
|
|
57
57
|
renderInputToolbar,
|
|
58
58
|
isInverted = true,
|
|
59
|
+
|
|
60
|
+
// Reply props
|
|
61
|
+
reply,
|
|
59
62
|
} = props
|
|
60
63
|
|
|
64
|
+
// Extract reply props for internal use
|
|
65
|
+
const replyMessageProp = reply?.message
|
|
66
|
+
const onClearReply = reply?.onClear
|
|
67
|
+
const onSwipeToReply = reply?.swipe?.onSwipe
|
|
68
|
+
const renderReplyPreview = reply?.renderPreview
|
|
69
|
+
const replyPreviewContainerStyle = reply?.previewStyle?.containerStyle
|
|
70
|
+
const replyPreviewTextStyle = reply?.previewStyle?.textStyle
|
|
71
|
+
|
|
61
72
|
const systemColorScheme = useColorScheme()
|
|
62
73
|
const colorScheme = colorSchemeProp !== undefined ? colorSchemeProp : systemColorScheme
|
|
63
74
|
|
|
64
75
|
const actionSheetRef = useRef<ActionSheetProviderRef>(null)
|
|
65
76
|
|
|
77
|
+
const insets = useSafeAreaInsets()
|
|
78
|
+
|
|
66
79
|
const messagesContainerRef = useMemo(
|
|
67
80
|
() => props.messagesContainerRef || createRef<AnimatedList<TMessage>>(),
|
|
68
81
|
[props.messagesContainerRef]
|
|
@@ -75,6 +88,10 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
75
88
|
|
|
76
89
|
const [isInitialized, setIsInitialized] = useState<boolean>(false)
|
|
77
90
|
const [text, setText] = useState<string | undefined>(() => props.text || '')
|
|
91
|
+
const [internalReplyMessage, setInternalReplyMessage] = useState<ReplyMessage | null>(null)
|
|
92
|
+
|
|
93
|
+
// Use controlled or uncontrolled reply state
|
|
94
|
+
const replyMessage = replyMessageProp !== undefined ? replyMessageProp : internalReplyMessage
|
|
78
95
|
|
|
79
96
|
const getTextFromProp = useCallback(
|
|
80
97
|
(fallback: string) => {
|
|
@@ -104,6 +121,31 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
104
121
|
[isInverted, messagesContainerRef]
|
|
105
122
|
)
|
|
106
123
|
|
|
124
|
+
const handleSwipeToReply = useCallback(
|
|
125
|
+
(message: TMessage) => {
|
|
126
|
+
if (replyMessageProp === undefined)
|
|
127
|
+
// Uncontrolled mode: manage state internally
|
|
128
|
+
setInternalReplyMessage({
|
|
129
|
+
_id: message._id,
|
|
130
|
+
text: message.text,
|
|
131
|
+
user: message.user,
|
|
132
|
+
image: message.image,
|
|
133
|
+
audio: message.audio,
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
onSwipeToReply?.(message)
|
|
137
|
+
},
|
|
138
|
+
[replyMessageProp, onSwipeToReply]
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
const clearReply = useCallback(() => {
|
|
142
|
+
if (replyMessageProp === undefined)
|
|
143
|
+
// Uncontrolled mode: manage state internally
|
|
144
|
+
setInternalReplyMessage(null)
|
|
145
|
+
|
|
146
|
+
onClearReply?.()
|
|
147
|
+
}, [replyMessageProp, onClearReply])
|
|
148
|
+
|
|
107
149
|
const renderMessages = useMemo(() => {
|
|
108
150
|
if (!isInitialized)
|
|
109
151
|
return null
|
|
@@ -118,6 +160,13 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
118
160
|
messages={messages}
|
|
119
161
|
forwardRef={messagesContainerRef}
|
|
120
162
|
isTyping={isTyping}
|
|
163
|
+
reply={{
|
|
164
|
+
...reply,
|
|
165
|
+
swipe: reply?.swipe ? {
|
|
166
|
+
...reply.swipe,
|
|
167
|
+
onSwipe: handleSwipeToReply,
|
|
168
|
+
} : undefined,
|
|
169
|
+
}}
|
|
121
170
|
/>
|
|
122
171
|
{renderComponentOrElement(renderChatFooter, {})}
|
|
123
172
|
</View>
|
|
@@ -130,6 +179,8 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
130
179
|
isInverted,
|
|
131
180
|
messagesContainerRef,
|
|
132
181
|
renderChatFooter,
|
|
182
|
+
reply,
|
|
183
|
+
handleSwipeToReply,
|
|
133
184
|
])
|
|
134
185
|
|
|
135
186
|
const notifyInputTextReset = useCallback(() => {
|
|
@@ -159,17 +210,22 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
159
210
|
user: user!,
|
|
160
211
|
createdAt: new Date(),
|
|
161
212
|
_id: messageIdGenerator?.(),
|
|
213
|
+
// Attach reply message if exists
|
|
214
|
+
...(replyMessage ? { replyMessage } : {}),
|
|
162
215
|
}
|
|
163
216
|
})
|
|
164
217
|
|
|
165
218
|
if (shouldResetInputToolbar === true)
|
|
166
219
|
resetInputToolbar()
|
|
167
220
|
|
|
221
|
+
// Clear reply after sending
|
|
222
|
+
clearReply()
|
|
223
|
+
|
|
168
224
|
onSend?.(newMessages)
|
|
169
225
|
|
|
170
226
|
setTimeout(() => scrollToBottom(), 10)
|
|
171
227
|
},
|
|
172
|
-
[messageIdGenerator, onSend, user, resetInputToolbar, scrollToBottom]
|
|
228
|
+
[messageIdGenerator, onSend, user, resetInputToolbar, scrollToBottom, replyMessage, clearReply]
|
|
173
229
|
)
|
|
174
230
|
|
|
175
231
|
const _onChangeText = useCallback(
|
|
@@ -214,6 +270,12 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
214
270
|
onChangeText: _onChangeText,
|
|
215
271
|
ref: textInputRef,
|
|
216
272
|
},
|
|
273
|
+
// Reply preview props
|
|
274
|
+
replyMessage,
|
|
275
|
+
onClearReply: clearReply,
|
|
276
|
+
renderReplyPreview,
|
|
277
|
+
replyPreviewContainerStyle,
|
|
278
|
+
replyPreviewTextStyle,
|
|
217
279
|
}
|
|
218
280
|
|
|
219
281
|
if (renderInputToolbar)
|
|
@@ -230,6 +292,11 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
230
292
|
textInputRef,
|
|
231
293
|
textInputProps,
|
|
232
294
|
_onChangeText,
|
|
295
|
+
replyMessage,
|
|
296
|
+
clearReply,
|
|
297
|
+
renderReplyPreview,
|
|
298
|
+
replyPreviewContainerStyle,
|
|
299
|
+
replyPreviewTextStyle,
|
|
233
300
|
])
|
|
234
301
|
|
|
235
302
|
const contextValues = useMemo(
|
|
@@ -254,29 +321,25 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
254
321
|
return (
|
|
255
322
|
<GiftedChatContext.Provider value={contextValues}>
|
|
256
323
|
<ActionSheetProvider ref={actionSheetRef}>
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
{...props.keyboardAvoidingViewProps}
|
|
324
|
+
<View
|
|
325
|
+
testID={TEST_ID.WRAPPER}
|
|
326
|
+
style={[stylesCommon.fill, styles.contentContainer]}
|
|
327
|
+
onLayout={onInitialLayoutViewLayout}
|
|
262
328
|
>
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
329
|
+
{/* @ts-expect-error */}
|
|
330
|
+
<KeyboardAvoidingView
|
|
331
|
+
behavior='translate-with-padding'
|
|
332
|
+
keyboardVerticalOffset={insets.top}
|
|
333
|
+
style={stylesCommon.fill}
|
|
334
|
+
{...props.keyboardAvoidingViewProps}
|
|
267
335
|
>
|
|
268
|
-
{isInitialized
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
: (
|
|
276
|
-
renderComponentOrElement(renderLoading, {})
|
|
277
|
-
)}
|
|
278
|
-
</View>
|
|
279
|
-
</KeyboardAvoidingView>
|
|
336
|
+
<View style={[stylesCommon.fill, !isInitialized && styles.hidden]}>
|
|
337
|
+
{renderMessages}
|
|
338
|
+
{inputToolbarFragment}
|
|
339
|
+
</View>
|
|
340
|
+
{!isInitialized && renderComponentOrElement(renderLoading, {})}
|
|
341
|
+
</KeyboardAvoidingView>
|
|
342
|
+
</View>
|
|
280
343
|
</ActionSheetProvider>
|
|
281
344
|
</GiftedChatContext.Provider>
|
|
282
345
|
)
|
package/src/GiftedChat/styles.ts
CHANGED
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}
|