Skip to content

Phase 2: Standardized AG Grid

Status: Planning Repository: ux-prototype (build), arda-frontend-app (test via linking) Parent: Vendor Page and Standard Layout

Make the design system’s data grid production-ready by standardizing input components, building cell editors as variants of those inputs, and filling gaps in the createEntityDataGrid factory. The result is a grid component that can replace hand-rolled grids in arda-frontend-app starting with the vendor page (Phase 3).

Work happens in ux-prototype. Testing happens in arda-frontend-app via npm run dev:local (linked builds). Multiple PRs expected as we touch many files across the input and grid systems.

  1. Build/refine inputs and cell editors in ux-prototype
  2. Link repos via npm run dev:local in arda-frontend-app
  3. Validate against real data and interactions in the frontend app
  4. PR to ux-prototype when a coherent slice is complete
  5. Repeat

Audit all input atoms to establish consistent interaction patterns across every input type. The goal is parallel behavior: once a user learns how one input works, every other input works the same way.

Interaction states to standardize:

StateExpected Behavior (all input types)
IdleDefault appearance, border-input, text-foreground
HoverSubtle border change (border-border-strong)
FocusRing treatment (ring-2 ring-ring), border highlight
BlurValidate, commit value, remove ring
DisabledReduced opacity, no pointer events
ErrorDestructive border, error message below
Read-onlyDisplay value without input chrome

Keyboard interactions to standardize:

KeyExpected Behavior (all input types)
TabMove focus to next input
Shift+TabMove focus to previous input
Enterenter edit state? or Commit value
EscapeExit edit state?
Arrow keysNavigate the grid in every direction when not in edit state. When in edit state navigate inside of input (select: cycle options; number: increment/decrement if set to true number fields should have this as boolean property)
Copy/PasteStandard clipboard behavior, respect input constraints. Works in edit state or view state.

Input types to audit:

  • Text input
  • Number input
  • Select / dropdown
  • Typeahead / combobox
  • Date picker
  • Date-time picker
  • Time picker
  • Boolean (checkbox / toggle)
  • Color picker
  • Image upload
  • Memo / multi-line text
  • Money (number + currency)
  • Quantity (number + unit)
  • Duration (number + time unit)
  • URL input
  • Merged cells (Allows creation of groups or divider lines)

Deliverables:

  • Interaction audit document (current state vs target state per input type)
  • Standardized base styles and interaction patterns
  • Updated Storybook stories showing all states for each input
  • Consistent keyboard behavior across all inputs

Build grid cell editors as thin wrappers around the standardized inputs from Part A. Each cell editor adapts a standard input for the AG Grid editing context (inline, constrained height, keyboard navigation between cells).

Cell-specific interaction layer:

TriggerGrid Behavior
Single clickSelect row (no edit)
Double clickEnter edit mode on cell
Enter (on selected cell)Enter edit mode
Enter (while editing)Commit value, exit edit mode, move to next row
Tab (while editing)Commit value, move to next editable cell
Shift+Tab (while editing)Commit value, move to previous editable cell
Escape (while editing)Exit edit mode
Arrow keys (while editing)Navigate within editor (not between cells)
Arrow keys (not editing)Navigate between cells
Ctrl+ZUndo edits sequentially
Copy and PasteAlways works if the object in the clipboard matches the input type. Do not need to be in edit mode. Focus ring turns red for a second and standard alert at the top of screen says “invalid input”

Cell editor types (corresponding to inputs):

Cell EditorWraps InputNotes
TextCellEditorText inputSingle-line inline
NumberCellEditorNumber inputNumeric constraints, formatting on blur, boolean for up down arrow incrementing
SelectCellEditorSelectDropdown opens on edit, keyboard navigable
TypeaheadCellEditorTypeaheadAsync lookup, create-new option, max list length prop (negative number for infinite, 0 or null defaults to 8)
DateCellEditorDate pickerCalendar dropdown
BooleanCellEditorCheckboxToggle on click or Space or enter, no double-click needed
ColorCellEditorColor pickerSwatch grid dropdown
ImageCellEditorImage uploadThumbnail + upload trigger, drag and drop should work without entering edit mode
MemoCellEditorMemoExpands to multi-line overlay
Money/Prefix CellEditorMoneyNumber + currency display, dropdown for editing prefix when in edit mode (focus defaults to value, shift tab to focus on prefix)

