refactor: unify sidebar state management in LayoutDataProvider to allow only one slide-out panel open at a time

This commit is contained in:
Anish Sarkar 2026-03-22 00:01:31 +05:30
parent 7d33a69e20
commit 020d806b1f
7 changed files with 333 additions and 258 deletions

View file

@ -1,6 +1,7 @@
"use client";
import { useMemo, useState } from "react";
import { AnimatePresence, motion } from "motion/react";
import { useCallback, useMemo, useState } from "react";
import { TooltipProvider } from "@/components/ui/tooltip";
import type { InboxItem } from "@/hooks/use-inbox";
import { useIsMobile } from "@/hooks/use-mobile";
@ -12,15 +13,16 @@ import { Header } from "../header";
import { IconRail } from "../icon-rail";
import { RightPanel } from "../right-panel/RightPanel";
import {
AllPrivateChatsSidebar,
AllSharedChatsSidebar,
AnnouncementsSidebar,
AllPrivateChatsSidebarContent,
AllSharedChatsSidebarContent,
AnnouncementsSidebarContent,
DocumentsSidebar,
InboxSidebar,
InboxSidebarContent,
MobileSidebar,
MobileSidebarTrigger,
Sidebar,
} from "../sidebar";
import { SidebarSlideOutPanel } from "../sidebar/SidebarSlideOutPanel";
// Per-tab data source
interface TabDataSource {
@ -34,10 +36,11 @@ interface TabDataSource {
markAllAsRead: () => Promise<boolean>;
}
export type ActiveSlideoutPanel = "inbox" | "shared" | "private" | "announcements" | null;
// Inbox-related props — per-tab data sources with independent loading/pagination
interface InboxProps {
isOpen: boolean;
onOpenChange: (open: boolean) => void;
totalUnreadCount: number;
comments: TabDataSource;
status: TabDataSource;
@ -75,22 +78,17 @@ interface LayoutShellProps {
isChatPage?: boolean;
children: React.ReactNode;
className?: string;
// Unified slide-out panel state
activeSlideoutPanel?: ActiveSlideoutPanel;
onSlideoutPanelChange?: (panel: ActiveSlideoutPanel) => void;
// Inbox props
inbox?: InboxProps;
announcementsPanel?: {
open: boolean;
onOpenChange: (open: boolean) => void;
};
isLoadingChats?: boolean;
// All chats panel props
allSharedChatsPanel?: {
open: boolean;
onOpenChange: (open: boolean) => void;
searchSpaceId: string;
};
allPrivateChatsPanel?: {
open: boolean;
onOpenChange: (open: boolean) => void;
searchSpaceId: string;
};
documentsPanel?: {
@ -133,8 +131,9 @@ export function LayoutShell({
isChatPage = false,
children,
className,
activeSlideoutPanel = null,
onSlideoutPanelChange,
inbox,
announcementsPanel,
isLoadingChats = false,
allSharedChatsPanel,
allPrivateChatsPanel,
@ -155,6 +154,26 @@ export function LayoutShell({
[isCollapsed, setIsCollapsed, toggleCollapsed, sidebarWidth]
);
const closeSlideout = useCallback(
(open: boolean) => {
if (!open) onSlideoutPanelChange?.(null);
},
[onSlideoutPanelChange]
);
const anySlideOutOpen = activeSlideoutPanel !== null;
const panelAriaLabel =
activeSlideoutPanel === "inbox"
? "Inbox"
: activeSlideoutPanel === "shared"
? "Shared Chats"
: activeSlideoutPanel === "private"
? "Private Chats"
: activeSlideoutPanel === "announcements"
? "Announcements"
: "Panel";
// Mobile layout
if (isMobile) {
return (
@ -171,8 +190,6 @@ export function LayoutShell({
searchSpaces={searchSpaces}
activeSearchSpaceId={activeSearchSpaceId}
onSearchSpaceSelect={onSearchSpaceSelect}
onSearchSpaceDelete={onSearchSpaceDelete}
onSearchSpaceSettings={onSearchSpaceSettings}
onAddSearchSpace={onAddSearchSpace}
searchSpace={searchSpace}
navItems={navItems}
@ -187,8 +204,8 @@ export function LayoutShell({
onChatArchive={onChatArchive}
onViewAllSharedChats={onViewAllSharedChats}
onViewAllPrivateChats={onViewAllPrivateChats}
isSharedChatsPanelOpen={allSharedChatsPanel?.open}
isPrivateChatsPanelOpen={allPrivateChatsPanel?.open}
isSharedChatsPanelOpen={activeSlideoutPanel === "shared"}
isPrivateChatsPanelOpen={activeSlideoutPanel === "private"}
user={user}
onSettings={onSettings}
onManageMembers={onManageMembers}
@ -200,70 +217,98 @@ export function LayoutShell({
isLoadingChats={isLoadingChats}
/>
<main className={cn("flex-1", isChatPage ? "overflow-hidden" : "overflow-auto")}>
{children}
</main>
<main className={cn("flex-1", isChatPage ? "overflow-hidden" : "overflow-auto")}>
{children}
</main>
{/* Mobile Inbox Sidebar - only render when open to avoid scroll blocking */}
{inbox?.isOpen && (
<InboxSidebar
open={inbox.isOpen}
onOpenChange={inbox.onOpenChange}
comments={inbox.comments}
status={inbox.status}
totalUnreadCount={inbox.totalUnreadCount}
onCloseMobileSidebar={() => setMobileMenuOpen(false)}
/>
)}
{/* Mobile unified slide-out panel */}
<SidebarSlideOutPanel
open={anySlideOutOpen}
onOpenChange={closeSlideout}
ariaLabel={panelAriaLabel}
>
<AnimatePresence mode="popLayout" initial={false}>
{activeSlideoutPanel === "inbox" && inbox && (
<motion.div
key="inbox"
className="h-full flex flex-col"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
>
<InboxSidebarContent
onOpenChange={(open) => closeSlideout(open)}
comments={inbox.comments}
status={inbox.status}
totalUnreadCount={inbox.totalUnreadCount}
onCloseMobileSidebar={() => setMobileMenuOpen(false)}
/>
</motion.div>
)}
{activeSlideoutPanel === "announcements" && (
<motion.div
key="announcements"
className="h-full flex flex-col"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
>
<AnnouncementsSidebarContent
onOpenChange={(open) => closeSlideout(open)}
onCloseMobileSidebar={() => setMobileMenuOpen(false)}
/>
</motion.div>
)}
{activeSlideoutPanel === "shared" && allSharedChatsPanel && (
<motion.div
key="shared"
className="h-full flex flex-col"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
>
<AllSharedChatsSidebarContent
onOpenChange={(open) => closeSlideout(open)}
searchSpaceId={allSharedChatsPanel.searchSpaceId}
onCloseMobileSidebar={() => setMobileMenuOpen(false)}
/>
</motion.div>
)}
{activeSlideoutPanel === "private" && allPrivateChatsPanel && (
<motion.div
key="private"
className="h-full flex flex-col"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
>
<AllPrivateChatsSidebarContent
onOpenChange={(open) => closeSlideout(open)}
searchSpaceId={allPrivateChatsPanel.searchSpaceId}
onCloseMobileSidebar={() => setMobileMenuOpen(false)}
/>
</motion.div>
)}
</AnimatePresence>
</SidebarSlideOutPanel>
{/* Mobile Documents Sidebar - slide-out panel */}
{documentsPanel && (
<DocumentsSidebar
open={documentsPanel.open}
onOpenChange={documentsPanel.onOpenChange}
/>
)}
{/* Mobile Announcements Sidebar */}
{announcementsPanel?.open && (
<AnnouncementsSidebar
open={announcementsPanel.open}
onOpenChange={announcementsPanel.onOpenChange}
onCloseMobileSidebar={() => setMobileMenuOpen(false)}
/>
)}
{/* Mobile All Shared Chats - slide-out panel */}
{allSharedChatsPanel && (
<AllSharedChatsSidebar
open={allSharedChatsPanel.open}
onOpenChange={allSharedChatsPanel.onOpenChange}
searchSpaceId={allSharedChatsPanel.searchSpaceId}
onCloseMobileSidebar={() => setMobileMenuOpen(false)}
/>
)}
{/* Mobile All Private Chats - slide-out panel */}
{allPrivateChatsPanel && (
<AllPrivateChatsSidebar
open={allPrivateChatsPanel.open}
onOpenChange={allPrivateChatsPanel.onOpenChange}
searchSpaceId={allPrivateChatsPanel.searchSpaceId}
onCloseMobileSidebar={() => setMobileMenuOpen(false)}
/>
)}
{/* Mobile Documents Sidebar - separate (not part of slide-out group) */}
{documentsPanel && (
<DocumentsSidebar
open={documentsPanel.open}
onOpenChange={documentsPanel.onOpenChange}
/>
)}
</div>
</TooltipProvider>
</SidebarProvider>
);
}
const anySlideOutOpen =
inbox?.isOpen ||
announcementsPanel?.open ||
allSharedChatsPanel?.open ||
allPrivateChatsPanel?.open;
// Desktop layout
return (
<SidebarProvider value={sidebarContextValue}>
@ -305,8 +350,8 @@ export function LayoutShell({
onChatArchive={onChatArchive}
onViewAllSharedChats={onViewAllSharedChats}
onViewAllPrivateChats={onViewAllPrivateChats}
isSharedChatsPanelOpen={allSharedChatsPanel?.open}
isPrivateChatsPanelOpen={allPrivateChatsPanel?.open}
isSharedChatsPanelOpen={activeSlideoutPanel === "shared"}
isPrivateChatsPanelOpen={activeSlideoutPanel === "private"}
user={user}
onSettings={onSettings}
onManageMembers={onManageMembers}
@ -324,39 +369,76 @@ export function LayoutShell({
isResizing={isResizing}
/>
{/* Slide-out panels render as siblings next to the sidebar */}
{inbox && (
<InboxSidebar
open={inbox.isOpen}
onOpenChange={inbox.onOpenChange}
comments={inbox.comments}
status={inbox.status}
totalUnreadCount={inbox.totalUnreadCount}
/>
)}
{announcementsPanel && (
<AnnouncementsSidebar
open={announcementsPanel.open}
onOpenChange={announcementsPanel.onOpenChange}
/>
)}
{allSharedChatsPanel && (
<AllSharedChatsSidebar
open={allSharedChatsPanel.open}
onOpenChange={allSharedChatsPanel.onOpenChange}
searchSpaceId={allSharedChatsPanel.searchSpaceId}
/>
)}
{allPrivateChatsPanel && (
<AllPrivateChatsSidebar
open={allPrivateChatsPanel.open}
onOpenChange={allPrivateChatsPanel.onOpenChange}
searchSpaceId={allPrivateChatsPanel.searchSpaceId}
/>
)}
{/* Unified slide-out panel — shell stays open, content cross-fades */}
<SidebarSlideOutPanel
open={anySlideOutOpen}
onOpenChange={closeSlideout}
ariaLabel={panelAriaLabel}
>
<AnimatePresence mode="popLayout" initial={false}>
{activeSlideoutPanel === "inbox" && inbox && (
<motion.div
key="inbox"
className="h-full flex flex-col"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
>
<InboxSidebarContent
onOpenChange={(open) => closeSlideout(open)}
comments={inbox.comments}
status={inbox.status}
totalUnreadCount={inbox.totalUnreadCount}
/>
</motion.div>
)}
{activeSlideoutPanel === "announcements" && (
<motion.div
key="announcements"
className="h-full flex flex-col"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
>
<AnnouncementsSidebarContent
onOpenChange={(open) => closeSlideout(open)}
/>
</motion.div>
)}
{activeSlideoutPanel === "shared" && allSharedChatsPanel && (
<motion.div
key="shared"
className="h-full flex flex-col"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
>
<AllSharedChatsSidebarContent
onOpenChange={(open) => closeSlideout(open)}
searchSpaceId={allSharedChatsPanel.searchSpaceId}
/>
</motion.div>
)}
{activeSlideoutPanel === "private" && allPrivateChatsPanel && (
<motion.div
key="private"
className="h-full flex flex-col"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
>
<AllPrivateChatsSidebarContent
onOpenChange={(open) => closeSlideout(open)}
searchSpaceId={allPrivateChatsPanel.searchSpaceId}
/>
</motion.div>
)}
</AnimatePresence>
</SidebarSlideOutPanel>
</div>
{/* Resize handle — negative margins eat the flex gap so spacing stays unchanged */}

View file

@ -16,7 +16,7 @@ import {
} from "lucide-react";
import { useParams, useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useCallback, useMemo, useRef, useState } from "react";
import { toast } from "sonner";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/animated-tabs";
import { Button } from "@/components/ui/button";
@ -50,19 +50,21 @@ import {
import { cn } from "@/lib/utils";
import { SidebarSlideOutPanel } from "./SidebarSlideOutPanel";
interface AllPrivateChatsSidebarProps {
open: boolean;
export interface AllPrivateChatsSidebarContentProps {
onOpenChange: (open: boolean) => void;
searchSpaceId: string;
onCloseMobileSidebar?: () => void;
}
export function AllPrivateChatsSidebar({
open,
interface AllPrivateChatsSidebarProps extends AllPrivateChatsSidebarContentProps {
open: boolean;
}
export function AllPrivateChatsSidebarContent({
onOpenChange,
searchSpaceId,
onCloseMobileSidebar,
}: AllPrivateChatsSidebarProps) {
}: AllPrivateChatsSidebarContentProps) {
const t = useTranslations("sidebar");
const router = useRouter();
const params = useParams();
@ -96,16 +98,6 @@ export function AllPrivateChatsSidebar({
const isSearchMode = !!debouncedSearchQuery.trim();
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === "Escape" && open) {
onOpenChange(false);
}
};
document.addEventListener("keydown", handleEscape);
return () => document.removeEventListener("keydown", handleEscape);
}, [open, onOpenChange]);
const {
data: threadsData,
error: threadsError,
@ -113,7 +105,7 @@ export function AllPrivateChatsSidebar({
} = useQuery({
queryKey: ["all-threads", searchSpaceId],
queryFn: () => fetchThreads(Number(searchSpaceId)),
enabled: !!searchSpaceId && open && !isSearchMode,
enabled: !!searchSpaceId && !isSearchMode,
});
const {
@ -123,7 +115,7 @@ export function AllPrivateChatsSidebar({
} = useQuery({
queryKey: ["search-threads", searchSpaceId, debouncedSearchQuery],
queryFn: () => searchThreads(Number(searchSpaceId), debouncedSearchQuery.trim()),
enabled: !!searchSpaceId && open && isSearchMode,
enabled: !!searchSpaceId && isSearchMode,
});
// Filter to only private chats (PRIVATE visibility or no visibility set)
@ -250,11 +242,7 @@ export function AllPrivateChatsSidebar({
const archivedCount = archivedChats.length;
return (
<SidebarSlideOutPanel
open={open}
onOpenChange={onOpenChange}
ariaLabel={t("chats") || "Private Chats"}
>
<>
<div className="shrink-0 p-4 pb-2 space-y-3">
<div className="flex items-center gap-2">
{isMobile && (
@ -530,6 +518,29 @@ export function AllPrivateChatsSidebar({
</DialogFooter>
</DialogContent>
</Dialog>
</>
);
}
export function AllPrivateChatsSidebar({
open,
onOpenChange,
searchSpaceId,
onCloseMobileSidebar,
}: AllPrivateChatsSidebarProps) {
const t = useTranslations("sidebar");
return (
<SidebarSlideOutPanel
open={open}
onOpenChange={onOpenChange}
ariaLabel={t("chats") || "Private Chats"}
>
<AllPrivateChatsSidebarContent
onOpenChange={onOpenChange}
searchSpaceId={searchSpaceId}
onCloseMobileSidebar={onCloseMobileSidebar}
/>
</SidebarSlideOutPanel>
);
}

View file

@ -16,7 +16,7 @@ import {
} from "lucide-react";
import { useParams, useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useCallback, useMemo, useRef, useState } from "react";
import { toast } from "sonner";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/animated-tabs";
import { Button } from "@/components/ui/button";
@ -50,19 +50,21 @@ import {
import { cn } from "@/lib/utils";
import { SidebarSlideOutPanel } from "./SidebarSlideOutPanel";
interface AllSharedChatsSidebarProps {
open: boolean;
export interface AllSharedChatsSidebarContentProps {
onOpenChange: (open: boolean) => void;
searchSpaceId: string;
onCloseMobileSidebar?: () => void;
}
export function AllSharedChatsSidebar({
open,
interface AllSharedChatsSidebarProps extends AllSharedChatsSidebarContentProps {
open: boolean;
}
export function AllSharedChatsSidebarContent({
onOpenChange,
searchSpaceId,
onCloseMobileSidebar,
}: AllSharedChatsSidebarProps) {
}: AllSharedChatsSidebarContentProps) {
const t = useTranslations("sidebar");
const router = useRouter();
const params = useParams();
@ -96,16 +98,6 @@ export function AllSharedChatsSidebar({
const isSearchMode = !!debouncedSearchQuery.trim();
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === "Escape" && open) {
onOpenChange(false);
}
};
document.addEventListener("keydown", handleEscape);
return () => document.removeEventListener("keydown", handleEscape);
}, [open, onOpenChange]);
const {
data: threadsData,
error: threadsError,
@ -113,7 +105,7 @@ export function AllSharedChatsSidebar({
} = useQuery({
queryKey: ["all-threads", searchSpaceId],
queryFn: () => fetchThreads(Number(searchSpaceId)),
enabled: !!searchSpaceId && open && !isSearchMode,
enabled: !!searchSpaceId && !isSearchMode,
});
const {
@ -123,7 +115,7 @@ export function AllSharedChatsSidebar({
} = useQuery({
queryKey: ["search-threads", searchSpaceId, debouncedSearchQuery],
queryFn: () => searchThreads(Number(searchSpaceId), debouncedSearchQuery.trim()),
enabled: !!searchSpaceId && open && isSearchMode,
enabled: !!searchSpaceId && isSearchMode,
});
// Filter to only shared chats (SEARCH_SPACE visibility)
@ -250,11 +242,7 @@ export function AllSharedChatsSidebar({
const archivedCount = archivedChats.length;
return (
<SidebarSlideOutPanel
open={open}
onOpenChange={onOpenChange}
ariaLabel={t("shared_chats") || "Shared Chats"}
>
<>
<div className="shrink-0 p-4 pb-2 space-y-3">
<div className="flex items-center gap-2">
{isMobile && (
@ -530,6 +518,29 @@ export function AllSharedChatsSidebar({
</DialogFooter>
</DialogContent>
</Dialog>
</>
);
}
export function AllSharedChatsSidebar({
open,
onOpenChange,
searchSpaceId,
onCloseMobileSidebar,
}: AllSharedChatsSidebarProps) {
const t = useTranslations("sidebar");
return (
<SidebarSlideOutPanel
open={open}
onOpenChange={onOpenChange}
ariaLabel={t("shared_chats") || "Shared Chats"}
>
<AllSharedChatsSidebarContent
onOpenChange={onOpenChange}
searchSpaceId={searchSpaceId}
onCloseMobileSidebar={onCloseMobileSidebar}
/>
</SidebarSlideOutPanel>
);
}

View file

@ -9,26 +9,27 @@ import { useAnnouncements } from "@/hooks/use-announcements";
import { useMediaQuery } from "@/hooks/use-media-query";
import { SidebarSlideOutPanel } from "./SidebarSlideOutPanel";
interface AnnouncementsSidebarProps {
open: boolean;
export interface AnnouncementsSidebarContentProps {
onOpenChange: (open: boolean) => void;
onCloseMobileSidebar?: () => void;
}
export function AnnouncementsSidebar({
open,
interface AnnouncementsSidebarProps extends AnnouncementsSidebarContentProps {
open: boolean;
}
export function AnnouncementsSidebarContent({
onOpenChange,
onCloseMobileSidebar,
}: AnnouncementsSidebarProps) {
}: AnnouncementsSidebarContentProps) {
const isMobile = !useMediaQuery("(min-width: 640px)");
const { announcements, markAllRead } = useAnnouncements();
useEffect(() => {
if (!open) return;
markAllRead();
}, [open, markAllRead]);
}, [markAllRead]);
const body = (
return (
<div className="h-full flex flex-col">
<div className="shrink-0 p-4 pb-2 space-y-3">
<div className="flex items-center justify-between">
@ -65,10 +66,19 @@ export function AnnouncementsSidebar({
</div>
</div>
);
}
export function AnnouncementsSidebar({
open,
onOpenChange,
onCloseMobileSidebar,
}: AnnouncementsSidebarProps) {
return (
<SidebarSlideOutPanel open={open} onOpenChange={onOpenChange} ariaLabel="Announcements">
{body}
<AnnouncementsSidebarContent
onOpenChange={onOpenChange}
onCloseMobileSidebar={onCloseMobileSidebar}
/>
</SidebarSlideOutPanel>
);
}

View file

@ -140,8 +140,7 @@ interface TabDataSource {
markAllAsRead: () => Promise<boolean>;
}
interface InboxSidebarProps {
open: boolean;
export interface InboxSidebarContentProps {
onOpenChange: (open: boolean) => void;
comments: TabDataSource;
status: TabDataSource;
@ -149,14 +148,17 @@ interface InboxSidebarProps {
onCloseMobileSidebar?: () => void;
}
export function InboxSidebar({
open,
interface InboxSidebarProps extends InboxSidebarContentProps {
open: boolean;
}
export function InboxSidebarContent({
onOpenChange,
comments,
status,
totalUnreadCount,
onCloseMobileSidebar,
}: InboxSidebarProps) {
}: InboxSidebarContentProps) {
const t = useTranslations("sidebar");
const router = useRouter();
const params = useParams();
@ -199,7 +201,7 @@ export function InboxSidebar({
},
}),
staleTime: 30 * 1000,
enabled: isSearchMode && open,
enabled: isSearchMode,
});
useEffect(() => {
@ -207,23 +209,13 @@ export function InboxSidebar({
}, []);
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === "Escape" && open) {
onOpenChange(false);
}
};
document.addEventListener("keydown", handleEscape);
return () => document.removeEventListener("keydown", handleEscape);
}, [open, onOpenChange]);
useEffect(() => {
if (!open || !isMobile) return;
if (!isMobile) return;
const originalOverflow = document.body.style.overflow;
document.body.style.overflow = "hidden";
return () => {
document.body.style.overflow = originalOverflow;
};
}, [open, isMobile]);
}, [isMobile]);
useEffect(() => {
if (activeTab !== "status") {
@ -239,7 +231,7 @@ export function InboxSidebar({
queryKey: cacheKeys.notifications.sourceTypes(searchSpaceId),
queryFn: () => notificationsApiService.getSourceTypes(searchSpaceId ?? undefined),
staleTime: 60 * 1000,
enabled: open && activeTab === "status",
enabled: activeTab === "status",
});
const statusSourceOptions = useMemo(() => {
@ -327,7 +319,7 @@ export function InboxSidebar({
// Infinite scroll — uses active tab's pagination
useEffect(() => {
if (!activeSource.hasMore || activeSource.loadingMore || !open || isSearchMode) return;
if (!activeSource.hasMore || activeSource.loadingMore || isSearchMode) return;
const observer = new IntersectionObserver(
(entries) => {
@ -347,7 +339,7 @@ export function InboxSidebar({
}
return () => observer.disconnect();
}, [activeSource.hasMore, activeSource.loadingMore, activeSource.loadMore, open, isSearchMode]);
}, [activeSource.hasMore, activeSource.loadingMore, activeSource.loadMore, isSearchMode]);
const handleItemClick = useCallback(
async (item: InboxItem) => {
@ -522,7 +514,7 @@ export function InboxSidebar({
const isLoading = isSearchMode ? isSearchLoading : activeSource.loading;
const inboxContent = (
return (
<>
<div className="shrink-0 p-4 pb-2 space-y-3">
<div className="flex items-center justify-between">
@ -546,7 +538,7 @@ export function InboxSidebar({
<Button
variant="ghost"
size="icon"
className="h-8 w-8 rounded-full"
className="h-7 w-7 rounded-full"
onClick={() => setFilterDrawerOpen(true)}
>
<ListFilter className="h-4 w-4 text-muted-foreground" />
@ -694,7 +686,7 @@ export function InboxSidebar({
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8 rounded-full">
<Button variant="ghost" size="icon" className="h-7 w-7 rounded-full">
<ListFilter className="h-4 w-4 text-muted-foreground" />
<span className="sr-only">{t("filter") || "Filter"}</span>
</Button>
@ -790,7 +782,7 @@ export function InboxSidebar({
<Button
variant="ghost"
size="icon"
className="h-8 w-8 rounded-full"
className="h-7 w-7 rounded-full"
onClick={handleMarkAllAsRead}
disabled={totalUnreadCount === 0}
>
@ -803,7 +795,7 @@ export function InboxSidebar({
<Button
variant="ghost"
size="icon"
className="h-8 w-8 rounded-full"
className="h-7 w-7 rounded-full"
onClick={handleMarkAllAsRead}
disabled={totalUnreadCount === 0}
>
@ -1051,10 +1043,27 @@ export function InboxSidebar({
</div>
</>
);
}
export function InboxSidebar({
open,
onOpenChange,
comments,
status,
totalUnreadCount,
onCloseMobileSidebar,
}: InboxSidebarProps) {
const t = useTranslations("sidebar");
return (
<SidebarSlideOutPanel open={open} onOpenChange={onOpenChange} ariaLabel={t("inbox") || "Inbox"}>
{inboxContent}
<InboxSidebarContent
onOpenChange={onOpenChange}
comments={comments}
status={status}
totalUnreadCount={totalUnreadCount}
onCloseMobileSidebar={onCloseMobileSidebar}
/>
</SidebarSlideOutPanel>
);
}

View file

@ -1,9 +1,9 @@
export { AllPrivateChatsSidebar } from "./AllPrivateChatsSidebar";
export { AllSharedChatsSidebar } from "./AllSharedChatsSidebar";
export { AnnouncementsSidebar } from "./AnnouncementsSidebar";
export { AllPrivateChatsSidebar, AllPrivateChatsSidebarContent } from "./AllPrivateChatsSidebar";
export { AllSharedChatsSidebar, AllSharedChatsSidebarContent } from "./AllSharedChatsSidebar";
export { AnnouncementsSidebar, AnnouncementsSidebarContent } from "./AnnouncementsSidebar";
export { ChatListItem } from "./ChatListItem";
export { DocumentsSidebar } from "./DocumentsSidebar";
export { InboxSidebar } from "./InboxSidebar";
export { InboxSidebar, InboxSidebarContent } from "./InboxSidebar";
export { MobileSidebar, MobileSidebarTrigger } from "./MobileSidebar";
export { NavSection } from "./NavSection";
export { PageUsageDisplay } from "./PageUsageDisplay";