Skip to main content

Overview

The ChatApp component serves as the root container for the WhatsApp-style chat application. It manages the global chat state using Zustand, handles mobile/desktop layout switching, and coordinates communication between the sidebar and chat panel.

Usage

import { ChatApp } from "@/components/chat/chat-app"

export default function Page() {
  return <ChatApp />
}

Features

  • Responsive layout with mobile/desktop adaptation
  • Global state management for chats, contacts, and messages
  • Chat sorting by pinned status and last activity
  • Auto-simulation of incoming messages via useChatSimulator hook
  • Empty state when no conversation is selected

Props

The ChatApp component takes no props. All state is managed internally via the useChatStore hook.

State management

The component consumes the following state from the chat store:
chats
Record<string, Chat>
Dictionary of all chat conversations indexed by chat ID
contacts
Record<string, Contact>
Dictionary of all contacts indexed by contact ID
messages
Record<string, Message>
Dictionary of all messages indexed by message ID
activeChatId
string | undefined
The ID of the currently active chat conversation
drafts
Record<string, DraftMessage>
Draft messages for each chat, persisted across sessions
typingIndicators
TypingIndicator[]
Array of active typing indicators showing who is currently typing

Layout behavior

Desktop layout

On desktop (viewport width >= 768px), both the sidebar and chat panel are visible side-by-side:
  • Sidebar: fixed width (max-w-sm)
  • Chat panel: flexible width (flex-1)

Mobile layout

On mobile devices, the layout switches between views:
  • When no chat is selected: shows sidebar only
  • When a chat is active: shows chat panel only with a back button
// Mobile layout logic from source
const content = (
  <div className="flex h-screen w-full overflow-hidden bg-app-surface">
    <div className={cn(
      "hidden h-full w-full max-w-sm shrink-0 border-border/60 md:flex",
      (!isMobile || !activeChatId) && "flex"
    )}>
      <ChatSidebar {...sidebarProps} />
    </div>
    
    <div className={cn(
      "flex h-full flex-1 bg-background",
      isMobile && !activeChatId && "hidden"
    )}>
      <ChatPanel {...chatPanelProps} />
    </div>
  </div>
)

Chat sorting

Chats are automatically sorted by:
  1. Pinned status - Pinned chats appear first
  2. Last activity - Most recent activity appears at the top
// Sorting implementation from source
function sortChats(chats: Chat[], contacts: Record<string, Contact>) {
  return [...chats].sort((a, b) => {
    const contactA = contacts[a.contactId]
    const contactB = contacts[b.contactId]
    const pinnedA = contactA?.pinned ? 1 : 0
    const pinnedB = contactB?.pinned ? 1 : 0
    if (pinnedA !== pinnedB) {
      return pinnedB - pinnedA
    }
    return a.lastActivityAt > b.lastActivityAt ? -1 : 1
  })
}

Empty state

When no conversation is selected, an empty state is displayed:
function EmptyConversationState() {
  return (
    <div className="flex flex-1 flex-col items-center justify-center gap-4 bg-gradient-to-br from-secondary/60 via-background to-background">
      <div className="rounded-full bg-primary/10 p-6">
        <span className="text-4xl">💬</span>
      </div>
      <div className="max-w-sm text-center">
        <h2 className="text-lg font-semibold text-foreground">Select a conversation</h2>
        <p className="mt-1 text-sm text-muted-foreground">
          Choose a chat to read messages, share updates, and keep conversations moving.
        </p>
      </div>
    </div>
  )
}

Build docs developers (and LLMs) love