@kesha-antonov/react-native-chat 4.3.0 → 5.0.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 (167) hide show
  1. package/README.md +318 -210
  2. package/lib/Avatar.d.ts.map +1 -1
  3. package/lib/Avatar.js +12 -5
  4. package/lib/Avatar.js.map +1 -1
  5. package/lib/Bubble/index.d.ts.map +1 -1
  6. package/lib/Bubble/index.js +15 -5
  7. package/lib/Bubble/index.js.map +1 -1
  8. package/lib/Chat/index.d.ts +1 -0
  9. package/lib/Chat/index.d.ts.map +1 -1
  10. package/lib/Chat/index.js +81 -34
  11. package/lib/Chat/index.js.map +1 -1
  12. package/lib/Chat/types.d.ts +37 -4
  13. package/lib/Chat/types.d.ts.map +1 -1
  14. package/lib/ChatContext.d.ts +2 -0
  15. package/lib/ChatContext.d.ts.map +1 -1
  16. package/lib/ChatContext.js +1 -0
  17. package/lib/ChatContext.js.map +1 -1
  18. package/lib/Composer.d.ts.map +1 -1
  19. package/lib/Composer.js +5 -0
  20. package/lib/Composer.js.map +1 -1
  21. package/lib/Constant.d.ts +1 -0
  22. package/lib/Constant.d.ts.map +1 -1
  23. package/lib/Constant.js +1 -0
  24. package/lib/Constant.js.map +1 -1
  25. package/lib/InputToolbar.d.ts.map +1 -1
  26. package/lib/InputToolbar.js +36 -10
  27. package/lib/InputToolbar.js.map +1 -1
  28. package/lib/Message/index.d.ts.map +1 -1
  29. package/lib/Message/index.js +11 -3
  30. package/lib/Message/index.js.map +1 -1
  31. package/lib/MessageImage.js +2 -2
  32. package/lib/MessageImage.js.map +1 -1
  33. package/lib/MessagesContainer/components/DayAnimated/debug.d.ts +1 -1
  34. package/lib/MessagesContainer/components/DayAnimated/debug.d.ts.map +1 -1
  35. package/lib/MessagesContainer/components/DayAnimated/debug.js +2 -2
  36. package/lib/MessagesContainer/components/DayAnimated/debug.js.map +1 -1
  37. package/lib/MessagesContainer/components/DayAnimated/index.d.ts.map +1 -1
  38. package/lib/MessagesContainer/components/DayAnimated/index.js +5 -5
  39. package/lib/MessagesContainer/components/DayAnimated/index.js.map +1 -1
  40. package/lib/MessagesContainer/components/DayAnimated/useScrollGatedOpacity.d.ts.map +1 -1
  41. package/lib/MessagesContainer/components/DayAnimated/useScrollGatedOpacity.js +1 -1
  42. package/lib/MessagesContainer/components/DayAnimated/useScrollGatedOpacity.js.map +1 -1
  43. package/lib/MessagesContainer/components/Item/index.d.ts.map +1 -1
  44. package/lib/MessagesContainer/components/Item/index.js +13 -11
  45. package/lib/MessagesContainer/components/Item/index.js.map +1 -1
  46. package/lib/MessagesContainer/index.d.ts.map +1 -1
  47. package/lib/MessagesContainer/index.js +71 -42
  48. package/lib/MessagesContainer/index.js.map +1 -1
  49. package/lib/MessagesContainer/types.d.ts +1 -0
  50. package/lib/MessagesContainer/types.d.ts.map +1 -1
  51. package/lib/Theme.d.ts +13 -2
  52. package/lib/Theme.d.ts.map +1 -1
  53. package/lib/Theme.js +7 -2
  54. package/lib/Theme.js.map +1 -1
  55. package/lib/TypingIndicator/index.d.ts.map +1 -1
  56. package/lib/TypingIndicator/index.js +11 -5
  57. package/lib/TypingIndicator/index.js.map +1 -1
  58. package/lib/components/MediaControls.d.ts +1 -1
  59. package/lib/components/MediaControls.d.ts.map +1 -1
  60. package/lib/components/MediaControls.js +2 -1
  61. package/lib/components/MediaControls.js.map +1 -1
  62. package/lib/components/ReplyPreview.d.ts.map +1 -1
  63. package/lib/components/ReplyPreview.js +2 -1
  64. package/lib/components/ReplyPreview.js.map +1 -1
  65. package/lib/components/TouchableOpacity.d.ts.map +1 -1
  66. package/lib/components/TouchableOpacity.js +10 -3
  67. package/lib/components/TouchableOpacity.js.map +1 -1
  68. package/lib/components/VoiceMessageInput.d.ts.map +1 -1
  69. package/lib/components/VoiceMessageInput.js +11 -4
  70. package/lib/components/VoiceMessageInput.js.map +1 -1
  71. package/lib/dayjsLocales.d.ts +18 -0
  72. package/lib/dayjsLocales.d.ts.map +1 -0
  73. package/lib/dayjsLocales.js +36 -0
  74. package/lib/dayjsLocales.js.map +1 -0
  75. package/lib/hooks/useIsRTL.d.ts +3 -0
  76. package/lib/hooks/useIsRTL.d.ts.map +1 -0
  77. package/lib/hooks/useIsRTL.js +7 -0
  78. package/lib/hooks/useIsRTL.js.map +1 -0
  79. package/lib/hooks/useKeyboardVerticalOffset.d.ts +12 -0
  80. package/lib/hooks/useKeyboardVerticalOffset.d.ts.map +1 -1
  81. package/lib/hooks/useKeyboardVerticalOffset.js +12 -0
  82. package/lib/hooks/useKeyboardVerticalOffset.js.map +1 -1
  83. package/lib/hooks/useUpdateLayoutEffect.d.ts.map +1 -1
  84. package/lib/hooks/useUpdateLayoutEffect.js +3 -0
  85. package/lib/hooks/useUpdateLayoutEffect.js.map +1 -1
  86. package/lib/i18n.d.ts +12 -5
  87. package/lib/i18n.d.ts.map +1 -1
  88. package/lib/i18n.js +20 -89
  89. package/lib/i18n.js.map +1 -1
  90. package/lib/index.d.ts +2 -0
  91. package/lib/index.d.ts.map +1 -1
  92. package/lib/index.js +2 -0
  93. package/lib/index.js.map +1 -1
  94. package/lib/linkParser.d.ts +14 -0
  95. package/lib/linkParser.d.ts.map +1 -1
  96. package/lib/linkParser.js +103 -34
  97. package/lib/linkParser.js.map +1 -1
  98. package/lib/locales/ar.d.ts +3 -0
  99. package/lib/locales/ar.d.ts.map +1 -0
  100. package/lib/locales/ar.js +22 -0
  101. package/lib/locales/ar.js.map +1 -0
  102. package/lib/locales/de.d.ts +3 -0
  103. package/lib/locales/de.d.ts.map +1 -0
  104. package/lib/locales/de.js +22 -0
  105. package/lib/locales/de.js.map +1 -0
  106. package/lib/locales/es.d.ts +3 -0
  107. package/lib/locales/es.d.ts.map +1 -0
  108. package/lib/locales/es.js +22 -0
  109. package/lib/locales/es.js.map +1 -0
  110. package/lib/locales/fr.d.ts +3 -0
  111. package/lib/locales/fr.d.ts.map +1 -0
  112. package/lib/locales/fr.js +22 -0
  113. package/lib/locales/fr.js.map +1 -0
  114. package/lib/locales/hi.d.ts +3 -0
  115. package/lib/locales/hi.d.ts.map +1 -0
  116. package/lib/locales/hi.js +22 -0
  117. package/lib/locales/hi.js.map +1 -0
  118. package/lib/locales/id.d.ts +3 -0
  119. package/lib/locales/id.d.ts.map +1 -0
  120. package/lib/locales/id.js +22 -0
  121. package/lib/locales/id.js.map +1 -0
  122. package/lib/locales/index.d.ts +19 -0
  123. package/lib/locales/index.d.ts.map +1 -0
  124. package/lib/locales/index.js +39 -0
  125. package/lib/locales/index.js.map +1 -0
  126. package/lib/locales/it.d.ts +3 -0
  127. package/lib/locales/it.d.ts.map +1 -0
  128. package/lib/locales/it.js +22 -0
  129. package/lib/locales/it.js.map +1 -0
  130. package/lib/locales/ja.d.ts +3 -0
  131. package/lib/locales/ja.d.ts.map +1 -0
  132. package/lib/locales/ja.js +22 -0
  133. package/lib/locales/ja.js.map +1 -0
  134. package/lib/locales/ko.d.ts +3 -0
  135. package/lib/locales/ko.d.ts.map +1 -0
  136. package/lib/locales/ko.js +22 -0
  137. package/lib/locales/ko.js.map +1 -0
  138. package/lib/locales/nl.d.ts +3 -0
  139. package/lib/locales/nl.d.ts.map +1 -0
  140. package/lib/locales/nl.js +22 -0
  141. package/lib/locales/nl.js.map +1 -0
  142. package/lib/locales/pl.d.ts +3 -0
  143. package/lib/locales/pl.d.ts.map +1 -0
  144. package/lib/locales/pl.js +22 -0
  145. package/lib/locales/pl.js.map +1 -0
  146. package/lib/locales/pt.d.ts +3 -0
  147. package/lib/locales/pt.d.ts.map +1 -0
  148. package/lib/locales/pt.js +22 -0
  149. package/lib/locales/pt.js.map +1 -0
  150. package/lib/locales/ru.d.ts +3 -0
  151. package/lib/locales/ru.d.ts.map +1 -0
  152. package/lib/locales/ru.js +22 -0
  153. package/lib/locales/ru.js.map +1 -0
  154. package/lib/locales/tr.d.ts +3 -0
  155. package/lib/locales/tr.d.ts.map +1 -0
  156. package/lib/locales/tr.js +22 -0
  157. package/lib/locales/tr.js.map +1 -0
  158. package/lib/locales/zh.d.ts +3 -0
  159. package/lib/locales/zh.d.ts.map +1 -0
  160. package/lib/locales/zh.js +22 -0
  161. package/lib/locales/zh.js.map +1 -0
  162. package/lib/rtl.d.ts +18 -0
  163. package/lib/rtl.d.ts.map +1 -0
  164. package/lib/rtl.js +31 -0
  165. package/lib/rtl.js.map +1 -0
  166. package/llms.txt +175 -0
  167. package/package.json +21 -22
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
  <h1 align="center">React Native Chat</h1>
