react-native-gifted-chat 2.9.0-alpha.0 → 3.0.0-alpha.1

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.
Files changed (38) hide show
  1. package/README.md +234 -319
  2. package/package.json +2 -2
  3. package/src/Avatar.tsx +12 -12
  4. package/src/Bubble/index.tsx +2 -2
  5. package/src/Bubble/types.ts +22 -22
  6. package/src/Composer.tsx +0 -3
  7. package/src/GiftedAvatar.tsx +1 -1
  8. package/src/GiftedChat/index.tsx +56 -145
  9. package/src/GiftedChat/types.ts +40 -52
  10. package/src/InputToolbar.tsx +6 -6
  11. package/src/Message/index.tsx +5 -6
  12. package/src/Message/types.ts +8 -12
  13. package/src/MessageContainer/components/DayAnimated/types.ts +1 -1
  14. package/src/MessageContainer/components/Item/index.tsx +2 -2
  15. package/src/MessageContainer/components/Item/types.ts +1 -1
  16. package/src/MessageContainer/index.tsx +30 -30
  17. package/src/MessageContainer/styles.ts +12 -5
  18. package/src/MessageContainer/types.ts +12 -16
  19. package/src/MessageImage.tsx +100 -67
  20. package/src/MessageText.tsx +1 -1
  21. package/src/Models.ts +63 -0
  22. package/src/QuickReplies.tsx +1 -1
  23. package/src/Send.tsx +30 -31
  24. package/src/SystemMessage.tsx +1 -1
  25. package/src/Time.tsx +1 -1
  26. package/src/__tests__/GiftedChat.test.tsx +0 -28
  27. package/src/__tests__/Message.test.tsx +2 -2
  28. package/src/__tests__/Send.test.tsx +1 -1
  29. package/src/__tests__/__snapshots__/Actions.test.tsx.snap +2 -86
  30. package/src/__tests__/__snapshots__/GiftedChat.test.tsx.snap +31 -54
  31. package/src/__tests__/__snapshots__/LoadEarlier.test.tsx.snap +3 -89
  32. package/src/__tests__/__snapshots__/Message.test.tsx.snap +2 -2
  33. package/src/__tests__/__snapshots__/MessageImage.test.tsx.snap +1 -1
  34. package/src/__tests__/__snapshots__/Send.test.tsx.snap +10 -142
  35. package/src/__tests__/data.ts +1 -1
  36. package/src/components/TouchableOpacity.tsx +19 -8
  37. package/src/types.ts +1 -63
  38. package/src/utils.ts +1 -1
@@ -8,46 +8,24 @@ import {
8
8
  StyleProp,
9
9
  ImageStyle,
10
10
  ImageURISource,
11
- Modal,
12
11
  TouchableOpacity,
13
12
  LayoutChangeEvent,
14
13
  useWindowDimensions,
14
+ StatusBar,
15
15
  } from 'react-native'
16
16
  import { BaseButton, GestureHandlerRootView, Text } from 'react-native-gesture-handler'
17
+ import { OverKeyboardView } from 'react-native-keyboard-controller'
18
+ import Animated, {
19
+ useAnimatedStyle,
20
+ useSharedValue,
21
+ withTiming,
22
+ runOnJS,
23
+ Easing,
24
+ } from 'react-native-reanimated'
17
25
  import { useSafeAreaInsets } from 'react-native-safe-area-context'
18
26
  import Zoom from 'react-native-zoom-reanimated'
27
+ import { IMessage } from './Models'
19
28
  import commonStyles from './styles'
20
- import { IMessage } from './types'
21
-
22
- const styles = StyleSheet.create({
23
- image: {
24
- width: 150,
25
- height: 100,
26
- borderRadius: 13,
27
- margin: 3,
28
- resizeMode: 'cover',
29
- },
30
- modalContent: {
31
- backgroundColor: '#000',
32
- },
33
- modalImageContainer: {
34
- width: '100%',
35
- height: '100%',
36
- },
37
-
38
- closeButtonContainer: {
39
- flexDirection: 'row',
40
- justifyContent: 'flex-end',
41
- },
42
- closeButtonContent: {
43
- padding: 10,
44
- },
45
- closeButtonIcon: {
46
- fontSize: 20,
47
- lineHeight: 20,
48
- color: 'white',
49
- },
50
- })
51
29
 
