EERP Suite

Architecture

This document describes the architecture of the @marlinjai/data-table package.

Package Structure

@marlinjai/data-table/
β”œβ”€β”€ packages/
β”‚   β”œβ”€β”€ core/                    # Core types, interfaces, engines
β”‚   β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”‚   β”œβ”€β”€ types.ts         # All TypeScript interfaces
β”‚   β”‚   β”‚   β”œβ”€β”€ db-adapter.ts    # DatabaseAdapter interface
β”‚   β”‚   β”‚   β”œβ”€β”€ formula/         # Formula Engine
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ FormulaParser.ts     # Lexer + recursive descent parser
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ FormulaFunctions.ts  # 50+ built-in functions
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ FormulaEngine.ts     # AST evaluation with caching
β”‚   β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ rollup/          # Rollup Engine
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ RollupEngine.ts      # Aggregation calculator
β”‚   β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   └── package.json
β”‚   β”‚
β”‚   β”œβ”€β”€ react/                   # React components + hooks
β”‚   β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”‚   β”œβ”€β”€ providers/       # DataTableProvider
β”‚   β”‚   β”‚   β”œβ”€β”€ components/      # TableView, cells, filters
β”‚   β”‚   β”‚   β”‚   └── views/       # View-specific components
β”‚   β”‚   β”‚   β”‚       β”œβ”€β”€ ViewSwitcher.tsx  # View tab navigation
β”‚   β”‚   β”‚   β”‚       β”œβ”€β”€ BoardView.tsx     # Kanban board view
β”‚   β”‚   β”‚   β”‚       └── CalendarView.tsx  # Calendar grid view
β”‚   β”‚   β”‚   β”œβ”€β”€ hooks/           # useTable, useColumns, useViews
β”‚   β”‚   β”‚   └── styles/          # CSS variables + base styles
β”‚   β”‚   └── package.json
β”‚   β”‚
β”‚   β”œβ”€β”€ adapter-memory/          # In-memory adapter
β”‚   └── adapter-d1/              # Cloudflare D1 adapter
β”‚
└── demo/                        # Demo application

Design Patterns

Adapter Pattern

The package uses the Adapter Pattern to decouple data storage from the UI layer:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   React Layer   │────▢│  DatabaseAdapter  β”‚
β”‚  (components)   β”‚     β”‚    (interface)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β–Ό                β–Ό                β–Ό
       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
       β”‚  Memory  β”‚     β”‚    D1    β”‚     β”‚ Supabase β”‚
       β”‚ Adapter  β”‚     β”‚ Adapter  β”‚     β”‚ Adapter  β”‚
       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Benefits:

  • Test with in-memory adapter, deploy with D1/Supabase
  • Easy to add new storage backends
  • No storage logic in UI components

Component Hierarchy

DataTableProvider (context)
└── TableView
    β”œβ”€β”€ TableHeader
    β”‚   └── HeaderCell (sortable, resizable)
    β”œβ”€β”€ TableBody
    β”‚   └── TableRow
    β”‚       └── CellRenderer β†’ TextCell | NumberCell | SelectCell | ...
    └── AddRowButton

Portal Pattern for Dropdowns

Dropdowns (SelectCell, MultiSelectCell) use React Portals to render to document.body:

{isOpen && createPortal(
  <div className="dt-dropdown" style={{ position: 'fixed', ... }}>
    {/* dropdown content */}
  </div>,
  document.body
)}

Why portals?

  • Avoids clipping from parent overflow: hidden
  • Correct z-index stacking
  • Works in scrollable containers

Data Flow

User Action
    β”‚
    β–Ό
Component Event Handler (onClick, onChange)
    β”‚
    β–Ό
Hook Method (updateCell, addRow, etc.)
    β”‚
    β–Ό
DatabaseAdapter Method (async)
    β”‚
    β–Ό
Local State Update (optimistic or after response)
    β”‚
    β–Ό
React Re-render

Database Schema

The adapter operates on these entities:

Tables

interface Table {
  id: string;
  workspaceId: string;
  name: string;
  icon?: string;
}

Columns

interface Column {
  id: string;
  tableId: string;
  name: string;
  type: ColumnType;
  position: number;
  width: number;
  config?: Record<string, unknown>;
}

Rows

interface Row {
  id: string;
  tableId: string;
  cells: Record<string, CellValue>; // columnId -> value
}

Select Options

interface SelectOption {
  id: string;
  columnId: string;
  name: string;
  color?: string;
}

CSS Architecture

All styles use CSS variables with the --dt- prefix:

variables.css    β†’ Defines all CSS variables (colors, sizing, etc.)
base.css        β†’ Base component styles using the variables

