@kesha-antonov/react-native-chat 4.1.0 → 4.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.
Files changed (253) hide show
  1. package/README.md +306 -62
  2. package/lib/Actions.d.ts +5 -5
  3. package/lib/Actions.d.ts.map +1 -1
  4. package/lib/Actions.js +28 -39
  5. package/lib/Actions.js.map +1 -1
  6. package/lib/Avatar.d.ts.map +1 -1
  7. package/lib/Avatar.js +22 -27
  8. package/lib/Avatar.js.map +1 -1
  9. package/lib/Bubble/index.d.ts.map +1 -1
  10. package/lib/Bubble/index.js +138 -37
  11. package/lib/Bubble/index.js.map +1 -1
  12. package/lib/Bubble/styles.d.ts +47 -11
  13. package/lib/Bubble/styles.d.ts.map +1 -1
  14. package/lib/Bubble/styles.js +108 -57
  15. package/lib/Bubble/styles.js.map +1 -1
  16. package/lib/Bubble/types.d.ts +16 -1
  17. package/lib/Bubble/types.d.ts.map +1 -1
  18. package/lib/Chat/ChatKeyboardAvoidingView.d.ts +21 -0
  19. package/lib/Chat/ChatKeyboardAvoidingView.d.ts.map +1 -0
  20. package/lib/Chat/ChatKeyboardAvoidingView.js +15 -0
  21. package/lib/Chat/ChatKeyboardAvoidingView.js.map +1 -0
  22. package/lib/Chat/index.d.ts.map +1 -1
  23. package/lib/Chat/index.js +81 -28
  24. package/lib/Chat/index.js.map +1 -1
  25. package/lib/Chat/types.d.ts +36 -6
  26. package/lib/Chat/types.d.ts.map +1 -1
  27. package/lib/ChatAvatar.d.ts.map +1 -1
  28. package/lib/ChatAvatar.js +11 -17
  29. package/lib/ChatAvatar.js.map +1 -1
  30. package/lib/ChatContext.d.ts +29 -1
  31. package/lib/ChatContext.d.ts.map +1 -1
  32. package/lib/ChatContext.js +3 -0
  33. package/lib/ChatContext.js.map +1 -1
  34. package/lib/Color.d.ts.map +1 -1
  35. package/lib/Color.js +6 -2
  36. package/lib/Color.js.map +1 -1
  37. package/lib/Composer.d.ts +14 -1
  38. package/lib/Composer.d.ts.map +1 -1
  39. package/lib/Composer.js +63 -50
  40. package/lib/Composer.js.map +1 -1
  41. package/lib/Day/index.d.ts.map +1 -1
  42. package/lib/Day/index.js +7 -3
  43. package/lib/Day/index.js.map +1 -1
  44. package/lib/Day/styles.d.ts +6 -7
  45. package/lib/Day/styles.d.ts.map +1 -1
  46. package/lib/Day/styles.js +11 -13
  47. package/lib/Day/styles.js.map +1 -1
  48. package/lib/Icons.d.ts +24 -0
  49. package/lib/Icons.d.ts.map +1 -0
  50. package/lib/Icons.js +2 -0
  51. package/lib/Icons.js.map +1 -0
  52. package/lib/InputToolbar.d.ts +18 -5
  53. package/lib/InputToolbar.d.ts.map +1 -1
  54. package/lib/InputToolbar.js +223 -19
  55. package/lib/InputToolbar.js.map +1 -1
  56. package/lib/LoadEarlierMessages.d.ts.map +1 -1
  57. package/lib/LoadEarlierMessages.js +12 -7
  58. package/lib/LoadEarlierMessages.js.map +1 -1
  59. package/lib/Message/index.d.ts.map +1 -1
  60. package/lib/Message/index.js +10 -9
  61. package/lib/Message/index.js.map +1 -1
  62. package/lib/Message/styles.d.ts +1 -4
  63. package/lib/Message/styles.d.ts.map +1 -1
  64. package/lib/Message/styles.js +4 -4
  65. package/lib/Message/styles.js.map +1 -1
  66. package/lib/MessageAudio.d.ts +2 -1
  67. package/lib/MessageAudio.d.ts.map +1 -1
  68. package/lib/MessageAudio.js +121 -19
  69. package/lib/MessageAudio.js.map +1 -1
  70. package/lib/MessageLocation.d.ts +9 -0
  71. package/lib/MessageLocation.d.ts.map +1 -0
  72. package/lib/MessageLocation.js +85 -0
  73. package/lib/MessageLocation.js.map +1 -0
  74. package/lib/MessageText.d.ts +12 -1
  75. package/lib/MessageText.d.ts.map +1 -1
  76. package/lib/MessageText.js +38 -11
  77. package/lib/MessageText.js.map +1 -1
  78. package/lib/MessageVideo.d.ts +2 -1
  79. package/lib/MessageVideo.d.ts.map +1 -1
  80. package/lib/MessageVideo.js +156 -18
  81. package/lib/MessageVideo.js.map +1 -1
  82. package/lib/MessagesContainer/FlashList.d.ts +9 -0
  83. package/lib/MessagesContainer/FlashList.d.ts.map +1 -0
  84. package/lib/MessagesContainer/FlashList.js +23 -0
  85. package/lib/MessagesContainer/FlashList.js.map +1 -0
  86. package/lib/MessagesContainer/components/Item/index.d.ts +2 -1
  87. package/lib/MessagesContainer/components/Item/index.d.ts.map +1 -1
  88. package/lib/MessagesContainer/components/Item/index.js +23 -1
  89. package/lib/MessagesContainer/components/Item/index.js.map +1 -1
  90. package/lib/MessagesContainer/index.d.ts.map +1 -1
  91. package/lib/MessagesContainer/index.js +133 -42
  92. package/lib/MessagesContainer/index.js.map +1 -1
  93. package/lib/MessagesContainer/styles.d.ts +23 -7
  94. package/lib/MessagesContainer/styles.d.ts.map +1 -1
  95. package/lib/MessagesContainer/styles.js +25 -9
  96. package/lib/MessagesContainer/styles.js.map +1 -1
  97. package/lib/MessagesContainer/types.d.ts +22 -2
  98. package/lib/MessagesContainer/types.d.ts.map +1 -1
  99. package/lib/Models.d.ts +72 -0
  100. package/lib/Models.d.ts.map +1 -1
  101. package/lib/QuickReplies.d.ts +1 -1
  102. package/lib/QuickReplies.d.ts.map +1 -1
  103. package/lib/QuickReplies.js +13 -7
  104. package/lib/QuickReplies.js.map +1 -1
  105. package/lib/Reactions/MessageReactions.d.ts.map +1 -1
  106. package/lib/Reactions/MessageReactions.js +11 -10
  107. package/lib/Reactions/MessageReactions.js.map +1 -1
  108. package/lib/Reactions/ReactionPicker.d.ts.map +1 -1
  109. package/lib/Reactions/ReactionPicker.js +5 -3
  110. package/lib/Reactions/ReactionPicker.js.map +1 -1
  111. package/lib/Send.d.ts +2 -2
  112. package/lib/Send.d.ts.map +1 -1
  113. package/lib/Send.js +37 -37
  114. package/lib/Send.js.map +1 -1
  115. package/lib/SystemMessage.d.ts.map +1 -1
  116. package/lib/SystemMessage.js +8 -5
  117. package/lib/SystemMessage.js.map +1 -1
  118. package/lib/Theme.d.ts +148 -0
  119. package/lib/Theme.d.ts.map +1 -0
  120. package/lib/Theme.js +142 -0
  121. package/lib/Theme.js.map +1 -0
  122. package/lib/Time.d.ts.map +1 -1
  123. package/lib/Time.js +7 -5
  124. package/lib/Time.js.map +1 -1
  125. package/lib/TypingIndicator/index.d.ts.map +1 -1
  126. package/lib/TypingIndicator/index.js +4 -1
  127. package/lib/TypingIndicator/index.js.map +1 -1
  128. package/lib/TypingIndicator/styles.d.ts +2 -2
  129. package/lib/TypingIndicator/styles.d.ts.map +1 -1
  130. package/lib/TypingIndicator/styles.js +4 -5
  131. package/lib/TypingIndicator/styles.js.map +1 -1
  132. package/lib/components/AttachmentSheet.d.ts +28 -0
  133. package/lib/components/AttachmentSheet.d.ts.map +1 -0
  134. package/lib/components/AttachmentSheet.js +177 -0
  135. package/lib/components/AttachmentSheet.js.map +1 -0
  136. package/lib/components/BasicMarkdown.d.ts +12 -0
  137. package/lib/components/BasicMarkdown.d.ts.map +1 -0
  138. package/lib/components/BasicMarkdown.js +220 -0
  139. package/lib/components/BasicMarkdown.js.map +1 -0
  140. package/lib/components/ContextMenu.d.ts +25 -0
  141. package/lib/components/ContextMenu.d.ts.map +1 -0
  142. package/lib/components/ContextMenu.js +122 -0
  143. package/lib/components/ContextMenu.js.map +1 -0
  144. package/lib/components/Icon.d.ts +17 -0
  145. package/lib/components/Icon.d.ts.map +1 -0
  146. package/lib/components/Icon.js +19 -0
  147. package/lib/components/Icon.js.map +1 -0
  148. package/lib/components/LucideIcon.d.ts +10 -0
  149. package/lib/components/LucideIcon.d.ts.map +1 -0
  150. package/lib/components/LucideIcon.js +118 -0
  151. package/lib/components/LucideIcon.js.map +1 -0
  152. package/lib/components/MarkdownMessageText.d.ts +19 -0
  153. package/lib/components/MarkdownMessageText.d.ts.map +1 -0
  154. package/lib/components/MarkdownMessageText.js +35 -0
  155. package/lib/components/MarkdownMessageText.js.map +1 -0
  156. package/lib/components/MediaCard.d.ts +19 -0
  157. package/lib/components/MediaCard.d.ts.map +1 -0
  158. package/lib/components/MediaCard.js +102 -0
  159. package/lib/components/MediaCard.js.map +1 -0
  160. package/lib/components/MediaControls.d.ts +41 -0
  161. package/lib/components/MediaControls.d.ts.map +1 -0
  162. package/lib/components/MediaControls.js +523 -0
  163. package/lib/components/MediaControls.js.map +1 -0
  164. package/lib/components/MessageReply.d.ts.map +1 -1
  165. package/lib/components/MessageReply.js +11 -9
  166. package/lib/components/MessageReply.js.map +1 -1
  167. package/lib/components/ReplyPreview.d.ts +3 -1
  168. package/lib/components/ReplyPreview.d.ts.map +1 -1
  169. package/lib/components/ReplyPreview.js +36 -36
  170. package/lib/components/ReplyPreview.js.map +1 -1
  171. package/lib/components/SendIcon.d.ts +13 -0
  172. package/lib/components/SendIcon.d.ts.map +1 -0
  173. package/lib/components/SendIcon.js +37 -0
  174. package/lib/components/SendIcon.js.map +1 -0
  175. package/lib/components/Ticks.d.ts +25 -0
  176. package/lib/components/Ticks.d.ts.map +1 -0
  177. package/lib/components/Ticks.js +102 -0
  178. package/lib/components/Ticks.js.map +1 -0
  179. package/lib/components/VideoNoteOverlay.d.ts +25 -0
  180. package/lib/components/VideoNoteOverlay.d.ts.map +1 -0
  181. package/lib/components/VideoNoteOverlay.js +151 -0
  182. package/lib/components/VideoNoteOverlay.js.map +1 -0
  183. package/lib/components/VideoNoteRecorder.d.ts +19 -0
  184. package/lib/components/VideoNoteRecorder.d.ts.map +1 -0
  185. package/lib/components/VideoNoteRecorder.js +634 -0
  186. package/lib/components/VideoNoteRecorder.js.map +1 -0
  187. package/lib/components/VideoRecordButton.d.ts +29 -0
  188. package/lib/components/VideoRecordButton.d.ts.map +1 -0
  189. package/lib/components/VideoRecordButton.js +117 -0
  190. package/lib/components/VideoRecordButton.js.map +1 -0
  191. package/lib/components/VoiceMessageInput.d.ts +33 -0
  192. package/lib/components/VoiceMessageInput.d.ts.map +1 -0
  193. package/lib/components/VoiceMessageInput.js +396 -0
  194. package/lib/components/VoiceMessageInput.js.map +1 -0
  195. package/lib/components/WaveformPlayer.d.ts +19 -0
  196. package/lib/components/WaveformPlayer.d.ts.map +1 -0
  197. package/lib/components/WaveformPlayer.js +367 -0
  198. package/lib/components/WaveformPlayer.js.map +1 -0
  199. package/lib/components/mediaPalette.d.ts +23 -0
  200. package/lib/components/mediaPalette.d.ts.map +1 -0
  201. package/lib/components/mediaPalette.js +16 -0
  202. package/lib/components/mediaPalette.js.map +1 -0
  203. package/lib/components/mediaPlayback.d.ts +22 -0
  204. package/lib/components/mediaPlayback.d.ts.map +1 -0
  205. package/lib/components/mediaPlayback.js +34 -0
  206. package/lib/components/mediaPlayback.js.map +1 -0
  207. package/lib/hooks/useColorScheme.d.ts +9 -2
  208. package/lib/hooks/useColorScheme.d.ts.map +1 -1
  209. package/lib/hooks/useColorScheme.js +9 -3
  210. package/lib/hooks/useColorScheme.js.map +1 -1
  211. package/lib/hooks/useHasKeyboardProvider.d.ts +19 -0
  212. package/lib/hooks/useHasKeyboardProvider.d.ts.map +1 -0
  213. package/lib/hooks/useHasKeyboardProvider.js +31 -0
  214. package/lib/hooks/useHasKeyboardProvider.js.map +1 -0
  215. package/lib/hooks/useIcons.d.ts +4 -0
  216. package/lib/hooks/useIcons.d.ts.map +1 -0
  217. package/lib/hooks/useIcons.js +8 -0
  218. package/lib/hooks/useIcons.js.map +1 -0
  219. package/lib/hooks/useIsKeyboardVisible.d.ts +12 -0
  220. package/lib/hooks/useIsKeyboardVisible.d.ts.map +1 -0
  221. package/lib/hooks/useIsKeyboardVisible.js +31 -0
  222. package/lib/hooks/useIsKeyboardVisible.js.map +1 -0
  223. package/lib/hooks/useKeyboardVerticalOffset.d.ts +28 -0
  224. package/lib/hooks/useKeyboardVerticalOffset.d.ts.map +1 -0
  225. package/lib/hooks/useKeyboardVerticalOffset.js +43 -0
  226. package/lib/hooks/useKeyboardVerticalOffset.js.map +1 -0
  227. package/lib/hooks/useLabels.d.ts +4 -0
  228. package/lib/hooks/useLabels.d.ts.map +1 -0
  229. package/lib/hooks/useLabels.js +8 -0
  230. package/lib/hooks/useLabels.js.map +1 -0
  231. package/lib/hooks/useTheme.d.ts +21 -0
  232. package/lib/hooks/useTheme.d.ts.map +1 -0
  233. package/lib/hooks/useTheme.js +32 -0
  234. package/lib/hooks/useTheme.js.map +1 -0
  235. package/lib/i18n.d.ts +59 -0
  236. package/lib/i18n.d.ts.map +1 -0
  237. package/lib/i18n.js +122 -0
  238. package/lib/i18n.js.map +1 -0
  239. package/lib/index.d.ts +17 -0
  240. package/lib/index.d.ts.map +1 -1
  241. package/lib/index.js +17 -0
  242. package/lib/index.js.map +1 -1
  243. package/lib/types.d.ts +2 -1
  244. package/lib/types.d.ts.map +1 -1
  245. package/package.json +103 -41
  246. package/lib/MessageReply.d.ts +0 -15
  247. package/lib/MessageReply.d.ts.map +0 -1
  248. package/lib/MessageReply.js +0 -102
  249. package/lib/MessageReply.js.map +0 -1
  250. package/lib/ReplyPreview.d.ts +0 -14
  251. package/lib/ReplyPreview.d.ts.map +0 -1
  252. package/lib/ReplyPreview.js +0 -94
  253. package/lib/ReplyPreview.js.map +0 -1
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  <p align="center">
2
2
  <a href="https://www.npmjs.com/package/@kesha-antonov/react-native-chat"><img alt="npm version" src="https://badge.fury.io/js/@kesha-antonov%2Freact-native-chat.svg"/></a>
