@ermis-network/ermis-chat-react 1.0.8 → 2.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 (99) hide show
  1. package/dist/index.cjs +15295 -4209
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +701 -195
  4. package/dist/index.css.map +1 -1
  5. package/dist/index.d.mts +862 -94
  6. package/dist/index.d.ts +862 -94
  7. package/dist/index.mjs +15246 -4186
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +9 -4
  10. package/src/channelTypeUtils.ts +1 -1
  11. package/src/components/Avatar.tsx +2 -1
  12. package/src/components/Channel.tsx +6 -2
  13. package/src/components/ChannelActions.tsx +61 -2
  14. package/src/components/ChannelHeader.tsx +19 -5
  15. package/src/components/ChannelInfo/AddMemberModal.tsx +5 -1
  16. package/src/components/ChannelInfo/ChannelInfo.tsx +330 -187
  17. package/src/components/ChannelInfo/ChannelInfoTabs.tsx +59 -297
  18. package/src/components/ChannelInfo/ChannelSettingsPanel.tsx +30 -174
  19. package/src/components/ChannelInfo/EditChannelModal.tsx +4 -1
  20. package/src/components/ChannelInfo/MediaGridItem.tsx +12 -2
  21. package/src/components/ChannelInfo/MemberListItem.tsx +2 -3
  22. package/src/components/ChannelInfo/MessageSearchPanel.tsx +27 -126
  23. package/src/components/ChannelInfo/States.tsx +1 -1
  24. package/src/components/ChannelInfo/index.ts +3 -0
  25. package/src/components/ChannelInfo/useChannelInfoTabs.tsx +386 -0
  26. package/src/components/ChannelInfo/useChannelSettings.ts +212 -0
  27. package/src/components/ChannelInfo/useMessageSearch.tsx +141 -0
  28. package/src/components/ChannelList.tsx +177 -290
  29. package/src/components/CreateChannelModal.tsx +166 -88
  30. package/src/components/Dropdown.tsx +1 -16
  31. package/src/components/EditPreview.tsx +1 -0
  32. package/src/components/ErmisCallProvider.tsx +72 -17
  33. package/src/components/ErmisCallUI.tsx +43 -20
  34. package/src/components/FlatTopicGroupItem.tsx +232 -0
  35. package/src/components/ForwardMessageModal.tsx +31 -77
  36. package/src/components/MediaLightbox.tsx +62 -40
  37. package/src/components/MentionSuggestions.tsx +47 -35
  38. package/src/components/MessageActionsBox.tsx +4 -1
  39. package/src/components/MessageInput.tsx +137 -16
  40. package/src/components/MessageInputDefaults.tsx +127 -1
  41. package/src/components/MessageItem.tsx +93 -26
  42. package/src/components/MessageQuickReactions.tsx +153 -26
  43. package/src/components/MessageReactions.tsx +2 -1
  44. package/src/components/MessageRenderers.tsx +111 -39
  45. package/src/components/Panel.tsx +1 -14
  46. package/src/components/PinnedMessages.tsx +17 -5
  47. package/src/components/PreviewOverlay.tsx +24 -0
  48. package/src/components/ReadReceipts.tsx +2 -1
  49. package/src/components/TopicList.tsx +221 -0
  50. package/src/components/TopicModal.tsx +4 -1
  51. package/src/components/TypingIndicator.tsx +14 -5
  52. package/src/components/UserPicker.tsx +87 -10
  53. package/src/components/VirtualMessageList.tsx +106 -20
  54. package/src/context/ChatComponentsContext.tsx +14 -0
  55. package/src/context/ChatProvider.tsx +18 -14
  56. package/src/context/ErmisCallContext.tsx +4 -0
  57. package/src/hooks/useChannelCapabilities.ts +7 -4
  58. package/src/hooks/useChannelData.ts +10 -3
  59. package/src/hooks/useChannelListUpdates.ts +72 -20
  60. package/src/hooks/useChannelMessages.ts +72 -10
  61. package/src/hooks/useChannelRowUpdates.ts +24 -5
  62. package/src/hooks/useChatUser.ts +31 -0
  63. package/src/hooks/useContactChannels.ts +45 -0
  64. package/src/hooks/useContactCount.ts +50 -0
  65. package/src/hooks/useDownloadHandler.ts +36 -0
  66. package/src/hooks/useDragAndDrop.ts +79 -0
  67. package/src/hooks/useForwardMessage.ts +112 -0
  68. package/src/hooks/useInviteChannels.ts +88 -0
  69. package/src/hooks/useInviteCount.ts +104 -0
  70. package/src/hooks/useMentions.ts +0 -1
  71. package/src/hooks/useMessageActions.ts +13 -10
  72. package/src/hooks/usePendingState.ts +21 -4
  73. package/src/hooks/usePreviewState.ts +69 -0
  74. package/src/hooks/useStickerPicker.ts +62 -0
  75. package/src/hooks/useTopicGroupUpdates.ts +197 -0
  76. package/src/index.ts +56 -6
  77. package/src/messageTypeUtils.ts +13 -1
  78. package/src/styles/_base.css +0 -1
  79. package/src/styles/_call-ui.css +59 -2
  80. package/src/styles/_channel-info.css +41 -4
  81. package/src/styles/_channel-list.css +97 -57
  82. package/src/styles/_create-channel-modal.css +10 -0
  83. package/src/styles/_forward-modal.css +16 -1
  84. package/src/styles/_media-lightbox.css +32 -0
  85. package/src/styles/_mentions.css +1 -1
  86. package/src/styles/_message-actions.css +3 -4
  87. package/src/styles/_message-bubble.css +286 -107
  88. package/src/styles/_message-input.css +131 -0
  89. package/src/styles/_message-list.css +33 -17
  90. package/src/styles/_message-quick-reactions.css +40 -9
  91. package/src/styles/_message-reactions.css +4 -0
  92. package/src/styles/_modal.css +2 -1
  93. package/src/styles/_preview-overlay.css +38 -0
  94. package/src/styles/_tokens.css +17 -15
  95. package/src/styles/_typing-indicator.css +7 -1
  96. package/src/styles/index.css +1 -0
  97. package/src/types.ts +362 -14
  98. package/src/utils/avatarColors.ts +48 -0
  99. package/src/utils.ts +193 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ermis-network/ermis-chat-react",
