組織リストブロック
ListOrganizationsコンポーネントは、React、TypeScript、MUIで構築された完全にカスタマイズ可能でアクセシブルな組織テーブルインターフェースです。モダンなデザインパターン、監査ステータス管理、検索機能、タブ、行アクション、ページネーションサポート、ローディング状態、および高度な組織管理アプリケーション向けの柔軟なカスタマイズオプションを備えた完全な表形式組織リスト体験を提供します。
🚀 インストール
npm install @nodeblocks/frontend-list-organization-block@0.2.1
📖 使用法
import {ListOrganizations} from '@nodeblocks/frontend-list-organization-block';
- 基本的な使用法
- 高度な使用法
function SimpleListOrganizations() { const organizationData = [ { id: '1', name: 'アクメ株式会社', createdAt: '2021-03-15', joinDate: '2024-01-10', auditStatus: 'approved', }, { id: '2', name: 'テック・スタートアップ株式会社', createdAt: '2022-06-20', joinDate: '2024-01-12', auditStatus: 'waiting_for_review', }, { id: '3', name: 'デザインスタジオ', createdAt: '2023-01-05', joinDate: '2024-01-14', auditStatus: 'rejected', }, ]; const tabs = [{label: 'すべて'}]; const labels = { emptyStateMessage: '組織が見つかりません', searchFieldPlaceholder: '組織を検索...', actions: { headerAction: '組織を作成', rowAction: '詳細を表示', }, headerRow: { createdAt: '作成日', name: '組織名', joinDate: '参加日', auditStatus: 'ステータス', }, cellData: { statusApproved: '承認済み', statusRejected: '拒否済み', statusWaitingForReview: '審査待ち', }, }; return ( <ListOrganizations data={organizationData} labels={labels} tabs={tabs} listOrganizationsTitle="マイ組織" onSearchFieldChange={value => console.log('検索:', value)} onActionClick={() => console.log('作成がクリックされました')} onNavigate={to => console.log('ナビゲート先:', to)} onRowActionClick={row => console.log('行アクション:', row.id)} rowHref={row => `/organizations/${row.id}`} > <ListOrganizations.Header /> <ListOrganizations.Table /> </ListOrganizations> ); }
function AdvancedListOrganizations() { const [searchQuery, setSearchQuery] = useState(''); const [currentTab, setCurrentTab] = useState('All'); const [currentPage, setCurrentPage] = useState(1); const allOrganizations = [ { id: '1', name: 'グローバルテックソリューションズ', createdAt: '2021-03-15', joinDate: '2024-01-10', auditStatus: 'approved', }, { id: '2', name: 'クリエイティブラボ', createdAt: '2022-06-20', joinDate: '2024-01-12', auditStatus: 'approved', }, { id: '3', name: 'データフローアナリティクス', createdAt: '2022-01-10', joinDate: '2024-01-14', auditStatus: 'waiting_for_review', }, { id: '4', name: 'クラウドファーストシステムズ', createdAt: '2020-11-05', joinDate: '2024-01-08', auditStatus: 'approved', }, { id: '5', name: 'モバイルイノベーション', createdAt: '2023-02-28', joinDate: '2024-01-15', auditStatus: 'rejected', }, ]; const filteredOrganizations = allOrganizations.filter(org => org.name.toLowerCase().includes(searchQuery.toLowerCase()), ); const tabs = [ {key: 'all', label: 'すべて'}, {key: 'approved', label: '承認済み'}, {key: 'pending', label: '審査待ち'}, {key: 'rejected', label: '拒否済み'}, ]; const labels = { emptyStateMessage: '検索条件に一致する組織が見つかりません', searchFieldPlaceholder: '組織名で検索...', actions: { headerAction: <span>+ 新規組織</span>, rowAction: <span>管理</span>, }, headerRow: { createdAt: '設立日', name: '組織', joinDate: 'メンバー登録日', auditStatus: '監査ステータス', }, cellData: { statusApproved: <span style={{color: '#166534'}}>✓ 承認済み</span>, statusRejected: <span style={{color: '#991b1b'}}>✕ 拒否済み</span>, statusWaitingForReview: <span style={{color: '#92400e'}}>⏳ 保留中</span>, }, }; const pagination = { currentPage, totalPages: 3, onPageChange: page => setCurrentPage(page), }; const getStatusColor = (status) => { const colors = { approved: {bg: '#dcfce7', text: '#166534'}, waiting_for_review: {bg: '#fef3c7', text: '#92400e'}, rejected: {bg: '#fee2e2', text: '#991b1b'}, }; return colors[status] || {bg: '#f3f4f6', text: '#374151'}; }; const handleNavigate = (to) => { console.log('ナビゲート先:', to); }; const handleRowAction = (row) => { console.log('組織を管理:', row.id, row.name); }; const handleCreateOrganization = () => { console.log('新規組織を作成'); }; return ( <ListOrganizations data={filteredOrganizations} labels={labels} tabs={tabs} currentTab={currentTab} onTabChange={tab => setCurrentTab(tab)} listOrganizationsTitle="組織管理" onSearchFieldChange={value => setSearchQuery(value)} onActionClick={handleCreateOrganization} onNavigate={handleNavigate} onRowActionClick={handleRowAction} rowHref={row => `/organizations/${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', marginBottom: '4px', }} > マイ組織 </div> <p style={{fontSize: '14px', color: '#64748b', margin: 0}}>所有またはメンバーである組織</p> </div> <button onClick={handleCreateOrganization} 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'}}> {filteredOrganizations.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'}}> {searchQuery ? '別の検索語を試してください' : '最初の組織を作成して始めましょう'} </p> </div> ) : ( <div style={{display: 'flex', flexDirection: 'column', gap: '12px'}}> {filteredOrganizations.map(org => { const statusColors = getStatusColor(org.auditStatus); return ( <div key={org.id} onClick={() => handleRowAction(org)} style={{ padding: '20px', borderRadius: '12px', border: '1px solid #e2e8f0', display: 'flex', alignItems: 'center', gap: '16px', cursor: 'pointer', transition: 'all 0.2s ease', }} > <div style={{ width: '60px', height: '60px', borderRadius: '12px', background: '#f1f5f9', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, }} > <span style={{ fontSize: '24px', fontWeight: '700', color: '#64748b', }} > {org.name.charAt(0)} </span> </div> <div style={{flex: 1, minWidth: 0}}> <div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '8px', }} > <span style={{ fontSize: '16px', fontWeight: '600', color: '#1e293b', }} > {org.name} </span> <span style={{ padding: '4px 12px', borderRadius: '20px', background: statusColors.bg, color: statusColors.text, fontSize: '12px', fontWeight: '600', }} > {org.auditStatus === 'approved' ? '承認済み' : org.auditStatus === 'rejected' ? '拒否済み' : '保留中'} </span> </div> <div style={{ display: 'flex', gap: '16px', fontSize: '13px', color: '#64748b', }} > <span>設立: {org.createdAt}</span> <span>参加: {org.joinDate}</span> </div> </div> <span style={{ color: '#94a3b8', fontSize: '18px', }} > → </span> </div> ); })} </div> )} </div> ); return { blocks: { ...defaultBlocks, header: customHeader, content: customContent, }, blockOrder: defaultBlockOrder, }; }} </ListOrganizations> ); }
🔧 プロパティリファレンス
メインコンポーネントのプロパティ
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
listOrganizationsTitle | ReactNode | 必須 | 組織テーブルセクションのタイトル |
labels | OrganizationLabels | 必須 | テーブルヘッダー、アクション、メッセージのラベルオブジェクト |
isLoading | boolean | undefined | テーブルが現在読み込み中かどうか |
onSearchFieldChange | (value: string) => void | 必須 | 検索フィールドの値が変更されたときのコールバック関数 |
onActionClick | () => void | 必須 | ヘッダーアクションボタンのコールバック関数 |
onNavigate | (to: string) => void | 必須 | ナビゲーション用のコールバック関数 |
onRowActionClick | (rowData: ListOrganizationsRowData) => void | 必須 | 行アクションがクリックされたときのコールバック関数 |
data | ListOrganizationsRowData[] | 必須 | 組織データオブジェクトの配列 |
rowHref | (row: ListOrganizationsRowData) => string | 必須 | 行リンクURLを生成する関数 |
tabs | TabData[] | 必須 | タブ設定オブジェクトの配列 |
currentTab | string | undefined | 現在アクティブなタブラベル |
onTabChange | (tab: string) => void | undefined | タブが変更されたときのコールバック関数 |
pagination | PaginationProps | undefined | ページネーション設定オブジェクト |
className | string | undefined | コンテナスタイリング用の追加CSSクラス名 |
children | BlocksOverride | undefined | デフォルトブロックをオーバーライドする関数 |
注意: メインコンポーネントはMUI Stack プロパティを継承します。ルートコンテナはデフォルトで spacing={3} と sx={{ p: 3 }} を使用します。
サブコンポーネント
ListOrganizationsコンポーネントは複数のサブコンポーネントを提供します。すべてのサブコンポーネントは、メインコンポーネントのコンテキストからデフォルト値を受け取り、プロパティを通じてこれらの値をオーバーライドできます。
ListOrganizations.Header
タイトルとアクションコンポーネントを含むヘッダーのコンテナです。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | undefined | デフォルトヘッダーレンダリングをオーバーライドするカスタムコンテンツ |
direction | StackProps['direction'] | "row" | レイアウトのフレックス方向 |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Stack プロパティを継承します。デフォルト justifyContent: 'space-between'。
ListOrganizations.Title
組織セクションのタイトルを表示します。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | コンテキストから | タイトルをオーバーライドするカスタムコンテンツ |
listOrganizationsTitle | ReactNode | コンテキストから | childrenが提供されていない場合のタイトルテキスト |
variant | TypographyProps['variant'] | "h4" | MUI Typographyバリアント |
component | ElementType | "h1" | レンダリングするHTML要素 |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Typography プロパティを継承します。
ListOrganizations.Action
検索フィールドとアクションボタンのコンテナです。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | undefined | デフォルトアクションレンダリングをオーバーライドするカスタムコンテンツ |
onActionClick | () => void | コンテキストから | アクションボタンのコールバック |
onSearchFieldChange | (value: string) => void | コンテキストから | 検索入力のコールバック |
labels | OrganizationLabels | コンテキストから | アクションテキストとプレースホルダーのラベル |
direction | StackProps['direction'] | "row" | フレックス方向 |
spacing | number | 2 | 要素間のStackスペーシング |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Stack プロパティを継承します。
ListOrganizations.Content
メインコンテンツエリア(ローダー、タブ、テーブル)のコンテナです。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | undefined | デフォルトレンダリングをオーバーライドするカスタムコンテンツ |
isLoading | boolean | コンテキストから | コンテキストからのローディング状態 |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントは spacing={3} を持つMUI Stack プロパティを継承します。読み込み中は Loader を、それ以外は Tabs + Table をレンダリングします。
ListOrganizations.Loader
ローディングインジケーターを表示します。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | CircularProgress | カスタムローディングインジケーターコンテンツ |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Stack プロパティを継承します。デフォルトの整列は alignItems: 'center'、justifyContent: 'center' です。
ListOrganizations.Tabs
組織をフィルタリングするためのタブナビゲーションを表示します。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
tabs | TabData[] | コンテキストから | タブ設定オブジェクトの配列 |
currentTab | string | コンテキストから | 現在アクティブなタブラベル |
onTabChange | (tab: string) => void | コンテキストから | タブ変更コールバック関数 |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Tabs プロパティを継承します(value、onChange、variant を除く)。デフォルトで variant="fullWidth" を使用します。
ListOrganizations.Table
組織データをテーブル形式で表示します。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
labels | OrganizationLabels | コンテキストから | テーブルヘッダーとアクション用のラベル |
data | ListOrganizationsRowData[] | コンテキストから | 組織データの配列 |
rowHref | (row: ListOrganizationsRowData) => string | コンテキストから | 行リンクを生成する関数 |
onNavigate | (to: string) => void | コンテキストから | 行クリック時のナビゲーションコールバック |
onRowActionClick | (row: ListOrganizationsRowData) => void | コンテキストから | 行アクションがクリックされたときのコールバック |
pagination | PaginationProps | コンテキストから | ページネーション設定 |
spacing | number | 3 | 要素間のStackスペーシング |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Stack プロパティを継承します。
🎨 Configuration examples
Custom Header Styling
<ListOrganizations.Header
sx={{
bgcolor: 'primary.main',
color: 'white',
borderRadius: 2,
p: 3
}}
/>
Custom Title Styling
<ListOrganizations.Title
variant="h3"
sx={{
fontWeight: 700,
color: 'primary.main'
}}
/>
Custom Status Labels with JSX
const labels = {
// ... other labels
cellData: {
statusApproved: <Chip label="承認済み" color="success" size="small" />,
statusRejected: <Chip label="拒否済み" color="error" size="small" />,
statusWaitingForReview: <Chip label="保留中" color="warning" size="small" />
}
};
Using Block Override Pattern
<ListOrganizations {...props}>
{({defaultBlocks, defaultBlockOrder}) => ({
blocks: {
...defaultBlocks,
header: customHeader,
content: customContent,
},
blockOrder: defaultBlockOrder,
})}
</ListOrganizations>
🔧 TypeScript サポート
包括的な型定義による完全なTypeScriptサポート:
import {ListOrganizations} from '@nodeblocks/frontend-list-organization-block';
import { StackProps } from '@mui/material';
import { ReactNode } from 'react';
// タブデータインターフェース
interface TabData {
key?: string;
label: string;
isDisabled?: boolean;
subtitle?: string;
}
// 行データインターフェース
interface ListOrganizationsRowData {
id: string;
createdAt: string;
name: string;
joinDate: string;
auditStatus: string;
}
// ページネーションプロパティインターフェース
interface PaginationProps {
className?: string;
currentPage: number;
onPageChange: (page: number) => void;
totalPages: number;
}
// ラベルインターフェース
interface OrganizationLabels {
emptyStateMessage: string;
searchFieldPlaceholder: string;
actions: {
headerAction: ReactNode;
rowAction: ReactNode;
};
headerRow: {
createdAt: string;
name: string;
joinDate: string;
auditStatus: string;
};
cellData: {
statusApproved: ReactNode;
statusRejected: ReactNode;
statusWaitingForReview: ReactNode;
};
}
// メインコンポーネントプロパティインターフェース
interface ListOrganizationsProps<T extends TabData[]> extends Omit<StackProps, 'children'> {
listOrganizationsTitle: ReactNode;
labels: OrganizationLabels;
isLoading?: boolean;
onSearchFieldChange: (value: string) => void;
onActionClick: () => void;
onNavigate: (to: string) => void;
onRowActionClick: (rowData: ListOrganizationsRowData) => void;
data: ListOrganizationsRowData[];
rowHref: (row: ListOrganizationsRowData) => string;
tabs: T;
currentTab?: T[number]['label'];
onTabChange?: (tab: T[number]['label']) => void;
pagination?: PaginationProps;
children?: BlocksOverride;
}
// Usage example with full typing
function TypedListOrganizations() {
const [page, setPage] = useState(1);
const [currentTab, setCurrentTab] = useState('すべて');
const organizationData: ListOrganizationsRowData[] = [
{
id: 'org-1',
name: 'テックカンパニー',
createdAt: '2020-05-10',
joinDate: '2024-01-15',
auditStatus: 'approved',
},
{
id: 'org-2',
name: 'スタートアップ株式会社',
createdAt: '2022-08-20',
joinDate: '2024-01-18',
auditStatus: 'waiting_for_review',
},
];
const tabs: TabData[] = [
{key: 'all', label: 'すべて'},
{key: 'approved', label: '承認済み'},
{key: 'pending', label: '保留中'},
];
const labels: OrganizationLabels = {
emptyStateMessage: '組織が見つかりません',
searchFieldPlaceholder: '組織を検索...',
actions: {
headerAction: '組織を作成',
rowAction: '管理',
},
headerRow: {
createdAt: '設立日',
name: '組織名',
joinDate: 'メンバー登録日',
auditStatus: '監査ステータス',
},
cellData: {
statusApproved: '承認済み',
statusRejected: '拒否済み',
statusWaitingForReview: '審査中',
},
};
const handleSearchChange = (value: string): void => {
console.log('検索値:', value);
};
const handleNavigate = (to: string): void => {
console.log('ナビゲート先:', to);
};
const handleRowAction = (row: ListOrganizationsRowData): void => {
console.log('組織を管理:', row.id);
};
const pagination: PaginationProps = {
currentPage: page,
totalPages: 5,
onPageChange: newPage => setPage(newPage),
};
return (
<ListOrganizations
data={organizationData}
labels={labels}
tabs={tabs}
currentTab={currentTab}
onTabChange={tab => setCurrentTab(tab)}
listOrganizationsTitle="組織リスト"
onSearchFieldChange={handleSearchChange}
onActionClick={() => console.log('作成がクリックされました')}
onNavigate={handleNavigate}
onRowActionClick={handleRowAction}
rowHref={row => `/organizations/${row.id}`}
pagination={pagination}
isLoading={false}
sx={{
maxWidth: 800,
mx: 'auto',
p: 3,
border: '1px solid #e5e7eb',
borderRadius: 2,
}}
>
<ListOrganizations.Header />
<ListOrganizations.Tabs />
<ListOrganizations.Table />
</ListOrganizations>
);
}
📝 注意事項
- ルートコンポーネントはデフォルトで
spacing={3}とsx={{ p: 3 }}のパディングを持つMUIのStackを使用します - MUI
Table、TableContainer、TableHead、TableBody、TableRowコンポーネントを使用します - 日付フォーマットは
luxonライブラリを使用し、表示にはyyyy/M/dフォーマットを使用します - 行アクションボタンは
auditStatus: 'waiting_for_review'の行にのみ表示されます - 空状態はラベルの
emptyStateMessageと共にpersonアイコン(SVG)を表示します - ページネーションは
variant="outlined"とshape="rounded"を持つMUIPaginationコンポーネントを使用します rowHrefが有効なURLを返す場合、テーブル行はクリック可能です(カーソルがポインターに変わります)- タブはデフォルトで
variant="fullWidth"を持つMUITabsコンポーネントを使用します ListOrganizations.Titleはデフォルトでvariant="h4"とcomponent="h1"を持つMUI Typographyを使用します- 検索フィールドはエンドアドーンメントに検索アイコンを持つMUI
TextFieldを使用します - アクションボタンは
variant="outlined"とsize="medium"を持つMUIButtonを使用します - すべてのサブコンポーネントはそれぞれのMUIコンポーネントプロパティを継承し、スタイリング用の
sxプロパティをサポートします - ブロックオーバーライドパターンにより、デフォルトブロックのカスタマイズ、置換、並べ替えが可能です
React、TypeScript、MUIを使用して❤️で構築されました。