@messenger-box/tailwind-ui-inbox 10.0.3-alpha.70 → 10.0.3-alpha.72

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 (155) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/lib/components/AIAgent/AIAgent.d.ts +14 -0
  3. package/lib/components/AIAgent/AIAgent.d.ts.map +1 -0
  4. package/lib/components/AIAgent/AIAgent.js +1148 -0
  5. package/lib/components/AIAgent/AIAgent.js.map +1 -0
  6. package/lib/components/AIAgent/index.d.ts +2 -0
  7. package/lib/components/AIAgent/index.d.ts.map +1 -0
  8. package/lib/components/InboxMessage/InputComponent.d.ts +9 -0
  9. package/lib/components/InboxMessage/InputComponent.d.ts.map +1 -0
  10. package/lib/components/InboxMessage/InputComponent.js +210 -0
  11. package/lib/components/InboxMessage/InputComponent.js.map +1 -0
  12. package/lib/components/InboxMessage/LeftSidebar.d.ts +2 -0
  13. package/lib/components/InboxMessage/LeftSidebar.d.ts.map +1 -1
  14. package/lib/components/InboxMessage/LeftSidebar.js +16 -5
  15. package/lib/components/InboxMessage/LeftSidebar.js.map +1 -1
  16. package/lib/components/InboxMessage/MessageInput.d.ts.map +1 -1
  17. package/lib/components/InboxMessage/MessageInput.js +14 -10
  18. package/lib/components/InboxMessage/MessageInput.js.map +1 -1
  19. package/lib/components/InboxMessage/MessageInputComponent.d.ts +9 -0
  20. package/lib/components/InboxMessage/MessageInputComponent.d.ts.map +1 -0
  21. package/lib/components/InboxMessage/MessageInputComponent.js +173 -0
  22. package/lib/components/InboxMessage/MessageInputComponent.js.map +1 -0
  23. package/lib/components/InboxMessage/Messages.d.ts +3 -1
  24. package/lib/components/InboxMessage/Messages.d.ts.map +1 -1
  25. package/lib/components/InboxMessage/Messages.js +16 -60
  26. package/lib/components/InboxMessage/Messages.js.map +1 -1
  27. package/lib/components/InboxMessage/MessagesBuilderUi.d.ts +17 -0
  28. package/lib/components/InboxMessage/MessagesBuilderUi.d.ts.map +1 -0
  29. package/lib/components/InboxMessage/MessagesBuilderUi.js +162 -0
  30. package/lib/components/InboxMessage/MessagesBuilderUi.js.map +1 -0
  31. package/lib/components/InboxMessage/UploadImageButton.d.ts +1 -0
  32. package/lib/components/InboxMessage/UploadImageButton.d.ts.map +1 -1
  33. package/lib/components/InboxMessage/UploadImageButton.js +3 -3
  34. package/lib/components/InboxMessage/UploadImageButton.js.map +1 -1
  35. package/lib/components/InboxMessage/index.d.ts +3 -0
  36. package/lib/components/InboxMessage/index.d.ts.map +1 -1
  37. package/lib/components/InboxMessage/message-widgets/CommonMessage.d.ts.map +1 -1
  38. package/lib/components/InboxMessage/message-widgets/CommonMessage.js +11 -6
  39. package/lib/components/InboxMessage/message-widgets/CommonMessage.js.map +1 -1
  40. package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts +14 -0
  41. package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts.map +1 -0
  42. package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.js +1525 -0
  43. package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.js.map +1 -0
  44. package/lib/components/InboxMessage/message-widgets/PlainMessage.d.ts.map +1 -1
  45. package/lib/components/InboxMessage/message-widgets/PlainMessage.js +6 -3
  46. package/lib/components/InboxMessage/message-widgets/PlainMessage.js.map +1 -1
  47. package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.d.ts +2 -0
  48. package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.d.ts.map +1 -1
  49. package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.js +225 -26
  50. package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.js.map +1 -1
  51. package/lib/components/InboxMessage/message-widgets/index.d.ts +1 -0
  52. package/lib/components/InboxMessage/message-widgets/index.d.ts.map +1 -1
  53. package/lib/components/index.d.ts +2 -1
  54. package/lib/components/index.d.ts.map +1 -1
  55. package/lib/compute.d.ts.map +1 -1
  56. package/lib/compute.js +79 -3
  57. package/lib/compute.js.map +1 -1
  58. package/lib/config/env-config.d.ts +6 -0
  59. package/lib/config/env-config.d.ts.map +1 -1
  60. package/lib/config/env-config.js +19 -1
  61. package/lib/config/env-config.js.map +1 -1
  62. package/lib/container/AiInbox.d.ts +15 -0
  63. package/lib/container/AiInbox.d.ts.map +1 -0
  64. package/lib/container/AiInbox.js +1520 -0
  65. package/lib/container/AiInbox.js.map +1 -0
  66. package/lib/container/AiInboxWithLoader.d.ts +36 -0
  67. package/lib/container/AiInboxWithLoader.d.ts.map +1 -0
  68. package/lib/container/AiInboxWithLoader.js +300 -0
  69. package/lib/container/AiInboxWithLoader.js.map +1 -0
  70. package/lib/container/AiLandingInput.d.ts +4 -0
  71. package/lib/container/AiLandingInput.d.ts.map +1 -0
  72. package/lib/container/AiLandingInput.js +164 -0
  73. package/lib/container/AiLandingInput.js.map +1 -0
  74. package/lib/container/Inbox.d.ts.map +1 -1
  75. package/lib/container/Inbox.js +177 -58
  76. package/lib/container/Inbox.js.map +1 -1
  77. package/lib/container/InboxAiMessagesLoader.d.ts +36 -0
  78. package/lib/container/InboxAiMessagesLoader.d.ts.map +1 -0
  79. package/lib/container/InboxAiMessagesLoader.js +47 -0
  80. package/lib/container/InboxAiMessagesLoader.js.map +1 -0
  81. package/lib/container/InboxContainer.d.ts +12 -0
  82. package/lib/container/InboxContainer.d.ts.map +1 -0
  83. package/lib/container/InboxContainer.js +31 -0
  84. package/lib/container/InboxContainer.js.map +1 -0
  85. package/lib/container/InboxTemplate1.d.ts +15 -0
  86. package/lib/container/InboxTemplate1.d.ts.map +1 -0
  87. package/lib/container/InboxTemplate1.js +1375 -0
  88. package/lib/container/InboxTemplate1.js.map +1 -0
  89. package/lib/container/InboxTemplate1WithLoader.d.ts +36 -0
  90. package/lib/container/InboxTemplate1WithLoader.d.ts.map +1 -0
  91. package/lib/container/InboxTemplate2.d.ts +15 -0
  92. package/lib/container/InboxTemplate2.d.ts.map +1 -0
  93. package/lib/container/InboxTemplate2.js +1426 -0
  94. package/lib/container/InboxTemplate2.js.map +1 -0
  95. package/lib/container/InboxWithAiLoader.d.ts +15 -0
  96. package/lib/container/InboxWithAiLoader.d.ts.map +1 -0
  97. package/lib/container/InboxWithAiLoader.js +56 -0
  98. package/lib/container/InboxWithAiLoader.js.map +1 -0
  99. package/lib/container/ServiceInbox.js +1 -1
  100. package/lib/container/ServiceInbox.js.map +1 -1
  101. package/lib/container/ThreadMessages.js +1 -1
  102. package/lib/container/ThreadMessages.js.map +1 -1
  103. package/lib/container/ThreadMessagesInbox.js +1 -1
  104. package/lib/container/ThreadMessagesInbox.js.map +1 -1
  105. package/lib/container/Threads.js +1 -1
  106. package/lib/container/Threads.js.map +1 -1
  107. package/lib/container/index.d.ts +4 -1
  108. package/lib/container/index.d.ts.map +1 -1
  109. package/lib/index.js +1 -1
  110. package/lib/machines/aiAgentMachine.d.ts +3 -0
  111. package/lib/machines/aiAgentMachine.d.ts.map +1 -0
  112. package/lib/machines/aiAgentMachine.js +1040 -0
  113. package/lib/machines/aiAgentMachine.js.map +1 -0
  114. package/lib/machines/types.d.ts +77 -0
  115. package/lib/machines/types.d.ts.map +1 -0
  116. package/lib/routes.json +40 -0
  117. package/lib/templates/InboxWithAi.d.ts +15 -0
  118. package/lib/templates/InboxWithAi.d.ts.map +1 -0
  119. package/lib/templates/InboxWithAi.js +405 -0
  120. package/lib/templates/InboxWithAi.js.map +1 -0
  121. package/lib/templates/InboxWithAi.tsx +502 -0
  122. package/package.json +7 -5
  123. package/src/components/AIAgent/AIAgent.tsx +1351 -0
  124. package/src/components/AIAgent/README.md +82 -0
  125. package/src/components/AIAgent/index.ts +1 -0
  126. package/src/components/InboxMessage/InputComponent.tsx +263 -0
  127. package/src/components/InboxMessage/LeftSidebar.tsx +14 -4
  128. package/src/components/InboxMessage/MessageInput.tsx +73 -66
  129. package/src/components/InboxMessage/MessageInputComponent.tsx +245 -0
  130. package/src/components/InboxMessage/Messages.tsx +24 -68
  131. package/src/components/InboxMessage/MessagesBuilderUi.tsx +205 -0
  132. package/src/components/InboxMessage/UploadImageButton.tsx +3 -2
  133. package/src/components/InboxMessage/index.ts +3 -0
  134. package/src/components/InboxMessage/message-widgets/CommonMessage.tsx +39 -21
  135. package/src/components/InboxMessage/message-widgets/ModernMessageGroup.tsx +1968 -0
  136. package/src/components/InboxMessage/message-widgets/PlainMessage.tsx +6 -2
  137. package/src/components/InboxMessage/message-widgets/SlackLikeMessageGroup.tsx +337 -53
  138. package/src/components/InboxMessage/message-widgets/index.ts +1 -0
  139. package/src/components/index.ts +4 -0
  140. package/src/compute.ts +83 -2
  141. package/src/config/env-config.ts +6 -0
  142. package/src/container/AiInbox.tsx +1796 -0
  143. package/src/container/AiInboxWithLoader.tsx +356 -0
  144. package/src/container/AiLandingInput.tsx +168 -0
  145. package/src/container/Inbox.tsx +280 -130
  146. package/src/container/InboxAiMessagesLoader.tsx +68 -0
  147. package/src/container/InboxContainer.tsx +35 -0
  148. package/src/container/InboxTemplate1.tsx +1542 -0
  149. package/src/container/InboxTemplate1WithLoader.tsx +338 -0
  150. package/src/container/InboxTemplate2.tsx +1606 -0
  151. package/src/container/InboxWithAiLoader.tsx +76 -0
  152. package/src/container/index.ts +15 -1
  153. package/src/machines/aiAgentMachine.ts +1248 -0
  154. package/src/machines/types.ts +59 -0
  155. package/src/templates/InboxWithAi.tsx +502 -0
