"use client"; 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"; import { cn } from "@/lib/utils"; import { SidebarProvider, useSidebarState } from "../../hooks"; import { useSidebarResize } from "../../hooks/useSidebarResize"; import type { ChatItem, NavItem, PageUsage, SearchSpace, User } from "../../types/layout.types"; import { Header } from "../header"; import { IconRail } from "../icon-rail"; import { RightPanel } from "../right-panel/RightPanel"; import { AllPrivateChatsSidebarContent, AllSharedChatsSidebarContent, AnnouncementsSidebarContent, DocumentsSidebar, InboxSidebarContent, MobileSidebar, MobileSidebarTrigger, Sidebar, } from "../sidebar"; import { SidebarSlideOutPanel } from "../sidebar/SidebarSlideOutPanel"; // Per-tab data source interface TabDataSource { items: InboxItem[]; unreadCount: number; loading: boolean; loadingMore: boolean; hasMore: boolean; loadMore: () => void; markAsRead: (id: number) => Promise; markAllAsRead: () => Promise; } export type ActiveSlideoutPanel = "inbox" | "shared" | "private" | "announcements" | null; // Inbox-related props — per-tab data sources with independent loading/pagination interface InboxProps { isOpen: boolean; totalUnreadCount: number; comments: TabDataSource; status: TabDataSource; } interface LayoutShellProps { searchSpaces: SearchSpace[]; activeSearchSpaceId: number | null; onSearchSpaceSelect: (id: number) => void; onSearchSpaceDelete?: (searchSpace: SearchSpace) => void; onSearchSpaceSettings?: (searchSpace: SearchSpace) => void; onAddSearchSpace: () => void; searchSpace: SearchSpace | null; navItems: NavItem[]; onNavItemClick?: (item: NavItem) => void; chats: ChatItem[]; sharedChats?: ChatItem[]; activeChatId?: number | null; onNewChat: () => void; onChatSelect: (chat: ChatItem) => void; onChatRename?: (chat: ChatItem) => void; onChatDelete?: (chat: ChatItem) => void; onChatArchive?: (chat: ChatItem) => void; onViewAllSharedChats?: () => void; onViewAllPrivateChats?: () => void; user: User; onSettings?: () => void; onManageMembers?: () => void; onUserSettings?: () => void; onLogout?: () => void; pageUsage?: PageUsage; theme?: string; setTheme?: (theme: "light" | "dark" | "system") => void; defaultCollapsed?: boolean; isChatPage?: boolean; children: React.ReactNode; className?: string; // Unified slide-out panel state activeSlideoutPanel?: ActiveSlideoutPanel; onSlideoutPanelChange?: (panel: ActiveSlideoutPanel) => void; // Inbox props inbox?: InboxProps; isLoadingChats?: boolean; // All chats panel props allSharedChatsPanel?: { searchSpaceId: string; }; allPrivateChatsPanel?: { searchSpaceId: string; }; documentsPanel?: { open: boolean; onOpenChange: (open: boolean) => void; isDocked?: boolean; onDockedChange?: (docked: boolean) => void; }; } export function LayoutShell({ searchSpaces, activeSearchSpaceId, onSearchSpaceSelect, onSearchSpaceDelete, onSearchSpaceSettings, onAddSearchSpace, searchSpace, navItems, onNavItemClick, chats, sharedChats, activeChatId, onNewChat, onChatSelect, onChatRename, onChatDelete, onChatArchive, onViewAllSharedChats, onViewAllPrivateChats, user, onSettings, onManageMembers, onUserSettings, onLogout, pageUsage, theme, setTheme, defaultCollapsed = false, isChatPage = false, children, className, activeSlideoutPanel = null, onSlideoutPanelChange, inbox, isLoadingChats = false, allSharedChatsPanel, allPrivateChatsPanel, documentsPanel, }: LayoutShellProps) { const isMobile = useIsMobile(); const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const { isCollapsed, setIsCollapsed, toggleCollapsed } = useSidebarState(defaultCollapsed); const { sidebarWidth, handleMouseDown: onResizeMouseDown, isDragging: isResizing, } = useSidebarResize(); // Memoize context value to prevent unnecessary re-renders const sidebarContextValue = useMemo( () => ({ isCollapsed, setIsCollapsed, toggleCollapsed, sidebarWidth }), [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 (
setMobileMenuOpen(true)} />} />
{children}
{/* Mobile unified slide-out panel */} {activeSlideoutPanel === "inbox" && inbox && ( closeSlideout(open)} comments={inbox.comments} status={inbox.status} totalUnreadCount={inbox.totalUnreadCount} onCloseMobileSidebar={() => setMobileMenuOpen(false)} /> )} {activeSlideoutPanel === "announcements" && ( closeSlideout(open)} onCloseMobileSidebar={() => setMobileMenuOpen(false)} /> )} {activeSlideoutPanel === "shared" && allSharedChatsPanel && ( closeSlideout(open)} searchSpaceId={allSharedChatsPanel.searchSpaceId} onCloseMobileSidebar={() => setMobileMenuOpen(false)} /> )} {activeSlideoutPanel === "private" && allPrivateChatsPanel && ( closeSlideout(open)} searchSpaceId={allPrivateChatsPanel.searchSpaceId} onCloseMobileSidebar={() => setMobileMenuOpen(false)} /> )} {/* Mobile Documents Sidebar - separate (not part of slide-out group) */} {documentsPanel && ( )}
); } // Desktop layout return (
{/* Sidebar + slide-out panels share one container; overflow visible so panels can overlay main content */} {/* Resize handle — negative margins eat the flex gap so spacing stays unchanged */} {!isCollapsed && (
)} {/* Main content panel */}
{children}
{/* Right panel — tabbed Sources/Report (desktop only) */} {documentsPanel && ( )}
); }