feat: implement RightPanel component for tabbed navigation between Sources and Report; update report panel handling in dashboard chat page for improved user experience

This commit is contained in:
Anish Sarkar 2026-03-11 01:22:24 +05:30
parent 8b468e06da
commit 414dceff2f
9 changed files with 259 additions and 87 deletions

View file

@ -32,7 +32,7 @@ import { closeReportPanelAtom } from "@/atoms/chat/report-panel.atom";
import { membersAtom } from "@/atoms/members/members-query.atoms"; import { membersAtom } from "@/atoms/members/members-query.atoms";
import { currentUserAtom } from "@/atoms/user/user-query.atoms"; import { currentUserAtom } from "@/atoms/user/user-query.atoms";
import { Thread } from "@/components/assistant-ui/thread"; import { Thread } from "@/components/assistant-ui/thread";
import { ReportPanel } from "@/components/report-panel/report-panel"; import { MobileReportPanel } from "@/components/report-panel/report-panel";
import type { ThinkingStep } from "@/components/tool-ui/deepagent-thinking"; import type { ThinkingStep } from "@/components/tool-ui/deepagent-thinking";
import { DisplayImageToolUI } from "@/components/tool-ui/display-image"; import { DisplayImageToolUI } from "@/components/tool-ui/display-image";
import { GeneratePodcastToolUI } from "@/components/tool-ui/generate-podcast"; import { GeneratePodcastToolUI } from "@/components/tool-ui/generate-podcast";
@ -1668,7 +1668,7 @@ export default function NewChatPage() {
<div className="flex-1 flex flex-col min-w-0 overflow-hidden"> <div className="flex-1 flex flex-col min-w-0 overflow-hidden">
<Thread messageThinkingSteps={messageThinkingSteps} /> <Thread messageThinkingSteps={messageThinkingSteps} />
</div> </div>
<ReportPanel /> <MobileReportPanel />
</div> </div>
</AssistantRuntimeProvider> </AssistantRuntimeProvider>
); );

View file

@ -1,5 +1,6 @@
import { atom } from "jotai"; import { atom } from "jotai";
import { documentsSidebarOpenAtom } from "@/atoms/documents/ui.atoms"; import { documentsSidebarOpenAtom } from "@/atoms/documents/ui.atoms";
import { rightPanelCollapsedAtom, rightPanelTabAtom } from "@/atoms/layout/right-panel.atom";
interface ReportPanelState { interface ReportPanelState {
isOpen: boolean; isOpen: boolean;
@ -44,15 +45,14 @@ export const openReportPanelAtom = atom(
wordCount: wordCount ?? null, wordCount: wordCount ?? null,
shareToken: shareToken ?? null, shareToken: shareToken ?? null,
}); });
set(documentsSidebarOpenAtom, false); set(rightPanelTabAtom, "report");
set(rightPanelCollapsedAtom, false);
set(documentsSidebarOpenAtom, true);
} }
); );
/** Action atom to close the report panel */ /** Action atom to close the report panel */
export const closeReportPanelAtom = atom(null, (get, set) => { export const closeReportPanelAtom = atom(null, (_get, set) => {
const wasOpen = get(reportPanelAtom).isOpen;
set(reportPanelAtom, initialState); set(reportPanelAtom, initialState);
if (wasOpen) { set(rightPanelTabAtom, "sources");
set(documentsSidebarOpenAtom, true);
}
}); });

View file

@ -0,0 +1,8 @@
import { atom } from "jotai";
export type RightPanelTab = "sources" | "report";
export const rightPanelTabAtom = atom<RightPanelTab>("sources");
/** Whether the right panel is collapsed (hidden but state preserved) */
export const rightPanelCollapsedAtom = atom(false);

View file