3
- "version": "1.0.8",
3
+ "version": "2.0.0",
4
4
  "description": "React UI components for Ermis Chat",
5
5
  "author": "Ermis",
6
6
  "homepage": "https://ermis.network/",
@@ -20,8 +20,10 @@
20
20
  "/src"
21
21
  ],
22
22
  "dependencies": {
23
- "@ermis-network/ermis-chat-sdk": "1.0.8",
24
- "virtua": "^0.48.8"
23
+ "@ermis-network/ermis-chat-sdk": "*",
24
+ "react-ts-audio-recorder": "^1.1.4",
25
+ "virtua": "^0.48.8",
26
+ "frimousse": "^0.3.0"
25
27
  },
26
28
  "peerDependencies": {
27
29
  "react": ">=18.0.0",
@@ -30,13 +32,16 @@
30
32
  "devDependencies": {
31
33
  "@types/react": "^18.2.0",
32
34
  "@types/react-dom": "^18.2.0",
35
+ "lucide-react": "^0.474.0",
36
+ "motion": "^12.0.0",
33
37
  "react": "^18.2.0",
34
38
  "react-dom": "^18.2.0",
39
+ "react-virtuoso": "^4.12.5",
35
40
  "tsup": "^8.0.0",
36
41
  "typescript": "^5.9.3"
37
42
  },
38
43
  "scripts": {
39
- "build": "rm -rf dist && tsup",
44
+ "build": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\" && tsup",
40
45
  "dev": "tsup --watch"
41
46
  }
42
47
  }
