ListOrderTableブロック
ListOrderTable は、BaseTable 上に構築された注文管理テーブルで、検索可能なヘッダーアクション、任意の検索チップ、タブ、行アクション、ページネーションを備えています。
インストール
- npm
- yarn
- pnpm
- bun
npm install @nodeblocks/frontend-list-order-table-block
yarn add @nodeblocks/frontend-list-order-table-block
pnpm add @nodeblocks/frontend-list-order-table-block
bun add @nodeblocks/frontend-list-order-table-block
必要なもの
| 項目 | 用途 |
|---|---|
labels | 検索/フィルターコントロール、列、行アクション、空状態のコピー |
data | テーブル行(ListOrderTableRowData) |
listOrderTableTitle | ヘッダータイトル |
tabs + currentTab + onTabChange (任意) | タブキーで行をフィルタリング |
searchValue + 検索チップ + ハンドラー (任意) | 制御された検索入力、チップリスト、送信/削除フロー |
pagination (任意) | 制御されたページ状態 |
| 行アクション + ハンドラー (任意) | labels.rowActions + resolveRowAction + アクションハンドラー |
ListOrderTable はタブ/検索/ページネーションの状態を保持しません。currentTab、検索の下書き/チップ、ページネーションをアプリ側で管理し、ハンドラー(onTabChange、onSearchFieldChange、onSearchSubmit、pagination.onPageChange)を渡して状態を更新してください。
コード例
- クイックスタート
- ラベルと URL
- 複合コンポーネント
- ブロックのオーバーライド
function Example() { const allOrderData = Array.from({length: 20}, (_, i) => ({ id: String(i + 1), createdAt: new Date(2024, 0, i + 1).toISOString(), ordererName: `ORD-${String(i + 1).padStart(3, '0')}`, ordererNameRuby: `オーダー ${i + 1}`, title: `注文 ${i + 1}`, status: ['pending', 'processing', 'accepted', 'canceled'][i % 4], })); const tabs = [ {key: 'pending', label: '保留中'}, {key: 'processing', label: '処理中'}, {key: 'accepted', label: '承認済み'}, {key: 'canceled', label: 'キャンセル済み'}, ]; const labels = { emptyStateMessage: '注文が見つかりません', searchFieldPlaceholder: '注文を検索...', actions: {filter: 'フィルター'}, headerRow: {createdAt: '作成日', title: 'タイトル', ordererName: '注文者'}, rowActions: { markProcessing: '処理中にする', cancelOrder: '注文をキャンセル', acceptOrder: '注文を承認', revertToPending: '保留中に戻す', sendMessage: 'メッセージを送信', }, }; const [currentTab, setCurrentTab] = React.useState('pending'); const [searchValue, setSearchValue] = React.useState(''); const [currentPage, setCurrentPage] = React.useState(1); const [lastAction, setLastAction] = React.useState('検索、フィルター、行アクションのフィードバックがここに表示されます。'); const itemsPerPage = 5; const filtered = allOrderData.filter(row => row.status === currentTab); const totalPages = Math.max(1, Math.ceil(filtered.length / itemsPerPage)); const start = (currentPage - 1) * itemsPerPage; const data = filtered.slice(start, start + itemsPerPage); return ( <> <ListOrderTable listOrderTableTitle="注文管理" labels={labels} data={data} tabs={tabs} currentTab={currentTab} onTabChange={tab => { setCurrentTab(tab); setCurrentPage(1); }} searchValue={searchValue} onSearchFieldChange={setSearchValue} onSearchSubmit={() => setLastAction(`検索送信: ${searchValue}`)} onFilterButtonClick={() => setLastAction('フィルタークリック')} pagination={{ currentPage, totalPages, onPageChange: setCurrentPage, }} rowHref={row => `/orders/${row.id}`} onNavigate={to => setLastAction(`ナビゲート: ${to}`)} /> <div style={{marginTop: 12, color: '#475569', fontSize: 13}}>{lastAction}</div> </> ); }
ラベル、タブのコピー、検索のコピー、行ナビゲーションの動作をカスタマイズします。
function Example() { const labels = { emptyStateMessage: '一致する注文がありません', searchFieldPlaceholder: 'タイトルまたは注文者で検索', actions: {filter: 'フィルターを開く'}, headerRow: {createdAt: '作成日時', title: '注文タイトル', ordererName: '注文者'}, rowActions: { markProcessing: '処理を開始', cancelOrder: 'キャンセル', acceptOrder: '承認', revertToPending: '戻す', sendMessage: 'メッセージ', }, }; const data = [ { id: '101', createdAt: new Date(2024, 3, 10).toISOString(), ordererName: 'ORD-101', title: 'プレミアムプラン注文', status: 'pending', }, ]; return ( <ListOrderTable listOrderTableTitle="注文" labels={labels} data={data} tabs={[{key: 'pending', label: '保留中'}]} currentTab="pending" onTabChange={() => {}} searchValue="" onSearchFieldChange={() => {}} onSearchSubmit={() => {}} onFilterButtonClick={() => {}} rowHref={row => `/admin/orders/${row.id}`} onNavigate={() => {}} /> ); }
searchChipsTitle、searchChips、onSearchChipDelete を渡すと、ヘッダーとタブの間に ListOrderTable.SearchChips がレンダリングされます。アクティブなキーワードがない場合はチップを省略してください(Storybook の例と同じ動作)。
ヘッダー/検索チップ/タブ/コンテンツ/ページネーションを明示的に構成します。
function Example() { const allOrderData = Array.from({length: 12}, (_, i) => ({ id: String(i + 1), createdAt: new Date(2024, 0, i + 1).toISOString(), ordererName: `ORD-${String(i + 1).padStart(3, '0')}`, title: `注文 ${i + 1}`, status: ['pending', 'processing', 'accepted'][i % 3], })); const labels = { emptyStateMessage: '注文が見つかりません', searchFieldPlaceholder: '注文を検索...', actions: {filter: 'フィルター'}, headerRow: {createdAt: '作成日', title: 'タイトル', ordererName: '注文者'}, rowActions: { markProcessing: '処理中にする', cancelOrder: '注文をキャンセル', acceptOrder: '注文を承認', revertToPending: '保留中に戻す', sendMessage: 'メッセージを送信', }, }; const tabs = [ {key: 'pending', label: '保留中'}, {key: 'processing', label: '処理中'}, {key: 'accepted', label: '承認済み'}, ]; const [currentTab, setCurrentTab] = React.useState('pending'); const [isLoading, setIsLoading] = React.useState(false); const [currentPage, setCurrentPage] = React.useState(1); const [searchValue, setSearchValue] = React.useState(''); const [lastAction, setLastAction] = React.useState('検索またはフィルターをクリックすると、インラインステータスが更新されます。'); const data = allOrderData.filter(row => row.status === currentTab).slice(0, 5); return ( <ListOrderTable listOrderTableTitle="注文管理" labels={labels} data={data} tabs={tabs} currentTab={currentTab} onTabChange={tab => setCurrentTab(tab)} > <ListOrderTable.Header> <ListOrderTable.Title listOrderTableTitle="注文管理" /> <ListOrderTable.Action labels={labels} searchValue={searchValue} onSearchFieldChange={setSearchValue} onSearchSubmit={() => setLastAction(`検索: ${searchValue}`)} onFilterButtonClick={() => setLastAction('フィルタークリック')} /> </ListOrderTable.Header> <ListOrderTable.Tabs /> <ListOrderTable.Content> {isLoading ? ( <ListOrderTable.Loader /> ) : ( <ListOrderTable.Table labels={labels} data={data} rowHref={row => `/orders/${row.id}`} onNavigate={to => setLastAction(`ナビゲート: ${to}`)} /> )} </ListOrderTable.Content> <ListOrderTable.Pagination pagination={{currentPage, totalPages: 1, onPageChange: setCurrentPage}} data={data} /> <div style={{marginTop: 12, color: '#475569', fontSize: 13}}>{lastAction}</div> </ListOrderTable> ); }
関数形式の children を使い、カスタムブロックを前置し、順序を制御します。
function Example() { const labels = { emptyStateMessage: '注文が見つかりません', searchFieldPlaceholder: '注文を検索...', actions: {filter: 'フィルター'}, headerRow: {createdAt: '作成日', title: 'タイトル', ordererName: '注文者'}, rowActions: { markProcessing: '処理中にする', cancelOrder: '注文をキャンセル', acceptOrder: '注文を承認', revertToPending: '保留中に戻す', sendMessage: 'メッセージを送信', }, }; const data = [ { id: '1', createdAt: new Date(2024, 0, 1).toISOString(), ordererName: 'ORD-001', title: '注文 1', status: 'pending', }, ]; return ( <ListOrderTable listOrderTableTitle="注文管理" labels={labels} data={data} tabs={[{key: 'pending', label: '保留中'}]} currentTab="pending" onTabChange={() => {}} searchValue="" onSearchFieldChange={() => {}} onSearchSubmit={() => {}} onFilterButtonClick={() => {}} > {({defaultBlocks, defaultBlockOrder}) => ({ blocks: { ...defaultBlocks, customNotification: ( <div style={{ marginBottom: 12, padding: 12, background: '#eef4ff', border: '1px solid #cddcff', borderRadius: 8, fontSize: 14, }} > カスタム通知: {data.length} 件の注文を表示中 </div> ), }, blockOrder: ['customNotification', ...defaultBlockOrder], })} </ListOrderTable> ); }
ブロックのオーバーライドを使うタイミング
テーブルレイアウトの前にカスタムバナーやステータスパネルが必要な場合にオーバーライドを使用し、デフォルトのテーブル動作は維持します。defaultBlockOrder は header、searchChips、tabs、content、pagination で、defaultBlocks のキーには title、action、loader、table も含まれます。
主要プロパティ
コアプロパティ
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
labels | { emptyStateMessage: string; searchFieldPlaceholder: string; actions: { filter: string }; headerRow: { createdAt: string; title: string; ordererName: string }; rowActions?: { markProcessing: string; cancelOrder: string; acceptOrder: string; revertToPending: string; sendMessage: string } } | はい | - | テーブル、アクション、空状態の UI コピー |
data | ListOrderTableRowData[] | はい | - | テーブル行 |
listOrderTableTitle | ReactNode | はい | - | ヘッダータイトル |
isLoading | boolean | いいえ | undefined | コンテンツ/ページネーションのローディング状態 |
searchValue | string | いいえ | undefined | 制御された検索入力値 |
onSearchFieldChange | (value: string) => void | いいえ | undefined | 検索入力変更ハンドラー |
onSearchSubmit | () => void | いいえ | undefined | 検索アイコンクリックまたは Enter キーでトリガー |
onFilterButtonClick | () => void | いいえ | undefined | フィルターボタンのハンドラー |
searchChipsTitle | ReactNode | いいえ | undefined | アクティブな検索チップの上のラベル |
searchChips | BaseTableSearchChip[] | いいえ | undefined | アクティブな検索チップ |
onSearchChipDelete | (chip: BaseTableSearchChip, index: number, event: SyntheticEvent) => void | いいえ | undefined | チップ削除ハンドラー |
tabs | { key: string; label: string; isDisabled?: boolean; subtitle?: string }[] | いいえ | [{ key: 'pending', label: '保留中' }, { key: 'processing', label: '処理中' }, { key: 'accepted', label: '承認済み' }, { key: 'canceled', label: 'キャンセル済み' }] | タブ定義 |
currentTab | string | いいえ | 最初の tabs キー | アクティブなタブキー |
onTabChange | (tab: string) => void | いいえ | undefined | タブ変更ハンドラー |
pagination | { currentPage: number; totalPages: number; onPageChange: (page: number) => void; className?: string } | いいえ | undefined | ページコントロール(ページは 1 から数える) |
rowHref | (row: ListOrderTableRowData) => string | いいえ | undefined | 行リンクを構築する。onNavigate が必要 |
onNavigate | (to: string) => void | いいえ | undefined | 行クリックで rowHref が解決されたときに呼ばれる |
shouldShowDropdownMenu | (row: ListOrderTableRowData) => boolean | いいえ | undefined | 行ごとに行メニューを表示/非表示 |
resolveRowAction | (row: ListOrderTableRowData) => ('markProcessing' | 'cancelOrder' | 'acceptOrder' | 'revertToPending' | 'sendMessage')[] | undefined | いいえ | undefined | レンダリングする行アクションの種類 |
onOrderProcessing | (id: string, title?: string) => void | いいえ | undefined | markProcessing アクションのハンドラー |
onOrderCanceled | (id: string, title?: string) => void | いいえ | undefined | cancelOrder アクションのハンドラー |
onOrderAccepted | (id: string, title?: string) => void | いいえ | undefined | acceptOrder アクションのハンドラー |
onOrderRevertedToPending | (id: string, title?: string) => void | いいえ | undefined | revertToPending アクションのハンドラー |
onOrderMessageSent | (id: string, title?: string) => void | いいえ | undefined | sendMessage アクションのハンドラー |
ListOrderTableRowData の形状:
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
createdAt | string | はい | - | 作成日列で使用する ISO 日時文字列 |
id | string | はい | - | 一意の行 ID |
ordererName | string | はい | - | メインの注文者名テキスト |
ordererNameRuby | string | いいえ | undefined | ordererName の下の任意の2行目 |
title | string | はい | - | 注文タイトル |
status | string | はい | - | 行データに付随するステータス文字列 |
コンテンツプロパティ
labels キー:
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
labels.emptyStateMessage | string | はい | - | 空のテーブルのメッセージ |
labels.searchFieldPlaceholder | string | はい | - | 検索入力のプレースホルダー |
labels.actions.filter | string | はい | - | フィルターボタンのラベル |
labels.headerRow.createdAt | string | はい | - | 作成日ヘッダー |
labels.headerRow.title | string | はい | - | タイトルヘッダー |
labels.headerRow.ordererName | string | はい | - | 注文者ヘッダー |
labels.rowActions.markProcessing | string | いいえ | undefined | 行アクションのラベル |
labels.rowActions.cancelOrder | string | いいえ | undefined | 行アクションのラベル |
labels.rowActions.acceptOrder | string | いいえ | undefined | 行アクションのラベル |
labels.rowActions.revertToPending | string | いいえ | undefined | 行アクションのラベル |
labels.rowActions.sendMessage | string | いいえ | undefined | 行アクションのラベル |
サブコンポーネント:
| コンポーネント | プロパティ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|---|
Title | listOrderTableTitle | ReactNode | いいえ | ルートの listOrderTableTitle | タイトルテキスト(children が優先) |
Title | children | ReactNode | いいえ | ルートのタイトル | カスタムタイトルマークアップ |
Action | labels | { searchFieldPlaceholder: string; actions: { filter: string } } | いいえ | ルートの labels | 検索入力 + フィルターアクションのコピー |
Action | searchValue | string | いいえ | ルートの searchValue | 制御された検索入力 |
Action | onSearchFieldChange | (value: string) => void | いいえ | ルートのハンドラー | 検索入力変更 |
Action | onSearchSubmit | () => void | いいえ | ルートのハンドラー | 検索送信 |
Action | onFilterButtonClick | () => void | いいえ | ルートのハンドラー | フィルタークリック |
Action | children | ReactNode | いいえ | 組み込みの検索 + フィルターコントロール | カスタムアクションコンテンツ |
SearchChips | searchChipsTitle | ReactNode | いいえ | ルートの searchChipsTitle | チップグループのタイトル |
SearchChips | searchChips | BaseTableSearchChip[] | いいえ | ルートの searchChips | チップデータ |
SearchChips | onSearchChipDelete | (chip, index, event) => void | いいえ | ルートのハンドラー | チップ削除コールバック |
Tabs | value | string | いいえ | ルートの currentTab | アクティブなタブ値をオーバーライド |
Tabs | onChange | TabsProps['onChange'] | いいえ | ルートの onTabChange | MUI タブ変更コールバック |
Tabs | tabProps | MUI Tab props excluding label, value, and disabled | いいえ | undefined | 各 MUI タブに転送されるプロパティ |
Table | columns | BaseTableColumn<ListOrderTableRowData>[] | いいえ | labels.headerRow から | グリッド列をオーバーライド |
Table | rowActions | (row: ListOrderTableRowData) => BaseTableRowAction<ListOrderTableRowData>[] | いいえ | 行アクションプロパティから生成 | 行アクションをオーバーライド |
Table | actionColumn | BaseTableActionColumn | いいえ | 行アクションがある場合 { pin: 'right' } | アクション列の設定 |
Table | rowMenu | ReactNode or function | いいえ | デフォルトメニュー | カスタム行メニュー UI |
Table | onRowClick | (row: ListOrderTableRowData) => void | いいえ | rowHref + onNavigate から導出 | 行クリックコールバック |
Loader | children | ReactNode | いいえ | デフォルトローダー | ローディングコンテンツ |
Pagination | pagination | { currentPage: number; totalPages: number; onPageChange: (page: number) => void; className?: string } | いいえ | ルートの pagination | ページネーションコントロール |
Pagination | data | ListOrderTableRowData[] | いいえ | ルートの data | 行数のソース |
Pagination | isLoading | boolean | いいえ | ルートの isLoading | ローディング中および行がない場合にページネーションを非表示 |
Title、Action、Header、Loader、SearchChips、Tabs、Content、Table、Pagination は ListOrderTable.Title などです。ListOrderTable.Tabs はルートの tabs プロパティからタブ定義を読み取ります。
レイアウトと構成プロパティ
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
children | BlocksOverride | ReactNode | いいえ | undefined | 複合 JSX の子、または blocks と blockOrder を返す関数オーバーライド |
className | string | いいえ | undefined | ルートコンテナ(nbb-list-order-table-container)のクラス |
sx | SxProps | いいえ | undefined | ルート用の MUI システムスタイル |
ListOrderTable は headerTitle、headerActions、タブ、行、行アクション、行なしオーバーレイ、任意のページネーションを備えた BaseTable ルートをレンダリングします。StackProps(children を除く)を継承します。defaultBlockOrder は header、searchChips、tabs、content、pagination です。
デフォルト UI ブロック
| ブロック | ベース | 備考 |
|---|---|---|
ListOrderTable (ルート) | BaseTable | ルートテーブルラッパー |
ListOrderTable.Title | BaseTable.Header.Title | listOrderTableTitle からのタイトル |
ListOrderTable.Action | BaseTable.Header.Actions + TextField + Button | 検索フィールド + フィルターボタン |
ListOrderTable.Header | BaseTable.Header | タイトル/アクションをラップ |
ListOrderTable.SearchChips | BaseTable.SearchChips | アクティブなキーワードチップブロック |
ListOrderTable.Tabs | BaseTable.Tabs | tabs からのタブナビゲーション |
ListOrderTable.Loader | BaseTable.Content.Loader | ローディング状態 |
ListOrderTable.Content | BaseTable.Content | コンテンツラッパー |
ListOrderTable.Table | BaseTable.Content.Grid | デフォルト列/アクション付きデータグリッド |
ListOrderTable.Pagination | BaseTable.Pagination | ページコントロール |
| 行なしオーバーレイアイコン | PersonOutlined | data が空のときに使用 |
TypeScript
import {ListOrderTable, ListOrderTableRowData, BaseTableSearchChip} from '@nodeblocks/frontend-list-order-table-block';
const rows: ListOrderTableRowData[] = [
{
id: '1',
createdAt: new Date().toISOString(),
ordererName: 'ORD-001',
ordererNameRuby: 'オーダー 1',
title: '注文 1',
status: 'pending',
},
];
const chips: BaseTableSearchChip[] = [{key: 'keyword-1', label: 'ORD-001'}];
<ListOrderTable
listOrderTableTitle="注文管理"
labels={{
emptyStateMessage: '注文が見つかりません',
searchFieldPlaceholder: '注文を検索...',
actions: {filter: 'フィルター'},
headerRow: {createdAt: '作成日', title: 'タイトル', ordererName: '注文者'},
}}
data={rows}
searchChipsTitle="検索キーワード"
searchChips={chips}
/>;