ListProductsTableブロック
ListProductsTable は、MUI の Table プリミティブ上に構築された包括的な表形式リストブロックです。固定列、タブ、閉じられる検索チップフィルター、行アクション、複合子ブロックまたは関数ベースのブロックオーバーライドによる深いレイアウト構成をサポートします。
インストール
- npm
- yarn
- pnpm
- bun
npm install @nodeblocks/frontend-list-products-table-block
yarn add @nodeblocks/frontend-list-products-table-block
pnpm add @nodeblocks/frontend-list-products-table-block
bun add @nodeblocks/frontend-list-products-table-block
必要なもの
| 項目 | 用途 |
|---|---|
listProductsTableTitle | テーブルヘッダーのタイトルまたは React ノード |
createHref | 「商品を作成」アクションボタンの遷移先リンク/ハッシュ |
data | ListProductsTableRowData に一致する行データの配列 |
labels | テーブルヘッダー、プレースホルダー、アクションボタン、空状態/フォールバック状態をマッピングする完全なラベルオブジェクト |
ListProductsTable はプレゼンテーション用で、フィルター、検索キーワード、タブ選択、ページネーションの内部状態を管理しません。タブの変更はアクティブなタブ状態のみを更新します。行を変更したい場合は、テーブルに渡す data を自分でフィルタリングするか、onTabChange や onPageChange などのコールバックに応じて新しいリストをクエリしてデータを更新してください。
コード例
- クイックスタート
- ラベルと URL
- 複合コンポーネント
- ブロックのオーバーライド
function Example() { const [currentTab, setCurrentTab] = React.useState('published'); const [page, setPage] = React.useState(1); const [lastAction, setLastAction] = React.useState('ナビゲーションと行アクションがここに表示されます。'); const products = [ { id: '1', tab: 'draft', category: '家具', title: 'プレミアムオフィスチェア', publication: { status: 'PUBLISHED', since: '2026-01-12', until: '2026-02-12' }, createdBy: 'マーティ', createdAt: '2026-01-10T09:21:00Z', updatedBy: 'アリス', updatedAt: '2026-01-11T10:30:00Z', favoritesCount: 14, ordersCount: 22, }, { id: '2', tab: 'published', category: '電子機器', title: 'ワイヤレスヘッドホン', publication: { status: 'PUBLISHED', since: '2026-01-03', until: '2026-03-03' }, createdBy: 'ケン', createdAt: '2026-01-02T09:21:00Z', updatedBy: 'マーティ', updatedAt: '2026-01-05T10:30:00Z', favoritesCount: 34, ordersCount: 13, } ]; const visibleProducts = products.filter((product) => product.tab === currentTab); const tabs = [ { key: 'draft', label: '下書き' }, { key: 'published', label: '公開済み' }, { key: 'archived', label: 'アーカイブ' }, ]; const labels = { emptyStateMessage: '商品が見つかりません', searchFieldPlaceholder: '商品を検索', actions: { createProduct: '商品を作成', createProductShort: '作成', filter: 'フィルター', }, headerRow: { productTitle: '商品タイトル', publicationPeriod: '公開期間', created: '作成', updated: '更新', favorites: 'お気に入り', orders: '注文', }, rowActions: { edit: '商品を編集', archive: '商品をアーカイブ', restore: '商品を復元', }, cellData: { sinceUnset: '未設定', untilUnset: '未設定', dateUnset: '公開期間なし', }, }; return ( <div style={{ minHeight: 450 }}> <ListProductsTable listProductsTableTitle="商品管理" createHref="#create" labels={labels} data={visibleProducts} tabs={tabs} currentTab={currentTab} onTabChange={setCurrentTab} onNavigate={(to) => setLastAction(`遷移先: ${to}`)} pagination={{ currentPage: page, totalPages: 1, onPageChange: setPage, }} /> <div style={{marginTop: 12, color: '#475569', fontSize: 13}}>{lastAction}</div> </div> ); }
検索状態、キーワードチップ、アクティブなコールバック、行ドロップダウンアクション、行および注文履歴のナビゲーション先を定義します。
function Example() { const [currentTab, setCurrentTab] = React.useState('published'); const [searchValue, setSearchValue] = React.useState(''); const [chips, setChips] = React.useState([ { key: 'filter-premium', label: 'プレミアム' } ]); const [lastAction, setLastAction] = React.useState('検索、フィルター、行アクションがここに表示されます。'); const products = [ { id: '1', tab: 'published', category: '家具', title: 'プレミアムエルゴノミックデスク', publication: { status: 'PUBLISHED', since: '2026-01-12' }, createdBy: 'サラ', createdAt: '2026-01-10T09:21:00Z', updatedBy: 'サラ', updatedAt: '2026-01-11T10:30:00Z', favoritesCount: 42, ordersCount: 8, } ]; const visibleProducts = products.filter((product) => product.tab === currentTab); const labels = { emptyStateMessage: 'カタログは空です', searchFieldPlaceholder: 'クイック検索...', actions: { createProduct: '新規商品を追加', createProductShort: '追加', filter: 'フィルターを適用', }, headerRow: { productTitle: 'カタログ項目', publicationPeriod: '利用可能日', created: '初回保存', updated: '最終更新', favorites: 'ブックマーク', orders: '販売数', }, rowActions: { edit: '詳細を変更', archive: 'アーカイブへ移動', restore: 'アーカイブ解除', }, cellData: { sinceUnset: '未定', untilUnset: '終了日なし', dateUnset: '利用可能期間が未設定', }, }; const handleChipDelete = (chipToDelete) => { setChips(prev => prev.filter(c => c.key !== chipToDelete.key)); }; const handleSearchSubmit = () => { if (searchValue.trim() && !chips.some(c => c.label === searchValue.trim())) { setChips(prev => [...prev, { key: `search-${Date.now()}`, label: searchValue.trim() }]); } setSearchValue(''); }; return ( <div style={{ minHeight: 450 }}> <ListProductsTable listProductsTableTitle="グローバルカタログ" createHref="#new-product" labels={labels} data={visibleProducts} tabs={[ { key: 'published', label: 'アクティブカタログ', subtitle: ' (1)' }, { key: 'archived', label: 'アーカイブ' } ]} currentTab={currentTab} onTabChange={setCurrentTab} // 検索とフィルター searchValue={searchValue} onSearchFieldChange={setSearchValue} onSearchSubmit={handleSearchSubmit} onFilterButtonClick={() => setLastAction('フィルタードロワーを開きました')} searchChipsTitle="アクティブなキーワード" searchChips={chips} onSearchChipDelete={handleChipDelete} // ナビゲーションと URL onNavigate={(to) => setLastAction(`ルーティング先: ${to}`)} rowHref={(row) => `#catalog/${row.id}`} ordersHref={(row) => `#catalog/${row.id}/orders`} // アクションハンドラーとリゾルバー onProductEdit={(id, title) => setLastAction(`項目 ${id} を編集中: ${title}`)} onProductArchive={(id, title) => setLastAction(`項目をアーカイブしました: ${title}`)} onProductUnarchive={(id) => setLastAction(`項目 ID を復元しました: ${id}`)} resolveRowAction={(row) => ['edit', 'archive']} shouldShowDropdownMenu={() => true} /> <div style={{marginTop: 12, color: '#475569', fontSize: 13}}>{lastAction}</div> </div> ); }
- 各行のクリック用リンク先を指定するには
rowHrefを使用します。行をクリックすると、生成された URL でonNavigateが自動的に呼び出されます。 ordersCount列内のアンカー先を定義するにはordersHrefを使用し、ユーザーが注文詳細に直接ドリルダウンできるようにします。- 行アクションメニューのドロップダウンに表示するオプションを選択的に制御するには、
'edit' | 'archive' | 'restore'の配列、またはundefinedを返すresolveRowAction関数を提供します。
レイアウトセクションを明示的に組み立てます。<ListProductsTable.Title> やその他のレイアウトセグメントに設定プロパティを直接渡します。
function Example() { const [currentTab, setCurrentTab] = React.useState('published'); const [lastAction, setLastAction] = React.useState('ナビゲーションフィードバックがここに表示されます。'); const products = [ { id: '1', category: '電子機器', title: 'スタジオモニタースピーカー', publication: { status: 'PUBLISHED', since: '2026-03-01' }, createdBy: 'アレックス', createdAt: '2026-02-28T09:00:00Z', updatedBy: 'アレックス', updatedAt: '2026-03-01T12:00:00Z', favoritesCount: 8, ordersCount: 4, } ]; const labels = { emptyStateMessage: 'リストは空です', searchFieldPlaceholder: '検索...', actions: { createProduct: '新規', filter: 'フィルター' }, headerRow: { productTitle: '項目', publicationPeriod: '期間', created: '作成', updated: '更新', favorites: 'お気に入り', orders: '注文', }, cellData: { sinceUnset: 'N/A', untilUnset: 'N/A', dateUnset: 'なし' }, }; return ( <div style={{ minHeight: 450 }}> <ListProductsTable listProductsTableTitle="カスタムリスト" createHref="#new" labels={labels} data={products} tabs={[{ key: 'published', label: '公開済み' }]} currentTab={currentTab} onTabChange={setCurrentTab} onNavigate={(to) => setLastAction(`遷移先: ${to}`)} > <ListProductsTable.Header sx={{ padding: '16px 24px', backgroundColor: '#fbfcfe', borderRadius: '8px 8px 0 0', borderBottom: '1px solid #e0e0e0', }} > <ListProductsTable.Title listProductsTableTitle="本番カタログ" /> <ListProductsTable.Action /> </ListProductsTable.Header> <ListProductsTable.Tabs /> <ListProductsTable.Content> <ListProductsTable.Table /> </ListProductsTable.Content> <ListProductsTable.Pagination /> </ListProductsTable> <div style={{marginTop: 12, color: '#475569', fontSize: 13}}>{lastAction}</div> </div> ); }
関数子のオーバーライドパターンを使って、ヘッダー、タブ、テーブルの間にブロックを並べ替えたり、警告や情報パネルを注入したりできます。
function Example() { const [currentTab, setCurrentTab] = React.useState('published'); const products = [ { id: '1', category: '家具', title: '手作りオーク製コンソール', publication: { status: 'PUBLISHED', since: '2026-01-12' }, createdBy: 'マーティ', createdAt: '2026-01-10T09:21:00Z', updatedBy: 'アリス', updatedAt: '2026-01-11T10:30:00Z', favoritesCount: 14, ordersCount: 22, } ]; const labels = { emptyStateMessage: '商品がありません', searchFieldPlaceholder: '検索...', actions: { createProduct: '作成', filter: 'フィルター' }, headerRow: { productTitle: 'タイトル', publicationPeriod: '期間', created: '作成', updated: '更新', favorites: 'いいね', orders: '注文', }, cellData: { sinceUnset: '-', untilUnset: '-', dateUnset: 'スケジュールなし' }, }; return ( <div style={{ minHeight: 500 }}> <ListProductsTable listProductsTableTitle="カスタムブロックテーブル" createHref="#new" labels={labels} data={products} tabs={[{ key: 'published', label: '公開済み' }]} currentTab={currentTab} onTabChange={setCurrentTab} onNavigate={() => {}} > {({ defaultBlocks, defaultBlockOrder }) => ({ blocks: { ...defaultBlocks, systemAlert: ( <div style={{ background: '#fff2e8', border: '1px solid #ffbb96', borderRadius: '6px', padding: '10px 16px', fontSize: '13px', color: '#d4380d', }} > 本日 02:00 UTC にシステムメンテナンスが予定されています。公開スケジュールが遅延する可能性があります。 </div> ), }, blockOrder: ['systemAlert', 'header', 'tabs', 'content', 'pagination'], })} </ListProductsTable> </div> ); }
ブロックのオーバーライドを使うタイミング
グローバルなステータスボード、フィルターパネル、サマリーカウンター、バナーを挿入する場合にオーバーライドを使用します。標準のブロック順は ['header', 'searchChips', 'tabs', 'content', 'pagination'](defaultBlockOrder)です。
主要プロパティ
コアプロパティ
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
data | ListProductsTableRowData[] | はい | - | 表示する商品データオブジェクトの配列 |
isLoading | boolean | いいえ | undefined | true のときテーブルレイアウトをローダーモードに切り替える |
onNavigate | (to: string) => void | いいえ | undefined | クリックアクション(行クリックや作成ボタンなど)で呼び出されるナビゲーションコールバック |
onProductEdit | (productId: string, title: string) => void | いいえ | undefined | 行メニューの「編集」アクションがクリックされたときに呼び出されるコールバック |
onProductArchive | (productId: string, title: string) => void | いいえ | undefined | 行メニューの「アーカイブ」アクションがクリックされたときに呼び出されるコールバック |
onProductUnarchive | (productId: string) => void | いいえ | undefined | 行メニューの「復元」アクションがクリックされたときに呼び出されるコールバック |
resolveRowAction | (row: ListProductsTableRowData) => ('edit' | 'archive' | 'restore')[] | undefined | いいえ | undefined | 行をアクティブなメニューアクションにマッピングする評価コールバック |
shouldShowDropdownMenu | (row: ListProductsTableRowData) => boolean | いいえ | undefined | 行メニューのドロップダウンボタンを表示または非表示にする条件チェック |
pagination | PaginationProps | いいえ | undefined | 状態付きページネーションコントロール: { currentPage, totalPages, onPageChange, className? } |
コンテンツプロパティ
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
listProductsTableTitle | ReactNode | はい | - | Title サブブロック内にレンダリングされるタイトルテキストまたはノード |
createHref | string | はい | - | 「商品を作成」アクションクリック時に onNavigate コールバックへ渡される href |
labels | { emptyStateMessage: string; searchFieldPlaceholder: string; actions: { createProduct: string; createProductShort?: string; filter: string }; headerRow: { productTitle: string; publicationPeriod: string; created: string; updated: string; favorites: string; orders: string }; rowActions?: { edit: string; archive: string; restore: string }; cellData: { sinceUnset: string; untilUnset: string; dateUnset: string } } | はい | - | UI テキスト設定を含む直接マップ |
rowHref | (row: ListProductsTableRowData) => string | いいえ | undefined | カスタム行リンク生成コールバック |
ordersHref | (row: ListProductsTableRowData) => string | いいえ | undefined | 注文数に適用される直接リンク生成コールバック |
tabs | { key: string; label: string; isDisabled?: boolean; subtitle?: string }[] | いいえ | undefined | 商品フィルタリング用タブ定義 |
currentTab | string | いいえ | undefined | 現在アクティブ/選択されているタブのキー |
onTabChange | (tab: string) => void | いいえ | undefined | 別のタブがクリックされたときに呼び出される切り替えコールバック |
searchValue | string | いいえ | undefined | アクティブな検索入力値 |
onSearchFieldChange | (value: string) => void | いいえ | undefined | 検索入力フィールド内で入力したときに呼び出されるコールバック |
onSearchSubmit | () => void | いいえ | undefined | 検索入力で Enter を押すか検索ボタンをクリックしたときに呼び出される |
onFilterButtonClick | () => void | いいえ | undefined | 検索横のフィルターボタンをクリックしたときに呼び出されるコールバック |
searchChipsTitle | ReactNode | いいえ | undefined | 検索チップコンテナの横に表示されるラベル |
searchChips | BaseTableSearchChip[] | いいえ | undefined | チップとしてレンダリングされるアクティブフィルター: { key, label } |
onSearchChipDelete | (chip: BaseTableSearchChip, index: number, event: SyntheticEvent) => void | いいえ | undefined | アクティブなフィルターチップを閉じたときに呼び出されるコールバック |
labels の構造
labels プロパティ内で次の構造に一致する翻訳と上書きを提供します:
{
emptyStateMessage: string;
searchFieldPlaceholder: string;
actions: {
createProduct: string;
createProductShort?: string;
filter: string;
};
headerRow: {
productTitle: string;
publicationPeriod: string;
created: string;
updated: string;
favorites: string;
orders: string;
};
rowActions?: {
edit: string;
archive: string;
restore: string;
};
cellData: {
sinceUnset: string;
untilUnset: string;
dateUnset: string;
};
}
レイアウトと構成プロパティ
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
children | BlocksOverride | ReactNode | いいえ | undefined | 複合子要素またはオーバーライド関数子 |
className | string | いいえ | undefined | ルート要素に適用されるカスタムスタイルクラス |
sx | SxProps | いいえ | undefined | 外側の Stack に渡される MUI SX スタイル上書き |
ListProductsTable は StackProps(children を除く)をすべて継承します。デフォルトのブロック順は ['header', 'searchChips', 'tabs', 'content', 'pagination'](defaultBlockOrder)です。
デフォルト UI ブロック
| ブロック | ベース | 備考 |
|---|---|---|
ListProductsTable (ルート) | BaseTable | テーブルコンテキスト全体をラップするメインの表形式ラッパーレイアウト |
ListProductsTable.Header | BaseTable.Header | タイトルとアクションを保持する Flex コンテナ |
ListProductsTable.Title | BaseTable.Header.Title | セクションヘッダーをレンダリング |
ListProductsTable.Action | BaseTable.Header.Actions | 検索入力、フィルター、作成アクションを保持する Flex コンテナ |
ListProductsTable.SearchChips | BaseTable.SearchChips | アクティブな検索フィルターを閉じられるチップとしてレンダリング |
ListProductsTable.Tabs | BaseTable.Tabs | テーブルフィルタリングに使用するレスポンシブタブ |
ListProductsTable.Content | BaseTable.Content | テーブルとローディングスピナー用の外側ブロックラッパー |
ListProductsTable.Loader | BaseTable.Content.Loader | 状態遷移中に表示されるスピナー |
ListProductsTable.Table | BaseTable.Content.Grid | 商品メタデータと行アクションを表示するプライマリテーブルグリッド |
ListProductsTable.Pagination | BaseTable.Pagination | フッターのページネーションコントロール |
TypeScript
import { ListProductsTable } from '@nodeblocks/frontend-list-products-table-block';
import type { ListProductsTableRowData } from '@nodeblocks/frontend-list-products-table-block';
const products: ListProductsTableRowData[] = [
{
id: 'prod-001',
category: '家電',
title: 'スマートオーブン Pro',
publication: { status: 'PUBLISHED', since: '2026-05-15T00:00:00Z' },
createdBy: 'システム管理者',
createdAt: '2026-05-10T09:21:00Z',
updatedBy: 'システム管理者',
updatedAt: '2026-05-11T10:30:00Z',
favoritesCount: 15,
ordersCount: 3,
}
];
const tabs = [
{ key: 'published', label: '公開済み商品' }
];
const labels = {
emptyStateMessage: '項目がありません',
searchFieldPlaceholder: 'カタログを検索',
actions: { createProduct: '新規', filter: 'フィルター' },
headerRow: {
productTitle: '商品',
publicationPeriod: '期間',
created: '作成',
updated: '更新',
favorites: 'お気に入り',
orders: '注文',
},
cellData: { sinceUnset: '-', untilUnset: '-', dateUnset: 'スケジュールなし' },
};
<ListProductsTable
listProductsTableTitle="アプリダッシュボード"
createHref="#new"
labels={labels}
data={products}
tabs={tabs}
currentTab="published"
/>;