3
3
  <a href="https://www.npmjs.com/package/@kesha-antonov/react-native-chat"><img alt="npm downloads" src="https://img.shields.io/npm/dm/@kesha-antonov%2Freact-native-chat.svg"/></a>
4
+ <a href="https://npm-stat.com/charts.html?package=%40kesha-antonov%2Freact-native-chat&from=2015-01-01"><img alt="total npm downloads" src="https://img.shields.io/badge/total%20downloads-466-blue.svg"/></a>
5
+ <a href="https://www.npmjs.com/package/@kesha-antonov/react-native-chat"><img alt="npm downloads (last 18 months)" src="https://img.shields.io/npm/dt/@kesha-antonov%2Freact-native-chat.svg?label=18-months%20downloads"/></a>
4
6
  <a href="https://github.com/kesha-antonov/react-native-chat/actions/workflows/main.yml"><img src="https://github.com/kesha-antonov/react-native-chat/actions/workflows/main.yml/badge.svg" alt="build"></a>
5
7
  <img src="https://img.shields.io/badge/platforms-iOS%20%7C%20Android%20%7C%20Web-lightgrey.svg" alt="platforms">
6
8
  <img src="https://img.shields.io/badge/TypeScript-supported-blue.svg" alt="TypeScript">
@@ -13,17 +15,34 @@
13
15
  The most complete chat UI for React Native & Web
