List Invites Block
The ListInvites Component is a fully customizable and accessible invite management interface built with React, TypeScript, and MUI. It provides a complete tabular invite listing experience with modern design patterns, action dropdowns, pagination support, loading states, and flexible customization options for advanced invite management applications.
π Installationβ
npm install @nodeblocks/frontend-list-invites-block@0.2.1
π Usageβ
import {ListInvites} from '@nodeblocks/frontend-list-invites-block';
- Basic Usage
- Advanced Usage
function SimpleListInvites() { const inviteData = [ { id: '1', name: 'John Doe', email: 'john@example.com', status: 'Pending', }, { id: '2', name: 'Jane Smith', email: 'jane@example.com', status: 'Accepted', }, { id: '3', name: 'Bob Wilson', email: 'bob@example.com', status: 'Expired', }, ]; const labels = { emptyStateMessage: 'No invitations found', actions: { inviteUser: 'Invite User', }, headerRow: { name: 'Name', email: 'Email', status: 'Status', }, rowActions: { reject: 'Reject', }, unsetDateMessage: 'Not set', }; return ( <ListInvites data={inviteData} labels={labels} listInvitesTitle="Team Invitations" onNavigate={to => console.log('Navigate to:', to)} onClickAction={() => console.log('Invite action clicked')} onItemReject={id => console.log('Reject invite:', id)} rowHref={row => `/invites/${row.id}`} > <ListInvites.Header /> <ListInvites.Content /> </ListInvites> ); }
function AdvancedListInvites() { const [currentPage, setCurrentPage] = useState(1); const allInvites = [ { id: '1', name: 'Alice Johnson', email: 'alice.johnson@company.com', status: 'Pending', }, { id: '2', name: 'Bob Williams', email: 'bob.williams@company.com', status: 'Accepted', }, { id: '3', name: 'Carol Davis', email: 'carol.davis@company.com', status: 'Pending', }, { id: '4', name: 'David Brown', email: 'david.brown@company.com', status: 'Expired', }, { id: '5', name: 'Eve Miller', email: 'eve.miller@company.com', status: 'Declined', }, ]; const labels = { emptyStateMessage: 'No invitations match your criteria', actions: { inviteUser: 'Invite New Member', }, headerRow: { name: 'Full Name', email: 'Email Address', status: 'Invitation Status', }, rowActions: { reject: 'Cancel Invitation', }, unsetDateMessage: 'Date not specified', }; const pagination = { currentPage, totalPages: 3, onPageChange: page => setCurrentPage(page), }; const getStatusColor = (status) => { const colors = { Pending: {bg: '#fef3c7', text: '#92400e', icon: 'β³'}, Accepted: {bg: '#dcfce7', text: '#166534', icon: 'β'}, Expired: {bg: '#f3f4f6', text: '#374151', icon: 'β'}, Declined: {bg: '#fee2e2', text: '#991b1b', icon: 'β'}, }; return colors[status] || {bg: '#f3f4f6', text: '#374151', icon: 'β’'}; }; const handleNavigate = (to) => { console.log('Navigating to:', to); }; const handleInviteAction = () => { console.log('Opening invite modal'); }; const handleRejectInvite = (id) => { console.log('Rejecting invite:', id); }; return ( <ListInvites data={allInvites} labels={labels} listInvitesTitle="Team Invitations" onNavigate={handleNavigate} onClickAction={handleInviteAction} onItemReject={handleRejectInvite} rowHref={row => `/invites/${row.id}`} pagination={pagination} sx={{ maxWidth: 1000, mx: 'auto', bgcolor: '#ffffff', borderRadius: 3, boxShadow: '0 4px 20px rgba(0,0,0,0.08)', overflow: 'hidden', }} > {({defaultBlocks, defaultBlockOrder}) => { const customHeader = ( <div style={{ padding: '24px', borderBottom: '1px solid #e2e8f0', }} > <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px', }} > <div> <div style={{ fontSize: '24px', fontWeight: '700', color: '#1e293b', margin: '0 0 4px 0', }} > Team Invitations </div> <p style={{fontSize: '14px', color: '#64748b', margin: 0}}>Manage pending and past team invitations</p> </div> <button onClick={handleInviteAction} style={{ padding: '12px 24px', borderRadius: '10px', background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', color: '#ffffff', border: 'none', fontSize: '14px', fontWeight: '600', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '8px', }} > <span>+</span> Invite Member </button> </div> </div> ); const customContent = ( <div style={{padding: '16px'}}> {allInvites.length === 0 ? ( <div style={{ textAlign: 'center', padding: '48px', color: '#64748b', }} > <div style={{fontSize: '48px', marginBottom: '16px'}}>βοΈ</div> <p style={{fontSize: '16px', fontWeight: '500'}}>No invitations found</p> <p style={{fontSize: '14px'}}>Invitations matching this filter will appear here</p> </div> ) : ( <div style={{display: 'flex', flexDirection: 'column', gap: '12px'}}> {allInvites.map(invite => { const statusInfo = getStatusColor(invite.status); return ( <div key={invite.id} style={{ padding: '20px', borderRadius: '12px', border: '1px solid #e2e8f0', display: 'flex', alignItems: 'center', gap: '16px', }} > <div style={{ width: '48px', height: '48px', borderRadius: '12px', background: '#f1f5f9', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '20px', flexShrink: 0, }} > {invite.name.charAt(0).toUpperCase()} </div> <div style={{flex: 1, minWidth: 0}}> <div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '4px', }} > <span style={{ fontSize: '15px', fontWeight: '600', color: '#1e293b', }} > {invite.name} </span> <span style={{ padding: '4px 10px', borderRadius: '6px', background: statusInfo.bg, color: statusInfo.text, fontSize: '12px', fontWeight: '500', display: 'flex', alignItems: 'center', gap: '4px', }} > {statusInfo.icon} {invite.status} </span> </div> <div style={{ fontSize: '13px', color: '#64748b', }} > {invite.email} </div> </div> <div style={{display: 'flex', gap: '8px'}}> {invite.status === 'Pending' && ( <button onClick={() => handleRejectInvite(invite.id)} style={{ padding: '8px 16px', borderRadius: '8px', border: '1px solid #fee2e2', background: '#fff5f5', color: '#dc2626', fontSize: '13px', fontWeight: '500', cursor: 'pointer', }} > Cancel </button> )} </div> </div> ); })} </div> )} </div> ); return { blocks: { ...defaultBlocks, header: customHeader, content: customContent, }, blockOrder: defaultBlockOrder, }; }} </ListInvites> ); }
π§ Props Referenceβ
Main Component Propsβ
| Prop | Type | Default | Description |
|---|---|---|---|
listInvitesTitle | ReactNode | Required | Title for the invites 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 |
onClickAction | () => void | Required | Callback function for the main action button |
onItemReject | (inviteId: string) => void | Required | Callback function when invite is rejected |
data | ListInvitesRowData[] | Required | Array of invite data objects |
rowHref | (row: ListInvitesRowData) => string | Required | Function to generate row link URLs |
shouldShowDropdownMenu | (row: ListInvitesRowData) => boolean | undefined | Function to conditionally show dropdown menu per row |
pagination | PaginationProps | undefined | Pagination configuration |
className | string | undefined | Additional CSS class name for styling the container |
children | BlocksOverride | undefined | Function to override default blocks |
Note: The main component inherits MUI Stack props. Root container uses spacing={3} and sx={{ p: 3 }} by default.
Sub-Componentsβ
The ListInvites component provides several sub-components. All sub-components receive their default values from the main component's context and can override these values through props.
ListInvites.Headerβ
Container for the header including title and action button.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | undefined | Custom content to override default header rendering |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI Box props. Contains a Stack with direction="row" and justifyContent: 'space-between'.
ListInvites.Titleβ
Displays the invites section title.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | From context | Custom content to override title |
listInvitesTitle | ReactNode | From context | Title text when children not provided |
variant | TypographyProps['variant'] | "h4" | MUI Typography variant |
component | ElementType | "h1" | HTML element to render |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI Typography props.
ListInvites.Actionβ
Container for the action button.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | undefined | Custom content to override default action rendering |
onClickAction | () => void | From context | Callback for the action button |
labels | TableLabels | From context | Labels for action text |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI Stack props (except direction which is fixed to "row"). Default alignment is alignItems: 'center'.
ListInvites.Contentβ
Container for the main content area (loader or table).
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | undefined | Custom content to override default rendering |
isLoading | boolean | From context | Loading state from context |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI Box props. Renders Loader when loading, Table otherwise.
ListInvites.Loaderβ
Displays a loading indicator.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | CircularProgress | Custom loading indicator content |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI Stack props. Default alignment is alignItems: 'center'.
ListInvites.Tableβ
Displays the invites data in a table format.
| Prop | Type | Default | Description |
|---|---|---|---|
labels | TableLabels | From context | Labels for table headers and actions |
data | ListInvitesRowData[] | From context | Array of invite data |
rowHref | (row: ListInvitesRowData) => string | From context | Function to generate row links |
onNavigate | (to: string) => void | From context | Navigation callback for row clicks |
onItemReject | (id: string) => void | From context | Callback when rejecting an invite |
shouldShowDropdownMenu | (row: ListInvitesRowData) => boolean | From context | Function to conditionally show dropdown |
pagination | PaginationProps | From context | Pagination configuration |
spacing | number | 3 | Stack spacing between elements |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI Stack props.
π¨ Configuration examplesβ
Custom Styled Exampleβ
<ListInvites
data={inviteData}
labels={labels}
listInvitesTitle="Invitations"
onNavigate={console.log}
onClickAction={() => console.log('Action clicked')}
onItemReject={console.log}
rowHref={row => `/invite/${row.id}`}
sx={{
bgcolor: 'grey.900',
color: 'grey.100',
p: 3,
borderRadius: 2,
}}
>
<ListInvites.Header
sx={{
'& .MuiTypography-root': {
color: 'grey.100',
},
}}
/>
<ListInvites.Content
sx={{
'& .MuiTableCell-root': {
color: 'grey.100',
borderColor: 'grey.700',
},
}}
/>
</ListInvites>
Using Block Override Patternβ
<ListInvites {...props}>
{({defaultBlocks, defaultBlockOrder}) => {
const customHeader = (
<div style={{padding: '24px', borderBottom: '1px solid #e2e8f0'}}>
<div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
<div>
<div style={{fontSize: '24px', fontWeight: '700', color: '#1e293b'}}>
Team Invitations
</div>
<p style={{fontSize: '14px', color: '#64748b', margin: 0}}>
Manage pending and past team invitations
</p>
</div>
<button
onClick={handleInviteAction}
style={{
padding: '12px 24px',
borderRadius: '10px',
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
color: '#ffffff',
border: 'none',
cursor: 'pointer',
}}
>
<span>+</span> Invite Member
</button>
</div>
</div>
);
return {
blocks: {
...defaultBlocks,
header: customHeader,
},
blockOrder: defaultBlockOrder,
};
}}
</ListInvites>
π§ TypeScript Supportβ
Full TypeScript support with comprehensive type definitions:
import {ListInvites} from '@nodeblocks/frontend-list-invites-block';
import { StackProps } from '@mui/material';
import { ReactNode } from 'react';
// Row data interface
interface ListInvitesRowData {
id: string;
name: string;
email: string;
status: string;
}
// Pagination props interface
interface PaginationProps {
className?: string;
currentPage: number;
onPageChange: (page: number) => void;
totalPages: number;
}
// Labels interface
interface TableLabels {
emptyStateMessage: string;
actions: {
inviteUser: string;
};
headerRow: {
name: string;
email: string;
status: string;
};
rowActions: {
reject: string;
};
unsetDateMessage: string;
}
// Main component props interface
interface ListInvitesProps extends Omit<StackProps, 'children'> {
listInvitesTitle: ReactNode;
labels: TableLabels;
isLoading?: boolean;
onNavigate: (to: string) => void;
onClickAction: () => void;
onItemReject: (inviteId: string) => void;
data: ListInvitesRowData[];
rowHref: (row: ListInvitesRowData) => string;
shouldShowDropdownMenu?: (row: ListInvitesRowData) => boolean;
pagination?: PaginationProps;
children?: BlocksOverride;
}
// Usage example with full typing
function TypedListInvites() {
const [page, setPage] = useState(1);
const inviteData: ListInvitesRowData[] = [
{
id: 'inv-1',
name: 'Developer One',
email: 'developer@company.com',
status: 'Pending',
},
{
id: 'inv-2',
name: 'Designer Two',
email: 'designer@company.com',
status: 'Accepted',
},
];
const labels: TableLabels = {
emptyStateMessage: 'No team invitations found',
actions: {
inviteUser: 'Invite Team Member',
},
headerRow: {
name: 'Full Name',
email: 'Email Address',
status: 'Invitation Status',
},
rowActions: {
reject: 'Reject Invitation',
},
unsetDateMessage: 'Date not set',
};
const handleNavigate = (to: string): void => {
console.log('Navigating to:', to);
};
const handleInviteAction = (): void => {
console.log('Opening invite dialog');
};
const handleReject = (id: string): void => {
console.log('Rejecting invitation:', id);
};
const pagination: PaginationProps = {
currentPage: page,
totalPages: 5,
onPageChange: newPage => setPage(newPage),
};
return (
<ListInvites
data={inviteData}
labels={labels}
listInvitesTitle="Team Invitations"
onNavigate={handleNavigate}
onClickAction={handleInviteAction}
onItemReject={handleReject}
rowHref={row => `/invitations/${row.id}`}
pagination={pagination}
isLoading={false}
sx={{
maxWidth: 800,
mx: 'auto',
p: 3,
border: '1px solid #e5e7eb',
borderRadius: 2,
}}
>
<ListInvites.Header />
<ListInvites.Table />
</ListInvites>
);
}
π Notesβ
- The root component uses MUI's
Stackwith defaultspacing={3}andsx={{ p: 3 }}padding - Uses MUI
Table,TableContainer,TableHead,TableBody, andTableRowcomponents - Row actions are displayed in a dropdown menu using MUI
MenuandMenuItemwith ablockicon - Empty state displays a
personicon (Material Symbols) with theemptyStateMessagefrom labels - Pagination uses MUI
Paginationcomponent withvariant="outlined"andshape="rounded" - Table rows are clickable when
rowHrefreturns a valid URL (cursor changes to pointer) - The
shouldShowDropdownMenuprop allows conditional display of the action menu per row - Default action button uses MUI
Buttonwithvariant="contained"and apeople_outlineicon ListInvites.Titleuses MUI Typography with defaultvariant="h4"andcomponent="h1"- All sub-components inherit their respective MUI component props and support the
sxprop for styling - Block override pattern allows customizing, replacing, or reordering default blocks
Built with β€οΈ using React, TypeScript, and MUI.