@jmruthers/pace-core 0.5.120 → 0.5.123
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AuthService-D4646R4b.d.ts → AuthService-DYuQPJj6.d.ts} +0 -9
- package/dist/{DataTable-DGZDJUYM.js → DataTable-WTS4IRF2.js} +7 -8
- package/dist/{PublicLoadingSpinner-DgDWTFqn.d.ts → PublicLoadingSpinner-CaoRbHvJ.d.ts} +30 -4
- package/dist/{UnifiedAuthProvider-UACKFATV.js → UnifiedAuthProvider-6C47WIML.js} +3 -4
- package/dist/{chunk-D6BOFXYR.js → chunk-35ZDPMBM.js} +3 -3
- package/dist/{chunk-CGURJ27Z.js → chunk-4MXVZVNS.js} +2 -2
- package/dist/{chunk-ZYJ6O5CA.js → chunk-C43QIDN3.js} +2 -2
- package/dist/{chunk-VKOCWWVY.js → chunk-CX5M4ZAG.js} +1 -6
- package/dist/{chunk-VKOCWWVY.js.map → chunk-CX5M4ZAG.js.map} +1 -1
- package/dist/{chunk-HFBOFZ3Z.js → chunk-DHMFMXFV.js} +258 -243
- package/dist/chunk-DHMFMXFV.js.map +1 -0
- package/dist/{chunk-RIEJGKD3.js → chunk-ESJTIADP.js} +15 -6
- package/dist/{chunk-RIEJGKD3.js.map → chunk-ESJTIADP.js.map} +1 -1
- package/dist/{chunk-SMJZMKYN.js → chunk-GEVIB2UB.js} +43 -10
- package/dist/chunk-GEVIB2UB.js.map +1 -0
- package/dist/{chunk-TDNI6ZWL.js → chunk-IJOZZOGT.js} +7 -7
- package/dist/chunk-IJOZZOGT.js.map +1 -0
- package/dist/{chunk-GZRXOUBE.js → chunk-M6DDYFUD.js} +2 -2
- package/dist/chunk-M6DDYFUD.js.map +1 -0
- package/dist/{chunk-B4GZ2BXO.js → chunk-NZGLXZGP.js} +3 -3
- package/dist/{chunk-NZ32EONV.js → chunk-QWNJCQXZ.js} +2 -2
- package/dist/{chunk-FKFHZUGF.js → chunk-XN6GWKMV.js} +43 -56
- package/dist/chunk-XN6GWKMV.js.map +1 -0
- package/dist/{chunk-BHWIUEYH.js → chunk-ZBLK676C.js} +1 -61
- package/dist/chunk-ZBLK676C.js.map +1 -0
- package/dist/{chunk-QPI2CCBA.js → chunk-ZPJMYGEP.js} +149 -96
- package/dist/chunk-ZPJMYGEP.js.map +1 -0
- package/dist/components.d.ts +1 -1
- package/dist/components.js +11 -11
- package/dist/{formatting-B1jSqgl-.d.ts → formatting-DFcCxUEk.d.ts} +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +9 -8
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.js +19 -17
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +2 -3
- package/dist/rbac/index.js +7 -8
- package/dist/styles/index.d.ts +1 -1
- package/dist/styles/index.js +5 -3
- package/dist/theming/runtime.d.ts +73 -1
- package/dist/theming/runtime.js +5 -5
- package/dist/{usePublicRouteParams-BdF8bZgs.d.ts → usePublicRouteParams-Dyt1tzI9.d.ts} +60 -8
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +5 -5
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +6 -6
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +6 -6
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +7 -7
- package/docs/api/interfaces/PublicErrorBoundaryState.md +5 -5
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +7 -7
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +51 -12
- package/docs/api/interfaces/PublicPageLayoutProps.md +72 -12
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +140 -30
- package/docs/best-practices/README.md +1 -1
- package/docs/implementation-guides/datatable-filtering.md +313 -0
- package/docs/implementation-guides/datatable-rbac-usage.md +317 -0
- package/docs/implementation-guides/hierarchical-datatable.md +850 -0
- package/docs/implementation-guides/large-datasets.md +281 -0
- package/docs/implementation-guides/performance.md +403 -0
- package/docs/implementation-guides/public-pages.md +4 -4
- package/docs/migration/quick-migration-guide.md +320 -0
- package/docs/rbac/quick-start.md +16 -16
- package/docs/troubleshooting/README.md +4 -4
- package/docs/troubleshooting/cake-page-permission-guard-issue-summary.md +1 -1
- package/docs/troubleshooting/debugging.md +1117 -0
- package/docs/troubleshooting/migration.md +918 -0
- package/examples/public-pages/CorrectPublicPageImplementation.tsx +30 -30
- package/examples/public-pages/PublicEventPage.tsx +41 -41
- package/examples/public-pages/PublicPageApp.tsx +33 -33
- package/examples/public-pages/PublicPageUsageExample.tsx +30 -30
- package/package.json +4 -4
- package/src/__tests__/hooks/usePermissions.test.ts +265 -0
- package/src/components/DataTable/DataTable.test.tsx +9 -38
- package/src/components/DataTable/DataTable.tsx +0 -7
- package/src/components/DataTable/components/DataTableCore.tsx +66 -136
- package/src/components/DataTable/components/DataTableModals.tsx +25 -22
- package/src/components/DataTable/components/EditableRow.tsx +118 -42
- package/src/components/DataTable/components/UnifiedTableBody.tsx +129 -76
- package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +33 -14
- package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +17 -5
- package/src/components/DataTable/utils/exportUtils.ts +3 -2
- package/src/components/DataTable/utils/flexibleImport.ts +27 -6
- package/src/components/Dialog/Dialog.tsx +1 -1
- package/src/components/Dialog/README.md +24 -24
- package/src/components/Dialog/examples/BasicHtmlTest.tsx +2 -2
- package/src/components/Dialog/examples/DebugHtmlExample.tsx +6 -6
- package/src/components/Dialog/examples/HtmlDialogExample.tsx +2 -2
- package/src/components/Dialog/examples/SimpleHtmlTest.tsx +3 -3
- package/src/components/Dialog/examples/__tests__/SimpleHtmlTest.test.tsx +4 -4
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +12 -1
- package/src/components/PublicLayout/EventLogo.tsx +175 -0
- package/src/components/PublicLayout/PublicErrorBoundary.tsx +22 -18
- package/src/components/PublicLayout/PublicLoadingSpinner.tsx +22 -14
- package/src/components/PublicLayout/PublicPageHeader.tsx +133 -40
- package/src/components/PublicLayout/PublicPageLayout.tsx +75 -72
- package/src/components/PublicLayout/__tests__/PublicErrorBoundary.test.tsx +1 -1
- package/src/components/PublicLayout/__tests__/PublicLoadingSpinner.test.tsx +8 -8
- package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +23 -16
- package/src/components/PublicLayout/__tests__/PublicPageLayout.test.tsx +86 -14
- package/src/examples/CorrectPublicPageImplementation.tsx +30 -30
- package/src/examples/PublicEventPage.tsx +41 -41
- package/src/examples/PublicPageApp.tsx +33 -33
- package/src/examples/PublicPageUsageExample.tsx +30 -30
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +583 -0
- package/src/hooks/__tests__/usePublicRouteParams.unit.test.ts +10 -3
- package/src/hooks/index.ts +1 -1
- package/src/hooks/public/usePublicEventLogo.ts +285 -0
- package/src/hooks/public/usePublicRouteParams.ts +21 -4
- package/src/hooks/useEventTheme.test.ts +119 -43
- package/src/hooks/useEventTheme.ts +84 -55
- package/src/index.ts +3 -1
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +630 -0
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +667 -0
- package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +647 -0
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +496 -0
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +496 -0
- package/src/rbac/secureClient.ts +4 -2
- package/src/services/EventService.ts +0 -66
- package/src/services/__tests__/EventService.eventColours.test.ts +44 -40
- package/src/styles/index.ts +1 -1
- package/src/theming/__tests__/parseEventColours.test.ts +209 -0
- package/src/theming/parseEventColours.ts +123 -0
- package/src/theming/runtime.ts +3 -0
- package/src/types/__tests__/file-reference.test.ts +447 -0
- package/src/types/database.generated.ts +1515 -424
- package/src/utils/formatDate.test.ts +11 -11
- package/src/utils/formatting.ts +3 -2
- package/dist/chunk-BHWIUEYH.js.map +0 -1
- package/dist/chunk-FKFHZUGF.js.map +0 -1
- package/dist/chunk-GZRXOUBE.js.map +0 -1
- package/dist/chunk-HFBOFZ3Z.js.map +0 -1
- package/dist/chunk-QPI2CCBA.js.map +0 -1
- package/dist/chunk-SMJZMKYN.js.map +0 -1
- package/dist/chunk-TDNI6ZWL.js.map +0 -1
- package/src/styles/semantic.css +0 -24
- /package/dist/{DataTable-DGZDJUYM.js.map → DataTable-WTS4IRF2.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-UACKFATV.js.map → UnifiedAuthProvider-6C47WIML.js.map} +0 -0
- /package/dist/{chunk-D6BOFXYR.js.map → chunk-35ZDPMBM.js.map} +0 -0
- /package/dist/{chunk-CGURJ27Z.js.map → chunk-4MXVZVNS.js.map} +0 -0
- /package/dist/{chunk-ZYJ6O5CA.js.map → chunk-C43QIDN3.js.map} +0 -0
- /package/dist/{chunk-B4GZ2BXO.js.map → chunk-NZGLXZGP.js.map} +0 -0
- /package/dist/{chunk-NZ32EONV.js.map → chunk-QWNJCQXZ.js.map} +0 -0
|
@@ -0,0 +1,850 @@
|
|
|
1
|
+
# Hierarchical DataTable Implementation Guide
|
|
2
|
+
|
|
3
|
+
This comprehensive guide covers implementing hierarchical parent/child rows in PACE Core's DataTable component.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The hierarchical DataTable feature allows you to display parent/child relationships in your data with:
|
|
8
|
+
- **Expand/Collapse All**: Single button to expand/collapse all parent rows
|
|
9
|
+
- **Individual Controls**: Click arrows next to each parent row
|
|
10
|
+
- **Different Action Icons**: Context-aware actions for parent vs child rows
|
|
11
|
+
- **Smart Sorting**: Sorting by child fields sorts children within their parents
|
|
12
|
+
- **Column Rendering**: Different content display for parent vs child rows
|
|
13
|
+
- **Visual Distinction**: Clear visual hierarchy with indentation and styling
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
### 1. Enable Hierarchical Features
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { DataTable, type HierarchicalDataRow } from '@jmruthers/pace-core';
|
|
21
|
+
|
|
22
|
+
<DataTable
|
|
23
|
+
data={hierarchicalData}
|
|
24
|
+
columns={columns}
|
|
25
|
+
features={{
|
|
26
|
+
hierarchical: true, // Enable hierarchical functionality
|
|
27
|
+
}}
|
|
28
|
+
hierarchical={{
|
|
29
|
+
enabled: true,
|
|
30
|
+
defaultExpanded: false, // Start with all collapsed
|
|
31
|
+
}}
|
|
32
|
+
/>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. Structure Your Data
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
interface Dish extends HierarchicalDataRow {
|
|
39
|
+
id: string;
|
|
40
|
+
isParent: boolean;
|
|
41
|
+
parentId?: string;
|
|
42
|
+
name: string;
|
|
43
|
+
type: string;
|
|
44
|
+
cost: number;
|
|
45
|
+
// ... other properties
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const hierarchicalData: Dish[] = [
|
|
49
|
+
// Parent rows (dishes)
|
|
50
|
+
{
|
|
51
|
+
id: 'dish-1',
|
|
52
|
+
isParent: true,
|
|
53
|
+
parentId: null,
|
|
54
|
+
name: 'Caesar Salad',
|
|
55
|
+
type: 'Salad',
|
|
56
|
+
cost: 12.99
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 'dish-2',
|
|
60
|
+
isParent: true,
|
|
61
|
+
parentId: null,
|
|
62
|
+
name: 'Beef Stir Fry',
|
|
63
|
+
type: 'Main Course',
|
|
64
|
+
cost: 18.99
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
// Child rows (ingredients) for Caesar Salad
|
|
68
|
+
{
|
|
69
|
+
id: 'ingredient-1',
|
|
70
|
+
isParent: false,
|
|
71
|
+
parentId: 'dish-1',
|
|
72
|
+
name: 'Romaine Lettuce',
|
|
73
|
+
type: 'Vegetable',
|
|
74
|
+
cost: 2.50
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'ingredient-2',
|
|
78
|
+
isParent: false,
|
|
79
|
+
parentId: 'dish-1',
|
|
80
|
+
name: 'Parmesan Cheese',
|
|
81
|
+
type: 'Dairy',
|
|
82
|
+
cost: 4.20
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
// Child rows (ingredients) for Beef Stir Fry
|
|
86
|
+
{
|
|
87
|
+
id: 'ingredient-3',
|
|
88
|
+
isParent: false,
|
|
89
|
+
parentId: 'dish-2',
|
|
90
|
+
name: 'Beef Strips',
|
|
91
|
+
type: 'Protein',
|
|
92
|
+
cost: 8.50
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: 'ingredient-4',
|
|
96
|
+
isParent: false,
|
|
97
|
+
parentId: 'dish-2',
|
|
98
|
+
name: 'Mixed Vegetables',
|
|
99
|
+
type: 'Vegetable',
|
|
100
|
+
cost: 3.20
|
|
101
|
+
}
|
|
102
|
+
];
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 3. Configure Columns for Different Row Types
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
const columns: DataTableColumn<Dish>[] = [
|
|
109
|
+
{
|
|
110
|
+
id: 'name',
|
|
111
|
+
accessorKey: 'name',
|
|
112
|
+
header: 'Name',
|
|
113
|
+
// Different rendering for parent vs child rows
|
|
114
|
+
renderForParent: (row) => (
|
|
115
|
+
<div className="flex items-center gap-2">
|
|
116
|
+
<span className="font-semibold text-main-800">{row.name}</span>
|
|
117
|
+
<span className="text-xs bg-main-100 text-main-700 px-2 py-1 rounded">
|
|
118
|
+
{row.type}
|
|
119
|
+
</span>
|
|
120
|
+
</div>
|
|
121
|
+
),
|
|
122
|
+
renderForChild: (row) => (
|
|
123
|
+
<div className="flex items-center gap-2 ml-4">
|
|
124
|
+
<span className="text-sec-700">{row.name}</span>
|
|
125
|
+
<span className="text-xs bg-sec-100 text-sec-600 px-2 py-1 rounded">
|
|
126
|
+
{row.type}
|
|
127
|
+
</span>
|
|
128
|
+
</div>
|
|
129
|
+
),
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
id: 'cost',
|
|
133
|
+
accessorKey: 'cost',
|
|
134
|
+
header: 'Cost',
|
|
135
|
+
renderForParent: (row) => (
|
|
136
|
+
<div className="text-right font-semibold text-main-800">
|
|
137
|
+
${row.cost?.toFixed(2)}
|
|
138
|
+
</div>
|
|
139
|
+
),
|
|
140
|
+
renderForChild: (row) => (
|
|
141
|
+
<div className="text-right text-sec-600 ml-4">
|
|
142
|
+
${row.cost?.toFixed(2)}
|
|
143
|
+
</div>
|
|
144
|
+
),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: 'ingredient-specific',
|
|
148
|
+
accessorKey: 'quantity',
|
|
149
|
+
header: 'Quantity',
|
|
150
|
+
// Only show for child rows
|
|
151
|
+
renderForParent: () => null,
|
|
152
|
+
renderForChild: (row) => (
|
|
153
|
+
<div className="text-sm ml-4">
|
|
154
|
+
<span className="font-medium">{row.quantity}</span>
|
|
155
|
+
{row.unit && <span className="text-sec-500 ml-1">{row.unit}</span>}
|
|
156
|
+
</div>
|
|
157
|
+
),
|
|
158
|
+
hideForParent: true, // Hide this column for parent rows
|
|
159
|
+
}
|
|
160
|
+
];
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Detailed Configuration
|
|
164
|
+
|
|
165
|
+
### Data Structure Requirements
|
|
166
|
+
|
|
167
|
+
Your hierarchical data must implement the `HierarchicalDataRow` interface:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
interface HierarchicalDataRow extends DataRecord {
|
|
171
|
+
/** Whether this row is a parent row */
|
|
172
|
+
isParent: boolean;
|
|
173
|
+
/** For child rows, references the parent row ID */
|
|
174
|
+
parentId?: string;
|
|
175
|
+
/** Unique identifier for this row */
|
|
176
|
+
id: string;
|
|
177
|
+
// ... your actual data properties
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Critical Requirements:**
|
|
182
|
+
- **Parent rows**: Must have `isParent: true` and `parentId: null` or `undefined`
|
|
183
|
+
- **Child rows**: Must have `isParent: false` and `parentId: string` (referencing a parent's `id`)
|
|
184
|
+
- **Unique IDs**: Every row must have a unique `id` value
|
|
185
|
+
- **Valid References**: All `parentId` values must reference an existing parent row's `id`
|
|
186
|
+
|
|
187
|
+
### Hierarchical Configuration Options
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
hierarchical={{
|
|
191
|
+
enabled: true, // Enable/disable hierarchical functionality
|
|
192
|
+
defaultExpanded: false, // Initial expansion state
|
|
193
|
+
onExpandedChange: (expandedIds) => {
|
|
194
|
+
console.log('Expanded rows:', expandedIds);
|
|
195
|
+
},
|
|
196
|
+
expandButton: CustomExpandButton, // Optional custom expand button
|
|
197
|
+
indentSize: 24, // Indentation for child rows in pixels
|
|
198
|
+
parentRowClassName: 'bg-main-50 font-medium', // CSS classes for parent rows
|
|
199
|
+
childRowClassName: 'bg-sec-25', // CSS classes for child rows
|
|
200
|
+
}}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Configuration Details:**
|
|
204
|
+
|
|
205
|
+
| Option | Type | Default | Description |
|
|
206
|
+
|--------|------|---------|-------------|
|
|
207
|
+
| `enabled` | `boolean` | `false` | Enable/disable hierarchical functionality |
|
|
208
|
+
| `defaultExpanded` | `boolean \| string[]` | `false` | Initial expansion state |
|
|
209
|
+
| `onExpandedChange` | `(expandedIds: string[]) => void` | `undefined` | Callback when expansion changes |
|
|
210
|
+
| `expandButton` | `React.ComponentType` | `ExpandButton` | Custom expand/collapse button |
|
|
211
|
+
| `indentSize` | `number` | `24` | Indentation for child rows in pixels |
|
|
212
|
+
| `parentRowClassName` | `string` | `''` | CSS classes for parent rows |
|
|
213
|
+
| `childRowClassName` | `string` | `''` | CSS classes for child rows |
|
|
214
|
+
|
|
215
|
+
### Column Configuration for Hierarchical Data
|
|
216
|
+
|
|
217
|
+
Configure how columns render for different row types using these properties:
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
const column: DataTableColumn<YourType> = {
|
|
221
|
+
// ... standard column properties
|
|
222
|
+
|
|
223
|
+
// Hierarchical-specific properties
|
|
224
|
+
renderForParent?: (row: YourType) => React.ReactNode; // Custom renderer for parent rows
|
|
225
|
+
renderForChild?: (row: YourType) => React.ReactNode; // Custom renderer for child rows
|
|
226
|
+
hideForParent?: boolean; // Hide this column for parent rows
|
|
227
|
+
hideForChild?: boolean; // Hide this column for child rows
|
|
228
|
+
};
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Column Rendering Strategies:**
|
|
232
|
+
|
|
233
|
+
1. **Different Content**: Use `renderForParent` and `renderForChild` for different content
|
|
234
|
+
2. **Hide Columns**: Use `hideForParent` or `hideForChild` to hide columns for specific row types
|
|
235
|
+
3. **Conditional Rendering**: Use both approaches together for complex layouts
|
|
236
|
+
|
|
237
|
+
**Example - Mixed Strategy:**
|
|
238
|
+
```tsx
|
|
239
|
+
{
|
|
240
|
+
id: 'actions',
|
|
241
|
+
header: 'Actions',
|
|
242
|
+
renderForParent: (row) => (
|
|
243
|
+
<div className="flex gap-2">
|
|
244
|
+
<Button size="sm">Edit Recipe</Button>
|
|
245
|
+
<Button size="sm" variant="outline">Add Ingredient</Button>
|
|
246
|
+
</div>
|
|
247
|
+
),
|
|
248
|
+
renderForChild: (row) => (
|
|
249
|
+
<div className="flex gap-2">
|
|
250
|
+
<Button size="sm" variant="ghost">Edit</Button>
|
|
251
|
+
<Button size="sm" variant="destructive">Remove</Button>
|
|
252
|
+
</div>
|
|
253
|
+
),
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Expand/Collapse All Controls
|
|
258
|
+
|
|
259
|
+
The DataTable automatically adds an expand/collapse all button in the header when hierarchical mode is enabled.
|
|
260
|
+
|
|
261
|
+
### Features
|
|
262
|
+
|
|
263
|
+
- **Expand All Button (▶️)**: Expands all parent rows to show their children
|
|
264
|
+
- **Collapse All Button (🔽)**: Collapses all parent rows to hide their children
|
|
265
|
+
- **Smart State Detection**: Button automatically updates based on current expansion state
|
|
266
|
+
- **Accessibility**: Proper ARIA labels and keyboard navigation support
|
|
267
|
+
|
|
268
|
+
### Button Behavior
|
|
269
|
+
|
|
270
|
+
- Shows ▶️ when some or all parent rows are collapsed
|
|
271
|
+
- Shows 🔽 when all parent rows are expanded
|
|
272
|
+
- Only appears when there are parent rows with children
|
|
273
|
+
- Positioned in the first column of the table header
|
|
274
|
+
|
|
275
|
+
### Custom Expand Button
|
|
276
|
+
|
|
277
|
+
You can provide a custom expand button component:
|
|
278
|
+
|
|
279
|
+
```tsx
|
|
280
|
+
import { ChevronRight, ChevronDown } from 'lucide-react';
|
|
281
|
+
|
|
282
|
+
const CustomExpandButton = ({ isExpanded, onClick, ...props }) => (
|
|
283
|
+
<button
|
|
284
|
+
onClick={onClick}
|
|
285
|
+
className="p-1 hover:bg-sec-100 rounded"
|
|
286
|
+
{...props}
|
|
287
|
+
>
|
|
288
|
+
{isExpanded ? (
|
|
289
|
+
<ChevronDown className="h-4 w-4" />
|
|
290
|
+
) : (
|
|
291
|
+
<ChevronRight className="h-4 w-4" />
|
|
292
|
+
)}
|
|
293
|
+
</button>
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
// Use in DataTable
|
|
297
|
+
<DataTable
|
|
298
|
+
hierarchical={{
|
|
299
|
+
enabled: true,
|
|
300
|
+
expandButton: CustomExpandButton,
|
|
301
|
+
}}
|
|
302
|
+
/>
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Hierarchical Actions
|
|
306
|
+
|
|
307
|
+
Configure different actions for parent vs child rows using the `DataTableAction` interface:
|
|
308
|
+
|
|
309
|
+
```tsx
|
|
310
|
+
const hierarchicalActions: DataTableAction<Dish>[] = [
|
|
311
|
+
{
|
|
312
|
+
label: 'View Details',
|
|
313
|
+
icon: Eye,
|
|
314
|
+
onClick: (row) => console.log('Viewing:', row.name),
|
|
315
|
+
variant: 'default',
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
label: 'Copy Recipe',
|
|
319
|
+
parentLabel: 'Copy Recipe',
|
|
320
|
+
childLabel: 'Copy Ingredient',
|
|
321
|
+
parentIcon: Copy,
|
|
322
|
+
childIcon: Copy,
|
|
323
|
+
onClick: (row) => console.log('Copying:', row.name),
|
|
324
|
+
variant: 'secondary',
|
|
325
|
+
showForParent: true,
|
|
326
|
+
showForChild: true,
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
label: 'Add Ingredient',
|
|
330
|
+
icon: Plus,
|
|
331
|
+
onClick: (row) => console.log('Adding ingredient to:', row.name),
|
|
332
|
+
variant: 'outline',
|
|
333
|
+
showForParent: true, // Only show for parent rows
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
label: 'Export',
|
|
337
|
+
icon: Download,
|
|
338
|
+
onClick: (row) => console.log('Exporting:', row.name),
|
|
339
|
+
variant: 'ghost',
|
|
340
|
+
showForChild: true, // Only show for child rows
|
|
341
|
+
}
|
|
342
|
+
];
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**Action Configuration Properties:**
|
|
346
|
+
|
|
347
|
+
| Property | Type | Description |
|
|
348
|
+
|----------|------|-------------|
|
|
349
|
+
| `showForParent` | `boolean` | Show this action only for parent rows |
|
|
350
|
+
| `showForChild` | `boolean` | Show this action only for child rows |
|
|
351
|
+
| `parentIcon` | `React.ComponentType` | Icon for parent rows (overrides `icon`) |
|
|
352
|
+
| `childIcon` | `React.ComponentType` | Icon for child rows (overrides `icon`) |
|
|
353
|
+
| `parentLabel` | `string` | Label for parent rows (overrides `label`) |
|
|
354
|
+
| `childLabel` | `string` | Label for child rows (overrides `label`) |
|
|
355
|
+
|
|
356
|
+
## Smart Sorting
|
|
357
|
+
|
|
358
|
+
When hierarchical mode is enabled, sorting behavior is optimized to preserve the parent-child relationship structure.
|
|
359
|
+
|
|
360
|
+
### How It Works
|
|
361
|
+
|
|
362
|
+
**Parent Row Sorting:**
|
|
363
|
+
- Parent rows maintain their original order and are not affected by sorting
|
|
364
|
+
- Only child rows within each parent group are sorted
|
|
365
|
+
- This prevents parent rows from being moved to unexpected positions
|
|
366
|
+
|
|
367
|
+
**Child Row Sorting:**
|
|
368
|
+
- Child rows are sorted within their parent group only
|
|
369
|
+
- Sorting by a child-specific column will sort children within each parent
|
|
370
|
+
- The parent row remains at the top of its group
|
|
371
|
+
|
|
372
|
+
### Example Sorting Behavior
|
|
373
|
+
|
|
374
|
+
```tsx
|
|
375
|
+
// When sorting by "Diet" column:
|
|
376
|
+
// ✅ Correct behavior:
|
|
377
|
+
// Parent: Caesar Salad
|
|
378
|
+
// ├─ Child: Dairy Free (Sour cream)
|
|
379
|
+
// ├─ Child: Egg Free (Mayonnaise)
|
|
380
|
+
// └─ Child: GF & Vegan (Lettuce)
|
|
381
|
+
// Parent: Beef Stir Fry
|
|
382
|
+
// ├─ Child: GF & Vegan (Vegetables)
|
|
383
|
+
// └─ Child: Halal (Beef)
|
|
384
|
+
|
|
385
|
+
// ❌ Incorrect behavior (what we prevent):
|
|
386
|
+
// Child: Dairy Free (Sour cream)
|
|
387
|
+
// Child: Egg Free (Mayonnaise)
|
|
388
|
+
// Child: GF & Vegan (Lettuce)
|
|
389
|
+
// Child: GF & Vegan (Vegetables)
|
|
390
|
+
// Child: Halal (Beef)
|
|
391
|
+
// Parent: Caesar Salad (moved to end)
|
|
392
|
+
// Parent: Beef Stir Fry (moved to end)
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Sorting Configuration
|
|
396
|
+
|
|
397
|
+
- All existing sorting features work with hierarchical data
|
|
398
|
+
- Multi-column sorting is supported
|
|
399
|
+
- Server-side sorting is compatible
|
|
400
|
+
- Column-specific sorting behavior is preserved
|
|
401
|
+
|
|
402
|
+
## Complete Example
|
|
403
|
+
|
|
404
|
+
Here's a complete example showing all hierarchical features:
|
|
405
|
+
|
|
406
|
+
```tsx
|
|
407
|
+
import React, { useMemo } from 'react';
|
|
408
|
+
import { DataTable, type HierarchicalDataRow, type DataTableColumn } from '@jmruthers/pace-core';
|
|
409
|
+
import { Eye, Edit, Plus, Download, Copy, Star, Trash2 } from 'lucide-react';
|
|
410
|
+
|
|
411
|
+
interface Dish extends HierarchicalDataRow {
|
|
412
|
+
id: string;
|
|
413
|
+
isParent: boolean;
|
|
414
|
+
parentId?: string;
|
|
415
|
+
name: string;
|
|
416
|
+
type: string;
|
|
417
|
+
description: string;
|
|
418
|
+
cost: number;
|
|
419
|
+
prepTime?: number;
|
|
420
|
+
difficulty?: string;
|
|
421
|
+
quantity?: string;
|
|
422
|
+
unit?: string;
|
|
423
|
+
supplier?: string;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function HierarchicalDishTable() {
|
|
427
|
+
const hierarchicalData: Dish[] = useMemo(() => [
|
|
428
|
+
// Parent rows (dishes)
|
|
429
|
+
{
|
|
430
|
+
id: 'dish-1',
|
|
431
|
+
isParent: true,
|
|
432
|
+
parentId: null,
|
|
433
|
+
name: 'Caesar Salad',
|
|
434
|
+
type: 'Salad',
|
|
435
|
+
description: 'Classic Caesar salad with romaine lettuce',
|
|
436
|
+
cost: 12.99,
|
|
437
|
+
prepTime: 15,
|
|
438
|
+
difficulty: 'Easy'
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
id: 'dish-2',
|
|
442
|
+
isParent: true,
|
|
443
|
+
parentId: null,
|
|
444
|
+
name: 'Beef Stir Fry',
|
|
445
|
+
type: 'Main Course',
|
|
446
|
+
description: 'Tender beef with mixed vegetables',
|
|
447
|
+
cost: 18.99,
|
|
448
|
+
prepTime: 25,
|
|
449
|
+
difficulty: 'Medium'
|
|
450
|
+
},
|
|
451
|
+
|
|
452
|
+
// Child rows (ingredients) for Caesar Salad
|
|
453
|
+
{
|
|
454
|
+
id: 'ingredient-1',
|
|
455
|
+
isParent: false,
|
|
456
|
+
parentId: 'dish-1',
|
|
457
|
+
name: 'Romaine Lettuce',
|
|
458
|
+
type: 'Vegetable',
|
|
459
|
+
description: 'Fresh romaine lettuce leaves',
|
|
460
|
+
cost: 2.50,
|
|
461
|
+
quantity: '2 heads',
|
|
462
|
+
unit: 'pieces',
|
|
463
|
+
supplier: 'Fresh Farms'
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
id: 'ingredient-2',
|
|
467
|
+
isParent: false,
|
|
468
|
+
parentId: 'dish-1',
|
|
469
|
+
name: 'Parmesan Cheese',
|
|
470
|
+
type: 'Dairy',
|
|
471
|
+
description: 'Grated parmesan cheese',
|
|
472
|
+
cost: 4.20,
|
|
473
|
+
quantity: '100g',
|
|
474
|
+
unit: 'grams',
|
|
475
|
+
supplier: 'Italian Imports'
|
|
476
|
+
},
|
|
477
|
+
|
|
478
|
+
// Child rows (ingredients) for Beef Stir Fry
|
|
479
|
+
{
|
|
480
|
+
id: 'ingredient-3',
|
|
481
|
+
isParent: false,
|
|
482
|
+
parentId: 'dish-2',
|
|
483
|
+
name: 'Beef Strips',
|
|
484
|
+
type: 'Protein',
|
|
485
|
+
description: 'Thinly sliced beef strips',
|
|
486
|
+
cost: 8.50,
|
|
487
|
+
quantity: '300g',
|
|
488
|
+
unit: 'grams',
|
|
489
|
+
supplier: 'Premium Meats'
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
id: 'ingredient-4',
|
|
493
|
+
isParent: false,
|
|
494
|
+
parentId: 'dish-2',
|
|
495
|
+
name: 'Mixed Vegetables',
|
|
496
|
+
type: 'Vegetable',
|
|
497
|
+
description: 'Bell peppers, broccoli, carrots',
|
|
498
|
+
cost: 3.20,
|
|
499
|
+
quantity: '200g',
|
|
500
|
+
unit: 'grams',
|
|
501
|
+
supplier: 'Garden Fresh'
|
|
502
|
+
}
|
|
503
|
+
], []);
|
|
504
|
+
|
|
505
|
+
const columns: DataTableColumn<Dish>[] = useMemo(() => [
|
|
506
|
+
{
|
|
507
|
+
id: 'name',
|
|
508
|
+
accessorKey: 'name',
|
|
509
|
+
header: 'Name',
|
|
510
|
+
enableSorting: true,
|
|
511
|
+
size: 200,
|
|
512
|
+
renderForParent: (row) => (
|
|
513
|
+
<div className="flex items-center gap-2">
|
|
514
|
+
<span className="font-semibold text-main-800">{row.name}</span>
|
|
515
|
+
<span className="text-xs bg-main-100 text-main-700 px-2 py-1 rounded">
|
|
516
|
+
{row.type}
|
|
517
|
+
</span>
|
|
518
|
+
</div>
|
|
519
|
+
),
|
|
520
|
+
renderForChild: (row) => (
|
|
521
|
+
<div className="flex items-center gap-2 ml-4">
|
|
522
|
+
<span className="text-sec-700">{row.name}</span>
|
|
523
|
+
<span className="text-xs bg-sec-100 text-sec-600 px-2 py-1 rounded">
|
|
524
|
+
{row.type}
|
|
525
|
+
</span>
|
|
526
|
+
</div>
|
|
527
|
+
),
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
id: 'description',
|
|
531
|
+
accessorKey: 'description',
|
|
532
|
+
header: 'Description',
|
|
533
|
+
enableSorting: true,
|
|
534
|
+
size: 250,
|
|
535
|
+
renderForParent: (row) => (
|
|
536
|
+
<div className="text-sm text-sec-600">
|
|
537
|
+
{row.description}
|
|
538
|
+
</div>
|
|
539
|
+
),
|
|
540
|
+
renderForChild: (row) => (
|
|
541
|
+
<div className="text-sm text-sec-500 ml-4">
|
|
542
|
+
{row.description}
|
|
543
|
+
</div>
|
|
544
|
+
),
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
id: 'quantity',
|
|
548
|
+
accessorKey: 'quantity',
|
|
549
|
+
header: 'Quantity',
|
|
550
|
+
enableSorting: true,
|
|
551
|
+
size: 120,
|
|
552
|
+
renderForParent: () => null,
|
|
553
|
+
renderForChild: (row) => (
|
|
554
|
+
<div className="text-sm ml-4">
|
|
555
|
+
<span className="font-medium">{row.quantity}</span>
|
|
556
|
+
{row.unit && <span className="text-sec-500 ml-1">{row.unit}</span>}
|
|
557
|
+
</div>
|
|
558
|
+
),
|
|
559
|
+
hideForParent: true,
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
id: 'cost',
|
|
563
|
+
accessorKey: 'cost',
|
|
564
|
+
header: 'Cost',
|
|
565
|
+
enableSorting: true,
|
|
566
|
+
size: 100,
|
|
567
|
+
renderForParent: (row) => (
|
|
568
|
+
<div className="text-right font-semibold text-main-800">
|
|
569
|
+
${row.cost?.toFixed(2)}
|
|
570
|
+
</div>
|
|
571
|
+
),
|
|
572
|
+
renderForChild: (row) => (
|
|
573
|
+
<div className="text-right text-sec-600 ml-4">
|
|
574
|
+
${row.cost?.toFixed(2)}
|
|
575
|
+
</div>
|
|
576
|
+
),
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
id: 'supplier',
|
|
580
|
+
accessorKey: 'supplier',
|
|
581
|
+
header: 'Supplier',
|
|
582
|
+
enableSorting: true,
|
|
583
|
+
size: 150,
|
|
584
|
+
renderForParent: () => null,
|
|
585
|
+
renderForChild: (row) => (
|
|
586
|
+
<div className="text-sm text-sec-600 ml-4">
|
|
587
|
+
{row.supplier}
|
|
588
|
+
</div>
|
|
589
|
+
),
|
|
590
|
+
hideForParent: true,
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
id: 'prepTime',
|
|
594
|
+
accessorKey: 'prepTime',
|
|
595
|
+
header: 'Prep Time',
|
|
596
|
+
enableSorting: true,
|
|
597
|
+
size: 100,
|
|
598
|
+
renderForParent: (row) => (
|
|
599
|
+
<div className="text-center">
|
|
600
|
+
<span className="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-sec-100 text-sec-800">
|
|
601
|
+
{row.prepTime} min
|
|
602
|
+
</span>
|
|
603
|
+
</div>
|
|
604
|
+
),
|
|
605
|
+
renderForChild: () => null,
|
|
606
|
+
hideForChild: true,
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
id: 'difficulty',
|
|
610
|
+
accessorKey: 'difficulty',
|
|
611
|
+
header: 'Difficulty',
|
|
612
|
+
enableSorting: true,
|
|
613
|
+
size: 100,
|
|
614
|
+
renderForParent: (row) => {
|
|
615
|
+
const colors = {
|
|
616
|
+
Easy: 'bg-main-100 text-main-800',
|
|
617
|
+
Medium: 'bg-acc-100 text-acc-800',
|
|
618
|
+
Hard: 'bg-acc-100 text-acc-800'
|
|
619
|
+
};
|
|
620
|
+
return (
|
|
621
|
+
<div className="text-center">
|
|
622
|
+
<span className={`inline-flex items-center px-2 py-1 rounded text-xs font-medium ${colors[row.difficulty as keyof typeof colors] || 'bg-sec-100 text-sec-800'}`}>
|
|
623
|
+
{row.difficulty}
|
|
624
|
+
</span>
|
|
625
|
+
</div>
|
|
626
|
+
);
|
|
627
|
+
},
|
|
628
|
+
renderForChild: () => null,
|
|
629
|
+
hideForChild: true,
|
|
630
|
+
}
|
|
631
|
+
], []);
|
|
632
|
+
|
|
633
|
+
const hierarchicalActions = useMemo(() => [
|
|
634
|
+
{
|
|
635
|
+
label: 'View Details',
|
|
636
|
+
icon: Eye,
|
|
637
|
+
onClick: (row: Dish) => {
|
|
638
|
+
console.log('Viewing details for:', row.name);
|
|
639
|
+
alert(`Viewing details for: ${row.name}`);
|
|
640
|
+
},
|
|
641
|
+
variant: 'default' as const,
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
label: 'Edit',
|
|
645
|
+
icon: Edit,
|
|
646
|
+
onClick: (row: Dish) => {
|
|
647
|
+
console.log('Editing:', row.name);
|
|
648
|
+
alert(`Editing: ${row.name}`);
|
|
649
|
+
},
|
|
650
|
+
variant: 'outline' as const,
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
label: 'Copy Recipe',
|
|
654
|
+
parentLabel: 'Copy Recipe',
|
|
655
|
+
childLabel: 'Copy Ingredient',
|
|
656
|
+
parentIcon: Copy,
|
|
657
|
+
childIcon: Copy,
|
|
658
|
+
onClick: (row: Dish) => {
|
|
659
|
+
console.log('Copying:', row.name);
|
|
660
|
+
alert(`Copying: ${row.name}`);
|
|
661
|
+
},
|
|
662
|
+
variant: 'secondary' as const,
|
|
663
|
+
showForParent: true,
|
|
664
|
+
showForChild: true,
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
label: 'Add Ingredient',
|
|
668
|
+
icon: Plus,
|
|
669
|
+
onClick: (row: Dish) => {
|
|
670
|
+
console.log('Adding ingredient to:', row.name);
|
|
671
|
+
alert(`Adding ingredient to: ${row.name}`);
|
|
672
|
+
},
|
|
673
|
+
variant: 'outline' as const,
|
|
674
|
+
showForParent: true, // Only show for parent rows (dishes)
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
label: 'Export',
|
|
678
|
+
icon: Download,
|
|
679
|
+
onClick: (row: Dish) => {
|
|
680
|
+
console.log('Exporting:', row.name);
|
|
681
|
+
alert(`Exporting: ${row.name}`);
|
|
682
|
+
},
|
|
683
|
+
variant: 'ghost' as const,
|
|
684
|
+
showForChild: true, // Only show for child rows (ingredients)
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
label: 'Favorite Recipe',
|
|
688
|
+
parentLabel: 'Favorite Recipe',
|
|
689
|
+
childLabel: 'Favorite Ingredient',
|
|
690
|
+
icon: Star,
|
|
691
|
+
onClick: (row: Dish) => {
|
|
692
|
+
console.log('Favoriting:', row.name);
|
|
693
|
+
alert(`Favoriting: ${row.name}`);
|
|
694
|
+
},
|
|
695
|
+
variant: 'ghost' as const,
|
|
696
|
+
showForParent: true,
|
|
697
|
+
showForChild: true,
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
label: 'Delete Recipe',
|
|
701
|
+
parentLabel: 'Delete Recipe',
|
|
702
|
+
childLabel: 'Remove Ingredient',
|
|
703
|
+
icon: Trash2,
|
|
704
|
+
onClick: (row: Dish) => {
|
|
705
|
+
console.log('Deleting:', row.name);
|
|
706
|
+
alert(`Deleting: ${row.name}`);
|
|
707
|
+
},
|
|
708
|
+
variant: 'destructive' as const,
|
|
709
|
+
showForParent: true,
|
|
710
|
+
showForChild: true,
|
|
711
|
+
}
|
|
712
|
+
], []);
|
|
713
|
+
|
|
714
|
+
return (
|
|
715
|
+
<DataTable
|
|
716
|
+
data={hierarchicalData}
|
|
717
|
+
columns={columns}
|
|
718
|
+
features={{
|
|
719
|
+
search: true,
|
|
720
|
+
pagination: true,
|
|
721
|
+
sorting: true,
|
|
722
|
+
filtering: true,
|
|
723
|
+
hierarchical: true,
|
|
724
|
+
editing: true,
|
|
725
|
+
deletion: true,
|
|
726
|
+
}}
|
|
727
|
+
actions={hierarchicalActions}
|
|
728
|
+
title="Dishes & Ingredients"
|
|
729
|
+
description="Hierarchical data showing dishes (parent rows) with their ingredients (child rows). Notice the expand/collapse all button in the header and different actions for parent vs child rows."
|
|
730
|
+
hierarchical={{
|
|
731
|
+
enabled: true,
|
|
732
|
+
defaultExpanded: false, // Start with all collapsed
|
|
733
|
+
onExpandedChange: (expandedIds) => {
|
|
734
|
+
console.log('Expanded rows:', expandedIds);
|
|
735
|
+
},
|
|
736
|
+
}}
|
|
737
|
+
getRowId={(row: Dish) => row.id}
|
|
738
|
+
/>
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
export default HierarchicalDishTable;
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
## Best Practices
|
|
746
|
+
|
|
747
|
+
### 1. Data Structure
|
|
748
|
+
- Always use unique IDs for each row
|
|
749
|
+
- Ensure parent-child relationships are valid
|
|
750
|
+
- Keep data structure consistent across all rows
|
|
751
|
+
|
|
752
|
+
### 2. User Experience
|
|
753
|
+
- Start with collapsed state (`defaultExpanded: false`)
|
|
754
|
+
- Use clear visual distinction between parent and child rows
|
|
755
|
+
- Provide meaningful action labels for different row types
|
|
756
|
+
|
|
757
|
+
### 3. Performance
|
|
758
|
+
- Use `useMemo` for column and action definitions
|
|
759
|
+
- Consider pagination for large hierarchical datasets
|
|
760
|
+
- Implement proper `getRowId` function
|
|
761
|
+
|
|
762
|
+
### 4. Accessibility
|
|
763
|
+
- Ensure proper ARIA labels for expand/collapse buttons
|
|
764
|
+
- Use semantic HTML structure
|
|
765
|
+
- Provide keyboard navigation support
|
|
766
|
+
|
|
767
|
+
## Troubleshooting
|
|
768
|
+
|
|
769
|
+
### Common Issues
|
|
770
|
+
|
|
771
|
+
**1. Rows not expanding/collapsing:**
|
|
772
|
+
- Check that `isParent` and `parentId` are set correctly
|
|
773
|
+
- Ensure all `parentId` values reference existing parent row IDs
|
|
774
|
+
- Verify `hierarchical.enabled` is set to `true`
|
|
775
|
+
|
|
776
|
+
**2. Actions not showing for correct row types:**
|
|
777
|
+
- Check `showForParent` and `showForChild` properties
|
|
778
|
+
- Ensure action configuration is correct
|
|
779
|
+
|
|
780
|
+
**3. Sorting not working as expected:**
|
|
781
|
+
- This is expected behavior - child rows sort within their parent groups
|
|
782
|
+
- Parent rows maintain their original order
|
|
783
|
+
|
|
784
|
+
**4. Visual styling issues:**
|
|
785
|
+
- Check `parentRowClassName` and `childRowClassName` configuration
|
|
786
|
+
- Ensure proper CSS classes are applied
|
|
787
|
+
- Verify `indentSize` is appropriate for your design
|
|
788
|
+
|
|
789
|
+
### Debug Tips
|
|
790
|
+
|
|
791
|
+
1. **Check console logs** for expansion state changes
|
|
792
|
+
2. **Verify data structure** matches `HierarchicalDataRow` interface
|
|
793
|
+
3. **Test with simple data** first before complex hierarchies
|
|
794
|
+
4. **Use browser dev tools** to inspect rendered HTML structure
|
|
795
|
+
|
|
796
|
+
## API Reference
|
|
797
|
+
|
|
798
|
+
### HierarchicalDataRow Interface
|
|
799
|
+
|
|
800
|
+
```typescript
|
|
801
|
+
interface HierarchicalDataRow extends DataRecord {
|
|
802
|
+
isParent: boolean;
|
|
803
|
+
parentId?: string;
|
|
804
|
+
id: string;
|
|
805
|
+
}
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
### HierarchicalConfig Interface
|
|
809
|
+
|
|
810
|
+
```typescript
|
|
811
|
+
interface HierarchicalConfig {
|
|
812
|
+
enabled: boolean;
|
|
813
|
+
defaultExpanded?: boolean | string[];
|
|
814
|
+
onExpandedChange?: (expandedIds: string[]) => void;
|
|
815
|
+
expandButton?: React.ComponentType<ExpandButtonProps>;
|
|
816
|
+
indentSize?: number;
|
|
817
|
+
parentRowClassName?: string;
|
|
818
|
+
childRowClassName?: string;
|
|
819
|
+
}
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
### DataTableColumn Hierarchical Properties
|
|
823
|
+
|
|
824
|
+
```typescript
|
|
825
|
+
interface DataTableColumn<TData> {
|
|
826
|
+
// ... standard properties
|
|
827
|
+
|
|
828
|
+
// Hierarchical-specific properties
|
|
829
|
+
renderForParent?: (row: TData) => React.ReactNode;
|
|
830
|
+
renderForChild?: (row: TData) => React.ReactNode;
|
|
831
|
+
hideForParent?: boolean;
|
|
832
|
+
hideForChild?: boolean;
|
|
833
|
+
}
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
### DataTableAction Hierarchical Properties
|
|
837
|
+
|
|
838
|
+
```typescript
|
|
839
|
+
interface DataTableAction<TData> {
|
|
840
|
+
// ... standard properties
|
|
841
|
+
|
|
842
|
+
// Hierarchical-specific properties
|
|
843
|
+
showForParent?: boolean;
|
|
844
|
+
showForChild?: boolean;
|
|
845
|
+
parentIcon?: React.ComponentType<{ className?: string }>;
|
|
846
|
+
childIcon?: React.ComponentType<{ className?: string }>;
|
|
847
|
+
parentLabel?: string;
|
|
848
|
+
childLabel?: string;
|
|
849
|
+
}
|
|
850
|
+
```
|