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
📖 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.