Skip to main content

Chat Conversation List Block

ChatConversationList is a responsive, highly customizable inbox listing view built on MUI Typography and Avatar elements. It supports a built-in search bar, subcomponent-level debounce control, unread badge counters, lazy-loading/scroll-to-bottom callback hooks, and a responsive two-column split-pane layout.

Installation

npm install @nodeblocks/frontend-chat-conversation-list-block

What You Need

ItemWhy it matters
conversationsArray of inbox conversation entries (id, title, content, dateTime, unreadCount, etc.)
onNavigateCallback triggered when clicking a conversation item
labels (optional)Text mapping for list headings and empty states
search (optional)Stateful search configurations containing callbacks (onChange) and default values
renderDesktopPane (optional)Render prop to inject a right-hand detail pane (like ChatConversation) on md and wider viewports
Controlled component

ChatConversationList is a controlled, presentational component and does not manage its own active thread state, search filtering, or pagination lists internally. Keep the active conversations array and the selected thread index/ID in your parent component's state, filter items in response to search.onChange, and manage loading indicators with the isLoading prop.

Code Examples

Live Editor
function Example() {
  const conversations = [
    {
      id: '1',
      title: 'Customer Support',
      content: 'Thank you for contacting us. How can we help you?',
      dateTime: new Date(Date.now() - 3 * 60 * 1000).toISOString(),
      titleLines: 1,
      unreadCount: 3,
      href: '#support',
      avatar: {children: 'CS', sx: {bgcolor: 'primary.main'}},
    },
    {
      id: '2',
      title: 'Project Discussion Group',
      content: 'The review slides are ready for feedback.',
      dateTime: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
      titleLines: 2,
      unreadCount: 0,
      href: '#team',
      avatar: {children: 'PD', sx: {bgcolor: 'secondary.main'}},
    },
  ];
  const [searchValue, setSearchValue] = React.useState('');
  const [lastEvent, setLastEvent] = React.useState('Ready');
  const visibleConversations = conversations.filter((conversation) => {
    const haystack = `${conversation.title} ${conversation.content ?? ''}`.toLowerCase();
    return !searchValue || haystack.includes(searchValue.toLowerCase());
  });

  return (
    <div style={{height: 400, border: '1px solid #e0e0e0', borderRadius: 8, overflow: 'hidden'}}>
      <ChatConversationList
        conversations={visibleConversations}
        labels={{
          heading: 'Conversations',
          emptyState: 'No chats found',
        }}
        placeholders={{
          search: 'Search conversations...',
        }}
        search={{
          defaultValue: '',
          onChange: val => {
            setSearchValue(val);
            setLastEvent(`Search key: ${val}`);
          },
        }}
        onNavigate={url => setLastEvent(`Routing to: ${url}`)}
      />
      <div style={{marginTop: 8, fontSize: 12, color: '#666'}}>{lastEvent}</div>
    </div>
  );
}
Result
Loading...

Important Props

Core Props

PropTypeRequiredDefaultDescription
conversationsArray<{ id: string; title: string; titleLines: 1 | 2; content?: string; dateTime?: string; href?: string; isSelected?: boolean; unreadCount?: number; avatar?: Partial<AvatarProps> & { isLoading?: boolean } }>Yes-Array of inbox conversation thread records rendered inside the list
onNavigate(url: string) => voidYes-Navigation callback triggered when a conversation with href is clicked
isLoadingbooleanNofalseRenders a loading spinner inside the scroll panel when true
onScrollBottom() => voidNoundefinedCallback triggered when scrolling to bottom of list (used for lazy-load list appending)

Conversation Object

Configure properties inside each conversation object in the conversations array:

PropertyTypeRequiredDefaultDescription
idstringYes-Unique conversation thread record identifier
titlestringYes-Heading label title of the conversation thread
titleLines1 | 2Yes-Maximum title lines displayed before truncation and ellipsis
contentstringNoundefinedMain message preview body (cut off after 1 line)
dateTimestringNoundefinedISO datetime string formatted by formatRelativeDateText
hrefstringNoundefinedRouting anchor link passed to onNavigate callback on row click
isSelectedbooleanNofalseHighlights active selection when true
unreadCountnumberNoundefinedDisplays unread count badge indicator (values >99 display as 99...)
avatarPartial<AvatarProps> & { isLoading?: boolean }NoundefinedNative MUI Avatar props + loading state indicator placeholder; linked avatars can be configured via component="a"

Content Props

PropTypeRequiredDefaultDescription
labels{ heading?: string; emptyState?: string }NoundefinedCustom inbox heading text and empty inbox indicator text
placeholders{ search?: string }NoundefinedSearch text input placeholders
search{ defaultValue?: string; onChange?: (value: string) => void }NoundefinedStateful search controls containing initial values and change callbacks
renderDesktopPane() => ReactNodeNoundefinedRender prop to load a split-pane layout to the right on md and wider viewports
formatRelativeDateText(dateTime: string) => stringNo(dateTime) => DateTime.fromISO(dateTime).toRelative() ?? ''Custom formatter for each row's ISO dateTime preview

Layout and Composition Props

PropTypeRequiredDefaultDescription
childrenBlocksOverride | ReactNodeNoundefinedCompound child elements or override function child
classNamestringNoundefinedStyling class applied on the outer container
sxSxPropsNoundefinedMUI SX styling overrides passed to the outer Stack

ChatConversationList inherits all StackProps (except children). defaultBlockOrder is ['heading', 'searchBar', 'header', 'scrollPanel', 'item']; default root rendering filters standalone heading, searchBar, and item, then renders header and scrollPanel.

Default UI Blocks

BlockBuilt onNotes
ChatConversationList (root)StackStacks the default header and scroll panel vertically
ChatConversationList.HeaderStackWrapper holding Heading and SearchBar child sections
ChatConversationList.HeadingTypographyPresents the main listing header
ChatConversationList.SearchBarTextFieldSearch input with debounceTime defaulting to 500 ms and an icon adornment
ChatConversationList.ScrollPanelStackScrolling viewport mapping list items, loading spinners, and empty states
ChatConversationList.ConversationItemLink + AvatarPresents titles, content previews, unread badges, and dates

TypeScript

import {ChatConversationList} from '@nodeblocks/frontend-chat-conversation-list-block';
import type {AvatarProps} from '@mui/material';

type Conversation = {
id: string;
title: string;
titleLines: 1 | 2;
content?: string;
dateTime?: string;
href?: string;
isSelected?: boolean;
unreadCount?: number;
avatar?: Partial<AvatarProps> & {
isLoading?: boolean;
};
};

const conversations: Conversation[] = [
{
id: 'thread-1',
title: 'Customer support',
content: 'Ticket resolved successfully.',
dateTime: '2026-05-27T02:00:00Z',
titleLines: 1,
unreadCount: 1,
avatar: {children: 'CS', sx: {bgcolor: 'primary.main'}},
},
];

<ChatConversationList
conversations={conversations}
labels={{
heading: 'Inbox',
emptyState: 'No threads active',
}}
placeholders={{
search: 'Filter threads...',
}}
search={{
onChange: val => void val,
}}
onNavigate={url => void url}
/>;