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.1.1
📖 Usage
import {ListProductsTable} from '@nodeblocks/frontend-list-products-table-block';
- Basic Usage
- Advanced Usage
function BasicListProductsTable() {
const [currentTab, setCurrentTab] = useState('published');
const [isLoading, setIsLoading] = 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, setIsLoading] = 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 Count'
},
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="🛍️ Premium 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">
{({ defaultBlocks, defaultBlockOrder }) => ({
blocks: {
...defaultBlocks,
// Enhanced header with gradient styling
header: {
...defaultBlocks.header,
props: {
...defaultBlocks.header.props,
style: {
background: 'white',
borderRadius: '20px',
padding: '32px',
color: 'white',
marginBottom: '24px',
width: 'calc(100% - 48px)',
margin: '0 auto',
marginTop: '24px',
boxShadow: '0 15px 35px rgba(0,0,0,0.1)',
border: '0.5px solid rgb(0, 0, 0)',
}
}
},
// Enhanced content with modern styling
content: {
...defaultBlocks.content,
props: {
...defaultBlocks.content.props,
style: {
background: 'white',
borderRadius: '16px',
padding: '24px',
boxShadow: '0 10px 35px rgba(0,0,0,0.05)',
width: 'calc(100% - 48px)',
margin: '0 auto',
borderRadius: '16px',
border: '0.5px solid rgb(0, 0, 0)',
}
}
}
},
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 | ProductAction | Required | Callback function when product is made public |
onProductPrivate | ProductAction | Required | Callback function when product is made private |
onProductArchive | ProductAction | Required | Callback function when product is archived |
onProductUnarchive | (productId: 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 update row link URLs |
tabs | Tab[] | Required | Array of tab configuration objects |
currentTab | string | undefined | Currently active tab identifier |
onTabChange | (tab: string) => void | undefined | Callback function when tab is changed |
createHref | string | Required | URL for creating new products |
pagination | {currentPage: number; onPageChange: (page: number) => void; totalPages: number} | From context | Pagination configuration |
className | string | undefined | Additional CSS class name for styling the container |
children | BlocksOverride | undefined | Custom block components to override default rendering |
Sub-Components
The ListProductsTable component provides several sub-components that can be used independently:
ListProductsTable.Header
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | undefined | Custom content to override default header rendering |
className | string | undefined | Additional CSS class name for styling |
ListProductsTable.Title
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | undefined | Custom content to override default title rendering |
className | string | undefined | Additional CSS class name for styling |
size | enum | "3XL" | Typography size for the title |
color | enum | "low-emphasis" | Color theme for the title |
weight | enum | "bold" | Weight of the title |
ListProductsTable.Action
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | undefined | Custom content to override default action rendering |
className | string | undefined | Additional CSS class name for styling |
createHref | string | From context | URL for the create action button |
labels | TableLabels | From context | Labels for action text |
direction | enum | "row" | Flex direction for action buttons |
alignItems | enum | "stretch" | Alignment of items in the container |
gapSize | enum | "S" | Gap between items in the container |
ListProductsTable.Content
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | undefined | Custom content to override default content rendering |
className | string | undefined | Additional CSS class name for styling |
isLoading | boolean | From context | Loading state from context |
ListProductsTable.Loader
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | Circular progress indicator | Custom loading indicator content |
className | string | undefined | Additional CSS class name for styling |
direction | enum | "row" | Flex direction for action buttons |
alignItems | enum | "stretch" | Alignment of items in the container |
gapSize | enum | "S" | Gap between items in the container |
ListProductsTable.Tabs
| Prop | Type | Default | Description |
|---|---|---|---|
tabs | Tab[] | From context | Array of tab configuration objects |
currentTab | string | From context | Currently active tab identifier |
onTabChange | (tab: string) => void | From context | Tab change callback function |
tabWidth | string | "stretch" | Width behavior for tabs |
className | string | undefined | Additional CSS class name for styling |
ListProductsTable.Table
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | undefined | Additional CSS class name for styling |
onProductPublic | (id: string, title: string) => void | From context | Callback function when product is made public |
onProductPrivate | (id: string, title: string) => void | From context | Callback function when product is made private |
onProductArchive | (id: string, title: string) => void | From context | Callback function when product is archived |
onProductUnarchive | (id: string) => void | From context | Callback function when product is unarchived |
data | ListProductsTableRowData[] | From context | Array of table data |
dropdownMenuItems | (cell: ListProductsTableRowData) => Array<DropDownMenuItems> | From context | Function to generate dropdown menu items |
dropdownMenuState | {openedDropdownMenuId: string} | From context | Currently opened dropdown menu |
setDropdownMenuState | ({openedDropdownMenuId: string}) => void | From context | Callback function when the opened dropdown menu changes |
emptyState | {icon?: IconType; message: string} | From context | Configuration for empty state |
isLoading | boolean | From context | Loading state |
onNavigate | (to: string) => void | From context | Navigation callback function |
pagination | {currentPage: number; onPageChange: (page: number) => void; totalPages: number} | From context | Pagination configuration |
rowHref | `(cell: ListProductsTableRowData) => string | undefined | null` |
updateRowHref | `(cell: ListProductsTableRowData) => string | undefined | null` |
🔧 TypeScript Support
Full TypeScript support with comprehensive type definitions:
import {ListProductsTable} from '@nodeblocks/frontend-list-products-table-block';
interface Tab {
isDisabled?: boolean;
key?: string;
label: string;
subtitle?: string;
}
interface ListProductsTableRowData {
category: string;
createdAt: string;
id: string;
numberOfApplicants: string;
publication?: {
since?: string;
status?: string;
until?: string;
};
title: string;
updatedAt: string;
}
interface CustomProductTableProps {
products: ListProductsTableRowData[];
onPublishProduct: (productId: string) => void;
onUnpublishProduct: (productId: string) => void;
onArchiveProduct: (productId: string) => void;
onRestoreProduct: (productId: string) => void;
tableTabs: Array<Tab>;
currentActiveTab: string;
onTabSwitch: (tab: string) => void;
isTableLoading: boolean;
}
const ProductTableComponent = ({
products,
onPublishProduct,
onUnpublishProduct,
onArchiveProduct,
onRestoreProduct,
tableTabs,
currentActiveTab,
onTabSwitch,
isTableLoading,
}: CustomProductTableProps) => {
const tableLabels = {
emptyStateMessage: 'No products available',
actions: {createProduct: 'New Product'},
headerRow: {
title: 'Product Name',
publicationPeriod: 'Publication Dates',
createdAt: 'Date Created',
updatedAt: 'Last Modified',
numberOfApplicants: 'Total Applicants',
},
rowActions: {
public: 'Publish',
private: 'Unpublish',
edit: 'Edit',
archive: 'Archive',
unarchive: 'Restore',
},
unsetData: {
since: 'Start date not set',
until: 'End date not set',
date: 'Publication period not configured',
},
};
return (
<ListProductsTable
listProductsTableTitle="Products Dashboard"
labels={tableLabels}
isLoading={isTableLoading}
onNavigate={path => console.log('Navigate:', path)}
onProductPublic={onPublishProduct}
onProductPrivate={onUnpublishProduct}
onProductArchive={onArchiveProduct}
onProductUnarchive={onRestoreProduct}
data={products}
rowHref={row => `/products/${row.id}`}
updateRowHref={row => `/products/${row.id}/edit`}
tabs={tableTabs}
currentTab={currentActiveTab}
onTabChange={onTabSwitch}
createHref="#products/create">
<ListProductsTable.Header />
<ListProductsTable.Content />
</ListProductsTable>
);
};
Built with ❤️ using React, TypeScript, and modern web standards.