List Organizations Block
The ListOrganizations Component is a fully customizable and accessible organizations table interface built with React, TypeScript, and MUI. It provides a complete tabular organization listing experience with modern design patterns, audit status management, search functionality, tabs, row actions, pagination support, loading states, and flexible customization options for advanced organization management applications.
π Installationβ
npm install @nodeblocks/frontend-list-organization-block@0.2.1
π Usageβ
import {ListOrganizations} from '@nodeblocks/frontend-list-organization-block';
- Basic Usage
- Advanced Usage
function SimpleListOrganizations() { const organizationData = [ { id: '1', name: 'Acme Corporation', createdAt: '2021-03-15', joinDate: '2024-01-10', auditStatus: 'approved', }, { id: '2', name: 'Tech Startup Inc', createdAt: '2022-06-20', joinDate: '2024-01-12', auditStatus: 'waiting_for_review', }, { id: '3', name: 'Design Studio', createdAt: '2023-01-05', joinDate: '2024-01-14', auditStatus: 'rejected', }, ]; const tabs = [{label: 'All'}]; const labels = { emptyStateMessage: 'No organizations found', searchFieldPlaceholder: 'Search organizations...', actions: { headerAction: 'Create Organization', rowAction: 'View Details', }, headerRow: { createdAt: 'Created', name: 'Organization Name', joinDate: 'Join Date', auditStatus: 'Status', }, cellData: { statusApproved: 'Approved', statusRejected: 'Rejected', statusWaitingForReview: 'Pending Review', }, }; return ( <ListOrganizations data={organizationData} labels={labels} tabs={tabs} listOrganizationsTitle="My Organizations" onSearchFieldChange={value => console.log('Search:', value)} onActionClick={() => console.log('Create clicked')} onNavigate={to => console.log('Navigate to:', to)} onRowActionClick={row => console.log('Row action:', row.id)} rowHref={row => `/organizations/${row.id}`} > <ListOrganizations.Header /> <ListOrganizations.Table /> </ListOrganizations> ); }
function AdvancedListOrganizations() { const [searchQuery, setSearchQuery] = useState(''); const [currentTab, setCurrentTab] = useState('All'); const [currentPage, setCurrentPage] = useState(1); const allOrganizations = [ { id: '1', name: 'Global Tech Solutions', createdAt: '2021-03-15', joinDate: '2024-01-10', auditStatus: 'approved', }, { id: '2', name: 'Creative Labs', createdAt: '2022-06-20', joinDate: '2024-01-12', auditStatus: 'approved', }, { id: '3', name: 'DataFlow Analytics', createdAt: '2022-01-10', joinDate: '2024-01-14', auditStatus: 'waiting_for_review', }, { id: '4', name: 'CloudFirst Systems', createdAt: '2020-11-05', joinDate: '2024-01-08', auditStatus: 'approved', }, { id: '5', name: 'Mobile Innovations', createdAt: '2023-02-28', joinDate: '2024-01-15', auditStatus: 'rejected', }, ]; const filteredOrganizations = allOrganizations.filter(org => org.name.toLowerCase().includes(searchQuery.toLowerCase()), ); const tabs = [ {key: 'all', label: 'All'}, {key: 'approved', label: 'Approved'}, {key: 'pending', label: 'Pending Review'}, {key: 'rejected', label: 'Rejected'}, ]; const labels = { emptyStateMessage: 'No organizations match your search', searchFieldPlaceholder: 'Search by organization name...', actions: { headerAction: <span>+ New Organization</span>, rowAction: <span>Manage</span>, }, headerRow: { createdAt: 'Founded', name: 'Organization', joinDate: 'Member Since', auditStatus: 'Audit Status', }, cellData: { statusApproved: <span style={{color: '#166534'}}>β Approved</span>, statusRejected: <span style={{color: '#991b1b'}}>β Rejected</span>, statusWaitingForReview: <span style={{color: '#92400e'}}>β³ Pending</span>, }, }; const pagination = { currentPage, totalPages: 3, onPageChange: page => setCurrentPage(page), }; const getStatusColor = (status) => { const colors = { approved: {bg: '#dcfce7', text: '#166534'}, waiting_for_review: {bg: '#fef3c7', text: '#92400e'}, rejected: {bg: '#fee2e2', text: '#991b1b'}, }; return colors[status] || {bg: '#f3f4f6', text: '#374151'}; }; const handleNavigate = (to) => { console.log('Navigating to:', to); }; const handleRowAction = (row) => { console.log('Managing organization:', row.id, row.name); }; const handleCreateOrganization = () => { console.log('Creating new organization'); }; return ( <ListOrganizations data={filteredOrganizations} labels={labels} tabs={tabs} currentTab={currentTab} onTabChange={tab => setCurrentTab(tab)} listOrganizationsTitle="Organization Management" onSearchFieldChange={value => setSearchQuery(value)} onActionClick={handleCreateOrganization} onNavigate={handleNavigate} onRowActionClick={handleRowAction} rowHref={row => `/organizations/${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', marginBottom: '4px', }} > My Organizations </div> <p style={{fontSize: '14px', color: '#64748b', margin: 0}}>Organizations you own or are a member of</p> </div> <button onClick={handleCreateOrganization} 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> New Organization </button> </div> </div> ); const customContent = ( <div style={{padding: '16px'}}> {filteredOrganizations.length === 0 ? ( <div style={{ textAlign: 'center', padding: '48px', color: '#64748b', }} > <div style={{fontSize: '48px', marginBottom: '16px'}}>π’</div> <p style={{fontSize: '16px', fontWeight: '500'}}>No organizations found</p> <p style={{fontSize: '14px'}}> {searchQuery ? 'Try a different search term' : 'Create your first organization to get started'} </p> </div> ) : ( <div style={{display: 'flex', flexDirection: 'column', gap: '12px'}}> {filteredOrganizations.map(org => { const statusColors = getStatusColor(org.auditStatus); return ( <div key={org.id} onClick={() => handleRowAction(org)} style={{ padding: '20px', borderRadius: '12px', border: '1px solid #e2e8f0', display: 'flex', alignItems: 'center', gap: '16px', cursor: 'pointer', transition: 'all 0.2s ease', }} > <div style={{ width: '60px', height: '60px', borderRadius: '12px', background: '#f1f5f9', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, }} > <span style={{ fontSize: '24px', fontWeight: '700', color: '#64748b', }} > {org.name.charAt(0)} </span> </div> <div style={{flex: 1, minWidth: 0}}> <div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '8px', }} > <span style={{ fontSize: '16px', fontWeight: '600', color: '#1e293b', }} > {org.name} </span> <span style={{ padding: '4px 12px', borderRadius: '20px', background: statusColors.bg, color: statusColors.text, fontSize: '12px', fontWeight: '600', }} > {org.auditStatus === 'approved' ? 'Approved' : org.auditStatus === 'rejected' ? 'Rejected' : 'Pending'} </span> </div> <div style={{ display: 'flex', gap: '16px', fontSize: '13px', color: '#64748b', }} > <span>Founded: {org.createdAt}</span> <span>Joined: {org.joinDate}</span> </div> </div> <span style={{ color: '#94a3b8', fontSize: '18px', }} > β </span> </div> ); })} </div> )} </div> ); return { blocks: { ...defaultBlocks, header: customHeader, content: customContent, }, blockOrder: defaultBlockOrder, }; }} </ListOrganizations> ); }
π§ Props Referenceβ
Main Component Propsβ
| Prop | Type | Default | Description |
|---|---|---|---|
listOrganizationsTitle | ReactNode | Required | Title for the organizations table section |
labels | OrganizationLabels | Required | Labels object for table headers, actions, and messages |
isLoading | boolean | undefined | Whether the table is currently loading |
onSearchFieldChange | (value: string) => void | Required | Callback function when search field value changes |
onActionClick | () => void | Required | Callback function for header action button |
onNavigate | (to: string) => void | Required | Callback function for navigation |
onRowActionClick | (rowData: ListOrganizationsRowData) => void | Required | Callback function when row action is clicked |
data | ListOrganizationsRowData[] | Required | Array of organization data objects |
rowHref | (row: ListOrganizationsRowData) => string | Required | Function to generate row link URLs |
tabs | TabData[] | Required | Array of tab configuration objects |
currentTab | string | undefined | Currently active tab label |
onTabChange | (tab: string) => void | undefined | Callback function when tab is changed |
pagination | PaginationProps | undefined | Pagination configuration object |
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 ListOrganizations 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.
ListOrganizations.Headerβ
Container for the header including title and action components.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | undefined | Custom content to override default header rendering |
direction | StackProps['direction'] | "row" | Flex direction for layout |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI Stack props. Default justifyContent: 'space-between'.
ListOrganizations.Titleβ
Displays the organizations section title.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | From context | Custom content to override title |
listOrganizationsTitle | 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.
ListOrganizations.Actionβ
Container for search field and action button.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | undefined | Custom content to override default action rendering |
onActionClick | () => void | From context | Callback for the action button |
onSearchFieldChange | (value: string) => void | From context | Callback for search input |
labels | OrganizationLabels | From context | Labels for action text and placeholder |
direction | StackProps['direction'] | "row" | Flex direction |
spacing | number | 2 | Stack spacing between elements |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI Stack props.
ListOrganizations.Contentβ
Container for the main content area (loader, tabs, 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 Stack props with spacing={3}. Renders Loader when loading, Tabs + Table otherwise.
ListOrganizations.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', justifyContent: 'center'.
ListOrganizations.Tabsβ
Displays tab navigation for filtering organizations.
| Prop | Type | Default | Description |
|---|---|---|---|
tabs | TabData[] | From context | Array of tab configuration objects |
currentTab | string | From context | Currently active tab label |
onTabChange | (tab: string) => void | From context | Tab change callback function |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI Tabs props (except value, onChange, variant). Uses variant="fullWidth" by default.
ListOrganizations.Tableβ
Displays the organizations data in a table format.
| Prop | Type | Default | Description |
|---|---|---|---|
labels | OrganizationLabels | From context | Labels for table headers and actions |
data | ListOrganizationsRowData[] | From context | Array of organization data |
rowHref | (row: ListOrganizationsRowData) => string | From context | Function to generate row links |
onNavigate | (to: string) => void | From context | Navigation callback for row clicks |
onRowActionClick | (row: ListOrganizationsRowData) => void | From context | Callback when row action is clicked |
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 Header Stylingβ
<ListOrganizations.Header
sx={{
bgcolor: 'primary.main',
color: 'white',
borderRadius: 2,
p: 3
}}
/>
Custom Title Stylingβ
<ListOrganizations.Title
variant="h3"
sx={{
fontWeight: 700,
color: 'primary.main'
}}
/>
Custom Status Labels with JSXβ
const labels = {
// ... other labels
cellData: {
statusApproved: <Chip label="Approved" color="success" size="small" />,
statusRejected: <Chip label="Rejected" color="error" size="small" />,
statusWaitingForReview: <Chip label="Pending" color="warning" size="small" />
}
};
Using Block Override Patternβ
<ListOrganizations {...props}>
{({defaultBlocks, defaultBlockOrder}) => ({
blocks: {
...defaultBlocks,
header: customHeader,
content: customContent,
},
blockOrder: defaultBlockOrder,
})}
</ListOrganizations>
π§ TypeScript Supportβ
Full TypeScript support with comprehensive type definitions:
import {ListOrganizations} from '@nodeblocks/frontend-list-organization-block';
import { StackProps } from '@mui/material';
import { ReactNode } from 'react';
// Tab data interface
interface TabData {
key?: string;
label: string;
isDisabled?: boolean;
subtitle?: string;
}
// Row data interface
interface ListOrganizationsRowData {
id: string;
createdAt: string;
name: string;
joinDate: string;
auditStatus: string;
}
// Pagination props interface
interface PaginationProps {
className?: string;
currentPage: number;
onPageChange: (page: number) => void;
totalPages: number;
}
// Labels interface
interface OrganizationLabels {
emptyStateMessage: string;
searchFieldPlaceholder: string;
actions: {
headerAction: ReactNode;
rowAction: ReactNode;
};
headerRow: {
createdAt: string;
name: string;
joinDate: string;
auditStatus: string;
};
cellData: {
statusApproved: ReactNode;
statusRejected: ReactNode;
statusWaitingForReview: ReactNode;
};
}
// Main component props interface
interface ListOrganizationsProps<T extends TabData[]> extends Omit<StackProps, 'children'> {
listOrganizationsTitle: ReactNode;
labels: OrganizationLabels;
isLoading?: boolean;
onSearchFieldChange: (value: string) => void;
onActionClick: () => void;
onNavigate: (to: string) => void;
onRowActionClick: (rowData: ListOrganizationsRowData) => void;
data: ListOrganizationsRowData[];
rowHref: (row: ListOrganizationsRowData) => string;
tabs: T;
currentTab?: T[number]['label'];
onTabChange?: (tab: T[number]['label']) => void;
pagination?: PaginationProps;
children?: BlocksOverride;
}
// Usage example with full typing
function TypedListOrganizations() {
const [page, setPage] = useState(1);
const [currentTab, setCurrentTab] = useState('All');
const organizationData: ListOrganizationsRowData[] = [
{
id: 'org-1',
name: 'Tech Company',
createdAt: '2020-05-10',
joinDate: '2024-01-15',
auditStatus: 'approved',
},
{
id: 'org-2',
name: 'Startup Inc',
createdAt: '2022-08-20',
joinDate: '2024-01-18',
auditStatus: 'waiting_for_review',
},
];
const tabs: TabData[] = [
{key: 'all', label: 'All'},
{key: 'approved', label: 'Approved'},
{key: 'pending', label: 'Pending'},
];
const labels: OrganizationLabels = {
emptyStateMessage: 'No organizations found',
searchFieldPlaceholder: 'Search organizations...',
actions: {
headerAction: 'Create Organization',
rowAction: 'Manage',
},
headerRow: {
createdAt: 'Founded',
name: 'Organization Name',
joinDate: 'Member Since',
auditStatus: 'Audit Status',
},
cellData: {
statusApproved: 'Approved',
statusRejected: 'Rejected',
statusWaitingForReview: 'Under Review',
},
};
const handleSearchChange = (value: string): void => {
console.log('Search value:', value);
};
const handleNavigate = (to: string): void => {
console.log('Navigating to:', to);
};
const handleRowAction = (row: ListOrganizationsRowData): void => {
console.log('Managing organization:', row.id);
};
const pagination: PaginationProps = {
currentPage: page,
totalPages: 5,
onPageChange: newPage => setPage(newPage),
};
return (
<ListOrganizations
data={organizationData}
labels={labels}
tabs={tabs}
currentTab={currentTab}
onTabChange={tab => setCurrentTab(tab)}
listOrganizationsTitle="Organization List"
onSearchFieldChange={handleSearchChange}
onActionClick={() => console.log('Create clicked')}
onNavigate={handleNavigate}
onRowActionClick={handleRowAction}
rowHref={row => `/organizations/${row.id}`}
pagination={pagination}
isLoading={false}
sx={{
maxWidth: 800,
mx: 'auto',
p: 3,
border: '1px solid #e5e7eb',
borderRadius: 2,
}}
>
<ListOrganizations.Header />
<ListOrganizations.Tabs />
<ListOrganizations.Table />
</ListOrganizations>
);
}
π Notesβ
- The root component uses MUI's
Stackwith defaultspacing={3}andsx={{ p: 3 }}padding - Uses MUI
Table,TableContainer,TableHead,TableBody, andTableRowcomponents - Date formatting uses
luxonlibrary withyyyy/M/dformat for display - Row action button only appears for rows with
auditStatus: 'waiting_for_review' - Empty state displays a person icon (SVG) with the
emptyStateMessagefrom labels - Pagination uses MUI
Paginationcomponent withvariant="outlined"andshape="rounded" - Table rows are clickable when
rowHrefreturns a valid URL (cursor changes to pointer) - Tabs use MUI
Tabscomponent withvariant="fullWidth"by default ListOrganizations.Titleuses MUI Typography with defaultvariant="h4"andcomponent="h1"- Search field uses MUI
TextFieldwith a search icon in the end adornment - Action button uses MUI
Buttonwithvariant="outlined"andsize="medium" - 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.