List Users Block
ListUsers is a user directory table built on MUI Table primitives. It supports status filtering tabs, dismissible search chip filters, status change dropdown actions, pagination, and direct layout customization through compound children or block overrides.
Installation
- npm
- yarn
- pnpm
- bun
npm install @nodeblocks/frontend-list-users-block
yarn add @nodeblocks/frontend-list-users-block
pnpm add @nodeblocks/frontend-list-users-block
bun add @nodeblocks/frontend-list-users-block
What You Need
| Item | Why it matters |
|---|---|
listUsersTitle | Heading text or React Node for the table section |
data | Array of user records matching ListUsersRowData (id, name, createdAt, status) |
labels | Text labels object mapping headers, search placeholders, row actions, and empty states |
ListUsers does not maintain list state. Manage parameters like current page, active search values, search chips, and tab filters in your application state and handle them in callback events.
Code Examples
- Quick Start
- Labels and URLs
- Compound Components
- Block Override
function Example() { const [page, setPage] = React.useState(1); const users = [ { id: '1', name: 'John Smith', createdAt: '2026-01-15T10:30:00Z', status: 'active' }, { id: '2', name: 'Sarah Johnson', createdAt: '2026-01-14T09:15:00Z', status: 'inactive' } ]; const labels = { emptyStateMessage: 'No users found', searchFieldPlaceholder: 'Search users...', headerRow: { name: 'Name', createdAt: 'Created At', status: 'Status', }, cellData: { statusActive: 'Active', statusInactive: 'Inactive', }, }; const [lastAction, setLastAction] = React.useState('Navigation feedback will appear here.'); return ( <div style={{ minHeight: 400 }}> {/** Inline feedback for navigation */} <ListUsers listUsersTitle="User Directory" labels={labels} data={users} onNavigate={(to) => setLastAction(`Navigating: ${to}`)} rowHref={(row) => `#users/${row.id}`} pagination={{ currentPage: page, totalPages: 1, onPageChange: setPage, }} /> <div style={{marginTop: 12, color: '#475569', fontSize: 13}}>{lastAction}</div> </div> ); }
Wire up active search submit, filter chips, navigation row URLs, and activation/deactivation callbacks.
function Example() { const [searchValue, setSearchValue] = React.useState(''); const [chips, setChips] = React.useState([ { key: 'admin', label: 'Administrator' } ]); const [lastAction, setLastAction] = React.useState('Inline feedback appears here when you search or navigate.'); const [users, setUsers] = React.useState([ { id: '1', name: 'Michael Brown', createdAt: '2026-05-20T08:00:00Z', status: 'active', }, { id: '2', name: 'Emma Watson', createdAt: '2026-05-18T14:45:00Z', status: 'inactive', } ]); const labels = { emptyStateMessage: 'No users match criteria', searchFieldPlaceholder: 'Search directory...', rowActions: { activate: 'Activate Account', deactivate: 'Suspend Account', }, headerRow: { name: 'Employee', createdAt: 'Date Joined', status: 'Lifecycle Status', }, cellData: { statusActive: 'Enabled', statusInactive: 'Disabled', }, }; const handleChipDelete = (chipToDelete) => { setChips(prev => prev.filter(c => c.key !== chipToDelete.key)); }; const handleSearchSubmit = () => { if (searchValue.trim() && !chips.some(c => c.label === searchValue.trim())) { setChips(prev => [...prev, { key: `search-${Date.now()}`, label: searchValue.trim() }]); } setSearchValue(''); }; const handleActivate = (id) => { setUsers(prev => prev.map(u => u.id === id ? { ...u, status: 'active' } : u)); setLastAction(`Activated user ID: ${id}`); }; const handleDeactivate = (id) => { setUsers(prev => prev.map(u => u.id === id ? { ...u, status: 'inactive' } : u)); setLastAction(`Suspended user ID: ${id}`); }; return ( <div style={{ minHeight: 400 }}> <ListUsers listUsersTitle="Corporate Directory" labels={labels} data={users} // Search & Filters searchValue={searchValue} onSearchFieldChange={setSearchValue} onSearchSubmit={handleSearchSubmit} searchChipsTitle="Filters" searchChips={chips} onSearchChipDelete={handleChipDelete} // Action Handlers onItemActivate={handleActivate} onItemDeactivate={handleDeactivate} resolveRowAction={(row) => row.status === 'inactive' ? ['activate'] : ['deactivate']} shouldShowDropdownMenu={() => true} // Row Navigation Hrefs onNavigate={(to) => setLastAction(`Routing to: ${to}`)} rowHref={(row) => `#profile/${row.id}`} /> <div style={{marginTop: 12, color: '#475569', fontSize: 13}}>{lastAction}</div> </div> ); }
- Provide an
onItemActivateandonItemDeactivatecallback. In response, trigger API requests to flip account statuses and sync localdata. - Set
resolveRowActionto return'activate' \| 'deactivate'lists, orundefined, controlling option toggles displayed inside the actions menu.
Compose the layout using fine-grained compound child blocks. Pass parameters directly to the specific elements rather than the parent context.
function Example() { const [currentTab, setCurrentTab] = React.useState('all'); const [searchValue, setSearchValue] = React.useState(''); const [lastAction, setLastAction] = React.useState('Inline feedback will update below.'); const users = [ { id: '1', name: 'John Smith', createdAt: '2026-01-15T10:30:00Z', status: 'active' } ]; const labels = { emptyStateMessage: 'No matches found', searchFieldPlaceholder: 'Search records...', headerRow: { name: 'Name', createdAt: 'Created', status: 'Status', }, cellData: { statusActive: 'Active', statusInactive: 'Inactive', }, }; return ( <div style={{ minHeight: 400 }}> <ListUsers listUsersTitle="Workspace Members" labels={labels} data={users} tabs={[ { key: 'all', label: 'All Users' }, { key: 'active', label: 'Enabled' } ]} currentTab={currentTab} onTabChange={setCurrentTab} onNavigate={(to) => setLastAction(`Navigate: ${to}`)} > <ListUsers.Header sx={{ padding: '12px 24px', backgroundColor: '#fafafa', borderRadius: '6px 6px 0 0', borderBottom: '1px solid #e8e8e8', }} > <ListUsers.Title listUsersTitle="Workspace Members" /> <ListUsers.Action labels={labels} searchValue={searchValue} onSearchFieldChange={setSearchValue} onSearchSubmit={() => setLastAction(`Searched: ${searchValue}`)} /> </ListUsers.Header> <ListUsers.Tabs /> <ListUsers.Content> <ListUsers.Table labels={labels} data={users} /> </ListUsers.Content> <ListUsers.Pagination /> </ListUsers> <div style={{marginTop: 12, color: '#475569', fontSize: 13}}>{lastAction}</div> </div> ); }
Reorder blocks or inject notice headers using the standard function children override block mapping.
function Example() { const [currentTab, setCurrentTab] = React.useState('all'); const users = [ { id: '1', name: 'John Smith', createdAt: '2026-01-15T10:30:00Z', status: 'active' } ]; const labels = { emptyStateMessage: 'Empty', searchFieldPlaceholder: 'Search...', headerRow: { name: 'Name', createdAt: 'Created', status: 'Status' }, cellData: { statusActive: 'Active', statusInactive: 'Inactive' }, }; return ( <div style={{ minHeight: 450 }}> <ListUsers listUsersTitle="Overridden List Layout" labels={labels} data={users} tabs={[{ key: 'all', label: 'All Users' }]} currentTab={currentTab} onTabChange={setCurrentTab} onNavigate={() => {}} > {({ defaultBlocks, defaultBlockOrder }) => ({ blocks: { ...defaultBlocks, systemAlert: ( <div style={{ background: '#f6ffed', border: '1px solid #b7eb8f', borderRadius: '6px', padding: '10px 16px', fontSize: '13px', color: '#389e0d', }} > Database connection is secure. Directory is up-to-date. </div> ), }, blockOrder: ['systemAlert', 'header', 'tabs', 'content', 'pagination'], })} </ListUsers> </div> ); }
When to use block overrides
Use overrides to prepend banners, announcements, or custom tables above the user grid list. The standard block order is: ['header', 'searchChips', 'content', 'pagination'] (defaultBlockOrder).
Important Props
Core Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
data | ListUsersRowData[] | Yes | - | Array of user record objects |
isLoading | boolean | No | undefined | Renders the circular loader instead of default content area when true |
onNavigate | (to: string) => void | No | undefined | Navigation callback triggered on click actions (such as row clicks) |
onItemActivate | (rowId: string) => void | No | undefined | Callback triggered when selecting the "Activate" action dropdown option |
onItemDeactivate | (rowId: string) => void | No | undefined | Callback triggered when selecting the "Deactivate" action dropdown option |
resolveRowAction | (row: ListUsersRowData) => ('activate' | 'deactivate')[] | undefined | No | undefined | Direct check evaluating active account options per row |
shouldShowDropdownMenu | (row: ListUsersRowData) => boolean | No | undefined | Check deciding whether to show action dropdown buttons entirely |
statusMatch | { inUse: string; notInUse: string } | No | { inUse: 'active', notInUse: 'inactive' } | Maps raw row.status values to active/inactive text in the generated status column |
pagination | PaginationProps | No | undefined | Stateful pagination controls: { currentPage, totalPages, onPageChange, className? } |
Content Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
listUsersTitle | ReactNode | Yes | - | Heading text or component rendered inside the Title sub-block |
labels | { emptyStateMessage: string; searchFieldPlaceholder: string; rowActions?: { activate: string; deactivate: string }; headerRow: { createdAt: string; name: string; status: string }; cellData: { statusActive: string; statusInactive: string } } | Yes | - | Direct layout translation and wording mappings |
rowHref | (row: ListUsersRowData) => string | No | undefined | Direct row href URL generator callback |
tabs | { key: string; label: string; isDisabled?: boolean; subtitle?: string }[] | No | undefined | Tab filter category items |
currentTab | string | No | undefined | Currently selected tab identifier |
onTabChange | (tab: string) => void | No | undefined | Callback triggered on category tab clicks |
searchValue | string | No | undefined | Value of search text input |
onSearchFieldChange | (value: string) => void | No | undefined | Callback triggered when typing into search text input |
onSearchSubmit | () => void | No | undefined | Triggered on search button clicks or Enter submits |
searchChipsTitle | ReactNode | No | undefined | Label displayed next to active chip filter badges |
searchChips | BaseTableSearchChip[] | No | undefined | Filters mapped inside the dismissible search chips bar |
onSearchChipDelete | (chip: BaseTableSearchChip, index: number, event: SyntheticEvent) => void | No | undefined | Callback triggered on active chip dismissals |
labels Structure
Provide exact wording keys inside the labels object matching this layout structure:
{
emptyStateMessage: string;
searchFieldPlaceholder: string;
rowActions?: {
activate: string;
deactivate: string;
};
headerRow: {
createdAt: string;
name: string;
status: string;
};
cellData: {
statusActive: string;
statusInactive: string;
};
}
Layout and Composition Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children | BlocksOverride | ReactNode | No | undefined | Compound child elements or override function child |
className | string | No | undefined | Styling class applied on the outer container |
sx | SxProps | No | undefined | MUI SX styling overrides passed to the outer Stack |
ListUsers inherits all StackProps (except children). Default block order is ['header', 'searchChips', 'content', 'pagination'] (defaultBlockOrder).
Default UI Blocks
| Block | Built on | Notes |
|---|---|---|
ListUsers (root) | BaseTable | Direct table wrapper establishing theme contexts |
ListUsers.Header | BaseTable.Header | Flex container holding Title and search Actions |
ListUsers.Title | BaseTable.Header.Title | Renders the main head text |
ListUsers.Action | BaseTable.Header.Actions | Wraps user search input components |
ListUsers.SearchChips | BaseTable.SearchChips | Container holding active search filters |
ListUsers.Tabs | BaseTable.Tabs | Available for compound/override layouts; reads tab definitions from root tabs |
ListUsers.Content | BaseTable.Content | Container block holding loaders or tabular data grids |
ListUsers.Loader | BaseTable.Content.Loader | Progress spinner shown during loadings |
ListUsers.Table | BaseTable.Content.Grid | Presents user name, creation date, status cells, and actions |
ListUsers.Pagination | BaseTable.Pagination | Standard pagination controls mapped at bottom |
TypeScript
import { ListUsers } from '@nodeblocks/frontend-list-users-block';
import type { ListUsersRowData } from '@nodeblocks/frontend-list-users-block';
const users: ListUsersRowData[] = [
{
id: 'usr_001',
name: 'Kenji Sato',
createdAt: '2026-05-27T01:00:00Z',
status: 'active',
}
];
const labels = {
emptyStateMessage: 'No employees found',
searchFieldPlaceholder: 'Search database',
rowActions: { activate: 'Enable', deactivate: 'Suspend' },
headerRow: {
name: 'Member',
createdAt: 'Joined',
status: 'Status',
},
cellData: { statusActive: 'Active', statusInactive: 'Suspended' },
};
<ListUsers
listUsersTitle="Corporate Registry"
labels={labels}
data={users}
/>;