Skip to main content

Chat Conversation List Block

The Chat Conversation List Component is a fully customizable and accessible conversation listing interface built with React and TypeScript. It provides a complete conversation overview with search functionality, infinite scrolling, and flexible customization options for chat application interfaces.


πŸš€ Installation​

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

πŸ“– Usage​

import {ChatConversationList} from '@nodeblocks/frontend-chat-conversation-list-block';
Live Editor
function SimpleChatConversationList() {
  const [conversations] = useState([
    {
      id: 'conv-001',
      title: 'Customer Support',
      content: 'Thank you for contacting us. How can we help you today?',
      dateTime: '2025-02-01T10:00:00Z',
      titleLines: 1,
      unreadCount: 3,
      href: '/chat/conv-001',
      avatar: {
        sx: {bgcolor: 'primary.main'},
      },
    },
    {
      id: 'conv-002',
      title: 'Project Team Discussion',
      content: 'The new feature implementation is ready for review.',
      dateTime: '2025-02-01T09:30:00Z',
      titleLines: 2,
      unreadCount: 1,
      href: '/chat/conv-002',
      avatar: {
        sx: {bgcolor: 'secondary.main'},
      },
    },
    {
      id: 'conv-003',
      title: 'Marketing Campaign',
      content: "Let's schedule a meeting to discuss the campaign strategy.",
      dateTime: '2025-01-31T16:45:00Z',
      titleLines: 1,
      href: '/chat/conv-003',
      avatar: {
        sx: {bgcolor: 'info.main'},
      },
    },
  ]);

  return (
    <ChatConversationList
      conversations={conversations}
      labels={{
        heading: 'Conversations',
        emptyState: 'No conversations yet',
      }}
      placeholders={{
        search: 'Search conversations...',
      }}
      search={{
        defaultValue: '',
        onChange: (value) => {
          console.log('Search changed:', value);
        },
      }}
      onNavigate={(url) => {
        console.log('Navigate to:', url);
      }}
      onScrollBottom={() => {
        console.log('Load more conversations');
      }}
      isLoading={false}
    >
      <ChatConversationList.Heading />
      <ChatConversationList.SearchBar />
      <ChatConversationList.ScrollPanel />
    </ChatConversationList>
  );
}
Result
Loading...

πŸ”§ Props Reference​

Main Component Props​

PropTypeDefaultDescription
conversationsConversation[]RequiredArray of conversation objects to display
labels{heading?: string; emptyState?: string;}undefinedText labels configuration
placeholders{search?: string;}undefinedPlaceholder text configuration
isLoadingbooleanundefinedShow loading state
onNavigate(url: string) => voidRequiredNavigation callback (required for href)
onScrollBottom() => voidundefinedCallback when the bottom of the list is reached
search{defaultValue?: string; onChange?: (value: string) => void;}undefinedSearch input configuration
size'compact' | 'normal'undefinedConfigures text size and spacing (sub-components default to 'compact')
classNamestringundefinedAdditional CSS class name for styling
sxSxProps<Theme>undefinedMUI system props for custom styling
childrenBlocksOverrideundefinedCustom block components to override default rendering

Note: This component inherits all MUI Stack component props.

Conversation Object​

PropTypeDefaultDescription
idstringRequiredUnique identifier for the conversation
titlestringRequiredTitle of the conversation
titleLines1 | 2RequiredNumber of lines before the title is cut off
contentstringundefinedConversation body (will be cut off after one line)
dateTimestringundefinedDate time as ISO string (formatted using date-fns with Japanese locale)
unreadCountnumberundefinedUnread count (>99 will be displayed as "99...")
hrefstringundefinedMakes conversation linkable and adds hover state
isSelectedbooleanundefinedWhen true, highlight the conversation
avatarPartial<AvatarProps> & {isLoading?: boolean}undefinedMUI Avatar props with optional loading state

Sub-Components​

The ChatConversationList component provides several sub-components. All sub-components receive their default values from the main component's context and can override these values through props.