13
13
 
14
14
  <p align="center">
15
- The most complete chat UI for React Native & Web
15
+ The most complete chat UI for React Native &amp; Web
16
16
  </p>
17
17
 
18
18
  <p align="center">
@@ -34,33 +34,63 @@
34
34
 
35
35
  ---
36
36
 
37
+ ## Quick start
38
+
39
+ ```bash
40
+ npx expo install @kesha-antonov/react-native-chat react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller
41
+ ```
42
+
43
+ ```jsx
44
+ import { useCallback, useState } from 'react'
45
+ import { Chat } from '@kesha-antonov/react-native-chat'
46
+
47
+ export function Example () {
48
+ const [messages, setMessages] = useState([
49
+ {
50
+ _id: 1,
51
+ text: 'Hello developer',
52
+ createdAt: new Date(),
53
+ user: { _id: 2, name: 'John Doe', avatar: 'https://placeimg.com/140/140/any' },
54
+ },
55
+ ])
56
+
57
+ const onSend = useCallback((newMessages = []) => {
58
+ setMessages(previous => Chat.append(previous, newMessages))
59
+ }, [])
60
+
61
+ return <Chat messages={messages} onSend={onSend} user={{ _id: 1 }} />
62
+ }
63
+ ```
64
+
65
+ That is the whole setup - everything below is optional.
66
+
67
+ ---
68
+
37
69
  ## ✨ Features