@@ -0,0 +1,245 @@
1
+ import React, { useCallback, useMemo, useState, useRef, useEffect } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { config } from '../../config';
4
+ import { UploadImageButton } from './UploadImageButton';
5
+ import { FilesList } from '../inbox';
6
+
7
+ type MessageInputProps = {
8
+ channelId?: string;
9
+ handleSend: (message: string, files: File[]) => Promise<void>;
10
+ placeholder?: string;
11
+ };
12
+
13
+ export const MessageInputComponent = ({ handleSend: handleSendProp, placeholder }: MessageInputProps) => {
14
+ const [message, setMessage] = useState('');
15
+ const [sending, setSending] = useState(false);
16
+ const [files, setFiles] = useState<File[]>([]);
17
+ const [showToast, setShowToast] = useState(false);
18
+ const [toastMessage, setToastMessage] = useState('');
19
+ const [isFocused, setIsFocused] = useState(false);
20
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
21
+ const { t } = useTranslation('translations');
22
+
23
+ // Auto-focus the textarea when component mounts
24
+ useEffect(() => {
25
+ if (textareaRef.current) {
26
+ textareaRef.current.focus();
27
+ }
28
+ }, []);
29
+
30
+ const showToastMessage = useCallback((message: string) => {
31
+ setToastMessage(message);
32
+ setShowToast(true);
33
+ setTimeout(() => setShowToast(false), 3000);
34
+ }, []);
35
+
36
+ const handleSend = useCallback(() => {
37
+ if (!message.trim() && files.length === 0) return;
38
+
39
+ setSending(true);
40
+ handleSendProp(message, files)
41
+ .then(() => {
42
+ setMessage('');
43
+ setFiles([]);
44
+ // Auto-focus the textarea after sending a message
45
+ setTimeout(() => {
46
+ if (textareaRef.current) {
47
+ textareaRef.current.focus();
48
+ }
49
+ }, 100);
50
+ })
51
+ .finally(() => setSending(false));
52
+ }, [files, handleSendProp, message]);
53
+
54
+ const handleKeyDown = useCallback(
55
+ (e) => {
56
+ const keyCode = e.which || e.keyCode;
57
+ if (keyCode == 13 && !e.shiftKey) {
58
+ e.preventDefault();
59
+ handleSend();
60
+ }
61
+ },
62
+ [handleSend],
63
+ );
64
+
65
+ const inputHeight = useMemo(() => {
66
+ const lines = message.split('\n').length;
67
+ return Math.max(48, Math.min(120, 48 + (lines - 1) * 20));
68
+ }, [message]);
69
+
70
+ const onUploadImageChange = useCallback(
71
+ ({ target }) => {
72
+ let fileList = [];
73
+ let index = 0;
74
+ if (files.length + target.files.length > config.FILES_PER_MESSAGE) {
75
+ showToastMessage(
76
+ t('tailwind_ui_inbox.you_can_not_upload_more_than_files', {
77
+ files_per_message: config.FILES_PER_MESSAGE,
78
+ }),
79
+ );
80
+ return;
81
+ }
82
+ while (target.files[index]) {
83
+ fileList.push(target.files[index]);
84
+ index += 1;
85
+ }
86
+ setFiles((oldFiles) => [...oldFiles, ...fileList]);
87
+ },
88
+ [setFiles, files, showToastMessage, t],
89
+ );
90
+
91
+ const canSend = message.trim() || files.length > 0;
92
+ const hasContent = message.trim().length > 0;
93
+
94
+ return (
95
+ <div className="bg-white">
96
+ {/* Toast notification */}
97
+ {showToast && (
98
+ <div className="fixed top-4 right-4 z-50 bg-orange-50 border border-orange-200 text-orange-800 px-4 py-3 rounded-lg shadow-lg animate-bounce">
99
+ <div className="flex items-center">
100
+ <svg className="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
101
+ <path
102
+ fillRule="evenodd"
103
+ d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
104
+ clipRule="evenodd"
105
+ />
106
+ </svg>
107
+ {toastMessage}
108
+ </div>
109
+ </div>
110
+ )}
111
+
112
+ {/* Files list */}
113
+ {files?.length > 0 && !sending && (
114
+ <div className="px-4 py-3 border-b border-gray-100 animate-fade-in">
115
+ <FilesList files={files} />
116
+ </div>
117
+ )}
118
+
119
+ <div className="p-4">
120
+ <div
121
+ className={`relative border-2 transition-all duration-200 ease-in-out ${
122
+ hasContent || isFocused
123
+ ? 'rounded-2xl border-gray-300 bg-white'
124
+ : 'rounded-2xl border-gray-300 bg-white'
125
+ } ${isFocused ? 'border-blue-500' : ''}`}
126
+ >
127
+ {/* Image/Upload icon */}
128
+ <div className="absolute left-4 top-1/2 transform -translate-y-1/2 z-10">
129
+ {hasContent || isFocused ? (
130
+ <UploadImageButton onChange={onUploadImageChange} />
131
+ ) : (
132
+ <svg
133
+ className="w-5 h-5 text-gray-400"
134
+ fill="none"
135
+ stroke="currentColor"
136
+ viewBox="0 0 24 24"
137
+ >
138
+ <path
139
+ strokeLinecap="round"
140
+ strokeLinejoin="round"
141
+ strokeWidth={2}
142
+ d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
143
+ />
144
+ </svg>
145
+ )}
146
+ </div>
147
+
148
+ {/* Textarea */}
149
+ <textarea
150
+ ref={textareaRef}
151
+ className={`w-full text-base bg-transparent border-none resize-none overflow-hidden text-gray-900 focus:outline-none focus:ring-0 scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-transparent ${
152
+ hasContent || isFocused
153
+ ? 'pl-14 pr-20 py-3 placeholder-gray-500'
154
+ : 'pl-12 pr-12 py-3 placeholder-gray-600'
155
+ }`}
156
+ style={{
157
+ height: hasContent || isFocused ? `${inputHeight}px` : '48px',
158
+ minHeight: '48px',
159
+ maxHeight: '120px',
160
+ }}
161
+ placeholder={placeholder || 'Message'}
162
+ value={sending ? '' : message}
163
+ onKeyDown={handleKeyDown}
164
+ onChange={(e) => setMessage(e.target.value)}
165
+ onFocus={() => setIsFocused(true)}
166
+ onBlur={() => setIsFocused(false)}
167
+ disabled={sending}
168
+ rows={1}
169
+ />
170
+
171
+ {/* Right side content */}
172
+ <div className="absolute right-3 top-1/2 transform -translate-y-1/2 flex items-center gap-2">
173
+ {hasContent && <span className="text-sm text-gray-500">{message.length}/10000</span>}
174
+
175
+ {hasContent || isFocused ? (
176
+ /* Send button when typing */
177
+ <button
178
+ className={`w-10 h-10 rounded-xl flex items-center justify-center transition-all duration-200 ease-in-out ${
179
+ canSend && !sending
180
+ ? 'bg-gray-900 hover:bg-gray-800 text-white shadow-md hover:shadow-lg'
181
+ : 'bg-gray-200 text-gray-400 cursor-not-allowed'
182
+ }`}
183
+ onClick={handleSend}
184
+ disabled={!canSend || sending}
185
+ type="button"
186
+ >
187
+ {sending ? (
188
+ <svg className="w-5 h-5 animate-spin" fill="none" viewBox="0 0 24 24">
189
+ <circle
190
+ className="opacity-25"
191
+ cx="12"
192
+ cy="12"
193
+ r="10"
194
+ stroke="currentColor"
195
+ strokeWidth="4"
196
+ ></circle>
197
+ <path
198
+ className="opacity-75"
199
+ fill="currentColor"
200
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
201
+ ></path>
202
+ </svg>
203
+ ) : (
204
+ <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
205
+ <path
206
+ strokeLinecap="round"
207
+ strokeLinejoin="round"
208
+ strokeWidth={2}
209
+ d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"
210
+ />
211
+ </svg>
212
+ )}
213
+ </button>
214
+ ) : (
215
+ /* Warning/Info icon when empty */
216
+ <svg
217
+ className="w-5 h-5 text-gray-400"
218
+ fill="none"
219
+ stroke="currentColor"
220
+ viewBox="0 0 24 24"
221
+ >
222
+ <path
223
+ strokeLinecap="round"
224
+ strokeLinejoin="round"
225
+ strokeWidth={2}
226
+ d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
227
+ />
228
+ </svg>
229
+ )}
230
+ </div>
231
+ </div>
232
+
233
+ {/* Helper text - only show when focused or has content */}
234
+ <div className="mt-2 px-1">
235
+ <span className="text-xs text-gray-400">Press Enter to send, Shift+Enter for new line</span>
236
+ </div>
237
+ {/* {(isFocused || hasContent) && (
238
+ <div className="mt-2 px-1">
239
+ <span className="text-xs text-gray-400">Press Enter to send, Shift+Enter for new line</span>
240
+ </div>
241
+ )} */}
242
+ </div>
243
+ </div>
244
+ );
245
+ };
@@ -1,7 +1,6 @@
1
1
  import { format, isToday, isYesterday } from 'date-fns';
