@pubuduth-aplicy/chat-ui 2.1.39 → 2.1.40
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/package.json +1 -1
- package/src/components/Loader.tsx +17 -0
- package/src/components/messages/Messages.tsx +84 -18
- package/src/style/style.css +107 -1
package/package.json
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const Loader = () => {
|
|
2
|
+
return (
|
|
3
|
+
|
|
4
|
+
<div className="dot-spinner">
|
|
5
|
+
<div className="dot-spinner__dot" />
|
|
6
|
+
<div className="dot-spinner__dot" />
|
|
7
|
+
<div className="dot-spinner__dot" />
|
|
8
|
+
<div className="dot-spinner__dot" />
|
|
9
|
+
<div className="dot-spinner__dot" />
|
|
10
|
+
<div className="dot-spinner__dot" />
|
|
11
|
+
<div className="dot-spinner__dot" />
|
|
12
|
+
<div className="dot-spinner__dot" />
|
|
13
|
+
</div>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default Loader
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import { useCallback, useEffect, useRef } from "react";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
3
|
import Message from "./Message";
|
|
4
4
|
import { useChatContext } from "../../providers/ChatProvider";
|
|
5
5
|
import { useMessages } from "../../hooks/queries/useChatApi";
|
|
@@ -7,27 +7,77 @@ import useChatUIStore from "../../stores/Zustant";
|
|
|
7
7
|
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
8
8
|
import { fetchMessages } from "../../service/messageService";
|
|
9
9
|
import { useInView } from "react-intersection-observer";
|
|
10
|
+
import Loader from "../loader";
|
|
10
11
|
|
|
11
12
|
const Messages = () => {
|
|
12
13
|
const { selectedConversation, setMessages, messages } = useChatUIStore();
|
|
13
14
|
const { userId, socket } = useChatContext();
|
|
14
15
|
const { ref, inView } = useInView();
|
|
15
16
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
17
|
+
const [isInitialLoad, setIsInitialLoad] = useState(true);
|
|
18
|
+
const [isAutoScrolling, setIsAutoScrolling] = useState(false);
|
|
19
|
+
|
|
16
20
|
|
|
17
21
|
// const { data, isLoading, isError, error } = useMessages(selectedConversation?._id, userId);
|
|
18
22
|
|
|
19
23
|
const lastMessageRef = useRef<HTMLDivElement>(null);
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
// const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
|
|
26
|
+
// useInfiniteQuery({
|
|
27
|
+
// queryKey: ["messages", selectedConversation?._id, userId],
|
|
28
|
+
// queryFn: ({ pageParam = 1 }) =>
|
|
29
|
+
// fetchMessages(selectedConversation?._id, userId, pageParam),
|
|
30
|
+
// initialPageParam: 1,
|
|
31
|
+
// getNextPageParam: (lastPage, allPages) => {
|
|
32
|
+
// return lastPage.messages.length ? allPages.length + 1: undefined;
|
|
33
|
+
// },
|
|
34
|
+
// });
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
data,
|
|
39
|
+
fetchNextPage,
|
|
40
|
+
hasNextPage,
|
|
41
|
+
isFetchingNextPage,
|
|
42
|
+
isLoading,
|
|
43
|
+
} = useInfiniteQuery({
|
|
44
|
+
queryKey: ["messages", selectedConversation?._id, userId],
|
|
45
|
+
queryFn: ({ pageParam = 1 }) =>
|
|
46
|
+
fetchMessages(selectedConversation?._id, userId, pageParam),
|
|
47
|
+
getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,
|
|
48
|
+
onSuccess: (data) => {
|
|
49
|
+
const allMessages = data.pages.flatMap(page => page.messages);
|
|
50
|
+
setMessages(allMessages);
|
|
51
|
+
|
|
52
|
+
// Scroll to bottom on initial load
|
|
53
|
+
if (isInitialLoad && scrollContainerRef.current) {
|
|
54
|
+
setTimeout(() => {
|
|
55
|
+
scrollContainerRef.current?.scrollTo({
|
|
56
|
+
top: scrollContainerRef.current.scrollHeight,
|
|
57
|
+
behavior: 'auto'
|
|
58
|
+
});
|
|
59
|
+
setIsInitialLoad(false);
|
|
60
|
+
}, 100);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Auto-scroll to bottom when new message arrives and user is near bottom
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (!scrollContainerRef.current || isAutoScrolling) return;
|
|
68
|
+
|
|
69
|
+
const container = scrollContainerRef.current;
|
|
70
|
+
const isNearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 100;
|
|
71
|
+
|
|
72
|
+
if (isNearBottom) {
|
|
73
|
+
setIsAutoScrolling(true);
|
|
74
|
+
container.scrollTo({
|
|
75
|
+
top: container.scrollHeight,
|
|
76
|
+
behavior: "smooth"
|
|
77
|
+
});
|
|
78
|
+
setTimeout(() => setIsAutoScrolling(false), 500);
|
|
79
|
+
}
|
|
80
|
+
}, [messages.length, isAutoScrolling]);
|
|
31
81
|
|
|
32
82
|
useEffect(() => {
|
|
33
83
|
if (inView) {
|
|
@@ -124,14 +174,22 @@ useEffect(() => {
|
|
|
124
174
|
|
|
125
175
|
const handleScroll = useCallback(() => {
|
|
126
176
|
if (!scrollContainerRef.current || isFetchingNextPage || !hasNextPage) return;
|
|
127
|
-
|
|
177
|
+
|
|
128
178
|
const { scrollTop } = scrollContainerRef.current;
|
|
129
|
-
|
|
179
|
+
// Load more when scrolled near the top (10% threshold)
|
|
130
180
|
if (scrollTop < 100) {
|
|
131
181
|
fetchNextPage();
|
|
132
182
|
}
|
|
133
183
|
}, [fetchNextPage, isFetchingNextPage, hasNextPage]);
|
|
134
184
|
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
const container = scrollContainerRef.current;
|
|
187
|
+
if (container) {
|
|
188
|
+
container.addEventListener('scroll', handleScroll);
|
|
189
|
+
return () => container.removeEventListener('scroll', handleScroll);
|
|
190
|
+
}
|
|
191
|
+
}, [handleScroll]);
|
|
192
|
+
|
|
135
193
|
// useEffect(() => {
|
|
136
194
|
// const scrollContainer = scrollContainerRef.current;
|
|
137
195
|
// if (scrollContainer) {
|
|
@@ -142,12 +200,20 @@ useEffect(() => {
|
|
|
142
200
|
console.log("📩 Messages:", messages);
|
|
143
201
|
console.log("📩 Messages Length:", messages?.length);
|
|
144
202
|
|
|
203
|
+
if (isLoading && !messages.length) {
|
|
204
|
+
return <Loader />;
|
|
205
|
+
}
|
|
206
|
+
|
|
145
207
|
return (
|
|
146
|
-
<div className="chatMessages"
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
208
|
+
<div className="chatMessages"
|
|
209
|
+
ref={scrollContainerRef}
|
|
210
|
+
style={{ overflowY: 'auto', height: '100%', position: 'relative' }}
|
|
211
|
+
>
|
|
212
|
+
{isFetchingNextPage && (
|
|
213
|
+
<div className="loading-indicator">
|
|
214
|
+
<Loader/>
|
|
215
|
+
</div>
|
|
216
|
+
)}
|
|
151
217
|
{messages?.length > 0 ? (
|
|
152
218
|
messages?.map((message: any) =>
|
|
153
219
|
// Check if the message object is valid and has an _id before rendering
|
package/src/style/style.css
CHANGED
|
@@ -442,4 +442,110 @@
|
|
|
442
442
|
50% {
|
|
443
443
|
transform: translateY(-3px);
|
|
444
444
|
}
|
|
445
|
-
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/* From Uiverse.io by abrahamcalsin */
|
|
448
|
+
.dot-spinner {
|
|
449
|
+
--uib-size: 2.8rem;
|
|
450
|
+
--uib-speed: .9s;
|
|
451
|
+
--uib-color: #183153;
|
|
452
|
+
position: relative;
|
|
453
|
+
display: flex;
|
|
454
|
+
align-items: center;
|
|
455
|
+
justify-content: flex-start;
|
|
456
|
+
height: var(--uib-size);
|
|
457
|
+
width: var(--uib-size);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.dot-spinner__dot {
|
|
461
|
+
position: absolute;
|
|
462
|
+
top: 0;
|
|
463
|
+
left: 0;
|
|
464
|
+
display: flex;
|
|
465
|
+
align-items: center;
|
|
466
|
+
justify-content: flex-start;
|
|
467
|
+
height: 100%;
|
|
468
|
+
width: 100%;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.dot-spinner__dot::before {
|
|
472
|
+
content: '';
|
|
473
|
+
height: 20%;
|
|
474
|
+
width: 20%;
|
|
475
|
+
border-radius: 50%;
|
|
476
|
+
background-color: var(--uib-color);
|
|
477
|
+
transform: scale(0);
|
|
478
|
+
opacity: 0.5;
|
|
479
|
+
animation: pulse0112 calc(var(--uib-speed) * 1.111) ease-in-out infinite;
|
|
480
|
+
box-shadow: 0 0 20px rgba(18, 31, 53, 0.3);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.dot-spinner__dot:nth-child(2) {
|
|
484
|
+
transform: rotate(45deg);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.dot-spinner__dot:nth-child(2)::before {
|
|
488
|
+
animation-delay: calc(var(--uib-speed) * -0.875);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.dot-spinner__dot:nth-child(3) {
|
|
492
|
+
transform: rotate(90deg);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.dot-spinner__dot:nth-child(3)::before {
|
|
496
|
+
animation-delay: calc(var(--uib-speed) * -0.75);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.dot-spinner__dot:nth-child(4) {
|
|
500
|
+
transform: rotate(135deg);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.dot-spinner__dot:nth-child(4)::before {
|
|
504
|
+
animation-delay: calc(var(--uib-speed) * -0.625);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.dot-spinner__dot:nth-child(5) {
|
|
508
|
+
transform: rotate(180deg);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.dot-spinner__dot:nth-child(5)::before {
|
|
512
|
+
animation-delay: calc(var(--uib-speed) * -0.5);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.dot-spinner__dot:nth-child(6) {
|
|
516
|
+
transform: rotate(225deg);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.dot-spinner__dot:nth-child(6)::before {
|
|
520
|
+
animation-delay: calc(var(--uib-speed) * -0.375);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.dot-spinner__dot:nth-child(7) {
|
|
524
|
+
transform: rotate(270deg);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
.dot-spinner__dot:nth-child(7)::before {
|
|
528
|
+
animation-delay: calc(var(--uib-speed) * -0.25);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.dot-spinner__dot:nth-child(8) {
|
|
532
|
+
transform: rotate(315deg);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.dot-spinner__dot:nth-child(8)::before {
|
|
536
|
+
animation-delay: calc(var(--uib-speed) * -0.125);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
@keyframes pulse0112 {
|
|
540
|
+
0%,
|
|
541
|
+
100% {
|
|
542
|
+
transform: scale(0);
|
|
543
|
+
opacity: 0.5;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
50% {
|
|
547
|
+
transform: scale(1);
|
|
548
|
+
opacity: 1;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|