List Organizations Block
The ListOrganizations Component is a fully customizable and accessible organizations table interface built with React and TypeScript. 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.1.1
📖 Usage
import {ListOrganizations} from '@nodeblocks/frontend-list-organization-block';
- Basic Usage
- Advanced Usage
function BasicListOrganizations() {
const [currentTab, setCurrentTab] = useState('all');
const [isLoading, setIsLoading] = useState(false);
const [searchValue, setSearchValue] = useState('');
const organizationData = [
{
id: '1',
createdAt: '2024-01-15T10:30:00Z',
name: 'Acme Corporation',
joinDate: '2024-01-15T10:30:00Z',
auditStatus: 'approved'
},
{
id: '2',
createdAt: '2024-01-14T09:15:00Z',
name: 'Tech Solutions Ltd',
joinDate: '2024-01-20T14:45:00Z',
auditStatus: 'waiting_for_review'
},
{
id: '3',
createdAt: '2024-01-13T16:20:00Z',
name: 'Global Industries',
joinDate: '2024-01-18T11:30:00Z',
auditStatus: 'rejected'
}
];
const tabs = [
{ label: 'All Organizations' },
{ label: 'Approved' },
{ label: 'Pending Review' },
{ label: 'Rejected' }
];
const labels = {
emptyStateMessage: 'No organizations found',
searchFieldPlaceholder: 'Search organizations...',
actions: {
headerAction: 'Add Organization',
rowAction: 'Review'
},
headerRow: {
createdAt: 'Created At',
name: 'Organization Name',
joinDate: 'Join Date',
auditStatus: 'Status'
},
cellData: {
statusApproved: 'Approved',
statusRejected: 'Rejected',
statusWaitingForReview: 'Pending Review'
}
};
const handleSearchChange = (value) => {
setSearchValue(value);
console.log('Search changed:', value);
};
const handleActionClick = () => {
console.log('Header action clicked');
};
const handleNavigate = (path) => {
console.log('Navigate to:', path);
};
const handleRowActionClick = (rowData) => {
console.log('Row action clicked for:', rowData);
};
const getRowHref = (row) => `/organizations/${row.id}`;
return (
<ListOrganizations
listOrganizationsTitle="Organizations Management"
labels={labels}
isLoading={isLoading}
onSearchFieldChange={handleSearchChange}
onActionClick={handleActionClick}
onNavigate={handleNavigate}
onRowActionClick={handleRowActionClick}
data={organizationData}
rowHref={getRowHref}
tabs={tabs}
currentTab={currentTab}
onTabChange={setCurrentTab}>
<ListOrganizations.Header style={{display: 'flex', justifyContent: 'space-between'}}>
<ListOrganizations.Title />
<ListOrganizations.Action />
</ListOrganizations.Header>
<ListOrganizations.Content>
{isLoading ? (
<ListOrganizations.Loader />
) : (
<>
<ListOrganizations.Tabs />
<ListOrganizations.Table />
</>
)}
</ListOrganizations.Content>
</ListOrganizations>
);
}
function AdvancedListOrganizations() {
const [currentTab, setCurrentTab] = useState('すべて');
const [isLoading, setIsLoading] = useState(false);
const [searchValue, setSearchValue] = useState('');
const organizationData = [
{
id: '1',
createdAt: '2024-01-15T10:30:00Z',
name: '株式会社テクノロジー',
joinDate: '2024-01-15T10:30:00Z',
auditStatus: 'approved'
},
{
id: '2',
createdAt: '2024-01-14T09:15:00Z',
name: 'グローバル・ソリューションズ',
joinDate: '2024-01-20T14:45:00Z',
auditStatus: 'waiting_for_review'
},
{
id: '3',
createdAt: '2024-01-13T16:20:00Z',
name: 'イノベーション株式会社',
joinDate: '2024-01-18T11:30:00Z',
auditStatus: 'rejected'
},
{
id: '4',
createdAt: '2024-01-12T14:10:00Z',
name: 'デジタル・エンタープライズ',
joinDate: '2024-01-16T09:20:00Z',
auditStatus: 'approved'
},
{
id: '5',
createdAt: '2024-01-11T11:45:00Z',
name: 'スマート・システムズ',
joinDate: '2024-01-19T16:30:00Z',
auditStatus: 'waiting_for_review'
}
];
const tabs = [
{ label: 'すべて', key: 'all' },
{ label: '承認済み', key: 'approved' },
{ label: '審査中', key: 'pending' },
{ label: '拒否済み', key: 'rejected' }
];
const labels = {
emptyStateMessage: '組織が見つかりません',
searchFieldPlaceholder: '組織名で検索...',
actions: {
headerAction: '組織を追加',
rowAction: '審査'
},
headerRow: {
createdAt: '作成日',
name: '組織名',
joinDate: '参加日',
auditStatus: 'ステータス'
},
cellData: {
statusApproved: <span style={{color: '#27ae60', fontWeight: 'bold'}}>✅ 承認済み</span>,
statusRejected: <span style={{color: '#e74c3c', fontWeight: 'bold'}}>❌ 拒否済み</span>,
statusWaitingForReview: <span style={{color: '#f39c12', fontWeight: 'bold'}}>⏳ 審査中</span>
}
};
const handleSearchChange = (value) => {
setSearchValue(value);
console.log('検索変更:', value);
};
const handleActionClick = () => {
console.log('新しい組織を追加');
};
const handleNavigate = (path) => {
console.log('ナビゲート:', path);
};
const handleRowActionClick = (rowData) => {
console.log('組織審査:', rowData);
};
const getRowHref = (row) => `/organizations/${row.id}`;
return (
<ListOrganizations
listOrganizationsTitle="🏢 組織管理システム"
labels={labels}
isLoading={isLoading}
onSearchFieldChange={handleSearchChange}
onActionClick={handleActionClick}
onNavigate={handleNavigate}
onRowActionClick={handleRowActionClick}
data={organizationData}
rowHref={getRowHref}
tabs={tabs}
currentTab={currentTab}
onTabChange={setCurrentTab}
style={{
background: 'linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)',
borderRadius: '24px',
padding: '32px',
margin: '20px',
boxShadow: '0 20px 60px rgba(0,0,0,0.12)',
border: '1px solid rgba(255,255,255,0.3)'
}}>
{({ defaultBlocks, defaultBlockOrder }) => ({
blocks: {
// Enhanced header with professional styling
header: {
...defaultBlocks.header,
props: {
...defaultBlocks.header.props,
style: {
background: 'rgba(255, 255, 255, 0.8)',
borderRadius: '20px',
padding: '24px',
marginBottom: '24px',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255,255,255,0.4)',
boxShadow: '0 8px 32px rgba(0,0,0,0.08)',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}
}
},
// Enhanced title with gradient styling
title: {
...defaultBlocks.title,
props: {
...defaultBlocks.title.props,
style: {
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
fontSize: window.innerWidth <= 800 ? '28px' : '36px',
fontWeight: 'bold',
margin: '0'
}
}
},
// Enhanced action section with search and button
action: {
...defaultBlocks.action,
props: {
...defaultBlocks.action.props,
children: (
<div style={{
display: 'flex',
alignItems: 'center',
gap: '16px',
flexWrap: 'wrap'
}}>
{/* Enhanced search input */}
<input
type="text"
placeholder={labels.searchFieldPlaceholder}
onChange={(e) => handleSearchChange(e.target.value)}
style={{
padding: '12px 16px',
borderRadius: '12px',
border: '1px solid rgba(102, 126, 234, 0.2)',
background: 'rgba(255, 255, 255, 0.9)',
fontSize: '14px',
minWidth: '200px',
transition: 'all 0.3s ease',
boxShadow: '0 2px 8px rgba(0,0,0,0.05)'
}}
onFocus={(e) => {
e.target.style.borderColor = '#667eea';
e.target.style.boxShadow = '0 4px 16px rgba(102, 126, 234, 0.2)';
}}
onBlur={(e) => {
e.target.style.borderColor = 'rgba(102, 126, 234, 0.2)';
e.target.style.boxShadow = '0 2px 8px rgba(0,0,0,0.05)';
}}
/>
{/* Enhanced action button */}
<button
onClick={handleActionClick}
style={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
border: 'none',
borderRadius: '12px',
padding: '12px 24px',
color: 'white',
fontSize: '14px',
fontWeight: 'bold',
cursor: 'pointer',
transition: 'all 0.3s ease',
boxShadow: '0 4px 16px rgba(102, 126, 234, 0.3)',
display: 'flex',
alignItems: 'center',
gap: '8px'
}}
onMouseOver={(e) => {
e.target.style.transform = 'translateY(-2px)';
e.target.style.boxShadow = '0 8px 24px rgba(102, 126, 234, 0.4)';
}}
onMouseOut={(e) => {
e.target.style.transform = 'translateY(0)';
e.target.style.boxShadow = '0 4px 16px rgba(102, 126, 234, 0.3)';
}}>
<span style={{ fontSize: '16px' }}>➕</span>
{labels.actions.headerAction}
</button>
</div>
)
}
},
// Enhanced content container
content: {
...defaultBlocks.content,
props: {
...defaultBlocks.content.props,
style: {
background: 'rgba(255, 255, 255, 0.9)',
borderRadius: '20px',
padding: '24px',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255,255,255,0.4)',
boxShadow: '0 8px 32px rgba(0,0,0,0.08)'
}
}
},
// Enhanced tabs with modern styling
tabs: {
...defaultBlocks.tabs,
props: {
...defaultBlocks.tabs.props,
style: {
marginBottom: '24px'
}
}
},
// Enhanced table with professional styling
table: {
...defaultBlocks.table,
props: {
...defaultBlocks.table.props,
style: {
borderRadius: '16px',
overflow: 'hidden',
boxShadow: '0 4px 20px rgba(0,0,0,0.08)'
}
}
}
},
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 |
pagination | {currentPage: number; onPageChange: (page: number) => void; totalPages: number} | undefined | Pagination configuration object |
data | ListOrganizationsRowData[] | Required | Array of organization data objects |
rowHref | (row: ListOrganizationsRowData) => string | Required | Function to generate row link URLs |
tabs | Tab[] | Required | Array of tab configuration objects |
currentTab | string | undefined | Currently active tab identifier |
onTabChange | (tab: string) => void | undefined | Callback function when tab is changed |
className | string | undefined | Additional CSS class name for styling the container |
children | BlocksOverride | undefined | Custom block components to override default rendering |
Note: This component inherits all standard HTML div element props except children.
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.Title
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | From context | Custom content to override default title rendering |
className | string | undefined | Additional CSS class name for styling |
size | enum | "3XL" | Typography size for the title |
color | enum | "low-emphasis" | Color theme for the title |
weight | enum | "bold" | Weight of the title |
ListOrganizations.Action
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | From context | Custom content to override default action rendering |
className | string | undefined | Additional CSS class name for styling |
direction | enum | "row" | Flex direction for action components |
alignItems | enum | From Spacing defaults | Alignment of items in the container |
gapSize | enum | From Spacing defaults | Gap between items in the container |
ListOrganizations.Header
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | Default header layout | Custom content to override default header rendering |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits all props from the HTML div element.
ListOrganizations.Loader
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | Circular progress indicator | Custom loading indicator content |
className | string | undefined | Additional CSS class name for styling |
direction | enum | "row" | Flex direction for action components |
alignItems | enum | From Spacing defaults | Alignment of items in the container |
gapSize | enum | From Spacing defaults | Gap between items in the container |
ListOrganizations.Content
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | Default content layout | Custom content to override default content rendering |
className | string | undefined | Additional CSS class name for styling |
isLoading | boolean | From context | Loading state from context |
Note: This component inherits all props from the HTML div element.
ListProductsTable.Tabs
| Prop | Type | Default | Description |
|---|---|---|---|
tabs | Tab[] | From context | Array of tab configuration objects |
currentTab | string | From context | Currently active tab identifier |
onTabChange | (tab: string) => void | From context | Tab change callback function |
tabWidth | string | "stretch" | Width behavior for tabs |
className | string | undefined | Additional CSS class name for styling |
ListOrganizations.Table
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | undefined | Additional CSS class name for styling |
labels | OrganizationLabels | From context | Labels for table headers and actions |
isLoading | boolean | From context | Loading state |
data | ListOrganizationsRowData[] | From context | Array of table data |
rowHref | (row: ListOrganizationsRowData) => string | From context | Row link function |
onNavigate | (to: string) => void | From context | Navigation callback function |
onRowActionClick | (rowData: ListOrganizationsRowData) => void | From context | Row action callback function |
pagination | PaginationProps | From context | Pagination configuration |
emptyState | {icon: enum, message: string} | From context | Configuration for empty state |
🔧 TypeScript Support
Full TypeScript support with comprehensive type definitions:
import {ListOrganizations} from '@nodeblocks/frontend-list-organization-block';
interface Tab {
isDisabled?: boolean;
key?: string;
label: string;
subtitle?: string;
}
interface ListOrganizationsRowData {
id: string;
createdAt: string;
name: string;
joinDate: string;
auditStatus: string;
}
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;
};
}
interface CustomOrganizationTableProps {
organizations: ListOrganizationsRowData[];
onSearch: (query: string) => void;
onAddOrganization: () => void;
onReviewOrganization: (org: ListOrganizationsRowData) => void;
tableTabs: Array<Tab>;
currentActiveTab: string;
onTabSwitch: (tab: string) => void;
isTableLoading: boolean;
}
const OrganizationTableComponent = ({
organizations,
onSearch,
onAddOrganization,
onReviewOrganization,
tableTabs,
currentActiveTab,
onTabSwitch,
isTableLoading,
}: CustomOrganizationTableProps) => {
const tableLabels: OrganizationLabels = {
emptyStateMessage: 'No organizations available',
searchFieldPlaceholder: 'Search by organization name...',
actions: {
headerAction: 'New Organization',
rowAction: 'Review Application',
},
headerRow: {
createdAt: 'Date Created',
name: 'Organization Name',
joinDate: 'Join Date',
auditStatus: 'Audit Status',
},
cellData: {
statusApproved: <span style={{color: 'green'}}>Approved</span>,
statusRejected: <span style={{color: 'red'}}>Rejected</span>,
statusWaitingForReview: <span style={{color: 'orange'}}>Under Review</span>,
},
};
return (
<ListOrganizations
listOrganizationsTitle="Organization Management Dashboard"
labels={tableLabels}
isLoading={isTableLoading}
onSearchFieldChange={onSearch}
onActionClick={onAddOrganization}
onNavigate={path => console.log('Navigate:', path)}
onRowActionClick={onReviewOrganization}
data={organizations}
rowHref={row => `/organizations/${row.id}`}
tabs={tableTabs}
currentTab={currentActiveTab}
onTabChange={onTabSwitch}>
<ListOrganizations.Header />
<ListOrganizations.Content />
</ListOrganizations>
);
};
Built with ❤️ using React, TypeScript, and modern web standards.