@ermis-network/ermis-chat-react 1.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 (88) hide show
  1. package/dist/index.cjs +6593 -0
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.css +3375 -0
  4. package/dist/index.css.map +1 -0
  5. package/dist/index.d.mts +1138 -0
  6. package/dist/index.d.ts +1138 -0
  7. package/dist/index.mjs +6500 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/package.json +42 -0
  10. package/src/components/Avatar.tsx +102 -0
  11. package/src/components/Channel.tsx +77 -0
  12. package/src/components/ChannelHeader.tsx +85 -0
  13. package/src/components/ChannelInfo/AddMemberModal.tsx +204 -0
  14. package/src/components/ChannelInfo/ChannelInfo.tsx +455 -0
  15. package/src/components/ChannelInfo/ChannelInfoTabs.tsx +282 -0
  16. package/src/components/ChannelInfo/ChannelSettingsPanel.tsx +479 -0
  17. package/src/components/ChannelInfo/EditChannelModal.tsx +272 -0
  18. package/src/components/ChannelInfo/FileListItem.tsx +49 -0
  19. package/src/components/ChannelInfo/LinkListItem.tsx +62 -0
  20. package/src/components/ChannelInfo/MediaGridItem.tsx +90 -0
  21. package/src/components/ChannelInfo/MemberListItem.tsx +85 -0
  22. package/src/components/ChannelInfo/MessageSearchPanel.tsx +333 -0
  23. package/src/components/ChannelInfo/States.tsx +36 -0
  24. package/src/components/ChannelInfo/index.ts +10 -0
  25. package/src/components/ChannelInfo/utils.tsx +49 -0
  26. package/src/components/ChannelList.tsx +395 -0
  27. package/src/components/Dropdown.tsx +120 -0
  28. package/src/components/EditPreview.tsx +102 -0
  29. package/src/components/FilesPreview.tsx +108 -0
  30. package/src/components/ForwardMessageModal.tsx +234 -0
  31. package/src/components/MentionSuggestions.tsx +59 -0
  32. package/src/components/MessageActionsBox.tsx +186 -0
  33. package/src/components/MessageInput.tsx +513 -0
  34. package/src/components/MessageInputDefaults.tsx +50 -0
  35. package/src/components/MessageItem.tsx +218 -0
  36. package/src/components/MessageQuickReactions.tsx +73 -0
  37. package/src/components/MessageReactions.tsx +59 -0
  38. package/src/components/MessageRenderers.tsx +565 -0
  39. package/src/components/Modal.tsx +58 -0
  40. package/src/components/Panel.tsx +64 -0
  41. package/src/components/PinnedMessages.tsx +165 -0
  42. package/src/components/QuotedMessagePreview.tsx +55 -0
  43. package/src/components/ReadReceipts.tsx +80 -0
  44. package/src/components/ReplyPreview.tsx +98 -0
  45. package/src/components/TypingIndicator.tsx +57 -0
  46. package/src/components/VirtualMessageList.tsx +425 -0
  47. package/src/context/ChatProvider.tsx +73 -0
  48. package/src/hooks/useBannedState.ts +48 -0
  49. package/src/hooks/useBlockedState.ts +55 -0
  50. package/src/hooks/useChannel.ts +18 -0
  51. package/src/hooks/useChannelCapabilities.ts +42 -0
  52. package/src/hooks/useChannelData.ts +55 -0
  53. package/src/hooks/useChannelListUpdates.ts +224 -0
  54. package/src/hooks/useChannelMessages.ts +159 -0
  55. package/src/hooks/useChannelRowUpdates.ts +78 -0
  56. package/src/hooks/useChatClient.ts +11 -0
  57. package/src/hooks/useEmojiPicker.ts +53 -0
  58. package/src/hooks/useFileUpload.ts +128 -0
  59. package/src/hooks/useLoadMessages.ts +178 -0
  60. package/src/hooks/useMentions.ts +287 -0
  61. package/src/hooks/useMessageActions.ts +87 -0
  62. package/src/hooks/useMessageSend.ts +164 -0
  63. package/src/hooks/usePendingState.ts +63 -0
  64. package/src/hooks/useScrollToMessage.ts +155 -0
  65. package/src/hooks/useTypingIndicator.ts +86 -0
  66. package/src/index.ts +129 -0
  67. package/src/styles/_add-member-modal.css +122 -0
  68. package/src/styles/_base.css +32 -0
  69. package/src/styles/_channel-info.css +941 -0
  70. package/src/styles/_channel-list.css +217 -0
  71. package/src/styles/_dropdown.css +69 -0
  72. package/src/styles/_forward-modal.css +191 -0
  73. package/src/styles/_mentions.css +102 -0
  74. package/src/styles/_message-actions.css +61 -0
  75. package/src/styles/_message-bubble.css +656 -0
  76. package/src/styles/_message-input.css +389 -0
  77. package/src/styles/_message-list.css +416 -0
  78. package/src/styles/_message-quick-reactions.css +62 -0
  79. package/src/styles/_message-reactions.css +67 -0
  80. package/src/styles/_modal.css +113 -0
  81. package/src/styles/_panel.css +69 -0
  82. package/src/styles/_pinned-messages.css +140 -0
  83. package/src/styles/_search-panel.css +219 -0
  84. package/src/styles/_tokens.css +92 -0
  85. package/src/styles/_typing-indicator.css +59 -0
  86. package/src/styles/index.css +24 -0
  87. package/src/types.ts +955 -0
  88. package/src/utils.ts +242 -0
