メインコンテンツまでスキップ

チャット会話リストブロック

Chat Conversation Listコンポーネントは、ReactとTypeScriptで構築された完全にカスタマイズ可能でアクセシブルな会話リストインターフェースです。検索機能、無限スクロール、チャットアプリケーションインターフェースのための柔軟なカスタマイズオプションを備えた完全な会話概要を提供します。


🚀 インストール

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

📖 使用法

import {ChatConversationList} from '@nodeblocks/frontend-chat-conversation-list-block';
ライブエディター
function SimpleChatConversationList() {
  const [conversations] = useState([
        {
          id: 'conv-001',
          title: 'カスタマーサポート',
          content: 'お問い合わせありがとうございます。今日はどのようなお手伝いができますか?',
          dateTime: '2025-02-01T10:00:00Z',
          titleLines: 1,
          unreadCount: 3,
          href: '/chat/conv-001',
          avatar: {
            sx: { bgcolor: 'primary.main' },
          },
        },
        {
          id: 'conv-002',
          title: 'プロジェクトチーム討議',
          content: '新機能の実装準備ができました。レビューをお願いします。',
          dateTime: '2025-02-01T09:30:00Z',
          titleLines: 2,
          unreadCount: 1,
          href: '/chat/conv-002',
          avatar: {
            sx: { bgcolor: 'secondary.main' },
          },
        },
        {
          id: 'conv-003',
          title: 'マーケティングキャンペーン',
          content: 'キャンペーン戦略について話し合うミーティングをスケジュールしましょう。',
          dateTime: '2025-01-31T16:45:00Z',
          titleLines: 1,
          href: '/chat/conv-003',
          avatar: {
            sx: { bgcolor: 'info.main' },
          },
        },
      ]);

      return (
        <ChatConversationList
          conversations={conversations}
          labels={{
            heading: '会話',
            emptyState: 'まだ会話がありません',
          }}
          placeholders={{
            search: '会話を検索...',
          }}
          search={{
            defaultValue: '',
            onChange: (value) => {
              console.log('検索が変更されました:', value);
            },
          }}
          onNavigate={(url) => {
            console.log('ナビゲート:', url);
          }}
          onScrollBottom={() => {
            console.log('さらに会話を読み込み');
          }}
          isLoading={false}
        >
          <ChatConversationList.Heading />
          <ChatConversationList.SearchBar />
          <ChatConversationList.ScrollPanel />
        </ChatConversationList>
      );
    }
結果
Loading...

🔧 プロパティリファレンス

メインコンポーネントプロパティ