@ -22,7 +22,6 @@ import {
PlusIcon, PlusIcon,
RefreshCwIcon, RefreshCwIcon,
SquareIcon, SquareIcon,
SquareLibrary,
Unplug, Unplug,
Upload, Upload,
X, X,
@ -37,7 +36,6 @@ import {
} from "@/atoms/chat/mentioned-documents.atom"; } from "@/atoms/chat/mentioned-documents.atom";
import { connectorDialogOpenAtom } from "@/atoms/connector-dialog/connector-dialog.atoms"; import { connectorDialogOpenAtom } from "@/atoms/connector-dialog/connector-dialog.atoms";
import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms"; import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
import { documentTypeCountsAtom } from "@/atoms/documents/document-query.atoms";
import { documentsSidebarOpenAtom } from "@/atoms/documents/ui.atoms"; import { documentsSidebarOpenAtom } from "@/atoms/documents/ui.atoms";
import { membersAtom } from "@/atoms/members/members-query.atoms"; import { membersAtom } from "@/atoms/members/members-query.atoms";
import { import {
@ -77,7 +75,6 @@ import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
@ -570,12 +567,6 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
const { openDialog: openUploadDialog } = useDocumentUploadDialog(); const { openDialog: openUploadDialog } = useDocumentUploadDialog();
const { data: connectors } = useAtomValue(connectorsAtom); const { data: connectors } = useAtomValue(connectorsAtom);
const connectorCount = connectors?.length ?? 0; const connectorCount = connectors?.length ?? 0;
const { data: typeCounts } = useAtomValue(documentTypeCountsAtom);
const totalDocuments = useMemo(
() => (typeCounts ? Object.values(typeCounts).reduce((sum, n) => sum + n, 0) : 0),
[typeCounts]
);
const isComposerTextEmpty = useAssistantState(({ composer }) => { const isComposerTextEmpty = useAssistantState(({ composer }) => {
const text = composer.text?.trim() || ""; const text = composer.text?.trim() || "";
return text.length === 0; return text.length === 0;
@ -631,18 +622,6 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
<Upload className="size-4 shrink-0" /> <Upload className="size-4 shrink-0" />
Upload files Upload files
</DropdownMenuItem> </DropdownMenuItem>
{totalDocuments > 0 && (
<DropdownMenuItem
onClick={() => {
setAddMenuOpen(false);
setDocumentsSidebarOpen(true);
}}
>
<SquareLibrary className="size-4 shrink-0" />
Manage Documents
</DropdownMenuItem>
)}
<DropdownMenuSeparator />
<DropdownMenuItem <DropdownMenuItem
onClick={() => { onClick={() => {
setAddMenuOpen(false); setAddMenuOpen(false);
@ -650,7 +629,7 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
}} }}
> >
<Unplug className="size-4 shrink-0" /> <Unplug className="size-4 shrink-0" />
{connectorCount > 0 ? "Manage connectors" : "Connect your tools"} {connectorCount > 0 ? "Manage tools" : "Connect your tools"}
{connectorCount > 0 && ( {connectorCount > 0 && (
<span className="ml-auto text-xs text-muted-foreground">{connectorCount}</span> <span className="ml-auto text-xs text-muted-foreground">{connectorCount}</span>
)} )}

View file

@ -1,11 +1,18 @@
"use client"; "use client";
import { useAtomValue } from "jotai"; import { useAtom, useAtomValue } from "jotai";
import { PanelRight } from "lucide-react";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { currentThreadAtom } from "@/atoms/chat/current-thread.atom"; import { currentThreadAtom } from "@/atoms/chat/current-thread.atom";
import { reportPanelAtom } from "@/atoms/chat/report-panel.atom";
import { documentsSidebarOpenAtom } from "@/atoms/documents/ui.atoms";
import { rightPanelCollapsedAtom } from "@/atoms/layout/right-panel.atom";
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { ChatHeader } from "@/components/new-chat/chat-header"; import { ChatHeader } from "@/components/new-chat/chat-header";
import { ChatShareButton } from "@/components/new-chat/chat-share-button"; import { ChatShareButton } from "@/components/new-chat/chat-share-button";
import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { useIsMobile } from "@/hooks/use-mobile";
import type { ChatVisibility, ThreadRecord } from "@/lib/chat/thread-persistence"; import type { ChatVisibility, ThreadRecord } from "@/lib/chat/thread-persistence";
interface HeaderProps { interface HeaderProps {
@ -15,6 +22,7 @@ interface HeaderProps {
export function Header({ mobileMenuTrigger }: HeaderProps) { export function Header({ mobileMenuTrigger }: HeaderProps) {
const pathname = usePathname(); const pathname = usePathname();
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom); const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
const isMobile = useIsMobile();
const isChatPage = pathname?.includes("/new-chat") ?? false; const isChatPage = pathname?.includes("/new-chat") ?? false;
@ -38,6 +46,13 @@ export function Header({ mobileMenuTrigger }: HeaderProps) {
const handleVisibilityChange = (_visibility: ChatVisibility) => {}; const handleVisibilityChange = (_visibility: ChatVisibility) => {};
const [collapsed, setCollapsed] = useAtom(rightPanelCollapsedAtom);
const documentsOpen = useAtomValue(documentsSidebarOpenAtom);
const reportState = useAtomValue(reportPanelAtom);
const reportOpen = reportState.isOpen && !!reportState.reportId;
const hasRightPanelContent = documentsOpen || reportOpen;
const showExpandButton = !isMobile && collapsed && hasRightPanelContent;
return ( return (
<header className="sticky top-0 z-10 flex h-14 shrink-0 items-center gap-2 bg-background/95 backdrop-blur supports-backdrop-filter:bg-background/60 px-4"> <header className="sticky top-0 z-10 flex h-14 shrink-0 items-center gap-2 bg-background/95 backdrop-blur supports-backdrop-filter:bg-background/60 px-4">
{/* Left side - Mobile menu trigger + Model selector */} {/* Left side - Mobile menu trigger + Model selector */}
@ -49,10 +64,26 @@ export function Header({ mobileMenuTrigger }: HeaderProps) {
</div> </div>
{/* Right side - Actions */} {/* Right side - Actions */}
<div className="flex items-center gap-4"> <div className="flex items-center gap-2">
{hasThread && ( {hasThread && (
<ChatShareButton thread={threadForButton} onVisibilityChange={handleVisibilityChange} /> <ChatShareButton thread={threadForButton} onVisibilityChange={handleVisibilityChange} />
)} )}
{showExpandButton && (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
onClick={() => setCollapsed(false)}
className="h-8 w-8 shrink-0"
>
<PanelRight className="h-4 w-4" />
<span className="sr-only">Expand panel</span>
</Button>
</TooltipTrigger>
<TooltipContent side="bottom">Expand panel</TooltipContent>
</Tooltip>
)}
</div> </div>
</header> </header>
); );

View file

@ -0,0 +1,129 @@
"use client";
import { useAtom, useAtomValue, useSetAtom } from "jotai";
import { PanelRight, PanelRightClose } from "lucide-react";
import { useEffect } from "react";
import { closeReportPanelAtom, reportPanelAtom } from "@/atoms/chat/report-panel.atom";
import { documentsSidebarOpenAtom } from "@/atoms/documents/ui.atoms";
import { rightPanelCollapsedAtom, rightPanelTabAtom } from "@/atoms/layout/right-panel.atom";
import { ReportPanelContent } from "@/components/report-panel/report-panel";
import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { DocumentsSidebar } from "../sidebar";
interface RightPanelProps {
documentsPanel?: {
open: boolean;
onOpenChange: (open: boolean) => void;
};
}
function CollapseButton({ onClick }: { onClick: () => void }) {
return (
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon" onClick={onClick} className="h-8 w-8 shrink-0">
<PanelRightClose className="h-4 w-4" />
<span className="sr-only">Collapse panel</span>
</Button>
</TooltipTrigger>
<TooltipContent side="left">Collapse panel</TooltipContent>
</Tooltip>
);
}
/**
* Absolutely positioned expand button renders at top-right of the main
* container so it occupies the same screen position as the collapse button
* inside the Documents header.
*/
export function RightPanelExpandButton() {
const [collapsed, setCollapsed] = useAtom(rightPanelCollapsedAtom);
const documentsOpen = useAtomValue(documentsSidebarOpenAtom);
const reportState = useAtomValue(reportPanelAtom);
const reportOpen = reportState.isOpen && !!reportState.reportId;
const hasContent = documentsOpen || reportOpen;
if (!collapsed || !hasContent) return null;
return (
<div className="absolute top-4 right-4 z-20">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
onClick={() => setCollapsed(false)}
className="h-8 w-8 shrink-0"
>
<PanelRight className="h-4 w-4" />
<span className="sr-only">Expand panel</span>
</Button>
</TooltipTrigger>
<TooltipContent side="left">Expand panel</TooltipContent>
</Tooltip>
</div>
);
}
export function RightPanel({ documentsPanel }: RightPanelProps) {
const [activeTab] = useAtom(rightPanelTabAtom);
const reportState = useAtomValue(reportPanelAtom);
const closeReport = useSetAtom(closeReportPanelAtom);
const [collapsed, setCollapsed] = useAtom(rightPanelCollapsedAtom);
const documentsOpen = documentsPanel?.open ?? false;
const reportOpen = reportState.isOpen && !!reportState.reportId;
// Close report on Escape key (works on Windows, macOS, and Linux)
// Must be called before early returns to satisfy Rules of Hooks.
useEffect(() => {
if (!reportOpen) return;
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
closeReport();
}
};
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, [reportOpen, closeReport]);
if (!documentsOpen && !reportOpen) return null;
if (collapsed) return null;
const effectiveTab =
activeTab === "report" && !reportOpen
? "sources"
: activeTab === "sources" && !documentsOpen
? "report"
: activeTab;
const collapseButton = <CollapseButton onClick={() => setCollapsed(true)} />;
const panelWidth = effectiveTab === "sources" ? "w-[420px]" : "w-[640px]";
return (
<aside className={`flex h-full ${panelWidth} shrink-0 flex-col border-l bg-background animate-in slide-in-from-right-4 duration-200 ease-out transition-[width] `}>
<div className="flex-1 min-h-0 overflow-hidden">
{effectiveTab === "sources" && documentsOpen && documentsPanel && (
<DocumentsSidebar
open={documentsPanel.open}
onOpenChange={documentsPanel.onOpenChange}
embedded
headerAction={collapseButton}
/>
)}
{effectiveTab === "report" && reportOpen && (
<div className="flex h-full flex-col">
<ReportPanelContent
reportId={reportState.reportId!}
title={reportState.title || "Report"}
onClose={closeReport}
shareToken={reportState.shareToken}
/>
</div>
)}
</div>
</aside>
);
}

View file

@ -10,6 +10,7 @@ import { useSidebarResize } from "../../hooks/useSidebarResize";
import type { ChatItem, NavItem, PageUsage, SearchSpace, User } from "../../types/layout.types"; import type { ChatItem, NavItem, PageUsage, SearchSpace, User } from "../../types/layout.types";
import { Header } from "../header"; import { Header } from "../header";
import { IconRail } from "../icon-rail"; import { IconRail } from "../icon-rail";
import { RightPanel } from "../right-panel/RightPanel";
import { import {
AllPrivateChatsSidebar, AllPrivateChatsSidebar,
AllSharedChatsSidebar, AllSharedChatsSidebar,
@ -275,46 +276,36 @@ export function LayoutShell({
{/* Main container with sidebar and content - relative for inbox positioning */} {/* Main container with sidebar and content - relative for inbox positioning */}
<div className="relative flex flex-1 rounded-xl border bg-background overflow-hidden"> <div className="relative flex flex-1 rounded-xl border bg-background overflow-hidden">
<Sidebar <Sidebar
searchSpace={searchSpace} searchSpace={searchSpace}
isCollapsed={isCollapsed} isCollapsed={isCollapsed}
onToggleCollapse={toggleCollapsed} onToggleCollapse={toggleCollapsed}
navItems={navItems} navItems={navItems}
onNavItemClick={onNavItemClick} onNavItemClick={onNavItemClick}
chats={chats} chats={chats}
sharedChats={sharedChats} sharedChats={sharedChats}
activeChatId={activeChatId} activeChatId={activeChatId}
onNewChat={onNewChat} onNewChat={onNewChat}
onChatSelect={onChatSelect} onChatSelect={onChatSelect}
onChatRename={onChatRename} onChatRename={onChatRename}
onChatDelete={onChatDelete} onChatDelete={onChatDelete}
onChatArchive={onChatArchive} onChatArchive={onChatArchive}
onViewAllSharedChats={onViewAllSharedChats} onViewAllSharedChats={onViewAllSharedChats}
onViewAllPrivateChats={onViewAllPrivateChats} onViewAllPrivateChats={onViewAllPrivateChats}
user={user} user={user}
onSettings={onSettings} onSettings={onSettings}
onManageMembers={onManageMembers} onManageMembers={onManageMembers}
onUserSettings={onUserSettings} onUserSettings={onUserSettings}
onLogout={onLogout} onLogout={onLogout}
pageUsage={pageUsage} pageUsage={pageUsage}
theme={theme} theme={theme}
setTheme={setTheme} setTheme={setTheme}
className="hidden md:flex border-r shrink-0" className="hidden md:flex border-r shrink-0"
isLoadingChats={isLoadingChats} isLoadingChats={isLoadingChats}
sidebarWidth={sidebarWidth} sidebarWidth={sidebarWidth}
onResizeMouseDown={onResizeMouseDown} onResizeMouseDown={onResizeMouseDown}
isResizing={isResizing} isResizing={isResizing}
/> />
{/* Docked Documents Sidebar - renders as flex sibling between sidebar and content */}
{documentsPanel?.isDocked && (
<DocumentsSidebar
open={documentsPanel.open}
onOpenChange={documentsPanel.onOpenChange}
isDocked={documentsPanel.isDocked}
onDockedChange={documentsPanel.onDockedChange}
/>
)}
<main className="flex-1 flex flex-col min-w-0"> <main className="flex-1 flex flex-col min-w-0">
<Header /> <Header />
@ -324,6 +315,16 @@ export function LayoutShell({
</div> </div>
</main> </main>
{/* Right panel — tabbed Sources/Report (desktop only) */}
{documentsPanel && (
<RightPanel
documentsPanel={{
open: documentsPanel.open,
onOpenChange: documentsPanel.onOpenChange,
}}
/>
)}
{/* Inbox Sidebar - slide-out panel */} {/* Inbox Sidebar - slide-out panel */}
{inbox && ( {inbox && (
<InboxSidebar <InboxSidebar
@ -335,16 +336,6 @@ export function LayoutShell({
/> />
)} )}
{/* Documents Sidebar - floating slide-out panel (non-docked mode) */}
{documentsPanel && !documentsPanel.isDocked && (
<DocumentsSidebar
open={documentsPanel.open}
onOpenChange={documentsPanel.onOpenChange}
isDocked={false}
onDockedChange={documentsPanel.onDockedChange}
/>
)}
{/* Announcements Sidebar */} {/* Announcements Sidebar */}
{announcementsPanel && ( {announcementsPanel && (
<AnnouncementsSidebar <AnnouncementsSidebar

View file

@ -30,7 +30,11 @@ const SHOWCASE_CONNECTORS = [
{ type: "GOOGLE_GMAIL_CONNECTOR", label: "Gmail" }, { type: "GOOGLE_GMAIL_CONNECTOR", label: "Gmail" },
{ type: "NOTION_CONNECTOR", label: "Notion" }, { type: "NOTION_CONNECTOR", label: "Notion" },
{ type: "YOUTUBE_CONNECTOR", label: "YouTube" }, { type: "YOUTUBE_CONNECTOR", label: "YouTube" },
{ type: "GOOGLE_CALENDAR_CONNECTOR", label: "Google Calendar" },
{ type: "SLACK_CONNECTOR", label: "Slack" }, { type: "SLACK_CONNECTOR", label: "Slack" },
{ type: "LINEAR_CONNECTOR", label: "Linear" },
{ type: "JIRA_CONNECTOR", label: "Jira" },
{ type: "GITHUB_CONNECTOR", label: "GitHub" },
] as const; ] as const;
interface DocumentsSidebarProps { interface DocumentsSidebarProps {
@ -38,6 +42,10 @@ interface DocumentsSidebarProps {
onOpenChange: (open: boolean) => void; onOpenChange: (open: boolean) => void;
isDocked?: boolean; isDocked?: boolean;
onDockedChange?: (docked: boolean) => void; onDockedChange?: (docked: boolean) => void;
/** When true, renders content without any wrapper — parent provides the container */
embedded?: boolean;
/** Optional action element rendered in the header row (e.g. collapse button) */
headerAction?: React.ReactNode;
} }
export function DocumentsSidebar({ export function DocumentsSidebar({
@ -45,6 +53,8 @@ export function DocumentsSidebar({
onOpenChange, onOpenChange,
isDocked = false, isDocked = false,
onDockedChange, onDockedChange,
embedded = false,
headerAction,
}: DocumentsSidebarProps) { }: DocumentsSidebarProps) {
const t = useTranslations("documents"); const t = useTranslations("documents");
const tSidebar = useTranslations("sidebar"); const tSidebar = useTranslations("sidebar");
@ -168,8 +178,8 @@ export function DocumentsSidebar({
const documentsContent = ( const documentsContent = (
<> <>
<div className="shrink-0 p-4 pb-10"> <div className="shrink-0 flex h-14 items-center px-4">
<div className="flex items-center justify-between"> <div className="flex w-full items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{isMobile && ( {isMobile && (
<Button <Button
@ -214,12 +224,13 @@ export function DocumentsSidebar({
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
)} )}
{headerAction}
</div> </div>
</div> </div>
</div> </div>
{/* Connected tools strip */} {/* Connected tools strip */}
<div className="shrink-0 mx-4 mb-3 flex select-none items-center gap-2 rounded-lg border bg-muted/50 px-3 py-2"> <div className="shrink-0 mx-4 mt-2 mb-3 flex select-none items-center gap-2 rounded-lg border bg-muted/50 px-3 py-2">
<button <button
type="button" type="button"
onClick={() => setConnectorDialogOpen(true)} onClick={() => setConnectorDialogOpen(true)}
@ -278,6 +289,14 @@ export function DocumentsSidebar({
</> </>
); );
if (embedded) {
return (
<div className="flex h-full flex-col bg-sidebar text-sidebar-foreground">
{documentsContent}
</div>
);
}
if (isDocked && open && !isMobile) { if (isDocked && open && !isMobile) {
return ( return (
<aside <aside

View file

@ -95,9 +95,9 @@ function ReportPanelSkeleton() {
} }
/** /**
* Inner content component used by both desktop panel and mobile drawer * Inner content component used by desktop panel, mobile drawer, and the layout right panel
*/ */
function ReportPanelContent({ export function ReportPanelContent({
reportId, reportId,
title, title,
onClose, onClose,
@ -579,3 +579,18 @@ export function ReportPanel() {
return <MobileReportDrawer />; return <MobileReportDrawer />;
} }
/**
* MobileReportPanel mobile-only report drawer
*
* Used in the dashboard chat page where the desktop report is handled
* by the layout-level RightPanel instead.
*/
export function MobileReportPanel() {
const panelState = useAtomValue(reportPanelAtom);
const isDesktop = useMediaQuery("(min-width: 1024px)");
if (isDesktop || !panelState.isOpen || !panelState.reportId) return null;
return <MobileReportDrawer />;
}