ChatConversationList.Heading​

Renders the conversation list heading using MUI Typography.

PropTypeDefaultDescription
labels{heading?: string;}Context valuesLabels configuration (overrides context)
size'compact' | 'normal''compact'Size variant (overrides context)
classNamestringundefinedAdditional CSS class name for styling
childrenReactNodeDefault heading contentCustom content to override default heading
componentElementType'h1'HTML element to render
variantTypographyVariant'h4'MUI Typography variant
sxSxProps<Theme>undefinedMUI system props for custom styling

Note: This component inherits all MUI Typography component props.

ChatConversationList.SearchBar​

Renders the search input with debounced search functionality.

PropTypeDefaultDescription
search{defaultValue?: string; onChange?: (value: string) => void;}Context valuesSearch configuration (overrides context)
placeholders{search?: string;}Context valuesPlaceholder configuration (overrides context)
conversationsConversation[]Context valuesConversations to determine visibility (overrides context)
size'compact' | 'normal''compact'Size variant (overrides context)
debounceTimenumber500Delay in milliseconds before triggering search onChange
classNamestringundefinedAdditional CSS class name for styling
defaultValuestringundefinedInitial value for the search input
placeholderstringundefinedPlaceholder text when empty
variantTextFieldVariant'outlined'MUI TextField variant
typestring'search'HTML input type
fullWidthbooleantrueWhether input takes full width (ignored when size='normal')
sxSxProps<Theme>undefinedMUI system props for custom styling

Note: This component inherits all MUI TextField component props (except onChange and size).

ChatConversationList.Header​

A convenience component that combines Heading and SearchBar with optional divider.

PropTypeDefaultDescription
size'compact' | 'normal'Context valuesSize variant (overrides context)
classNamestringundefinedAdditional CSS class name for styling
childrenReactNodeDefault header contentCustom content to override default header
sxSxProps<Theme>undefinedMUI system props for custom styling

Note: This component inherits all MUI Stack component props (except direction).

ChatConversationList.ScrollPanel​

Renders the scrollable list of conversations with infinite scroll support.

PropTypeDefaultDescription
conversationsConversation[]Context valuesConversations array (overrides context)
labels{heading?: string; emptyState?: string;}Context valuesLabels configuration (overrides context)
isLoadingbooleanContext valuesLoading state (overrides context)
onScrollBottom() => voidContext valuesScroll callback (overrides context)
size'compact' | 'normal''compact'Size variant (overrides context)
classNamestringundefinedAdditional CSS class name for styling
childrenReactNodeDefault conversation itemsCustom content to override default rendering
sxSxProps<Theme>undefinedMUI system props for custom styling

Note: This component inherits all MUI Stack component props (except direction and spacing).


🎨 Configuration examples​

The component uses MUI Avatar props for avatar customization. Here's how to configure avatars:

const conversation = {
id: 'conv-001',
title: 'Support Team',
titleLines: 1,
avatar: {
// MUI Avatar props
src: '/path/to/image.jpg', // Image source
srcSet: '/path/to/image@2x.jpg 2x', // Responsive images
sx: { bgcolor: 'primary.main' }, // Custom styling
children: 'ST', // Fallback content (initials)

// Extended props
isLoading: false, // Show loading placeholder
},
};

πŸ”§ TypeScript Support​

Full TypeScript support with comprehensive type definitions:

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

interface LabelsConfig {
heading?: string;
emptyState?: string;
}

interface SearchConfig {
defaultValue?: string;
onChange?: (value: string) => void;
}

interface PlaceholdersConfig {
search?: string;
}

interface Conversation {
/** Unique identifier for the conversation */
id: string;
/** Title of the conversation */
title: string;
/** Amount of lines before the title is cut off */
titleLines: 1 | 2;
/** Conversation body (will be cut off after one line) */
content?: string;
/** Date time as ISO string (formatted using date-fns) */
dateTime?: string;
/** Unread count (>99 will be cut off at 99) */
unreadCount?: number;
/** Makes conversation linkable and adds hover state */
href?: string;
/** When true, highlight the conversation */
isSelected?: boolean;
/** MUI Avatar props with optional loading state */
avatar?: Partial<AvatarProps> & {
/** Show a temporary loading state */
isLoading?: boolean;
};
}

