yk-grid
v0.1.4
Published
Production-ready AI-assisted React DataGrid component with sorting, filtering, grouping, pagination, virtual scrolling, and optional natural-language query input
Downloads
551
Maintainers
Readme
yk-grid

A production-ready React DataGrid component with built-in sorting, filtering, grouping, pagination, selection, column management, CSV export, virtual scrolling, inline cell editing, and optional AI-assisted natural-language query input.
Dual-format library (ESM + CJS) with full TypeScript generics. Zero runtime dependencies beyond React, Zod, and @tanstack/react-virtual.
Features
- Sorting — multi-column, asc/desc, shift-click to stack
- Filtering — funnel icon per column; text, number (with operator picker), select (searchable checkboxes), and date filter types
- Grouping — nest rows by one or more columns with collapse/expand
- Aggregations — sum, avg, count, min, max per group
- Pagination — client-side or server-side, with configurable page sizes
- Virtual scrolling — renders only visible rows for large datasets (pass
height) - Row selection — single or multiple, with page or filtered-set select-all scope
- Inline cell editing — double-click any
editablecell; Enter/Tab commits, Escape cancels - Column resizing — drag column edges to resize
- Column visibility — show/hide columns via per-column menu
- CSV export — export visible or selected rows
- Custom toolbar actions — inject buttons or controls into the toolbar via a render prop
- AI query input — optional natural-language command bar backed by your own endpoint
- Imperative ref API — programmatically read state, clear selection, trigger export
Install
npm install yk-gridPeer dependencies:
npm install react react-dom zodImport the library stylesheet once in your app entry point:
import 'yk-grid/dist/yk-grid.css';Quick start
import 'yk-grid/dist/yk-grid.css';
import { DataGrid } from 'yk-grid';
import type { ColumnDef } from 'yk-grid';
interface User {
id: string;
name: string;
email: string;
age: number;
status: 'active' | 'inactive';
}
const columns: ColumnDef<User>[] = [
{ id: 'name', header: 'Name', accessor: r => r.name, sortable: true, filterable: true },
{ id: 'email', header: 'Email', accessor: r => r.email, sortable: true, filterable: true },
{ id: 'age', header: 'Age', accessor: r => r.age, sortable: true, filterType: 'number', editable: true },
{ id: 'status', header: 'Status', accessor: r => r.status, sortable: true, filterType: 'select' },
];
export default function App() {
return (
<DataGrid<User>
data={users}
columns={columns}
getRowId={r => r.id}
dataMode="client"
pageSize={50}
height={600}
selectionMode="multiple"
enableColumnResize
enableColumnVisibility
onCellEdit={(value, row, col) => console.log(col.id, row.id, '→', value)}
/>
);
}Column definition (ColumnDef<T>)
Every column is defined with a ColumnDef<T> object.
| Property | Type | Description |
| --------------- | ---------------------------------------------- | ------------------------------------------------------------------- |
| id | string | Unique column identifier |
| header | string | Column header label |
| accessor | (row: T) => string \| number \| Date \| null | Extracts the raw cell value from the row |
| cell | (value, row: T) => ReactNode | Optional custom cell renderer |
| exportValue | (row: T) => string \| number | Override the value used in CSV export |
| sortable | boolean | Enable sort on this column |
| filterable | boolean | Show the funnel filter button for this column |
| filterType | 'text' \| 'number' \| 'select' \| 'date' | Filter panel variant. Defaults to 'text' |
| filterOptions | string[] | Static options for filterType: 'select' |
| groupable | boolean | Allow this column to be used as a grouping key |
| aggregation | 'sum' \| 'avg' \| 'count' \| 'min' \| 'max' | Aggregation shown in group header rows |
| editable | boolean | Double-click to edit the cell inline |
| width | number | Default column width in pixels |
| minWidth | number | Minimum width when resizing (default: 50) |
| resizable | boolean | Override per-column resize (default: inherits enableColumnResize) |
| hideable | boolean | Whether this column can be hidden in the visibility picker |
| defaultHidden | boolean | Start the column hidden |
Props
Required
| Prop | Type | Description |
| ---------- | -------------------- | ------------------------------ |
| data | T[] | Row data |
| columns | ColumnDef<T>[] | Column definitions |
| getRowId | (row: T) => string | Unique row identifier function |
Data & loading
| Prop | Type | Default | Description |
| --------------- | ---------------------------- | ---------- | ------------------------------------------------------------------------------------------ |
| dataMode | 'client' \| 'server' | 'server' | client handles sort/filter/page locally; server delegates those to your API |
| pageSize | number | 20 | Initial rows per page |
| rowCount | number | — | Total row count for server-side pagination |
| loading | boolean | false | Shows a loading overlay |
| onStateChange | (state: GridState) => void | — | Fires when sorts, filters, grouping, or pagination change — in both client and server mode |
| initialState | Partial<GridState> | — | Seed initial sorts, filters, grouping, pagination, etc. |
Selection
| Prop | Type | Default | Description |
| ------------------- | ------------------------------------ | -------- | ------------------------------------ |
| selectionMode | 'none' \| 'single' \| 'multiple' | 'none' | Row selection behaviour |
| selectAllScope | 'page' \| 'filtered' | 'page' | What the select-all checkbox targets |
| onSelectionChange | (rows: T[], ids: string[]) => void | — | Fires whenever the selection changes |
Click handlers
| Prop | Type | Description |
| ------------- | -------------------------------------------------------------- | ----------------------------------------- |
| onRowClick | (row: T, index: number, e: MouseEvent) => void | Called when a data row is clicked |
| onCellClick | (value, row: T, column: ColumnDef<T>, e: MouseEvent) => void | Called when an individual cell is clicked |
Column features
| Prop | Type | Default | Description |
| ------------------------ | --------- | ------- | --------------------------------------- |
| enableColumnResize | boolean | false | Drag-to-resize column widths |
| enableColumnVisibility | boolean | false | Show/hide column picker in header menus |
Filtering
| Prop | Type | Description |
| -------------------- | ----------------------------------------- | ------------------------------------------------------------- |
| fetchFilterOptions | (columnId: string) => Promise<string[]> | Server mode: fetch options for filterType: 'select' columns |
Virtual scrolling
| Prop | Type | Default | Description |
| -------------------- | ------------------ | ------- | ----------------------------------------------------------------- |
| height | number \| string | — | Fixed height activates virtual scrolling (e.g. 600 or '80vh') |
| estimatedRowHeight | number | 41 | Row height hint for the virtualiser; affects scroll accuracy |
Inline editing
| Prop | Type | Description |
| ------------ | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| onCellEdit | (newValue: string \| number, row: T, column: ColumnDef<T>) => void | Called when a cell edit is committed. Set editable: true on columns you want editable. |
Export
| Prop | Type | Default | Description |
| ----------------- | --------- | -------------- | --------------------------------- |
| enableCsvExport | boolean | false | Adds CSV export button to toolbar |
| csvFilename | string | 'export.csv' | Downloaded file name |
Toolbar
| Prop | Type | Description |
| ---------------- | ----------------------------------- | ------------------------------------- |
| toolbarActions | (ctx: ToolbarCtx<T>) => ReactNode | Render prop injected into the toolbar |
ToolbarCtx<T> contains: selectedRows, selectedIds, processedRows, gridState, clearSelection.
AI
| Prop | Type | Description |
| ---- | -------------------------------------------- | ---------------------------------------- |
| ai | { endpoint: string; placeholder?: string } | Enables the natural-language command bar |
Display
| Prop | Type | Description |
| ------------ | ----------- | ----------------------------------------- |
| emptyState | ReactNode | Custom content when there are no rows |
| className | string | Class applied to the root wrapper element |
Imperative ref (GridRef<T>)
Pass a ref to read state and trigger actions programmatically:
import { useRef } from 'react'
import { DataGrid, GridRef } from 'yk-grid'
const gridRef = useRef<GridRef<User>>(null)
<DataGrid ref={gridRef} ... />| Method | Returns | Description |
| -------------------- | ----------- | ---------------------------------------------------------------- |
| getSelectedRows() | T[] | Currently selected row objects |
| getProcessedRows() | T[] | All rows after filtering (ignores pagination) |
| getGridState() | GridState | Full current grid state snapshot |
| clearSelection() | void | Clear all selected rows |
| exportCsv(opts?) | void | Trigger CSV download. opts.selectedOnly exports selection only |
| setState(partial) | void | Programmatically set sorts, filters, or grouping |
Theming
All visual properties are controlled via CSS custom properties on :root (or any ancestor element):
| Variable | Default | Description |
| ----------------------- | ------------------- | ------------------------------------------------------------ |
| --grid-font-size | 0.875rem | Base font size |
| --grid-border-colour | #e2e8f0 | Cell/row border colour |
| --grid-header-bg | #f1f5f9 | Header and toolbar background |
| --grid-row-hover-bg | #f8fafc | Row hover background |
| --grid-selected-bg | #eef2ff | Selected row background |
| --grid-accent | #6366f1 | Accent colour (focus rings, active filters, sort indicators) |
| --grid-focus-ring | 0 0 0 2px #6366f1 | Focus ring box-shadow |
| --grid-radius | 0.5rem | Border radius of the outer wrapper |
| --grid-cell-padding | 0.625rem 0.875rem | Cell padding (shorthand) |
| --grid-cell-padding-y | 0.625rem | Vertical cell padding |
| --grid-cell-padding-x | 0.875rem | Horizontal cell padding |
| --grid-toolbar-gap | 0.5rem | Toolbar item gap |
Example — dark theme:
.my-dark-wrapper {
--grid-border-colour: #334155;
--grid-header-bg: #1e293b;
--grid-row-hover-bg: #1e293b;
--grid-selected-bg: #312e81;
--grid-accent: #818cf8;
}Server mode
In dataMode="server", the grid delegates sorting, filtering, grouping, and pagination to your API. onStateChange fires whenever any of those change — use it to trigger a fetch:
const [data, setData] = useState<User[]>([]);
const [rowCount, setRowCount] = useState(0);
const [loading, setLoading] = useState(false);
async function fetchData(state: GridState) {
setLoading(true);
const res = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify(state),
});
const json = await res.json();
setData(json.rows);
setRowCount(json.total);
setLoading(false);
}
<DataGrid<User>
dataMode="server"
data={data}
columns={columns}
getRowId={r => r.id}
rowCount={rowCount}
loading={loading}
onStateChange={fetchData}
/>;Chart integration
yk-grid ships a small set of data-shaping helpers in a separate subpath so chart code stays tree-shakeable and the core bundle stays clean. You bring your own chart library — the example below uses AG-Charts Community (MIT, canvas-based), but the helpers work with any library that accepts a flat data array.
npm install ag-charts-react ag-charts-communityRegister the AG-Charts module once at your app entry point:
import { AllCommunityModule, ModuleRegistry } from 'ag-charts-community';
ModuleRegistry.registerModules([AllCommunityModule]);Then wire the chart to the grid's filtered rows:
import { useRef, useState, useMemo, useCallback } from 'react';
import { AgCharts } from 'ag-charts-react';
import type { AgChartOptions } from 'ag-charts-community';
import { DataGrid } from 'yk-grid';
import { projectRows } from 'yk-grid/chart';
import type { GridRef } from 'yk-grid';
export default function Page() {
const gridRef = useRef<GridRef<Transaction>>(null);
const [chartRows, setChartRows] = useState<Transaction[]>(allData);
const handleStateChange = useCallback(() => {
setChartRows(gridRef.current?.getProcessedRows() ?? []);
}, []);
const chartOptions = useMemo<AgChartOptions>(() => ({
data: projectRows(chartRows, columns, { columnIds: ['amount', 'revenue'] }),
series: [{ type: 'scatter', xKey: 'amount', yKey: 'revenue' }],
}), [chartRows]);
return (
<>
<DataGrid<Transaction>
ref={gridRef}
data={allData}
columns={columns}
getRowId={r => r.id}
dataMode="client"
onStateChange={handleStateChange}
/>
<AgCharts options={chartOptions} />
</>
);
}projectRows maps filtered grid rows to { [columnId]: value } objects — the flat shape chart libraries expect. Null values are omitted; Date values are coerced to ISO strings by default (pass { dateFormat: 'epoch' } for time-axis charts). The optional columnIds subset keeps the projected objects lean.
For simple bar/line/area/scatter charts, buildChartOptions assembles both data and series in one call:
import { buildChartOptions } from 'yk-grid/chart';
import type { AgChartOptions } from 'ag-charts-community';
const spec = buildChartOptions({ rows: chartRows, columns, xColumnId: 'date', yColumnIds: ['revenue'] });
// spec is a ChartSpec — cast if your chart lib requires the stricter type
<AgCharts options={spec as AgChartOptions} />Note:
onStateChangefires in bothclientandservermode. In server mode,getProcessedRows()returns the current page of data (the server has already filtered); the chart will reflect only the loaded page.
AI integration
The AI bar accepts natural-language queries and translates them into grid actions (sort, filter, group) via your endpoint.
<DataGrid
ai={{
endpoint: '/api/grid-ai',
placeholder: 'e.g. "show failed refunds over £200, sorted by amount"',
}}
...
/>Wire up the server handler (Express or Next.js App Router):
// Express
import { handleGridAiRequest } from 'yk-grid/server';
app.post('/api/grid-ai', async (req, res) => {
const result = await handleGridAiRequest(req.body);
res.json(result);
});
// Next.js App Router
import { handleGridAiRequest } from 'yk-grid/server';
export async function POST(req: Request) {
const body = await req.json();
const result = await handleGridAiRequest(body);
return Response.json(result);
}Set ANTHROPIC_API_KEY in your environment. To use OpenAI instead, set LLM_PROVIDER=openai and OPENAI_API_KEY.
Development
npm run dev:example # Vite dev server at localhost:5173 (example app + AI middleware)
npm test # vitest single pass
npm run test:watch # vitest watch
npm run typecheck # tsc --noEmit
npm run build # typecheck + vite build → dist/
npx playwright test # E2E smoke tests (requires: npx playwright install chromium)Set ANTHROPIC_API_KEY in example/.env.local to use the AI bar during development.
Licence
MIT