Bulk editing (multi-cell selection):

Users should be able to select multiple cells and apply a single edit to all of them — for example, selecting 10 rows and changing a dropdown value, or pasting a value across a range. AG Grid Enterprise provides enableRangeSelection and enableFillHandle for this. Evaluate whether Community edition’s clipboard support is sufficient or if Enterprise range selection is needed.

Bulk ActionExpected Behavior
Select range + type valueApply typed value to all selected cells of the same column
Select range + pastePaste clipboard content across selected cells
Select range + dropdown changeApply selected dropdown value to all cells in range
Fill handle dragExtend a cell’s value down/across like a spreadsheet
Select range + Delete/BackspaceClear all selected cells

Add row:

The grid must support adding a new row inline. This creates a blank row at the top (or bottom, configurable) of the grid in edit mode. The consumer provides an onRowAdd callback prop that receives the new row data and is responsible for the API call to persist it. The grid itself is not opinionated about the backend — it just provides the UI and calls the prop.

BehaviorDetails
Trigger”Add row” button above grid (or keyboard shortcut)
New row positionTop of grid (default), configurable
Initial stateEdit mode on first editable cell, all fields empty or defaults
CommitEnter or Tab out of last cell → calls onRowAdd(rowData)
CancelEscape → removes the uncommitted row
ValidationCell-level validation runs before commit; row not saved until all cells valid

Row grouping / hierarchy:

AG Grid has built-in row grouping (groupDisplayType, rowGroup on ColDef) that we should leverage directly. This enables tree-structured data views like supplier → items, category → subcategory → items, etc.

FeatureAG Grid CapabilityEdition
Single-column groupingrowGroup: true on ColDefEnterprise
Multi-column groupingMultiple rowGroup columnsEnterprise
Group row renderinggroupRowRendererEnterprise
Expand/collapseBuilt-in with groupDefaultExpandedEnterprise
Aggregation in group rowsaggFunc on ColDefEnterprise
Tree data (arbitrary hierarchy)treeData: true + getDataPathEnterprise

Note: Row grouping requires AG Grid Enterprise. If we want to avoid the Enterprise dependency, we’d need to implement grouping at the data layer (pre-group rows before passing to the grid), which is what the current order queue page does manually.

Grouped/composite columns:

Some data is logically grouped — values that belong together and should display as a single cell but split apart when editing. Examples:

Grouped ColumnComponent FieldsDisplayEdit Mode
Min Quantityamount + unit”10 each”Two inputs side by side (number + select)
Order Quantityamount + unit”100 boxes”Two inputs side by side
Unit Costvalue + currency”$5.00 USD”Number + currency dropdown
Addressstreet#, street, city, state, zip, country”123 Main St, Austin, TX 78701”Multi-field form (expandable)
Durationlength + unit”3 days”Number + time unit select

Grouped column behaviors:

  • Display as one cell — combined formatted value in read mode
  • Split on edit — expand into component inputs when editing
  • Move together — reordering a grouped column moves all component fields as a unit
  • Resize together — the group shares a single column width
  • Sort by primary field — sort uses the first component (e.g. amount for quantity, value for money)
  • Validation cascades — changing one field may invalidate others (e.g. changing country invalidates state; address validation may need to validate the full composite via a dropdown/lookup)

This pattern mirrors a broader need for form groups/composite inputs — reusable multi-field components like an Address input that combines several independent inputs (street, city, state, country) where:

  • Some sub-inputs can exist independently (state, country dropdowns)
  • Changing one sub-input may invalidate or constrain others
  • The composite may have its own validation (e.g. address verification API)
  • The same composite appears in both form mode and cell editor mode

