@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
useCollectionVariablesHook: Manages filter, sort, and pagination state; outputs Tailor Platform-compatible GraphQL variablesCollectionControlProvider: Shares collection control state via React Context across sibling componentsTable.*Compound Components: Static, unstyled table primitives (<table>,<thead>,<tbody>,<tr>,<th>,<td>)DataTable.*Compound Components: Data-bound table with sort indicators, cell renderers, anduseDataTableintegrationuseDataTableHook: 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,insertRowwith rollback - Presentation Agnostic: Same
useCollectionVariablescan 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-viewerFor 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.gitThis provides AI-optimized documentation for better code generation and assistance.
Peer Dependencies
npm install react react-domQuick 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; // booleanDataTable.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.
- 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
});- Run the generator:
tailor-sdk generateinferColumns(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
