import { lazy, Suspense, useRef, type ComponentProps } from 'react'; import { StatusBar } from './StatusBar'; import { Sidebar } from './Sidebar'; import { ConversationPane } from './ConversationPane'; import { NewMessageModal } from './NewMessageModal'; import { ContactInfoPane } from './ContactInfoPane'; import { ChannelInfoPane } from './ChannelInfoPane'; import { Toaster } from './ui/sonner'; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from './ui/sheet'; import { SETTINGS_SECTION_ICONS, SETTINGS_SECTION_LABELS, SETTINGS_SECTION_ORDER, type SettingsSection, } from './settings/settingsConstants'; import { getContrastTextColor, type LocalLabel } from '../utils/localLabel'; import type { CrackerPanelProps } from './CrackerPanel'; import type { SearchViewProps } from './SearchView'; import type { SettingsModalProps } from './SettingsModal'; import { cn } from '@/lib/utils'; const SettingsModal = lazy(() => import('./SettingsModal').then((m) => ({ default: m.SettingsModal })) ); const CrackerPanel = lazy(() => import('./CrackerPanel').then((m) => ({ default: m.CrackerPanel })) ); const SearchView = lazy(() => import('./SearchView').then((m) => ({ default: m.SearchView }))); type SidebarProps = ComponentProps; type ConversationPaneProps = ComponentProps; type NewMessageModalProps = Omit, 'open' | 'onClose'>; type ContactInfoPaneProps = ComponentProps; type ChannelInfoPaneProps = ComponentProps; interface AppShellProps { localLabel: LocalLabel; showNewMessage: boolean; showSettings: boolean; settingsSection: SettingsSection; sidebarOpen: boolean; showCracker: boolean; onSettingsSectionChange: (section: SettingsSection) => void; onSidebarOpenChange: (open: boolean) => void; onCrackerRunningChange: (running: boolean) => void; onToggleSettingsView: () => void; onCloseSettingsView: () => void; onCloseNewMessage: () => void; onLocalLabelChange: (label: LocalLabel) => void; statusProps: Pick, 'health' | 'config'>; sidebarProps: SidebarProps; conversationPaneProps: ConversationPaneProps; searchProps: SearchViewProps; settingsProps: Omit< SettingsModalProps, 'open' | 'pageMode' | 'externalSidebarNav' | 'desktopSection' | 'onClose' | 'onLocalLabelChange' >; crackerProps: Omit; newMessageModalProps: NewMessageModalProps; contactInfoPaneProps: ContactInfoPaneProps; channelInfoPaneProps: ChannelInfoPaneProps; } export function AppShell({ localLabel, showNewMessage, showSettings, settingsSection, sidebarOpen, showCracker, onSettingsSectionChange, onSidebarOpenChange, onCrackerRunningChange, onToggleSettingsView, onCloseSettingsView, onCloseNewMessage, onLocalLabelChange, statusProps, sidebarProps, conversationPaneProps, searchProps, settingsProps, crackerProps, newMessageModalProps, contactInfoPaneProps, channelInfoPaneProps, }: AppShellProps) { const searchMounted = useRef(false); if (conversationPaneProps.activeConversation?.type === 'search') { searchMounted.current = true; } const crackerMounted = useRef(false); if (showCracker) { crackerMounted.current = true; } const settingsSidebarContent = ( ); const activeSidebarContent = showSettings ? ( settingsSidebarContent ) : ( ); return (
Skip to content {localLabel.text && (
{localLabel.text}
)} onSidebarOpenChange(true)} />
{activeSidebarContent}
{ event.preventDefault(); }} > Navigation Sidebar navigation
{activeSidebarContent}
{searchMounted.current && (
Loading search...
} >
)} {showSettings && (
Loading settings...
} >
)}
{ if (showCracker && el) { const focusable = el.querySelector('input, button:not([disabled])'); if (focusable) { setTimeout(() => focusable.focus(), 210); } } }} className={cn( 'border-t border-border bg-background transition-all duration-200 overflow-hidden', showCracker ? 'h-[275px]' : 'h-0' )} > {crackerMounted.current && ( Loading cracker...
} > )} { newMessageModalProps.onSelectConversation(conv); onCloseNewMessage(); }} /> ); }