| title | Plugin Grid |
|---|
import { InteractiveDemo } from '@/app/components/InteractiveDemo'; import { PluginLoader } from '@/app/components/PluginLoader';
Advanced data grid with sorting, filtering, pagination, and row selection capabilities.
npm install @object-ui/plugin-grid<PluginLoader plugins={['grid']}>
- Sorting - Multi-column sorting support
- Filtering - Column-level filtering
- Pagination - Built-in pagination controls
- Row Selection - Single and multi-row selection
- Custom Cells - Custom cell renderers
- Responsive - Mobile-friendly layouts
{
type: 'grid',
columns: GridColumn[],
data?: any[],
sortable?: boolean,
filterable?: boolean,
pagination?: boolean | PaginationConfig,
selectable?: boolean,
onRowClick?: (row) => void,
className?: string
}
interface GridColumn {
header: string;
accessorKey: string;
sortable?: boolean;
filterable?: boolean;
width?: number | string;
align?: 'left' | 'center' | 'right';
cell?: (value, row) => ReactNode;
}
A column can declare a footer aggregation with summary, either as a shorthand
string or as an object that aggregates a different field than the one displayed:
{
"columns": [
{ "field": "name", "summary": "count_filled" },
{ "field": "amount", "type": "currency", "summary": "sum" },
{ "field": "owner", "summary": { "type": "count_unique", "field": "owner_id" } }
]
}The accepted values are ColumnSummarySchema from @objectstack/spec:
summary |
Footer shows | Reads |
|---|---|---|
none |
nothing — the column opts out | — |
count |
number of rows | every row |
count_filled |
rows whose cell is non-empty | raw values |
count_empty |
rows whose cell is empty | raw values |
count_unique |
distinct non-empty values | raw values |
percent_filled |
share of rows that are non-empty | raw values |
percent_empty |
share of rows that are empty | raw values |
sum |
total | numeric values |
avg |
mean | numeric values |
min |
smallest | numeric values |
max |
largest | numeric values |
A cell counts as empty when it is null, undefined, "" or an empty array,
so an unset multi-select or lookup reads as empty rather than as a filled [].
The count and percent families read raw cell values, so they work on text,
select and lookup columns. sum/avg/min/max need numeric values (numeric
strings are parsed) and render nothing when the column has none.
A currency or percent column formats its sum/avg/min/max in that
unit. Counts stay plain cardinalities and percentages carry their own %, so
count_unique on a currency column reads Unique: 3, not $3.00.
The footer row renders only when at least one column resolves to a summary — a
view whose columns are all none (or carry no summary) has no footer.
import '@object-ui/plugin-grid';
import { gridComponents } from '@object-ui/plugin-grid';
import { ComponentRegistry } from '@object-ui/core';
Object.entries(gridComponents).forEach(([type, component]) => {
ComponentRegistry.register(type, component);
});
{
"type": "grid",
"columns": [
{ "header": "Name", "accessorKey": "name" },
{
"header": "Status",
"accessorKey": "status",
"cell": {
"type": "badge",
"label": "${value}",
"variant": "${value === 'Active' ? 'success' : 'default'}"
}
},
{
"header": "Actions",
"accessorKey": "id",
"cell": {
"type": "button-group",
"buttons": [
{ "label": "Edit" },
{ "label": "Delete", "variant": "destructive" }
]
}
}
]
}{
"type": "grid",
"columns": [...],
"data": [...],
"selectable": true,
"onSelectionChange": "handleSelection"
}{
"type": "grid",
"columns": [...],
"data": [...],
"pagination": {
"pageSize": 10,
"showSizeChanger": true,
"pageSizeOptions": [10, 20, 50, 100]
}
}{
"type": "object-grid",
"object": "users",
"columns": [
{ "header": "Name", "accessorKey": "name" },
{ "header": "Email", "accessorKey": "email" },
{ "header": "Created", "accessorKey": "created_at" }
],
"pagination": true,
"sortable": true,
"filterable": true
}Enable sorting on all or specific columns:
{
"type": "grid",
"sortable": true,
"columns": [
{ "header": "Name", "accessorKey": "name", "sortable": true },
{ "header": "Email", "accessorKey": "email", "sortable": false }
]
}Enable column-level filtering:
{
"type": "grid",
"filterable": true,
"columns": [
{ "header": "Status", "accessorKey": "status", "filterable": true }
]
}Add action buttons to each row:
{
"type": "grid",
"rowActions": [
{ "label": "View", "onClick": "viewRow" },
{ "label": "Edit", "onClick": "editRow" },
{ "label": "Delete", "onClick": "deleteRow" }
]
}Enable inline cell editing for quick data updates:
{
"type": "object-grid",
"objectName": "users",
"columns": [
{ "header": "ID", "accessorKey": "id", "editable": false },
{ "header": "Name", "accessorKey": "name" },
{ "header": "Email", "accessorKey": "email" },
{ "header": "Status", "accessorKey": "status" }
],
"editable": true,
"onCellChange": "handleCellChange"
}Features:
- Double-click or press Enter on a cell to start editing
- Press Enter to save changes
- Press Escape to cancel editing
- Column-level control: Set
editable: falseon specific columns to prevent editing - Callback support: Use
onCellChangeto handle cell value updates
Example Handler:
function handleCellChange(rowIndex, columnKey, newValue, row) {
console.log(`Cell at row ${rowIndex}, column ${columnKey} changed to:`, newValue);
console.log('Full row data:', row);
// Update your data source here
}Edit multiple cells across multiple rows and save them individually or all at once:
{
"type": "object-grid",
"objectName": "products",
"columns": [
{ "header": "SKU", "accessorKey": "sku", "editable": false },
{ "header": "Name", "accessorKey": "name" },
{ "header": "Price", "accessorKey": "price" }
],
"editable": true,
"rowActions": true,
"onRowSave": "handleRowSave",
"onBatchSave": "handleBatchSave"
}Features:
- Pending changes tracking: Edit multiple cells across rows before saving
- Visual indicators: Modified rows highlighted in amber, modified cells in bold
- Row-level save/cancel: Individual row save and cancel buttons
- Batch operations: Save All and Cancel All buttons for bulk actions
- Flexible callbacks:
onRowSavefor single row,onBatchSavefor multiple rows
Example Handlers:
function handleRowSave(rowIndex, changes, row) {
console.log('Saving row:', rowIndex, changes);
// Save single row to backend
await dataSource.update(row.id, changes);
}
function handleBatchSave(allChanges) {
console.log('Batch saving:', allChanges.length, 'rows');
// Save all changes at once
await Promise.all(
allChanges.map(({ row, changes }) =>
dataSource.update(row.id, changes)
)
);
}import type { GridSchema, GridColumn } from '@object-ui/plugin-grid';
const grid: GridSchema = {
type: 'grid',
columns: [...],
data: [],
sortable: true,
pagination: true
};
MIT