List Order Block
ListOrder is an order list built on MUI Stack + cards, with optional chips, status banner, load-more behavior, and composition via compound children or block overrides.
Installation
- npm
- yarn
- pnpm
- bun
npm install @nodeblocks/frontend-list-order-block
yarn add @nodeblocks/frontend-list-order-block
pnpm add @nodeblocks/frontend-list-order-block
bun add @nodeblocks/frontend-list-order-block
What You Need
| Item | Why it matters |
|---|---|
orders | Rows to render (id, logoUrl, title, subtitle, date, optional chips, disabled, status) |
hasMore | Used by LoadMoreButton to decide whether it renders |
onClickOrder (optional) | Row click handler used by default OrderCard / OrderListSection |
onClickLoadMore (optional) | Load-more handler consumed by LoadMoreButton |
isLoadingList (optional) | Toggles OrderLoadingCircle and disables LoadMoreButton |
ListOrder does not own list state. Keep orders, hasMore, and loading state in your app, then pass them to the block. The common pattern is: slice/fetch next page in onClickLoadMore, append rows, and update hasMore.
Code Examples
- Quick Start
- States and actions
- Compound Components
- Block Override
function Example() { const allOrders = Array.from({length: 12}, (_, i) => ({ id: String(i + 1), logoUrl: 'https://unsplash.it/16', title: `Order #${i + 1}`, subtitle: `Organization ${i + 1}`, date: new Date(2024, 0, i + 1).toLocaleDateString(), chips: i % 3 === 0 ? [{label: 'Pending'}] : [{label: 'Approved'}], })); const [displayedOrders, setDisplayedOrders] = React.useState(allOrders.slice(0, 5)); const [hasMore, setHasMore] = React.useState(true); const [lastAction, setLastAction] = React.useState('Clicking an order will show inline feedback here.'); const handleLoadMore = () => { const next = allOrders.slice(0, displayedOrders.length + 5); setDisplayedOrders(next); setHasMore(next.length < allOrders.length); }; return ( <> <ListOrder orders={displayedOrders} hasMore={hasMore} onClickOrder={(order) => setLastAction(`Clicked: ${order.title}`)} onClickLoadMore={handleLoadMore} sx={{bgcolor: 'background.default'}} > <ListOrder.OrderListSection /> <ListOrder.OrderLoadingCircle /> <ListOrder.LoadMoreButton /> </ListOrder> <div style={{marginTop: 12, color: '#475569', fontSize: 13}}>{lastAction}</div> </> ); }
Customize row states (chips, disabled, optional status) and loading behavior.
function Example() { const [isLoadingList, setIsLoadingList] = React.useState(false); const [orders, setOrders] = React.useState([ { id: '1', logoUrl: 'https://unsplash.it/16', title: 'Order #1', subtitle: 'Organization 1', date: 'January 1, 2024', chips: [{label: 'Pending'}], status: 'Awaiting confirmation', }, { id: '2', logoUrl: 'https://unsplash.it/16', title: 'Order #2', subtitle: 'Organization 2', date: 'January 2, 2024', chips: [{label: 'Rejected'}], disabled: true, }, ]); const [hasMore, setHasMore] = React.useState(true); const [lastAction, setLastAction] = React.useState('Order click feedback will appear here.'); const handleLoadMore = () => { setIsLoadingList(true); setTimeout(() => { setOrders((prev) => [ ...prev, { id: String(prev.length + 1), logoUrl: 'https://unsplash.it/16', title: `Order #${prev.length + 1}`, subtitle: `Organization ${prev.length + 1}`, date: 'January 3, 2024', chips: [{label: 'Approved'}], }, ]); setHasMore(false); setIsLoadingList(false); }, 600); }; return ( <> <ListOrder orders={orders} hasMore={hasMore} isLoadingList={isLoadingList} onClickOrder={(order) => setLastAction(`Open ${order.id}`)} onClickLoadMore={handleLoadMore} > <ListOrder.OrderListSection /> <ListOrder.OrderLoadingCircle /> <ListOrder.LoadMoreButton /> </ListOrder> <div style={{marginTop: 12, color: '#475569', fontSize: 13}}>{lastAction}</div> </> ); }
OrderCard renders the status banner only when order.status exists, and it is shown on sm+ (display: { xs: 'none', sm: 'block' }).
Use child blocks directly to control the section layout (same pattern as Storybook).
function Example() { const allOrders = Array.from({length: 10}, (_, i) => ({ id: String(i + 1), logoUrl: 'https://unsplash.it/16', title: `Order #${i + 1}`, subtitle: `Organization ${i + 1}`, date: new Date(2024, 0, i + 1).toLocaleDateString(), chips: i % 2 === 0 ? [{label: 'Pending'}] : [{label: 'Approved'}], })); const [displayedOrders, setDisplayedOrders] = React.useState(allOrders.slice(0, 5)); const [hasMore, setHasMore] = React.useState(true); const [isLoading, setIsLoading] = React.useState(false); const handleLoadMore = () => { setIsLoading(true); setTimeout(() => { const next = allOrders.slice(0, displayedOrders.length + 5); setDisplayedOrders(next); setHasMore(next.length < allOrders.length); setIsLoading(false); }, 500); }; return ( <> <ListOrder orders={displayedOrders} hasMore={hasMore} > {isLoading ? ( <ListOrder.OrderLoadingCircle isLoadingList={isLoading} /> ) : ( <ListOrder.OrderListSection orders={displayedOrders}> {displayedOrders.map((order, index) => ( <ListOrder.OrderCard key={order.id} order={order} sx={index === 1 ? {bgcolor: '#EBF7FF'} : undefined} /> ))} </ListOrder.OrderListSection> )} <ListOrder.LoadMoreButton hasMore={hasMore} isLoadingList={isLoading} onClick={handleLoadMore} /> </ListOrder> </> ); }
Use function children to prepend custom blocks and reorder defaults.
function Example() { const allOrders = Array.from({length: 10}, (_, i) => ({ id: String(i + 1), logoUrl: 'https://unsplash.it/16', title: `Order #${i + 1}`, subtitle: `Organization ${i + 1}`, date: new Date(2024, 0, i + 1).toLocaleDateString(), chips: i % 2 === 0 ? [{label: 'Pending'}] : [{label: 'Approved'}], })); const [displayedOrders, setDisplayedOrders] = React.useState(allOrders.slice(0, 5)); const [hasMore, setHasMore] = React.useState(true); const pendingCount = displayedOrders.filter((o) => o.chips?.some((c) => c.label === 'Pending')).length; const handleLoadMore = () => { const next = allOrders.slice(0, displayedOrders.length + 5); setDisplayedOrders(next); setHasMore(next.length < allOrders.length); }; return ( <ListOrder orders={displayedOrders} hasMore={hasMore} onClickLoadMore={handleLoadMore} > {({defaultBlocks, defaultBlockOrder}) => ({ blocks: { ...defaultBlocks, notificationBanner: ( <div style={{ marginBottom: 12, padding: 12, background: '#eef4ff', border: '1px solid #cddcff', borderRadius: 8, fontSize: 14, }} > Pending orders: {pendingCount} </div> ), }, blockOrder: ['notificationBanner', ...defaultBlockOrder], })} </ListOrder> ); }
When to use block overrides
Use overrides when you need banners/stats above the list or custom ordering. defaultBlockOrder is orderListSection, orderLoadingCircle, loadMoreButton, orderCard, orderLogo, orderSubtitle, orderDate, orderStatusBanner. At root render, the composed list blocks are orderListSection and orderLoadingCircle; LoadMoreButton is typically added with compound children.
Important Props
Core Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
orders | Order[] | Yes | - | Order rows |
hasMore | boolean | Yes | - | Context value used by LoadMoreButton to render/hide itself |
onClickOrder | (order: Order) => void | No | undefined | Called when a row is clicked |
onClickLoadMore | () => void | No | undefined | Handler for load-more action |
isLoadingList | boolean | No | undefined | Shows OrderLoadingCircle; disables LoadMoreButton |
Order shape:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | Yes | - | Unique row id |
logoUrl | string | Yes | - | Organization logo URL |
title | string | Yes | - | Primary text |
subtitle | string | Yes | - | Secondary text |
date | string | Yes | - | Date text |
chips | ({ label: string } & ChipProps)[] | No | undefined | Optional chips above date |
disabled | boolean | No | undefined | Applies disabled card style (bgcolor: grey.75) |
status | string | No | undefined | Optional status banner text (sm+ only) |
Content Props
| Component | Prop | Type | Required | Default | Description |
|---|---|---|---|---|---|
OrderListSection | orders | Order[] | No | Root orders | Rows used in default list mapping |
OrderListSection | onClickOrder | (order: Order) => void | No | Root handler | Row click fallback when children are omitted |
OrderListSection | children | ReactNode | No | Maps orders to OrderCard | Custom list content |
OrderCard | order | Order | Yes | - | Row data for one card |
OrderCard | onClick | CardProps['onClick'] | No | Root onClickOrder(order) | Click handler override |
OrderCard | children | ReactNode | No | Built card content from order | Custom card body |
OrderLoadingCircle | isLoadingList | boolean | No | Root isLoadingList | Renders spinner only when true |
LoadMoreButton | hasMore | boolean | No | Root hasMore | Renders only when true |
LoadMoreButton | onClick | ButtonProps['onClick'] | No | Root onClickLoadMore | Click handler override |
LoadMoreButton | isLoadingList | boolean | No | Root isLoadingList | Disables button when loading |
LoadMoreButton | children | ReactNode | No | Read More | Button label/content |
OrderLogo | src | string | No | from order.logoUrl | Logo image source |
OrderLogo | alt | string | No | undefined | Image alt text |
OrderTitle | children | ReactNode | No | from order.title | Title content |
OrderSubtitle | children | ReactNode | No | from order.subtitle | Subtitle content |
OrderDate | children | ReactNode | No | from order.date | Date content |
OrderStatusBanner | children | ReactNode | No | from order.status | Status content |
OrderListSection, OrderCard, OrderLoadingCircle, LoadMoreButton, OrderLogo, OrderTitle, OrderSubtitle, OrderDate, and OrderStatusBanner are ListOrder.OrderListSection, etc.
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 | Root class (nbb-list-order is applied by default) |
sx | SxProps | No | undefined | MUI system styles for root list |
ListOrder inherits StackProps (except children), with root defaults spacing={2} and p: 2. defaultBlockOrder is orderListSection, orderLoadingCircle, loadMoreButton, orderCard, orderLogo, orderSubtitle, orderDate, orderStatusBanner.
Default UI Blocks
| Block | Built on | Notes |
|---|---|---|
ListOrder (root) | Stack | Root list container |
ListOrder.OrderListSection | Stack | Default order list section |
ListOrder.OrderCard | Card + CardActionArea + CardContent | Card row built from order |
ListOrder.OrderLogo | Box (component="img") | Circular logo image |
ListOrder.OrderTitle | Typography | Title (variant="h6") |
ListOrder.OrderSubtitle | Typography | Subtitle (variant="body1") |
ListOrder.OrderDate | Box | Date text with secondary color |
ListOrder.OrderStatusBanner | Box + Typography | Status banner (sm+) |
ListOrder.OrderLoadingCircle | CircularProgress | Loading spinner |
ListOrder.LoadMoreButton | Button | Full-width action, default text Read More |
chips inside default OrderCard | Chip | Uses order.chips entries (label required) |
TypeScript
import { ListOrder, type Order } from '@nodeblocks/frontend-list-order-block';
const orders: Order[] = [
{
id: '1',
logoUrl: 'https://unsplash.it/16',
title: 'Order #1',
subtitle: 'Organization 1',
date: 'January 1, 2024',
chips: [{label: 'Pending'}],
status: 'Processing',
},
];
<ListOrder
orders={orders}
hasMore={false}
onClickOrder={() => {}}
/>;