npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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

Readme

yk-grid

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 editable cell; 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-grid

Peer dependencies:

npm install react react-dom zod

Import 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-community

Register 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: onStateChange fires in both client and server mode. 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