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.1.3
π 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.