Consumers can override any variable:

:root {
  --dt-bg-primary: #000;
  --dt-text-primary: #fff;
}

Dark mode is handled via:

  1. @media (prefers-color-scheme: dark) - Auto
  2. .dark class or [data-theme="dark"] - Manual

Formula Engine

The Formula Engine (packages/core/src/formula/) provides Notion-like formula evaluation capabilities using a recursive descent parser and AST-based evaluation.

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Formula String  │────▢│  FormulaParser   │────▢│      AST         β”‚
β”‚  "prop("A") + 1" β”‚     β”‚  (Lexer + Parser)β”‚     β”‚  (Abstract Tree) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                          β”‚
                                                          β–Ό
                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                         β”‚    Cell Value    │◀────│  FormulaEngine   β”‚
                         β”‚                  β”‚     β”‚  (AST Evaluator) β”‚
                         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                          β”‚
                                                          β–Ό
                                                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                                  β”‚ FormulaFunctions β”‚
                                                  β”‚  (50+ built-ins) β”‚
                                                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Components

FileResponsibility
FormulaParser.tsLexer (tokenizer) + recursive descent parser producing AST
FormulaFunctions.tsRegistry of 50+ built-in functions (math, text, logic, date)
FormulaEngine.tsAST evaluation with caching and custom function support

Design Pattern: Visitor Pattern

The engine uses the Visitor Pattern for AST evaluation. Each node type has a corresponding evaluation method:

private evaluateNode(node: ASTNode, context: EvaluationContext): FormulaValue {
  switch (node.type) {
    case 'NumberLiteral':
      return node.value;
    case 'PropertyReference':
      return this.evaluatePropertyReference(node.propertyName, context);
    case 'BinaryExpression':
      return this.evaluateBinaryExpression(node.operator, node.left, node.right, context);
    case 'FunctionCall':
      return this.evaluateFunctionCall(node.name, node.arguments, context);
    // ... other node types
  }
}

AST Node Types

ASTNode
β”œβ”€β”€ NumberLiteral      (e.g., 42, 3.14)
β”œβ”€β”€ StringLiteral      (e.g., "hello")
β”œβ”€β”€ BooleanLiteral     (true, false)
β”œβ”€β”€ PropertyReference  (e.g., prop("Column Name"))
β”œβ”€β”€ BinaryExpression   (e.g., a + b, x == y)
β”œβ”€β”€ UnaryExpression    (e.g., not x, -5)
β”œβ”€β”€ FunctionCall       (e.g., add(1, 2))
└── ConditionalExpression (ternary: a ? b : c)

Built-in Function Categories

CategoryFunctions
Mathadd, subtract, multiply, divide, mod, abs, round, floor, ceil, min, max, pow, sqrt, sign, ln, log10, exp
Textconcat, length, contains, replace, lower, upper, trim, slice, split, startsWith, endsWith, indexOf, repeat, padStart, padEnd, format, toNumber
Logicif, and, or, not, empty, coalesce, equal, unequal, larger, smaller, largerEq, smallerEq
Datenow, today, dateAdd, dateSubtract, dateBetween, formatDate, year, month, day, dayOfWeek, hour, minute, second, timestamp, date, parseDate, startOf, endOf

Performance Features

  • AST Caching: Parsed formulas are cached to avoid re-parsing identical formula strings
  • Lazy Evaluation: Conditional expressions only evaluate the taken branch
  • Depth Limiting: Configurable max evaluation depth prevents infinite recursion

Rollup Engine

The Rollup Engine (packages/core/src/rollup/) calculates aggregated values from related rows based on rollup configuration.

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Current Row    β”‚     β”‚ Related Rows    β”‚     β”‚  RollupEngine   β”‚
β”‚  (has relation) │────▢│ (via relation)  │────▢│  .calculate()   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                        β”‚
                                                        β–Ό
                                                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                                β”‚ Aggregated Valueβ”‚
                                                β”‚ (number/array)  β”‚
                                                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Supported Aggregation Types (14 total)

CategoryAggregations
Countingcount, countValues, countUnique, countEmpty, countNotEmpty
Numericsum, average, min, max
PercentagepercentEmpty, percentNotEmpty
DisplayshowOriginal, showUnique

Usage Example

const engine = new RollupEngine();
const result = engine.calculate(
  { relationColumnId: 'rel1', targetColumnId: 'price', aggregation: 'sum' },
  relatedRows,
  priceColumn
);
// result = 150 (sum of all related row prices)

View System

The View System provides multiple ways to visualize the same table data. Views are persisted and can be customized with filters, sorts, and view-specific configurations.

