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

@izumisy-tailor/tailor-data-viewer

v0.3.4

Published

Flexible data viewer component for Tailor Platform

Downloads

3,200

Readme

Tailor Data Viewer

A low-level React component library for building data table interfaces with Tailor Platform (GraphQL) backends. Provides composable hooks and compound components for query parameter management, table rendering, filtering, sorting, and pagination.

Features

  • Separation of Concerns: Data fetching, query parameter management, and UI are fully decoupled
  • useCollectionVariables Hook: Manages filter, sort, and pagination state; outputs Tailor Platform-compatible GraphQL variables
  • CollectionControlProvider: Shares collection control state via React Context across sibling components
  • Table.* Compound Components: Static, unstyled table primitives (<table>, <thead>, <tbody>, <tr>, <th>, <td>)
  • DataTable.* Compound Components: Data-bound table with sort indicators, cell renderers, and useDataTable integration
  • useDataTable Hook: Integrates data, column visibility, row operations (optimistic updates), and props generators
  • Column Definition Helper: createColumnHelper<TRow>() returns { column, inferColumns } with the row type bound
  • Metadata-based Inference: inferColumns() auto-derives sort/filter config from generated table metadata
  • Utility Components: ColumnSelector, CsvButton, SearchFilterForm, Pagination — all props-based, spreadable from hooks
  • Multi-sort Support: Multiple simultaneous sort fields
  • Optimistic Updates: updateRow, deleteRow, insertRow with rollback
  • Presentation Agnostic: Same useCollectionVariables can drive tables, kanbans, calendars, etc.

Installation

npm install @izumisy-tailor/tailor-data-viewer
# or
pnpm add @izumisy-tailor/tailor-data-viewer
# or
yarn add @izumisy-tailor/tailor-data-viewer

For AI Users

If you're using AI coding assistants (Claude Code, Cursor, GitHub Copilot, etc.), we strongly recommend installing the Data Viewer skill:

npx skills add [email protected]:tailor-sandbox/tailor-data-viewer.git

This provides AI-optimized documentation for better code generation and assistance.

Peer Dependencies

npm install react react-dom

Quick Start

import {
  useCollectionVariables,
  useDataTable,
  DataTable,
  Pagination,
  createColumnHelper,
} from "@izumisy-tailor/tailor-data-viewer/component";
import "@izumisy-tailor/tailor-data-viewer/styles/theme.css";

// 1. Define columns
const { column } = createColumnHelper<Order>();

const columns = [
  column({
    label: "Name",
    render: (row) => row.name,
    accessor: (row) => row.name,
    sort: { field: "name", type: "string" },
    filter: { field: "name", type: "string" },
  }),
  column({
    label: "Amount",
    render: (row) => String(row.amount),
    accessor: (row) => row.amount,
    sort: { field: "amount", type: "number" },
    filter: { field: "amount", type: "number" },
  }),
  column({
    label: "Status",
    render: (row) => row.status,
    filter: {
      field: "status",
      type: "enum",
      options: [
        { value: "DRAFT", label: "Draft" },
        { value: "APPROVED", label: "Approved" },
      ],
    },
  }),
  column({
    id: "actions",
    label: "Actions",
    width: 50,
    render: (row) => <button onClick={() => handleEdit(row)}>Edit</button>,
  }),
];

// 2. Build a page
function OrdersPage() {
  const { variables, control } = useCollectionVariables({ params: { pageSize: 20 } });
  const [result] = useQuery({
    query: GET_ORDERS,
    variables: {
      ...variables.pagination,
      query: variables.query,
      order: variables.order
    }
  });

  const table = useDataTable<Order>({
    columns,
    data: result.data?.orders,
    loading: result.fetching,
    control,
  });

  return (
    <DataTable.Provider value={table}>
      <DataTable.Root>
        <DataTable.Headers />
        <DataTable.Body />
      </DataTable.Root>
      <Pagination />
    </DataTable.Provider>
  );
}

API Overview

useCollectionVariables(options)

Manages filter, sort, and pagination state. Returns variables containing query, order, and pagination sub-properties in Tailor Platform-compatible format.

const { variables, control } = useCollectionVariables({
  params: {
    pageSize: 20,
    initialSort: [{ field: "createdAt", direction: "Desc" }],
  },
});

// Use variables in your query
const [result] = useQuery({
  query: GET_ORDERS,
  variables: {
    ...variables.pagination,
    query: variables.query,
    order: variables.order,
  },
});

// Filter operations
control.addFilter("status", "eq", "ACTIVE");
control.setFilters([{ field: "status", operator: "eq", value: "ACTIVE" }]);
control.removeFilter("status");
control.clearFilters();

