@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pubuduth-aplicy/chat-ui",
3
- "version": "2.1.39",
3
+ "version": "2.1.40",
4
4
  "description": "This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.",
5
5
  "license": "ISC",
6
6
  "author": "",
@@ -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
- const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
22
- useInfiniteQuery({
23
- queryKey: ["messages", selectedConversation?._id, userId],
24
- queryFn: ({ pageParam = 1 }) =>
25
- fetchMessages(selectedConversation?._id, userId, pageParam),
26
- initialPageParam: 1,
27
- getNextPageParam: (lastPage, allPages) => {
28
- return lastPage.messages.length ? allPages.length + 1: undefined;
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
- <div ref={ref} className="my-8">
149
- {isFetchingNextPage ? '<Loading isLoading={isFetchingNextPage} /> ': null}
150
- </div>
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
@@ -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
+