プロパティデフォルト説明
conversationsConversation[]必須表示する会話オブジェクトの配列
labels{heading?: string; emptyState?: string;}undefinedテキストラベルの設定
placeholders{search?: string;}undefinedプレースホルダーテキストの設定
isLoadingbooleanundefinedローディング状態を表示
onNavigate(url: string) => void必須ナビゲーションコールバック(hrefに必要)
onScrollBottom() => voidundefinedリストの下部に到達したときのコールバック
search{defaultValue?: string; onChange?: (value: string) => void;}undefined検索入力の設定
size'compact' | 'normal'undefinedテキストサイズとスペーシングを設定(サブコンポーネントはデフォルトで'compact'
classNamestringundefinedスタイリング用の追加CSSクラス名
sxSxProps<Theme>undefinedカスタムスタイリング用のMUIシステムプロパティ
childrenBlocksOverrideundefinedデフォルトレンダリングをオーバーライドするカスタムブロックコンポーネント

注意: このコンポーネントはすべてのMUI Stackコンポーネントプロパティを継承します。

会話オブジェクト

プロパティデフォルト説明
idstring必須会話の一意識別子
titlestring必須会話のタイトル
titleLines1 | 2必須タイトルが切り取られる前の行数
contentstringundefined会話本文(一行後に切り取られます)
dateTimestringundefinedISO文字列としての日時(date-fnsで日本語ロケールを使用してフォーマット)
unreadCountnumberundefined未読数(99を超えると「99...」と表示されます)
hrefstringundefined会話をリンク可能にし、ホバー状態を追加
isSelectedbooleanundefinedtrueの場合、会話をハイライト
avatarPartial<AvatarProps> & {isLoading?: boolean}undefinedオプションのローディング状態を持つMUI Avatarプロパティ

サブコンポーネント

ChatConversationListコンポーネントは複数のサブコンポーネントを提供します。すべてのサブコンポーネントは、メインコンポーネントのコンテキストからデフォルト値を受け取り、プロパティを通じてこれらの値をオーバーライドできます。

ChatConversationList.Heading

MUI Typographyを使用して会話リストのヘッダーをレンダリングします。

プロパティデフォルト説明
labels{heading?: string;}コンテキスト値ラベル設定(コンテキストをオーバーライド)
size'compact' | 'normal''compact'サイズバリアント(コンテキストをオーバーライド)
classNamestringundefinedスタイリング用の追加CSSクラス名
childrenReactNodeデフォルトヘッダーコンテンツデフォルトヘッダーをオーバーライドするカスタムコンテンツ
componentElementType'h1'レンダリングするHTML要素
variantTypographyVariant'h4'MUI Typographyバリアント
sxSxProps<Theme>undefinedカスタムスタイリング用のMUIシステムプロパティ

注意: このコンポーネントはすべてのMUI Typographyコンポーネントプロパティを継承します。

ChatConversationList.SearchBar

デバウンス検索機能付きの検索入力をレンダリングします。

プロパティデフォルト説明
search{defaultValue?: string; onChange?: (value: string) => void;}コンテキスト値検索設定(コンテキストをオーバーライド)
placeholders{search?: string;}コンテキスト値プレースホルダー設定(コンテキストをオーバーライド)
conversationsConversation[]コンテキスト値表示を決定する会話(コンテキストをオーバーライド)
size'compact' | 'normal''compact'サイズバリアント(コンテキストをオーバーライド)
debounceTimenumber500検索onChangeをトリガーする前の遅延(ミリ秒)
classNamestringundefinedスタイリング用の追加CSSクラス名
defaultValuestringundefined検索入力の初期値
placeholderstringundefined空のときのプレースホルダーテキスト
variantTextFieldVariant'outlined'MUI TextFieldバリアント
typestring'search'HTML入力タイプ
fullWidthbooleantrue入力が全幅を取るかどうか(size='normal'時は無視)
sxSxProps<Theme>undefinedカスタムスタイリング用のMUIシステムプロパティ

注意: このコンポーネントはすべてのMUI TextFieldコンポーネントプロパティを継承します(onChangesizeを除く)。

ChatConversationList.Header

HeadingとSearchBarをオプションのディバイダー付きで組み合わせる便利なコンポーネント。

プロパティデフォルト説明
size'compact' | 'normal'コンテキスト値サイズバリアント(コンテキストをオーバーライド)
classNamestringundefinedスタイリング用の追加CSSクラス名
childrenReactNodeデフォルトヘッダーコンテンツデフォルトヘッダーをオーバーライドするカスタムコンテンツ
sxSxProps<Theme>undefinedカスタムスタイリング用のMUIシステムプロパティ

注意: このコンポーネントはすべてのMUI Stackコンポーネントプロパティを継承します(directionを除く)。

ChatConversationList.ScrollPanel

無限スクロールサポート付きのスクロール可能な会話リストをレンダリングします。

プロパティデフォルト説明
conversationsConversation[]コンテキスト値会話配列(コンテキストをオーバーライド)
labels{heading?: string; emptyState?: string;}コンテキスト値ラベル設定(コンテキストをオーバーライド)
isLoadingbooleanコンテキスト値ローディング状態(コンテキストをオーバーライド)
onScrollBottom() => voidコンテキスト値スクロールコールバック(コンテキストをオーバーライド)
size'compact' | 'normal''compact'サイズバリアント(コンテキストをオーバーライド)
classNamestringundefinedスタイリング用の追加CSSクラス名
childrenReactNodeデフォルト会話アイテムデフォルトレンダリングをオーバーライドするカスタムコンテンツ
sxSxProps<Theme>undefinedカスタムスタイリング用のMUIシステムプロパティ

注意: このコンポーネントはすべてのMUI Stackコンポーネントプロパティを継承します(directionspacingを除く)。


🎨 設定例

このコンポーネントはアバターのカスタマイズにMUI Avatarプロパティを使用します。アバターの設定方法は以下の通りです:

const conversation = {
id: 'conv-001',
title: 'サポートチーム',
titleLines: 1,
avatar: {
// MUI Avatarプロパティ
src: '/path/to/image.jpg', // 画像ソース
srcSet: '/path/to/image@2x.jpg 2x', // レスポンシブ画像
sx: { bgcolor: 'primary.main' }, // カスタムスタイリング
children: 'ST', // フォールバックコンテンツ(イニシャル)

// 拡張プロパティ
isLoading: false, // ローディングプレースホルダーを表示
},
};

🔧 TypeScriptサポート

包括的な型定義による完全なTypeScriptサポート:

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 {
/** 会話の一意識別子 */
id: string;
/** 会話のタイトル */
title: string;
/** タイトルが切り取られる前の行数 */
titleLines: 1 | 2;
/** 会話本文(一行後に切り取られます) */
content?: string;
/** ISO文字列としての日時(date-fnsでフォーマット) */
dateTime?: string;
/** 未読数(99を超えると99で切り取られます) */
unreadCount?: number;
/** 会話をリンク可能にし、ホバー状態を追加 */
href?: string;
/** trueの場合、会話をハイライト */
isSelected?: boolean;
/** オプションのローディング状態を持つMUI Avatarプロパティ */
avatar?: Partial<AvatarProps> & {
/** 一時的なローディング状態を表示 */
isLoading?: boolean;
};
}

// 検索とページネーション付きの完全な型付き例
function TypedChatConversationList() {
const [conversations, setConversations] = useState<Conversation[]>([
{
id: 'support-001',
title: 'カスタマーサポートチーム',
content: 'あなたの問題は解決されました。他にお手伝いできることはありますか?',
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: '開発チーム討議',
content: 'コードレビューが完了しました。新機能ブランチをマージする準備ができています。',
dateTime: '2025-02-01T13:15:00Z',
titleLines: 2,
unreadCount: 5,
href: '/chat/team-002',
isSelected: true,
avatar: {
sx: { bgcolor: 'secondary.main' },
},
},
{
id: 'client-003',
title: 'クライアントプロジェクト更新',
content: 'プロジェクトのタイムラインについて話し合うため、明日午後2時にミーティングを予定しています。',
dateTime: '2025-02-01T11:45:00Z',
titleLines: 1,
href: '/chat/client-003',
avatar: {
sx: { bgcolor: 'info.main' },
src: '/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('さらに会話を読み込み中...');
setIsLoading(true);

setTimeout(() => {
const newConversations: Conversation[] = [
{
id: `conv-${Date.now()}`,
title: '新しい会話',
content: 'これは新しく読み込まれた会話です。',
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('ナビゲート:', url);
setConversations(prev =>
prev.map(conv => ({
...conv,
isSelected: conv.href === url,
})),
);
};

return (
<ChatConversationList
conversations={filteredConversations}
labels={{
heading: 'チャット会話',
emptyState: searchValue ? '検索に一致する会話がありません' : 'まだ会話がありません',
}}
placeholders={{
search: '名前またはメッセージで検索...',
}}
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>
);
}

📝 注意事項

  • 日付はdate-fnsライブラリを使用して日本語ロケール(ja)で相対時間表示(例:「5分前」)でフォーマットされます
  • 検索入力はデフォルトでデバウンスされています(500ms)、過度なコールバックを防ぐため
  • 検索入力の特殊文字は自動的にサニタイズされます
  • 検索バーは会話がある場合、またはユーザーが検索を操作した場合にのみ表示されます
  • size='normal'を使用すると、レイアウトが異なるスペーシングでより広い表示に調整されます
  • 無限スクロールは効率的な検出のためにIntersectionObserverを使用して実装されています
  • 選択された会話は水色の背景(#e0f3fc)でハイライト表示されます

React、TypeScript、MUI、date-fnsを使用して❤️で構築されました。