チャット会話ブロック
ChatConversation は、MUI Stack と TextField 入力に基づく、レスポンシブで高度にカスタマイズ可能なメッセージングインターフェースです。日付ごとにグループ化されたメッセージストリーム、モバイル向けに最適化されたオーバーフローメニュー、無限スクロールのページネーションフック、レイアウト構成をサポートします。
インストール
- npm
- yarn
- pnpm
- bun
npm install @nodeblocks/frontend-chat-conversation-block
yarn add @nodeblocks/frontend-chat-conversation-block
pnpm add @nodeblocks/frontend-chat-conversation-block
bun add @nodeblocks/frontend-chat-conversation-block
必要なもの
| 項目 | 理由 |
|---|---|
messages | 表示するメッセージバブルの配列。ブロックは createdAt でグループ化し、並べ替えます |
chatView | 見出し、アバター、モバイル用オーバーフローメニュー、ローダー表示を制御するチャットパネル設定 |
labels | 見出しタイトル、任意のサブ見出し、デスクトップのアクションボタン用テキストラベル |
placeholders | メッセージコメント入力などの入力プレースホルダー |
onNavigate | ナビゲーション見出しをクリックしたり、アバターをリンクしたりしたときに呼び出されるコールバックフック |
ChatConversation は表示専用で、アクティブな状態リストは管理しません。アプリケーション内でローカルの messages 状態を維持し、onMessageSubmit 内で配列を更新し、chatView.isLoading でページ読み込みスピナーを切り替えてください。
コード例
- クイックスタート
- ラベルとURL
- コンパウンドコンポーネント
- ブロックのオーバーライド
function Example() { const [messages, setMessages] = React.useState([ { id: 'msg-1', title: 'John Doe', content: 'Hey! Are we still on for the project review today?', createdAt: new Date(Date.now() - 3600000).toISOString(), isOwnMessage: false, }, { id: 'msg-2', title: 'You', content: 'Yes! I have the slides ready.', createdAt: new Date(Date.now() - 1800000).toISOString(), isOwnMessage: true, }, ]); const [isLoading, setIsLoading] = React.useState(false); const [lastEvent, setLastEvent] = React.useState('Ready'); const handleMessageSubmit = text => { setLastEvent(`Sent: ${text}`); // Add user message const userMsg = { id: `msg-${Date.now()}`, title: 'You', content: text, createdAt: new Date().toISOString(), isOwnMessage: true, }; setMessages(prev => [...prev, userMsg]); // Simulate reply setIsLoading(true); setTimeout(() => { setMessages(prev => [ ...prev, { id: `reply-${Date.now()}`, title: 'John Doe', content: 'Awesome, see you in the meeting room!', createdAt: new Date().toISOString(), isOwnMessage: false, avatar: {children: 'JD'}, }, ]); setIsLoading(false); }, 1500); }; return ( <div style={{height: 500, border: '1px solid #e0e0e0', borderRadius: 8, overflow: 'hidden'}}> <ChatConversation chatView={{ heading: { avatar: {children: 'JD'}, buttonHref: '#home', }, isLoading: isLoading, }} labels={{ chatViewHeadingButtonText: 'Home', chatViewHeadingText: 'Project Chat', chatViewHeadingSubText: 'Review team', }} placeholders={{ commentInput: 'Type your message...', }} messages={messages} onMessageSubmit={handleMessageSubmit} onNavigate={url => setLastEvent(`Navigating to: ${url}`)} /> <div style={{marginTop: 8, fontSize: 12, color: '#666'}}>{lastEvent}</div> </div> ); }
アクティブなローディングインジケーター、カスタムクリックアクション付きのモバイル用オーバーフローメニュー、戻るボタン、入力プレースホルダーを設定します。
function Example() { const [messages, setMessages] = React.useState([ { id: 'msg-1', title: 'Support Agent', content: 'Hello! How can I help you today?', createdAt: new Date(Date.now() - 3600000).toISOString(), isOwnMessage: false, avatar: {children: 'SA', sx: {bgcolor: 'secondary.main'}}, }, ]); const [lastEvent, setLastEvent] = React.useState('Ready'); const handleMessageSubmit = text => { setLastEvent(`Sent: ${text}`); setMessages(prev => [ ...prev, { id: `msg-${Date.now()}`, title: 'You', content: text, createdAt: new Date().toISOString(), isOwnMessage: true, }, ]); }; const chatViewConfig = { heading: { avatar: {children: 'SA', sx: {bgcolor: 'secondary.main'}}, buttonHref: '/support/dashboard', onBack: () => setLastEvent('Back clicked on mobile'), menuItems: [ { label: 'View Profile', icon: <PersonOutlined fontSize="small" />, onClick: () => setLastEvent('Viewing agent profile'), }, { label: 'Close Ticket', icon: <NotificationsNone fontSize="small" />, onClick: () => setLastEvent('Ticket closed'), }, ], }, isLoading: false, }; return ( <div style={{height: 500, border: '1px solid #e0e0e0', borderRadius: 8, overflow: 'hidden'}}> <ChatConversation chatView={chatViewConfig} labels={{ chatViewHeadingButtonText: 'Support', chatViewHeadingText: 'Customer Support', chatViewHeadingSubText: 'Ticket #14052', }} placeholders={{ commentInput: 'Describe your issue...', }} messages={messages} onMessageSubmit={handleMessageSubmit} onNavigate={url => setLastEvent(`Redirecting to: ${url}`)} /> <div style={{marginTop: 8, fontSize: 12, color: '#666'}}>{lastEvent}</div> </div> ); }
chatView.heading.menuItems を使って、スレッドを閉じる、共有する、フラグを付けるといったカスタムのモバイルアクションを指定します。labels.chatViewHeadingSubText の下に表示されるサブ見出し文字列は、モバイルでは自然に2行目へ折り返され、デスクトップレイアウトでは標準のタイトル行としてまとまります。
細かいコンパウンド子ブロックを使ってレイアウトを構成します。ルートには引き続き必要なコンテキスト props が渡され、子の props で特定のセクションを上書きできます。
function Example() { const [messages, setMessages] = React.useState([ { id: 'msg-1', title: 'John Doe', content: 'Hey! Here is the link to the design: Figma.com/file/123', createdAt: new Date().toISOString(), isOwnMessage: false, }, ]); const [lastEvent, setLastEvent] = React.useState('Ready'); return ( <div style={{height: 500, border: '1px solid #e0e0e0', borderRadius: 8, overflow: 'hidden'}}> <ChatConversation chatView={{ heading: { avatar: {children: 'JD'}, buttonHref: '#back', }, }} labels={{ chatViewHeadingButtonText: 'Back', chatViewHeadingText: 'Design Thread', }} placeholders={{ commentInput: 'Reply to design thread...', }} messages={messages} onNavigate={url => setLastEvent(`Navigating: ${url}`)} > <ChatConversation.Heading chatView={{ heading: { avatar: {children: 'JD'}, buttonHref: '#back', }, }} labels={{ chatViewHeadingButtonText: 'Back', chatViewHeadingText: 'Design Thread', }} /> <ChatConversation.Body messages={messages} placeholders={{ commentInput: 'Reply to design thread...', }} onMessageSubmit={text => { setLastEvent(`Sent: ${text}`); setMessages(prev => [ ...prev, { id: String(Date.now()), title: 'You', content: text, createdAt: new Date().toISOString(), isOwnMessage: true, }, ]); }} /> </ChatConversation> <div style={{marginTop: 8, fontSize: 12, color: '#666'}}>{lastEvent}</div> </div> ); }
標準の関数型 children オーバーライドのブロックマッピングを使って、ブロックの並び替えや、見出しと会話セクションの間へのシステム通知アラートの挿入ができます。
function Example() { const [messages, setMessages] = React.useState([ { id: 'msg-1', title: 'Bot', content: 'Welcome! How can I assist you today?', createdAt: new Date().toISOString(), isOwnMessage: false, }, ]); return ( <div style={{height: 500, border: '1px solid #e0e0e0', borderRadius: 8, overflow: 'hidden'}}> <ChatConversation chatView={{ heading: { buttonHref: '#home', }, }} labels={{ chatViewHeadingButtonText: 'Exit', chatViewHeadingText: 'Assistant Bot', }} placeholders={{ commentInput: 'Type a query...', }} messages={messages} onMessageSubmit={text => { setMessages(prev => [ ...prev, { id: String(Date.now()), title: 'You', content: text, createdAt: new Date().toISOString(), isOwnMessage: true, }, ]); }} onNavigate={url => void url} > {({defaultBlocks, defaultBlockOrder}) => ({ blocks: { ...defaultBlocks, systemAlert: ( <div style={{ background: '#fffbe6', borderBottom: '1px solid #ffe58f', padding: '8px 16px', fontSize: '12px', color: '#d46b08', textAlign: 'center', }} > This is a secure automated workspace assistant session. </div> ), }, blockOrder: ['heading', 'systemAlert', 'body'], })} </ChatConversation> <div style={{marginTop: 8, fontSize: 12, color: '#666'}}>System notice inserted between blocks.</div> </div> ); }
ブロックのオーバーライドを使うタイミング
オーバーライドは、会話表示領域の先頭にバナー、システムステータス、セキュリティ開示、添付アクション用トレイを追加するのに使います。デフォルトのブロック順は ['heading', 'body'] (defaultBlockOrder) です。
重要なプロパティ
コアプロパティ
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
messages | Array<{ id: string; content: string; createdAt: string; title?: string; isOwnMessage?: boolean; avatar?: object }> | はい | - | createdAt でグループ化・並べ替えされたチャットメッセージレコードの配列 |
onMessageSubmit | (text: string) => void | いいえ | undefined | テキストフィールドから入力を送信したときに呼び出されるコールバック |
onInputChange | (text: string) => void | いいえ | undefined | コメント入力テキストが編集されたときに呼び出されるコールバック |
メッセージオブジェクト
messages 配列内のプロパティを設定します:
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
id | string | はい | - | 一意のメッセージレコード識別子 |
content | string | はい | - | チャットバブル内に表示される主要なコンテンツテキスト |
createdAt | string | はい | - | 日区切りのグループ化に使う日時タイムスタンプ(ISO文字列形式) |
isOwnMessage | boolean | いいえ | false | true のとき、自分のコメントを強調し、バブルを右端に配置します |
title | string | いいえ | undefined | チャットバブルの上に表示する作成者ヘッダータイトル |
avatar | object | いいえ | undefined | 個別コメントの Avatar 設定。MUI Avatar の props に加え、任意の href、isLoading、リンク/クリック動作(component="a" 対応)を含みます |
コンテンツプロパティ
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
chatView | { heading?: { avatar?: object; buttonHref: string; onBack?: () => void; menuItems?: Array<{ label: string; href?: string; icon?: ReactNode; onClick?: (e: MouseEvent<HTMLElement>) => void }> }; isLoading?: boolean; onScrollTop?: (earliestId: string) => void } | はい | - | 見出し、モバイルメニュー、戻る操作、無限スクロールを設定するパネルレイアウト。見出しアバターは component="a" + href によるリンク付きアバターを含む標準の MUI Avatar props を受け付けます |
labels | { chatViewHeadingButtonText?: string; chatViewHeadingText?: string; chatViewHeadingSubText?: string } | はい | - | メイン見出しラベルと終了ボタンのテキスト |
placeholders | { commentInput: string } | はい | - | 入力欄の文言設定 |
onNavigate | (url: string) => void | はい | - | クリックされた要素や設定をマッピングする標準のリダイレクトフック |
commentInput | { isDisabled?: boolean; errorText?: string } | いいえ | undefined | 下部コメント入力の設定 |
commentSubmitButton | { isDisabled?: boolean } | いいえ | undefined | 送信ボタンの有効状態 |
chatView の構造
chatView 内でレイアウト見出しインジケーターを設定します:
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
heading | { avatar?: object; buttonHref: string; onBack?: () => void; menuItems?: ChatConversationHeadingMenuItem[] } | いいえ | undefined | 見出しアクションの設定。heading がある場合は buttonHref が必須です。 |
isLoading | boolean | いいえ | false | データ取得時に上部のページネーションローダーや全画面スピナーを表示します |
onScrollTop | (earliestId: string) => void | いいえ | undefined | 先頭までスクロールしたときに呼び出されるコールバック(過去履歴の読み込みに使用) |
メニュー項目オブジェクト
見出しのオーバーフロードロップダウンメニューリスト(menuItems)のオプション設定:
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
label | string | はい | - | メニュー項目に表示されるラベルテキスト |
href | string | いいえ | undefined | onNavigate によってマッピングされる遷移先 |
icon | ReactNode | いいえ | undefined | メニュー項目ラベルの先頭に表示されるアイコン要素 |
onClick | (e: MouseEvent<HTMLElement>) => void | いいえ | undefined | 項目をクリックしたときに呼び出されるハンドラ |
レイアウトと構成のプロパティ
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
children | BlocksOverride | ReactNode | いいえ | undefined | コンパウンド子要素またはオーバーライド関数の子 |
className | string | いいえ | undefined | 外側コンテナに適用されるスタイルクラス |
sx | SxProps | いいえ | undefined | 外側の Stack に渡される MUI SX スタイル上書き |
ChatConversation は StackProps(children を除く)をすべて継承します。デフォルトのブロック順は ['heading', 'body'] (defaultBlockOrder) です。
デフォルト UI ブロック
| ブロック | 基盤 | 備考 |
|---|---|---|
ChatConversation (root) | Stack | 見出しと本文のパネルを縦に積み重ねます |
ChatConversation.Heading | Stack | 戻るボタン、アバター詳細、結合タイトル、終了ボタンを表示します |
ChatConversation.Body | Stack | メッセージを日ごとにグループ化し、createdAt で並べ替え、onScrollTop 境界を発火し、入力欄をマウントします |
TypeScript
import {ChatConversation} from '@nodeblocks/frontend-chat-conversation-block';
import type {MouseEvent, ReactNode} from 'react';
type ChatMessage = {
id: string;
content: string;
createdAt: string;
title?: string;
isOwnMessage?: boolean;
avatar?: {
component?: 'a';
onClick?: (event: MouseEvent<HTMLElement>) => void;
children?: ReactNode;
src?: string;
srcSet?: string;
href?: string;
isLoading?: boolean;
sx?: object;
};
};
type MenuItem = {
label: string;
href?: string;
icon?: ReactNode;
onClick?: (event: MouseEvent<HTMLElement>) => void;
};
const messages: ChatMessage[] = [
{
id: 'msg_001',
title: 'Customer support',
content: 'We are processing your request. Please hold on.',
createdAt: '2026-05-27T02:00:00Z',
isOwnMessage: false,
avatar: {children: 'CS', sx: {bgcolor: 'primary.main'}},
},
];
const menuItems: MenuItem[] = [{label: 'View Profile', onClick: () => void 0}];
<ChatConversation
chatView={{
heading: {
buttonHref: '/support',
menuItems,
},
isLoading: false,
}}
labels={{
chatViewHeadingButtonText: 'Exit',
chatViewHeadingText: 'Customer Care',
}}
placeholders={{
commentInput: 'Describe your issue...',
}}
messages={messages}
onMessageSubmit={text => void text}
onNavigate={url => void url}
/>;