mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-14 22:52:15 +02:00
feat: implement skeleton loading states in sidebar components for improved user experience
This commit is contained in:
parent
8c3b65bac2
commit
db27ee95ce
3 changed files with 89 additions and 11 deletions
|
|
@ -27,6 +27,7 @@ import {
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { Spinner } from "@/components/ui/spinner";
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
|
|
@ -303,8 +304,16 @@ export function AllPrivateChatsSidebar({
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto overflow-x-hidden p-2">
|
<div className="flex-1 overflow-y-auto overflow-x-hidden p-2">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex items-center justify-center py-8">
|
<div className="space-y-1">
|
||||||
<Spinner size="md" className="text-muted-foreground" />
|
{[75, 90, 55, 80, 65, 85].map((titleWidth, i) => (
|
||||||
|
<div
|
||||||
|
key={`skeleton-${i}`}
|
||||||
|
className="flex items-center gap-2 rounded-md px-2 py-1.5"
|
||||||
|
>
|
||||||
|
<Skeleton className="h-4 w-4 shrink-0 rounded" />
|
||||||
|
<Skeleton className="h-4 rounded" style={{ width: `${titleWidth}%` }} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : error ? (
|
) : error ? (
|
||||||
<div className="text-center py-8 text-sm text-destructive">
|
<div className="text-center py-8 text-sm text-destructive">
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import {
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { Spinner } from "@/components/ui/spinner";
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
|
|
@ -303,8 +304,16 @@ export function AllSharedChatsSidebar({
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto overflow-x-hidden p-2">
|
<div className="flex-1 overflow-y-auto overflow-x-hidden p-2">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex items-center justify-center py-8">
|
<div className="space-y-1">
|
||||||
<Spinner size="md" className="text-muted-foreground" />
|
{[75, 90, 55, 80, 65, 85].map((titleWidth, i) => (
|
||||||
|
<div
|
||||||
|
key={`skeleton-${i}`}
|
||||||
|
className="flex items-center gap-2 rounded-md px-2 py-1.5"
|
||||||
|
>
|
||||||
|
<Skeleton className="h-4 w-4 shrink-0 rounded" />
|
||||||
|
<Skeleton className="h-4 rounded" style={{ width: `${titleWidth}%` }} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : error ? (
|
) : error ? (
|
||||||
<div className="text-center py-8 text-sm text-destructive">
|
<div className="text-center py-8 text-sm text-destructive">
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import {
|
||||||
Inbox,
|
Inbox,
|
||||||
LayoutGrid,
|
LayoutGrid,
|
||||||
ListFilter,
|
ListFilter,
|
||||||
Loader2,
|
|
||||||
MessageSquare,
|
MessageSquare,
|
||||||
Search,
|
Search,
|
||||||
X,
|
X,
|
||||||
|
|
@ -43,6 +42,7 @@ import {
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { Spinner } from "@/components/ui/spinner";
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
|
|
@ -928,12 +928,39 @@ export function InboxSidebar({
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto overflow-x-hidden p-2">
|
<div className="flex-1 overflow-y-auto overflow-x-hidden p-2">
|
||||||
{(isSearchMode ? isSearchLoading : loading) ? (
|
{(isSearchMode ? isSearchLoading : loading) ? (
|
||||||
<div className="flex items-center justify-center py-8">
|
<div className="space-y-2">
|
||||||
{isSearchMode ? (
|
{activeTab === "comments"
|
||||||
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
? /* Comments skeleton: avatar + two-line text + time */
|
||||||
) : (
|
[85, 60, 90, 70, 50, 75].map((titleWidth, i) => (
|
||||||
<Spinner size="md" className="text-muted-foreground" />
|
<div
|
||||||
)}
|
key={`skeleton-comment-${i}`}
|
||||||
|
className="flex items-center gap-3 rounded-lg px-3 py-3 h-[80px]"
|
||||||
|
>
|
||||||
|
<Skeleton className="h-8 w-8 rounded-full shrink-0" />
|
||||||
|
<div className="flex-1 min-w-0 space-y-2">
|
||||||
|
<Skeleton className="h-3 rounded" style={{ width: `${titleWidth}%` }} />
|
||||||
|
<Skeleton className="h-2.5 w-[70%] rounded" />
|
||||||
|
</div>
|
||||||
|
<Skeleton className="h-3 w-6 shrink-0 rounded" />
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
: /* Status skeleton: status icon circle + two-line text + time */
|
||||||
|
[75, 90, 55, 80, 65, 85].map((titleWidth, i) => (
|
||||||
|
<div
|
||||||
|
key={`skeleton-status-${i}`}
|
||||||
|
className="flex items-center gap-3 rounded-lg px-3 py-3 h-[80px]"
|
||||||
|
>
|
||||||
|
<Skeleton className="h-8 w-8 rounded-full shrink-0" />
|
||||||
|
<div className="flex-1 min-w-0 space-y-2">
|
||||||
|
<Skeleton className="h-3 rounded" style={{ width: `${titleWidth}%` }} />
|
||||||
|
<Skeleton className="h-2.5 w-[60%] rounded" />
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1.5 shrink-0">
|
||||||
|
<Skeleton className="h-3 w-6 rounded" />
|
||||||
|
<Skeleton className="h-2 w-2 rounded-full" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : filteredItems.length > 0 ? (
|
) : filteredItems.length > 0 ? (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|
@ -1000,6 +1027,39 @@ export function InboxSidebar({
|
||||||
{!isSearchMode && filteredItems.length < 5 && hasMore && (
|
{!isSearchMode && filteredItems.length < 5 && hasMore && (
|
||||||
<div ref={prefetchTriggerRef} className="h-1" />
|
<div ref={prefetchTriggerRef} className="h-1" />
|
||||||
)}
|
)}
|
||||||
|
{/* Loading more skeletons at the bottom during infinite scroll */}
|
||||||
|
{loadingMore && (
|
||||||
|
activeTab === "comments"
|
||||||
|
? [80, 60, 90].map((titleWidth, i) => (
|
||||||
|
<div
|
||||||
|
key={`loading-more-comment-${i}`}
|
||||||
|
className="flex items-center gap-3 rounded-lg px-3 py-3 h-[80px]"
|
||||||
|
>
|
||||||
|
<Skeleton className="h-8 w-8 rounded-full shrink-0" />
|
||||||
|
<div className="flex-1 min-w-0 space-y-2">
|
||||||
|
<Skeleton className="h-3 rounded" style={{ width: `${titleWidth}%` }} />
|
||||||
|
<Skeleton className="h-2.5 w-[70%] rounded" />
|
||||||
|
</div>
|
||||||
|
<Skeleton className="h-3 w-6 shrink-0 rounded" />
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
: [70, 85, 55].map((titleWidth, i) => (
|
||||||
|
<div
|
||||||
|
key={`loading-more-status-${i}`}
|
||||||
|
className="flex items-center gap-3 rounded-lg px-3 py-3 h-[80px]"
|
||||||
|
>
|
||||||
|
<Skeleton className="h-8 w-8 rounded-full shrink-0" />
|
||||||
|
<div className="flex-1 min-w-0 space-y-2">
|
||||||
|
<Skeleton className="h-3 rounded" style={{ width: `${titleWidth}%` }} />
|
||||||
|
<Skeleton className="h-2.5 w-[60%] rounded" />
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1.5 shrink-0">
|
||||||
|
<Skeleton className="h-3 w-6 rounded" />
|
||||||
|
<Skeleton className="h-2 w-2 rounded-full" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : isSearchMode ? (
|
) : isSearchMode ? (
|
||||||
<div className="text-center py-8">
|
<div className="text-center py-8">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue