Build a generic Resource Table widget that provides CRUD table UX with sort, filter, paginate, bulk-select, search, and standardized empty/loading/error states. It builds on the DataGrid primitive and generalizes the patterns demonstrated by people-table.tsx and billing-history-table.tsx into a reusable, consumer-configurable widget.
Introduction
Overview
The design system has DataGrid as a powerful table primitive and two specialized table widgets (PeopleTable, BillingHistoryTable). However, every new CRUD resource list (projects, environments, deployments, documents) requires teams to re-compose DataGrid with search, filters, bulk actions, and state handling. A generic ResourceTable widget would provide this composition out of the box, configured via props rather than rebuilt from scratch.
Goals
Provide a ResourceTable widget composing DataGrid with ListToolbar, search, filter presets, bulk actions, and state handling.
Support consumer-defined column configurations using the existing DataGridColumn type.
Provide built-in search integration that filters across all searchable columns.
Handle loading (skeleton rows), empty (configurable message + CTA), and error (message + retry) states.
Support row-level actions via a configurable actions column.
Ship with Storybook stories demonstrating diverse resource types and unit tests.
Non-Goals
Server-side pagination/filtering (widget operates on client-side data; server-side is a consumer responsibility via callbacks).
Inline row editing (covered by InlineEdit in FRD-028).
Tree/hierarchical table display.
Scope
In Scope
Item
Description
ResourceTable widget
Composed DataGrid with toolbar, search, filters, bulk actions, pagination, and state handling.
Column configuration
Consumer passes DataGridColumn[] to define columns, including custom cell renderers.
Search integration
Built-in search that filters across columns marked as searchable.
Filter presets
Consumer-defined filter presets rendered as toolbar filter chips or a filter popover.
Loading skeleton, empty state with CTA, error state with retry.
Pagination
Built-in pagination with configurable page sizes.
Stories and tests
Storybook stories for various resource types; unit tests for search, filter, bulk selection.
Out of Scope
Item
Reason
Server-side pagination
Consumer manages via onPageChange callback and provides paginated data.
Inline editing
Separate FRD (FRD-028 InlineEdit).
Column reordering / drag-to-resize
DataGrid primitive concern; future enhancement.
Virtualized rows
DataGrid handles virtualization internally for large datasets.
Users and Pain Points
User
Pain Point
SaaS developers
Rebuild DataGrid + toolbar + search + filters + pagination for every new resource type.
Product designers
Inconsistent table patterns across different resource pages.
QA teams
Each custom table implementation has different state handling bugs.
End users
Inconsistent search, filter, and pagination UX across different resource lists.
Definitions
Term
Definition
Resource
Any CRUD entity displayed in a table: users, projects, deployments, keys, invoices, etc.
Bulk action
An action applied to all currently selected rows (e.g., delete, export, archive).
Row action
An action specific to a single row, rendered in an actions column dropdown.
Filter preset
A named, pre-configured filter (e.g., “Active only”, “Created this week”) rendered as a toolbar chip.
Searchable column
A column whose accessor value is included in the full-text search.
Current State
DataGrid (src/components/ui/data-grid.tsx): Full-featured table with sorting, filtering, pagination, row selection via TanStack Table. Accepts ColumnDef[], data, sorting/filter/pagination state.
ListToolbar (src/components/ui/list-toolbar.tsx): Toolbar with search input, filter buttons, and action buttons. Used above DataGrid.
PeopleTable (src/components/widgets/people-table.tsx): Specialized DataGrid for team members with avatar, name, email, role, status columns.
BillingHistoryTable (src/components/patterns/billing-history-table.tsx): Specialized DataGrid for invoices.
StatusCard: Used for empty/error states.
No generic ResourceTable widget exists.
Proposed Solution
Build src/components/widgets/resource-table.tsx as a composed widget that wraps DataGrid, ListToolbar, and state handling into a single configurable component.
Unit tests cover search, sort, pagination, bulk selection, and state transitions.
AC-09
pnpm typecheck and pnpm vitest run --project unit pass with zero errors.
LLM Handoff Instructions
When implementing this FRD:
Createsrc/components/widgets/resource-table.tsx. Study people-table.tsx and billing-history-table.tsx for the composition pattern.
Use DataGrid as the core table. Pass columns, data, sorting, pagination, and selection props through.
Compose ListToolbar above the DataGrid. Wire the search input to DataGrid’s global filter. Render filter preset chips and bulk action buttons conditionally.
Auto-append actions column: If rowActions is provided, append a final column with a DropdownMenu containing the action items.
State handling: Check loading, error, data.length in order. Render skeleton/error/empty as appropriate. Empty state is gated on !loading && data.length === 0.
Generic typing: ResourceTable<T extends { id: string }> — the id constraint is needed for row selection keys.