import { useEffect, useState } from 'react'; import { Outlet, useParams } from 'react-router-dom'; import { ChatListColumn } from '@/features/messages/ChatListColumn'; import { DetailColumn } from '@/features/messages/DetailColumn'; import { HistorySearchModal } from '@/features/messages/HistorySearchModal'; import { OfflineBanner } from '@/features/messages/OfflineBanner'; import type { MediaType } from '@/lib/api/messages'; import { useMediaQuery } from '@/lib/hooks'; import { useChatList } from '@/lib/tinode/hooks'; /** * The 4-tier responsive split described in the design handoff: * - <640 and 640-959: list OR main, whichever the route implies (topicId present -> main; the * conversation header then needs its own back-to-list affordance, since NavRail alone doesn't * provide one at this tier -- see ConversationView). * - 960-1279: list + main together, detail as an overlay drawer toggled by the header's "i" icon. * - >=1280: list + main + detail all docked side by side. * Below 960px (both the two-column and single-column tiers), the detail panel instead pushes as a * full-page overlay -- README §1.4's "详情/设置等作为整页推入" convention -- since there's no room * to dock or drawer it in without covering the conversation entirely anyway. */ export function MessagesLayout() { const { topicId } = useParams(); const [detailOpen, setDetailOpen] = useState(false); const [historyTab, setHistoryTab] = useState<'text' | MediaType | null>(null); const isDocked = useMediaQuery('(min-width: 1280px)'); const showListOnNarrow = !topicId; const contacts = useChatList(); const peerName = contacts.find((c) => c.topic === topicId)?.public?.fn || topicId || ''; // The "i" button should read as active whenever the detail panel is actually visible right now -- // always true when docked (>=1280px, no close affordance), otherwise whatever the drawer/full-page // toggle state is. Previously hardcoded to the breakpoint itself instead of this real state. const detailVisible = isDocked || detailOpen; // MessagesLayout is one long-lived instance reused across every conversation (route is // `:topicId`) -- a history-search modal left open while switching topics would otherwise keep // showing results for whichever topic it was opened against, not the one now on screen. useEffect(() => setHistoryTab(null), [topicId]); return (
setDetailOpen((v) => !v), detailVisible, onOpenHistory: setHistoryTab }} />
{topicId ? (
) : null} {detailOpen && topicId ? (
setDetailOpen(false)} />
setDetailOpen(false)} onOpenHistory={setHistoryTab} />
) : null} {detailOpen && topicId ? (
setDetailOpen(false)} onOpenHistory={setHistoryTab} />
) : null} {historyTab && topicId ? setHistoryTab(null)} /> : null}
); }