// Complete typed example with search and pagination
function TypedChatConversationList() {
const [conversations, setConversations] = useState<Conversation[]>([
{
id: 'support-001',
title: 'Customer Support Team',
content: 'Your issue has been resolved. Is there anything else we can help you with?',
dateTime: '2025-02-01T14:30:00Z',
titleLines: 1,
unreadCount: 2,
href: '/chat/support-001',
isSelected: false,
avatar: {
sx: {bgcolor: 'primary.main'},
src: '/avatars/support-team.jpg',
},
},
{
id: 'team-002',
title: 'Development Team Discussion',
content: 'The code review is complete. Ready to merge the new feature branch.',
dateTime: '2025-02-01T13:15:00Z',
titleLines: 2,
unreadCount: 5,
href: '/chat/team-002',
isSelected: true,
avatar: {
sx: {bgcolor: 'secondary.main'},
},
},
]);

const [isLoading, setIsLoading] = useState(false);
const [searchValue, setSearchValue] = useState('');
const [filteredConversations, setFilteredConversations] = useState(conversations);

const handleSearch = (value: string) => {
setSearchValue(value);

if (!value.trim()) {
setFilteredConversations(conversations);
return;
}

const filtered = conversations.filter(
conv =>
conv.title.toLowerCase().includes(value.toLowerCase()) ||
conv.content?.toLowerCase().includes(value.toLowerCase()),
);

setFilteredConversations(filtered);
};

const handleScrollBottom = () => {
console.log('Loading more conversations...');
setIsLoading(true);

setTimeout(() => {
const newConversations: Conversation[] = [
{
id: `conv-${Date.now()}`,
title: 'New Conversation',
content: 'This is a newly loaded conversation.',
dateTime: new Date().toISOString(),
titleLines: 1,
href: `/chat/conv-${Date.now()}`,
avatar: {
sx: {bgcolor: 'warning.main'},
},
},
];

setConversations(prev => [...prev, ...newConversations]);
if (!searchValue.trim()) {
setFilteredConversations(prev => [...prev, ...newConversations]);
}
setIsLoading(false);
}, 1500);
};

const handleNavigate = (url: string) => {
console.log('Navigating to:', url);
setConversations(prev =>
prev.map(conv => ({
...conv,
isSelected: conv.href === url,
})),
);
};

return (
<ChatConversationList
conversations={filteredConversations}
labels={{
heading: 'Chat Conversations',
emptyState: searchValue ? 'No conversations match your search' : 'No conversations yet',
}}
placeholders={{
search: 'Search by name or message...',
}}
search={{
defaultValue: searchValue,
onChange: handleSearch,
}}
onNavigate={handleNavigate}
onScrollBottom={handleScrollBottom}
isLoading={isLoading}
className="custom-conversation-list"
sx={{maxHeight: '500px', border: '1px solid #e0e0e0'}}
>
<ChatConversationList.Heading className="custom-heading" />
<ChatConversationList.SearchBar className="custom-search-bar" debounceTime={300} />
<ChatConversationList.ScrollPanel className="custom-scroll-panel" />
</ChatConversationList>
);
}

πŸ“ Notes​

  • Dates are formatted using the date-fns library with Japanese locale (ja) showing relative time (e.g., "5εˆ†ε‰")
  • The search input is debounced by default (500ms) to prevent excessive callbacks
  • Special characters in search input are automatically sanitized
  • The search bar only appears when there are conversations or when the user has interacted with search
  • When size='normal' is used, the layout adjusts for a wider display with different spacing
  • Infinite scroll is implemented using IntersectionObserver for efficient detection
  • Selected conversations are highlighted with a light blue background (#e0f3fc)

Built with ❀️ using React, TypeScript, MUI, and date-fns.