pro-ui

ProTable

Advanced data table with server-side pagination, sorting, filtering, row selection, bulk actions, and expandable rows.

ProTable is the flagship component of pro-ui. It replaces 300–500 lines of data table boilerplate with a single component.

Import

import { ProTable } from '@dangbt/pro-ui'
import type { ProColumnType } from '@dangbt/pro-ui'

Basic example

interface User {
  id: string
  name: string
  email: string
  status: 'active' | 'inactive'
  createdAt: string
}
 
const columns: ProColumnType<User>[] = [
  { title: 'Name', dataIndex: 'name', sortable: true },
  { title: 'Email', dataIndex: 'email' },
  {
    title: 'Status',
    dataIndex: 'status',
    valueType: 'select',
    valueEnum: {
      active: { text: 'Active', color: 'success' },
      inactive: { text: 'Inactive', color: 'default' },
    },
  },
  { title: 'Created', dataIndex: 'createdAt', valueType: 'date' },
]
 
export function UsersPage() {
  return (
    <ProTable<User>
      headerTitle="Users"
      columns={columns}
      rowKey="id"
      request={async ({ current, pageSize, ...filters }) => {
        const res = await fetch(`/api/users?page=${current}&limit=${pageSize}`)
        const data = await res.json()
        return { data: data.items, total: data.total, success: true }
      }}
    />
  )
}

Props

PropTypeRequiredDefaultDescription
columnsProColumnType<T>[]Column definitions
rowKeykeyof T | ((record: T) => string)Unique key for each row
request(params: QueryParams) => Promise<RequestResult<T>>Server-side data fetcher. Mutually exclusive with dataSource
dataSourceT[]Client-side static data. Mutually exclusive with request
headerTitlestringTable header title
toolBarRender() => ReactNode[]Extra buttons in the toolbar
searchbooleantrueSet false to hide the search form
loadingbooleanOverride loading state
pagination{ defaultPageSize?: number; pageSizeOptions?: number[] }Pagination config
rowSelection{ onChange?: (keys: string[], rows: T[]) => void }Enable row selection with checkboxes
bulkActionsBulkActionDef<T>[]Actions shown when rows are selected
expandedRowRender(record: T) => ReactNodeRender content below expanded row
rowClassName(record: T, index: number) => stringAdd CSS classes to rows conditionally
onRow(record: T, index: number) => handlersRow event handlers (onClick, onDoubleClick, onContextMenu)
size'sm' | 'md' | 'lg''md'Table density

Server-side mode

Use the request prop for server-side pagination, search, and filtering. The function receives:

interface QueryParams {
  current: number        // current page (1-based)
  pageSize: number       // rows per page
  search?: string        // search input value
  sorter?: {
    field: string
    order: 'ascend' | 'descend'
  }
  filters?: Record<string, string | string[]>
}

Return:

interface RequestResult<T> {
  data: T[]
  total: number
  success: boolean
}

Client-side mode

Use dataSource for static or pre-fetched data:

<ProTable<User>
  columns={columns}
  rowKey="id"
  dataSource={users}
/>

ProTable handles client-side filtering, sorting, and pagination automatically.

Row selection + bulk actions

<ProTable<User>
  columns={columns}
  rowKey="id"
  request={fetchUsers}
  rowSelection={{
    onChange: (selectedKeys, selectedRows) => {
      setSelected(selectedKeys)
    },
  }}
  bulkActions={[
    {
      label: 'Delete selected',
      danger: true,
      onClick: async (keys) => {
        await deleteUsers(keys)
      },
    },
    {
      label: 'Export CSV',
      onClick: (keys, rows) => exportToCSV(rows),
    },
  ]}
/>

Toolbar with extra buttons

<ProTable<User>
  columns={columns}
  rowKey="id"
  request={fetchUsers}
  toolBarRender={() => [
    <Button key="add" variant="solid" onPress={() => setAddOpen(true)}>
      Add User
    </Button>,
    <Button key="export" variant="outline" onPress={handleExport}>
      Export
    </Button>,
  ]}
/>

Column definition reference

interface ProColumnType<T> {
  title: string
  dataIndex: keyof T
  key?: string
  sortable?: boolean
  width?: number | string
  align?: 'left' | 'center' | 'right'
  fixed?: 'left' | 'right'
  hidden?: boolean
  valueType?: 'date' | 'select' | 'boolean' | 'number'
  valueEnum?: Record<string, { text: string; color?: string }>
  render?: (value: any, record: T, index: number) => ReactNode
  filters?: { label: string; value: string }[]
}

Expandable rows

<ProTable<Order>
  columns={columns}
  rowKey="id"
  request={fetchOrders}
  expandedRowRender={(record) => (
    <div className="p-4">
      <h4 className="font-medium">Order items</h4>
      {record.items.map(item => (
        <div key={item.id}>{item.name} × {item.qty}</div>
      ))}
    </div>
  )}
/>

On this page