@messenger-box/tailwind-ui-inbox 10.0.3-alpha.69 → 10.0.3-alpha.71
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.
- package/CHANGELOG.md +8 -0
- package/lib/components/InboxMessage/LeftSidebar.d.ts +2 -0
- package/lib/components/InboxMessage/LeftSidebar.d.ts.map +1 -1
- package/lib/components/InboxMessage/LeftSidebar.js +16 -5
- package/lib/components/InboxMessage/LeftSidebar.js.map +1 -1
- package/lib/components/InboxMessage/Messages.d.ts +3 -1
- package/lib/components/InboxMessage/Messages.d.ts.map +1 -1
- package/lib/components/InboxMessage/Messages.js +56 -15
- package/lib/components/InboxMessage/Messages.js.map +1 -1
- package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.d.ts +14 -0
- package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.d.ts.map +1 -0
- package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.js +138 -0
- package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.js.map +1 -0
- package/lib/components/InboxMessage/message-widgets/index.d.ts +1 -0
- package/lib/components/InboxMessage/message-widgets/index.d.ts.map +1 -1
- package/lib/components/slot-fill/chat-message-filler.js +1 -1
- package/lib/components/slot-fill/chat-message-filler.js.map +1 -1
- package/lib/container/Inbox.d.ts.map +1 -1
- package/lib/container/Inbox.js +293 -69
- package/lib/container/Inbox.js.map +1 -1
- package/lib/index.js +1 -1
- package/package.json +2 -2
- package/src/components/InboxMessage/LeftSidebar.tsx +14 -4
- package/src/components/InboxMessage/Messages.tsx +62 -15
- package/src/components/InboxMessage/message-widgets/SlackLikeMessageGroup.tsx +240 -0
- package/src/components/InboxMessage/message-widgets/index.ts +1 -0
- package/src/container/Inbox.tsx +391 -134
package/src/container/Inbox.tsx
CHANGED
|
@@ -63,6 +63,32 @@ const useMediaQuery = (query: string) => {
|
|
|
63
63
|
return matches;
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
+
// Hook to get window dimensions
|
|
67
|
+
const useWindowDimensions = () => {
|
|
68
|
+
const [windowDimensions, setWindowDimensions] = React.useState({
|
|
69
|
+
width: typeof window !== 'undefined' ? window.innerWidth : 1024,
|
|
70
|
+
height: typeof window !== 'undefined' ? window.innerHeight : 768,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (typeof window === 'undefined') return;
|
|
75
|
+
|
|
76
|
+
const handleResize = () => {
|
|
77
|
+
setWindowDimensions({
|
|
78
|
+
width: window.innerWidth,
|
|
79
|
+
height: window.innerHeight,
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
window.addEventListener('resize', handleResize);
|
|
84
|
+
handleResize(); // Set initial dimensions
|
|
85
|
+
|
|
86
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
87
|
+
}, []);
|
|
88
|
+
|
|
89
|
+
return windowDimensions;
|
|
90
|
+
};
|
|
91
|
+
|
|
66
92
|
// Static components
|
|
67
93
|
const Spinner = React.memo(({ className = '' }: { className?: string }) => (
|
|
68
94
|
<div className={`animate-spin rounded-full border-4 border-gray-200 border-t-blue-500 ${className}`}>
|
|
@@ -76,10 +102,13 @@ const Drawer = React.memo(({ isOpen, onClose, children, title }: DrawerProps) =>
|
|
|
76
102
|
return (
|
|
77
103
|
<div className="fixed inset-0 z-50 overflow-hidden">
|
|
78
104
|
<div className="absolute inset-0 bg-black bg-opacity-50" onClick={onClose} />
|
|
79
|
-
<div className="absolute bottom-0 left-0 right-0 bg-white rounded-t-lg shadow-lg max-h-[80vh] overflow-hidden">
|
|
80
|
-
<div className="flex items-center justify-between p-4 border-b border-gray-200">
|
|
81
|
-
<h2 className="text-lg font-semibold">{title}</h2>
|
|
82
|
-
<button
|
|
105
|
+
<div className="absolute bottom-0 left-0 right-0 bg-white rounded-t-lg shadow-lg max-h-[80vh] flex flex-col overflow-hidden">
|
|
106
|
+
<div className="flex items-center justify-between p-4 border-b border-gray-200 flex-shrink-0">
|
|
107
|
+
<h2 className="text-lg font-semibold truncate">{title}</h2>
|
|
108
|
+
<button
|
|
109
|
+
onClick={onClose}
|
|
110
|
+
className="p-1 hover:bg-gray-100 rounded-full transition-colors flex-shrink-0 ml-2"
|
|
111
|
+
>
|
|
83
112
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
84
113
|
<path
|
|
85
114
|
strokeLinecap="round"
|
|
@@ -90,15 +119,17 @@ const Drawer = React.memo(({ isOpen, onClose, children, title }: DrawerProps) =>
|
|
|
90
119
|
</svg>
|
|
91
120
|
</button>
|
|
92
121
|
</div>
|
|
93
|
-
<div className="p-4 overflow-y-auto"
|
|
122
|
+
<div className="flex-1 p-4 overflow-y-auto" style={{ minHeight: 0 }}>
|
|
123
|
+
{children}
|
|
124
|
+
</div>
|
|
94
125
|
</div>
|
|
95
126
|
</div>
|
|
96
127
|
);
|
|
97
128
|
});
|
|
98
129
|
|
|
99
130
|
const EmptyState = React.memo(() => (
|
|
100
|
-
<div className="h-full flex items-center justify-center bg-gray-100 p-4 sm:p-6">
|
|
101
|
-
<div className="text-center max-w-sm">
|
|
131
|
+
<div className="h-full flex items-center justify-center bg-gray-100 p-4 sm:p-6 overflow-hidden">
|
|
132
|
+
<div className="text-center max-w-sm mx-auto">
|
|
102
133
|
<div className="text-3xl sm:text-4xl text-gray-400 mb-4">💬</div>
|
|
103
134
|
<h3 className="text-lg sm:text-xl font-semibold text-gray-600 mb-2">Welcome to Messenger</h3>
|
|
104
135
|
<p className="text-sm sm:text-base text-gray-500 leading-relaxed">
|
|
@@ -133,8 +164,14 @@ const Inbox = (props: InboxProps) => {
|
|
|
133
164
|
mobilePreviewCTAText: false,
|
|
134
165
|
});
|
|
135
166
|
|
|
136
|
-
// Hooks
|
|
137
|
-
const
|
|
167
|
+
// Hooks - improved responsive breakpoints with better granularity
|
|
168
|
+
const { width: windowWidth, height: windowHeight } = useWindowDimensions();
|
|
169
|
+
const isMobileView = useMediaQuery('(max-width: 640px)');
|
|
170
|
+
const isSmallTabletView = useMediaQuery('(min-width: 641px) and (max-width: 900px)');
|
|
171
|
+
const isTabletView = useMediaQuery('(min-width: 901px) and (max-width: 1024px)');
|
|
172
|
+
const isDesktopView = useMediaQuery('(min-width: 1025px)');
|
|
173
|
+
const isLargeDesktopView = useMediaQuery('(min-width: 1440px)');
|
|
174
|
+
const isSmallScreen = useMediaQuery('(max-width: 900px)');
|
|
138
175
|
const auth = useSelector(userSelector);
|
|
139
176
|
|
|
140
177
|
// Data destructuring from Apollo queries
|
|
@@ -198,6 +235,13 @@ const Inbox = (props: InboxProps) => {
|
|
|
198
235
|
return title || 'Direct Message';
|
|
199
236
|
}
|
|
200
237
|
|
|
238
|
+
if (members.length === 1 && members[0].user.id === currentUser?.id) {
|
|
239
|
+
if (members[0].user?.givenName && members[0].user?.familyName) {
|
|
240
|
+
return `${members[0].user?.givenName} ${members[0].user?.familyName}`;
|
|
241
|
+
}
|
|
242
|
+
return members[0].user?.givenName || members[0].user?.familyName || 'Direct Message';
|
|
243
|
+
}
|
|
244
|
+
|
|
201
245
|
return title || 'Channel';
|
|
202
246
|
}, [channels, pathChannelId, currentUser]);
|
|
203
247
|
|
|
@@ -268,6 +312,11 @@ const Inbox = (props: InboxProps) => {
|
|
|
268
312
|
const detailSidebarOptions = useMemo(
|
|
269
313
|
() => ({
|
|
270
314
|
isMobileView,
|
|
315
|
+
isSmallTabletView,
|
|
316
|
+
isTabletView,
|
|
317
|
+
isDesktopView,
|
|
318
|
+
isLargeDesktopView,
|
|
319
|
+
isSmallScreen,
|
|
271
320
|
setMobilePreviewCTAText: (v: string | ReactNode) =>
|
|
272
321
|
localDispatch({ payload: { mobilePreviewCTAText: v }, type: 'update' }),
|
|
273
322
|
setMobilePreviewText: (v: string | ReactNode) =>
|
|
@@ -275,17 +324,39 @@ const Inbox = (props: InboxProps) => {
|
|
|
275
324
|
setMobilePreviewVisibility: (v: boolean) =>
|
|
276
325
|
localDispatch({ payload: { mobilePreviewVisibility: v }, type: 'update' }),
|
|
277
326
|
}),
|
|
278
|
-
[isMobileView],
|
|
327
|
+
[isMobileView, isSmallTabletView, isTabletView, isDesktopView, isLargeDesktopView, isSmallScreen],
|
|
279
328
|
);
|
|
280
329
|
|
|
281
330
|
return (
|
|
282
|
-
<div
|
|
331
|
+
<div
|
|
332
|
+
className="border-t border-gray-300 flex overflow-hidden"
|
|
333
|
+
style={{
|
|
334
|
+
height: `${windowHeight}px`,
|
|
335
|
+
maxHeight: '100vh',
|
|
336
|
+
}}
|
|
337
|
+
>
|
|
283
338
|
{/* Left Sidebar - Responsive Design */}
|
|
284
339
|
<div
|
|
285
340
|
className={`
|
|
286
341
|
flex-shrink-0 bg-gray-50 border-r border-gray-300 overflow-hidden transition-all duration-300 ease-in-out
|
|
287
|
-
${isMobileView && pathChannelId ? 'hidden' : '
|
|
342
|
+
${isMobileView && pathChannelId ? 'hidden' : ''}
|
|
288
343
|
`}
|
|
344
|
+
style={{
|
|
345
|
+
width:
|
|
346
|
+
isMobileView && !pathChannelId
|
|
347
|
+
? '100%'
|
|
348
|
+
: isMobileView && pathChannelId
|
|
349
|
+
? '0px'
|
|
350
|
+
: isSmallTabletView
|
|
351
|
+
? `${Math.min(288, windowWidth * 0.35)}px` // w-72 or 35% of window
|
|
352
|
+
: isTabletView
|
|
353
|
+
? `${Math.min(320, windowWidth * 0.3)}px` // w-80 or 30% of window
|
|
354
|
+
: isLargeDesktopView
|
|
355
|
+
? `${Math.min(384, windowWidth * 0.25)}px` // w-96 or 25% of window
|
|
356
|
+
: `${Math.min(320, windowWidth * 0.28)}px`, // w-80 or 28% of window
|
|
357
|
+
height: `${windowHeight}px`,
|
|
358
|
+
maxHeight: '100vh',
|
|
359
|
+
}}
|
|
289
360
|
>
|
|
290
361
|
<LeftSidebar
|
|
291
362
|
currentUser={currentUser}
|
|
@@ -298,15 +369,23 @@ const Inbox = (props: InboxProps) => {
|
|
|
298
369
|
getChannelsRefetch={getChannelsRefetch}
|
|
299
370
|
role={channelRole}
|
|
300
371
|
messagesQuery={data?.[1]}
|
|
372
|
+
windowHeight={windowHeight}
|
|
373
|
+
windowWidth={windowWidth}
|
|
301
374
|
/>
|
|
302
375
|
</div>
|
|
303
376
|
|
|
304
377
|
{/* Main Content Area - Responsive */}
|
|
305
378
|
<div
|
|
306
379
|
className={`
|
|
307
|
-
flex-1 min-w-0 overflow-hidden transition-all duration-300 ease-in-out
|
|
308
|
-
${isMobileView && !pathChannelId ? 'hidden' : '
|
|
380
|
+
flex-1 min-w-0 flex flex-col overflow-hidden transition-all duration-300 ease-in-out
|
|
381
|
+
${isMobileView && !pathChannelId ? 'hidden' : 'flex'}
|
|
309
382
|
`}
|
|
383
|
+
style={{
|
|
384
|
+
minWidth: isSmallScreen ? '300px' : isDesktopView ? '500px' : '400px',
|
|
385
|
+
width: 'auto',
|
|
386
|
+
height: `${windowHeight}px`,
|
|
387
|
+
maxHeight: '100vh',
|
|
388
|
+
}}
|
|
310
389
|
>
|
|
311
390
|
{pathChannelId ? (
|
|
312
391
|
<ContentComponent
|
|
@@ -315,6 +394,13 @@ const Inbox = (props: InboxProps) => {
|
|
|
315
394
|
channelRole={channelRole}
|
|
316
395
|
pathPrefix={props.pathPrefix}
|
|
317
396
|
isMobileView={isMobileView}
|
|
397
|
+
isSmallTabletView={isSmallTabletView}
|
|
398
|
+
isTabletView={isTabletView}
|
|
399
|
+
isDesktopView={isDesktopView}
|
|
400
|
+
isLargeDesktopView={isLargeDesktopView}
|
|
401
|
+
isSmallScreen={isSmallScreen}
|
|
402
|
+
windowWidth={windowWidth}
|
|
403
|
+
windowHeight={windowHeight}
|
|
318
404
|
mobilePreviewState={mobilePreviewState}
|
|
319
405
|
detailSidebarOptions={detailSidebarOptions}
|
|
320
406
|
isBottomDrawerOpen={isBottomDrawerOpen}
|
|
@@ -328,14 +414,23 @@ const Inbox = (props: InboxProps) => {
|
|
|
328
414
|
</div>
|
|
329
415
|
|
|
330
416
|
{/* Right Sidebar - Desktop Only */}
|
|
331
|
-
{pathChannelId && data?.[1] &&
|
|
332
|
-
<div
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
417
|
+
{pathChannelId && data?.[1] && isDesktopView && (
|
|
418
|
+
// <div
|
|
419
|
+
// className="border-l border-gray-200 bg-white flex-shrink-0 overflow-hidden"
|
|
420
|
+
// style={{
|
|
421
|
+
// width: isLargeDesktopView
|
|
422
|
+
// ? `${Math.min(384, windowWidth * 0.25)}px` // w-96 or 25% of window
|
|
423
|
+
// : `${Math.min(320, windowWidth * 0.22)}px`, // w-80 or 22% of window
|
|
424
|
+
// height: `${windowHeight}px`,
|
|
425
|
+
// maxHeight: '100vh'
|
|
426
|
+
// }}
|
|
427
|
+
// >
|
|
428
|
+
<RightSidebarWrapper
|
|
429
|
+
MessagesLoaderQuery={data?.[1]}
|
|
430
|
+
selectedPost={null}
|
|
431
|
+
detailSidebarOptions={detailSidebarOptions}
|
|
432
|
+
/>
|
|
433
|
+
// </div>
|
|
339
434
|
)}
|
|
340
435
|
</div>
|
|
341
436
|
);
|
|
@@ -348,6 +443,13 @@ const ContentComponent = React.memo((props: any) => {
|
|
|
348
443
|
pathPrefix,
|
|
349
444
|
postId,
|
|
350
445
|
isMobileView,
|
|
446
|
+
isSmallTabletView,
|
|
447
|
+
isTabletView,
|
|
448
|
+
isDesktopView,
|
|
449
|
+
isLargeDesktopView,
|
|
450
|
+
isSmallScreen,
|
|
451
|
+
windowWidth,
|
|
452
|
+
windowHeight,
|
|
351
453
|
mobilePreviewState,
|
|
352
454
|
detailSidebarOptions,
|
|
353
455
|
isBottomDrawerOpen,
|
|
@@ -371,18 +473,22 @@ const ContentComponent = React.memo((props: any) => {
|
|
|
371
473
|
}, [channelData]);
|
|
372
474
|
|
|
373
475
|
return (
|
|
374
|
-
<div className="flex
|
|
476
|
+
<div className="flex overflow-hidden" style={{ height: `${windowHeight}px`, maxHeight: '100vh' }}>
|
|
375
477
|
{/* Main Chat Content */}
|
|
376
|
-
<div className="flex-1 flex flex-col">
|
|
478
|
+
<div className="flex-1 flex flex-col min-w-0 overflow-hidden">
|
|
377
479
|
{/* Channel Header */}
|
|
378
480
|
{channelId && (
|
|
379
|
-
<div
|
|
481
|
+
<div
|
|
482
|
+
className={`border-b border-gray-200 bg-white flex-shrink-0 z-10 ${
|
|
483
|
+
isSmallScreen ? 'px-3 py-3' : 'px-4 sm:px-6 py-4'
|
|
484
|
+
}`}
|
|
485
|
+
>
|
|
380
486
|
<div className="flex items-center justify-between">
|
|
381
|
-
<div className="flex items-center space-x-
|
|
382
|
-
{/* Mobile Back Button */}
|
|
383
|
-
{isMobileView && (
|
|
487
|
+
<div className="flex items-center space-x-2 min-w-0 flex-1">
|
|
488
|
+
{/* Mobile/Small Screen Back Button */}
|
|
489
|
+
{(isMobileView || isSmallTabletView) && (
|
|
384
490
|
<button
|
|
385
|
-
className="p-2 hover:bg-gray-100 rounded-full transition-colors
|
|
491
|
+
className="p-2 hover:bg-gray-100 rounded-full transition-colors flex-shrink-0"
|
|
386
492
|
onClick={() => window.history.back()}
|
|
387
493
|
aria-label="Go back"
|
|
388
494
|
>
|
|
@@ -401,11 +507,17 @@ const ContentComponent = React.memo((props: any) => {
|
|
|
401
507
|
</svg>
|
|
402
508
|
</button>
|
|
403
509
|
)}
|
|
404
|
-
<h2
|
|
510
|
+
<h2
|
|
511
|
+
className={`font-semibold text-gray-800 truncate ${
|
|
512
|
+
isSmallScreen ? 'text-base' : 'text-lg'
|
|
513
|
+
}`}
|
|
514
|
+
>
|
|
515
|
+
{channelName}
|
|
516
|
+
</h2>
|
|
405
517
|
</div>
|
|
406
|
-
{isMobileView && mobilePreviewState?.mobilePreviewVisibility && (
|
|
518
|
+
{(isMobileView || isSmallTabletView) && mobilePreviewState?.mobilePreviewVisibility && (
|
|
407
519
|
<button
|
|
408
|
-
className="text-sm px-3 py-1 bg-teal-500 hover:bg-teal-600 text-white rounded-md transition-colors"
|
|
520
|
+
className="text-sm px-3 py-1 bg-teal-500 hover:bg-teal-600 text-white rounded-md transition-colors flex-shrink-0"
|
|
409
521
|
onClick={() => setBottomDrawer(true)}
|
|
410
522
|
>
|
|
411
523
|
{mobilePreviewState?.mobilePreviewCTAText}
|
|
@@ -416,13 +528,13 @@ const ContentComponent = React.memo((props: any) => {
|
|
|
416
528
|
)}
|
|
417
529
|
|
|
418
530
|
{/* Mobile Preview */}
|
|
419
|
-
{isMobileView && channelId && mobilePreviewState?.mobilePreviewVisibility && (
|
|
420
|
-
<div className=
|
|
531
|
+
{(isMobileView || isSmallTabletView) && channelId && mobilePreviewState?.mobilePreviewVisibility && (
|
|
532
|
+
<div className={`mt-4 ${isSmallScreen ? 'mx-3' : 'mx-4'}`}>
|
|
421
533
|
<div className="mb-2">
|
|
422
|
-
<div className="w-full flex justify-between mb-[5px]">
|
|
423
|
-
<span>{mobilePreviewState?.mobilePreviewText}</span>
|
|
534
|
+
<div className="w-full flex justify-between items-center gap-2 mb-[5px]">
|
|
535
|
+
<span className="truncate flex-1 text-sm">{mobilePreviewState?.mobilePreviewText}</span>
|
|
424
536
|
<button
|
|
425
|
-
className="text-
|
|
537
|
+
className="text-sm px-3 py-2 bg-teal-500 hover:bg-teal-600 text-white rounded-md transition-colors flex-shrink-0"
|
|
426
538
|
onClick={() => setBottomDrawer(true)}
|
|
427
539
|
>
|
|
428
540
|
{mobilePreviewState?.mobilePreviewCTAText}
|
|
@@ -434,45 +546,51 @@ const ContentComponent = React.memo((props: any) => {
|
|
|
434
546
|
)}
|
|
435
547
|
|
|
436
548
|
{/* Content based on postId */}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
postId
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
549
|
+
<div className="flex-1 flex flex-col min-h-0 overflow-hidden">
|
|
550
|
+
{channelId && (
|
|
551
|
+
<>
|
|
552
|
+
{postId ? (
|
|
553
|
+
postId === '1' ? (
|
|
554
|
+
<ThreadsInbox
|
|
555
|
+
channelId={channelId}
|
|
556
|
+
role={channelRole}
|
|
557
|
+
pathPrefix={pathPrefix}
|
|
558
|
+
setChannelId={() => {}}
|
|
559
|
+
setPostId={() => {}}
|
|
560
|
+
setGoBack={() => {}}
|
|
561
|
+
/>
|
|
562
|
+
) : (
|
|
563
|
+
<ThreadMessagesInbox
|
|
564
|
+
channelId={channelId}
|
|
565
|
+
postId={postId}
|
|
566
|
+
role={channelRole}
|
|
567
|
+
goBack={false}
|
|
568
|
+
pathPrefix={pathPrefix}
|
|
569
|
+
setPostId={() => {}}
|
|
570
|
+
setChannelId={() => {}}
|
|
571
|
+
onMessageClick={onMessageClick}
|
|
572
|
+
/>
|
|
573
|
+
)
|
|
449
574
|
) : (
|
|
450
|
-
<
|
|
575
|
+
<MessagesComponent
|
|
451
576
|
channelId={channelId}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
pathPrefix={pathPrefix}
|
|
456
|
-
setPostId={() => {}}
|
|
457
|
-
setChannelId={() => {}}
|
|
577
|
+
MessagesLoaderQuery={MessagesLoaderQuery}
|
|
578
|
+
channelsDetail={channelsDetail}
|
|
579
|
+
channelLoading={channelLoading}
|
|
458
580
|
onMessageClick={onMessageClick}
|
|
581
|
+
isSmallScreen={isSmallScreen}
|
|
582
|
+
isDesktopView={isDesktopView}
|
|
583
|
+
windowHeight={windowHeight}
|
|
584
|
+
windowWidth={windowWidth}
|
|
459
585
|
/>
|
|
460
|
-
)
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
MessagesLoaderQuery={MessagesLoaderQuery}
|
|
465
|
-
channelsDetail={channelsDetail}
|
|
466
|
-
channelLoading={channelLoading}
|
|
467
|
-
onMessageClick={onMessageClick}
|
|
468
|
-
/>
|
|
469
|
-
)}
|
|
470
|
-
</>
|
|
471
|
-
)}
|
|
586
|
+
)}
|
|
587
|
+
</>
|
|
588
|
+
)}
|
|
589
|
+
</div>
|
|
472
590
|
</div>
|
|
473
591
|
|
|
474
|
-
{/* Mobile Drawer */}
|
|
475
|
-
{isMobileView && (
|
|
592
|
+
{/* Mobile/Small Screen Drawer */}
|
|
593
|
+
{(isMobileView || isSmallTabletView) && (
|
|
476
594
|
<Drawer
|
|
477
595
|
isOpen={isBottomDrawerOpen}
|
|
478
596
|
onClose={() => setBottomDrawer(false)}
|
|
@@ -500,7 +618,7 @@ const RightSidebarWrapper = React.memo(({ MessagesLoaderQuery, selectedPost, det
|
|
|
500
618
|
if (!sortedMessages.length) return null;
|
|
501
619
|
|
|
502
620
|
return (
|
|
503
|
-
<div className="
|
|
621
|
+
<div className="h-full flex flex-col overflow-hidden">
|
|
504
622
|
<RightSidebar
|
|
505
623
|
channelMessages={sortedMessages}
|
|
506
624
|
visibility="visible"
|
|
@@ -512,11 +630,24 @@ const RightSidebarWrapper = React.memo(({ MessagesLoaderQuery, selectedPost, det
|
|
|
512
630
|
});
|
|
513
631
|
|
|
514
632
|
const MessagesComponent = React.memo((props: any) => {
|
|
515
|
-
const {
|
|
633
|
+
const {
|
|
634
|
+
channelId,
|
|
635
|
+
MessagesLoaderQuery,
|
|
636
|
+
channelsDetail,
|
|
637
|
+
channelLoading,
|
|
638
|
+
onMessageClick,
|
|
639
|
+
isSmallScreen,
|
|
640
|
+
isDesktopView,
|
|
641
|
+
windowHeight = 768,
|
|
642
|
+
windowWidth = 1024,
|
|
643
|
+
} = props;
|
|
516
644
|
|
|
517
645
|
const messageRootListRef = useRef(null);
|
|
518
646
|
const messageListRef = useRef(null);
|
|
519
647
|
const apolloClient = useApolloClient();
|
|
648
|
+
const [isLoadingOlder, setIsLoadingOlder] = React.useState(false);
|
|
649
|
+
const isLoadingOlderRef = useRef(false);
|
|
650
|
+
const scrollTimeoutRef = useRef(null);
|
|
520
651
|
|
|
521
652
|
const auth = useSelector(userSelector);
|
|
522
653
|
const { startUpload } = useUploadFiles();
|
|
@@ -533,21 +664,28 @@ const MessagesComponent = React.memo((props: any) => {
|
|
|
533
664
|
const totalCount = data?.messages?.totalCount || 0;
|
|
534
665
|
|
|
535
666
|
const scrollToBottom = useCallback(() => {
|
|
536
|
-
if (
|
|
667
|
+
if (messageRootListRef?.current) {
|
|
537
668
|
messageRootListRef.current.scrollTop = messageRootListRef.current.scrollHeight;
|
|
538
669
|
}
|
|
539
670
|
}, []);
|
|
540
671
|
|
|
541
|
-
// Auto-scroll on new messages
|
|
672
|
+
// Auto-scroll on new messages (but not when loading older messages)
|
|
542
673
|
useEffect(() => {
|
|
543
|
-
|
|
544
|
-
|
|
674
|
+
if (!isLoadingOlderRef.current) {
|
|
675
|
+
const timer = setTimeout(() => scrollToBottom(), 100);
|
|
676
|
+
return () => clearTimeout(timer);
|
|
677
|
+
}
|
|
545
678
|
}, [messages.length, scrollToBottom]);
|
|
546
679
|
|
|
547
680
|
const onFetchOld = useCallback(
|
|
548
681
|
async (skip: number) => {
|
|
549
|
-
if (channelId && fetchMoreMessages) {
|
|
682
|
+
if (channelId && fetchMoreMessages && !isLoadingOlder) {
|
|
550
683
|
try {
|
|
684
|
+
setIsLoadingOlder(true);
|
|
685
|
+
isLoadingOlderRef.current = true;
|
|
686
|
+
// Capture current scroll height before fetching
|
|
687
|
+
const oldScrollHeight = messageRootListRef?.current?.scrollHeight || 0;
|
|
688
|
+
|
|
551
689
|
await fetchMoreMessages({
|
|
552
690
|
variables: {
|
|
553
691
|
channelId: channelId.toString(),
|
|
@@ -570,28 +708,119 @@ const MessagesComponent = React.memo((props: any) => {
|
|
|
570
708
|
},
|
|
571
709
|
});
|
|
572
710
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
711
|
+
// Maintain scroll position after loading older messages
|
|
712
|
+
setTimeout(() => {
|
|
713
|
+
if (messageRootListRef?.current) {
|
|
714
|
+
const newScrollHeight = messageRootListRef.current.scrollHeight;
|
|
715
|
+
const scrollDiff = newScrollHeight - oldScrollHeight;
|
|
716
|
+
// For normal flex layout, maintain position by adjusting scroll offset
|
|
717
|
+
messageRootListRef.current.scrollTop = scrollDiff;
|
|
718
|
+
}
|
|
719
|
+
// Reset the loading flag after position is maintained
|
|
720
|
+
setTimeout(() => {
|
|
721
|
+
isLoadingOlderRef.current = false;
|
|
722
|
+
}, 50);
|
|
723
|
+
}, 100);
|
|
576
724
|
} catch (error) {
|
|
577
725
|
console.error('Error fetching older messages:', error);
|
|
726
|
+
isLoadingOlderRef.current = false;
|
|
727
|
+
} finally {
|
|
728
|
+
setIsLoadingOlder(false);
|
|
578
729
|
}
|
|
579
730
|
}
|
|
580
731
|
},
|
|
581
|
-
[channelId, fetchMoreMessages],
|
|
732
|
+
[channelId, fetchMoreMessages, isLoadingOlder],
|
|
582
733
|
);
|
|
583
734
|
|
|
735
|
+
// Scroll to bottom when channel changes
|
|
736
|
+
useEffect(() => {
|
|
737
|
+
if (channelId && messages.length > 0) {
|
|
738
|
+
isLoadingOlderRef.current = false; // Reset flag on channel change
|
|
739
|
+
const timer = setTimeout(() => scrollToBottom(), 200);
|
|
740
|
+
return () => clearTimeout(timer);
|
|
741
|
+
}
|
|
742
|
+
}, [channelId, scrollToBottom]);
|
|
743
|
+
|
|
744
|
+
// Alternative scroll detection for Firefox
|
|
745
|
+
useEffect(() => {
|
|
746
|
+
const element = messageRootListRef.current;
|
|
747
|
+
if (!element) return;
|
|
748
|
+
|
|
749
|
+
// Firefox-specific scroll detection using passive listeners
|
|
750
|
+
const handleScrollEnd = () => {
|
|
751
|
+
if (!isLoadingOlder && element) {
|
|
752
|
+
const { scrollTop } = element;
|
|
753
|
+
const isAtTop = Math.round(scrollTop) <= 30;
|
|
754
|
+
const hasMoreMessages = totalCount > messages.length;
|
|
755
|
+
|
|
756
|
+
if (isAtTop && hasMoreMessages) {
|
|
757
|
+
console.log('ScrollEnd triggered load more (Firefox):', {
|
|
758
|
+
scrollTop: Math.round(scrollTop),
|
|
759
|
+
totalCount,
|
|
760
|
+
messagesLength: messages.length,
|
|
761
|
+
});
|
|
762
|
+
onFetchOld(messages.length);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
// Use scrollend event if available (modern Firefox/Chrome)
|
|
768
|
+
if ('onscrollend' in element) {
|
|
769
|
+
element.addEventListener('scrollend', handleScrollEnd, { passive: true });
|
|
770
|
+
return () => {
|
|
771
|
+
element.removeEventListener('scrollend', handleScrollEnd);
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
}, [totalCount, messages.length, onFetchOld, isLoadingOlder]);
|
|
775
|
+
|
|
776
|
+
// Cleanup scroll timeout on unmount
|
|
777
|
+
useEffect(() => {
|
|
778
|
+
return () => {
|
|
779
|
+
if (scrollTimeoutRef.current) {
|
|
780
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
}, []);
|
|
784
|
+
|
|
584
785
|
const onMessagesScroll = useCallback(
|
|
585
786
|
async (e: any) => {
|
|
586
|
-
|
|
587
|
-
|
|
787
|
+
// Throttle scroll events for better performance, especially in Firefox
|
|
788
|
+
if (scrollTimeoutRef.current) {
|
|
789
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
790
|
+
}
|
|
588
791
|
|
|
589
|
-
|
|
590
|
-
|
|
792
|
+
scrollTimeoutRef.current = setTimeout(async () => {
|
|
793
|
+
if (messageRootListRef.current && !isLoadingOlder) {
|
|
794
|
+
const element = messageRootListRef.current;
|
|
795
|
+
const { clientHeight, scrollHeight, scrollTop } = element;
|
|
796
|
+
|
|
797
|
+
// Firefox-compatible scroll detection
|
|
798
|
+
// Use Math.ceil to handle Firefox's fractional scrollTop values
|
|
799
|
+
const isAtTop = Math.ceil(scrollTop) <= 25;
|
|
800
|
+
const hasMoreMessages = totalCount > messages.length;
|
|
801
|
+
|
|
802
|
+
// Additional Firefox-specific check
|
|
803
|
+
const isFirefox = navigator.userAgent.includes('Firefox');
|
|
804
|
+
const firefoxAdjustedTop = isFirefox ? Math.round(scrollTop) <= 30 : isAtTop;
|
|
805
|
+
|
|
806
|
+
if ((isAtTop || firefoxAdjustedTop) && hasMoreMessages) {
|
|
807
|
+
console.log('Triggering load more:', {
|
|
808
|
+
scrollTop: Math.ceil(scrollTop),
|
|
809
|
+
originalScrollTop: scrollTop,
|
|
810
|
+
totalCount,
|
|
811
|
+
messagesLength: messages.length,
|
|
812
|
+
scrollHeight,
|
|
813
|
+
clientHeight,
|
|
814
|
+
browser: isFirefox ? 'Firefox' : 'Other',
|
|
815
|
+
isAtTop,
|
|
816
|
+
firefoxAdjustedTop,
|
|
817
|
+
});
|
|
818
|
+
await onFetchOld(messages.length);
|
|
819
|
+
}
|
|
591
820
|
}
|
|
592
|
-
}
|
|
821
|
+
}, 100);
|
|
593
822
|
},
|
|
594
|
-
[totalCount, messages.length, onFetchOld],
|
|
823
|
+
[totalCount, messages.length, onFetchOld, isLoadingOlder],
|
|
595
824
|
);
|
|
596
825
|
|
|
597
826
|
// Optimistic message sending with Apollo cache updates
|
|
@@ -768,57 +997,85 @@ const MessagesComponent = React.memo((props: any) => {
|
|
|
768
997
|
<>
|
|
769
998
|
<div
|
|
770
999
|
ref={messageRootListRef}
|
|
771
|
-
className=
|
|
1000
|
+
className={`overflow-y-scroll bg-gray-50 ${
|
|
1001
|
+
isSmallScreen ? 'p-2 px-3' : isDesktopView ? 'p-6 px-8' : 'p-4 px-6'
|
|
1002
|
+
}`}
|
|
772
1003
|
onScroll={onMessagesScroll}
|
|
1004
|
+
style={{
|
|
1005
|
+
height: `${windowHeight - 140}px`, // Subtract header + input height
|
|
1006
|
+
maxHeight: '100vh',
|
|
1007
|
+
scrollbarWidth: 'thin',
|
|
1008
|
+
scrollbarColor: '#cbd5e0 #f7fafc',
|
|
1009
|
+
overflowY: 'scroll',
|
|
1010
|
+
WebkitOverflowScrolling: 'touch',
|
|
1011
|
+
}}
|
|
773
1012
|
>
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
1013
|
+
<div className="min-h-full">
|
|
1014
|
+
{messages.length > 0 ? (
|
|
1015
|
+
<>
|
|
1016
|
+
{/* Loading indicator for older messages at the top */}
|
|
1017
|
+
{isLoadingOlder && (
|
|
1018
|
+
<div className="flex justify-center py-4">
|
|
1019
|
+
<div className="flex items-center space-x-2 text-gray-500">
|
|
1020
|
+
<Spinner className="w-4 h-4" />
|
|
1021
|
+
<span className="text-sm">Loading older messages...</span>
|
|
1022
|
+
</div>
|
|
1023
|
+
</div>
|
|
1024
|
+
)}
|
|
1025
|
+
<Messages
|
|
1026
|
+
innerRef={messageListRef}
|
|
1027
|
+
channelId={channelId}
|
|
1028
|
+
currentUser={auth}
|
|
1029
|
+
channelMessages={messages}
|
|
1030
|
+
totalCount={totalCount}
|
|
1031
|
+
onMessageClick={onMessageClick}
|
|
1032
|
+
isDesktopView={isDesktopView || false}
|
|
1033
|
+
isSmallScreen={isSmallScreen || false}
|
|
1034
|
+
/>
|
|
1035
|
+
<SubscriptionHandler
|
|
1036
|
+
subscribeToMore={subscribeToMore}
|
|
1037
|
+
document={CHAT_MESSAGE_ADDED}
|
|
1038
|
+
variables={{ channelId: channelId.toString() }}
|
|
1039
|
+
enabled={!!channelId && !!subscribeToMore}
|
|
1040
|
+
updateQuery={(prev: any, { subscriptionData }: any) => {
|
|
1041
|
+
console.log('Subscription updateQuery called:', { prev, subscriptionData });
|
|
1042
|
+
if (!subscriptionData.data) {
|
|
1043
|
+
console.log('No subscription data, returning prev');
|
|
1044
|
+
return prev;
|
|
1045
|
+
}
|
|
1046
|
+
const newMessage = subscriptionData.data.chatMessageAdded;
|
|
1047
|
+
console.log('New message received via subscription:', newMessage);
|
|
1048
|
+
|
|
1049
|
+
return {
|
|
1050
|
+
...prev,
|
|
1051
|
+
messages: {
|
|
1052
|
+
...prev?.messages,
|
|
1053
|
+
data: uniqBy([...(prev?.messages?.data || []), newMessage], 'id'),
|
|
1054
|
+
totalCount: (prev?.messages?.totalCount || 0) + 1,
|
|
1055
|
+
},
|
|
1056
|
+
};
|
|
1057
|
+
}}
|
|
1058
|
+
onError={(error) => {
|
|
1059
|
+
console.error('Subscription error:', error);
|
|
1060
|
+
}}
|
|
1061
|
+
/>
|
|
1062
|
+
</>
|
|
1063
|
+
) : (
|
|
1064
|
+
<div className="flex items-center justify-center text-gray-500 min-h-96">
|
|
1065
|
+
<div className="text-center max-w-sm mx-auto px-4">
|
|
1066
|
+
<div className="text-6xl mb-4 opacity-50">💬</div>
|
|
1067
|
+
<h3 className="text-lg font-semibold text-gray-600 mb-2">No messages yet</h3>
|
|
1068
|
+
<p className="text-sm text-gray-500">
|
|
1069
|
+
Start the conversation by sending a message below!
|
|
1070
|
+
</p>
|
|
1071
|
+
</div>
|
|
817
1072
|
</div>
|
|
818
|
-
|
|
819
|
-
|
|
1073
|
+
)}
|
|
1074
|
+
</div>
|
|
1075
|
+
</div>
|
|
1076
|
+
<div className="flex-shrink-0 border-t border-gray-200 bg-white">
|
|
1077
|
+
<MessageInput channelId={channelId} handleSend={handleSend} placeholder="Message" />
|
|
820
1078
|
</div>
|
|
821
|
-
<MessageInput channelId={channelId} handleSend={handleSend} placeholder="Message" />
|
|
822
1079
|
</>
|
|
823
1080
|
);
|
|
824
1081
|
});
|