The cell editor architecture (Open Question #7) should account for composite editors — a grouped cell editor wraps multiple inputs and manages their internal layout, focus order, and cross-field validation.

Cell display components:

Each cell type also has a display variant (read-only rendering) that matches the visual language of the editor. Display components handle formatting (currency symbols, date formatting, color swatches, image thumbnails, etc.).

Deliverables:

  • Cell editors wrapping standardized inputs (factory pattern: createXCellEditor())
  • Cell display components with consistent formatting
  • Storybook stories showing edit lifecycle for each cell type
  • Documentation of cell interaction model

Fill gaps in createEntityDataGrid to make it production-ready. The current factory provides the foundation but is missing features the production ArdaGrid has.

Gap analysis (prototype vs production ArdaGrid):

FeaturePrototype (createEntityDataGrid)Production (ArdaGrid)Action
Column persistenceuseColumnPersistence hookComplex inline localStorage logicKeep prototype pattern, verify completeness
Sort UIAG Grid defaultCustom SortMenuHeader with portal dropdownEvaluate: adopt custom header or use AG Grid default
Server paginationSupported via configSupported + page size selectorAdd page size selector
Row editing + auto-publishBuilt-in via useRowAutoPublishManual draft/publish in parentKeep prototype pattern
Cell editingFactory-pattern editorsHand-rolled per-domain editorsStandardize in Part B
Enterprise featuresCommunity editionEnterprise modules (optional)Support optional enterprise license
SearchBuilt-in client-sideNot built into gridKeep prototype search
FilteringNot built inAG Grid column filtersAdd configurable filtering
Empty stateConfigurable componentOverlay-basedKeep prototype pattern
Loading stateSupportedSkeleton overlaysAlign approaches
SelectionCheckbox columnCheckbox + stable row IDsVerify stable selection
Drag-to-scrollSupportedNot in ArdaGridKeep
Sticky columns/rowsNot implementedAction column pinned rightAdd configurable pinning (columns and rows)
Column visibility & orderingNot implementedCustom UI — users toggle and reorder columns, saved per tenantMust preserve — port to design system grid
Column layout presetsNot implementedNot implementedFuture: let users save named column layouts and switch between them for different workflows

Note: Filtering and infinite scroll using Server-Side Row Model are coming to the Arda grid in the coming days. We will have to capture this functionality.

Row height configurability (nice-to-have):

Support a density prop on the grid:

DensityRow HeightUse Case
compact32pxDense data views (items list, order queue)
comfortable40pxDefault, balanced
spacious48pxTouch-friendly, fewer rows (mobile, receiving)

Consumer sets density at the grid level; all rows respect it.

Deliverables:

  • Updated createEntityDataGrid factory with gap fixes
  • Configurable filtering support
  • Page size selector for server pagination
  • Optional enterprise module support
  • Row height / density prop (nice-to-have)
  • Sticky/pinned column and row support (configurable per ColDef)
  • Column visibility and ordering UI (port existing ArdaGrid feature)
  • Column layout persistence per tenant (preserve existing behavior)
  • Updated stories and documentation

Future (not in Phase 2 scope but design for it):

  • Column layout presets — users save named configurations (e.g. “Ordering view”, “Receiving view”) and switch between them. The persistence layer should be designed to support multiple named layouts per grid per tenant, even though Phase 2 only implements a single default layout.
AssetPathNotes
DataGrid moleculecanary/molecules/data-grid/Low-level AG Grid wrapper
EntityDataGrid factorycanary/organisms/shared/entity-data-grid/Main factory (~650 lines)
EntityDataGrid shimcanary/organisms/shared/entity-data-grid-shim/Tier 3b extensions
Cell atoms (10 types)canary/atoms/grid/text, number, select, boolean, date, color, image, memo, action, typeahead
Column persistence hookcanary/molecules/data-grid/use-column-persistence.tslocalStorage with namespacing
Row auto-publish hookcanary/organisms/shared/entity-data-grid/use-row-auto-publish.tsPer-row dirty tracking
Item grid columnscanary/molecules/item-grid/item-grid-columns.tsxColumn def factory
Item grid organismcanary/organisms/item-grid/Item-specific grid
Kitchen sink storiescanary/organisms/shared/entity-data-grid/kitchen-sink.stories.tsxComprehensive demo

In arda-frontend-app (reference, not build here)

Section titled “In arda-frontend-app (reference, not build here)”
AssetPathNotes
ArdaGridcomponents/table/ArdaGrid.tsxProduction grid (~1328 lines)
Column presetscomponents/table/columnPresets.tsx30+ column defs (~1500 lines)
Domain cell editors (9)components/items/*CellEditor.tsxSupplier, Type, Location, etc.
ItemTableAGGridapp/items/ItemTableAGGrid.tsxItem page grid
OrderQueueAGGridapp/order-queue/OrderQueueAGGrid.tsxOrder queue grid

Before building custom implementations, we need to audit what AG Grid already provides out of the box. Many of the features listed in this spec (cell editors, keyboard navigation, clipboard, undo/redo, validation, filtering, column persistence) are available as built-in AG Grid capabilities — either in the Community or Enterprise edition.

The current ArdaGrid and columnPresets.tsx in arda-frontend-app contain ~2,800 lines of hand-rolled grid logic. A significant portion may duplicate functionality that AG Grid provides natively. We need to determine:

  1. What AG Grid already does — which features in our spec have direct AG Grid equivalents (built-in cell editors, cellEditorParams, undo via undoRedoCellEditing, clipboard via enableRangeSelection, etc.)?
  2. Why it wasn’t used — was there a technical reason (incompatibility with our UI patterns, limitations in the Community edition, Radix portal conflicts) or was it simply a gap in awareness at the time?
  3. What we should adopt vs wrap — for each feature, decide whether to:
    • Adopt the AG Grid built-in directly (no custom code)
    • Wrap the built-in with a thin styling/behavior layer
    • Replace with a custom implementation (only when AG Grid’s version genuinely doesn’t work for our use case)

This audit should happen at the start of Part A and inform the scope of Parts B and C. Adopting built-in features where possible reduces our maintenance burden and keeps us on the upgrade path for future AG Grid versions.

Known AG Grid built-ins relevant to this spec:

FeatureAG Grid CapabilityEditionOur Current Approach
Cell editors (text, number, select, date)agTextCellEditor, agNumberCellEditor, agSelectCellEditor, agDateCellEditorCommunityHand-rolled per domain
Keyboard navigation (Enter, Tab, Escape, arrows)Built-in with enterNavigatesVertically, tabToNextCellCommunityPartially custom
Undo/RedoundoRedoCellEditing, undoRedoCellEditingLimitCommunityNot implemented
Clipboard copy/pasteenableCellTextSelection, clipboardDelimiterCommunityNot implemented
Range selection + fill handleenableRangeSelection, enableFillHandleEnterpriseNot implemented
Column state persistencecolumnApi.getColumnState() / applyColumnState()CommunityCustom useColumnPersistence hook
Filtering (column filters)agTextColumnFilter, agNumberColumnFilter, agSetColumnFilterCommunity/EnterpriseNot built in
Row sortingBuilt-in with sortable: trueCommunityCustom SortMenuHeader
ValidationvalueSetter with validation logicCommunityNot standardized
Row heightrowHeight, getRowHeightCommunityFixed per grid
Pinned/sticky columnspinned: 'left' / pinned: 'right' on ColDefCommunityUsed on some columns (actions)
Pinned/sticky rowspinnedTopRowData, pinnedBottomRowDataCommunityNot implemented
Column visibility & reordercolumnApi.setColumnsVisible(), drag reorder, suppressMovableCommunityCustom column visibility UI in ArdaGrid

Current Enterprise usage in arda-frontend-app:

ArdaGrid.tsx registers AllEnterpriseModule (all Enterprise features available). Currently only rowGroup is actively used (in OrderQueueAGGrid for supplier/order method grouping). The following Enterprise features are licensed and available but unused — these should be evaluated before building custom alternatives:

  • enableRangeSelection — multi-cell selection (needed for bulk edit)
  • enableFillHandle — drag to extend values (spreadsheet-style)
  • agSetColumnFilter — set-based column filtering
  • Server-Side Row Model — infinite scroll with server pagination
  • Tree Data — arbitrary hierarchy display
  • Clipboard with range — paste across selected cell ranges
#QuestionOptionsRecommendationDecision
1Should we adopt ArdaGrid’s custom SortMenuHeader or use AG Grid’s built-in sort?A) Port SortMenuHeader to design system; B) Use AG Grid default; C) Build newB — use AG Grid built-in sortUse AG Grid default
2How should cell validation errors display?A) Inline below cell; B) Tooltip on hover; C) Cell border + row-level error summaryCell shakes and reverts to previous value. A toast shows at the top of the screen explaining the error. For bulk edits, a single persistent toast lists all errors (user must manually dismiss). AG Grid provides isCancelAfterEnd() and getValidationErrors() hooks on cell editors for this.Shake + toast
3Should inputs support both “form mode” and “cell mode” via a prop, or should cell editors be separate components that share styles?A) Single component with mode prop; B) Separate components sharing base styles; C) Use AG Grid’s cellDataType + dataTypeDefinitions systemAG Grid already solves this. Cell editors are controlled components receiving value + onValueChange — the same contract as React form inputs. AG Grid’s cellDataType system auto-wires editors, renderers, formatters, and filters from a single declaration (e.g. cellDataType: 'number'). Custom data types extend built-in ones. Our inputs should be built as standalone components that also conform to AG Grid’s CustomCellEditorProps contract (value, onValueChange). No wrapper needed — the input IS the editor. See OQ#7 for details.Align with AG Grid contract
4How much of our custom grid code duplicates AG Grid built-ins?A) Audit and adopt built-ins where possible; B) Keep custom implementationsA — audit first, replace where AG Grid’s version meets our needs, document reasons where it doesn’tPending
5Where should column layout persistence live?A) localStorage per tenant (current); B) Backend API per user-account; C) Both with syncKeep current Redux persist / localStorage pattern for v1. Backend UserAccount.settings field available for future cross-device sync but not addressed in this phase.localStorage (v1)
6Should column layout presets be per-grid or global?A) Per-grid (items grid has its own presets); B) Global presets applied to any gridA — per-grid. Each grid instance has different data and use cases. Not addressed in v1; the persistence layer should be designed to support named presets per grid for a future version.Per-grid (future)
7Cell editor architecture: generic wrapper or per-input?A) Each input is a standalone cell editor; B) Generic GridCellEditor wrapper; C) HOC/factory; D) Align with AG Grid’s native contractD — AG Grid’s cell editor contract is already minimal and matches React input conventions. A cell editor is a controlled component with value + onValueChange props, plus optional lifecycle hooks via useGridCellEditor (isCancelBeforeStart, isCancelAfterEnd, focusIn). AG Grid also provides cellDataType / dataTypeDefinitions to auto-wire editors from a type declaration. Our approach: build each input as a standard React component that accepts value + onChange. To use it as a cell editor, either (a) it already matches CustomCellEditorProps directly, or (b) wrap it in a thin adapter that maps onChangeonValueChange and adds useGridCellEditor for validation. Shared grid behavior (shake on error, toast) lives in the adapter or a shared hook, not in every input. See “AG Grid Cell Editor Architecture” section below.AG Grid native + thin adapter
8Do we need AG Grid Enterprise for this phase?A) Community only; B) Enterprise for range selection, grouping, tree data; C) Enterprise optional (degrade gracefully)Already resolved: arda-frontend-app has Enterprise v34.3.1 with a license key (ArdaGrid.tsx line 35). The ux-prototype uses Community only. Decision: the design system grid should work with Community for Storybook/dev, but detect and enable Enterprise features (range selection, row grouping, fill handle, set filters) when the consumer provides a license. The consumer (arda-frontend-app) already has one.Enterprise available

Based on AG Grid’s documented contract, cell editors are controlled components — they receive value and onValueChange as props. This is nearly identical to how standard React inputs work (value + onChange).

AG Grid’s cell editor contract:

// AG Grid expects this shape (CustomCellEditorProps)
interface CustomCellEditorProps {
value: any;
onValueChange: (value: any) => void;
// Plus context: column, colDef, data, node, rowIndex, eventKey,
// stopEditing(), eGridCell, parseValue(), formatValue(), validate()
}

AG Grid’s cellDataType system:

Instead of manually wiring editors per column, you declare a data type once and AG Grid auto-selects the editor, renderer, formatter, and filter:

// Column definition — just declare the type
{ field: 'unitCost', cellDataType: 'currency' }
// Grid-level — define custom data types
const dataTypeDefinitions = {
currency: {
extendsDataType: 'number',
baseDataType: 'number',
valueFormatter: params => `$${params.value?.toFixed(2)}`,
// Automatically uses NumberCellEditor, or our custom one if registered
}
};

Recommended architecture for our inputs:

┌─────────────────────────────────────────────┐
│ Standard Input Component │
│ (TextInput, NumberInput, Select, etc.) │
│ Props: value, onChange, onBlur, error, ... │
│ Works in forms, dialogs, standalone │
└──────────────────┬──────────────────────────┘
┌──────────▼──────────┐
│ Cell Editor Adapter │ (thin, per-input-type)
│ Maps onChange → │
│ onValueChange │
│ Adds useGridCell- │
│ Editor hooks: │
│ - isCancelAfterEnd │
│ (validation) │
│ - focusIn/focusOut │
│ Shake + toast on │
│ validation fail │
└──────────┬──────────┘
┌──────────▼──────────┐
│ AG Grid renders it │
│ as a cell editor │
│ via cellDataType or │
│ colDef.cellEditor │
└─────────────────────┘

Key decisions:

  1. Inputs are NOT AG Grid-aware. They are standard React components with value + onChange. They work anywhere — forms, dialogs, sidebars.
  2. Cell editor adapters are thin. Each adapter maps onChangeonValueChange, adds useGridCellEditor hooks for validation, and handles grid-specific behavior (shake animation, toast on error).
  3. Shared grid behavior lives in a hook. A useGridCellValidation hook (or similar) provides the shake + toast pattern so each adapter doesn’t re-implement it.
  4. cellDataType wires it up. Custom data types in dataTypeDefinitions auto-select the right adapter-wrapped editor per column — no manual cellEditor prop needed on each ColDef.
  5. Composite editors (quantity+unit, money+currency, address) are adapters that render multiple inputs internally and manage their own focus order and cross-field validation.
  • AG Grid built-in feature audit completed — documented which built-ins we adopt, wrap, or replace (and why)
  • All input types have consistent interaction states (hover, focus, blur, error, disabled)
  • All input types have consistent keyboard behavior (Enter, Escape, Tab, arrows)
  • Cell editors wrap standardized inputs via generic wrapper/factory (shared grid behavior)
  • Bulk edit works — multi-cell selection, paste across range, dropdown apply to range
  • Add row works — inline new row with onRowAdd callback prop for backend persistence
  • Row grouping/hierarchy supported (Enterprise) with graceful Community fallback
  • Sticky/pinned columns and rows configurable per ColDef
  • Column visibility and ordering UI ported from ArdaGrid
  • Column layout persistence works (localStorage, designed for future backend sync)
  • createEntityDataGrid supports configurable filtering
  • Grid works with linked arda-frontend-app via npm run dev:local
  • Storybook stories demonstrate all cell types in edit lifecycle
  • No regressions in existing grid stories

Copyright: (c) Arda Systems 2025-2026, All rights reserved