14
16
  </p>
15
17
 
18
+ <p align="center">
19
+ <img width="300" src="https://raw.githubusercontent.com/kesha-antonov/react-native-chat/main/media/streaming-demo.gif" alt="Streaming AI reply rendering markdown token by token" />
20
+ </p>
21
+
16
22
  <p align="center">
17
23
  <a href="https://snack.expo.dev/@kesha-antonov/react-native-chat-playground" target="_blank">
18
24
  <img src="https://img.shields.io/badge/▶️_Try_in_Browser-4630EB?style=for-the-badge&logo=expo&logoColor=white" alt="Try Chat on Expo Snack"/>
19
25
  </a>
20
26
  </p>
21
27
 
28
+ <p align="center">
29
+ <strong>Coming from <code>react-native-gifted-chat</code>?</strong> This is a drop-in replacement -
30
+ same <code>IMessage</code>, same props, a few renamed exports.
31
+ <br/>
32
+ <a href="docs/MIGRATION.md">Migrate in one command with the codemod →</a>
33
+ </p>
34
+
22
35
  ---
23
36
 
24
37
  ## ✨ Features
25
38
 
39
+ > Actively maintained, New Architecture ready, and built for the latest Reanimated/Gesture Handler. A modern, themeable, drop-in successor to `react-native-gifted-chat`. **[See what is new ▸](#-whats-new-vs-react-native-gifted-chat)**
40
+
41
+ - 🌗 **[Modern UI, Dark Mode & Theming](#-whats-new-vs-react-native-gifted-chat)** - A clean, modern default look with a full light/dark theme system, runtime theme switching, and every token overridable
26
42
  - 🤖 **[Streaming (AI) Messages](#streaming-ai-messages)** - Token-by-token streamed replies with a typing cursor and stop control
43
+ - 🎥 **[Video & Audio Messages](#-whats-new-vs-react-native-gifted-chat)** - Real inline playback (optional `expo-video` / `expo-audio`) with a graceful tappable fallback - no more "implement it yourself"
44
+ - 🎙️ **[Voice & Video Recording](#-whats-new-vs-react-native-gifted-chat)** - Telegram-style hold-to-record voice notes and camera video messages (optional, opt-in)
45
+ - 📍 **[Location Messages](#-whats-new-vs-react-native-gifted-chat)** - Map card that opens the system maps app on tap
27
46
  - 🎨 **[Fully Customizable](#-props-reference)** - Override any component with your own implementation
28
47
  - 📎 **[Composer Actions](#actions--action-sheet)** - Attach photos, files, or trigger custom actions
29
48
  - ↩️ **[Reply to Messages](#reply-to-messages)** - Swipe-to-reply with reply preview and message threading
@@ -53,6 +72,75 @@
53
72
 
54
73
  ---
55
74
 
75
+ ## 🆕 What's new vs react-native-gifted-chat
76
+
77
+ This library is kept in sync with upstream [`react-native-gifted-chat`](https://github.com/FaridSafi/react-native-gifted-chat)'s latest `master`, so you **keep everything it already has** - New Architecture support, the animated sticky day header, Reanimated 3/4, Gesture Handler, TypeScript - and the same `IMessage` model and prop names. **Moving over is a package swap plus a rename** (see [Migrating](#-migrating-from-react-native-gifted-chat)).
78
+
79
+ On top of that, this fork adds the features people kept asking upstream for, plus a modern look out of the box:
80
+
81
+ | Added in this fork | react-native-gifted-chat | @kesha-antonov/react-native-chat |
82
+ | --- | :---: | :---: |
83
+ | Active maintenance | 💤 sporadic | ✅ active |
84
+ | Modern default UI (Telegram-inspired) | dated 2020 look | ✅ modern, fully overridable |
85
+ | Light/Dark **theme system** | per-component color props | ✅ `theme` / `darkTheme` tokens, runtime switch |
86
+ | Streaming (AI) messages | ❌ | ✅ token-by-token + typing cursor |
87
+ | Emoji reactions | ❌ | ✅ long-press picker + reaction pills |
88
+ | Swipe-to-reply + reply preview | ❌ | ✅ built in |
89
+ | **Video / audio messages** | "not implemented, render your own" | ✅ inline players + tappable fallback |
90
+ | **Voice notes** (hold-to-record + waveform) | ❌ | ✅ optional, Telegram-style |
91
+ | **Video messages** (round camera notes) | ❌ | ✅ optional, Telegram-style |
92
+ | **Location messages** | ❌ ignored | ✅ map card → opens system maps |
93
+ | Bubble tails + tighter message grouping | flat bubbles | ✅ |
94
+
95
+ Everything new is **non-breaking and opt-in**: keep passing the same props you do today and you simply get the modern look and the extra features for free. The media/recording features only activate when you install their optional peer deps and enable them - nobody is forced to add a single dependency.
96
+
97
+ ### Theming in one line
98
+
99
+ ```jsx
100
+ // Modern defaults out of the box, or override any token (light + dark):
101
+ <Chat
102
+ theme={{ colors: { accent: '#3390EC', outgoingBubble: '#EFFEDE' } }}
103
+ darkTheme={{ colors: { background: '#0E1621' } }}
104
+ {...props}
105
+ />
106
+ ```
107
+
108
+ ### Voice, video and location
109
+
110
+ ```jsx
111
+ <Chat
112
+ audioRecording={{ isEnabled: true }} // hold the mic to record a voice note (needs expo-audio)
113
+ videoRecording={{ isEnabled: true }} // record a video message (needs expo-image-picker)
114
+ // location messages render automatically for any IMessage with a `location`
115
+ {...props}
116
+ />
117
+ ```
118
+
119
+ ```bash
120
+ # Optional inline media playback + recording:
121
+ npx expo install expo-video expo-audio expo-image-picker
122
+ ```
123
+
124
+ ### Custom icons (e.g. Lucide)
125
+
126
+ Built-in icons are the official [Lucide](https://lucide.dev) glyphs, rendered via the optional `react-native-svg` peer when it is installed, or drawn with `View`s (no dependency) otherwise. Override any of them via the `icons` prop - for example with [`lucide-react-native`](https://lucide.dev/) - and the built-in icon is used for anything you don't override:
127
+
128
+ ```tsx
129
+ import { Send, Mic } from 'lucide-react-native'
130
+
131
+ <Chat
132
+ icons={{
133
+ send: ({ color, size }) => <Send color={color} size={size} />,
134
+ mic: ({ color, size }) => <Mic color={color} size={size} />,
135
+ }}
136
+ {...props}
137
+ />
138
+ ```
139
+
140
+ Overridable names: `send`, `mic`, `camera`, `play`, `pause`, `check`, `checkAll`, `clock`, `pin`, `plus`, `close`, `chevronLeft`, `chevronDown`, `emoji`, `paperclip`, `reply`, `pencil`, `lock`, `trash`.
141
+
142
+ ---
143
+
56
144
  <h3 align="center">Support This Project</h3>
57
145
 
58
146
  <p align="center">
@@ -68,6 +156,7 @@
68
156
  ## 📖 Table of Contents
69
157
 
70
158
  - [Features](#-features)
159
+ - [What's new vs react-native-gifted-chat](#-whats-new-vs-react-native-gifted-chat)
71
160
  - [Requirements](#-requirements)
72
161
  - [Installation](#-installation)
73
162
  - [Migrating from react-native-gifted-chat](#-migrating-from-react-native-gifted-chat)
@@ -75,6 +164,7 @@
75
164
  - [Props Reference](#-props-reference)
76
165
  - [Data Structure](#-data-structure)
77
166
  - [Platform Notes](#-platform-notes)
167
+ - [Performance](#-performance)
78
168
  - [Example App](#-example-app)
79
169
  - [Troubleshooting](#-troubleshooting)
80
170
  - [Contributing](#-contributing)
@@ -100,7 +190,7 @@
100
190
  ### Expo Projects
101
191
 
102
192
  ```bash
103
- npx expo install react-native-chat react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller
193
+ npx expo install @kesha-antonov/react-native-chat react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller
104
194
  ```
105
195
 
106
196
  ### Bare React Native Projects
@@ -109,12 +199,12 @@ npx expo install react-native-chat react-native-reanimated react-native-gesture-
109
199
 
110
200
  Using yarn:
111
201
  ```bash
112
- yarn add react-native-chat react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller
202
+ yarn add @kesha-antonov/react-native-chat react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller
113
203
  ```
114
204
 
115
205
  Using npm:
116
206
  ```bash
117
- npm install --save react-native-chat react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller
207
+ npm install --save @kesha-antonov/react-native-chat react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller
118
208
  ```
119
209
 
120
210
  **Step 2:** Install iOS pods
@@ -157,15 +247,10 @@ See the full guide (codemod included) in **[docs/MIGRATION.md](docs/MIGRATION.md
157
247
  ```jsx
158
248
  import React, { useState, useCallback, useEffect } from 'react'
159
249
  import { Chat } from '@kesha-antonov/react-native-chat'
160
- import { useHeaderHeight } from '@react-navigation/elements'
161
250
 
162
251
  export function Example() {
163
252
  const [messages, setMessages] = useState([])
164
253
 
165
- // keyboardVerticalOffset = distance from screen top to Chat container
166
- // useHeaderHeight() returns status bar + navigation header height
167
- const headerHeight = useHeaderHeight()
168
-
169
254
  useEffect(() => {
170
255
  setMessages([
171
256
  {
@@ -194,7 +279,6 @@ export function Example() {
194
279
  user={{
195
280
  _id: 1,
196
281
  }}
197
- keyboardAvoidingViewProps={{ keyboardVerticalOffset: headerHeight }}
198
282
  />
199
283
  )
200
284
  }
@@ -269,6 +353,10 @@ interface User {
269
353
  - **`messageIdGenerator`** _(Function)_ - Generate an id for new messages. Defaults to a simple random string generator.
270
354
  - **`locale`** _(String)_ - Locale to localize the dates. You need first to import the locale you need (ie. `require('dayjs/locale/de')` or `import 'dayjs/locale/fr'`)
271
355
  - **`colorScheme`** _('light' | 'dark')_ - Force color scheme (light/dark mode). When set to `'light'` or `'dark'`, it overrides the system color scheme. When `undefined`, it uses the system color scheme. Default is `undefined`.
356
+ - **`theme`** _(Object)_ - Override the default light theme tokens (`colors` / `radii` / `spacing` / `typography` / `avatar` / `sendButton` / `composer` / `voice`). Deep-merged over `defaultLightTheme`; any subset is allowed. See [Theming & Dark Mode](#theming--dark-mode).
357
+ - **`darkTheme`** _(Object)_ - Same as `theme`, applied when the resolved color scheme is dark (deep-merged over `defaultDarkTheme`).
358
+ - **`icons`** _(Object)_ - Icon override registry. Supply a render function for any built-in icon to replace it (e.g. with `lucide-react-native`). See [Custom icons](#custom-icons-eg-lucide).
359
+ - **`labels`** _(Object)_ - Override any UI string. See [Localization (i18n)](#localization-i18n).
272
360
 
273
361
  ### Refs
274
362
 
@@ -277,44 +365,29 @@ interface User {
277
365
 
278
366
  ### Keyboard & Layout
279
367
 
280
- - **`keyboardProviderProps`** _(Object)_ - Props to be passed to the [`KeyboardProvider`](https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-provider) for keyboard handling. Default values:
281
- - `statusBarTranslucent: true` - Required on Android for correct keyboard height calculation when status bar is translucent (edge-to-edge mode)
282
- - `navigationBarTranslucent: true` - Required on Android for correct keyboard height calculation when navigation bar is translucent (edge-to-edge mode)
368
+ - **`keyboardProviderProps`** _(Object)_ - Props to be passed to the [`KeyboardProvider`](https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-provider) for keyboard handling. No defaults are applied - in particular Chat does **not** set `statusBarTranslucent` / `navigationBarTranslucent`, because on Android those change the *activity window* and the change outlives the chat screen ([#2755](https://github.com/FaridSafi/react-native-gifted-chat/issues/2755)). `react-native-keyboard-controller` turns them on by itself when the app is genuinely edge-to-edge, so there is nothing to set in a normal app.
369
+
370
+ Only used when Chat mounts the provider itself. If your app already mounts a `KeyboardProvider` (the setup `react-native-keyboard-controller` recommends - once, at the root), Chat detects it and reuses it instead of nesting a second one, and this prop is ignored. Configure the provider where you mount it.
371
+ - **`disableKeyboardProvider`** _(Bool)_ - Skip the built-in `KeyboardProvider` entirely; default is `false`. You do **not** need this just because your app mounts its own provider - that case is detected and reused. Reach for it only to opt out completely, e.g. when the provider's edge-to-edge behavior causes layout shift or a header jump on Android/Expo.
283
372
  - **`keyboardAvoidingViewProps`** _(Object)_ - Props to be passed to the [`KeyboardAvoidingView`](https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/components/keyboard-avoiding-view). See **keyboardVerticalOffset** below for proper keyboard handling.
284
- - **`isAlignedTop`** _(Boolean)_ Controls whether or not the message bubbles appear at the top of the chat (Default is false - bubbles align to bottom)
373
+ - **`isAlignedTop`** _(`boolean | 'auto'`)_ - Where the bubbles sit **while the whole conversation fits on screen**; once it is taller than the list this has no effect. `false` (default) keeps the usual bottom-anchored chat, `true` pins the messages to the top, and `'auto'` pins them to the top while the keyboard is closed and re-anchors them to the bottom while it is open - so a short conversation starts under the header and moves above the keyboard when the composer is focused ([#2736](https://github.com/FaridSafi/react-native-gifted-chat/issues/2736)). Works with either `isInverted` setting; ignored when `isFlashListEnabled` is set, since FlashList positions its own items.
285
374
  - **`isInverted`** _(Bool)_ - Reverses display order of `messages`; default is `true`
286
375
 
287
376
  #### Understanding `keyboardVerticalOffset`
288
377
 
289
- The [`keyboardVerticalOffset`](https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/components/keyboard-avoiding-view#keyboardverticaloffset) tells the KeyboardAvoidingView where its container starts relative to the top of the screen. This is essential when Chat is not positioned at the very top of the screen (e.g., when you have a navigation header).
290
-
291
- **Default value:** `insets.top` (status bar height from `useSafeAreaInsets()`). This works correctly only when Chat fills the entire screen without a navigation header. If you have a navigation header, you need to pass the correct offset via `keyboardAvoidingViewProps`.
378
+ [`keyboardVerticalOffset`](https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/components/keyboard-avoiding-view#keyboardverticaloffset) tells the KeyboardAvoidingView how far down the screen its container starts. That distance depends on the navigation header and on anything else you render above the chat.
292
379
 
293
- **What the value means:** The offset equals the distance (in points) from the top of the screen to the top of your Chat container. This typically includes:
294
- - Status bar height
295
- - Navigation header height (on iOS, `useHeaderHeight()` already includes status bar)
380
+ **You do not normally need to set it.** Chat measures its own position on screen and uses that, so the input toolbar sits on the keyboard whether the chat is full-screen or under a navigation header. The measurement comes from the `SafeAreaProvider` frame and updates on rotation and layout changes.
296
381
 
297
- **How to use:**
382
+ Pass your own value only to add extra space above the keyboard - it replaces the measured one:
298
383
 
299
384
  ```jsx
300
- import { useHeaderHeight } from '@react-navigation/elements'
301
-
302
- function ChatScreen() {
303
- // useHeaderHeight() returns status bar + navigation header height on iOS
304
- const headerHeight = useHeaderHeight()
305
-
306
- return (
307
- <Chat
308
- keyboardAvoidingViewProps={{ keyboardVerticalOffset: headerHeight }}
309
- // ... other props
310
- />
311
- )
312
- }
385
+ <Chat keyboardAvoidingViewProps={{ keyboardVerticalOffset: headerHeight + 16 }} />
313
386
  ```
314
387
 
315
- > **Note:** `useHeaderHeight()` requires your chat component to be rendered inside a proper navigation screen (not conditional rendering). If it returns `0`, ensure your chat screen is a real navigation screen with a visible header.
388
+ If you do, sanity-check it on device: a toolbar behind the keyboard means the value is too small, a gap above the keyboard means it is too large. `useHeaderHeight()` is the usual source, but some navigator setups report a value that does not match the header you actually render.
316
389
 
317
- **Why this matters:** Without the correct offset, the keyboard may overlap the input field or leave extra space. The KeyboardAvoidingView uses this value to calculate how much to shift the content when the keyboard appears.
390
+ > **Upgrading from 4.1.0 or earlier:** the default used to be `insets.top`, which could not account for a navigation header - so most apps passed `useHeaderHeight()` to compensate. That is no longer needed; drop it and let Chat measure, or the toolbar will float above the keyboard by the header height.
318
391
 
319
392
  ### Text Input & Composer
320
393
 
@@ -322,22 +395,25 @@ function ChatScreen() {
322
395
  - **`initialText`** _(String)_ - Initial text to display in the input field
323
396
  - **`isSendButtonAlwaysVisible`** _(Bool)_ - Always show send button in input text composer; default `false`, show only when text input is not empty
324
397
  - **`isTextOptional`** _(Bool)_ - Allow sending messages without text (useful for media-only messages); default `false`. Use with `isSendButtonAlwaysVisible` for media attachments.
325
- - **`minComposerHeight`** _(Object)_ - Custom min-height of the composer.
326
- - **`maxComposerHeight`** _(Object)_ - Custom max height of the composer.
327
- - **`minInputToolbarHeight`** _(Integer)_ - Minimum height of the input toolbar; default is `44`
398
+ - **`isMultiline`** _(Bool)_ - Whether the composer accepts multiple lines; default `true`. With `true` the return key inserts a newline and you send with the send button. Set `false` for a single-line composer whose **return key sends the message** (the keyboard's return key becomes "send" and stays open afterwards).
328
399
  - **`renderInputToolbar`** _(Component | Function)_ - Custom message composer container
329
400
  - **`renderComposer`** _(Component | Function)_ - Custom text input message composer
330
401
  - **`renderSend`** _(Component | Function)_ - Custom send button; you can pass children to the original `Send` component quite easily, for example, to use a custom icon ([example](https://github.com/kesha-antonov/react-native-chat/pull/487))
331
402
  - **`renderActions`** _(Component | Function)_ - Custom action button on the left of the message composer
332
403
  - **`renderAccessory`** _(Component | Function)_ - Custom second line of actions below the message composer
404
+ - **`onPressEmoji`** _(Function)_ - Callback for the optional emoji button on the left of the composer field. When omitted, the emoji button is hidden.
405
+ - **`audioRecording`** _(Object)_ - Enable Telegram-style hold-to-record voice notes. `{ isEnabled, minDurationMs?, onError? }`. Requires the optional `expo-audio` peer (and `react-native-audio-api` for the playback waveform); the mic button is hidden when it is absent.
406
+ - **`videoRecording`** _(Object)_ - Enable record-and-send video messages. `{ isEnabled, maxDuration?, onError? }`. Uses `react-native-vision-camera` for round camera notes, falling back to `expo-image-picker`'s system camera.
333
407
  - **`textInputProps`** _(Object)_ - props to be passed to the [`<TextInput>`](https://reactnative.dev/docs/textinput).
334
408
 
409
+ > **Composer height** - there are no height props. The composer starts one line tall and grows with its content. Constrain it through `textInputProps.style` (e.g. `{ maxHeight: 120 }`), which is applied after the measured height and wins.
410
+
335
411
  ### Actions & Action Sheet
336
412
 
337
- - **`onPressActionButton`** _(Function)_ - Callback when the Action button is pressed (if set, the default `actionSheet` will not be used)
338
- - **`actionSheet`** _(Function)_ - Custom action sheet interface for showing action options
339
- - **`actions`** _(Array)_ - Custom action options for the input toolbar action button; array of objects with `title` (string) and `action` (function) properties
340
- - **`actionSheetOptionTintColor`** _(String)_ - Tint color for action sheet options
413
+ - **`actions`** _(Array)_ - Action options for the composer "+" button. Array of `{ title, action }`; add `icon` (and optional `color`) to an action to render a Telegram-style attachment **grid** (tiles) instead of a list. Opens the built-in themed `AttachmentSheet` - no extra dependency.
414
+ - **`onPressActionButton`** _(Function)_ - Callback when the "+" button is pressed (if set, the built-in `AttachmentSheet` is not shown)
415
+ - **`actionSheet`** _(Function)_ - Escape hatch for a custom system action sheet. **The bundled `@expo/react-native-action-sheet` dependency was removed**, so `context.actionSheet()` defaults to a no-op; pass your own implementation (with `ActionSheetProvider` in your tree) if you relied on it.
416
+ - **`actionSheetOptionTintColor`** _(String)_ - Tint color for action labels in the attachment sheet
341
417
 
342
418
  ### Messages & Message Container
343
419
 
@@ -347,6 +423,7 @@ function ChatScreen() {
347
423
  - **`renderChatEmpty`** _(Component | Function)_ - Custom component to render in the ListView when messages are empty
348
424
  - **`renderChatFooter`** _(Component | Function)_ - Custom component to render below the MessagesContainer (separate from the ListView)
349
425
  - **`listProps`** _(Object)_ - Extra props to be passed to the messages [`<FlatList>`](https://reactnative.dev/docs/flatlist). Supports all FlatList props including `maintainVisibleContentPosition` for keeping scroll position when new messages arrive (useful for AI chatbots).
426
+ - **`isFlashListEnabled`** _(Bool)_ - Render messages with [`@shopify/flash-list`](https://shopify.github.io/flash-list/) v2 instead of `FlatList`; default is `false`. See [FlashList](#flashlist-opt-in).
350
427
 
351
428
  ### Message Bubbles & Content
352
429
 
@@ -355,10 +432,13 @@ function ChatScreen() {
355
432
  - **`renderMessageImage`** _(Component | Function)_ - Custom message image
356
433
  - **`renderMessageVideo`** _(Component | Function)_ - Custom message video
357
434
  - **`renderMessageAudio`** _(Component | Function)_ - Custom message audio
435
+ - **`renderMessageLocation`** _(Component | Function)_ - Custom renderer for `IMessage.location`; defaults to a map card that opens the system maps app on tap
436
+ - **`messageActions`** _(Array | Function(`message`))_ - Telegram-style long-press context menu. Each item is `{ label, icon?, onPress, destructive? }`. See [Message actions](#message-actions-long-press-context-menu).
358
437
  - **`renderCustomView`** _(Component | Function)_ - Custom view inside the bubble
359
438
  - **`isCustomViewBottom`** _(Bool)_ - Determine whether renderCustomView is displayed before or after the text, image and video views; default is `false`
360
439
  - **`onPressMessage`** _(Function(`context`, `message`))_ - Callback when a message bubble is pressed
361
440
  - **`onLongPressMessage`** _(Function(`context`, `message`))_ - Callback when a message bubble is long-pressed; you can use this to show action sheets (e.g., copy, delete, reply)
441
+ - **`isMessageGestureEnabled`** _(Bool | Function(`message`))_ - Whether the bubble itself is part of the row's tap / long-press surface that `reactions` and `messageActions` rely on; default is `true`. Pass `false`, or a predicate, for messages that render natively interactive content - the row beside the bubble stays pressable either way. See [Interactive content inside bubbles](#interactive-content-inside-bubbles-video-players-maps).
362
442
  - **`imageProps`** _(Object)_ - Extra props to be passed to the [`<Image>`](https://reactnative.dev/docs/image) component created by the default `renderMessageImage`
363
443
  - **`imageStyle`** _(Object)_ - Custom style for message images
364
444
  - **`videoProps`** _(Object)_ - Extra props to be passed to the video component created by the required `renderMessageVideo`
@@ -444,11 +524,19 @@ See full example in [LinksExample](example/components/chat-examples/LinksExample
444
524
  - **`timeFormat`** _(String)_ - Format to use for rendering times; default is `'LT'` (see [Day.js Format](https://day.js.org/docs/en/display/format))
445
525
  - **`dateFormat`** _(String)_ - Format to use for rendering dates; default is `'D MMMM'` (see [Day.js Format](https://day.js.org/docs/en/display/format))
446
526
  - **`dateFormatCalendar`** _(Object)_ - Format to use for rendering relative times; default is `{ sameDay: '[Today]' }` (see [Day.js Calendar](https://day.js.org/docs/en/plugin/calendar))
447
- - **`renderDay`** _(Component | Function)_ - Custom day above a message
448
- - **`dayProps`** _(Object)_ - Props to pass to the Day component:
449
- - `containerStyle` - Custom style for the day container
450
- - `wrapperStyle` - Custom style for the day wrapper
451
- - `textProps` - Props to pass to the Text component (e.g., `style`, `allowFontScaling`, `numberOfLines`)
527
+ - **`renderDay`** _(Component | Function)_ - Custom day above a message. This is also how the day label is styled - it receives `DayProps` (`createdAt`, `dateFormat`, `dateFormatCalendar`, `containerStyle`, `wrapperStyle`, `textProps`, `isAnimated`), so render the built-in `Day` with the styles you want:
528
+
529
+ ```tsx
530
+ import { Chat, Day, DayProps } from '@kesha-antonov/react-native-chat'
531
+
532
+ <Chat
533
+ renderDay={(props: DayProps) => (
534
+ <Day {...props} wrapperStyle={{ backgroundColor: '#eee' }} textProps={{ style: { color: '#333' } }} />
535
+ )}
536
+ />
537
+ ```
538
+
539
+ `isAnimated` is `true` for the floating header that sticks to the top while scrolling and `false` for the inline separators, so one function can style them differently.
452
540
  - **`renderTime`** _(Component | Function)_ - Custom time inside a message
453
541
  - **`timeTextStyle`** _(Object)_ - Custom text style for time inside messages (supports left/right styles)
454
542
  - **`isDayAnimationEnabled`** _(Bool)_ - Enable animated day label that appears on scroll; default is `true`
@@ -664,9 +752,14 @@ const [isScrolledUp, setIsScrolledUp] = useState(false)
664
752
 
665
753
  Render AI assistant replies token-by-token. The library batches incoming chunks with `requestAnimationFrame` (one render per frame, only the streaming bubble re-renders) and shows a blinking caret while a message is still streaming.
666
754
 
755
+ <p align="center">
756
+ <img width="250" src="https://raw.githubusercontent.com/kesha-antonov/react-native-chat/main/media/ai-streaming-markdown.png" alt="AI reply streaming token-by-token and rendering as markdown, with a blinking caret and a Stop button in the composer" />
757
+ </p>
758
+
759
+ > The reply above streams in token-by-token (note the caret `▋`) and renders as **markdown** - bold, italics, lists, inline and fenced code - while the composer's send button turns into a **Stop** control mid-stream. See [Markdown rendering for AI replies](#markdown-rendering-for-ai-replies) to enable markdown.
760
+
667
761
  - **`IMessage.streaming`** - flag a message as streaming (shows the caret)
668
- - **`Chat.updateMessage(messages, id, patch)`** - immutable per-message update, cheap enough for per-token appends
669
- - **`useStreamingMessages(...)`** - owns the message list, rAF-batches `push()`, and supports stop via `AbortController`
762
+ - **`useStreamingMessages(...)`** - owns the message list, rAF-batches `push()`, and supports stop via `AbortController`. It returns `{ messages, setMessages, append, startStream, isStreaming, stop }`; `setMessages` is there for anything the hook does not cover, so you can patch a message with a plain `map`.
670
763
 
671
764
  ```tsx
672
765
  import { useCallback } from 'react'
@@ -692,6 +785,33 @@ function Bot () {
692
785
 
693
786
  See **[docs/STREAMING.md](./docs/STREAMING.md)** for the full hook API and a real Claude streaming adapter (via a backend proxy). A runnable demo lives in `example/components/chat-examples/AIBotExample.tsx`.
694
787
 
788
+ #### Markdown rendering for AI replies
789
+
790
+ AI/LLM replies are usually markdown (bold, lists, fenced code). Enable markdown with `messageTextProps={{ markdown: true }}` (streamed messages auto-render as markdown unless you pass `markdown={false}`):
791
+
792
+ ```tsx
793
+ // Force markdown for every message:
794
+ <Chat messageTextProps={{ markdown: true }} {...props} />
795
+
796
+ // Streamed messages auto-render as markdown; disable with markdown: false.
797
+ ```
798
+
799
+ There are two renderers and you get the best available one automatically:
800
+
801
+ - **Built-in, zero-dependency renderer** (default). Covers headings, bullet/ordered lists, blockquotes, fenced + inline code, bold/italic/strikethrough, and links. It handles streaming-incomplete markdown gracefully (a half-written `**bold` or an unclosed code fence renders as plain text until complete), so it's safe to feed token-by-token. Nothing to install. Exposed as `BasicMarkdown` if you want to use it directly.
802
+
803
+ - **[`react-native-streamdown`](https://github.com/software-mansion-labs/react-native-streamdown)** (optional upgrade). When installed it's used instead, for richer streaming-safe markdown (tables, partial-table handling, etc.). It is a native module with its own peers - install the full set:
804
+
805
+ ```bash
806
+ npx expo install react-native-streamdown react-native-enriched-markdown remend katex
807
+ ```
808
+
809
+ It also requires `react-native-worklets >= 0.8.3` (i.e. `react-native-reanimated >= 4.3`), and `react-native-enriched-markdown` is a native module, so a dev build / prebuild is required (it does not work in Expo Go). Pass through Streamdown's own theming/rules via `markdownProps`:
810
+
811
+ ```tsx
812
+ <Chat messageTextProps={{ markdown: true, markdownProps: { /* ... */ } }} {...props} />
813
+ ```
814
+
695
815
  ### Emoji Reactions
696
816
 
697
817
  Long-press a message to open a quick emoji picker; selected reactions render as pills below the bubble and toggle on tap. The core ships a lightweight quick picker (built on `react-native-gesture-handler` and `react-native-reanimated`, no extra dependencies). A full emoji browser is optional and demonstrated in the example app via the `renderReactionPicker` override.
@@ -788,29 +908,86 @@ Message text is automatically scanned for URLs, emails, and phone numbers; hasht
788
908
 
789
909
  For full control, pass custom `matchers` (`{ type, pattern, getLinkUrl?, getLinkText?, renderLink?, onPress? }[]`) to add or override patterns. See the Links example in the [example app](#-example-app).
790
910
 
791
- ### Copy to Clipboard
911
+ ### Message actions (long-press context menu)
792
912
 
793
- Long-press handling is exposed via `onLongPressMessage`. Chat wraps [`@expo/react-native-action-sheet`](https://github.com/expo/react-native-action-sheet), so you can show a "Copy Text" action sheet and copy with the clipboard library of your choice:
913
+ Long-press a message to open a floating, themed **context menu** (Telegram style) anchored to the bubble. Provide the actions via `messageActions` - an array, or a function of the message - each `{ label, icon?, onPress, destructive? }`. When reactions are enabled, a reactions row is shown on top of the menu automatically.
794
914
 
795
915
  ```tsx
796
- import { useActionSheet } from '@expo/react-native-action-sheet'
797
916
  import { setStringAsync } from 'expo-clipboard'
917
+ import { Copy, Trash2 } from 'lucide-react-native' // optional icons
798
918
 
799
- const { showActionSheetWithOptions } = useActionSheet()
919
+ <Chat
920
+ messageActions={message => [
921
+ { label: 'Copy', icon: ({ color, size }) => <Copy color={color} size={size} />, onPress: () => setStringAsync(message.text) },
922
+ { label: 'Delete', destructive: true, onPress: () => deleteMessage(message) },
923
+ ]}
924
+ />
925
+ ```
926
+
927
+ ### Interactive content inside bubbles (video players, maps)
928
+
929
+ When `reactions` or `messageActions` are enabled, the long-press surface spans the **whole message row** - the bubble *and* the empty space beside it, the way Telegram behaves on Android. (A tap gesture is added on top only when `onPressMessage` is set.) Those recognizers do not cancel touches on native subviews, so a `react-native-video` / `expo-video` player rendered through `renderMessageVideo` keeps its native `controls` interactive.
800
930
 
931
+ If a message must own every touch that lands on it, set `isMessageGestureEnabled` to `false` for it. The gesture surface then drops *behind* the bubble: the bubble's content takes its touches, and long-pressing the row next to the bubble still opens the picker - so reactions are never lost for that message.
932
+
933
+ ```tsx
801
934
  <Chat
802
- onLongPressMessage={(_context, message) => {
803
- showActionSheetWithOptions(
804
- { options: ['Copy Text', 'Cancel'], cancelButtonIndex: 1 },
805
- buttonIndex => {
806
- if (buttonIndex === 0)
807
- setStringAsync(message.text)
808
- }
809
- )
935
+ reactions={{ isEnabled: true, onReactionPress }}
936
+ renderMessageVideo={props => <Video source={{ uri: props.currentMessage.video }} controls style={styles.video} />}
937
+ // the video owns its controls; long-press beside the bubble still reacts
938
+ isMessageGestureEnabled={message => !message.video}
939
+ />
940
+ ```
941
+
942
+ > **Note:** This library no longer depends on `@expo/react-native-action-sheet`. Prefer `messageActions` above. If you specifically want a native action sheet, install it yourself, wrap your tree in `ActionSheetProvider`, and either call `useActionSheet()` in your own `onLongPressMessage` or pass an `actionSheet` prop - the `actionSheet` prop / `context.actionSheet()` escape hatch still works when you provide an implementation. The composer "+" actions use the built-in themed `AttachmentSheet` and need no setup.
943
+
944
+ ### Theming & Dark Mode
945
+
946
+ The chat ships with a modern default look and a full token-based theme. Override any subset of tokens via `theme` (light) and `darkTheme` (dark); your overrides are deep-merged over `defaultLightTheme` / `defaultDarkTheme`, and the resolved theme switches at runtime with the color scheme (system, or forced via `colorScheme`). Explicit per-component style props still win over the theme.
947
+
948
+ ```tsx
949
+ <Chat
950
+ theme={{
951
+ colors: { accent: '#3390EC', outgoingBubble: '#EFFEDE' },
952
+ radii: { bubble: 18 },
810
953
  }}
954
+ darkTheme={{ colors: { background: '#0E1621', incomingBubble: '#182533' } }}
955
+ // colorScheme="dark" // optional: force a scheme instead of following the system
956
+ {...props}
811
957
  />
812
958
  ```
813
959
 
960
+ Token groups: `colors`, `radii`, `spacing`, `typography`, `avatar`, `sendButton`, `composer`, `voice`. Build your own theme-aware components with the exported hooks:
961
+
962
+ ```tsx
963
+ import { useTheme, useThemedStyles } from '@kesha-antonov/react-native-chat'
964
+ import { StyleSheet } from 'react-native'
965
+
966
+ const MyBadge = () => {
967
+ const theme = useTheme()
968
+ const styles = useThemedStyles(t => StyleSheet.create({
969
+ badge: { backgroundColor: t.colors.accent, borderRadius: t.radii.bubble },
970
+ }))
971
+ return <View style={styles.badge} />
972
+ }
973
+ ```
974
+
975
+ Also exported: `defaultLightTheme`, `defaultDarkTheme`, and the `ChatTheme` / `PartialChatTheme` types.
976
+
977
+ ### Localization (i18n)
978
+
979
+ All built-in UI strings (composer placeholder, send/cancel, load earlier, today, voice/video/location labels, slide-to-cancel, reply/edit banner, camera-permission text) route through a label table. Built-in translations ship for `en`, `es`, `fr`, `de`, and `ru`, selected by the existing `locale` prop. Override any individual string with `labels`:
980
+
981
+ ```tsx
982
+ <Chat
983
+ locale="fr" // pick a built-in translation
984
+ labels={{ placeholder: 'Votre message...' }} // override any string
985
+ {...props}
986
+ />
987
+ ```
988
+
989
+ Exported helpers: `ChatLabels` (type), `defaultLabels`, `translations`, `resolveLabels`, and the `useLabels` hook for reading the resolved labels in custom components.
990
+
814
991
  ### Message Status
815
992
 
816
993
  Set `sent`, `received`, or `pending` on a message to show its delivery status. By default these render as tick indicators next to the timestamp (`✓` sent, `✓✓` received, `🕓` pending):
@@ -922,6 +1099,73 @@ module.exports = function override(config, env) {
922
1099
 
923
1100
  ---
924
1101
 
1102
+ ## ⚡ Performance
1103
+
1104
+ The chat is built for long lists, but a few habits on your side unlock the most:
1105
+
1106
+ **Memoize your render props and config.** Each message row is wrapped in `React.memo` with a comparator that deep-compares the message and reference-compares every other prop. So an unchanged row only skips a re-render when the props you pass it are referentially stable. If you pass inline functions or objects, that row re-renders on every parent render:
1107
+
1108
+ ```jsx
1109
+ // ❌ New reference every render - the row can't skip
1110
+ <Chat renderBubble={props => <MyBubble {...props} />} reactions={{ isEnabled: true, onReactionPress }} />
1111
+
1112
+ // ✅ Stable references - unchanged rows skip re-renders
1113
+ const renderBubble = useCallback(props => <MyBubble {...props} />, [])
1114
+ const reactions = useMemo(() => ({ isEnabled: true, onReactionPress }), [onReactionPress])
1115
+ const messageActions = useCallback(message => [{ label: 'Copy', onPress: () => copy(message.text) }], [copy])
1116
+
1117
+ <Chat renderBubble={renderBubble} reactions={reactions} messageActions={messageActions} />
1118
+ ```
1119
+
1120
+ This applies to all render props (`renderBubble`, `renderMessageText`, `renderAvatar`, ...), the `reactions` / `audioRecording` / `videoRecording` / `messageActions` config objects, and any style objects.
1121
+
1122
+ **Keep messages immutable.** Update messages by creating new arrays/objects (e.g. `Chat.append(...)`), never by mutating an existing message in place - the row comparator relies on value changes to detect updates.
1123
+
1124
+ **Theme, icons and labels don't need drilling.** `theme` / `darkTheme`, `icons`, and `labels` are read from context (`useTheme`, `useIcons`, `useLabels`), so passing them once on `<Chat>` is enough; they don't cause per-row churn.
1125
+
1126
+ **Tune virtualization if needed.** Sensible `FlatList` defaults ship out of the box (`removeClippedSubviews` on Android, `initialNumToRender`, `maxToRenderPerBatch`, `windowSize`, `updateCellsBatchingPeriod`). `windowSize` is measured in screen-heights (not messages); the default keeps a few screens of content mounted around the viewport. Override any of them via `listProps`:
1127
+
1128
+ ```jsx
1129
+ <Chat listProps={{ windowSize: 7, removeClippedSubviews: true }} {...props} />
1130
+ ```
1131
+
1132
+ ### FlashList (opt-in)
1133
+
1134
+ On long histories `FlatList` can log `VirtualizedList: You have a large list that is slow to update`. [FlashList](https://shopify.github.io/flash-list/) v2 recycles rows instead of keeping them mounted, which removes that class of stall. It is supported as an **optional** dependency - install it yourself and flip one prop:
1135
+
1136
+ ```bash
1137
+ yarn add @shopify/flash-list
1138
+ ```
1139
+
1140
+ ```jsx
1141
+ <Chat messages={messages} user={user} isFlashListEnabled />
1142
+ ```
1143
+
1144
+ Everything else keeps working: `isInverted`, the floating day header, `loadEarlierMessagesProps` infinite scroll, the scroll-to-bottom button, and `listProps` (spread last, so it overrides the defaults below).
1145
+
1146
+ The chat sets FlashList's `maintainVisibleContentPosition` for you - `startRenderingFromBottom` when `isInverted={false}`, plus `autoscrollToBottomThreshold: 0.2` so new messages follow the viewport only when you are already at the bottom. Override it through `listProps` if you want different thresholds:
1147
+
1148
+ ```jsx
1149
+ <Chat
1150
+ isFlashListEnabled
1151
+ listProps={{
1152
+ maintainVisibleContentPosition: {
1153
+ autoscrollToBottomThreshold: 0.1,
1154
+ animateAutoScrollToBottom: false,
1155
+ },
1156
+ }}
1157
+ {...props}
1158
+ />
1159
+ ```
1160
+
1161
+ Notes:
1162
+
1163
+ - FlashList v2 requires the **New Architecture**. On the old architecture it falls back to a slower JS path.
1164
+ - `FlatList`-only knobs (`windowSize`, `maxToRenderPerBatch`, `initialNumToRender`, `updateCellsBatchingPeriod`, `removeClippedSubviews`) are not forwarded to FlashList - it sizes its own render window.
1165
+ - If `@shopify/flash-list` is not installed, the prop is ignored, a warning is logged, and `FlatList` is used.
1166
+
1167
+ ---
1168
+
925
1169
  ## 🧪 Testing
926
1170
 
927
1171
  <details>
@@ -1068,7 +1312,7 @@ yarn prepublishOnly
1068
1312
 
1069
1313
  ## 👥 Authors
1070
1314
 
1071
- Based on [FaridSafi/react-native-chat](https://github.com/FaridSafi/react-native-chat), which is no longer actively maintained.
1315
+ Based on [FaridSafi/react-native-gifted-chat](https://github.com/FaridSafi/react-native-gifted-chat), which is no longer actively maintained.
1072
1316
 
1073
1317
  **Maintainer:** [Kesha Antonov](https://github.com/kesha-antonov)
1074
1318
 
package/lib/Actions.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import React, { ReactNode } from 'react';
2
2
  import { StyleProp, ViewStyle, TextStyle } from 'react-native';
3
+ import { AttachmentAction } from './components/AttachmentSheet';
3
4
  export interface ActionsProps {
4
- actions?: Array<{
5
- title: string;
6
- action: () => void;
7
- }>;
5
+ /** Actions shown in the attachment sheet. Add `icon`/`color` for a grid layout. */
6
+ actions?: AttachmentAction[];
7
+ /** Tint color for the action labels in the sheet (defaults to the theme accent). */
8
8
  actionSheetOptionTintColor?: string;
9
9
  icon?: () => ReactNode;
10
10
  wrapperStyle?: StyleProp<ViewStyle>;
@@ -12,5 +12,5 @@ export interface ActionsProps {
12
12
  buttonStyle?: StyleProp<ViewStyle>;
13
13
  onPressActionButton?(): void;
14
14
  }
15
- export declare function Actions({ actions, actionSheetOptionTintColor, icon, wrapperStyle, iconTextStyle, onPressActionButton, buttonStyle, }: ActionsProps): React.JSX.Element;
15
+ export declare function Actions({ actions, actionSheetOptionTintColor, icon, wrapperStyle, onPressActionButton, buttonStyle, }: ActionsProps): React.JSX.Element;
16
16
  //# sourceMappingURL=Actions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Actions.d.ts","sourceRoot":"","sources":["../src/Actions.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,SAAS,EAAe,MAAM,OAAO,CAAA;AACrD,OAAO,EAGL,SAAS,EACT,SAAS,EACT,SAAS,EACH,MAAM,cAAc,CAAA;AAM5B,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC,CAAA;IACtD,0BAA0B,CAAC,EAAE,MAAM,CAAA;IACnC,IAAI,CAAC,EAAE,MAAM,SAAS,CAAA;IACtB,YAAY,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;IACnC,aAAa,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;IACpC,WAAW,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;IAClC,mBAAmB,CAAC,IAAI,IAAI,CAAA;CAC7B;AAED,wBAAgB,OAAO,CAAE,EACvB,OAAO,EACP,0BAAkD,EAClD,IAAI,EACJ,YAAY,EACZ,aAAa,EACb,mBAAmB,EACnB,WAAW,GACZ,EAAE,YAAY,qBAmDd"}
1
+ {"version":3,"file":"Actions.d.ts","sourceRoot":"","sources":["../src/Actions.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,SAAS,EAAyB,MAAM,OAAO,CAAA;AAC/D,OAAO,EAGL,SAAS,EACT,SAAS,EACT,SAAS,EAAE,MAAM,cAAc,CAAA;AACjC,OAAO,EAAE,gBAAgB,EAAmB,MAAM,8BAA8B,CAAA;AAQhF,MAAM,WAAW,YAAY;IAC3B,mFAAmF;IACnF,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC5B,oFAAoF;IACpF,0BAA0B,CAAC,EAAE,MAAM,CAAA;IACnC,IAAI,CAAC,EAAE,MAAM,SAAS,CAAA;IACtB,YAAY,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;IACnC,aAAa,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;IACpC,WAAW,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;IAClC,mBAAmB,CAAC,IAAI,IAAI,CAAA;CAC7B;AAED,wBAAgB,OAAO,CAAE,EACvB,OAAO,EACP,0BAA0B,EAC1B,IAAI,EACJ,YAAY,EACZ,mBAAmB,EACnB,WAAW,GACZ,EAAE,YAAY,qBA2Cd"}