View Types

type ViewType = 'table' | 'board' | 'calendar' | 'gallery' | 'timeline' | 'list';

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       View Layer (React)                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”             β”‚
β”‚  β”‚ ViewSwitcherβ”‚  β”‚  useViews   β”‚  β”‚  View       β”‚             β”‚
β”‚  β”‚ (component) β”‚  β”‚  (hook)     β”‚  β”‚  Components β”‚             β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                              β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    DatabaseAdapter (Core)                        β”‚
β”‚  createView | getViews | updateView | deleteView | reorderViews β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                              β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       Storage Backend                            β”‚
β”‚           (Memory, D1, Supabase, etc.)                          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

View Data Model

interface View {
  id: string;
  tableId: string;
  name: string;
  type: ViewType;
  position: number;
  config: ViewConfig;
  isDefault?: boolean;
}

interface ViewConfig {
  filters?: FilterConfig[];
  sorts?: SortConfig[];
  hiddenColumns?: string[];
  boardConfig?: BoardViewConfig;      // For board views
  calendarConfig?: CalendarViewConfig; // For calendar views
  // ... other view-specific configs
}

useViews Hook

Located at packages/react/src/hooks/useViews.ts, provides view state management:

interface UseViewsResult {
  views: View[];
  currentView: View | null;
  isLoading: boolean;
  error: Error | null;

  // Operations
  createView: (input) => Promise<View>;
  updateView: (viewId, updates) => Promise<View>;
  deleteView: (viewId) => Promise<void>;
  reorderViews: (viewIds) => Promise<void>;
  setCurrentView: (viewId) => void;
  refresh: () => Promise<void>;
}

ViewSwitcher Component

Located at packages/react/src/components/views/ViewSwitcher.tsx, provides:

  • Tab-based view switching UI
  • Inline view renaming
  • View creation menu (all 6 view types)
  • View deletion with confirmation

BoardView (Kanban)

The BoardView (packages/react/src/components/views/BoardView.tsx) displays data as a Kanban board with drag-and-drop support.

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         BoardView                                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚
β”‚  β”‚ No Status   β”‚  β”‚ Column 1    β”‚  β”‚ Column 2    β”‚  β”‚ ...     β”‚β”‚
β”‚  β”‚ (null group)β”‚  β”‚ (option 1)  β”‚  β”‚ (option 2)  β”‚  β”‚         β”‚β”‚
β”‚  β”‚             β”‚  β”‚             β”‚  β”‚             β”‚  β”‚         β”‚β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”  β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”  β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”  β”‚  β”‚         β”‚β”‚
β”‚  β”‚  β”‚ Card  β”‚  β”‚  β”‚  β”‚ Card  β”‚  β”‚  β”‚  β”‚ Card  β”‚  β”‚  β”‚         β”‚β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚  β”‚         β”‚β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”  β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”  β”‚  β”‚             β”‚  β”‚         β”‚β”‚
β”‚  β”‚  β”‚ Card  β”‚  β”‚  β”‚  β”‚ Card  β”‚  β”‚  β”‚             β”‚  β”‚         β”‚β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚  β”‚             β”‚  β”‚         β”‚β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Drag-and-Drop Architecture

User Drag Start
      β”‚
      β–Ό
handleDragStart()
  - Set draggingRowId state
  - Set dataTransfer data
  - Apply visual feedback (opacity)
      β”‚
      β–Ό
handleDragOver()
  - Track dragOverGroup for visual feedback
  - Allow drop (preventDefault)
      β”‚
      β–Ό
handleDrop()
  - Determine new group value
  - Update cell value via onCellChange
  - Clear drag state
      β”‚
      β–Ό
handleDragEnd()
  - Reset all drag-related state
  - Restore visual styling

Configuration

interface BoardViewConfig {
  groupByColumnId: string;    // Must be select or multi_select column
  cardProperties: string[];   // Column IDs to display on cards
  showEmptyGroups?: boolean;  // Show columns with no cards
}

Key Features

  • Groups rows by select/multi-select column values
  • Drag-and-drop to change row's group value
  • "No Status" column for rows without a group value
  • Configurable card properties (which fields to display)
  • Add card button per column (pre-fills group value)

CalendarView