// Sort operations
control.setSort("createdAt", "Desc");
control.setSort("name", "Asc", true); // append for multi-sort
control.clearSort();

// Pagination
control.nextPage(endCursor);
control.prevPage(startCursor);
control.resetPage();

// Page info tracking
control.setPageInfo(pageInfo);
control.hasPrevPage;  // boolean
control.hasNextPage;  // boolean

DataTable.Provider / useDataTableContext() / useCollectionControl()

DataTable.Provider wraps the table UI and provides both data table and collection control context. All utility components (Pagination, ColumnSelector, CsvButton, SearchFilterForm) read from this context — no prop spreading needed.

When control is passed to useDataTable, DataTable.Provider automatically wraps a CollectionControlProvider so child components can use useCollectionControl().

<DataTable.Provider value={table}>
  <StatusFilter />   {/* useCollectionControl() inside */}
  <DataTable.Root>
    <DataTable.Headers />
    <DataTable.Body />
  </DataTable.Root>
  <Pagination />
</DataTable.Provider>

For cases where you need CollectionControlProvider without DataTable.Provider (e.g., non-table UIs), you can use it standalone:

<CollectionControlProvider value={control}>
  <StatusFilter />
  <CustomKanbanBoard />
</CollectionControlProvider>

Column Definition Helper

column(options)

Defines a column with required label and render. Optionally supports sort, filter, accessor, width, and id.

column<Order>({
  label: "Name",
  render: (row) => <strong>{row.name}</strong>,
  sort: { field: "name", type: "string" },
  filter: { field: "name", type: "string" },
})

Table Metadata Generator

This library includes a metadata generator for Tailor Platform SDK that produces type-safe table metadata with as const assertions. The generated metadata is used by inferColumns() for automatic sort/filter configuration.

  1. Configure the generator in your tailor.config.ts:
import { defineConfig, defineGenerators } from "@tailor-platform/sdk";
import { dataViewerMetadataGenerator } from "@izumisy-tailor/tailor-data-viewer/generator";

export const generators = defineGenerators(
  dataViewerMetadataGenerator({
    distPath: "src/generated/data-viewer-metadata.generated.ts",
  }),
);

export default defineConfig({
  name: "my-app",
  // ... your config
});
  1. Run the generator:
tailor-sdk generate

inferColumns(tableMetadata)

column() requires manually specifying sort/filter type configs and enum options for every column. inferColumns() eliminates this boilerplate by automatically deriving these from the generated table metadata. Based on each field's type (string, number, date, enum, etc.), the appropriate SortConfig / FilterConfig is set automatically, and enum fields get their options populated from the schema.

import { createColumnHelper } from "@izumisy-tailor/tailor-data-viewer/component";
import { tableMetadata } from "./generated/data-viewer-metadata.generated";

const { column, inferColumns } = createColumnHelper<Task>();
const infer = inferColumns(tableMetadata.task);

const taskColumns = [
  column(infer("title")),                            // sort/filter auto-configured from metadata
  column(infer("status")),                           // enum options auto-derived
  column(infer("dueDate")),                          // date type auto-detected
  column(infer("priority", { sort: false })),        // override: disable sort
  column({
    id: "actions",
    label: "Actions",
    render: (row) => <ActionMenu row={row} />,
  }),
];

inferColumns() can be freely mixed with manual column() definitions. Use manual column() for fields not in the metadata or those requiring custom configuration, and inferColumns() for everything else.

useDataTable(options)

Integrates data, column visibility, row operations, and props generators.

const table = useDataTable<Order>({
  columns,
  data: result.data?.orders,   // CollectionResult<Order>
  loading: result.fetching,
  error: result.error,
  control,
  onClickRow: (row) => navigate(`/orders/${row.id}`),
  rowActions: [
    { id: "delete", label: "Delete", variant: "destructive", onClick: (row) => handleDelete(row.id) },
  ],
});

// Wrap with DataTable.Provider (utility components read from context)
<DataTable.Provider value={table}>
  <DataTable.Root>...</DataTable.Root>
  <ColumnSelector />
  <CsvButton filename="orders" />
  <Pagination />
</DataTable.Provider>

// Column visibility
table.toggleColumn("amount");
table.showAllColumns();
table.hideAllColumns();
table.isColumnVisible("amount");

// Optimistic updates
const { rollback } = table.updateRow(rowId, { status: "APPROVED" });
const { rollback, deletedRow } = table.deleteRow(rowId);
const { rollback } = table.insertRow(newRow);

