Skip to main content

Chat Conversation Block

The Chat Conversation Component is a fully customizable and accessible chat interface built with React and TypeScript. It provides a complete conversation view with message display, input handling, and flexible customization options for real-time messaging applications.


πŸš€ Installation​

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

πŸ“– Usage​

import {ChatConversation} from '@nodeblocks/frontend-chat-conversation-block';
Live Editor
function SimpleChatConversation() {
  const [messages, setMessages] = useState([
    {
      title: 'John Doe',
      content: 'Hello! How are you doing today?',
      id: 'message-001',
      createdAt: '2025-02-01T10:00:00Z',
      isOwnMessage: false,
      avatar: {
        sx: {bgcolor: 'primary.main'},
      },
    },
    {
      title: 'You',
      content: 'Hi! I am doing great, thanks for asking!',
      id: 'message-002',
      createdAt: '2025-02-01T10:01:00Z',
      isOwnMessage: true,
    },
  ]);

  return (
    <ChatConversation
      chatView={{
        heading: {
          avatar: {
            sx: {bgcolor: 'secondary.main'},
          },
          buttonHref: '/home',
        },
        isLoading: false,
      }}
      onInputChange={(text) => {
        console.log('Input changed:', text);
      }}
      onMessageSubmit={(text) => {
        console.log('Message submitted:', text);
        setMessages(prev => [
          ...prev,
          {
            title: 'You',
            content: text,
            id: 'message-' + Date.now(),
            createdAt: new Date().toISOString(),
            isOwnMessage: true,
          },
        ]);
      }}
      commentInput={{
        isDisabled: false,
      }}
      commentSubmitButton={{
        isDisabled: false,
      }}
      messages={messages}
      labels={{
        chatViewHeadingButtonText: 'Home',
        chatViewHeadingText: 'Conversation',
      }}
      onNavigate={(url) => {
        console.log('Navigate to:', url);
      }}
      placeholders={{
        commentInput: 'Type a message...',
      }}
    >
      <ChatConversation.Heading />
      <ChatConversation.Body />
    </ChatConversation>
  );
}
Result
Loading...

πŸ”§ Props Reference​

Main Component Props​

PropTypeDefaultDescription
onMessageSubmit(text: string) => voidundefinedCallback when submitting a message
onInputChange(text: string) => voidundefinedCallback on comment input change
commentInputCommentInputConfigundefinedConfiguration for the comment input at the bottom
commentSubmitButton{isDisabled?: boolean;}undefinedConfiguration for the comment submit button
chatViewChatViewConfigRequiredChat panel configuration
messagesMessage[]RequiredArray of messages to display in the chat view
labelsLabelsConfigRequiredLabel text configuration
onNavigate(url: string) => voidRequiredNavigation callback (required for href)
placeholders{commentInput: string}RequiredPlaceholder text configuration
classNamestringundefinedAdditional CSS class name for styling
childrenBlocksOverrideundefinedCustom block components to override default rendering
sxSxProps<Theme>undefinedMUI system props for custom styling

Note: This component inherits all MUI Stack component props (e.g., direction, spacing, divider, etc.).

Sub-Components​

The ChatConversation 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.

ChatConversation.Heading​

PropTypeDefaultDescription
labelsLabelsConfigContext valuesLabel configuration (overrides context)
chatViewChatViewConfigContext valuesChat view configuration (overrides context)
onNavigate(url: string) => voidContext valuesNavigation callback (overrides context)
classNamestringundefinedAdditional CSS class name for styling
childrenReactNodeDefault heading contentCustom content to override default heading
directionStackDirection"row"Flex direction for the heading layout
spacingnumber | string2Spacing between heading elements
sxSxProps<Theme>undefinedMUI system props for custom styling

Note: This component inherits all MUI Stack component props.

ChatConversation.Body​

PropTypeDefaultDescription
onMessageSubmit(text: string) => voidContext valuesMessage submit callback (overrides context)
onInputChange(text: string) => voidContext valuesInput change callback (overrides context)
commentInputCommentInputConfigContext valuesComment input configuration (overrides context)
commentSubmitButton{isDisabled?: boolean;}Context valuesSubmit button configuration (overrides context)
chatViewChatViewConfigContext valuesChat view configuration (overrides context)
messagesMessage[]Context valuesMessages array (overrides context)
onNavigate(url: string) => voidContext valuesNavigation callback (overrides context)
placeholders{commentInput: string}Context valuesPlaceholder configuration (overrides context)
classNamestringundefinedAdditional CSS class name for styling
sxSxProps<Theme>undefinedMUI system props for custom styling

Note: This component accepts MUI Box component props for styling purposes.


🎨 Configuration examples​

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

Message Avatar​

const message = {
id: 'msg-001',
content: 'Hello!',
createdAt: new Date().toISOString(),
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: 'JD', // Fallback content (initials)

// Extended props
href: '/users/john-doe', // Navigation link (requires onNavigate)
isLoading: false, // Show loading placeholder

// For link behavior
component: 'a', // Render as anchor tag
},
};

