List Products Table Block
The ListProductsTable Component is a fully customizable and accessible products table interface built with React and TypeScript. It provides a complete tabular product listing experience with modern design patterns, sortable columns, action dropdowns, tabs, publication status management, pagination support, loading states, and flexible customization options for advanced product management applications.
π Installationβ
npm install @nodeblocks/frontend-list-products-table-block@0.2.0
π Usageβ
import {ListProductsTable} from '@nodeblocks/frontend-list-products-table-block';
- Basic Usage
- Advanced Usage
function BasicListProductsTable() { const [currentTab, setCurrentTab] = useState('published'); const [isLoading] = useState(false); const productData = [ { id: '1', title: 'Premium Office Chair', category: 'Furniture', createdAt: '2024-01-15T10:30:00Z', updatedAt: '2024-01-16T14:20:00Z', numberOfApplicants: '5', publication: { since: '2024-01-15T00:00:00Z', until: '2024-03-15T23:59:59Z', status: 'ACTIVE', }, }, { id: '2', title: 'Wireless Headphones', category: 'Electronics', createdAt: '2024-01-14T09:15:00Z', updatedAt: '2024-01-14T09:15:00Z', numberOfApplicants: '12', publication: { since: '2024-01-20T00:00:00Z', until: '2024-04-20T23:59:59Z', status: 'INACTIVE', }, }, ]; const tabs = [{label: 'Published Products'}, {label: 'Draft Products'}, {label: 'Archived Products'}]; const labels = { emptyStateMessage: 'No products found', actions: { createProduct: 'Create Product', }, headerRow: { title: 'Product Title', publicationPeriod: 'Publication Period', createdAt: 'Created At', updatedAt: 'Updated At', numberOfApplicants: 'Applicants', }, rowActions: { public: 'Make Public', private: 'Make Private', edit: 'Edit Product', archive: 'Archive Product', unarchive: 'Unarchive Product', }, unsetData: { since: 'Not set', until: 'Not set', date: 'No publication period', }, }; const handleProductPublic = (productId, title) => { console.log('Product made public:', productId, title); }; const handleProductPrivate = (productId, title) => { console.log('Product made private:', productId, title); }; const handleProductArchive = (productId, title) => { console.log('Product archived:', productId, title); }; const handleProductUnarchive = (productId) => { console.log('Product unarchived:', productId); }; const handleNavigate = (path) => { console.log('Navigate to:', path); }; const getRowHref = (row) => `/products/${row.id}`; const getUpdateRowHref = (row) => `/products/${row.id}/edit`; return ( <ListProductsTable listProductsTableTitle="Product Management" labels={labels} isLoading={isLoading} onNavigate={handleNavigate} onProductPublic={handleProductPublic} onProductPrivate={handleProductPrivate} onProductArchive={handleProductArchive} onProductUnarchive={handleProductUnarchive} data={productData} rowHref={getRowHref} updateRowHref={getUpdateRowHref} tabs={tabs} currentTab={currentTab} onTabChange={setCurrentTab} createHref="#products/new" > <ListProductsTable.Header style={{display: 'flex', justifyContent: 'space-between'}}> <ListProductsTable.Title /> <ListProductsTable.Action /> </ListProductsTable.Header> <ListProductsTable.Content> {isLoading ? ( <ListProductsTable.Loader /> ) : ( <> <ListProductsTable.Tabs /> <ListProductsTable.Table /> </> )} </ListProductsTable.Content> </ListProductsTable> ); }
function AdvancedProductsTable() { const [currentTab, setCurrentTab] = useState('published'); const [isLoading] = useState(false); const productData = [ { id: '3', title: 'Premium Noise-Canceling Headphones', category: 'Electronics', createdAt: '2024-01-15T10:30:00Z', updatedAt: '2024-01-16T14:20:00Z', numberOfApplicants: '24', publication: { since: '2024-01-15T00:00:00Z', until: '2024-03-15T23:59:59Z', status: 'ACTIVE', }, }, { id: '4', title: 'MacBook Pro 16" M3 Max', category: 'Computing', createdAt: '2024-01-14T09:15:00Z', updatedAt: '2024-01-14T09:15:00Z', numberOfApplicants: '45', publication: { since: '2024-01-20T00:00:00Z', until: '2024-04-20T23:59:59Z', status: 'INACTIVE', }, }, { id: '6', title: 'PlayStation 5 Pro Console', category: 'Gaming', createdAt: '2024-01-13T08:45:00Z', updatedAt: '2024-01-15T16:30:00Z', numberOfApplicants: '67', publication: { since: '2024-01-13T00:00:00Z', until: '2024-05-13T23:59:59Z', status: 'ACTIVE', }, }, ]; const tabs = [ {label: 'Live Products', key: 'published'}, {label: 'Draft Products', key: 'draft'}, {label: 'Archived Products', key: 'archived'}, ]; const labels = { emptyStateMessage: 'No products found in this category', actions: { createProduct: 'Add New Product', }, headerRow: { title: 'Product Information', publicationPeriod: 'Availability Period', createdAt: 'Created', updatedAt: 'Last Updated', numberOfApplicants: 'Interest', }, rowActions: { public: 'Publish', private: 'Make Private', edit: 'Edit Details', archive: 'Archive', unarchive: 'Restore', }, unsetData: { since: 'Start date TBD', until: 'End date TBD', date: 'Publication period not configured', }, }; return ( <ListProductsTable listProductsTableTitle="Product Dashboard" labels={labels} isLoading={isLoading} onNavigate={path => console.log('Navigate to:', path)} onProductPublic={(id, title) => console.log('Publishing:', title)} onProductPrivate={(id, title) => console.log('Making private:', title)} onProductArchive={(id, title) => console.log('Archiving:', title)} onProductUnarchive={id => console.log('Restoring product:', id)} data={productData} rowHref={row => `/products/${row.id}`} updateRowHref={row => `/products/${row.id}/edit`} tabs={tabs} currentTab={currentTab} onTabChange={setCurrentTab} createHref="#products/new" sx={{background: '#fafafa', borderRadius: '16px'}} > {({defaultBlocks, defaultBlockOrder}) => ({ blocks: { ...defaultBlocks, // Header with soft blue gradient header: { ...defaultBlocks.header, props: { ...defaultBlocks.header.props, sx: { background: 'linear-gradient(135deg, #dbeafe 0%, #e0f2fe 100%)', borderRadius: '14px', padding: '24px', border: '1px solid #93c5fd', marginBottom: '16px', }, }, }, // Title with blue accent title: { ...defaultBlocks.title, props: { ...defaultBlocks.title.props, sx: {color: '#1e40af', fontWeight: 700}, }, }, // Content with white background content: { ...defaultBlocks.content, props: { ...defaultBlocks.content.props, sx: { background: 'white', borderRadius: '14px', padding: '20px', border: '1px solid #e5e7eb', }, }, }, }, blockOrder: defaultBlockOrder, })} </ListProductsTable> ); }
π§ Props Referenceβ
Main Component Propsβ
| Prop | Type | Default | Description |
|---|---|---|---|
listProductsTableTitle | ReactNode | Required | Title for the products table section |
labels | TableLabels | Required | Labels object for table headers, actions, and messages |
isLoading | boolean | undefined | Whether the table is currently loading |
onNavigate | (to: string) => void | Required | Callback function for navigation |
onProductPublic | (id: string, title: string) => void | Required | Callback function when product is made public |
onProductPrivate | (id: string, title: string) => void | Required | Callback function when product is made private |
onProductArchive | (id: string, title: string) => void | Required | Callback function when product is archived |
onProductUnarchive | (id: string) => void | Required | Callback function when product is unarchived |
data | ListProductsTableRowData[] | Required | Array of product data objects |
rowHref | (row: ListProductsTableRowData) => string | Required | Function to generate row link URLs |
updateRowHref | (row: ListProductsTableRowData) => string | Required | Function to generate edit page URLs |
tabs | TabData[] | Required | Array of tab configuration objects |
currentTab | string | undefined | Currently active tab label |
onTabChange | (tab: string) => void | undefined | Callback function when tab is changed |
createHref | string | Required | URL for creating new products |
pagination | PaginationProps | undefined | Pagination configuration object |
spacing | number | 3 | Spacing between child elements |
className | string | undefined | Additional CSS class name for styling the container |
children | BlocksOverride | undefined | Custom block components to override default rendering |
Note: The main component extends MUI StackProps. Default styling includes p: 3 padding.
Sub-Componentsβ
The ListProductsTable component provides several sub-components that can be used independently:
ListProductsTable.Headerβ
Container for title and action button.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | Default layout | Custom content to override default header rendering |
className | string | undefined | Additional CSS class name for styling |
Note: This component extends MUI StackProps. Default styling includes direction="row" and justifyContent: 'space-between'.
ListProductsTable.Titleβ
Title typography component.
| Prop | Type | Default | Description |
|---|---|---|---|
listProductsTableTitle | ReactNode | From context | Content to display as title |
variant | TypographyVariant | "h4" | MUI Typography variant |
component | ElementType | "h1" | HTML element to render |
children | ReactNode | undefined | Custom content to override title |
className | string | undefined | Additional CSS class name |
Note: This component extends MUI TypographyProps.
ListProductsTable.Actionβ
Container for action buttons (e.g., create product).
| Prop | Type | Default | Description |
|---|---|---|---|
createHref | string | From context | URL for the create action button |
labels | TableLabels | From context | Labels for action text |
onNavigate | (to: string) => void | From context | Navigation callback |
children | ReactNode | Default button | Custom content to override action rendering |
className | string | undefined | Additional CSS class name |
Note: This component extends MUI StackProps (except direction). Default styling includes direction="row" and alignItems: 'center'.
ListProductsTable.Contentβ
Container for table content area.
| Prop | Type | Default | Description |
|---|---|---|---|
isLoading | boolean | From context | Loading state |
children | ReactNode | Default layout | Custom content to override rendering |
className | string | undefined | Additional CSS class name |
Note: This component extends MUI BoxProps. When no children provided, renders Loader (if loading) or Tabs + Table.
ListProductsTable.Loaderβ
Loading indicator component.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | CircularProgress | Custom loading indicator |
className | string | undefined | Additional CSS class name |
Note: This component extends MUI StackProps. Default styling includes alignItems: 'center'.
ListProductsTable.Tabsβ
Tab navigation component.
| Prop | Type | Default | Description |
|---|---|---|---|
tabs | TabData[] | From context | Array of tab configuration objects |
currentTab | string | From context | Currently active tab label |
onTabChange | (tab: string) => void | From context | Tab change callback function |
className | string | undefined | Additional CSS class name |
Note: This component extends MUI TabsProps (except value, onChange, variant). Uses variant="fullWidth" internally.
ListProductsTable.Tableβ
Data table component with row actions and pagination.
| Prop | Type | Default | Description |
|---|---|---|---|
labels | TableLabels | From context | Labels for headers and actions |
data | ListProductsTableRowData[] | From context | Array of table data |
rowHref | (row) => string | From context | Function to generate row link URLs |
updateRowHref | (row) => string | From context | Function to generate edit page URLs |
onNavigate | (to: string) => void | From context | Navigation callback function |
onProductPublic | (id, title) => void | From context | Callback when product is made public |
onProductPrivate | (id, title) => void | From context | Callback when product is made private |
onProductArchive | (id, title) => void | From context | Callback when product is archived |
onProductUnarchive | (id) => void | From context | Callback when product is unarchived |
pagination | PaginationProps | From context | Pagination configuration |
spacing | number | 3 | Spacing between elements |
className | string | undefined | Additional CSS class name |
Note: This component extends MUI StackProps. Shows empty state when data is empty.
π¨ Configuration examplesβ
Custom Header Layout:
<ListProductsTable {...props}>
<ListProductsTable.Header sx={{bgcolor: 'primary.main', p: 2, borderRadius: 2}}>
<ListProductsTable.Title sx={{color: 'white'}} />
<ListProductsTable.Action />
</ListProductsTable.Header>
<ListProductsTable.Content />
</ListProductsTable>
With Pagination:
const pagination = {
currentPage: currentPage,
totalPages: 10,
onPageChange: page => {
setCurrentPage(page);
console.log('Fetching page:', page);
},
};
<ListProductsTable
{...props}
pagination={pagination}
>
<ListProductsTable.Header />
<ListProductsTable.Content />
</ListProductsTable>
Block Override Pattern:
<ListProductsTable {...props}>
{({defaultBlocks, defaultBlockOrder}) => ({
blocks: {
...defaultBlocks,
header: {
...defaultBlocks.header,
props: {
...defaultBlocks.header.props,
sx: {bgcolor: '#f5f5f5', p: 3, borderRadius: 2},
},
},
},
blockOrder: defaultBlockOrder,
})}
</ListProductsTable>
π§ TypeScript Supportβ
Full TypeScript support with comprehensive type definitions:
import {ListProductsTable} from '@nodeblocks/frontend-list-products-table-block';
import {useState} from 'react';
interface ListProductsTableRowData {
id: string;
title: string;
category: string;
createdAt: string;
updatedAt: string;
numberOfApplicants: string;
publication?: {
since?: string;
until?: string;
status?: string;
};
}
interface TabData {
key?: string;
label: string;
isDisabled?: boolean;
subtitle?: string;
}
interface TableLabels {
emptyStateMessage: string;
actions: {
createProduct: string;
};
headerRow: {
title: string;
publicationPeriod: string;
createdAt: string;
updatedAt: string;
numberOfApplicants: string;
};
rowActions: {
public: string;
private: string;
edit: string;
archive: string;
unarchive: string;
};
unsetData: {
since: string;
until: string;
date: string;
};
}
function TypedListProductsTableExample() {
const [currentTab, setCurrentTab] = useState('published');
const [isLoading] = useState(false);
const products: ListProductsTableRowData[] = [
{
id: '1',
title: 'Product Name',
category: 'Electronics',
createdAt: '2024-01-15T10:30:00Z',
updatedAt: '2024-01-16T14:20:00Z',
numberOfApplicants: '5',
publication: {
since: '2024-01-15T00:00:00Z',
until: '2024-03-15T23:59:59Z',
status: 'ACTIVE',
},
},
];
const tabs: TabData[] = [
{label: 'Published', key: 'published'},
{label: 'Draft', key: 'draft'},
{label: 'Archived', key: 'archived', isDisabled: false},
];
const labels: TableLabels = {
emptyStateMessage: 'No products available',
actions: {createProduct: 'New Product'},
headerRow: {
title: 'Product Name',
publicationPeriod: 'Publication Period',
createdAt: 'Created',
updatedAt: 'Updated',
numberOfApplicants: 'Applicants',
},
rowActions: {
public: 'Publish',
private: 'Unpublish',
edit: 'Edit',
archive: 'Archive',
unarchive: 'Restore',
},
unsetData: {
since: 'Not set',
until: 'Not set',
date: 'No publication period',
},
};
return (
<ListProductsTable
listProductsTableTitle="Products Dashboard"
labels={labels}
isLoading={isLoading}
onNavigate={path => console.log('Navigate:', path)}
onProductPublic={(id, title) => console.log('Publish:', id, title)}
onProductPrivate={(id, title) => console.log('Unpublish:', id, title)}
onProductArchive={(id, title) => console.log('Archive:', id, title)}
onProductUnarchive={id => console.log('Restore:', id)}
data={products}
rowHref={row => `/products/${row.id}`}
updateRowHref={row => `/products/${row.id}/edit`}
tabs={tabs}
currentTab={currentTab}
onTabChange={setCurrentTab}
createHref="#products/create"
>
<ListProductsTable.Header />
<ListProductsTable.Content />
</ListProductsTable>
);
}
π Notesβ
- The main component extends MUI
StackPropswith defaultspacing={3}andp: 3padding ListProductsTable.Titleuses MUITypographywithvariant="h4"andcomponent="h1"by defaultListProductsTable.Tabsuses MUITabswithvariant="fullWidth"- tabs stretch to fill container- Dates are formatted using
luxonlibrary:yyyy/M/dfor publication dates,yyyy/M/d HH:mmfor created/updated - Row dropdown menu shows different actions based on
publication.status:ACTIVEstatus: shows "Make Private", "Edit", "Archive" optionsINACTIVEstatus: shows "Make Public", "Edit", "Archive" optionsARCHIVEDstatus: shows only "Unarchive" option
- Empty state displays a "person" icon with the
emptyStateMessagelabel - When
rowHrefreturns a URL, the entire table row becomes clickable with hover effect - Pagination uses MUI
Paginationcomponent withvariant="outlined"andshape="rounded"
Built with β€οΈ using React, TypeScript, and MUI.