package/src/types.ts ADDED
@@ -0,0 +1,955 @@
1
+ import type { FormatMessageResponse, MessageLabel, Attachment, Channel, ChannelFilters, ChannelSort, ChannelQueryOptions } from '@ermis-network/ermis-chat-sdk';
2
+ import type { ErmisChat } from '@ermis-network/ermis-chat-sdk';
3
+
4
+ /* ----------------------------------------------------------
5
+ Context types
6
+ ---------------------------------------------------------- */
7
+ export type Theme = 'dark' | 'light';
8
+
9
+ export type ReadStateEntry = {
10
+ last_read: Date | string;
11
+ last_read_message_id?: string;
12
+ unread_messages: number;
13
+ user: {
14
+ id: string;
15
+ name?: string;
16
+ avatar?: string;
17
+ };
18
+ last_send?: string;
19
+ };
20
+
21
+ export type ChatContextValue = {
22
+ client: ErmisChat;
23
+ activeChannel: Channel | null;
24
+ setActiveChannel: (channel: Channel | null) => void;
25
+ theme: Theme;
26
+ setTheme: (theme: Theme) => void;
27
+ messages: FormatMessageResponse[];
28
+ setMessages: React.Dispatch<React.SetStateAction<FormatMessageResponse[]>>;
29
+ /** Re-read messages from SDK state into React state */
30
+ syncMessages: () => void;
31
+ /** Message being replied to (shown as preview in MessageInput) */
32
+ quotedMessage: FormatMessageResponse | null;
33
+ setQuotedMessage: (message: FormatMessageResponse | null) => void;
34
+ /** Message being edited (shown as preview in MessageInput and alters send behavior) */
35
+ editingMessage: FormatMessageResponse | null;
36
+ setEditingMessage: (message: FormatMessageResponse | null) => void;
37
+ /** Read state per user — maps userId to their read status */
38
+ readState: Record<string, ReadStateEntry>;
39
+ setReadState: React.Dispatch<React.SetStateAction<Record<string, ReadStateEntry>>>;
40
+ /** Message being forwarded (triggers ForwardMessageModal) */
41
+ forwardingMessage: FormatMessageResponse | null;
42
+ setForwardingMessage: (message: FormatMessageResponse | null) => void;
43
+ /** Message ID to jump/scroll to (set by search, cleared after scroll) */
44
+ jumpToMessageId: string | null;
45
+ setJumpToMessageId: (id: string | null) => void;
46
+ };
47
+
48
+ export type ChatProviderProps = {
49
+ client: ErmisChat;
50
+ children: React.ReactNode;
51
+ /** Initial theme, defaults to 'dark' */
52
+ initialTheme?: Theme;
53
+ };
54
+
55
+ /* ----------------------------------------------------------
56
+ Avatar types
57
+ ---------------------------------------------------------- */
58
+ export type AvatarProps = {
59
+ /** Image URL */
60
+ image?: string | null;
61
+ /** Name used for fallback initials */
62
+ name?: string;
63
+ /** Size in pixels (default: 36) */
64
+ size?: number;
65
+ /** Additional CSS class name */
66
+ className?: string;
67
+ };
68
+
69
+ /* ----------------------------------------------------------
70
+ Channel types
71
+ ---------------------------------------------------------- */
72
+ export type ChannelProps = {
73
+ children: React.ReactNode;
74
+ /** Additional CSS class name */
75
+ className?: string;
76
+ /** Custom component shown when no channel is selected */
77
+ EmptyStateIndicator?: React.ComponentType;
78
+ /** Replace the default ChannelHeader entirely */
79
+ HeaderComponent?: React.ComponentType<ChannelHeaderData>;
80
+ /** Replace the default ForwardMessageModal entirely */
81
+ ForwardMessageModalComponent?: React.ComponentType<ForwardMessageModalProps>;
82
+ };
83
+
84
+ export type ChannelHeaderProps = {
85
+ /** Additional CSS class name */
86
+ className?: string;
87
+ /** Custom avatar component */
88
+ AvatarComponent?: React.ComponentType<AvatarProps>;
89
+ /** Override channel name */
90
+ title?: string;
91
+ /** Override channel image */
92
+ image?: string;
93
+ /** Subtitle text (e.g. member count, online status) */
94
+ subtitle?: string;
95
+ /** Render custom content on the right side */
96
+ renderRight?: (channel: Channel, actionDisabled?: boolean) => React.ReactNode;
97
+ /** Override default title rendering */
98
+ renderTitle?: (channel: Channel) => React.ReactNode;
99
+ };
100
+
101
+ /** Data passed to a fully custom HeaderComponent */
102
+ export type ChannelHeaderData = {
103
+ channel: Channel;
104
+ name: string;
105
+ image?: string;
106
+ };
107
+
108
+ /* ----------------------------------------------------------
109
+ ChannelList types
110
+ ---------------------------------------------------------- */
111
+ export type ChannelItemProps = {
112
+ channel: Channel;
113
+ isActive: boolean;
114
+ hasUnread: boolean;
115
+ unreadCount: number;
116
+ lastMessageText: string;
117
+ lastMessageUser: string;
118
+ onSelect: (channel: Channel) => void;
119
+ AvatarComponent: React.ComponentType<AvatarProps>;
120
+ /** Whether the current user has blocked this channel (messaging only) */
121
+ isBlocked?: boolean;
122
+ /** Whether the current user is pending an invitation for this channel */
123
+ isPending?: boolean;
124
+ /** Label for the pending channel badge indicator */
125
+ pendingBadgeLabel?: string;
126
+ /** Label for the blocked channel badge indicator */
127
+ blockedBadgeLabel?: string;
128
+ };
129
+
130
+ export type ChannelListProps = {
131
+ filters?: ChannelFilters;
132
+ sort?: ChannelSort;
133
+ options?: ChannelQueryOptions;
134
+ renderChannel?: (channel: Channel, isActive: boolean) => React.ReactNode;
135
+ onChannelSelect?: (channel: Channel) => void;
136
+ className?: string;
137
+ LoadingIndicator?: React.ComponentType<{ text?: string }>;
138
+ EmptyStateIndicator?: React.ComponentType<{ text?: string }>;
139
+ AvatarComponent?: React.ComponentType<AvatarProps>;
140
+ /** Replace the default channel list item component */
141
+ ChannelItemComponent?: React.ComponentType<ChannelItemProps>;
142
+ /** Label for the pending invites accordion header */
143
+ pendingInvitesLabel?: string | ((count: number) => string);
144
+ /** Label for the regular channels section header */
145
+ channelsLabel?: string;
146
+ /** Label for the pending channel badge indicator */
147
+ pendingBadgeLabel?: string;
148
+ /** Label for the loading indicator */
149
+ loadingLabel?: string;
150
+ /** Label for the empty state indicator */
151
+ emptyStateLabel?: string;
152
+ /** Label for the blocked channel badge hover */
153
+ blockedBadgeLabel?: string;
154
+ };
155
+
156
+ /* ----------------------------------------------------------
157
+ MessageRenderers types
158
+ ---------------------------------------------------------- */
159
+ export type AttachmentProps = {
160
+ attachment: Attachment;
161
+ };
162
+
163
+ export type MessageRendererProps = {
164
+ message: FormatMessageResponse;
165
+ isOwnMessage: boolean;
166
+ };
167
+
168
+ export type MessageBubbleProps = {
169
+ message: FormatMessageResponse;
170
+ isOwnMessage: boolean;
171
+ children: React.ReactNode;
172
+ };
173
+
174
+ export type DateSeparatorProps = {
175
+ label: string;
176
+ };
177
+
178
+ export type JumpToLatestProps = {
179
+ onClick: () => void;
180
+ };
181
+
182
+ /* ----------------------------------------------------------
183
+ MessageList types
184
+ ---------------------------------------------------------- */
185
+ export type MessageListProps = {
186
+ /** Fully custom render for each message */
187
+ renderMessage?: (message: FormatMessageResponse, isOwnMessage: boolean) => React.ReactNode;
188
+ /** Additional CSS class name */
189
+ className?: string;
190
+ /** Custom empty state component */
191
+ EmptyStateIndicator?: React.ComponentType;
192
+ /** Custom avatar component */
193
+ AvatarComponent?: React.ComponentType<AvatarProps>;
194
+ /** Custom message bubble wrapper */
195
+ MessageBubble?: React.ComponentType<MessageBubbleProps>;
196
+ /** Custom renderers per message type */
197
+ messageRenderers?: Partial<Record<MessageLabel, React.ComponentType<MessageRendererProps>>>;
198
+ /** Number of older messages to load per page (default: 25) */
199
+ loadMoreLimit?: number;
200
+ /** Custom date separator component */
201
+ DateSeparatorComponent?: React.ComponentType<DateSeparatorProps>;
202
+ /** Custom message item component (replaces the entire row) */
203
+ MessageItemComponent?: React.ComponentType<MessageItemProps>;
204
+ /** Custom system message item component */
205
+ SystemMessageItemComponent?: React.ComponentType<SystemMessageItemProps>;
206
+ /** Custom "Jump to latest" button */
207
+ JumpToLatestButton?: React.ComponentType<JumpToLatestProps>;
208
+ /** Custom quoted message preview inside message items */
209
+ QuotedMessagePreviewComponent?: React.ComponentType<QuotedMessagePreviewProps>;
210
+ /** Custom message actions component (hover buttons + dropdown) */
211
+ MessageActionsBoxComponent?: React.ComponentType<MessageActionsBoxProps>;
212
+ /** Show pinned messages bar (default: true) */
213
+ showPinnedMessages?: boolean;
214
+ /** Custom pinned messages component */
215
+ PinnedMessagesComponent?: React.ComponentType<any>;
216
+ /** Custom reply preview component in MessageInput */
217
+ ReplyPreviewComponent?: React.ComponentType<ReplyPreviewProps>;
218
+ /** Show read receipts (default: true) */
219
+ showReadReceipts?: boolean;
220
+ /** Custom read receipts component (replaces the entire read-receipts row) */
221
+ ReadReceiptsComponent?: React.ComponentType<ReadReceiptsProps>;
222
+ /** Custom read receipts tooltip component */
223
+ ReadReceiptsTooltipComponent?: React.ComponentType<ReadReceiptsTooltipProps>;
224
+ /** Max visible avatars in read receipts before showing +N (default: 5) */
225
+ readReceiptsMaxAvatars?: number;
226
+ /** Show typing indicator (default: true) */
227
+ showTypingIndicator?: boolean;
228
+ /** Custom typing indicator component */
229
+ TypingIndicatorComponent?: React.ComponentType;
230
+ /** Custom component for message reactions */
231
+ MessageReactionsComponent?: React.ComponentType<MessageReactionsProps>;
232
+
233
+ /** I18n Labels */
234
+ emptyTitle?: string;
235
+ emptySubtitle?: string;
236
+ jumpToLatestLabel?: string;
237
+ bannedOverlayTitle?: string;
238
+ bannedOverlaySubtitle?: string;
239
+ blockedOverlayTitle?: string;
240
+ blockedOverlaySubtitle?: string;
241
+ pendingOverlayTitle?: string;
242
+ pendingOverlaySubtitle?: string;
243
+ pendingAcceptLabel?: string;
244
+ pendingRejectLabel?: string;
245
+ };
246
+
247
+ /* ----------------------------------------------------------
248
+ Message Reactions types
249
+ ---------------------------------------------------------- */
250
+ export type ReactionUser = {
251
+ id: string;
252
+ name?: string;
253
+ avatar?: string;
254
+ };
255
+
256
+ export type LatestReaction = {
257
+ user: ReactionUser;
258
+ type: string;
259
+ };
260
+
261
+ export type MessageReactionsProps = {
262
+ /** Map of reaction type to count */
263
+ reactionCounts?: Record<string, number>;
264
+ /** Array of current user's reactions */
265
+ ownReactions?: LatestReaction[];
266
+ /** Array of latest reactions to show in tooltip/hover */
267
+ latestReactions?: LatestReaction[];
268
+ /** Avatar Component if consumer wants to use it in tooltips */
269
+ AvatarComponent?: React.ComponentType<AvatarProps>;
270
+ /** Callback when clicking a reaction */
271
+ onClickReaction?: (type: string) => void;
272
+ };
273
+
274
+ /* ----------------------------------------------------------
275
+ ReadReceipts types
276
+ ---------------------------------------------------------- */
277
+ export type ReadReceiptUser = {
278
+ id: string;
279
+ name?: string;
280
+ avatar?: string;
281
+ last_read?: Date | string;
282
+ };
283
+
284
+ export type ReadReceiptsTooltipProps = {
285
+ /** All users who have read this message */
286
+ readers: ReadReceiptUser[];
287
+ /** Avatar component for rendering user avatars */
288
+ AvatarComponent: React.ComponentType<AvatarProps>;
289
+ };
290
+
291
+ export type ReadReceiptsProps = {
292
+ /** Users who have read the message */
293
+ readers: ReadReceiptUser[];
294
+ /** Max number of visible avatars before showing +N overflow (default: 5) */
295
+ maxAvatars?: number;
296
+ /** Avatar component for rendering user avatars */
297
+ AvatarComponent: React.ComponentType<AvatarProps>;
298
+ /** Custom tooltip component */
299
+ TooltipComponent?: React.ComponentType<ReadReceiptsTooltipProps>;
300
+ /** Whether to show the tooltip on hover (default: true) */
301
+ showTooltip?: boolean;
302
+ /** Whether the message belongs to the current user (used to show 'Sent/Delivered' status when nobody has read it) */
303
+ isOwnMessage?: boolean;
304
+ /** Whether the message is the last in a group of consecutive messages by the same user */
305
+ isLastInGroup?: boolean;
306
+ /** The message status (e.g., 'sending', 'failed', 'sent') */
307
+ status?: string;
308
+ };
309
+
310
+ /* ----------------------------------------------------------
311
+ MessageItem types
312
+ ---------------------------------------------------------- */
313
+ export type MessageItemProps = {
314
+ message: FormatMessageResponse;
315
+ isOwnMessage: boolean;
316
+ isFirstInGroup: boolean;
317
+ isLastInGroup: boolean;
318
+ isHighlighted: boolean;
319
+ AvatarComponent: React.ComponentType<AvatarProps>;
320
+ MessageBubble: React.ComponentType<MessageBubbleProps>;
321
+ MessageRenderer: React.ComponentType<MessageRendererProps>;
322
+ onClickQuote?: (messageId: string) => void;
323
+ /** Custom quoted message preview component */
324
+ QuotedMessagePreviewComponent?: React.ComponentType<QuotedMessagePreviewProps>;
325
+ /** Custom message actions component (hover buttons + dropdown) */
326
+ MessageActionsBoxComponent?: React.ComponentType<MessageActionsBoxProps>;
327
+ /** Users who have read up to this message */
328
+ readBy?: Array<{ id: string; name?: string; avatar?: string }>;
329
+ /** Custom component for message reactions */
330
+ MessageReactionsComponent?: React.ComponentType<MessageReactionsProps>;
331
+ /** I18n Label for forwarded message */
332
+ forwardedLabel?: string;
333
+ /** I18n Label for edited state */
334
+ editedLabel?: string;
335
+ };
336
+
337
+ export type SystemMessageItemProps = {
338
+ message: FormatMessageResponse;
339
+ isOwnMessage: boolean;
340
+ SystemRenderer: React.ComponentType<MessageRendererProps>;
341
+ };
342
+
343
+ export type SendButtonProps = { disabled: boolean; onClick: () => void };
344
+ export type AttachButtonProps = { disabled: boolean; onClick: () => void };
345
+
346
+ /** Props passed to a consumer-provided emoji picker component */
347
+ export type EmojiPickerProps = {
348
+ /** Called when user selects an emoji — insert the emoji string into the input */
349
+ onSelect: (emoji: string) => void;
350
+ /** Called when the picker should close (e.g. click outside) */
351
+ onClose: () => void;
352
+ };
353
+
354
+ /** Props passed to the emoji button component */
355
+ export type EmojiButtonProps = {
356
+ /** Whether the picker is currently open */
357
+ active: boolean;
358
+ /** Toggle the picker */
359
+ onClick: () => void;
360
+ };
361
+
362
+ export type MessageInputProps = {
363
+ /** Placeholder text */
364
+ placeholder?: string;
365
+ /** Callback after message is sent */
366
+ onSend?: (text: string) => void;
367
+ /** Additional CSS class name */
368
+ className?: string;
369
+ /** Custom send button component */
370
+ SendButton?: React.ComponentType<SendButtonProps>;
371
+ /** Custom attach button component */
372
+ AttachButton?: React.ComponentType<AttachButtonProps>;
373
+ /** Custom file preview component */
374
+ FilesPreviewComponent?: React.ComponentType<FilesPreviewProps>;
375
+ /** Custom mention suggestions component */
376
+ MentionSuggestionsComponent?: React.ComponentType<MentionSuggestionsProps>;
377
+ /** Disable file attachments entirely */
378
+ disableAttachments?: boolean;
379
+ /** Disable @mention suggestions (overrides auto-detection) */
380
+ disableMentions?: boolean;
381
+ /** Render custom content above the input row (e.g. reply preview) */
382
+ renderAbove?: () => React.ReactNode;
383
+ /** Hook called before sending — return false to cancel */
384
+ onBeforeSend?: (text: string, attachments: FilePreviewItem[]) => boolean | Promise<boolean>;
385
+ /** Consumer-provided emoji picker component (not bundled — bring your own) */
386
+ EmojiPickerComponent?: React.ComponentType<EmojiPickerProps>;
387
+ /** Custom emoji button component (defaults to 😀 toggle button) */
388
+ EmojiButtonComponent?: React.ComponentType<EmojiButtonProps>;
389
+ /** Custom reply preview component */
390
+ ReplyPreviewComponent?: React.ComponentType<ReplyPreviewProps>;
391
+ /** Custom edit preview component */
392
+ EditPreviewComponent?: React.ComponentType<{ message: FormatMessageResponse; onDismiss: () => void; editingMessageLabel?: string }>;
393
+ /** I18n Label for banned state */
394
+ bannedLabel?: string;
395
+ /** I18n Label for blocked state (messaging channels) */
396
+ blockedLabel?: string;
397
+ /** I18n Label for links disabled error */
398
+ linksDisabledLabel?: string;
399
+ /** I18n Label for keyword blocked error */
400
+ keywordBlockedLabel?: (match: string) => string;
401
+ /** I18n Label for sending capability disabled */
402
+ sendDisabledLabel?: string;
403
+ /** I18n Label for slow mode active */
404
+ slowModeLabel?: (cooldown: number) => React.ReactNode;
405
+ };
406
+
407
+ /* ----------------------------------------------------------
408
+ ReplyPreview types
409
+ ---------------------------------------------------------- */
410
+ export type ReplyPreviewProps = {
411
+ message: FormatMessageResponse;
412
+ onDismiss: () => void;
413
+ replyingToLabel?: string;
414
+ };
415
+
416
+ /* ----------------------------------------------------------
417
+ Message Actions Box types
418
+ ---------------------------------------------------------- */
419
+ export type MessageActionsBoxProps = {
420
+ message: FormatMessageResponse;
421
+ isOwnMessage: boolean;
422
+ onReply?: (message: FormatMessageResponse) => void;
423
+ onForward?: (message: FormatMessageResponse) => void;
424
+ onPinToggle?: (message: FormatMessageResponse, isPinned: boolean) => void;
425
+ onEdit?: (message: FormatMessageResponse) => void;
426
+ onCopy?: (message: FormatMessageResponse) => void;
427
+ onDelete?: (message: FormatMessageResponse) => void;
428
+ onDeleteForMe?: (message: FormatMessageResponse) => void;
429
+
430
+ /** I18n Labels */
431
+ pinLabel?: string;
432
+ unpinLabel?: string;
433
+ editLabel?: string;
434
+ copyLabel?: string;
435
+ deleteForMeLabel?: string;
436
+ deleteForEveryoneLabel?: string;
437
+ };
438
+
439
+ /* ----------------------------------------------------------
440
+ Forward Message Modal types
441
+ ---------------------------------------------------------- */
442
+ export type ForwardChannelItemProps = {
443
+ channel: Channel;
444
+ selected: boolean;
445
+ onToggle: (channel: Channel) => void;
446
+ AvatarComponent: React.ComponentType<AvatarProps>;
447
+ };
448
+
449
+ export type ForwardMessageModalProps = {
450
+ message: FormatMessageResponse;
451
+ onDismiss: () => void;
452
+ /** Custom channel list item for the picker */
453
+ ChannelItemComponent?: React.ComponentType<ForwardChannelItemProps>;
454
+ /** Custom search input component */
455
+ SearchInputComponent?: React.ComponentType<{ value: string; onChange: (v: string) => void }>;
456
+ };
457
+
458
+ /* ----------------------------------------------------------
459
+ Pinned Messages types
460
+ ---------------------------------------------------------- */
461
+ export type PinnedMessageItemProps = {
462
+ message: FormatMessageResponse;
463
+ isOwnMessage: boolean;
464
+ onClickMessage?: (messageId: string) => void;
465
+ onUnpin?: (messageId: string) => void;
466
+ AvatarComponent: React.ComponentType<AvatarProps>;
467
+ };
468
+
469
+ export type PinnedMessagesProps = {
470
+ /** Additional CSS class name */
471
+ className?: string;
472
+ /** Custom avatar component */
473
+ AvatarComponent?: React.ComponentType<AvatarProps>;
474
+ /** Custom pinned message item component */
475
+ PinnedMessageItemComponent?: React.ComponentType<PinnedMessageItemProps>;
476
+ /** Callback when a pinned message is clicked (e.g. scroll to it) */
477
+ onClickMessage?: (messageId: string) => void;
478
+ /** Max messages to show in collapsed state (default: 1) */
479
+ maxCollapsed?: number;
480
+ };
481
+
482
+ /* ----------------------------------------------------------
483
+ QuotedMessagePreview types
484
+ ---------------------------------------------------------- */
485
+ export type QuotedMessagePreviewProps = {
486
+ /** The quoted (replied-to) message object */
487
+ quotedMessage: {
488
+ id: string;
489
+ text?: string;
490
+ user?: { id?: string; name?: string };
491
+ };
492
+ /** Whether the parent message is from the current user */
493
+ isOwnMessage: boolean;
494
+ /** Callback when the quote box is clicked */
495
+ onClick: (messageId: string) => void;
496
+ };
497
+
498
+ /* ----------------------------------------------------------
499
+ MentionSuggestions types
500
+ ---------------------------------------------------------- */
501
+ export type MentionSuggestionsProps = {
502
+ members: MentionMember[];
503
+ highlightIndex: number;
504
+ onSelect: (member: MentionMember) => void;
505
+ };
506
+
507
+ /* ----------------------------------------------------------
508
+ useChannel types
509
+ ---------------------------------------------------------- */
510
+ export type UseChannelReturn = {
511
+ channel: Channel | null;
512
+ loading: boolean;
513
+ error: Error | null;
514
+ };
515
+
516
+ /* ----------------------------------------------------------
517
+ Mention types
518
+ ---------------------------------------------------------- */
519
+ export type MentionMember = {
520
+ id: string;
521
+ name: string;
522
+ avatar?: string;
523
+ };
524
+
525
+ export type MentionPayload = {
526
+ text: string;
527
+ mentioned_all: boolean;
528
+ mentioned_users: string[];
529
+ };
530
+
531
+ export type UseMentionsOptions = {
532
+ members: MentionMember[];
533
+ currentUserId?: string;
534
+ editableRef: React.RefObject<HTMLDivElement | null>;
535
+ };
536
+
537
+ export type UseMentionsReturn = {
538
+ showSuggestions: boolean;
539
+ filteredMembers: MentionMember[];
540
+ highlightIndex: number;
541
+ /** Call on each input event of the contenteditable */
542
+ handleInput: () => void;
543
+ /** Call on keydown. Returns true if the event was consumed (e.g. Enter for selection). */
544
+ handleKeyDown: (e: React.KeyboardEvent) => boolean;
545
+ /** Select a member from the suggestion list */
546
+ selectMention: (member: MentionMember) => void;
547
+ /** Build the payload from the contenteditable DOM */
548
+ buildPayload: () => MentionPayload;
549
+ /** Reset mention state (call after send) */
550
+ reset: () => void;
551
+ };
552
+
553
+ /* ----------------------------------------------------------
554
+ File preview types (upload attachments)
555
+ ---------------------------------------------------------- */
556
+ export type FilePreviewItem = {
557
+ /** Unique ID for keying */
558
+ id: string;
559
+ /** Original File object (optional for existing server attachments) */
560
+ file?: File;
561
+ /** Blob URL for image/video preview */
562
+ previewUrl?: string;
563
+ /** Upload status */
564
+ status: 'pending' | 'uploading' | 'done' | 'error';
565
+ /** Error message if upload failed */
566
+ error?: string;
567
+ /** URL returned after successful upload */
568
+ uploadedUrl?: string;
569
+ /** Thumbnail URL (video only) */
570
+ thumbUrl?: string;
571
+ /** File with normalized name */
572
+ normalizedFile?: File;
573
+ /** Track original attachments during edits */
574
+ originalAttachment?: Attachment;
575
+ };
576
+
577
+ export type FilesPreviewProps = {
578
+ files: FilePreviewItem[];
579
+ onRemove: (id: string) => void;
580
+ };
581
+
582
+ /* --------------------------------------------------------------------------
583
+ * Modal Components
584
+ * -------------------------------------------------------------------------- */
585
+
586
+ export interface ModalProps {
587
+ isOpen: boolean;
588
+ onClose: () => void;
589
+ title?: React.ReactNode;
590
+ children: React.ReactNode;
591
+ footer?: React.ReactNode;
592
+ maxWidth?: string;
593
+ hideCloseButton?: boolean;
594
+ }
595
+
596
+ /* ----------------------------------------------------------
597
+ Channel Info types
598
+ ---------------------------------------------------------- */
599
+
600
+ /** Attachment item from the channel attachment query API */
601
+ export type AttachmentItem = {
602
+ id: string;
603
+ attachment_type: string;
604
+ user_id: string;
605
+ cid: string;
606
+ url: string;
607
+ thumb_url: string;
608
+ file_name: string;
609
+ content_type: string;
610
+ content_length: number;
611
+ content_disposition: string;
612
+ message_id: string;
613
+ created_at: string;
614
+ updated_at: string;
615
+ // for link previews
616
+ title?: string;
617
+ title_link?: string;
618
+ og_scrape_url?: string;
619
+ image_url?: string;
620
+ text?: string;
621
+ };
622
+
623
+ export type MediaTab = 'members' | 'media' | 'links' | 'files';
624
+
625
+ /* Sub-component prop types for consumer customization */
626
+
627
+ export type ChannelInfoMemberItemProps = {
628
+ member: ChannelInfoMember;
629
+ AvatarComponent: React.ComponentType<AvatarProps>;
630
+ onRemove?: (id: string) => void;
631
+ canRemove?: boolean;
632
+ onBan?: (id: string) => void;
633
+ canBan?: boolean;
634
+ onUnban?: (id: string) => void;
635
+ canUnban?: boolean;
636
+ onPromote?: (id: string) => void;
637
+ canPromote?: boolean;
638
+ onDemote?: (id: string) => void;
639
+ canDemote?: boolean;
640
+ };
641
+
642
+ export type ChannelInfoMediaItemProps = {
643
+ item: AttachmentItem;
644
+ onClick: (url: string) => void;
645
+ };
646
+
647
+ export type ChannelInfoLinkItemProps = {
648
+ item: AttachmentItem;
649
+ };
650
+
651
+ export type ChannelInfoFileItemProps = {
652
+ item: AttachmentItem;
653
+ onClick: (url: string) => void;
654
+ };
655
+
656
+ export type ChannelInfoEmptyStateProps = {
657
+ label: string;
658
+ };
659
+
660
+ /* Section component prop types */
661
+
662
+ export type ChannelInfoHeaderProps = {
663
+ title: string;
664
+ onClose?: () => void;
665
+ };
666
+
667
+ export type ChannelInfoCoverProps = {
668
+ channelName: string;
669
+ channelImage?: string;
670
+ channelDescription?: string;
671
+ AvatarComponent: React.ComponentType<AvatarProps>;
672
+ /** Whether the current user can edit channel info */
673
+ canEdit?: boolean;
674
+ /** Callback when the edit button is clicked */
675
+ onEditClick?: () => void;
676
+ /** Whether the channel is public */
677
+ isPublic?: boolean;
678
+ /** Whether the channel is a team channel */
679
+ isTeamChannel?: boolean;
680
+ };
681
+
682
+ export type ChannelInfoActionsProps = {
683
+ onSearchClick?: () => void;
684
+ onSettingsClick?: () => void;
685
+ onLeaveChannel?: () => void;
686
+ onDeleteChannel?: () => void;
687
+ onBlockUser?: () => void;
688
+ onUnblockUser?: () => void;
689
+ isTeamChannel?: boolean;
690
+ isBlocked?: boolean;
691
+ currentUserRole?: string;
692
+ searchLabel?: string;
693
+ settingsLabel?: string;
694
+ deleteLabel?: string;
695
+ leaveLabel?: string;
696
+ blockLabel?: string;
697
+ unblockLabel?: string;
698
+ };
699
+
700
+ export type ChannelInfoMember = {
701
+ id: string;
702
+ name?: string;
703
+ avatar?: string;
704
+ [key: string]: any;
705
+ };
706
+
707
+ /** Payload for updating channel profile */
708
+ export type EditChannelData = {
709
+ name?: string;
710
+ image?: string;
711
+ description?: string;
712
+ public?: boolean;
713
+ };
714
+
715
+ /** Props for the EditChannelModal */
716
+ export type EditChannelModalProps = {
717
+ channel: Channel;
718
+ onClose: () => void;
719
+ /** Override the default save logic entirely */
720
+ onSave?: (data: EditChannelData) => Promise<void>;
721
+ /** Custom avatar component */
722
+ AvatarComponent: React.ComponentType<AvatarProps>;
723
+ /** Modal title (default: 'Edit Channel') */
724
+ title?: string;
725
+ /** Label for the name input (default: 'Channel Name') */
726
+ nameLabel?: string;
727
+ /** Label for the description input (default: 'Description') */
728
+ descriptionLabel?: string;
729
+ /** Placeholder for name input */
730
+ namePlaceholder?: string;
731
+ /** Placeholder for description input */
732
+ descriptionPlaceholder?: string;
733
+ /** Label for the public toggle (default: 'Public Channel') */
734
+ publicLabel?: string;
735
+ /** Label for the save button (default: 'Save') */
736
+ saveLabel?: string;
737
+ /** Label for the cancel button (default: 'Cancel') */
738
+ cancelLabel?: string;
739
+ /** Label shown while saving (default: 'Saving...') */
740
+ savingLabel?: string;
741
+ /** Label for the change avatar button (default: 'Change Avatar') */
742
+ changeAvatarLabel?: string;
743
+ /** Accept attribute for the file input (default: 'image/*') */
744
+ imageAccept?: string;
745
+ /** Max file size in bytes (default: 5MB = 5242880) */
746
+ maxImageSize?: number;
747
+ /** Error text for exceeding max size (default: 'Image must be less than 5MB') */
748
+ maxImageSizeError?: string;
749
+ };
750
+
751
+ /** Props for the Add Member button rendered at the top of the Members tab */
752
+ export type AddMemberButtonProps = {
753
+ onClick: () => void;
754
+ /** Label text for the button (default: 'Add Member') */
755
+ label?: string;
756
+ };
757
+
758
+ /** Props for individual user items inside the AddMemberModal */
759
+ export type AddMemberUserItemProps = {
760
+ user: any;
761
+ /** Whether the user already belongs to the channel */
762
+ isExisting: boolean;
763
+ /** Whether the user is currently being added */
764
+ isAdding: boolean;
765
+ /** Callback to add the user */
766
+ onAdd: (userId: string) => void;
767
+ AvatarComponent: React.ComponentType<AvatarProps>;
768
+ /** Label shown when the user is already in the channel (default: 'Added') */
769
+ addedLabel?: string;
770
+ /** Label shown while adding (default: 'Adding...') */
771
+ addingLabel?: string;
772
+ /** Label for the add button (default: 'Add') */
773
+ addLabel?: string;
774
+ };
775
+
776
+ /** Props for the AddMemberModal */
777
+ export type AddMemberModalProps = {
778
+ channel: Channel;
779
+ currentMembers: any[];
780
+ onClose: () => void;
781
+ AvatarComponent: React.ComponentType<AvatarProps>;
782
+ /** Modal title (default: 'Add Member') */
783
+ title?: string;
784
+ /** Search input placeholder (default: 'Search by name, email or phone...') */
785
+ searchPlaceholder?: string;
786
+ /** Text shown while loading users (default: 'Loading users...') */
787
+ loadingText?: string;
788
+ /** Text shown when no users match the search (default: 'No users found.') */
789
+ emptyText?: string;
790
+ /** Label for the add button on each user row (default: 'Add') */
791
+ addLabel?: string;
792
+ /** Label shown while a user is being added (default: 'Adding...') */
793
+ addingLabel?: string;
794
+ /** Label shown when a user already belongs to the channel (default: 'Added') */
795
+ addedLabel?: string;
796
+ /** Custom user item component (replaces the default row) */
797
+ UserItemComponent?: React.ComponentType<AddMemberUserItemProps>;
798
+ /** Custom search input component */
799
+ SearchInputComponent?: React.ComponentType<{ value: string; onChange: (e: React.ChangeEvent<HTMLInputElement>) => void; placeholder: string }>;
800
+ };
801
+
802
+ export type ChannelInfoTabsProps = {
803
+ channel: Channel;
804
+ members: ChannelInfoMember[];
805
+ AvatarComponent: React.ComponentType<AvatarProps>;
806
+ currentUserId?: string;
807
+ currentUserRole?: string;
808
+ onAddMemberClick?: () => void;
809
+ onRemoveMember?: (id: string) => void;
810
+ onBanMember?: (id: string) => void;
811
+ onUnbanMember?: (id: string) => void;
812
+ onPromoteMember?: (id: string) => void;
813
+ onDemoteMember?: (id: string) => void;
814
+
815
+ /** Label for the 'Add Member' button in the Members tab (default: 'Add Member') */
816
+ addMemberButtonLabel?: string;
817
+ /** Custom component for the 'Add Member' button */
818
+ AddMemberButtonComponent?: React.ComponentType<AddMemberButtonProps>;
819
+
820
+ /** Custom sub-component overrides */
821
+ MemberItemComponent?: React.ComponentType<ChannelInfoMemberItemProps>;
822
+ MediaItemComponent?: React.ComponentType<ChannelInfoMediaItemProps>;
823
+ LinkItemComponent?: React.ComponentType<ChannelInfoLinkItemProps>;
824
+ FileItemComponent?: React.ComponentType<ChannelInfoFileItemProps>;
825
+ EmptyStateComponent?: React.ComponentType<ChannelInfoEmptyStateProps>;
826
+ LoadingComponent?: React.ComponentType;
827
+ };
828
+
829
+ export type ChannelInfoProps = {
830
+ /** Optional channel override. Defaults to activeChannel from context */
831
+ channel?: Channel;
832
+ /** Additional CSS class */
833
+ className?: string;
834
+ /** Custom avatar component */
835
+ AvatarComponent?: React.ComponentType<AvatarProps>;
836
+ /** Optional callback when the user clicks a close button */
837
+ onClose?: () => void;
838
+ /** Custom Title String for the banner */
839
+ title?: string;
840
+
841
+ /** Custom components to replace internal sections */
842
+ HeaderComponent?: React.ComponentType<ChannelInfoHeaderProps>;
843
+ CoverComponent?: React.ComponentType<ChannelInfoCoverProps>;
844
+ ActionsComponent?: React.ComponentType<ChannelInfoActionsProps>;
845
+ TabsComponent?: React.ComponentType<ChannelInfoTabsProps>;
846
+ /** Custom component replacing the entire AddMemberModal */
847
+ AddMemberModalComponent?: React.ComponentType<AddMemberModalProps>;
848
+ /** Custom component replacing the entire EditChannelModal */
849
+ EditChannelModalComponent?: React.ComponentType<EditChannelModalProps>;
850
+
851
+ /** Custom sub-component overrides (passed through to TabsComponent) */
852
+ MemberItemComponent?: React.ComponentType<ChannelInfoMemberItemProps>;
853
+ MediaItemComponent?: React.ComponentType<ChannelInfoMediaItemProps>;
854
+ LinkItemComponent?: React.ComponentType<ChannelInfoLinkItemProps>;
855
+ FileItemComponent?: React.ComponentType<ChannelInfoFileItemProps>;
856
+ EmptyStateComponent?: React.ComponentType<ChannelInfoEmptyStateProps>;
857
+ LoadingComponent?: React.ComponentType;
858
+
859
+ /** Add Member customization (passed through to AddMemberModal) */
860
+ addMemberModalTitle?: string;
861
+ addMemberSearchPlaceholder?: string;
862
+ addMemberLoadingText?: string;
863
+ addMemberEmptyText?: string;
864
+ addMemberAddLabel?: string;
865
+ addMemberAddingLabel?: string;
866
+ addMemberAddedLabel?: string;
867
+ /** Label for the 'Add Member' button in Members tab */
868
+ addMemberButtonLabel?: string;
869
+ /** Custom component for the 'Add Member' button */
870
+ AddMemberButtonComponent?: React.ComponentType<AddMemberButtonProps>;
871
+
872
+ /** Edit Channel customization (passed through to EditChannelModal) */
873
+ /** Override edit channel save logic */
874
+ onEditChannel?: (data: EditChannelData) => Promise<void>;
875
+ editChannelModalTitle?: string;
876
+ editChannelNameLabel?: string;
877
+ editChannelDescriptionLabel?: string;
878
+ editChannelNamePlaceholder?: string;
879
+ editChannelDescriptionPlaceholder?: string;
880
+ editChannelPublicLabel?: string;
881
+ editChannelSaveLabel?: string;
882
+ editChannelCancelLabel?: string;
883
+ editChannelSavingLabel?: string;
884
+ editChannelChangeAvatarLabel?: string;
885
+ editChannelImageAccept?: string;
886
+ editChannelMaxImageSize?: number;
887
+ editChannelMaxImageSizeError?: string;
888
+
889
+ /** Action Labels */
890
+ actionsSearchLabel?: string;
891
+ actionsSettingsLabel?: string;
892
+ actionsDeleteLabel?: string;
893
+ actionsLeaveLabel?: string;
894
+
895
+ /** Action callbacks */
896
+ onSearchClick?: () => void;
897
+ onLeaveChannel?: () => void;
898
+ onDeleteChannel?: () => void;
899
+ onAddMemberClick?: () => void;
900
+ onRemoveMember?: (id: string) => void;
901
+ onBanMember?: (id: string) => void;
902
+ onUnbanMember?: (id: string) => void;
903
+ onPromoteMember?: (id: string) => void;
904
+ onDemoteMember?: (id: string) => void;
905
+
906
+ /** Block/Unblock callbacks (messaging channels only) */
907
+ onBlockUser?: () => void;
908
+ onUnblockUser?: () => void;
909
+ /** I18n labels for block/unblock actions */
910
+ actionsBlockLabel?: string;
911
+ actionsUnblockLabel?: string;
912
+ };
913
+
914
+ /* ----------------------------------------------------------
915
+ Message Search Panel types
916
+ ---------------------------------------------------------- */
917
+ export type SearchResultMessage = {
918
+ id: string;
919
+ text?: string;
920
+ user_id?: string;
921
+ user?: { id?: string; name?: string; avatar?: string; image?: string; avatar_url?: string };
922
+ created_at?: string;
923
+ [key: string]: any;
924
+ };
925
+
926
+ export type MessageSearchPanelProps = {
927
+ isOpen: boolean;
928
+ onClose: () => void;
929
+ channel: Channel;
930
+ /** Custom avatar component */
931
+ AvatarComponent?: React.ComponentType<AvatarProps>;
932
+ /** Title for the panel */
933
+ title?: string;
934
+ /** Search input placeholder */
935
+ placeholder?: string;
936
+ /** Text shown when loading */
937
+ loadingText?: string;
938
+ /** Text shown when no more messages or no results */
939
+ emptyText?: string;
940
+ /** Debounce wait time in ms (default: 500) */
941
+ debounceMs?: number;
942
+ };
943
+
944
+ /* ----------------------------------------------------------
945
+ Channel Settings Panel types
946
+ ---------------------------------------------------------- */
947
+ export type ChannelSettingsPanelProps = {
948
+ isOpen: boolean;
949
+ onClose: () => void;
950
+ channel: Channel;
951
+ /** Title for the settings panel */
952
+ title?: string;
953
+ /** Custom slow mode options */
954
+ slowModeOptions?: { label: string; value: number }[];
955
+ };