招待リストブロック
ListInvitesコンポーネントは、React、TypeScript、MUIで構築された完全にカスタマイズ可能でアクセシブルな招待管理インターフェースです。モダンなデザインパターン、アクションドロップダウン、ページネーションサポート、ローディング状態、および高度な招待管理アプリケーション向けの柔軟なカスタマイズオプションを備えた完全な表形式招待リスト体験を提供します。
🚀 インストール
npm install @nodeblocks/frontend-list-invites-block@0.2.1
📖 使用法
import {ListInvites} from '@nodeblocks/frontend-list-invites-block';
- 基本的な使用法
- 高度な使用法
function SimpleListInvites() { const inviteData = [ { id: '1', name: '田中太郎', email: 'tanaka.taro@example.com', status: 'Pending', }, { id: '2', name: '佐藤花子', email: 'sato.hanako@example.com', status: 'Accepted', }, { id: '3', name: '鈴木次郎', email: 'suzuki.jiro@example.com', status: 'Expired', }, ]; const labels = { emptyStateMessage: '招待が見つかりません', actions: { inviteUser: 'ユーザーを招待', }, headerRow: { name: '名前', email: 'メールアドレス', status: 'ステータス', }, rowActions: { reject: '拒否', }, unsetDateMessage: '設定されていません', }; return ( <ListInvites data={inviteData} labels={labels} listInvitesTitle="チーム招待" onNavigate={to => console.log('ナビゲート先:', to)} onClickAction={() => console.log('招待アクションがクリックされました')} onItemReject={id => console.log('招待を拒否:', id)} rowHref={row => `/invites/${row.id}`} > <ListInvites.Header /> <ListInvites.Content /> </ListInvites> ); }
function AdvancedListInvites() { const [currentPage, setCurrentPage] = useState(1); const allInvites = [ { id: '1', name: '田中太郎', email: 'tanaka.taro@company.jp', status: 'Pending', }, { id: '2', name: '佐藤花子', email: 'sato.hanako@company.jp', status: 'Accepted', }, { id: '3', name: '鈴木次郎', email: 'suzuki.jiro@company.jp', status: 'Pending', }, { id: '4', name: '山田三郎', email: 'yamada.saburo@company.jp', status: 'Expired', }, { id: '5', name: '高橋四郎', email: 'takahashi.shiro@company.jp', status: 'Declined', }, ]; const labels = { emptyStateMessage: '条件に一致する招待が見つかりません', actions: { inviteUser: '新しいメンバーを招待', }, headerRow: { name: '氏名', email: 'メールアドレス', status: '招待ステータス', }, rowActions: { reject: '招待を取り消し', }, unsetDateMessage: '日付が指定されていません', }; const pagination = { currentPage, totalPages: 3, onPageChange: page => setCurrentPage(page), }; const getStatusColor = (status) => { const colors = { Pending: {bg: '#fef3c7', text: '#92400e', icon: '⏳'}, Accepted: {bg: '#dcfce7', text: '#166534', icon: '✓'}, Expired: {bg: '#f3f4f6', text: '#374151', icon: '⌛'}, Declined: {bg: '#fee2e2', text: '#991b1b', icon: '✕'}, }; return colors[status] || {bg: '#f3f4f6', text: '#374151', icon: '•'}; }; const handleNavigate = (to) => { console.log('ナビゲート先:', to); }; const handleInviteAction = () => { console.log('招待モーダルを開く'); }; const handleRejectInvite = (id) => { console.log('招待を拒否:', id); }; return ( <ListInvites data={allInvites} labels={labels} listInvitesTitle="チーム招待" onNavigate={handleNavigate} onClickAction={handleInviteAction} onItemReject={handleRejectInvite} rowHref={row => `/invites/${row.id}`} pagination={pagination} sx={{ maxWidth: 1000, mx: 'auto', bgcolor: '#ffffff', borderRadius: 3, boxShadow: '0 4px 20px rgba(0,0,0,0.08)', overflow: 'hidden', }} > {({defaultBlocks, defaultBlockOrder}) => { const customHeader = ( <div style={{ padding: '24px', borderBottom: '1px solid #e2e8f0', }} > <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px', }} > <div> <div style={{ fontSize: '24px', fontWeight: '700', color: '#1e293b', margin: '0 0 4px 0', }} > チーム招待 </div> <p style={{fontSize: '14px', color: '#64748b', margin: 0}}>保留中および過去のチーム招待を管理</p> </div> <button onClick={handleInviteAction} style={{ padding: '12px 24px', borderRadius: '10px', background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', color: '#ffffff', border: 'none', fontSize: '14px', fontWeight: '600', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '8px', }} > <span>+</span> メンバーを招待 </button> </div> </div> ); const customContent = ( <div style={{padding: '16px'}}> {allInvites.length === 0 ? ( <div style={{ textAlign: 'center', padding: '48px', color: '#64748b', }} > <div style={{fontSize: '48px', marginBottom: '16px'}}>✉️</div> <p style={{fontSize: '16px', fontWeight: '500'}}>招待が見つかりません</p> <p style={{fontSize: '14px'}}>このフィルターに一致する招待がここに表示されます</p> </div> ) : ( <div style={{display: 'flex', flexDirection: 'column', gap: '12px'}}> {allInvites.map(invite => { const statusInfo = getStatusColor(invite.status); return ( <div key={invite.id} style={{ padding: '20px', borderRadius: '12px', border: '1px solid #e2e8f0', display: 'flex', alignItems: 'center', gap: '16px', }} > <div style={{ width: '48px', height: '48px', borderRadius: '12px', background: '#f1f5f9', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '20px', flexShrink: 0, }} > {invite.name.charAt(0).toUpperCase()} </div> <div style={{flex: 1, minWidth: 0}}> <div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '4px', }} > <span style={{ fontSize: '15px', fontWeight: '600', color: '#1e293b', }} > {invite.name} </span> <span style={{ padding: '4px 10px', borderRadius: '6px', background: statusInfo.bg, color: statusInfo.text, fontSize: '12px', fontWeight: '500', display: 'flex', alignItems: 'center', gap: '4px', }} > {statusInfo.icon} {invite.status} </span> </div> <div style={{ fontSize: '13px', color: '#64748b', }} > {invite.email} </div> </div> <div style={{display: 'flex', gap: '8px'}}> {invite.status === 'Pending' && ( <button onClick={() => handleRejectInvite(invite.id)} style={{ padding: '8px 16px', borderRadius: '8px', border: '1px solid #fee2e2', background: '#fff5f5', color: '#dc2626', fontSize: '13px', fontWeight: '500', cursor: 'pointer', }} > キャンセル </button> )} </div> </div> ); })} </div> )} </div> ); return { blocks: { ...defaultBlocks, header: customHeader, content: customContent, }, blockOrder: defaultBlockOrder, }; }} </ListInvites> ); }
🔧 プロパティリファレンス
メインコンポーネントのプロパティ
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
listInvitesTitle | ReactNode | 必須 | 招待セクションのタイトル |
labels | TableLabels | 必須 | テーブルヘッダー、アクション、メッセージのラベルオブジェクト |
isLoading | boolean | undefined | テーブルが現在読み込み中かどうか |
onNavigate | (to: string) => void | 必須 | ナビゲーション用のコールバック関数 |
onClickAction | () => void | 必須 | メインアクションボタンのコールバック関数 |
onItemReject | (inviteId: string) => void | 必須 | 招待が拒否されたときのコールバック関数 |
data | ListInvitesRowData[] | 必須 | 招待データオブジェクトの配列 |
rowHref | (row: ListInvitesRowData) => string | 必須 | 行リンクURLを生成する関数 |
shouldShowDropdownMenu | (row: ListInvitesRowData) => boolean | undefined | 行ごとにドロップダウンメニューを条件付きで表示する関数 |
pagination | PaginationProps | undefined | ページネーション設定 |
className | string | undefined | コンテナスタイリング用の追加CSSクラス名 |
children | BlocksOverride | undefined | デフォルトブロックをオーバーライドする関数 |
注意: メインコンポーネントはMUI Stack プロパティを継承します。ルートコンテナはデフォルトで spacing={3} と sx={{ p: 3 }} を使用します。
サブコンポーネント
ListInvitesコンポーネントは複数のサブコンポーネントを提供します。すべてのサブコンポーネントは、メインコンポーネントのコンテキストからデフォルト値を受け取り、プロパティを通じてこれらの値をオーバーライドできます。
ListInvites.Header
タイトルとアクションボタンを含むヘッダーのコンテナです。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | undefined | デフォルトヘッダーレンダリングをオーバーライドするカスタムコンテンツ |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Box プロパティを継承します。direction="row" と justifyContent: 'space-between' のStackを含みます。
ListInvites.Title
招待セクションのタイトルを表示します。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | コンテキストから | タイトルをオーバーライドするカスタムコンテンツ |
listInvitesTitle | ReactNode | コンテキストから | childrenが提供されていない場合のタイトルテキスト |
variant | TypographyProps['variant'] | "h4" | MUI Typographyバリアント |
component | ElementType | "h1" | レンダリングするHTML要素 |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Typography プロパティを継承します。
ListInvites.Action
アクションボタンのコンテナです。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | undefined | デフォルトアクションレンダリングをオーバーライドするカスタムコンテンツ |
onClickAction | () => void | コンテキストから | アクションボタンのコールバック |
labels | TableLabels | コンテキストから | アクションテキスト用のラベル |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Stack プロパティを継承します(direction は "row" に固定)。デフォルトの整列は alignItems: 'center' です。
ListInvites.Content
メインコンテンツエリア(ローダーまたはテーブル)のコンテナです。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | undefined | デフォルトレンダリングをオーバーライドするカスタムコンテンツ |
isLoading | boolean | コンテキストから | コンテキストからのローディング状態 |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Box プロパティを継承します。読み込み中は Loader を、それ以外は Table をレンダリングします。
ListInvites.Loader
ローディングインジケーターを表示します。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | CircularProgress | カスタムローディングインジケーターコンテンツ |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Stack プロパティを継承します。デフォルトの整列は alignItems: 'center' です。
ListInvites.Table
招待データをテーブル形式で表示します。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
labels | TableLabels | コンテキストから | テーブルヘッダーとアクション用のラベル |
data | ListInvitesRowData[] | コンテキストから | 招待データの配列 |
rowHref | (row: ListInvitesRowData) => string | コンテキストから | 行リンクを生成する関数 |
onNavigate | (to: string) => void | コンテキストから | 行クリック時のナビゲーションコールバック |
onItemReject | (id: string) => void | コンテキストから | 招待を拒否するときのコールバック |
shouldShowDropdownMenu | (row: ListInvitesRowData) => boolean | コンテキストから | ドロップダウンを条件付きで表示する関数 |
pagination | PaginationProps | コンテキストから | ページネーション設定 |
spacing | number | 3 | 要素間のStackスペーシング |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Stack プロパティを継承します。
🎨 Configuration examples
Custom Styled Example
<ListInvites
data={inviteData}
labels={labels}
listInvitesTitle="招待"
onNavigate={console.log}
onClickAction={() => console.log('アクションがクリックされました')}
onItemReject={console.log}
rowHref={row => `/invite/${row.id}`}
sx={{
bgcolor: 'grey.900',
color: 'grey.100',
p: 3,
borderRadius: 2,
}}
>
<ListInvites.Header
sx={{
'& .MuiTypography-root': {
color: 'grey.100',
},
}}
/>
<ListInvites.Content
sx={{
'& .MuiTableCell-root': {
color: 'grey.100',
borderColor: 'grey.700',
},
}}
/>
</ListInvites>
Using Block Override Pattern
<ListInvites {...props}>
{({defaultBlocks, defaultBlockOrder}) => {
const customHeader = (
<div style={{padding: '24px', borderBottom: '1px solid #e2e8f0'}}>
<div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
<div>
<div style={{fontSize: '24px', fontWeight: '700', color: '#1e293b'}}>
チーム招待
</div>
<p style={{fontSize: '14px', color: '#64748b', margin: 0}}>
保留中および過去のチーム招待を管理
</p>
</div>
<button
onClick={handleInviteAction}
style={{
padding: '12px 24px',
borderRadius: '10px',
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
color: '#ffffff',
border: 'none',
cursor: 'pointer',
}}
>
<span>+</span> メンバーを招待
</button>
</div>
</div>
);
return {
blocks: {
...defaultBlocks,
header: customHeader,
},
blockOrder: defaultBlockOrder,
};
}}
</ListInvites>
🔧 TypeScript サポート
包括的な型定義による完全なTypeScriptサポート:
import {ListInvites} from '@nodeblocks/frontend-list-invites-block';
import { StackProps } from '@mui/material';
import { ReactNode } from 'react';
// 行データインターフェース
interface ListInvitesRowData {
id: string;
name: string;
email: string;
status: string;
}
// ページネーションプロパティインターフェース
interface PaginationProps {
className?: string;
currentPage: number;
onPageChange: (page: number) => void;
totalPages: number;
}
// ラベルインターフェース
interface TableLabels {
emptyStateMessage: string;
actions: {
inviteUser: string;
};
headerRow: {
name: string;
email: string;
status: string;
};
rowActions: {
reject: string;
};
unsetDateMessage: string;
}
// メインコンポーネントプロパティインターフェース
interface ListInvitesProps extends Omit<StackProps, 'children'> {
listInvitesTitle: ReactNode;
labels: TableLabels;
isLoading?: boolean;
onNavigate: (to: string) => void;
onClickAction: () => void;
onItemReject: (inviteId: string) => void;
data: ListInvitesRowData[];
rowHref: (row: ListInvitesRowData) => string;
shouldShowDropdownMenu?: (row: ListInvitesRowData) => boolean;
pagination?: PaginationProps;
children?: BlocksOverride;
}
// Usage example with full typing
function TypedListInvites() {
const [page, setPage] = useState(1);
const inviteData: ListInvitesRowData[] = [
{
id: 'inv-1',
name: '開発者1',
email: 'developer@company.com',
status: 'Pending',
},
{
id: 'inv-2',
name: 'デザイナー2',
email: 'designer@company.com',
status: 'Accepted',
},
];
const labels: TableLabels = {
emptyStateMessage: 'チーム招待が見つかりません',
actions: {
inviteUser: 'チームメンバーを招待',
},
headerRow: {
name: '氏名',
email: 'メールアドレス',
status: '招待ステータス',
},
rowActions: {
reject: '招待を拒否',
},
unsetDateMessage: '日付が設定されていません',
};
const handleNavigate = (to: string): void => {
console.log('ナビゲート先:', to);
};
const handleInviteAction = (): void => {
console.log('招待ダイアログを開く');
};
const handleReject = (id: string): void => {
console.log('招待を拒否:', id);
};
const pagination: PaginationProps = {
currentPage: page,
totalPages: 5,
onPageChange: newPage => setPage(newPage),
};
return (
<ListInvites
data={inviteData}
labels={labels}
listInvitesTitle="チーム招待"
onNavigate={handleNavigate}
onClickAction={handleInviteAction}
onItemReject={handleReject}
rowHref={row => `/invitations/${row.id}`}
pagination={pagination}
isLoading={false}
sx={{
maxWidth: 800,
mx: 'auto',
p: 3,
border: '1px solid #e5e7eb',
borderRadius: 2,
}}
>
<ListInvites.Header />
<ListInvites.Table />
</ListInvites>
);
}
📝 注意事項
- ルートコンポーネントはデフォルトで
spacing={3}とsx={{ p: 3 }}のパディングを持つMUIのStackを使用します - MUI
Table、TableContainer、TableHead、TableBody、TableRowコンポーネントを使用します - 行アクションは
blockアイコンを持つMUIMenuとMenuItemを使用してドロップダウンメニューで表示されます - 空状態はラベルの
emptyStateMessageと共にpersonアイコン(Material Symbols)を表示します - ページネーションは
variant="outlined"とshape="rounded"を持つMUIPaginationコンポーネントを使用します rowHrefが有効なURLを返す場合、テーブル行はクリック可能です(カーソルがポインターに変わります)shouldShowDropdownMenuプロパティにより、行ごとにアクションメニューの条件付き表示が可能です- デフォルトのアクションボタンは
variant="contained"とpeople_outlineアイコンを持つMUIButtonを使用します ListInvites.Titleはデフォルトでvariant="h4"とcomponent="h1"を持つMUI Typographyを使用します- すべてのサブコンポーネントはそれぞれのMUIコンポーネントプロパティを継承し、スタイリング用の
sxプロパティをサポートします - ブロックオーバーライドパターンにより、デフォルトブロックのカスタマイズ、置換、並べ替えが可能です
React、TypeScript、MUIを使用して❤️で構築されました。