Heading Avatar​

<ChatConversation
chatView={{
heading: {
avatar: {
src: '/team-avatar.jpg',
sx: {width: 32, height: 32},
isLoading: false,
// For clickable avatar
component: 'a',
href: '/team-profile',
},
buttonHref: '/home',
},
}}
// ...other props
/>

πŸ”§ TypeScript Support​

Full TypeScript support with comprehensive type definitions:

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

interface LabelsConfig {
chatViewHeadingButtonText?: string;
chatViewHeadingText?: string;
}

interface CommentInputConfig {
/** Disables input (while submitting or similar) */
isDisabled?: boolean;
/** Error message to show below the comment input field */
errorText?: string;
}

interface ChatViewConfig {
heading?: {
/** MUI Avatar props with optional isLoading state */
avatar?:
| (Partial<AvatarProps> & { isLoading?: boolean })
| (Partial<AvatarProps<'a'>> & { isLoading?: boolean; component: 'a' });
buttonHref: string;
};
/**
* Loading state for either overall view or for previous messages
* (depends on whether messages is empty)
*/
isLoading?: boolean;
/**
* Callback when the user has scrolled up and reached the top of the chat view.
* This can be used to fetch more messages from the server.
*/
onScrollTop?: (earliestMessageId: string) => void;
}

// Message interface
interface Message {
avatar?: {
sx?: Record<string, unknown>;
src?: string;
href?: string;
isLoading?: boolean;
};
content: string;
createdAt: string;
id: string;
isOwnMessage?: boolean;
title?: string;
}

// Complete typed example
function TypedChatConversation() {
const [messages, setMessages] = useState<Message[]>([
{
title: 'Support Agent',
content: 'Hello! How can I help you today?',
id: 'msg-001',
createdAt: '2025-02-01T09:00:00Z',
isOwnMessage: false,
avatar: {
sx: {bgcolor: 'info.main'},
src: '/path/to/agent-avatar.jpg',
},
},
]);

const [inputValue, setInputValue] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [inputError, setInputError] = useState<string | undefined>();

const handleInputChange = (text: string) => {
setInputValue(text);

if (text.length > 500) {
setInputError('Message too long (max 500 characters)');
} else {
setInputError(undefined);
}
};

const handleMessageSubmit = async (text: string) => {
if (!text.trim()) return;

setIsSubmitting(true);

const userMessage: Message = {
title: 'You',
content: text,
id: `msg-${Date.now()}`,
createdAt: new Date().toISOString(),
isOwnMessage: true,
};

setMessages(prev => [...prev, userMessage]);
setInputValue('');

try {
await new Promise(resolve => setTimeout(resolve, 2000));

const responseMessage: Message = {
title: 'Support Agent',
content: 'Thank you for your message. Let me help you with that.',
id: `msg-${Date.now()}-response`,
createdAt: new Date().toISOString(),
isOwnMessage: false,
avatar: {
sx: {bgcolor: 'info.main'},
},
};

setMessages(prev => [...prev, responseMessage]);
} catch (error) {
console.error('Failed to send message:', error);
} finally {
setIsSubmitting(false);
}
};

const handleScrollTop = (earliestMessageId: string) => {
console.log('Loading more messages before:', earliestMessageId);
};

return (
<ChatConversation
chatView={{
heading: {
avatar: {
sx: {bgcolor: 'secondary.main', width: 32, height: 32},
src: '/path/to/avatar.jpg',
},
buttonHref: '/support/dashboard',
},
isLoading: isSubmitting,
onScrollTop: handleScrollTop,
}}
onInputChange={handleInputChange}
onMessageSubmit={handleMessageSubmit}
commentInput={{
isDisabled: isSubmitting,
errorText: inputError,
}}
commentSubmitButton={{
isDisabled: isSubmitting || Boolean(inputError) || !inputValue.trim(),
}}
messages={messages}
labels={{
chatViewHeadingButtonText: 'Back to Dashboard',
chatViewHeadingText: 'Customer Support',
}}
onNavigate={(url) => {
window.location.href = url;
}}
placeholders={{
commentInput: 'Type your message here...',
}}
className="custom-chat-container"
sx={{maxHeight: '600px'}}
>
<ChatConversation.Heading className="custom-chat-header" />
<ChatConversation.Body className="custom-chat-body" />
</ChatConversation>
);
}

πŸ“ Notes​

  • Messages are grouped by date using the Luxon library with Japanese date format (yyyyεΉ΄MM月ddζ—₯)
  • When a new message is added, the chat view automatically scrolls to the bottom
  • The onScrollTop callback is triggered when the user scrolls to the top, useful for pagination
  • Pressing Enter (without Shift) submits the message; Shift+Enter creates a new line
  • The component uses MUI's theming system for consistent styling

Built with ❀️ using React, TypeScript, MUI, and Luxon.