52
30
  export interface MessageImageProps<TMessage extends IMessage> {
53
31
  currentMessage: TMessage
@@ -70,6 +48,10 @@ export function MessageImage<TMessage extends IMessage = IMessage> ({
70
48
 
71
49
  const insets = useSafeAreaInsets()
72
50
 
51
+ // Animation values
52
+ const modalOpacity = useSharedValue(0)
53
+ const modalScale = useSharedValue(0.9)
54
+
73
55
  const imageSource = useMemo(() => ({
74
56
  ...imageSourceProps,
75
57
  uri: currentMessage?.image,
@@ -88,15 +70,23 @@ export function MessageImage<TMessage extends IMessage = IMessage> ({
88
70
 
89
71
  setIsModalVisible(true)
90
72
 
73
+ // Animate modal entrance
74
+ modalOpacity.value = withTiming(1, { duration: 300, easing: Easing.out(Easing.ease) })
75
+ modalScale.value = withTiming(1, { duration: 300, easing: Easing.out(Easing.ease) })
76
+
91
77
  if (isImageSourceChanged.current || !imageDimensions)
92
78
  Image.getSize(imageSource.uri, (width, height) => {
93
79
  setImageDimensions({ width, height })
94
80
  })
95
- }, [imageSource.uri, imageDimensions])
81
+ }, [imageSource.uri, imageDimensions, modalOpacity, modalScale])
96
82
 
97
83
  const handleModalClose = useCallback(() => {
98
- setIsModalVisible(false)
99
- }, [])
84
+ // Animate modal exit
85
+ modalOpacity.value = withTiming(0, { duration: 200, easing: Easing.in(Easing.ease) })
86
+ modalScale.value = withTiming(0.9, { duration: 200, easing: Easing.in(Easing.ease) }, () => {
87
+ runOnJS(setIsModalVisible)(false)
88
+ })
89
+ }, [modalOpacity, modalScale])
100
90
 
101
91
  const handleImageLayout = useCallback((e: LayoutChangeEvent) => {
102
92
  setImageDimensions({
@@ -125,6 +115,11 @@ export function MessageImage<TMessage extends IMessage = IMessage> ({
125
115
  }
126
116
  }, [imageDimensions, windowDimensions.height, windowDimensions.width])
127
117
 
118
+ const modalAnimatedStyle = useAnimatedStyle(() => ({
119
+ opacity: modalOpacity.value,
120
+ transform: [{ scale: modalScale.value }],
121
+ }), [modalOpacity, modalScale])
122
+
128
123
  useEffect(() => {
129
124
  isImageSourceChanged.current = true
130
125
  }, [imageSource.uri])
@@ -140,42 +135,80 @@ export function MessageImage<TMessage extends IMessage = IMessage> ({
140
135
  style={computedImageStyle}
141
136
  source={imageSource}
142
137
  onLayout={handleImageLayout}
138
+ resizeMode='cover'
143
139
  />
144
140
  </TouchableOpacity>
145
141
 
146
- <Modal
147
- visible={isModalVisible}
148
- onRequestClose={handleModalClose}
149
- animationType='slide'
150
- transparent={false}
151
- >
152
- <GestureHandlerRootView style={commonStyles.fill}>
153
- <View style={[commonStyles.fill, styles.modalContent, { paddingTop: insets.top, paddingBottom: insets.bottom }]}>
154
-
155
- {/* close button */}
156
- <View style={styles.closeButtonContainer}>
157
- <BaseButton onPress={handleModalClose}>
158
- <View style={styles.closeButtonContent}>
159
- <Text style={styles.closeButtonIcon}>
160
- {'X'}
161
- </Text>
142
+ {isModalVisible && (
143
+ <OverKeyboardView visible={isModalVisible}>
144
+ <StatusBar animated barStyle='dark-content' />
145
+ <Animated.View style={[styles.modalOverlay, modalAnimatedStyle]}>
146
+ <GestureHandlerRootView style={commonStyles.fill}>
147
+ <View style={[commonStyles.fill, styles.modalContent, { paddingTop: insets.top, paddingBottom: insets.bottom }]}>
148
+
149
+ {/* close button */}
150
+ <View style={styles.closeButtonContainer}>
151
+ <BaseButton onPress={handleModalClose}>
152
+ <View style={styles.closeButtonContent}>
153
+ <Text style={styles.closeButtonIcon}>
154
+ {'X'}
155
+ </Text>
156
+ </View>
157
+ </BaseButton>
158
+ </View>
159
+
160
+ <View style={[commonStyles.fill, commonStyles.centerItems]}>
161
+ <Zoom>
162
+ <Image
163
+ style={modalImageDimensions}
164
+ source={imageSource}
165
+ resizeMode='contain'
166
+ {...imageProps}
167
+ />
168
+ </Zoom>
162
169
  </View>
163
- </BaseButton>
164
- </View>
165
-
166
- <View style={[commonStyles.fill, commonStyles.centerItems]}>
167
- <Zoom>
168
- <Image
169
- style={modalImageDimensions}
170
- source={imageSource}
171
- resizeMode='contain'
172
- {...imageProps}
173
- />
174
- </Zoom>
175
- </View>
176
- </View>
177
- </GestureHandlerRootView>
178
- </Modal>
170
+ </View>
171
+ </GestureHandlerRootView>
172
+ </Animated.View>
173
+ </OverKeyboardView>
174
+ )}
179
175
  </View>
180
176
  )
181
177
  }
178
+
179
+ const styles = StyleSheet.create({
180
+ image: {
181
+ width: 150,
182
+ height: 100,
183
+ borderRadius: 13,
184
+ margin: 3,
185
+ },
186
+ modalOverlay: {
187
+ position: 'absolute',
188
+ top: 0,
189
+ left: 0,
190
+ right: 0,
191
+ bottom: 0,
192
+ zIndex: 1000,
193
+ },
194
+ modalContent: {
195
+ backgroundColor: '#000',
196
+ },
197
+ modalImageContainer: {
198
+ width: '100%',
199
+ height: '100%',
200
+ },
201
+
202
+ closeButtonContainer: {
203
+ flexDirection: 'row',
204
+ justifyContent: 'flex-end',
205
+ },
206
+ closeButtonContent: {
207
+ padding: 20,
208
+ },
209
+ closeButtonIcon: {
210
+ fontSize: 20,
211
+ lineHeight: 20,
212
+ color: 'white',
213
+ },
214
+ })
@@ -9,7 +9,7 @@ import {
9
9
 
10
10
  import { Match } from 'autolinker/dist/es2015'
11
11
  import Autolink, { AutolinkProps } from 'react-native-autolink'
12
- import { LeftRightStyle, IMessage } from './types'
12
+ import { LeftRightStyle, IMessage } from './Models'
13
13
 
14
14
  export type MessageTextProps<TMessage extends IMessage> = {
15
15
  position?: 'left' | 'right'
package/src/Models.ts ADDED
@@ -0,0 +1,63 @@
1
+ import { StyleProp, ViewStyle } from 'react-native'
2
+
3
+ export type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
4
+
5
+ export interface LeftRightStyle<T> {
6
+ left?: StyleProp<T>
7
+ right?: StyleProp<T>
8
+ }
9
+
10
+ type renderFunction = (x: unknown) => React.ReactNode
11
+
12
+ export interface User {
13
+ _id: string | number
14
+ name?: string
15
+ avatar?: string | number | renderFunction
16
+ }
17
+
18
+ export interface Reply {
19
+ title: string
20
+ value: string
21
+ messageId?: number | string
22
+ }
23
+
24
+ export interface QuickReplies {
25
+ type: 'radio' | 'checkbox'
26
+ values: Reply[]
27
+ keepIt?: boolean
28
+ }
29
+
30
+ export interface IMessage {
31
+ _id: string | number
32
+ text: string
33
+ createdAt: Date | number
34
+ user: User
35
+ image?: string
36
+ video?: string
37
+ audio?: string
38
+ system?: boolean
39
+ sent?: boolean
40
+ received?: boolean
41
+ pending?: boolean
42
+ quickReplies?: QuickReplies
43
+ location?: {
44
+ latitude: number
45
+ longitude: number
46
+ }
47
+ }
48
+
49
+ export type IChatMessage = IMessage
50
+
51
+ export interface MessageVideoProps<TMessage extends IMessage> {
52
+ currentMessage: TMessage
53
+ containerStyle?: StyleProp<ViewStyle>
54
+ videoStyle?: StyleProp<ViewStyle>
55
+ videoProps?: object
56
+ }
57
+
58
+ export interface MessageAudioProps<TMessage extends IMessage> {
59
+ currentMessage: TMessage
60
+ containerStyle?: StyleProp<ViewStyle>
61
+ audioStyle?: StyleProp<ViewStyle>
62
+ audioProps?: object
63
+ }
@@ -10,8 +10,8 @@ import {
10
10
  import { Color } from './Color'
11
11
  import { TouchableOpacity } from './components/TouchableOpacity'
12
12
  import { warning } from './logging'
13
+ import { IMessage, Reply } from './Models'
13
14
  import stylesCommon from './styles'
14
- import { IMessage, Reply } from './types'
15
15
 
16
16
  const styles = StyleSheet.create({
17
17
  container: {
package/src/Send.tsx CHANGED
@@ -12,26 +12,7 @@ import { Color } from './Color'
12
12
 
13
13
  import { TouchableOpacity, TouchableOpacityProps } from './components/TouchableOpacity'
14
14
  import { TEST_ID } from './Constant'
15
- import { IMessage } from './types'
16
-
17
- const styles = StyleSheet.create({
18
- container: {
19
- height: 44,
20
- justifyContent: 'flex-end',
21
- },
22
- text: {
23
- color: Color.defaultBlue,
24
- fontWeight: '600',
25
- fontSize: 17,
26
- backgroundColor: Color.backgroundTransparent,
27
- marginBottom: 12,
28
- marginLeft: 10,
29
- marginRight: 10,
30
- },
31
- text_dark: {
32
- color: '#4da6ff',
33
- },
34
- })
15
+ import { IMessage } from './Models'
35
16
 
36
17
  export interface SendProps<TMessage extends IMessage> {
37
18
  text?: string
@@ -39,8 +20,7 @@ export interface SendProps<TMessage extends IMessage> {
39
20
  containerStyle?: StyleProp<ViewStyle>
40
21
  textStyle?: StyleProp<TextStyle>
41
22
  children?: React.ReactNode
42
- alwaysShowSend?: boolean
43
- disabled?: boolean
23
+ isSendButtonAlwaysVisible?: boolean
44
24
  sendButtonProps?: Partial<TouchableOpacityProps>
45
25
  onSend?(
46
26
  messages: Partial<TMessage> | Partial<TMessage>[],
@@ -54,21 +34,22 @@ export const Send = <TMessage extends IMessage = IMessage>({
54
34
  children,
55
35
  textStyle,
56
36
  label = 'Send',
57
- alwaysShowSend = false,
58
- disabled = false,
37
+ isSendButtonAlwaysVisible = false,
59
38
  sendButtonProps,
60
39
  onSend,
61
40
  }: SendProps<TMessage>) => {
62
41
  const colorScheme = useColorScheme()
63
42
 
64
43
  const handleOnPress = useCallback(() => {
65
- if (text && onSend)
66
- onSend({ text: text.trim() } as Partial<TMessage>, true)
44
+ const message = { text: text?.trim() } as Partial<TMessage>
45
+
46
+ if (onSend && message.text?.length)
47
+ onSend(message, true)
67
48
  }, [text, onSend])
68
49
 
69
50
  const showSend = useMemo(
70
- () => alwaysShowSend || (text && text.trim().length > 0),
71
- [alwaysShowSend, text]
51
+ () => isSendButtonAlwaysVisible || !!text?.trim().length,
52
+ [isSendButtonAlwaysVisible, text]
72
53
  )
73
54
 
74
55
  if (!showSend)
@@ -77,12 +58,11 @@ export const Send = <TMessage extends IMessage = IMessage>({
77
58
  return (
78
59
  <TouchableOpacity
79
60
  testID={TEST_ID.SEND_TOUCHABLE}
80
- accessible
81
- accessibilityLabel='send'
82
61
  style={[styles.container, containerStyle]}
83
62
  onPress={handleOnPress}
63
+ accessible
64
+ accessibilityLabel='send'
84
65
  accessibilityRole='button'
85
- disabled={disabled}
86
66
  {...sendButtonProps}
87
67
  >
88
68
  <View>
@@ -91,3 +71,22 @@ export const Send = <TMessage extends IMessage = IMessage>({
91
71
  </TouchableOpacity>
92
72
  )
93
73
  }
74
+
75
+ const styles = StyleSheet.create({
76
+ container: {
77
+ height: 44,
78
+ justifyContent: 'flex-end',
79
+ },
80
+ text: {
81
+ color: Color.defaultBlue,
82
+ fontWeight: '600',
83
+ fontSize: 17,
84
+ backgroundColor: Color.backgroundTransparent,
85
+ marginBottom: 12,
86
+ marginLeft: 10,
87
+ marginRight: 10,
88
+ },
89
+ text_dark: {
90
+ color: '#4da6ff',
91
+ },
92
+ })
@@ -8,8 +8,8 @@ import {
8
8
  } from 'react-native'
9
9
  import { Color } from './Color'
10
10
  import { MessageText } from './MessageText'
11
+ import { IMessage } from './Models'
11
12
  import stylesCommon from './styles'
12
- import { IMessage } from './types'
13
13
 
14
14
  export interface SystemMessageProps<TMessage extends IMessage> {
15
15
  currentMessage: TMessage
package/src/Time.tsx CHANGED
@@ -5,7 +5,7 @@ import dayjs from 'dayjs'
5
5
  import { Color } from './Color'
6
6
  import { TIME_FORMAT } from './Constant'
7
7
  import { useChatContext } from './GiftedChatContext'
8
- import { LeftRightStyle, IMessage } from './types'
8
+ import { LeftRightStyle, IMessage } from './Models'
9
9
 
10
10
  const { containerStyle } = StyleSheet.create({
11
11
  containerStyle: {
@@ -1,7 +1,6 @@
1
1
  import React from 'react'
2
2
  import { render } from '@testing-library/react-native'
3
3
 
4
- import { useReanimatedKeyboardAnimation } from 'react-native-keyboard-controller'
5
4
  import { GiftedChat } from '..'
6
5
 
7
6
  const messages = [
@@ -17,32 +16,6 @@ const messages = [
17
16
  ]
18
17
 
19
18
  it('should render <GiftedChat/> and compare with snapshot', () => {
20
- (useReanimatedKeyboardAnimation as jest.Mock).mockReturnValue({
21
- height: {
22
- value: 0,
23
- },
24
- })
25
-
26
- const { toJSON } = render(
27
- <GiftedChat
28
- messages={messages}
29
- onSend={() => {}}
30
- user={{
31
- _id: 1,
32
- }}
33
- />
34
- )
35
-
36
- expect(toJSON()).toMatchSnapshot()
37
- })
38
-
39
- it('should render <GiftedChat/> with isKeyboardInternallyHandled=false', () => {
40
- (useReanimatedKeyboardAnimation as jest.Mock).mockReturnValue({
41
- height: {
42
- value: 0,
43
- },
44
- })
45
-
46
19
  const { toJSON } = render(
47
20
  <GiftedChat
48
21
  messages={messages}
@@ -50,7 +23,6 @@ it('should render <GiftedChat/> with isKeyboardInternallyHandled=false', () => {
50
23
  user={{
51
24
  _id: 1,
52
25
  }}
53
- isKeyboardInternallyHandled={false}
54
26
  />
55
27
  )
56
28
 
@@ -38,7 +38,7 @@ describe('Message component', () => {
38
38
  user={{ _id: 1 }}
39
39
  currentMessage={DEFAULT_TEST_MESSAGE}
40
40
  position='left'
41
- showUserAvatar
41
+ isUserAvatarVisible
42
42
  />
43
43
  )
44
44
 
@@ -58,7 +58,7 @@ describe('Message component', () => {
58
58
  },
59
59
  }}
60
60
  position='left'
61
- showUserAvatar
61
+ isUserAvatarVisible
62
62
  />
63
63
  )
64
64
 
@@ -10,7 +10,7 @@ describe('Send', () => {
10
10
  })
11
11
 
12
12
  it('should always render <Send /> and compare with snapshot', () => {
13
- const { toJSON } = render(<Send alwaysShowSend />)
13
+ const { toJSON } = render(<Send isSendButtonAlwaysVisible />)
14
14
  expect(toJSON()).toMatchSnapshot()
15
15
  })
16
16
 
@@ -6,7 +6,7 @@ exports[`should render <Actions /> and compare with snapshot 1`] = `
6
6
  {
7
7
  "busy": undefined,
8
8
  "checked": undefined,
9
- "disabled": undefined,
9
+ "disabled": false,
10
10
  "expanded": undefined,
11
11
  "selected": undefined,
12
12
  }
@@ -20,97 +20,13 @@ exports[`should render <Actions /> and compare with snapshot 1`] = `
20
20
  }
21
21
  }
22
22
  accessible={true}
23
- collapsable={false}
24
23
  focusable={true}
25
- onBlur={[Function]}
26
24
  onClick={[Function]}
27
- onFocus={[Function]}
28
25
  onResponderGrant={[Function]}
29
26
  onResponderMove={[Function]}
30
27
  onResponderRelease={[Function]}
31
28
  onResponderTerminate={[Function]}
32
29
  onResponderTerminationRequest={[Function]}
33
30
  onStartShouldSetResponder={[Function]}
34
- >
35
- <View
36
- collapsable={false}
37
- jestAnimatedProps={
38
- {
39
- "value": {},
40
- }
41
- }
42
- jestAnimatedStyle={
43
- {
44
- "value": {
45
- "opacity": 1,
46
- },
47
- }
48
- }
49
- jestInlineStyle={
50
- [
51
- [
52
- {
53
- "height": 26,
54
- "marginBottom": 10,
55
- "marginLeft": 10,
56
- "width": 26,
57
- },
58
- undefined,
59
- ],
60
- ]
61
- }
62
- style={
63
- [
64
- {
65
- "height": 26,
66
- "marginBottom": 10,
67
- "marginLeft": 10,
68
- "width": 26,
69
- },
70
- undefined,
71
- {
72
- "opacity": 1,
73
- },
74
- ]
75
- }
76
- >
77
- <View
78
- style={
79
- [
80
- {
81
- "flex": 1,
82
- },
83
- {
84
- "alignItems": "center",
85
- "justifyContent": "center",
86
- },
87
- {
88
- "borderColor": "#b2b2b2",
89
- "borderRadius": 13,
90
- "borderWidth": 2,
91
- },
92
- undefined,
93
- ]
94
- }
95
- >
96
- <Text
97
- style={
98
- [
99
- {
100
- "backgroundColor": "transparent",
101
- "color": "#b2b2b2",
102
- "fontSize": 16,
103
- "fontWeight": "bold",
104
- "lineHeight": 16,
105
- "textAlign": "center",
106
- },
107
- undefined,
108
- ]
109
- }
110
- >
111
- +
112
- </Text>
113
- </View>
114
- </View>
115
- </View>
31
+ />
116
32
  `;