38
70
 
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
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
46
- - 🎨 **[Fully Customizable](#-props-reference)** - Override any component with your own implementation
47
- - 📎 **[Composer Actions](#actions--action-sheet)** - Attach photos, files, or trigger custom actions
48
- - ↩️ **[Reply to Messages](#reply-to-messages)** - Swipe-to-reply with reply preview and message threading
49
- - ⏮️ **[Load Earlier Messages](#load-earlier-messages)** - Infinite scroll with pagination support
50
- - 📋 **[Copy to Clipboard](#copy-to-clipboard)** - Long-press messages to copy text
51
- - 🔗 **[Smart Link Parsing](#smart-link-parsing)** - Auto-detect URLs, emails, phone numbers, hashtags, mentions
52
- - 👤 **[Avatars](#avatars)** - User initials or custom avatar images
53
- - 🌍 **[Localized Dates](#date--time)** - Full i18n support via Day.js
54
- - ⌨️ **[Keyboard Handling](#keyboard--layout)** - Smart keyboard avoidance for all platforms
55
- - 💬 **[System Messages](#system-messages)** - Display system notifications in chat
56
- - ⚡ **[Quick Replies](#quick-replies)** - Bot-style quick reply buttons
57
- - 😀 **[Emoji Reactions](#emoji-reactions)** - Long-press to react, with reaction pills and an optional full emoji browser
58
- - ✍️ **[Typing Indicator](#typing-indicator)** - Show when users are typing
59
- - ✅ **[Message Status](#message-status)** - Tick indicators for sent/delivered/read states
60
- - ⬇️ **[Scroll to Bottom](#scroll-to-bottom)** - Quick navigation button
61
- - 🌐 **[Web Support](#web-react-native-web)** - Works with react-native-web
62
- - 📱 **[Expo Support](#expo-projects)** - Easy integration with Expo projects
63
- - 📝 **[TypeScript](#typescript)** - Complete TypeScript definitions included
71
+ - 🌗 **[Dark mode & theming](#theming--dark-mode)** - full light/dark token system, switchable at runtime
72
+ - 🤖 **[Streaming AI messages](#streaming-ai-messages)** - token-by-token replies with a typing caret and stop control
73
+ - 😀 **[Emoji reactions](#emoji-reactions)** - long-press picker and reaction pills
74
+ - 🎥 **[Video & audio messages](#-whats-new-vs-react-native-gifted-chat)** - real inline playback, with a tappable fallback
75
+ - 🎙️ **[Voice & video recording](#-whats-new-vs-react-native-gifted-chat)** - Telegram-style hold-to-record notes (opt-in)
76
+ - 📍 **[Location messages](#-whats-new-vs-react-native-gifted-chat)** - map card that opens the system maps app
77
+ - ↩️ **[Swipe to reply](#reply-to-messages)** - reply preview and message threading
78
+ - 📋 **[Long-press actions](#message-actions-long-press-context-menu)** - themed context menu anchored to the bubble
79
+ - ⚡ **[Quick replies](#quick-replies)** - bot-style reply buttons
80
+ - 🔗 **[Smart link parsing](#smart-link-parsing)** - URLs, emails, phones, hashtags, mentions
81
+ - 👤 **[Avatars](#avatars)** - user initials or custom images
82
+ - ✍️ **[Typing indicator](#typing-indicator)** - show when someone is typing
83
+ - ⏮️ **[Load earlier messages](#load-earlier-messages)** - pagination and infinite scroll
84
+ - ⬇️ **[Scroll to bottom](#scroll-to-bottom)** - quick jump back to the latest
85
+ - ✅ **[Message status](#message-status)** - ticks for sent, delivered and read
86
+ - 💬 **[System messages](#system-messages)** - in-line notifications
87
+ - 🌍 **[Localized dates & i18n](#localization-i18n)** - Day.js dates, translated UI strings
88
+ - ⌨️ **[Keyboard handling](#keyboard--layout)** - smart avoidance on every platform
89
+ - 🎨 **[Fully customizable](#-props-reference)** - override any component with your own
90
+ - 📱 **[Expo](#-installation)** & 🌐 **[Web](#web-react-native-web)** - works with Expo and react-native-web
91
+ - 📝 **[TypeScript](#typescript)** - complete definitions, generic over your message type
92
+
93
+ Actively maintained, New Architecture ready, built for the latest Reanimated / Gesture Handler.
64
94
 
65
95
  <p align="center">
66
96
  <img width="200" src="https://github.com/user-attachments/assets/c9da88f5-0b20-471c-8cd7-373bdb767517" />
@@ -74,9 +104,10 @@
74
104
 
75
105
  ## 🆕 What's new vs react-native-gifted-chat
76
106
 
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)).
107
+ 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** - and the same `IMessage` model and prop names. Everything added here is **non-breaking and opt-in**; the media and recording features only activate once you install their optional peer deps.
78
108
 
79
- On top of that, this fork adds the features people kept asking upstream for, plus a modern look out of the box:
109
+ <details>
110
+ <summary><strong>Side-by-side comparison</strong></summary>
80
111
 
81
112
  | Added in this fork | react-native-gifted-chat | @kesha-antonov/react-native-chat |
82
113
  | --- | :---: | :---: |
@@ -92,10 +123,13 @@ On top of that, this fork adds the features people kept asking upstream for, plu
92
123
  | **Location messages** | ❌ ignored | ✅ map card → opens system maps |
93
124
  | Bubble tails + tighter message grouping | flat bubbles | ✅ |
94
125
 
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.
126
+ </details>
96
127
 
97
128
  ### Theming in one line
98
129
 
130
+ <details>
131
+ <summary><strong>Override any token, light and dark</strong></summary>
132
+
99
133
  ```jsx
100
134
  // Modern defaults out of the box, or override any token (light + dark):
101
135
  <Chat
@@ -105,8 +139,13 @@ Everything new is **non-breaking and opt-in**: keep passing the same props you d
105
139
  />
106
140
  ```
107
141
 
142
+ </details>
143
+
108
144
  ### Voice, video and location
109
145
 
146
+ <details>
147
+ <summary><strong>Opt-in recording and media playback</strong></summary>
148
+
110
149
  ```jsx
111
150
  <Chat
112
151
  audioRecording={{ isEnabled: true }} // hold the mic to record a voice note (needs expo-audio)
@@ -121,9 +160,14 @@ Everything new is **non-breaking and opt-in**: keep passing the same props you d
121
160
  npx expo install expo-video expo-audio expo-image-picker
122
161
  ```
123
162
 
163
+ </details>
164
+
124
165
  ### Custom icons (e.g. Lucide)
125
166
 
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:
167
+ <details>
168
+ <summary><strong>Override any built-in icon</strong></summary>
169
+
170
+ 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 - the built-in icon is used for anything you don't override:
127
171
 
128
172
  ```tsx
129
173
  import { Send, Mic } from 'lucide-react-native'
@@ -139,6 +183,8 @@ import { Send, Mic } from 'lucide-react-native'
139
183
 
140
184
  Overridable names: `send`, `mic`, `camera`, `play`, `pause`, `check`, `checkAll`, `clock`, `pin`, `plus`, `close`, `chevronLeft`, `chevronDown`, `emoji`, `paperclip`, `reply`, `pencil`, `lock`, `trash`.
141
185
 
186
+ </details>
187
+
142
188
  ---
143
189
 
144
190
  <h3 align="center">Support This Project</h3>
@@ -153,18 +199,35 @@ Overridable names: `send`, `mic`, `camera`, `play`, `pause`, `check`, `checkAll`
153
199
 
154
200
  ---
155
201
 
202
+ > **Using an AI coding agent?** [`llms.txt`](llms.txt) is a condensed integration guide for
203
+ > agents adding this library to an app, and [`AGENTS.md`](AGENTS.md) covers working inside
204
+ > this repository.
205
+
156
206
  ## 📖 Table of Contents
157
207
 
208
+ - [Quick start](#quick-start)
158
209
  - [Features](#-features)
159
210
  - [What's new vs react-native-gifted-chat](#-whats-new-vs-react-native-gifted-chat)
160
211
  - [Requirements](#-requirements)
161
212
  - [Installation](#-installation)
162
213
  - [Migrating from react-native-gifted-chat](#-migrating-from-react-native-gifted-chat)
163
214
  - [Usage](#-usage)
164
- - [Props Reference](#-props-reference)
165
215
  - [Data Structure](#-data-structure)
216
+ - [Props Reference](#-props-reference)
217
+ - [Guides](#-guides)
218
+ - [Reply to Messages](#reply-to-messages)
219
+ - [Maintaining Scroll Position (AI Chatbots)](#maintaining-scroll-position-ai-chatbots)
220
+ - [Streaming (AI) Messages](#streaming-ai-messages)
221
+ - [Emoji Reactions](#emoji-reactions)
222
+ - [Smart Link Parsing](#smart-link-parsing)
223
+ - [Message actions](#message-actions-long-press-context-menu)
224
+ - [Theming & Dark Mode](#theming--dark-mode)
225
+ - [Localization (i18n)](#localization-i18n)
226
+ - [Message Status](#message-status)
227
+ - [TypeScript](#typescript)
166
228
  - [Platform Notes](#-platform-notes)
167
229
  - [Performance](#-performance)
230
+ - [Testing](#-testing)
168
231
  - [Example App](#-example-app)
169
232
  - [Troubleshooting](#-troubleshooting)
170
233
  - [Contributing](#-contributing)
@@ -175,13 +238,9 @@ Overridable names: `send`, `mic`, `camera`, `play`, `pause`, `check`, `checkAll`
175
238
 
176
239
  ## 📋 Requirements
177
240
 
178
- | Requirement | Version |
179
- |-------------|---------|
180
- | React Native | >= 0.70.0 |
181
- | iOS | >= 13.4 |
182
- | Android | API 21+ (Android 5.0) |
183
- | Expo | SDK 50+ |
184
- | TypeScript | >= 5.0 (optional) |
241
+ | React Native | iOS | Android | Expo | TypeScript |
242
+ |---|---|---|---|---|
243
+ | >= 0.70.0 | >= 13.4 | API 21+ (5.0) | SDK 50+ | >= 5.0 (optional) |
185
244
 
186
245
  ---
187
246
 
@@ -195,33 +254,37 @@ npx expo install @kesha-antonov/react-native-chat react-native-reanimated react-
195
254
 
196
255
  ### Bare React Native Projects
197
256
 
198
- **Step 1:** Install the packages
257
+ <details>
258
+ <summary><strong>Three steps</strong></summary>
259
+
260
+ **1.** Install the packages:
199
261
 
200
- Using yarn:
201
262
  ```bash
202
263
  yarn add @kesha-antonov/react-native-chat react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller
203
264
  ```
204
265
 
205
- Using npm:
206
266
  ```bash
207
267
  npm install --save @kesha-antonov/react-native-chat react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller
208
268
  ```
209
269
 
210
- **Step 2:** Install iOS pods
270
+ **2.** Install iOS pods:
211
271
 
212
272
  ```bash
213
273
  npx pod-install
214
274
  ```
215
275
 
216
- **Step 3:** Configure react-native-reanimated
276
+ **3.** Add Reanimated's Babel plugin, following the [react-native-reanimated installation guide](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/#step-2-add-reanimateds-babel-plugin).
217
277
 
218
- Follow the [react-native-reanimated installation guide](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/#step-2-add-reanimateds-babel-plugin) to add the Babel plugin.
278
+ </details>
219
279
 
220
280
  ---
221
281
 
222
282
  ## 🔄 Migrating from react-native-gifted-chat
223
283
 
224
- This library is a rebranded continuation of [`react-native-gifted-chat`](https://github.com/FaridSafi/react-native-gifted-chat), built from its latest `master`. **The API, props, and `IMessage` model are unchanged** - migrating is a package swap plus renaming the `GiftedChat*` identifiers.
284
+ **The API, props, and `IMessage` model are unchanged** - migrating is a package swap plus renaming the `GiftedChat*` identifiers. Full guide with a codemod: **[docs/MIGRATION.md](docs/MIGRATION.md)**.
285
+
286
+ <details>
287
+ <summary><strong>What to rename</strong></summary>
225
288
 
226
289
  ```bash
227
290
  yarn remove react-native-gifted-chat
@@ -236,7 +299,7 @@ yarn add @kesha-antonov/react-native-chat
236
299
  | `GiftedChatContext` | `ChatContext` |
237
300
  | `IMessage`, `User`, `useChatContext`, … | unchanged |
238
301
 
239
- See the full guide (codemod included) in **[docs/MIGRATION.md](docs/MIGRATION.md)**.
302
+ </details>
240
303
 
241
304
  ---
242
305
 
@@ -244,47 +307,7 @@ See the full guide (codemod included) in **[docs/MIGRATION.md](docs/MIGRATION.md
244
307
 
245
308
  ### Basic Example
246
309
 
247
- ```jsx
248
- import React, { useState, useCallback, useEffect } from 'react'
249
- import { Chat } from '@kesha-antonov/react-native-chat'
250
-
251
- export function Example() {
252
- const [messages, setMessages] = useState([])
253
-
254
- useEffect(() => {
255
- setMessages([
256
- {
257
- _id: 1,
258
- text: 'Hello developer',
259
- createdAt: new Date(),
260
- user: {
261
- _id: 2,
262
- name: 'John Doe',
263
- avatar: 'https://placeimg.com/140/140/any',
264
- },
265
- },
266
- ])
267
- }, [])
268
-
269
- const onSend = useCallback((messages = []) => {
270
- setMessages(previousMessages =>
271
- Chat.append(previousMessages, messages),
272
- )
273
- }, [])
274
-
275
- return (
276
- <Chat
277
- messages={messages}
278
- onSend={messages => onSend(messages)}
279
- user={{
280
- _id: 1,
281
- }}
282
- />
283
- )
284
- }
285
- ```
286
-
287
- > **💡 Tip:** Check out more examples in the [`example`](example) directory including Slack-style messages, quick replies, and custom components.
310
+ See [Quick start](#quick-start) above for the minimal setup. More examples - Slack-style messages, quick replies, custom components - live in the [`example`](example) directory.
288
311
 
289
312
  ---
290
313
 
@@ -293,7 +316,7 @@ export function Example() {
293
316
  Messages, system messages, and quick replies follow the structure defined in [Models.ts](src/Models.ts).
294
317
 
295
318
  <details>
296
- <summary><strong>Message Object Structure</strong></summary>
319
+ <summary><strong>Message object structure</strong></summary>
297
320
 
298
321
  ```typescript
299
322
  interface IMessage {
@@ -345,6 +368,11 @@ interface User {
345
368
 
346
369
  ## 📖 Props Reference
347
370
 
371
+ Every group below is collapsed - open the one you need.
372
+
373
+ <details>
374
+ <summary><strong>Core configuration, refs &amp; theming</strong></summary>
375
+
348
376
  ### Core Configuration
349
377
 
350
378
  - **`messages`** _(Array)_ - Messages to display
@@ -363,12 +391,18 @@ interface User {
363
391
  - **`messagesContainerRef`** _(FlatList ref)_ - Ref to the flatlist
364
392
  - **`textInputRef`** _(TextInput ref)_ - Ref to the text input
365
393
 
394
+ </details>
395
+
396
+ <details>
397
+ <summary><strong>Keyboard &amp; layout</strong></summary>
398
+
366
399
  ### Keyboard & Layout
367
400
 
368
401
  - **`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
402
 
370
403
  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.
404
+ - **`enableKeyboardProvider`** _(Bool)_ - Render the built-in `KeyboardProvider`; default is `true`. You do **not** need to turn this off just because your app mounts its own provider - that case is detected and reused. Set it to `false` 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.
405
+ - **`enableGestureHandlerRootView`** _(Bool)_ - Render the `GestureHandlerRootView` Chat mounts around itself; default is `true`. Unlike `KeyboardProvider`, an existing one can't be auto-detected - `react-native-gesture-handler` doesn't expose that publicly - so set this to `false` yourself if your app (or a library it uses, e.g. a bottom sheet) already mounts one at the root. Nesting a second one changes the native view hierarchy around the composer, which has been observed to make a rare Fabric/Yoga layout assertion on iOS more likely, especially when Chat is mounted and unmounted while the keyboard is open ([#17](https://github.com/kesha-antonov/react-native-chat/issues/17)).
372
406
  - **`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.
373
407
  - **`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.
374
408
  - **`isInverted`** _(Bool)_ - Reverses display order of `messages`; default is `true`
@@ -389,6 +423,11 @@ If you do, sanity-check it on device: a toolbar behind the keyboard means the va
389
423
 
390
424
  > **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.
391
425
 
426
+ </details>
427
+
428
+ <details>
429
+ <summary><strong>Text input, composer &amp; actions</strong></summary>
430
+
392
431
  ### Text Input & Composer
393
432
 
394
433
  - **`text`** _(String)_ - Input text; default is `undefined`, but if specified, it will override Chat's internal state. Useful for managing text state outside of Chat (e.g. with Redux). Don't forget to implement `textInputProps.onChangeText` to update the text state.
@@ -415,6 +454,11 @@ If you do, sanity-check it on device: a toolbar behind the keyboard means the va
415
454
  - **`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
455
  - **`actionSheetOptionTintColor`** _(String)_ - Tint color for action labels in the attachment sheet
417
456
 
457
+ </details>
458
+
459
+ <details>
460
+ <summary><strong>Messages, container &amp; bubbles</strong></summary>
461
+
418
462
  ### Messages & Message Container
419
463
 
420
464
  - **`messagesContainerStyle`** _(Object)_ - Custom style for the messages container
@@ -442,20 +486,16 @@ If you do, sanity-check it on device: a toolbar behind the keyboard means the va
442
486
  - **`imageProps`** _(Object)_ - Extra props to be passed to the [`<Image>`](https://reactnative.dev/docs/image) component created by the default `renderMessageImage`
443
487
  - **`imageStyle`** _(Object)_ - Custom style for message images
444
488
  - **`videoProps`** _(Object)_ - Extra props to be passed to the video component created by the required `renderMessageVideo`
445
- - **`messageTextProps`** _(Object)_ - Extra props to be passed to the MessageText component. Useful for customizing link parsing behavior, text styles, and matchers. Supports the following props:
489
+ - **`messageTextProps`** _(Object)_ - Extra props to be passed to the MessageText component. Useful for customizing link parsing behavior, text styles, and matchers:
446
490
  - `matchers` - Custom matchers for linking message content (like URLs, phone numbers, hashtags, mentions)
447
491
  - `linkStyle` - Custom style for links
448
- - `email` - Enable/disable email parsing (default: true)
449
- - `phone` - Enable/disable phone number parsing (default: true)
450
- - `url` - Enable/disable URL parsing (default: true)
451
- - `hashtag` - Enable/disable hashtag parsing (default: false)
452
- - `mention` - Enable/disable mention parsing (default: false)
453
- - `hashtagUrl` - Base URL for hashtags (e.g., 'https://x.com/hashtag')
454
- - `mentionUrl` - Base URL for mentions (e.g., 'https://x.com')
492
+ - `email` / `phone` / `url` - Enable/disable parsing (default: true)
493
+ - `hashtag` / `mention` - Enable/disable parsing (default: false)
494
+ - `hashtagUrl` / `mentionUrl` - Base URLs (e.g. `'https://x.com/hashtag'`)
455
495
  - `stripPrefix` - Strip 'http://' or 'https://' from URL display (default: false)
456
496
  - `TextComponent` - Custom Text component to use (e.g., from react-native-gesture-handler)
457
497
 
458
- Example:
498
+ A custom matcher, replacing the default phone linking with an action sheet:
459
499
 
460
500
  ```tsx
461
501
  <Chat
@@ -503,7 +543,12 @@ Example:
503
543
  />
504
544
  ```
505
545
 
506
- See full example in [LinksExample](example/components/chat-examples/LinksExample.tsx)
546
+ See the full example in [LinksExample](example/components/chat-examples/LinksExample.tsx).
547
+
548
+ </details>
549
+
550
+ <details>
551
+ <summary><strong>Avatars, username, date &amp; time, system messages</strong></summary>
507
552
 
508
553
  ### Avatars
509
554
 
@@ -545,17 +590,20 @@ See full example in [LinksExample](example/components/chat-examples/LinksExample
545
590
 
546
591
  - **`renderSystemMessage`** _(Component | Function)_ - Custom system message
547
592
 
593
+ </details>
594
+
595
+ <details>
596
+ <summary><strong>Load earlier, typing indicator, quick replies, scroll to bottom</strong></summary>
597
+
548
598
  ### Load Earlier Messages
549
599
 
550
- - **`loadEarlierMessagesProps`** _(Object)_ - Props to pass to the LoadEarlierMessages component. The button is only visible when `isAvailable` is `true`. Supports the following props:
600
+ - **`loadEarlierMessagesProps`** _(Object)_ - Props to pass to the LoadEarlierMessages component. The button is only visible when `isAvailable` is `true`:
551
601
  - `isAvailable` - Controls button visibility (default: false)
552
602
  - `onPress` - Callback when button is pressed
553
603
  - `isLoading` - Display loading indicator (default: false)
554
604
  - `isInfiniteScrollEnabled` - Enable infinite scroll up when reaching the top of messages container, automatically calls `onPress` (not yet supported for web)
555
605
  - `label` - Override the default "Load earlier messages" text
556
- - `containerStyle` - Custom style for the button container
557
- - `wrapperStyle` - Custom style for the button wrapper
558
- - `textStyle` - Custom style for the button text
606
+ - `containerStyle` / `wrapperStyle` / `textStyle` - Custom styles for the button
559
607
  - `activityIndicatorStyle` - Custom style for the loading indicator
560
608
  - `activityIndicatorColor` - Color of the loading indicator (default: 'white')
561
609
  - `activityIndicatorSize` - Size of the loading indicator (default: 'small')
@@ -563,14 +611,14 @@ See full example in [LinksExample](example/components/chat-examples/LinksExample
563
611
 
564
612
  ### Typing Indicator
565
613
 
566
- - **`isTyping`** _(Bool)_ - Typing Indicator state; default `false`. If you use`renderFooter` it will override this.
614
+ - **`isTyping`** _(Bool)_ - Typing Indicator state; default `false`. If you use `renderFooter` it will override this.
567
615
  - **`renderTypingIndicator`** _(Component | Function)_ - Custom typing indicator component
568
616
  - **`typingIndicatorStyle`** _(StyleProp<ViewStyle>)_ - Custom style for the TypingIndicator component.
569
- - **`renderFooter`** _(Component | Function)_ - Custom footer component on the ListView, e.g. `'User is typing...'`; see [CustomizedFeaturesExample.tsx](example/components/chat-examples/CustomizedFeaturesExample.tsx) for an example. Overrides default typing indicator that triggers when `isTyping` is true.
617
+ - **`renderFooter`** _(Component | Function)_ - Custom footer component on the ListView, e.g. `'User is typing...'`; see [CustomizedRenderingExample.tsx](example/components/chat-examples/CustomizedRenderingExample.tsx) for an example. Overrides default typing indicator that triggers when `isTyping` is true.
570
618
 
571
619
  ### Quick Replies
572
620
 
573
- See [Quick Replies example in messages.ts](example/example-expo/data/messages.ts)
621
+ See the [quick replies example in messages.ts](example/example-expo/data/messages.ts).
574
622
 
575
623
  - **`onQuickReply`** _(Function)_ - Callback when sending a quick reply (to backend server)
576
624
  - **`renderQuickReplies`** _(Function)_ - Custom all quick reply view
@@ -579,11 +627,26 @@ See [Quick Replies example in messages.ts](example/example-expo/data/messages.ts
579
627
  - **`quickReplyContainerStyle`** _(StyleProp<ViewStyle>)_ - Custom container style for quick replies
580
628
  - **`renderQuickReplySend`** _(Function)_ - Custom quick reply **send** view
581
629
 
630
+ ### Scroll to Bottom
631
+
632
+ - **`isScrollToBottomEnabled`** _(Bool)_ - Enables the scroll to bottom Component (Default is false)
633
+ - **`scrollToBottomComponent`** _(Function)_ - Custom Scroll To Bottom Component container
634
+ - **`scrollToBottomOffset`** _(Integer)_ - Custom Height Offset upon which to begin showing Scroll To Bottom Component (Default is 200)
635
+ - **`scrollToBottomStyle`** _(Object)_ - Custom style for Scroll To Bottom wrapper (position, bottom, right, etc.)
636
+ - **`scrollToBottomContentStyle`** _(Object)_ - Custom style for Scroll To Bottom content (size, background, shadow, etc.)
637
+
638
+ </details>
639
+
640
+ ---
641
+
642
+ ## 🧩 Guides
643
+
582
644
  ### Reply to Messages
583
645
 
584
- React Native Chat supports swipe-to-reply functionality out of the box. When enabled, users can swipe on a message to reply to it, displaying a reply preview in the input toolbar and the replied message above the new message bubble.
646
+ <details>
647
+ <summary><strong>Swipe-to-reply, reply preview and threading</strong></summary>
585
648
 
586
- > **Note:** This feature uses `ReanimatedSwipeable` from `react-native-gesture-handler` and `react-native-reanimated` for smooth, performant animations.
649
+ Swipe on a message to reply to it, showing a reply preview in the input toolbar and the replied message above the new bubble. Uses `ReanimatedSwipeable` from `react-native-gesture-handler` plus `react-native-reanimated`.
587
650
 
588
651
  #### Basic Usage
589
652
 
@@ -603,8 +666,6 @@ React Native Chat supports swipe-to-reply functionality out of the box. When ena
603
666
 
604
667
  #### Reply Props (Grouped)
605
668
 
606
- The `reply` prop accepts an object with the following structure:
607
-
608
669
  ```typescript
609
670
  interface ReplyProps<TMessage> {
610
671
  // Swipe gesture configuration
@@ -651,8 +712,6 @@ interface ReplyProps<TMessage> {
651
712
 
652
713
  #### ReplyMessage Structure
653
714
 
654
- When a message has a reply, it includes a `replyMessage` property:
655
-
656
715
  ```typescript
657
716
  interface ReplyMessage {
658
717
  _id: string | number
@@ -694,23 +753,15 @@ const [replyMessage, setReplyMessage] = useState<ReplyMessage | null>(null)
694
753
 
695
754
  #### Smooth Animations
696
755
 
697
- The reply preview automatically animates when:
698
- - **Appearing**: Smoothly expands from zero height with fade-in effect
699
- - **Disappearing**: Smoothly collapses with fade-out effect
700
- - **Content changes**: Smoothly transitions when replying to a different message
701
-
702
- These animations use `react-native-reanimated` for 60fps performance.
703
-
704
- ### Scroll to Bottom
756
+ The reply preview animates automatically - it expands from zero height with a fade-in when it appears, collapses with a fade-out when cleared, and transitions smoothly when you reply to a different message. These use `react-native-reanimated` for 60fps performance.
705
757
 
706
- - **`isScrollToBottomEnabled`** _(Bool)_ - Enables the scroll to bottom Component (Default is false)
707
- - **`scrollToBottomComponent`** _(Function)_ - Custom Scroll To Bottom Component container
708
- - **`scrollToBottomOffset`** _(Integer)_ - Custom Height Offset upon which to begin showing Scroll To Bottom Component (Default is 200)
709
- - **`scrollToBottomStyle`** _(Object)_ - Custom style for Scroll To Bottom wrapper (position, bottom, right, etc.)
710
- - **`scrollToBottomContentStyle`** _(Object)_ - Custom style for Scroll To Bottom content (size, background, shadow, etc.)
758
+ </details>
711
759
 
712
760
  ### Maintaining Scroll Position (AI Chatbots)
713
761
 
762
+ <details>
763
+ <summary><strong>Keep the reading position while long answers stream in</strong></summary>
764
+
714
765
  For AI chat interfaces where long responses arrive and you don't want to disrupt the user's reading position, use [`maintainVisibleContentPosition`](https://reactnative.dev/docs/scrollview#maintainvisiblecontentposition) via `listProps`:
715
766
 
716
767
  ```tsx
@@ -748,15 +799,20 @@ const [isScrolledUp, setIsScrolledUp] = useState(false)
748
799
  />
749
800
  ```
750
801
 
802
+ </details>
803
+
751
804
  ### Streaming (AI) Messages
752
805
 
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.
806
+ Render AI assistant replies token-by-token. Incoming chunks are batched with `requestAnimationFrame` (one render per frame, only the streaming bubble re-renders) and a blinking caret shows while a message is still streaming.
754
807
 
755
808
  <p align="center">
756
809
  <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
810
  </p>
758
811
 
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.
812
+ <details>
813
+ <summary><strong>useStreamingMessages, stop control and a full example</strong></summary>
814
+
815
+ 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
816
 
761
817
  - **`IMessage.streaming`** - flag a message as streaming (shows the caret)
762
818
  - **`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`.
@@ -785,8 +841,13 @@ function Bot () {
785
841
 
786
842
  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`.
787
843
 
844
+ </details>
845
+
788
846
  #### Markdown rendering for AI replies
789
847
 
848
+ <details>
849
+ <summary><strong>Built-in renderer, or react-native-streamdown</strong></summary>
850
+
790
851
  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
852
 
792
853
  ```tsx
@@ -812,9 +873,11 @@ There are two renderers and you get the best available one automatically:
812
873
  <Chat messageTextProps={{ markdown: true, markdownProps: { /* ... */ } }} {...props} />
813
874
  ```
814
875
 
876
+ </details>
877
+
815
878
  ### Emoji Reactions
816
879
 
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.
880
+ Long-press a message to open a quick emoji picker; selected reactions render as pills below the bubble and toggle on tap. The quick picker ships in the core with no extra dependencies; a full emoji browser is optional via the `renderReactionPicker` override.
818
881
 
819
882
  <p align="center">
820
883
  <img width="200" src="https://raw.githubusercontent.com/kesha-antonov/react-native-chat/main/media/reactions-picker.png" />
@@ -824,6 +887,9 @@ Long-press a message to open a quick emoji picker; selected reactions render as
824
887
  <img width="200" src="https://raw.githubusercontent.com/kesha-antonov/react-native-chat/main/media/reactions-emoji-browser.png" />
825
888
  </p>
826
889
 
890
+ <details>
891
+ <summary><strong>Wiring up the toggle, and the full prop list</strong></summary>
892
+
827
893
  Store reactions on each message as a `reactions` array, then enable the feature and handle the toggle. Reaction state is owned by you, so it works with any backend:
828
894
 
829
895
  ```tsx
@@ -873,8 +939,6 @@ const handleReactionPress = useCallback((message: IChatMessage, emoji: string) =
873
939
 
874
940
  #### Reactions Props (Grouped)
875
941
 
876
- The `reactions` prop accepts:
877
-
878
942
  - **`isEnabled`** _(Bool)_ - Enable emoji reactions (default `false`)
879
943
  - **`emojis`** _(String[])_ - Emojis shown in the quick picker (default `['👍', '❤️', '😂', '😮', '😢', '👎']`)
880
944
  - **`onReactionPress`** _(Function)_ - `(message, emoji) => void` called when an emoji is selected or a pill is tapped. Toggle logic is left to you
@@ -883,8 +947,13 @@ The `reactions` prop accepts:
883
947
  - **`containerStyle`**, **`reactionStyle`**, **`reactionActiveStyle`**, **`reactionTextStyle`**, **`reactionCountStyle`** - Styles for the reaction pills
884
948
  - **`pickerContainerStyle`**, **`pickerEmojiStyle`** - Styles for the quick picker
885
949
 
950
+ </details>
951
+
886
952
  ### Smart Link Parsing
887
953
 
954
+ <details>
955
+ <summary><strong>URLs, emails, phones, hashtags and mentions</strong></summary>
956
+
888
957
  Message text is automatically scanned for URLs, emails, and phone numbers; hashtags and mentions are opt-in. Configure it via `messageTextProps`:
889
958
 
890
959
  ```tsx
@@ -908,9 +977,14 @@ Message text is automatically scanned for URLs, emails, and phone numbers; hasht
908
977
 
909
978
  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).
910
979
 
980
+ </details>
981
+
911
982
  ### Message actions (long-press context menu)
912
983
 
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.
984
+ <details>
985
+ <summary><strong>Telegram-style floating menu</strong></summary>
986
+
987
+ Long-press a message to open a floating, themed context menu 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.
914
988
 
915
989
  ```tsx
916
990
  import { setStringAsync } from 'expo-clipboard'
@@ -924,8 +998,15 @@ import { Copy, Trash2 } from 'lucide-react-native' // optional icons
924
998
  />
925
999
  ```
926
1000
 
1001
+ > **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.
1002
+
1003
+ </details>
1004
+
927
1005
  ### Interactive content inside bubbles (video players, maps)
928
1006
 
1007
+ <details>
1008
+ <summary><strong>Keeping native controls tappable</strong></summary>
1009
+
929
1010
  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.
930
1011
 
931
1012
  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.
@@ -939,10 +1020,13 @@ If a message must own every touch that lands on it, set `isMessageGestureEnabled
939
1020
  />
940
1021
  ```
941
1022
 
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.
1023
+ </details>
943
1024
 
944
1025
  ### Theming & Dark Mode
945
1026
 
1027
+ <details>
1028
+ <summary><strong>Token groups, runtime switching and themed components</strong></summary>
1029
+
946
1030
  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
1031
 
948
1032
  ```tsx
@@ -974,9 +1058,14 @@ const MyBadge = () => {
974
1058
 
975
1059
  Also exported: `defaultLightTheme`, `defaultDarkTheme`, and the `ChatTheme` / `PartialChatTheme` types.
976
1060
 
1061
+ </details>
1062
+
977
1063
  ### Localization (i18n)
978
1064
 
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`:
1065
+ <details>
1066
+ <summary><strong>Built-in translations and label overrides</strong></summary>
1067
+
1068
+ 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 `es`, `fr`, `de`, `ru`, `zh`, `ar`, `pt`, `ja`, `ko`, `it`, `tr`, `hi`, `nl`, `pl` and `id`, with English as the default, selected by the existing `locale` prop. A regional tag falls back to its base language, so `pt-BR` resolves to `pt`. Override any individual string with `labels`:
980
1069
 
981
1070
  ```tsx
982
1071
  <Chat
@@ -988,8 +1077,13 @@ All built-in UI strings (composer placeholder, send/cancel, load earlier, today,
988
1077
 
989
1078
  Exported helpers: `ChatLabels` (type), `defaultLabels`, `translations`, `resolveLabels`, and the `useLabels` hook for reading the resolved labels in custom components.
990
1079
 
1080
+ </details>
1081
+
991
1082
  ### Message Status
992
1083
 
1084
+ <details>
1085
+ <summary><strong>Tick indicators for sent / delivered / read</strong></summary>
1086
+
993
1087
  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):
994
1088
 
995
1089
  ```tsx
@@ -1012,8 +1106,13 @@ Customize the indicators with `renderTicks` (full override) or `tickStyle` (styl
1012
1106
  />
1013
1107
  ```
1014
1108
 
1109
+ </details>
1110
+
1015
1111
  ### TypeScript
1016
1112
 
1113
+ <details>
1114
+ <summary><strong>Generic over your own message type</strong></summary>
1115
+
1017
1116
  Chat ships complete type definitions and is generic over your message type. Extend `IMessage` to add custom fields and everything stays typed end to end:
1018
1117
 
1019
1118
  ```tsx
@@ -1030,8 +1129,7 @@ interface MyMessage extends IMessage {
1030
1129
  />
1031
1130
  ```
1032
1131
 
1033
- ---
1034
-
1132
+ </details>
1035
1133
 
1036
1134
  ---
1037
1135
 
@@ -1067,43 +1165,68 @@ For **Expo**, you can append `KeyboardAvoidingView` after Chat (Android only):
1067
1165
 
1068
1166
  ### Web (react-native-web)
1069
1167
 
1168
+ Install the web renderer alongside the library:
1169
+
1170
+ ```bash
1171
+ yarn add react-native-web react-dom
1172
+ ```
1173
+
1070
1174
  <details>
1071
- <summary><strong>With create-react-app</strong></summary>
1072
-
1073
- 1. Install react-app-rewired: `yarn add -D react-app-rewired`
1074
- 2. Create `config-overrides.js`:
1075
-
1076
- ```js
1077
- module.exports = function override(config, env) {
1078
- config.module.rules.push({
1079
- test: /\.js$/,
1080
- exclude: /node_modules[/\\](?!react-native-chat)/,
1081
- use: {
1082
- loader: 'babel-loader',
1083
- options: {
1084
- babelrc: false,
1085
- configFile: false,
1086
- presets: [
1087
- ['@babel/preset-env', { useBuiltIns: 'usage' }],
1088
- '@babel/preset-react',
1089
- ],
1090
- plugins: ['@babel/plugin-proposal-class-properties'],
1091
- },
1092
- },
1093
- })
1094
- return config
1095
- }
1175
+ <summary><strong>Expo / Metro (recommended) - no extra config</strong></summary>
1176
+
1177
+ ```bash
1178
+ npx expo start --web
1179
+ ```
1180
+
1181
+ Nothing else to set up. Metro runs Babel over `node_modules`, so this package's JSX and its Reanimated worklets get transpiled the same way your own code does. The [example app](#-example-app) runs on web exactly like this.
1182
+
1183
+ </details>
1184
+
1185
+ <details>
1186
+ <summary><strong>Other bundlers (webpack, Vite, Next.js) - the package must be transpiled</strong></summary>
1187
+
1188
+ The published files are ES modules with JSX left in place, and the component uses Reanimated worklets, which need `react-native-reanimated/plugin` to run. Bundlers that skip `node_modules` when transpiling - webpack's `babel-loader` does by default - will choke on that. So outside Metro you need to:
1189
+
1190
+ 1. Alias `react-native` to `react-native-web` and let the resolver pick up `.web.js` / `.web.tsx` extensions
1191
+ 2. Run `@kesha-antonov/react-native-chat` - and the React Native packages it depends on - through Babel with `@react-native/babel-preset` and `react-native-reanimated/plugin`, instead of excluding all of `node_modules`
1192
+
1193
+ In Next.js that is `transpilePackages` in `next.config.js`; in a plain webpack setup it means widening the `babel-loader` rule's `exclude` so these packages are included.
1194
+
1195
+ </details>
1196
+
1197
+ ### React Strict DOM
1198
+
1199
+ <details>
1200
+ <summary><strong>Works inside an RSD tree - verified on iOS and Expo web</strong></summary>
1201
+
1202
+ Chat drops into a [React Strict DOM](https://github.com/facebook/react-strict-dom) app unchanged. RSD's native build renders through React Native, so `<Chat>` is just another RN subtree:
1203
+
1204
+ ```tsx
1205
+ import { css, html } from 'react-strict-dom'
1206
+ import { Chat } from '@kesha-antonov/react-native-chat'
1207
+
1208
+ const styles = css.create({
1209
+ root: { display: 'flex', flexDirection: 'column', height: '100%' },
1210
+ })
1211
+
1212
+ <html.div data-layoutconformance='strict' style={styles.root}>
1213
+ <Chat messages={messages} onSend={onSend} user={user} />
1214
+ </html.div>
1096
1215
  ```
1097
1216
 
1217
+ Three things to know:
1218
+
1219
+ - **Web needs `react-native-web`.** RSD's web build contains no React Native code at all, so Chat does not resolve there on its own. Expo web gives you `react-native-web` already, so RSD (real DOM) and Chat (RNW's DOM output) render side by side in one tree. A Vite or Next.js RSD app has to add the alias and transpile step from the section above
1220
+ - **Metro needs `unstable_enablePackageExports: true`.** RSD publishes only an `exports` map with no `main`, so with package exports off it fails to resolve. This is the default on recent Expo SDKs
1221
+ - **StyleX styles do not reach Chat.** It takes React Native style objects and its own `theme` prop, not `css.create` output. On native, `compat.native` is the bridge if you want RSD to drive a wrapper around it
1222
+
1098
1223
  </details>
1099
1224
 
1100
1225
  ---
1101
1226
 
1102
1227
  ## ⚡ Performance
1103
1228
 
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:
1229
+ The chat is built for long lists. The one habit that matters most: **memoize your render props and config**, because each message row is `React.memo`'d with a comparator that deep-compares the message and reference-compares everything else - so an inline function or object re-renders that row on every parent render.
1107
1230
 
1108
1231
  ```jsx
1109
1232
  // ❌ New reference every render - the row can't skip
@@ -1112,12 +1235,14 @@ The chat is built for long lists, but a few habits on your side unlock the most:
1112
1235
  // ✅ Stable references - unchanged rows skip re-renders
1113
1236
  const renderBubble = useCallback(props => <MyBubble {...props} />, [])
1114
1237
  const reactions = useMemo(() => ({ isEnabled: true, onReactionPress }), [onReactionPress])
1115
- const messageActions = useCallback(message => [{ label: 'Copy', onPress: () => copy(message.text) }], [copy])
1116
1238
 
1117
- <Chat renderBubble={renderBubble} reactions={reactions} messageActions={messageActions} />
1239
+ <Chat renderBubble={renderBubble} reactions={reactions} />
1118
1240
  ```
1119
1241
 
1120
- This applies to all render props (`renderBubble`, `renderMessageText`, `renderAvatar`, ...), the `reactions` / `audioRecording` / `videoRecording` / `messageActions` config objects, and any style objects.
1242
+ <details>
1243
+ <summary><strong>Immutability, context props and virtualization tuning</strong></summary>
1244
+
1245
+ The memoization advice applies to all render props (`renderBubble`, `renderMessageText`, `renderAvatar`, ...), the `reactions` / `audioRecording` / `videoRecording` / `messageActions` config objects, and any style objects.
1121
1246
 
1122
1247
  **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
1248
 
@@ -1129,8 +1254,13 @@ This applies to all render props (`renderBubble`, `renderMessageText`, `renderAv
1129
1254
  <Chat listProps={{ windowSize: 7, removeClippedSubviews: true }} {...props} />
1130
1255
  ```
1131
1256
 
1257
+ </details>
1258
+
1132
1259
  ### FlashList (opt-in)
1133
1260
 
1261
+ <details>
1262
+ <summary><strong>Recycling rows on very long histories</strong></summary>
1263
+
1134
1264
  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
1265
 
1136
1266
  ```bash
@@ -1164,6 +1294,8 @@ Notes:
1164
1294
  - `FlatList`-only knobs (`windowSize`, `maxToRenderPerBatch`, `initialNumToRender`, `updateCellsBatchingPeriod`, `removeClippedSubviews`) are not forwarded to FlashList - it sizes its own render window.
1165
1295
  - If `@shopify/flash-list` is not installed, the prop is ignored, a warning is logged, and `FlatList` is used.
1166
1296
 
1297
+ </details>
1298
+
1167
1299
  ---
1168
1300
 
1169
1301
  ## 🧪 Testing
@@ -1196,33 +1328,22 @@ fireEvent(loadingWrapper, 'layout', {
1196
1328
 
1197
1329
  ## 📦 Example App
1198
1330
 
1199
- The repository includes a comprehensive example app demonstrating all features:
1331
+ <details>
1332
+ <summary><strong>Running the demo locally</strong></summary>
1333
+
1334
+ The repository includes a comprehensive example app demonstrating all features - basic chat, custom bubbles and avatars, swipe-to-reply, quick replies, typing indicators, attachment actions, link parsing with custom matchers, and web compatibility.
1200
1335
 
1201
1336
  ```bash
1202
- # Clone and install
1203
1337
  git clone https://github.com/kesha-antonov/react-native-chat.git
1204
1338
  cd react-native-chat/example
1205
1339
  yarn install
1206
1340
 
1207
- # Run on iOS
1208
- npx expo run:ios
1209
-
1210
- # Run on Android
1211
- npx expo run:android
1212
-
1213
- # Run on Web
1214
- npx expo start --web
1341
+ npx expo run:ios # iOS
1342
+ npx expo run:android # Android
1343
+ npx expo start --web # Web
1215
1344
  ```
1216
1345
 
1217
- The example app showcases:
1218
- - 💬 Basic chat functionality
1219
- - 🎨 Custom message bubbles and avatars
1220
- - ↩️ Reply to messages with swipe gesture
1221
- - ⚡ Quick replies (bot-style)
1222
- - ✍️ Typing indicators
1223
- - 📎 Attachment actions
1224
- - 🔗 Link parsing and custom matchers
1225
- - 🌐 Web compatibility
1346
+ </details>
1226
1347
 
1227
1348
  ---
1228
1349
 
@@ -1276,37 +1397,24 @@ See [this issue](https://github.com/kesha-antonov/react-native-chat/issues/298)
1276
1397
 
1277
1398
  ## 🤝 Contributing
1278
1399
 
1279
- Contributions are welcome! Please feel free to submit a Pull Request.
1280
-
1281
- 1. Fork the repository
1282
- 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
1283
- 3. Install dependencies (`yarn install`)
1284
- 4. Make your changes
1285
- 5. Run tests (`yarn test`)
1286
- 6. Run linting (`yarn lint`)
1287
- 7. Build the library (`yarn build`)
1288
- 8. Commit your changes (`git commit -m 'Add amazing feature'`)
1289
- 9. Push to the branch (`git push origin feature/amazing-feature`)
1290
- 10. Open a Pull Request
1400
+ Contributions are welcome - fork, branch, and open a Pull Request.
1291
1401
 
1292
1402
  ### Development Setup
1293
1403
 
1294
- ```bash
1295
- # Install dependencies
1296
- yarn install
1297
-
1298
- # Build the library
1299
- yarn build
1404
+ <details>
1405
+ <summary><strong>Commands</strong></summary>
1300
1406
 
1301
- # Run tests
1302
- yarn test
1407
+ ```bash
1408
+ yarn install # install dependencies
1409
+ yarn build # build the library
1410
+ yarn test # run tests
1411
+ yarn lint # run linting
1412
+ yarn prepublishOnly # full validation
1413
+ ```
1303
1414
 
1304
- # Run linting
1305
- yarn lint
1415
+ Before opening a PR, please make sure `yarn test`, `yarn lint` and `yarn build` all pass.
1306
1416
 
1307
- # Full validation
1308
- yarn prepublishOnly
1309
- ```
1417
+ </details>
1310
1418
 
1311
1419
  ---
1312
1420