The CalendarView (packages/react/src/components/views/CalendarView.tsx) displays data on a date-based grid layout.

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        CalendarView                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  < >  Today              January 2024                      β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”                    β”‚
β”‚  β”‚ Sun β”‚ Mon β”‚ Tue β”‚ Wed β”‚ Thu β”‚ Fri β”‚ Sat β”‚                    β”‚
β”‚  β”œβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€                    β”‚
β”‚  β”‚     β”‚  1  β”‚  2  β”‚  3  β”‚  4  β”‚  5  β”‚  6  β”‚                    β”‚
β”‚  β”‚     β”‚Eventβ”‚     β”‚     β”‚Eventβ”‚     β”‚     β”‚                    β”‚
β”‚  β”œβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€                    β”‚
β”‚  β”‚  7  β”‚  8  β”‚  9  β”‚ 10  β”‚ 11  β”‚ 12  β”‚ 13  β”‚                    β”‚
β”‚  β”‚     β”‚     β”‚Eventβ”‚Multi-day span────│     β”‚                    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”˜                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Event Mapping Flow

Row Data                     Calendar Events
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Row 1            β”‚        β”‚                  β”‚
β”‚  startDate: 1/5  │───────▢│ Jan 5: [Event 1] β”‚
β”‚  endDate: 1/7    β”‚        β”‚ Jan 6: [Event 1] β”‚
β”‚                  β”‚        β”‚ Jan 7: [Event 1] β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€        β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Row 2            β”‚        β”‚                  β”‚
β”‚  startDate: 1/5  │───────▢│ Jan 5: [Event 2] β”‚
β”‚  endDate: null   β”‚        β”‚                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Configuration

interface CalendarViewConfig {
  dateColumnId: string;      // Column containing start date
  endDateColumnId?: string;  // Optional column for multi-day events
  showWeekends?: boolean;
}

Key Features

  • Month navigation (prev/next/today)
  • Multi-day event spanning
  • Visual distinction for current day, selected day, and out-of-month days
  • Event click handlers
  • Day click handlers (for adding new events)
  • Event count footer

Sub-items (Hierarchical Rows)

The data table supports hierarchical row structures where rows can have parent-child relationships, similar to Notion's sub-items feature.

Data Model

interface Row {
  id: string;
  tableId: string;
  parentRowId?: string; // For sub-items/hierarchical rows
  cells: Record<string, CellValue>;
  // ...
}

Configuration

interface SubItemsConfig {
  enabled: boolean;
  displayMode?: 'nested' | 'flat'; // nested shows hierarchy, flat shows all
  filterMode?: 'all' | 'parents' | 'subitems'; // What to show
  collapsedParents?: string[]; // Parent row IDs that are collapsed
}

Query Options

interface RowQueryOptions {
  // ...
  parentRowId?: string | null; // null = top-level only, undefined = all, string = children of parent
  includeSubItems?: boolean; // Include all sub-items recursively
}

Creating Sub-items

interface CreateRowInput {
  tableId: string;
  parentRowId?: string; // For creating sub-items
  cells?: Record<string, CellValue>;
}

Display Modes

ModeDescription
nestedShows hierarchy with indentation, parent rows have expand/collapse
flatShows all rows at the same level, ignores hierarchy

Filter Modes

ModeDescription
allShow all rows (parents and sub-items)
parentsShow only top-level rows (no sub-items)
subitemsShow only sub-items (no top-level rows)

Grouping System

The grouping system allows rows to be organized into collapsible groups based on select or multi-select column values.

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        TableView                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ GroupHeader (Group A)                              [β–Ό] 5  β”‚  β”‚
β”‚  β”‚  β”œβ”€β”€ Row 1                                                β”‚  β”‚
β”‚  β”‚  β”œβ”€β”€ Row 2                                                β”‚  β”‚
β”‚  β”‚  └── Row 3                                                β”‚  β”‚
β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  β”‚
β”‚  β”‚ GroupHeader (Group B)                              [β–Ά] 3  β”‚  β”‚
β”‚  β”‚  (collapsed - rows hidden)                                β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

GroupHeader Component

Located at packages/react/src/components/GroupHeader.tsx:

interface GroupHeaderProps {
  label: string;           // Group name (option label)
  rowCount: number;        // Number of rows in group
  isCollapsed: boolean;    // Collapse state
  onToggleCollapse: () => void;
  colSpan: number;         // Table columns to span
  className?: string;
}

Features

  • Collapsible groups: Click header to expand/collapse
  • Row count badge: Shows number of rows in each group
  • Visual feedback: Chevron rotates to indicate state
  • "No Status" group: Rows without a group value appear in a special group

Grouping Configuration

Grouping is configured through the view config:

interface ViewConfig {
  // ...
  groupByColumnId?: string; // Must be select or multi_select column
}

Usage

  1. Add a select or multi_select column to your table
  2. Configure the view with groupByColumnId pointing to that column
  3. Rows are automatically grouped by their value in that column
  4. Rows with no value appear in the "No Status" group