feat: add skeleton loader
This commit is contained in:
+17
-1
@@ -1,4 +1,5 @@
|
|||||||
import Avatar from "@/components/ui/avatar";
|
import Avatar from "@/components/ui/avatar";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { useChats } from "@/hooks/useChats";
|
import { useChats } from "@/hooks/useChats";
|
||||||
import { useContacts } from "@/hooks/useContact";
|
import { useContacts } from "@/hooks/useContact";
|
||||||
import { getFormattedName } from "@/lib/utils";
|
import { getFormattedName } from "@/lib/utils";
|
||||||
@@ -23,7 +24,7 @@ export default function ChatsSection({ selected, onSelect }: Props) {
|
|||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
|
|
||||||
const contacts = useContacts();
|
const contacts = useContacts();
|
||||||
const { data: chats } = useChats({
|
const { data: chats, isLoading } = useChats({
|
||||||
refetchInterval: 10_000,
|
refetchInterval: 10_000,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -77,6 +78,21 @@ export default function ChatsSection({ selected, onSelect }: Props) {
|
|||||||
</div> */}
|
</div> */}
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto px-2 pb-3">
|
<div className="flex-1 overflow-y-auto px-2 pb-3">
|
||||||
|
{chats == null || isLoading
|
||||||
|
? [...Array(20)].map((_, i) => (
|
||||||
|
<div key={i} className="w-full flex items-center gap-3 px-3 mb-4">
|
||||||
|
<Skeleton key={i} className="size-12 rounded-full bg-accent" />
|
||||||
|
<div className="flex-1">
|
||||||
|
<Skeleton
|
||||||
|
key={i}
|
||||||
|
className="flex-1 h-4 bg-accent mb-2 w-1/2"
|
||||||
|
/>
|
||||||
|
<Skeleton key={i} className="flex-1 h-11 bg-accent" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
: null}
|
||||||
|
|
||||||
{filteredChats?.map((chat) => (
|
{filteredChats?.map((chat) => (
|
||||||
<button
|
<button
|
||||||
key={chat.id}
|
key={chat.id}
|
||||||
|
|||||||
+25
-4
@@ -24,6 +24,7 @@ import Avatar from "@/components/ui/avatar";
|
|||||||
import { useIntersection } from "@/hooks/useIntersection";
|
import { useIntersection } from "@/hooks/useIntersection";
|
||||||
import Paragraph from "@/components/paragraph";
|
import Paragraph from "@/components/paragraph";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
@@ -280,15 +281,35 @@ export default function MessagesSection({ chatId, maxCount = 50 }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{messages == null || isLoading || isFetchingNextPage
|
||||||
|
? [...Array(10)].map((_, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className={cn(
|
||||||
|
"mb-4 shrink-0 w-full max-w-[44%]",
|
||||||
|
i % 2 === 0 ? "self-start" : "self-end",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Skeleton key={i} className="w-20 mb-3 h-6 bg-card" />
|
||||||
|
<Skeleton
|
||||||
|
key={i}
|
||||||
|
className={cn(
|
||||||
|
"w-full h-20 bg-card rounded-2xl",
|
||||||
|
["h-20", "h-32", "h-24", "h-40"][i % 4],
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
: null}
|
||||||
|
|
||||||
<div ref={endScrollRef} className="h-1" />
|
<div ref={endScrollRef} className="h-1" />
|
||||||
|
|
||||||
<div className="mb-6 flex items-center justify-center gap-2 text-[11px] text-muted-foreground">
|
<div className="mb-6 flex items-center justify-center gap-2 text-[11px] text-muted-foreground">
|
||||||
<span className="h-px flex-1 bg-border" />
|
<span className="h-px flex-1 bg-border" />
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{isLoading ||
|
{isLoading || isFetchingNextPage ? (
|
||||||
(isFetchingNextPage && (
|
<Loader2Icon className="animate-spin size-4" />
|
||||||
<Loader2Icon className="animate-spin size-4" />
|
) : null}
|
||||||
))}
|
|
||||||
<span>
|
<span>
|
||||||
{isLoading || isFetchingNextPage
|
{isLoading || isFetchingNextPage
|
||||||
? "Loading..."
|
? "Loading..."
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { cn } from "cn";
|
||||||
|
|
||||||
|
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="skeleton"
|
||||||
|
className={cn("animate-pulse rounded-md bg-background/30", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Skeleton };
|
||||||
Reference in New Issue
Block a user