Skip to content

Phase 4: Entity Data Grid

Port the entity data grid factory (createEntityDataGrid<T>()), create the composable useDirtyTracking<T>() hook, build the ActionCellRenderer<T> atom, and create the vendored-compatible shim (createEntityDataGridShim<T>()).

Corresponds to Waves 3a and 3b of the Porting Strategy.

Phase 3 must be complete. Verify by checking artifacts — do NOT re-run tests.

Verify before starting:

  • DataGrid molecule present: ls src/components/canary/molecules/data-grid/ shows data-grid.tsx, sort-menu-header.tsx, use-column-persistence.ts, index.ts, test and story files
  • DataGrid exports compile: grep 'DataGrid' src/components/canary/molecules/data-grid/index.ts confirms the barrel exports DataGrid, DataGridRef, config types
  • SortMenuHeader present: ls src/components/canary/molecules/data-grid/sort-menu-header.tsx exists
  • Phase 3 exit gate passed: check for npm run lint + npm run test success in the Phase 3 terminal output (do not re-run)

T4.1 — Create useDirtyTracking<T>() Hook

Section titled “T4.1 — Create useDirtyTracking<T>() Hook”
  • Target: components/canary/organisms/shared/entity-data-grid/use-dirty-tracking.ts
  • Extract dirty tracking logic from components/extras/organisms/shared/entity-data-grid/create-entity-data-grid.tsx:
    • Clone row data for comparison
    • Track per-cell edits (dirty set)
    • saveAllDrafts() — clears dirty state optimistically
    • getHasUnsavedChanges() — returns boolean
    • discardAllDrafts() — restores original values
    • onUnsavedChangesChange?(hasChanges: boolean) — callback
  • This is a standalone composable hook — no dependency on DataGrid or AG Grid
  • Create unit tests: verify dirty state transitions, save/discard behavior, callback invocations
  • Copy components/extras/organisms/shared/entity-data-grid/create-entity-data-grid.tsx to components/canary/organisms/shared/entity-data-grid/create-entity-data-grid.tsx
  • Rename exported function from createArdaEntityDataGrid to createEntityDataGrid
  • Update imports:
    • ArdaDataGrid / ArdaDataGridRef → canary DataGrid / DataGridRef from @/components/canary/molecules/data-grid
    • PaginationData@/types/canary/pagination
    • ag-grid-community types — no change
  • Remove inline dirty tracking — replace with optional composition of useDirtyTracking<T>() from T4.1. The factory should accept an optional useDirtyTracking instance rather than embedding the logic.
  • Add Tier 3a features (from vendored ArdaGrid features allocated to 3a in the Q6 feature table):
    • enableMultiSort?: boolean + onSortChanged?(sortModel: any): void — maps to AG Grid’s multiSortKey prop
    • enableFiltering?: boolean + onFilterChanged?(filterModel: any): void — maps to filter: true on defaultColDef
    • onCellEditingStarted?, onCellEditingStopped?, onCellFocused? — passthrough AG Grid native events
    • getRowClass?(params): string | string[] — passthrough AG Grid native prop
  • Copy and adapt existing tests and stories
  • Create barrel: components/canary/organisms/shared/entity-data-grid/index.ts
  • All unit tests pass
  • Storybook stories render in Components/Canary/Organisms/Shared/Entity Data Grid
  • Dirty tracking testable in isolation via hook tests
  • npm run lint + npm run test pass
  • Target: components/canary/atoms/grid/action/action-cell-renderer.tsx
  • Props: rowData: T, actions: RowAction<T>[] where RowAction<T> = { label: string; icon?: ReactNode; onClick: (rowData: T) => void }
  • Behavior: Renders a button that opens a dropdown menu listing actions. Click an action → onClick(rowData). Click outside or Escape → close.
  • Uses portal (createPortal) for dropdown overflow escaping (same pattern as SortMenuHeader)
  • Dependencies: cn*, lucide-react (for default MoreVertical icon)
  • Create tests and story
  • Export RowAction<T> type alongside the component

T4.5 — Create createEntityDataGridShim<T>()

Section titled “T4.5 — Create createEntityDataGridShim<T>()”
  • Target: components/canary/organisms/shared/entity-data-grid-shim/create-entity-data-grid-shim.tsx
  • Wraps createEntityDataGrid<T>() from Wave 3a
  • Add Tier 3b features (from vendored ArdaGrid features allocated to 3b in the Q6 feature table):
    • enableRowActions?: boolean + rowActions?: RowAction<T>[] — injects ActionCellRenderer as a pinned-right column
    • onRowDoubleClicked?(entity: T): void — passthrough AG Grid native event
    • hasActiveSearch?: boolean — vendored compatibility shim: when true + no data, shows “No items found” instead of emptyStateComponent
    • initialState?: GridState — passthrough AG Grid v31+ state restoration prop
  • Extended ref:
    • refreshData(): void — delegates to gridApi.refreshCells()
    • getSelectedRows(): T[] — delegates to gridApi.getSelectedRows()
    • selectAll(): void — delegates to gridApi.selectAll()
    • deselectAll(): void — delegates to gridApi.deselectAll()
  • Create tests and stories demonstrating vendored ArdaGrid feature parity
  • Create barrel: components/canary/organisms/shared/entity-data-grid-shim/index.ts
  • All unit tests pass
  • Storybook stories demonstrate: row actions dropdown, double-click handler, hasActiveSearch empty state, extended ref methods
  • npm run lint + npm run test pass
  • components/canary/organisms/shared/entity-data-grid/ contains: factory, dirty tracking hook, index barrel, tests, stories
  • components/canary/organisms/shared/entity-data-grid-shim/ contains: shim factory, index barrel, tests, stories
  • components/canary/atoms/grid/action/ contains: ActionCellRenderer, RowAction type, tests, story
  • useDirtyTracking<T>() works as a standalone composable hook
  • createEntityDataGrid<T>() provides Tier 3a features (multi-sort, filtering, cell editing lifecycle, getRowClass)
  • createEntityDataGridShim<T>() provides vendored ArdaGrid compatibility (row actions, double-click, hasActiveSearch, initialState, extended ref)
  • No imports from extras or vendored in any canary file
  • npm run lint + npm run test pass