List Invites Block
ListInvites is an invite management table with pagination, row actions, and block composition.
Installation
- npm
- yarn
- pnpm
- bun
npm install @nodeblocks/frontend-list-invites-block
yarn add @nodeblocks/frontend-list-invites-block
pnpm add @nodeblocks/frontend-list-invites-block
bun add @nodeblocks/frontend-list-invites-block
What You Need
| Item | Why it matters |
|---|---|
labels | Copy for headers, statuses, empty state, and actions |
data | Rows (id, name, email, status) |
listInvitesTitle | Page title in the header |
onClickAction | Handler for the invite-user button |
isLoading (optional) | Show the loading state |
pagination (optional) | Client-side or server-driven page changes |
rowHref + onNavigate (optional) | Make rows clickable with app routing |
statusMatch (optional) | Map raw row.status values to status labels |
shouldShowDropdownMenu (optional) | Hide row action menus for matching rows |
resolveRowAction + handlers (optional) | Per-row activate/deactivate menu actions |
ListInvites does not own table or pagination state. Keep data, pagination.currentPage, and pagination.onPageChange in your app (slice rows per page on the client, or fetch a page from your API). Pass rowHref and onNavigate together to make rows clickable.
Each row is a ListInvitesRowData object: id, name, email, and status. The generated status column compares row.status with statusMatch (default active / inactive / pending) to choose the displayed status label. Row actions require labels.rowActions, resolveRowAction, and the matching handler (onItemActivate / onItemDeactivate); shouldShowDropdownMenu can hide them per row. statusMatch does not choose row actions.
Code Examples
- Quick Start
- Labels and URLs
- Compound Components
- Block Override
function Example() { const allInviteData = [ {id: '0', name: 'User 0', email: 'user0@example.com', status: 'pending'}, {id: '1', name: 'User 1', email: 'user1@example.com', status: 'active'}, {id: '2', name: 'User 2', email: 'user2@example.com', status: 'inactive'}, {id: '3', name: 'User 3', email: 'user3@example.com', status: 'pending'}, {id: '4', name: 'User 4', email: 'user4@example.com', status: 'active'}, {id: '5', name: 'User 5', email: 'user5@example.com', status: 'inactive'}, ]; const labels = { emptyStateMessage: 'No invites found', actions: {inviteUser: 'Invite User'}, headerRow: {name: 'Name', email: 'Email', status: 'Status'}, cellData: {statusActive: 'Active', statusInactive: 'Inactive', statusPending: 'Pending'}, rowActions: {activate: 'Activate Invite', deactivate: 'Deactivate Invite'}, }; const [currentPage, setCurrentPage] = React.useState(1); const [lastAction, setLastAction] = React.useState('Select an invite action to see feedback here.'); const itemsPerPage = 5; const totalPages = Math.ceil(allInviteData.length / itemsPerPage); const startIndex = (currentPage - 1) * itemsPerPage; const paginatedData = allInviteData.slice(startIndex, startIndex + itemsPerPage); return ( <> <ListInvites listInvitesTitle="Manage Invites" labels={labels} data={paginatedData} rowHref={row => `/invites/${row.id}`} onNavigate={path => setLastAction(`Navigate: ${path}`)} onClickAction={() => setLastAction('Invite user clicked')} onItemActivate={id => setLastAction(`Activate: ${id}`)} onItemDeactivate={id => setLastAction(`Deactivate: ${id}`)} resolveRowAction={row => { if (row.id === '0') return undefined; return row.status === 'inactive' ? ['activate'] : ['deactivate']; }} pagination={{ currentPage, totalPages, onPageChange: setCurrentPage, }} /> <div style={{marginTop: 12, color: '#475569', fontSize: 13}}>{lastAction}</div> </> ); }
Customize labels, title, and row navigation URL behavior while keeping the default layout.
function Example() { const labels = { emptyStateMessage: 'No team invites yet', actions: {inviteUser: 'Send invite'}, headerRow: {name: 'Member', email: 'Email', status: 'Status'}, cellData: {statusActive: 'Active', statusInactive: 'Inactive', statusPending: 'Pending'}, }; return ( <> <div style={{marginBottom: 12, color: '#475569', fontSize: 13}}>Use the action button and row menu to update the inline status below.</div> <ListInvites listInvitesTitle="Team invites" labels={labels} data={[{id: '1', name: 'Alex', email: 'alex@example.com', status: 'active'}]} rowHref={row => `/team/invites/${row.id}`} onNavigate={() => {}} onClickAction={() => {}} /> </> ); }
Set isLoading={true} to show the loader. For the empty state, pass data={[]} with isLoading={false} (or omit isLoading) to show labels.emptyStateMessage.
Compose header, content (loader or table), and pagination sub-blocks explicitly.
function Example() { const allInviteData = [ {id: '0', name: 'User 0', email: 'user0@example.com', status: 'pending'}, {id: '1', name: 'User 1', email: 'user1@example.com', status: 'active'}, {id: '2', name: 'User 2', email: 'user2@example.com', status: 'inactive'}, {id: '3', name: 'User 3', email: 'user3@example.com', status: 'pending'}, {id: '4', name: 'User 4', email: 'user4@example.com', status: 'active'}, ]; const labels = { emptyStateMessage: 'No invites found', actions: {inviteUser: 'Invite User'}, headerRow: {name: 'Name', email: 'Email', status: 'Status'}, cellData: {statusActive: 'Active', statusInactive: 'Inactive', statusPending: 'Pending'}, rowActions: {activate: 'Activate Invite', deactivate: 'Deactivate Invite'}, }; const [currentPage, setCurrentPage] = React.useState(1); const isLoading = false; const [lastAction, setLastAction] = React.useState('Inline action feedback appears here.'); const itemsPerPage = 5; const totalPages = Math.ceil(allInviteData.length / itemsPerPage); const startIndex = (currentPage - 1) * itemsPerPage; const paginatedData = allInviteData.slice(startIndex, startIndex + itemsPerPage); return ( <ListInvites listInvitesTitle="Manage Invites" labels={labels} onClickAction={() => setLastAction('Invite user clicked')} data={paginatedData} > <ListInvites.Header> <ListInvites.Title listInvitesTitle="Manage Invites" /> <ListInvites.Action labels={labels} onClickAction={() => setLastAction('Invite user clicked')} /> </ListInvites.Header> <ListInvites.Content> {isLoading ? ( <ListInvites.Loader /> ) : ( <ListInvites.Table labels={labels} data={paginatedData} rowHref={row => `/invites/${row.id}`} onNavigate={path => setLastAction(`Navigate: ${path}`)} onItemActivate={id => setLastAction(`Activate: ${id}`)} onItemDeactivate={id => setLastAction(`Deactivate: ${id}`)} resolveRowAction={row => { if (row.id === '0') return undefined; return row.status === 'inactive' ? ['activate'] : ['deactivate']; }} /> )} </ListInvites.Content> <ListInvites.Pagination pagination={{ currentPage, totalPages, onPageChange: setCurrentPage, }} data={paginatedData} /> <div style={{marginTop: 12, color: '#475569', fontSize: 13}}>{lastAction}</div> </ListInvites> ); }
Pass columns, rowActions, and rowMenu on ListInvites.Table for custom grids and MUI Menu row actions (see Storybook WithCompoundComponents).
Use function children to prepend custom blocks and control order.
function Example() { const allInviteData = [ {id: '0', name: 'User 0', email: 'user0@example.com', status: 'pending'}, {id: '1', name: 'User 1', email: 'user1@example.com', status: 'active'}, {id: '2', name: 'User 2', email: 'user2@example.com', status: 'inactive'}, {id: '3', name: 'User 3', email: 'user3@example.com', status: 'pending'}, {id: '4', name: 'User 4', email: 'user4@example.com', status: 'active'}, ]; const labels = { emptyStateMessage: 'No invites found', actions: {inviteUser: 'Invite User'}, headerRow: {name: 'Name', email: 'Email', status: 'Status'}, cellData: {statusActive: 'Active', statusInactive: 'Inactive', statusPending: 'Pending'}, }; const [currentPage, setCurrentPage] = React.useState(1); const [lastAction, setLastAction] = React.useState('Select an invite action to see feedback here.'); const itemsPerPage = 5; const totalPages = Math.ceil(allInviteData.length / itemsPerPage); const startIndex = (currentPage - 1) * itemsPerPage; const paginatedData = allInviteData.slice(startIndex, startIndex + itemsPerPage); return ( <> <ListInvites listInvitesTitle="Manage Invites" labels={labels} data={paginatedData} onClickAction={() => setLastAction('Invite user clicked')} pagination={{ currentPage, totalPages, onPageChange: setCurrentPage, }} > {({defaultBlocks}) => ({ blocks: { ...defaultBlocks, notificationBanner: ( <div style={{ marginBottom: 16, padding: 12, background: '#eef4ff', border: '1px solid #cddcff', borderRadius: 8, fontSize: 14, }} > You have pending invites that need attention. </div> ), stats: ( <div style={{ marginBottom: 16, padding: 12, background: '#f5f5f5', borderRadius: 8, fontSize: 14, }} > Total invites: {allInviteData.length} </div> ), table: <ListInvites.Table />, pagination: <ListInvites.Pagination />, }, blockOrder: ['notificationBanner', 'stats', 'header', 'table', 'pagination'], })} </ListInvites> <div style={{marginTop: 12, color: '#475569', fontSize: 13}}>{lastAction}</div> </> ); }
When to use block overrides
Use overrides when you need banners or stats above the table, or when you want explicit header, content, table, and pagination blocks in a custom order. Without overrides, render order is header, content, pagination (defaultBlockOrder); the default table still derives its columns from labels and statusMatch and its rows from data. defaultBlocks also includes title, action, loader, table, and pagination for spreads, so you can replace the content wrapper with explicit table/pagination composition when customizing layout (same pattern as Storybook).
Important Props
Core Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
labels | { emptyStateMessage: string; actions: { inviteUser: string }; headerRow: { name: string; email: string; status: string }; cellData: { statusActive: string; statusInactive: string; statusPending: string }; rowActions?: { activate: string; deactivate: string } } | Yes | - | UI copy for headers, statuses, empty state, and actions |
data | ListInvitesRowData[] | Yes | - | Table rows (id, name, email, status) |
listInvitesTitle | ReactNode | Yes | - | Header title |
onClickAction | () => void | Yes | - | Invite-user button handler |
isLoading | boolean | No | undefined | Shows the loading state when true |
pagination | { currentPage: number; totalPages: number; onPageChange: (page: number) => void; className?: string } | No | undefined | Page controls; pages are counted from 1 |
rowHref | (row: ListInvitesRowData) => string | No | undefined | Build a row link target; pair with onNavigate to make rows clickable |
onNavigate | (to: string) => void | No | undefined | Called when a clickable row is activated |
shouldShowDropdownMenu | (row: ListInvitesRowData) => boolean | No | undefined | Return false to hide row action menu items for a row |
resolveRowAction | (row: ListInvitesRowData) => ('activate' | 'deactivate')[] | undefined | No | undefined | Selects which row actions to show |
statusMatch | { active: string; inactive: string; pending: string } | No | { active: 'active', inactive: 'inactive', pending: 'pending' } | Maps raw row.status values to the generated status labels |
onItemActivate | (id: string) => void | No | undefined | Handles activate row actions |
onItemDeactivate | (id: string) => void | No | undefined | Handles deactivate row actions |
Row actions require labels.rowActions and resolveRowAction. If a handler is missing, that action is omitted from the menu.
Content Props
labels keys (all required on the root labels object except rowActions):
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
labels.emptyStateMessage | string | Yes | - | Empty table message |
labels.actions.inviteUser | string | Yes | - | Header button label |
labels.headerRow.name | string | Yes | - | Name column header |
labels.headerRow.email | string | Yes | - | Email column header |
labels.headerRow.status | string | Yes | - | Status column header |
labels.cellData.statusActive | string | Yes | - | Label for active status |
labels.cellData.statusInactive | string | Yes | - | Label for inactive status |
labels.cellData.statusPending | string | Yes | - | Label for pending status |
labels.rowActions.activate | string | No | undefined | Row menu label for activate action |
labels.rowActions.deactivate | string | No | undefined | Row menu label for deactivate action |
Subcomponents (compound layout):
| Component | Prop | Type | Required | Default | Description |
|---|---|---|---|---|---|
Title | children | ReactNode | No | Root listInvitesTitle | Custom title markup |
Title | listInvitesTitle | ReactNode | No | Root listInvitesTitle | Title text when children is not provided |
Action | children | ReactNode | No | Invite button from root labels.actions.inviteUser and onClickAction | Custom action markup |
Action | labels | { actions: { inviteUser: string } } | No | Root labels | Button label source |
Action | onClickAction | () => void | No | Root handler | Invite button click handler |
Header | children | ReactNode | No | undefined | Header wrapper content |
Loader | children | ReactNode | No | CircularProgress | Loading indicator |
Content | children | ReactNode | No | undefined | Wraps the loader and/or table |
Content | isLoading | boolean | No | undefined | Forwards loading state to the content wrapper |
Table | labels | { emptyStateMessage: string; actions: { inviteUser: string }; headerRow: { name: string; email: string; status: string }; cellData: { statusActive: string; statusInactive: string; statusPending: string }; rowActions?: { activate: string; deactivate: string } } | No | Root labels | Generates default columns and row actions |
Table | data | ListInvitesRowData[] | No | Root data | Rows to render |
Table | rowHref | (row: ListInvitesRowData) => string | No | Root rowHref | Row link target |
Table | onNavigate | (to: string) => void | No | Root onNavigate | Row navigation handler |
Table | onItemActivate | (id: string) => void | No | Root onItemActivate | Handles activate row actions |
Table | onItemDeactivate | (id: string) => void | No | Root onItemDeactivate | Handles deactivate row actions |
Table | shouldShowDropdownMenu | (row: ListInvitesRowData) => boolean | No | Root shouldShowDropdownMenu | Hides row menus for matching rows |
Table | resolveRowAction | (row: ListInvitesRowData) => ('activate' | 'deactivate')[] | undefined | No | Root resolveRowAction | Chooses row menu actions |
Table | statusMatch | { active: string; inactive: string; pending: string } | No | Root statusMatch | Custom status mapping |
Pagination | pagination | { currentPage: number; totalPages: number; onPageChange: (page: number) => void; className?: string } | No | Root pagination | Page controls |
Pagination | data | ListInvitesRowData[] | No | Root data | Row count for pagination |
Pagination | isLoading | boolean | No | Root isLoading | Hides pagination while loading |
Title, Action, Header, Loader, Content, Table, and Pagination are ListInvites.Title, etc. ListInvites.Table also accepts custom columns, rowActions, rowMenu, actionColumn, and onRowClick when you need fully custom grid behavior.
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 the root container (nbb-list-invites-container) |
sx | SxProps | No | undefined | MUI system styles for the root |
ListInvites inherits StackProps passthrough except children. Render order without overrides is header, content, pagination; defaultBlockOrder is header, content, pagination (defaultBlocks keys: title, action, header, loader, content, table, pagination).
Default UI Blocks
| Block | Built on | Notes |
|---|---|---|
ListInvites (root) | Stack | Table shell; renders header/content/pagination; empty state uses PersonOutlined |
ListInvites.Title | Typography | Title text from listInvitesTitle |
ListInvites.Action | Button | Invite-user action with PeopleOutlineOutlined icon |
ListInvites.Header | Stack | Header wrapper for title and actions |
ListInvites.Loader | CircularProgress | Loading state |
ListInvites.Content | Box | Content wrapper for loader, empty state, or grid |
ListInvites.Table | Stack + TableContainer + Table | Data grid; supports custom columns, row actions, row menu, and row click behavior |
ListInvites.Pagination | Stack + Pagination | Page controls |
TypeScript
import { ListInvites, type ListInvitesRowData } from '@nodeblocks/frontend-list-invites-block';
const labels = {
emptyStateMessage: 'No invites found',
actions: {inviteUser: 'Invite User'},
headerRow: {name: 'Name', email: 'Email', status: 'Status'},
cellData: {statusActive: 'Active', statusInactive: 'Inactive', statusPending: 'Pending'},
rowActions: {activate: 'Activate Invite', deactivate: 'Deactivate Invite'},
};
const rows: ListInvitesRowData[] = [{id: '1', name: 'Alex', email: 'alex@example.com', status: 'pending'}];
<ListInvites
listInvitesTitle="Manage Invites"
labels={labels}
data={rows}
onClickAction={() => {}}
rowHref={row => `/invites/${row.id}`}
onNavigate={(path) => window.location.assign(path)}
/>;