@@ -22,7 +22,7 @@ export function isDirectChannel(channel: Channel | null | undefined): boolean {
22
22
 
23
23
  /** Channel is a topic (sub-channel of a group channel) */
24
24
  export function isTopicChannel(channel: Channel | null | undefined): boolean {
25
- return channel ? (channel.type === 'topic' || Boolean(channel.data?.parent_cid)) : false;
25
+ return channel ? channel.type === 'topic' || Boolean(channel.data?.parent_cid) : false;
26
26
  }
27
27
 
28
28
  /** Channel is a public group that users can join without invite */
@@ -1,5 +1,6 @@
1
1
  import React, { useMemo, useState } from 'react';
2
2
  import { MediaLightbox } from './MediaLightbox';
3
+ import { getAvatarGradient } from '../utils/avatarColors';
3
4
  import type { AvatarProps } from '../types';
4
5
 
5
6
  export type { AvatarProps } from '../types';
@@ -85,7 +86,7 @@ export const Avatar: React.FC<AvatarProps> = React.memo(({
85
86
  {/* 1. Underlying Fallback (Placeholder) */}
86
87
  <div
87
88
  className="ermis-avatar ermis-avatar--fallback"
88
- style={contentStyle}
89
+ style={{ ...contentStyle, background: getAvatarGradient(name) }}
89
90
  title={name}
90
91
  >
91
92
  {initials}
@@ -1,5 +1,6 @@
1
1
  import React, { useEffect, useMemo, useState } from 'react';
2
2
  import { useChatClient } from '../hooks/useChatClient';
3
+ import { useChatComponents } from '../context/ChatComponentsContext';
3
4
  import { useBannedState } from '../hooks/useBannedState';
4
5
  import { useBlockedState } from '../hooks/useBlockedState';
5
6
  import { ForwardMessageModal } from './ForwardMessageModal';
@@ -25,9 +26,12 @@ export const Channel: React.FC<ChannelProps> = React.memo(({
25
26
  className,
26
27
  EmptyStateIndicator = DefaultEmpty,
27
28
  HeaderComponent,
28
- ForwardMessageModalComponent = ForwardMessageModal,
29
+ ForwardMessageModalComponent: ForwardMessageModalProp,
29
30
  }) => {
30
31
  const { activeChannel, client, forwardingMessage, setForwardingMessage } = useChatClient();
32
+ const { ForwardMessageModalComponent: ForwardMessageModalContext } = useChatComponents();
33
+
34
+ const ForwardMessageModalView = ForwardMessageModalProp || ForwardMessageModalContext || ForwardMessageModal;
31
35
  const { isBanned } = useBannedState(activeChannel, client.userID);
32
36
  const { isBlocked } = useBlockedState(activeChannel, client.userID);
33
37
 
@@ -64,7 +68,7 @@ export const Channel: React.FC<ChannelProps> = React.memo(({
64
68
  {HeaderComponent && headerData && <HeaderComponent {...headerData} />}
65
69
  {children}
66
70
  {forwardingMessage && (
67
- <ForwardMessageModalComponent
71
+ <ForwardMessageModalView
68
72
  message={forwardingMessage}
69
73
  onDismiss={() => setForwardingMessage(null)}
70
74
  />
@@ -32,6 +32,8 @@ export function computeDefaultActions(
32
32
  onAddTopic?: (channel: Channel) => void;
33
33
  onEditTopic?: (channel: Channel) => void;
34
34
  onToggleCloseTopic?: (channel: Channel, isClosed: boolean) => void;
35
+ onDeleteTopic?: (channel: Channel) => void;
36
+ onTruncateChannel?: (channel: Channel) => void;
35
37
  isBlocked?: boolean;
36
38
  actionLabels?: ChannelActionLabels;
37
39
  actionIcons?: ChannelActionIcons;
@@ -99,6 +101,25 @@ export function computeDefaultActions(
99
101
  }
100
102
  },
101
103
  });
104
+
105
+ // Direct channel: Truncate / Clear history
106
+ actions.push({
107
+ id: 'truncate',
108
+ label: actionLabels?.truncateChannel || 'Clear history',
109
+ icon: actionIcons?.TruncateChannelIcon || <TrashIcon />,
110
+ isDanger: true,
111
+ onClick: async (ch) => {
112
+ if (options?.onTruncateChannel) {
113
+ await options.onTruncateChannel(ch);
114
+ } else {
115
+ try {
116
+ await ch.truncate();
117
+ } catch (e) {
118
+ console.error('Error clearing channel history', e);
119
+ }
120
+ }
121
+ },
122
+ });
102
123
  } else if (isTopic) {
103
124
  // Topic: Edit topic (owner & moder only)
104
125
  if (canManageChannel(role)) {
@@ -118,8 +139,46 @@ export function computeDefaultActions(
118
139
  label: isClosed ? (actionLabels?.reopenTopic || 'Reopen topic') : (actionLabels?.closeTopic || 'Close topic'),
119
140
  icon: isClosed ? (actionIcons?.ReopenTopicIcon || <UnlockIcon />) : (actionIcons?.CloseTopicIcon || <LockIcon />),
120
141
  isDanger: !isClosed,
121
- onClick: (ch) => {
122
- options?.onToggleCloseTopic?.(ch, isClosed);
142
+ onClick: async (ch) => {
143
+ if (options?.onToggleCloseTopic) {
144
+ await options.onToggleCloseTopic(ch, isClosed);
145
+ return;
146
+ }
147
+ // Default behavior: call SDK API directly
148
+ const parentCid = ch.data?.parent_cid as string | undefined;
149
+ if (!parentCid) return;
150
+ try {
151
+ const client = ch.getClient();
152
+ const parentChannel = client.activeChannels[parentCid];
153
+ if (!parentChannel) return;
154
+ if (isClosed) {
155
+ await parentChannel.reopenTopic(ch.cid);
156
+ } else {
157
+ await parentChannel.closeTopic(ch.cid);
158
+ }
159
+ } catch (err) {
160
+ console.error('Failed to toggle topic close state', err);
161
+ }
162
+ },
163
+ });
164
+ }
165
+ // Topic: Delete (owner only)
166
+ if (role === CHANNEL_ROLES.OWNER) {
167
+ actions.push({
168
+ id: 'delete_topic',
169
+ label: actionLabels?.deleteTopic || 'Delete topic',
170
+ icon: actionIcons?.DeleteTopicIcon || <TrashIcon />,
171
+ isDanger: true,
172
+ onClick: async (ch) => {
173
+ if (options?.onDeleteTopic) {
174
+ await options.onDeleteTopic(ch);
175
+ return;
176
+ }
177
+ try {
178
+ await ch.delete();
179
+ } catch (err) {
180
+ console.error('Failed to delete topic', err);
181
+ }
123
182
  },
124
183
  });
125
184
  }
@@ -59,9 +59,23 @@ export const ChannelHeader: React.FC<ChannelHeaderProps> = React.memo(({
59
59
 
60
60
  useEffect(() => {
61
61
  if (!activeChannel) return;
62
- const sub = activeChannel.on('channel.updated', () => setChannelUpdateCount(c => c + 1));
63
- return () => sub.unsubscribe();
64
- }, [activeChannel]);
62
+ const handleUpdate = () => setChannelUpdateCount((c) => c + 1);
63
+
64
+ const sub1 = activeChannel.on('channel.updated', handleUpdate);
65
+
66
+ // Also listen for client-level notifications that might affect this channel's roles/members
67
+ // We only care about this for messaging (direct) channels to update online status
68
+ const sub2 = client.on('notification.invite_accepted', (event) => {
69
+ if (event.cid === activeChannel.cid && isDirectChannel(activeChannel)) {
70
+ handleUpdate();
71
+ }
72
+ });
73
+
74
+ return () => {
75
+ sub1.unsubscribe();
76
+ sub2.unsubscribe();
77
+ };
78
+ }, [activeChannel, client]);
65
79
 
66
80
  // eslint-disable-next-line react-hooks/exhaustive-deps
67
81
  const channelName = useMemo(() =>
@@ -108,13 +122,13 @@ export const ChannelHeader: React.FC<ChannelHeaderProps> = React.memo(({
108
122
  if (memberId !== currentUserId) return memberId;
109
123
  }
110
124
  return undefined;
111
- }, [activeChannel, currentUserId]);
125
+ }, [activeChannel, currentUserId, channelUpdateCount]);
112
126
 
113
127
  // Check if this is a friend channel (both members are owner).
114
128
  const isFriend = useMemo(() => {
115
129
  if (!otherUserId || !currentUserId || !activeChannel) return false;
116
130
  return isFriendChannel(activeChannel, otherUserId, currentUserId);
117
- }, [activeChannel, otherUserId, currentUserId]);
131
+ }, [activeChannel, otherUserId, currentUserId, channelUpdateCount]);
118
132
 
119
133
  // Derive online status from watchers + subscribe to realtime events.
120
134
  const [onlineStatus, setOnlineStatus] = useState<OnlineStatus>('unknown');
@@ -1,7 +1,8 @@
1
1
  import React, { useState, useMemo, useCallback } from 'react';
2
- import { Modal } from '../Modal';
2
+ import { Modal as DefaultModal } from '../Modal';
3
3
  import { UserPicker } from '../UserPicker';
4
4
  import { Avatar } from '../Avatar';
5
+ import { useChatComponents } from '../../context/ChatComponentsContext';
5
6
  import type { AddMemberModalProps, UserPickerUser } from '../../types';
6
7
 
7
8
  export const AddMemberModal: React.FC<AddMemberModalProps> = ({
@@ -19,6 +20,8 @@ export const AddMemberModal: React.FC<AddMemberModalProps> = ({
19
20
  UserItemComponent,
20
21
  SearchInputComponent,
21
22
  }) => {
23
+ const { ModalComponent } = useChatComponents();
24
+ const Modal = ModalComponent || DefaultModal;
22
25
  const [selectedUsers, setSelectedUsers] = useState<UserPickerUser[]>([]);
23
26
  const [isAdding, setIsAdding] = useState(false);
24
27
 
@@ -62,6 +65,7 @@ export const AddMemberModal: React.FC<AddMemberModalProps> = ({
62
65
  <Modal isOpen onClose={onClose} title={title} maxWidth="480px" footer={footer}>
63
66
  <UserPicker
64
67
  mode="checkbox"
68
+ friendsOnly={true}
65
69
  onSelectionChange={handleSelectionChange}
66
70
  excludeUserIds={excludeUserIds}
67
71
  pageSize={30}