List Order Table Block
ListOrderTable is an order management table built on BaseTable, with searchable header actions, optional search chips, tabs, row actions, and pagination.
Installationâ
- 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
What You Needâ
| Item | Why it matters |
|---|---|
labels | Copy for search/filter controls, columns, row actions, and empty state |
data | Table rows (ListOrderTableRowData) |
listOrderTableTitle | Header title |
tabs + currentTab + onTabChange (optional) | Filter rows by tab key |
searchValue + search chips + handlers (optional) | Controlled search input, chip list, and submit/delete flow |
pagination (optional) | Controlled page state |
| row actions + handlers (optional) | labels.rowActions + resolveRowAction + action handlers |
ListOrderTable does not own tab/search/pagination state. Keep currentTab, search draft/chips, and pagination in your app, then pass handlers (onTabChange, onSearchFieldChange, onSearchSubmit, pagination.onPageChange) to update state.
Code Examplesâ
- Quick Start
- Labels and URLs
- Compound Components
- Block Override
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: `Order ${i + 1}`, status: ['pending', 'processing', 'accepted', 'canceled'][i % 4], })); const tabs = [ {key: 'pending', label: 'Pending'}, {key: 'processing', label: 'Processing'}, {key: 'accepted', label: 'Accepted'}, {key: 'canceled', label: 'Canceled'}, ]; const labels = { emptyStateMessage: 'No orders found', searchFieldPlaceholder: 'Search orders...', actions: {filter: 'Filter'}, headerRow: {createdAt: 'Created', title: 'Title', ordererName: 'Orderer'}, rowActions: { markProcessing: 'Mark Processing', cancelOrder: 'Cancel Order', acceptOrder: 'Accept Order', revertToPending: 'Revert to Pending', sendMessage: 'Send message', }, }; const [currentTab, setCurrentTab] = React.useState('pending'); const [searchValue, setSearchValue] = React.useState(''); const [currentPage, setCurrentPage] = React.useState(1); const [lastAction, setLastAction] = React.useState('Search, filter, and row actions will show inline feedback here.'); 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="Order Management" labels={labels} data={data} tabs={tabs} currentTab={currentTab} onTabChange={tab => { setCurrentTab(tab); setCurrentPage(1); }} searchValue={searchValue} onSearchFieldChange={setSearchValue} onSearchSubmit={() => setLastAction(`Search submitted: ${searchValue}`)} onFilterButtonClick={() => setLastAction('Filter clicked')} pagination={{ currentPage, totalPages, onPageChange: setCurrentPage, }} rowHref={row => `/orders/${row.id}`} onNavigate={to => setLastAction(`Navigate: ${to}`)} /> <div style={{marginTop: 12, color: '#475569', fontSize: 13}}>{lastAction}</div> </> ); }
Customize labels, tab copy, search copy, and row navigation behavior.
function Example() { const labels = { emptyStateMessage: 'No matching orders', searchFieldPlaceholder: 'Search by title or orderer', actions: {filter: 'Open filters'}, headerRow: {createdAt: 'Created at', title: 'Order title', ordererName: 'Orderer'}, rowActions: { markProcessing: 'Start processing', cancelOrder: 'Cancel', acceptOrder: 'Accept', revertToPending: 'Revert', sendMessage: 'Message', }, }; const data = [ { id: '101', createdAt: new Date(2024, 3, 10).toISOString(), ordererName: 'ORD-101', title: 'Premium Plan Order', status: 'pending', }, ]; return ( <ListOrderTable listOrderTableTitle="Orders" labels={labels} data={data} tabs={[{key: 'pending', label: 'Pending'}]} currentTab="pending" onTabChange={() => {}} searchValue="" onSearchFieldChange={() => {}} onSearchSubmit={() => {}} onFilterButtonClick={() => {}} rowHref={row => `/admin/orders/${row.id}`} onNavigate={() => {}} /> ); }
Pass searchChipsTitle, searchChips, and onSearchChipDelete to render ListOrderTable.SearchChips between header and tabs. Omit chips when there are no active keywords (same behavior as Storybook examples).
Compose header/search chips/tabs/content/pagination explicitly.
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: `Order ${i + 1}`, status: ['pending', 'processing', 'accepted'][i % 3], })); const labels = { emptyStateMessage: 'No orders found', searchFieldPlaceholder: 'Search orders...', actions: {filter: 'Filter'}, headerRow: {createdAt: 'Created', title: 'Title', ordererName: 'Orderer'}, rowActions: { markProcessing: 'Mark Processing', cancelOrder: 'Cancel Order', acceptOrder: 'Accept Order', revertToPending: 'Revert To Pending', sendMessage: 'Send message', }, }; const tabs = [ {key: 'pending', label: 'Pending'}, {key: 'processing', label: 'Processing'}, {key: 'accepted', label: 'Accepted'}, ]; 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('Inline status will update when you search or click filter.'); const data = allOrderData.filter(row => row.status === currentTab).slice(0, 5); return ( <ListOrderTable listOrderTableTitle="Order Management" labels={labels} data={data} tabs={tabs} currentTab={currentTab} onTabChange={tab => setCurrentTab(tab)} > <ListOrderTable.Header> <ListOrderTable.Title listOrderTableTitle="Order Management" /> <ListOrderTable.Action labels={labels} searchValue={searchValue} onSearchFieldChange={setSearchValue} onSearchSubmit={() => setLastAction(`Search: ${searchValue}`)} onFilterButtonClick={() => setLastAction('Filter clicked')} /> </ListOrderTable.Header> <ListOrderTable.Tabs /> <ListOrderTable.Content> {isLoading ? ( <ListOrderTable.Loader /> ) : ( <ListOrderTable.Table labels={labels} data={data} rowHref={row => `/orders/${row.id}`} onNavigate={to => setLastAction(`Navigate: ${to}`)} /> )} </ListOrderTable.Content> <ListOrderTable.Pagination pagination={{currentPage, totalPages: 1, onPageChange: setCurrentPage}} data={data} /> <div style={{marginTop: 12, color: '#475569', fontSize: 13}}>{lastAction}</div> </ListOrderTable> ); }
Use function children to prepend custom blocks and control order.
function Example() { const labels = { emptyStateMessage: 'No orders found', searchFieldPlaceholder: 'Search orders...', actions: {filter: 'Filter'}, headerRow: {createdAt: 'Created', title: 'Title', ordererName: 'Orderer'}, rowActions: { markProcessing: 'Mark Processing', cancelOrder: 'Cancel Order', acceptOrder: 'Accept Order', revertToPending: 'Revert To Pending', sendMessage: 'Send message', }, }; const data = [ { id: '1', createdAt: new Date(2024, 0, 1).toISOString(), ordererName: 'ORD-001', title: 'Order 1', status: 'pending', }, ]; return ( <ListOrderTable listOrderTableTitle="Order Management" labels={labels} data={data} tabs={[{key: 'pending', label: 'Pending'}]} 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, }} > Custom notification: showing {data.length} orders </div> ), }, blockOrder: ['customNotification', ...defaultBlockOrder], })} </ListOrderTable> ); }
When to use block overrides
Use overrides when you need custom banners or status panels before the table layout while keeping default table behavior. defaultBlockOrder is header, searchChips, tabs, content, pagination and defaultBlocks keys also include title, action, loader, and table.
Important Propsâ
Core Propsâ
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
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 } } | Yes | - | UI copy for table, actions, and empty state |
data | ListOrderTableRowData[] | Yes | - | Table rows |
listOrderTableTitle | ReactNode | Yes | - | Header title |
isLoading | boolean | No | undefined | Loading state for content/pagination |
searchValue | string | No | undefined | Controlled search input value |
onSearchFieldChange | (value: string) => void | No | undefined | Search input change handler |
onSearchSubmit | () => void | No | undefined | Triggered by search icon click or Enter key |
onFilterButtonClick | () => void | No | undefined | Filter button handler |
searchChipsTitle | ReactNode | No | undefined | Label above active search chips |
searchChips | BaseTableSearchChip[] | No | undefined | Active search chips |
onSearchChipDelete | (chip: BaseTableSearchChip, index: number, event: SyntheticEvent) => void | No | undefined | Remove-chip handler |
tabs | { key: string; label: string; isDisabled?: boolean; subtitle?: string }[] | No | [{ key: 'pending', label: 'Pending' }, { key: 'processing', label: 'Processing' }, { key: 'accepted', label: 'Accepted' }, { key: 'canceled', label: 'Canceled' }] | Tab definitions |
currentTab | string | No | first tabs key | Active tab key |
onTabChange | (tab: string) => void | No | undefined | Tab change handler |
pagination | { currentPage: number; totalPages: number; onPageChange: (page: number) => void; className?: string } | No | undefined | Page controls (pages are counted from 1) |
rowHref | (row: ListOrderTableRowData) => string | No | undefined | Build row link; requires onNavigate |
onNavigate | (to: string) => void | No | undefined | Called when row click resolves rowHref |
shouldShowDropdownMenu | (row: ListOrderTableRowData) => boolean | No | undefined | Hide/show row menu per row |
resolveRowAction | (row: ListOrderTableRowData) => ('markProcessing' | 'cancelOrder' | 'acceptOrder' | 'revertToPending' | 'sendMessage')[] | undefined | No | undefined | Row action types to render |
onOrderProcessing | (id: string, title?: string) => void | No | undefined | Handler for markProcessing action |
onOrderCanceled | (id: string, title?: string) => void | No | undefined | Handler for cancelOrder action |
onOrderAccepted | (id: string, title?: string) => void | No | undefined | Handler for acceptOrder action |
onOrderRevertedToPending | (id: string, title?: string) => void | No | undefined | Handler for revertToPending action |
onOrderMessageSent | (id: string, title?: string) => void | No | undefined | Handler for sendMessage action |
ListOrderTableRowData shape:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
createdAt | string | Yes | - | ISO datetime string used in created-at column |
id | string | Yes | - | Unique row id |
ordererName | string | Yes | - | Main orderer name text |
ordererNameRuby | string | No | undefined | Optional second line under ordererName |
title | string | Yes | - | Order title |
status | string | Yes | - | Status string carried with the row data |
Content Propsâ
labels keys:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
labels.emptyStateMessage | string | Yes | - | Empty table message |
labels.searchFieldPlaceholder | string | Yes | - | Search input placeholder |
labels.actions.filter | string | Yes | - | Filter button label |
labels.headerRow.createdAt | string | Yes | - | Created-at header |
labels.headerRow.title | string | Yes | - | Title header |
labels.headerRow.ordererName | string | Yes | - | Orderer header |
labels.rowActions.markProcessing | string | No | undefined | Row action label |
labels.rowActions.cancelOrder | string | No | undefined | Row action label |
labels.rowActions.acceptOrder | string | No | undefined | Row action label |
labels.rowActions.revertToPending | string | No | undefined | Row action label |
labels.rowActions.sendMessage | string | No | undefined | Row action label |
Subcomponents:
| Component | Prop | Type | Required | Default | Description |
|---|---|---|---|---|---|
Title | listOrderTableTitle | ReactNode | No | Root listOrderTableTitle | Title text (children wins) |
Title | children | ReactNode | No | Root title | Custom title markup |
Action | labels | { searchFieldPlaceholder: string; actions: { filter: string } } | No | Root labels | Search input + filter action copy |
Action | searchValue | string | No | Root searchValue | Controlled search input |
Action | onSearchFieldChange | (value: string) => void | No | Root handler | Search input change |
Action | onSearchSubmit | () => void | No | Root handler | Search submit |
Action | onFilterButtonClick | () => void | No | Root handler | Filter click |
Action | children | ReactNode | No | Built search + filter controls | Custom action content |
SearchChips | searchChipsTitle | ReactNode | No | Root searchChipsTitle | Chips group title |
SearchChips | searchChips | BaseTableSearchChip[] | No | Root searchChips | Chips data |
SearchChips | onSearchChipDelete | (chip, index, event) => void | No | Root handler | Delete-chip callback |
Tabs | value | string | No | Root currentTab | Override active tab value |
Tabs | onChange | TabsProps['onChange'] | No | Root onTabChange | MUI tab change callback |
Tabs | tabProps | MUI Tab props excluding label, value, and disabled | No | undefined | Props forwarded to each MUI tab |
Table | columns | BaseTableColumn<ListOrderTableRowData>[] | No | From labels.headerRow | Override grid columns |
Table | rowActions | (row: ListOrderTableRowData) => BaseTableRowAction<ListOrderTableRowData>[] | No | Generated from row-action props | Override row actions |
Table | actionColumn | BaseTableActionColumn | No | { pin: 'right' } when row actions exist | Action column config |
Table | rowMenu | ReactNode or function | No | Default menu | Custom row menu UI |
Table | onRowClick | (row: ListOrderTableRowData) => void | No | Derived from rowHref + onNavigate | Row click callback |
Loader | children | ReactNode | No | Default loader | Loading content |
Pagination | pagination | { currentPage: number; totalPages: number; onPageChange: (page: number) => void; className?: string } | No | Root pagination | Pagination controls |
Pagination | data | ListOrderTableRowData[] | No | Root data | Row count source |
Pagination | isLoading | boolean | No | Root isLoading | Hides pagination while loading and when no rows exist |
Title, Action, Header, Loader, SearchChips, Tabs, Content, Table, and Pagination are ListOrderTable.Title, etc. ListOrderTable.Tabs reads the tab definitions from the root tabs prop.
Layout and Composition Propsâ
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children | BlocksOverride | ReactNode | No | undefined | Compound JSX children or function override returning blocks and blockOrder |
className | string | No | undefined | Class on root container (nbb-list-order-table-container) |
sx | SxProps | No | undefined | MUI system styles for root |
ListOrderTable renders a BaseTable root with headerTitle, headerActions, tabs, rows, row actions, no-rows overlay, and optional pagination. It inherits StackProps passthrough (except children). defaultBlockOrder is header, searchChips, tabs, content, pagination.
Default UI Blocksâ
| Block | Built on | Notes |
|---|---|---|
ListOrderTable (root) | BaseTable | Root table wrapper |
ListOrderTable.Title | BaseTable.Header.Title | Title from listOrderTableTitle |
ListOrderTable.Action | BaseTable.Header.Actions + TextField + Button | Search field + filter button |
ListOrderTable.Header | BaseTable.Header | Wraps title/actions |
ListOrderTable.SearchChips | BaseTable.SearchChips | Active keyword chips block |
ListOrderTable.Tabs | BaseTable.Tabs | Tab navigation from tabs |
ListOrderTable.Loader | BaseTable.Content.Loader | Loading state |
ListOrderTable.Content | BaseTable.Content | Content wrapper |
ListOrderTable.Table | BaseTable.Content.Grid | Data grid with default columns/actions |
ListOrderTable.Pagination | BaseTable.Pagination | Page controls |
| no-rows overlay icon | PersonOutlined | Used when data is empty |
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: 'Order 1',
status: 'pending',
},
];
const chips: BaseTableSearchChip[] = [{key: 'keyword-1', label: 'ORD-001'}];
<ListOrderTable
listOrderTableTitle="Order Management"
labels={{
emptyStateMessage: 'No orders found',
searchFieldPlaceholder: 'Search orders...',
actions: {filter: 'Filter'},
headerRow: {createdAt: 'Created', title: 'Title', ordererName: 'Orderer'},
}}
data={rows}
searchChipsTitle="Search Keywords"
searchChips={chips}
/>;