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
π Usageβ
import {ChatConversationList} from '@nodeblocks/frontend-chat-conversation-list-block';
- Basic Usage
- Advanced Usage
function BasicChatConversationList() { const [conversations, setConversations] = 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: { avatarColor: '1', avatarSize: 'medium', }, }, { 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: { avatarColor: '2', avatarSize: 'medium', }, }, { 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: { avatarColor: '3', avatarSize: 'medium', }, }, ]); 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> ); }
function AdvancedChatConversationList() { const [conversations, setConversations] = useState([ { 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: 5, href: '/chat/support-001', isSelected: false, avatar: { avatarColor: '1', avatarSize: 'medium', avatarUrl: '/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: 2, href: '/chat/team-002', isSelected: true, avatar: { avatarColor: '2', avatarSize: 'medium', }, }, { id: 'client-003', title: 'Client Project Updates', content: 'Meeting scheduled for tomorrow at 2 PM to discuss project timeline.', dateTime: '2025-02-01T11:45:00Z', titleLines: 1, href: '/chat/client-003', avatar: { avatarColor: '3', avatarSize: 'medium', avatarUrl: '/avatars/client.jpg', }, }, ]); return ( <ChatConversationList conversations={conversations} labels={{ heading: 'Team Conversations', emptyState: 'No conversations match your search', }} placeholders={{ search: 'Search by name or message...', }} search={{ defaultValue: '', onChange: (value) => { console.log('Search changed:', value); }, }} onNavigate={(url) => { console.log('Navigate to:', url); }} onScrollBottom={() => { console.log('Load more conversations'); }} isLoading={false} className="custom-conversation-list" style={{ maxHeight: '500px', border: '1px solid #e0e0e0' }}> {({ defaultBlocks, defaultBlockOrder }) => ({ blocks: { ...defaultBlocks, // π― Custom heading with enhanced styling heading: { ...defaultBlocks.heading, props: { ...defaultBlocks.heading.props, className: 'custom-heading', style: { backgroundColor: '#f0f8ff', borderBottom: '2px solid #007bff', padding: '20px 16px', fontWeight: 'bold', }, }, }, // π Enhanced search bar with custom styling serachBar: { ...defaultBlocks.serachBar, props: { ...defaultBlocks.serachBar.props, className: 'custom-search-bar', debounceTime: 300, style: { backgroundColor: '#e7f3ff', borderBottom: '1px solid #b3d9ff', }, }, }, // π Custom stats bar above conversations statsBar: ( <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '12px 16px', backgroundColor: '#f8f9fa', borderBottom: '1px solid #dee2e6', fontSize: '14px', color: '#6c757d', }}> <span>π {conversations.length} conversations</span> <span>π {conversations.reduce((acc, conv) => acc + (conv.unreadCount || 0), 0)} unread</span> </div> ), // π¬ Custom scroll panel with enhanced styling scrollPanel: { ...defaultBlocks.scrollPanel, props: { ...defaultBlocks.scrollPanel.props, className: 'custom-scroll-panel', style: { backgroundColor: '#fafafa', borderRadius: '8px', maxHeight: '400px', }, }, }, // π¨ Custom footer with actions footer: ( <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', padding: '16px', backgroundColor: '#f8f9fa', borderTop: '1px solid #dee2e6', }}> <button style={{ padding: '8px 16px', backgroundColor: '#007bff', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer', }}> β Start New Conversation </button> </div> ), }, blockOrder: ['heading', 'serachBar', 'statsBar', 'scrollPanel', 'footer'], })} </ChatConversationList> ); }
π§ Props Referenceβ
Main Component Propsβ
The main ChatConversationList
component inherits all props from the Spacing
component and adds:
Prop | Type | Default | Description |
---|---|---|---|
labels | {heading?: string; emptyState?: string;} | undefined | Text labels configuration |
placeholders | {search?: string;} | undefined | Placeholder text configuration |
isLoading | boolean | undefined | Show loading state |
onNavigate | (url: string) => void | Required | Navigation callback (required for href) |
onScrollBottom | () => void | undefined | Callback when the bottom of the list is reached |
search | {defaultValue?: string; onChange?: (value: string) => void;} | undefined | Search input configuration |
conversations | Conversation[] | Required | Array of conversation objects to display |
className | string | undefined | Additional CSS class name for styling |
children | BlocksOverride | undefined | Custom block components to override default rendering |
Conversation Objectβ
Prop | Type | Default | Description |
---|---|---|---|
id | string | Required | Unique identifier for the conversation |
title | string | Required | Title of the conversation |
content | string | undefined | Conversation body (will be cut off after one line) |
dateTime | string | undefined | Date time as ISO string (will be formatted by component) |
titleLines | 1 | 2 | Required | Number of lines before the title is cut off |
unreadCount | number | undefined | Unread count (>99 will be cut off at 99) |
href | string | undefined | Makes conversation linkable and adds hover state |
isSelected | boolean | undefined | When true, highlight the conversation |
avatar | AvatarProps | undefined | Avatar configuration |
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.
Prop | Type | Default | Description |
---|---|---|---|
labels | {heading?: string;} | Context values | Labels configuration (overrides context) |
className | string | undefined | Additional CSS class name for styling |
children | ReactNode | Default heading content | Custom content to override default heading |
Note: This component inherits all HTML div
element props.
ChatConversationList.SearchBarβ
Renders the search input with debounced search functionality.
Prop | Type | Default | Description |
---|---|---|---|
search | {defaultValue?: string; onChange?: (value: string) => void;} | Context values | Search configuration object (overrides context values) |
placeholders | {search?: string;} | Context values | Placeholder text configuration (overrides context values) |
conversations | Conversation[] | Context values | Array of conversations to determine search bar visibility (overrides context) |
debounceTime | number | 500 | Delay in milliseconds before triggering search onChange callback |
className | string | undefined | Additional CSS class name for styling the search container |
defaultValue | string | undefined | Initial value for the search input field |
placeholder | string | undefined | Placeholder text displayed when search input is empty |
disabled | boolean | undefined | Whether the search input is disabled |
id | string | undefined | HTML id attribute for the search input |
name | string | undefined | HTML name attribute for the search input |
autoComplete | string | undefined | HTML autocomplete attribute for the search input |
autoFocus | boolean | undefined | Whether to automatically focus the search input on mount |
ChatConversationList.ScrollPanelβ
Renders the scrollable list of conversations with infinite scroll support.
Prop | Type | Default | Description |
---|---|---|---|
conversations | Conversation[] | Context values | Conversations array (overrides context) |
labels | {heading?: string; emptyState?: string;} | Context values | Labels configuration (overrides context) |
isLoading | boolean | Context values | Loading state (overrides context) |
onScrollBottom | () => void | Context values | Scroll callback (overrides context) |
className | string | undefined | Additional CSS class name for styling |
children | ReactNode | Default conversation items | Custom content to override default conversation rendering |
Note: This component inherits all HTML div
element props.
π§ TypeScript Supportβ
Full TypeScript support with comprehensive type definitions:
import {ChatConversationList} from '@nodeblocks/frontend-chat-conversation-list-block';
import {AvatarProps} from '@basaldev/blocks-frontend-framework';
import {useState} from 'react';
interface LabelsConfig {
heading?: string;
emptyState?: string;
}
interface SearchConfig {
defaultValue?: string;
onChange?: (value: string) => void;
}
interface PlaceholdersConfig {
search?: string;
}
interface Conversation {
id: string;
title: string;
content?: string;
dateTime?: string;
titleLines: 1 | 2;
unreadCount?: number;
href?: string;
isSelected?: boolean;
avatar?: AvatarProps;
}
// Advanced conversation list with search and infinite scroll
function AdvancedChatConversationList() {
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: {
avatarColor: '1',
avatarSize: 'medium',
avatarUrl: '/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: {
avatarColor: '2',
avatarSize: 'medium',
},
},
{
id: 'client-003',
title: 'Client Project Updates',
content: 'Meeting scheduled for tomorrow at 2 PM to discuss project timeline.',
dateTime: '2025-02-01T11:45:00Z',
titleLines: 1,
href: '/chat/client-003',
avatar: {
avatarColor: '3',
avatarSize: 'medium',
avatarUrl: '/avatars/client.jpg',
},
},
]);
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);
// Simulate API call
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: {
avatarColor: '4',
avatarSize: 'medium',
},
},
];
setConversations(prev => [...prev, ...newConversations]);
if (!searchValue.trim()) {
setFilteredConversations(prev => [...prev, ...newConversations]);
}
setIsLoading(false);
}, 1500);
};
const handleNavigate = (url: string) => {
console.log('Navigating to:', url);
// Update selected conversation
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"
style={{ 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>
);
}
Built with β€οΈ using React, TypeScript, and modern web standards.