Table.* — Static Table Components

Low-level table primitives without data binding. Use for fully custom layouts or skeleton loading.

<Table.Root>
  <Table.Headers>
    <Table.HeaderRow>
      <Table.HeaderCell>Name</Table.HeaderCell>
      <Table.HeaderCell>Status</Table.HeaderCell>
    </Table.HeaderRow>
  </Table.Headers>
  <Table.Body>
    <Table.Row>
      <Table.Cell>Order A</Table.Cell>
      <Table.Cell>Approved</Table.Cell>
    </Table.Row>
  </Table.Body>
</Table.Root>

DataTable.* — Data-bound Table Components

Pair with useDataTable for automatic header sorting, cell rendering, and row operations.

// Basic usage (wrap with DataTable.Provider)
<DataTable.Provider value={table}>
  <DataTable.Root>
    <DataTable.Headers />
    <DataTable.Body />
  </DataTable.Root>
</DataTable.Provider>

// Custom row rendering
<DataTable.Provider value={table}>
  <DataTable.Root>
    <DataTable.Headers />
    <DataTable.Body>
      {table.rows.map((row) => (
        <DataTable.Row
          key={row.id}
          onClick={() => navigate(`/orders/${row.id}`)}
        >
          {table.visibleColumns.map((col) => (
            <DataTable.Cell key={col.id ?? col.label} />
          ))}
        </DataTable.Row>
      ))}
    </DataTable.Body>
  </DataTable.Root>
</DataTable.Provider>

Utility Components

All utility components read from DataTable.Provider context.

| Component | Props | Description | |-----------|-------|-------------| | Pagination | (none) | Previous/Next page controls | | ColumnSelector | (none) | Column visibility toggle UI | | CsvButton | filename? | Export visible data as CSV | | SearchFilterForm | labels?, trigger? | Multi-field filter form with operator selection |

All utility components read from DataTable.Provider context — no prop spreading needed:

<DataTable.Provider value={table}>
  <SearchFilterForm />
  <ColumnSelector />
  <CsvButton filename="orders-export" />
  <DataTable.Root>
    <DataTable.Headers />
    <DataTable.Body />
  </DataTable.Root>
  <Pagination />
</DataTable.Provider>

Optimistic Updates in Cell Renderers

Use useDataTableContext() inside DataTable.Provider to access row operations from custom renderers.

function StatusEditor({ row }: { row: Order }) {
  const { updateRow } = useDataTableContext<Order>();
  const [updateOrder] = useMutation(UPDATE_ORDER);

  const handleChange = async (newStatus: string) => {
    const { rollback } = updateRow(row.id, { status: newStatus });
    try {
      await updateOrder({ id: row.id, input: { status: newStatus } });
    } catch (error) {
      rollback();
    }
  };

  return <StatusSelect value={row.status} onChange={handleChange} />;
}

// Use in column definition
column<Order>({
  label: "Status",
  render: (row) => <StatusEditor row={row} />,
})

Relation Fields

Use column() with GraphQL query expansion to show relation data.

// Include relations in your GraphQL query
const GET_MEMBERSHIPS = graphql(`
  query SupplierGroupMemberships {
    supplierGroupMemberships {
      edges {
        node {
          id
          supplier { companyName, contactName }
          addedBy { name }
          createdAt
        }
      }
    }
  }
`);

// Display relation fields with column()
const columns = [
  column<Membership>({
    label: "Company",
    render: (row) => row.supplier?.companyName ?? "-",
  }),
  column<Membership>({
    label: "Added By",
    render: (row) => row.addedBy?.name ?? "-",
  }),
  column<Membership>({
    label: "Created",
    render: (row) => row.createdAt,
    sort: { field: "createdAt", type: "date" },
  }),
];

Styling

The library uses Tailwind CSS classes. Import the included theme or provide your own CSS variables:

/* Option 1: Import included theme */
@import "@izumisy-tailor/tailor-data-viewer/styles/theme.css";

/* Option 2: Define your own CSS variables */
:root {
  --background: 0 0% 100%;
  --foreground: 222.2 84% 4.9%;
  --primary: 222.2 47.4% 11.2%;
  /* ... see theme.css for full list */
}

Exports

| Entry Point | Description | |-------------|-------------| | @izumisy-tailor/tailor-data-viewer/component | Hooks, components, helpers, and types | | @izumisy-tailor/tailor-data-viewer/generator | Metadata generator for Tailor SDK | | @izumisy-tailor/tailor-data-viewer/styles | CSS theme file |

Development

See the Development Guide for setup and contribution instructions.

License

MIT