2
2
  import React, { useMemo, useRef, useEffect, useState } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
- import { UserModalContent } from './UserModalContent';
5
4
  import { MessageSliceRenderer } from './message-widgets';
6
5
  import { SlackLikeMessageGroup } from './message-widgets/SlackLikeMessageGroup';
7
6
 
@@ -15,6 +14,8 @@ interface MessagesProps {
15
14
  subscribeToNewMessages?: () => any;
16
15
  subscribeToNewServiceMessages?: () => any;
17
16
  onMessageClick: (msg) => void;
17
+ isDesktopView?: boolean;
18
+ isSmallScreen?: boolean;
18
19
  }
19
20
 
20
21
  export const Messages = ({
@@ -26,20 +27,13 @@ export const Messages = ({
26
27
  subscribeToNewMessages,
27
28
  subscribeToNewServiceMessages,
28
29
  onMessageClick,
30
+ isDesktopView = false,
31
+ isSmallScreen = false,
29
32
  }: MessagesProps) => {
30
- const [isOpen, setIsOpen] = useState(false);
31
- const [selectedElement, setSelectedElement] = useState(null);
32
33
  const { t } = useTranslation('translations');
33
34
 
34
- const onOpen = (element?: any) => {
35
- setSelectedElement(element);
36
- setIsOpen(true);
37
- };
38
-
39
- const onClose = () => {
40
- setIsOpen(false);
41
- setSelectedElement(null);
42
- };
35
+ // Disable user modal behavior: no-op handler
36
+ const onOpen = () => {};
43
37
 
44
38
  useEffect(() => {
45
39
  subscribeToNewMessages?.();
@@ -105,15 +99,17 @@ export const Messages = ({
105
99
 
106
100
  return (
107
101
  <>
108
- <div className="w-full" ref={innerRef}>
102
+ <div
103
+ className={`w-full pb-8 pt-4 ${
104
+ isDesktopView ? 'space-y-8 max-w-full mx-auto' : isSmallScreen ? 'space-y-4' : 'space-y-6'
105
+ }`}
106
+ ref={innerRef}
107
+ >
109
108
  {messagesByDate?.map((section, sectionIndex) => (
110
- <div
111
- key={`section-${sectionIndex}`}
112
- className={sectionIndex === messagesByDate.length - 1 ? '' : 'mb-4'}
113
- >
109
+ <div key={`section-${sectionIndex}`} className="w-full">
114
110
  {/* Date separator */}
115
111
  {section.date && (
116
- <div className="flex items-center justify-center my-4">
112
+ <div className="flex items-center justify-center my-6">
117
113
  <div className="flex-grow border-t border-gray-200"></div>
118
114
  <div className="mx-4 px-3 py-1 bg-white border border-gray-200 rounded-full text-xs font-medium text-gray-600">
119
115
  {section.date}
@@ -123,59 +119,19 @@ export const Messages = ({
123
119
  )}
124
120
 
125
121
  {/* Messages grouped by user and time */}
126
- <SlackLikeMessageGroup
127
- messages={section.messages}
128
- currentUser={currentUser}
129
- onOpen={onOpen}
130
- onMessageClick={onMessageClick}
131
- />
122
+ <div className={`${isDesktopView ? 'mb-6' : 'mb-4'}`}>
123
+ <SlackLikeMessageGroup
124
+ messages={section.messages}
125
+ currentUser={currentUser}
126
+ onOpen={onOpen}
127
+ onMessageClick={onMessageClick}
128
+ isDesktopView={isDesktopView}
129
+ isSmallScreen={isSmallScreen}
130
+ />
131
+ </div>
132
132
  </div>
133
133
  ))}
134
134
  </div>
135
- <ChatModal element={selectedElement} isOpen={isOpen} onClose={onClose} />
136
- </>
137
- );
138
- };
139
-
140
- const ChatModal = ({ element, isOpen, onClose }) => {
141
- if (!isOpen) return null;
142
-
143
- return (
144
- <>
145
- {/* Modal Overlay */}
146
- <div className="fixed inset-0 bg-black bg-opacity-50 z-40" onClick={onClose} />
147
-
148
- {/* Modal Content */}
149
- <div className="fixed z-50 left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2">
150
- <div
151
- className="bg-white w-[1036px] h-[700px] rounded-lg shadow-xl"
152
- style={{
153
- boxShadow: '0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23)',
154
- }}
155
- >
156
- {/* Modal Header */}
157
- <div className="flex justify-between border-b border-gray-300 pb-4 pt-4">
158
- <button onClick={onClose} className="w-8 ml-3 text-black hover:text-black focus:outline-none">
159
- <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
160
- <path
161
- strokeLinecap="round"
162
- strokeLinejoin="round"
163
- strokeWidth={2}
164
- d="M6 18L18 6M6 6l12 12"
165
- />
166
- </svg>
167
- </button>
168
- </div>
169
-
170
- {/* Modal Body */}
171
- <div className="p-4">
172
- <UserModalContent
173
- username={element?.author?.username}
174
- image={element?.imageUrl ? element.imageUrl : ''}
175
- />
176
- </div>
177
- </div>
178
- </div>
179
135
  </>
180
136
  );
181
137
  };
@@ -0,0 +1,205 @@
1
+ import { format, isToday, isYesterday } from 'date-fns';
2
+ import React, { useMemo, useRef, useEffect, useState } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { UserModalContent } from './UserModalContent';
5
+ import { MessageSliceRenderer } from './message-widgets';
6
+ import { ModernMessageGroupComponent } from './message-widgets/ModernMessageGroup';
7
+
8
+ interface MessagesProps {
9
+ channelId: number;
10
+ currentUser: any;
11
+ channelMessages: any[];
12
+ totalCount: number;
13
+ innerRef?: any;
14
+ refetchMessages?: () => void;
15
+ subscribeToNewMessages?: () => any;
16
+ subscribeToNewServiceMessages?: () => any;
17
+ onMessageClick: (msg) => void;
18
+ isDesktopView?: boolean;
19
+ isSmallScreen?: boolean;
20
+ }
21
+
22
+ export const MessagesBuilderUi = ({
23
+ currentUser,
24
+ channelMessages,
25
+ refetchMessages,
26
+ innerRef,
27
+ channelId,
28
+ subscribeToNewMessages,
29
+ subscribeToNewServiceMessages,
30
+ onMessageClick,
31
+ isDesktopView = false,
32
+ isSmallScreen = false,
33
+ }: MessagesProps) => {
34
+ const [isOpen, setIsOpen] = useState(false);
35
+ const [selectedElement, setSelectedElement] = useState(null);
36
+ const { t } = useTranslation('translations');
37
+
38
+ const onOpen = (element?: any) => {
39
+ setSelectedElement(element);
40
+ setIsOpen(true);
41
+ };
42
+
43
+ const onClose = () => {
44
+ setIsOpen(false);
45
+ setSelectedElement(null);
46
+ };
47
+
48
+ useEffect(() => {
49
+ subscribeToNewMessages?.();
50
+ subscribeToNewServiceMessages?.();
51
+ }, [channelId]);
52
+
53
+ const messageListRef = useRef(null);
54
+ const scrollToBottom = React.useCallback(() => {
55
+ if (innerRef.current)
56
+ innerRef.current?.querySelector(':scope > :last-child')?.scrollIntoView({
57
+ behavior: 'smooth',
58
+ inline: 'start',
59
+ });
60
+ }, [innerRef]);
61
+
62
+ React.useEffect(() => {
63
+ if (channelMessages?.length) {
64
+ // scrollToBottom();
65
+ }
66
+ }, [channelMessages]);
67
+
68
+ const messageListWithDates = useMemo(() => {
69
+ let currentDate = '';
70
+ let res = [];
71
+
72
+ // Add original messages
73
+ channelMessages?.map((msg) => {
74
+ const date = new Date(msg.createdAt);
75
+ let msgDate;
76
+ if (isToday(date)) msgDate = t('tailwind_ui_inbox.today');
77
+ else if (isYesterday(date)) msgDate = t('tailwind_ui_inbox.yesterday');
78
+ else msgDate = format(new Date(msg.createdAt), 'eee, do MMMM');
79
+
80
+ if (msgDate !== currentDate) {
81
+ res.push({ type: 'date', content: msgDate });
82
+ currentDate = msgDate;
83
+ }
84
+ res.push(msg);
85
+ });
86
+
87
+ // Add dummy messages at the end if we have existing messages
88
+ if (channelMessages && channelMessages.length > 0) {
89
+ // Add today's date if not already added
90
+ const todayLabel = t('tailwind_ui_inbox.today');
91
+ if (currentDate !== todayLabel) {
92
+ res.push({ type: 'date', content: todayLabel });
93
+ }
94
+
95
+ // dummyMessages.forEach(msg => res.push(msg));
96
+ }
97
+
98
+ return res;
99
+ }, [channelMessages, t]);
100
+
101
+ // Group messages by date sections for Slack-like rendering
102
+ const messagesByDate = useMemo(() => {
103
+ const sections = [];
104
+ let currentSection = { date: null, messages: [] };
105
+
106
+ messageListWithDates.forEach((item) => {
107
+ if (item?.type === 'date') {
108
+ if (currentSection.messages.length > 0) {
109
+ sections.push(currentSection);
110
+ }
111
+ currentSection = { date: item.content, messages: [] };
112
+ } else {
113
+ currentSection.messages.push(item);
114
+ }
115
+ });
116
+
117
+ if (currentSection.messages.length > 0) {
118
+ sections.push(currentSection);
119
+ }
120
+
121
+ return sections;
122
+ }, [messageListWithDates]);
123
+
124
+ return (
125
+ <>
126
+ <div
127
+ className={`w-full pb-4 pt-2 ${
128
+ isDesktopView ? 'space-y-3 max-w-full mx-auto' : isSmallScreen ? 'space-y-2' : 'space-y-2'
129
+ }`}
130
+ ref={innerRef}
131
+ >
132
+ {messagesByDate?.map((section, sectionIndex) => (
133
+ <div key={`section-${sectionIndex}`} className="w-full">
134
+ {/* Date separator */}
135
+ {section.date && (
136
+ <div className="flex items-center justify-center my-3">
137
+ <div className="flex-grow border-t border-gray-200"></div>
138
+ <div className="mx-4 px-3 py-1 bg-white border border-gray-200 rounded-full text-xs font-medium text-gray-600">
139
+ {section.date}
140
+ </div>
141
+ <div className="flex-grow border-t border-gray-200"></div>
142
+ </div>
143
+ )}
144
+
145
+ {/* Messages grouped by user and time */}
146
+ <div className={`${isDesktopView ? 'mb-2' : 'mb-1'}`}>
147
+ <ModernMessageGroupComponent
148
+ messages={section.messages}
149
+ currentUser={currentUser}
150
+ onOpen={onOpen}
151
+ onMessageClick={onMessageClick}
152
+ isDesktopView={isDesktopView}
153
+ isSmallScreen={isSmallScreen}
154
+ />
155
+ </div>
156
+ </div>
157
+ ))}
158
+ </div>
159
+ <ChatModal element={selectedElement} isOpen={isOpen} onClose={onClose} />
160
+ </>
161
+ );
162
+ };
163
+
164
+ const ChatModal = ({ element, isOpen, onClose }) => {
165
+ if (!isOpen) return null;
166
+
167
+ return (
168
+ <>
169
+ {/* Modal Overlay */}
170
+ <div className="fixed inset-0 bg-black bg-opacity-50 z-40" onClick={onClose} />
171
+
172
+ {/* Modal Content */}
173
+ <div className="fixed z-50 left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2">
174
+ <div
175
+ className="bg-white w-[1036px] h-[700px] rounded-lg shadow-xl"
176
+ style={{
177
+ boxShadow: '0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23)',
178
+ }}
179
+ >
180
+ {/* Modal Header */}
181
+ <div className="flex justify-between border-b border-gray-300 pb-4 pt-4">
182
+ <button onClick={onClose} className="w-8 ml-3 text-black hover:text-black focus:outline-none">
183
+ <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
184
+ <path
185
+ strokeLinecap="round"
186
+ strokeLinejoin="round"
187
+ strokeWidth={2}
188
+ d="M6 18L18 6M6 6l12 12"
189
+ />
190
+ </svg>
191
+ </button>
192
+ </div>
193
+
194
+ {/* Modal Body */}
195
+ <div className="p-4">
196
+ <UserModalContent
197
+ username={element?.author?.username}
198
+ image={element?.imageUrl ? element.imageUrl : ''}
199
+ />
200
+ </div>
201
+ </div>
202
+ </div>
203
+ </>
204
+ );
205
+ };
@@ -4,6 +4,7 @@ import { BiImage } from '@react-icons/all-files/bi/BiImage.js';
4
4
 
5
5
  export interface IUploadImageButtonProps {
6
6
  onChange: ChangeEventHandler<HTMLInputElement>;
7
+ children?: React.ReactNode;
7
8
  }
8
9
 
9
10
  export const UploadImageButton = (props: IUploadImageButtonProps) => {
@@ -34,11 +35,11 @@ export const UploadImageButton = (props: IUploadImageButtonProps) => {
34
35
  </div>
35
36
  <button
36
37
  type="button"
37
- className="flex items-center justify-center w-8 h-8 rounded-full bg-transparent hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50 transition-all duration-200 ease-in-out hover:scale-105 active:scale-95"
38
+ className="flex items-center justify-center w-8 h-8 bg-transparent hover:bg-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50 transition-all duration-200 ease-in-out"
38
39
  onClick={selectFile}
39
40
  aria-label={t('tailwind_ui_inbox.upload_image')}
40
41
  >
41
- <BiImage className="w-5 h-5 text-gray-500 group-hover:text-gray-700 transition-colors duration-200" />
42
+ {props.children || <BiImage className="w-5 h-5 text-gray-500 hover:text-gray-700" />}
42
43
  </button>
43
44
  </div>
44
45
  );
@@ -10,3 +10,6 @@ export * from './UserModalContent';
10
10
  export * from './message-widgets';
11
11
  export * from './RightSidebar';
12
12
  export * from './ServiceInboxItem';
13
+ export * from './MessageInputComponent';
14
+ export * from './InputComponent';
15
+ export * from './MessagesBuilderUi';