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

@alphinex/table

v1.2.1

Published

Enterprise DataGrid: headless engine + styled renderer.

Readme

@alphinex/table

Enterprise DataGrid: a headless useDataGrid() engine — a thin, opinionated wrapper over TanStack Table (see ADR-0003) — plus <DataGrid>, the styled renderer built on top of it, complete with toolbar, pagination, column-visibility, and row selection. useDataGrid() returns the real TanStack Table<TData> instance, so anything TanStack Table exposes is available if you want to build fully custom rendering instead of using <DataGrid>.

useDataGrid

The headless engine. Give it data and columns (standard TanStack ColumnDef[]) and it manages sorting, column filters, a global filter, pagination, row selection, and column visibility as internal state, wiring them into a useReactTable() instance:

import { useDataGrid, type ColumnDef } from "@alphinex/table";
import { flexRender } from "@tanstack/react-table";

interface Invoice {
  id: string;
  customer: string;
  amount: number;
}

const columns: ColumnDef<Invoice, unknown>[] = [
  { accessorKey: "id", header: "Invoice" },
  { accessorKey: "customer", header: "Customer" },
  { accessorKey: "amount", header: "Amount" },
];

function CustomInvoiceTable({ data }: { data: Invoice[] }) {
  const table = useDataGrid({ data, columns, defaultPageSize: 25 });

  return (
    <table>
      <thead>
        {table.getHeaderGroups().map((hg) => (
          <tr key={hg.id}>
            {hg.headers.map((header) => (
              <th key={header.id} onClick={header.column.getToggleSortingHandler()}>
                {flexRender(header.column.columnDef.header, header.getContext())}
              </th>
            ))}
          </tr>
        ))}
      </thead>
      <tbody>
        {table.getRowModel().rows.map((row) => (
          <tr key={row.id}>
            {row.getVisibleCells().map((cell) => (
              <td key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

Key options (UseDataGridOptions<TData>):

  • mode?: "client" | "server" (default "client") — in "server" mode, sorting/filtering/ pagination stop running locally (manualSorting/manualFiltering/manualPagination) and onQueryChange fires with the current { sorting, columnFilters, globalFilter, pagination } whenever any of them changes, so the app can refetch.
  • rowCount?: number — total row count across all pages; required for correct pagination math in "server" mode.
  • selection?: "none" | "single" | "multiple" (default "none") — enables/limits TanStack's row-selection state.
  • getRowId?: (row, index) => string and defaultPageSize?: number (default 10).

DataGrid

The styled renderer: everything useDataGrid() leaves as an implementation detail, wired up with @alphinex/ui primitives and semantic tokens. It renders as an ARIA grid over divs (not a native <table>, so rows can be virtualized reliably), auto-virtualizes once there are more than ~50 rows, and includes a toolbar and pagination footer by default:

import { DataGrid } from "@alphinex/table";
import type { ColumnDef } from "@tanstack/react-table";
import { Badge } from "@alphinex/ui";
import { formatCurrency } from "@alphinex/utils";

interface Invoice {
  id: string;
  customer: string;
  amount: number;
  status: "paid" | "pending" | "overdue";
}

const columns: ColumnDef<Invoice, unknown>[] = [
  { accessorKey: "id", header: "Invoice" },
  { accessorKey: "customer", header: "Customer" },
  {
    accessorKey: "amount",
    header: "Amount",
    cell: ({ getValue }) => formatCurrency(getValue<number>()),
  },
  {
    accessorKey: "status",
    header: "Status",
    cell: ({ getValue }) => <Badge size="sm">{getValue<string>()}</Badge>,
  },
];

function InvoiceGrid({ invoices }: { invoices: Invoice[] }) {
  return (
    <DataGrid
      data={invoices}
      columns={columns}
      selection="multiple"
      defaultPageSize={10}
      searchPlaceholder="Search invoices…"
    />
  );
}

DataGridProps<TData> extends UseDataGridOptions<TData> and adds rendering concerns: isLoading? (renders skeleton rows), emptyState? (node shown for zero rows), virtualized? (force on/off — default is auto above the 50-row threshold), maxHeight? (scroll-container height while virtualized, default "24rem"), toolbar? (pass false to hide it, or a node to replace the default DataGridToolbar), searchPlaceholder?, pageSizeOptions?, and showPagination? (pass false when the app paginates externally).

Column sorting comes for free: any column without enableSorting: false gets a clickable header with a sort-direction chevron, driven by TanStack's getToggleSortingHandler().

DataGridToolbar, DataGridPagination, ColumnVisibilityMenu

These are the pieces <DataGrid> composes internally — exported individually so you can reuse them in a custom layout (e.g. next to useDataGrid()), or pass your own toolbar node to <DataGrid> while keeping the default pagination:

import {
  useDataGrid,
  DataGridToolbar,
  DataGridPagination,
  ColumnVisibilityMenu,
} from "@alphinex/table";

function CustomToolbarGrid({
  data,
  columns,
}: {
  data: Invoice[];
  columns: ColumnDef<Invoice, unknown>[];
}) {
  const table = useDataGrid({ data, columns });

  return (
    <div>
      <DataGridToolbar table={table} searchPlaceholder="Filter…" />
      {/* custom row rendering using `table` */}
      <DataGridPagination table={table} pageSizeOptions={[10, 25, 50]} />
    </div>
  );
}

DataGridToolbar renders a global-filter Input plus ColumnVisibilityMenu (a minimal anchored dropdown — not the general-purpose @alphinex/ui Menu, per ADR-0001 — that lists any column with enableHiding not disabled and toggles it). DataGridPagination renders the page-size Select, page-count label, and previous/next buttons, driven by table.previousPage() / table.nextPage() / table.setPageSize().

createSelectionColumn

Builds the checkbox column <DataGrid> prepends when selection isn't "none". It lives outside useDataGrid() deliberately: headless consumers building fully custom rendering add their own selection column (or don't) rather than getting one automatically.

import { createSelectionColumn } from "@alphinex/table";

const selectionColumn = createSelectionColumn<Invoice>("multiple"); // or "single" / "none" (→ null)
const columnsWithSelection = selectionColumn ? [selectionColumn, ...columns] : columns;

CSV export

DataGridToolbar shows an "Export CSV" button by default (enableCsvExport={false} to hide it, csvFilename to name the download — both also available as <DataGrid> props). It exports the currently filtered/sorted rows for the currently visible columns, not just the current page:

<DataGrid data={invoices} columns={columns} csvFilename="invoices.csv" />

The underlying pieces are exported too, if you want a custom export trigger: exportTableToCsv(table, filename?) (downloads directly) or tableToCsv(table) (just returns the CSV string).

CSV import — CsvImportDialog

A Dialog composite (Sprint 27) for CSV import with inline, per-row validation — see ADR-0020 (CSV only, no .xlsx support yet):

import { CsvImportDialog } from "@alphinex/table";
import { z } from "zod";

const contactSchema = z.object({
  Name: z.string().min(1, "Name is required"),
  Role: z.enum(["admin", "member"]),
});

<CsvImportDialog
  isOpen={isImportOpen}
  onClose={() => setIsImportOpen(false)}
  schema={contactSchema}
  onImport={(rows) => saveContacts(rows)} // only the rows that passed validation
/>;

The schema's field names must match the CSV's header row. Rows that fail schema.safeParse() are listed inline with their row number and error message and excluded from onImport — this package has no persistence opinion, onImport receives validated data and the app decides where it goes.

Column reorder

Drag any column's grab handle in the ColumnVisibilityMenu ("Columns" button in the default toolbar) to reorder it — uses @dnd-kit/sortable, the same library @alphinex/kanban already adopted (ADR-0011), wired through TanStack Table's columnOrder state. No extra setup needed — it's part of useDataGrid()/<DataGrid> already.

See documentation/ARCHITECTURE.md for the full package contract, dependency rules, and roadmap placement.