npm package: @gulibs/react-vtable
Description: ![NPM version](https://img.shields.io/npm/v/@gulibs/react-vtable.svg?style=flat-square)
Latest version: 0.0.22

# @gulibs/react-vtable

![NPM version](https://img.shields.io/npm/v/@gulibs/react-vtable.svg?style=flat-square)

A powerful and flexible React table component library built with shadcn/ui and TanStack Table.

[English](#english) | [中文](#chinese)

---

## English

### Table of Contents

- [Features](#features)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Core Concepts](#core-concepts)
- [Complete API Reference](#complete-api-reference)
- [Advanced Examples](#advanced-examples)
- [Best Practices](#best-practices)
- [Development](#development)
- [License](#license)

### Features

- ✨ **Powerful Table**: Built on TanStack Table v8 for maximum flexibility and performance
- 🎨 **shadcn/ui Components**: Beautiful, accessible, and customizable UI components
- 🔍 **Search & Filter**: Advanced search with auto-filtering for local/remote data
- 📄 **Pagination**: Client-side and server-side pagination support
- ✅ **Row Selection**: Single/multiple selection with cross-page selection support
- 🎯 **Batch Actions**: Flexible batch operations with smart button grouping
- ✏️ **Inline Editing**: Edit rows inline with validation support
- 📌 **Fixed Columns**: Pin columns to left/right with automatic shadow effects
- 🌳 **Tree Table**: Hierarchical data with expand/collapse support
- 🔄 **Drag & Drop**: Row and column reordering with @dnd-kit
- 📱 **Responsive**: Mobile-friendly design
- 🌐 **i18n**: Built-in English and Chinese locales
- 🎭 **Rich Value Types**: 20+ built-in value types (date, money, status, etc.)
- 📋 **Copy to Clipboard**: One-click copy for cells
- 💬 **Tooltip**: Customizable tooltips for cells and headers
- 📏 **Text Ellipsis**: Single-line and multi-line text truncation
- 🔧 **TypeScript**: Full TypeScript support with comprehensive types

### Prerequisites

This library requires **Tailwind CSS** to be installed and configured in your project.

### Installation

```bash
# Using pnpm (recommended)
pnpm add @gulibs/react-vtable

# Using npm
npm install @gulibs/react-vtable

# Using yarn
yarn add @gulibs/react-vtable

# Peer dependencies (if not already installed)
pnpm add react react-dom @tanstack/react-table tailwindcss
```

### Configuration

#### Tailwind CSS Setup

Add the library path to your `tailwind.config.js`:

```javascript
// tailwind.config.js
export default {
  content: [
    './src/**/*.{js,ts,jsx,tsx}',
    './node_modules/@gulibs/react-vtable/dist/**/*.js'
  ],
  // No additional configuration needed - styles are automatically injected
}
```

> 💡 **Note**: The library automatically injects its CSS styles. No manual CSS imports are required.

### Quick Start

```tsx
import { ProTable } from '@gulibs/react-vtable'

function App() {
  const columns = [
    { title: 'Name', dataIndex: 'name', key: 'name' },
    { title: 'Age', dataIndex: 'age', key: 'age' },
    { title: 'Email', dataIndex: 'email', key: 'email' },
  ]

  const dataSource = [
    { id: 1, name: 'John Doe', age: 30, email: 'john@example.com' },
    { id: 2, name: 'Jane Smith', age: 25, email: 'jane@example.com' },
  ]

  return (
    <ProTable
      columns={columns}
      dataSource={dataSource}
      rowKey="id"
    />
  )
}
```

### Core Concepts

#### 1. Data Modes

**Local Data Mode** (using `dataSource`):
```tsx
<ProTable
  dataSource={localData}
  columns={columns}
  rowKey="id"
/>
```

**Remote Data Mode** (using `request`):
```tsx
<ProTable
  request={async (params) => {
    // params includes: current, pageSize, filters, sorter
    const res = await fetchData(params)
    return {
      data: res.items,
      total: res.total,
      success: true
    }
  }}
  columns={columns}
  rowKey="id"
/>
```

#### 2. Search Behavior

The table provides **automatic search behavior**:

- **Local Mode**: Converts search values to TanStack Table's `columnFilters` and filters in real-time
- **Remote Mode**: Triggers data fetch with search parameters sent to backend

Default filter supports:
- Case-insensitive substring matching for text fields
- Exact match for enum/select fields (`valueEnum`, `filters`, `valueType: 'select'`)
- Array-based multi-select filtering
- Automatic empty/null value handling

#### 3. Pagination Modes

**Client-side Pagination** (default with `dataSource`):
```tsx
<ProTable
  dataSource={data}
  pagination={{ pageSize: 10 }}
/>
```

**Server-side Pagination** (with `request`):
```tsx
<ProTable
  request={fetchData}
  pagination={{ pageSize: 10 }}
/>
```

**Manual Server Pagination** (without `request`):
```tsx
<ProTable
  dataSource={currentPageData}
  pagination={{
    mode: 'server',  // Important!
    current: page,
    pageSize: pageSize,
    total: total,
    onChange: (page, pageSize) => fetchPage(page, pageSize)
  }}
/>
```

---

## Complete API Reference

### ProTable Props

#### Core Props

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `columns` | `ProColumn<T>[]` | `[]` | Column definitions |
| `dataSource` | `T[]` | `[]` | Local data array |
| `request` | `(params) => Promise<Response>` | - | Remote data fetcher |
| `rowKey` | `string \| (record) => string` | `'id'` | Unique row identifier |
| `loading` | `boolean` | `false` | Loading state |
| `defaultData` | `T[]` | `[]` | Default data for initial render |
| `postData` | `(data: T[]) => T[]` | - | Transform data after fetch |
| `params` | `Record<string, any>` | - | Extra params for `request` |

#### Layout Props

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `size` | `'small' \| 'middle' \| 'large'` | `'middle'` | Table size |
| `bordered` | `boolean` | `false` | Show table borders |
| `tableLayout` | `'auto' \| 'fixed'` | `'auto'` | Table layout algorithm |
| `scroll` | `{ x?: number \| string \| true; y?: number \| string }` | - | Scrollable area config |
| `sticky` | `boolean \| { offsetHeader?: number }` | `false` | Sticky header config |
| `showHeader` | `boolean` | `true` | Show table header |
| `className` | `string` | - | Custom class name |
| `style` | `CSSProperties` | - | Custom styles |
| `tableStyle` | `CSSProperties` | - | Table element styles |

#### Feature Props

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `search` | `ProTableSearch \| false` | `false` | Search form config |
| `toolbar` | `ProTableToolBar \| false` | `false` | Toolbar config |
| `pagination` | `ProTablePagination \| false` | `false` | Pagination config |
| `rowSelection` | `ProTableRowSelection<T>` | - | Row selection config |
| `batchActions` | `ProTableBatchActions<T>` | - | Batch actions config |
| `editable` | `ProTableEditable<T>` | - | Inline editing config |
| `expandable` | `ProTableExpandable<T> \| false` | - | Tree table config |
| `draggable` | `ProTableDraggable \| false` | - | Drag & drop config |
| `columnsState` | `ProColumnState` | - | Column state management |

#### Callback Props

| Property | Type | Description |
|----------|------|-------------|
| `onChange` | `(pagination, filters, sorter, extra) => void` | Table change callback |
| `onLoad` | `(dataSource: T[]) => void` | Called after data loaded |
| `onLoadingChange` | `(loading: boolean) => void` | Loading state change |
| `onRequestError` | `(error: Error) => void` | Request error callback |
| `onSubmit` | `(params: any) => void` | Search submit callback |
| `onReset` | `() => void` | Search reset callback |
| `onRow` | `(record, index) => HTMLAttributes` | Row props callback |

#### Advanced Props

| Property | Type | Description |
|----------|------|-------------|
| `headerTitle` | `ReactNode` | Table header title |
| `headerSubTitle` | `ReactNode` | Table header subtitle |
| `footer` | `(data) => ReactNode` | Table footer render |
| `emptyRender` | `ReactNode` | Empty state render |
| `tableRender` | `(props, dom, domList) => ReactNode` | Custom table render |
| `tableExtraRender` | `(props, data) => ReactNode` | Extra content after table |
| `paginationRender` | `(opts) => ReactNode` | Custom pagination render |
| `searchFormRender` | `(props, dom) => ReactNode` | Custom search form render |
| `actionRef` | `Ref<ProTableAction<T>>` | Table action reference |
| `formRef` | `Ref<FormInstance>` | Search form reference |
| `locale` | `ProTableLocale` | i18n locale config |

---

### ProColumn Configuration

#### Basic Props

| Property | Type | Description |
|----------|------|-------------|
| `title` | `string` | Column header text |
| `dataIndex` | `keyof T \| string \| string[]` | Data field path (supports nested: `'user.name'` or `['user', 'name']`) |
| `dataPath` | `string` | Alternative to dataIndex (string path only: `'user.name'`) |
| `key` | `string` | Unique column key |
| `width` | `number \| string` | Column width (required for fixed columns) |
| `align` | `'left' \| 'center' \| 'right'` | Text alignment |
| `fixed` | `'left' \| 'right'` | Pin column to left/right |

#### Display Props

| Property | Type | Description |
|----------|------|-------------|
| `valueType` | `ProFieldValueType` | Value display type (20+ types) |
| `valueEnum` | `Record<string, { text: string; status?: string; color?: string }>` | Enum value mapping |
| `render` | `(value, record, index) => ReactNode` | Custom cell render (takes priority over `valueType`) |
| `ellipsis` | `boolean \| ProColumnEllipsis` | Text ellipsis config |
| `tooltip` | `boolean \| ProColumnTooltip` | Tooltip config |
| `headerEllipsis` | `boolean \| ProColumnEllipsis` | Header ellipsis config |
| `headerTooltip` | `boolean \| ProColumnTooltip` | Header tooltip config |
| `copyable` | `boolean \| ProColumnCopyable` | Enable copy to clipboard |

#### Search Props

| Property | Type | Description |
|----------|------|-------------|
| `search` | `boolean \| ProColumnSearch` | Include in search form |
| `hideInSearch` | `boolean` | Hide in search form |
| `searchFormItemProps` | `FormItemProps` | Search form item props |
| `renderFormItem` | `(value, onChange, field, column) => ReactNode` | Custom search input render |

#### Table Display Props

| Property | Type | Description |
|----------|------|-------------|
| `hideInTable` | `boolean` | Hide in table |
| `sorter` | `boolean \| ((a, b) => number)` | Enable sorting |
| `filters` | `Array<{ text: string; value: any }>` | Filter options |
| `onFilter` | `(value, record) => boolean` | Filter function |

#### Edit Props

| Property | Type | Description |
|----------|------|-------------|
| `editable` | `boolean \| ((record) => boolean)` | Enable inline editing |
| `hideInForm` | `boolean` | Hide in edit form |
| `formItemProps` | `FormItemProps` | Form item props |
| `fieldProps` | `Record<string, any>` | Field component props |

---

### Value Types Reference

Supported `valueType` values:

| Type | Description | Example |
|------|-------------|---------|
| `text` | Plain text | "Hello World" |
| `textarea` | Multi-line text | Long content |
| `number` | Number | 123 |
| `money` | Currency | $1,234.56 |
| `percent` | Percentage | 75% |
| `date` | Date | 2024-01-01 |
| `dateTime` | Date with time | 2024-01-01 10:30:00 |
| `dateRange` | Date range | 2024-01-01 ~ 2024-01-31 |
| `select` | Select dropdown | Uses `valueEnum` |
| `status` | Status badge | Uses `valueEnum` |
| `tags` | Multiple tags | Array display |
| `switch` | Boolean switch | true/false |
| `avatar` | Avatar image | URL display |
| `image` | Image | URL display |
| `progress` | Progress bar | 0-100 |
| `code` | Code block | Monospace text |
| `fromNow` | Relative time | "2 hours ago" |
| `email` | Email address | With mailto link |
| `phone` | Phone number | Formatted |
| `url` | URL link | Clickable link |
| `color` | Color picker | Color swatch |
| `rate` | Rating stars | 1-5 stars |
| `custom` | Custom render | Use `render` function |

---

### Search Configuration

```typescript
interface ProTableSearch {
  filterType?: 'query' | 'light'  // Query: form above table, Light: inline filters
  searchText?: string              // Search button text
  resetText?: string               // Reset button text
  submitText?: string              // Submit button text
  labelWidth?: number | 'auto'     // Label width
  span?: number                    // Grid column span
  defaultCollapsed?: boolean       // Initially collapsed
  collapsed?: boolean              // Controlled collapse state
  onCollapse?: (collapsed: boolean) => void
  optionRender?: (config, props, dom) => ReactNode[]  // Custom action buttons
}
```

**Example:**

```tsx
<ProTable
  search={{
    filterType: 'query',
    searchText: 'Search',
    resetText: 'Reset',
    labelWidth: 'auto',
    defaultCollapsed: false,
  }}
/>
```

---

### Pagination Configuration

```typescript
interface ProTablePagination {
  mode?: 'client' | 'server'  // Pagination mode
  pageSize?: number            // Items per page
  current?: number             // Current page (controlled)
  total?: number               // Total items
  showSizeChanger?: boolean    // Show page size selector
  showQuickJumper?: boolean    // Show quick jump input
  showTotal?: (total, range) => ReactNode
  pageSizeOptions?: string[]   // Page size options
  size?: 'default' | 'small'
  simple?: boolean             // Simple pagination
  disabled?: boolean
  onChange?: (page, pageSize) => void
  onShowSizeChange?: (current, size) => void
}
```

---

### Row Selection Configuration

```typescript
interface ProTableRowSelection<T> {
  type?: 'checkbox' | 'radio'  // Selection type
  selectedRowKeys?: React.Key[]  // Controlled selection
  onChange?: (keys, rows) => void
  preserveSelectedRowKeys?: boolean  // Keep selection across pages (default: true)
  getCheckboxProps?: (record) => any
  selections?: boolean | any[]
  hideSelectAll?: boolean
  fixed?: boolean              // Fix selection column
  columnWidth?: number | string
  renderCell?: (checked, record, index, originNode) => ReactNode
  alwaysShowAlert?: boolean    // Always show batch actions bar
  batchActions?: ProTableBatchActions<T>  // Batch operations config
}
```

#### Cross-Page Selection

When using `request` or `pagination.onChange`, selection automatically persists across pages:

```tsx
<ProTable
  request={fetchData}
  rowSelection={{
    type: 'checkbox',
    preserveSelectedRowKeys: true,  // Default: true
    onChange: (selectedRowKeys, selectedRows) => {
      // selectedRowKeys: all selected keys across all pages
      // selectedRows: rows from current page only
      console.log('Total selected:', selectedRowKeys.length)
    }
  }}
/>
```

---

### Batch Actions Configuration (Updated API)

```typescript
interface ProTableBatchActions<T> {
  actions?: Array<ProTableBatchAction<T>>  // Batch action list
  maxVisibleActions?: number  // Max buttons to show directly (default: 3)
  hideClearButton?: boolean   // Hide clear selection button
}

interface ProTableBatchAction<T> {
  key: string                  // Unique action key
  label: string                // Button text
  icon?: ReactNode             // Button icon
  onClick: (rows: T[]) => void // Click handler
  variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'
  clearSelection?: boolean     // Clear selection after action (default: false)
  showInMore?: boolean         // Show in "More" menu (default: false)
  isExportFormat?: boolean     // Mark as export format (auto-groups multiple export formats)
}
```

**Example:**

```tsx
import { Trash2, Download, FileText, Mail } from 'lucide-react'

<ProTable
  rowSelection={{
    type: 'checkbox',
    batchActions: {
      maxVisibleActions: 3,  // Show max 3 buttons directly
      actions: [
        {
          key: 'delete',
          label: 'Delete',
          icon: <Trash2 className="h-3 w-3 mr-1" />,
          onClick: (rows) => {
            console.log('Delete:', rows)
          },
          variant: 'destructive',
          clearSelection: true,  // Clear after delete
        },
        {
          key: 'export',
          label: 'Export',
          icon: <Download className="h-3 w-3 mr-1" />,
          onClick: (rows) => {
            console.log('Export:', rows)
          },
          variant: 'outline',
        },
        {
          key: 'exportExcel',
          label: 'Export Excel',
          icon: <FileText className="h-3 w-3 mr-1" />,
          onClick: (rows) => {
            console.log('Export Excel:', rows)
          },
          variant: 'outline',
          isExportFormat: true,  // Groups with other export formats
          showInMore: true,
        },
        {
          key: 'exportCSV',
          label: 'Export CSV',
          icon: <FileText className="h-3 w-3 mr-1" />,
          onClick: (rows) => {
            console.log('Export CSV:', rows)
          },
          variant: 'outline',
          isExportFormat: true,
          showInMore: true,
        },
        {
          key: 'email',
          label: 'Send Email',
          icon: <Mail className="h-3 w-3 mr-1" />,
          onClick: (rows) => {
            console.log('Email:', rows)
          },
          variant: 'outline',
          showInMore: true,  // Move to "More" menu
        },
      ],
    },
  }}
/>
```

**Features:**
- Smart button grouping: buttons exceeding `maxVisibleActions` auto-move to "More" (⋯) menu
- Export format grouping: multiple `isExportFormat: true` actions auto-merge into dropdown
- Flexible action control: use `clearSelection` to auto-clear selection after certain actions

---

### Editable Configuration

```typescript
interface ProTableEditable<T> {
  type?: 'single' | 'multiple'  // Edit mode
  form?: any                     // React Hook Form instance
  formProps?: Record<string, any>
  onSave?: (key, record, originRow) => Promise<void>
  onDelete?: (key, record) => Promise<void>
  onCancel?: (key, record, originRow) => void
  actionRender?: (record, config) => ReactNode[]  // Custom action buttons
  deletePopconfirmMessage?: ReactNode
  onlyOneLineEditorAlertMessage?: ReactNode
  onlyAddOneLineAlertMessage?: ReactNode
}
```

---

### Expandable Configuration (Tree Table)

```typescript
interface ProTableExpandable<T> {
  childrenColumnName?: string    // Children field name (default: 'children')
  defaultExpandedRowKeys?: React.Key[]  // Default expanded rows
  expandedRowKeys?: React.Key[]  // Controlled expanded rows
  onExpand?: (expanded, record) => void
  onExpandedRowsChange?: (keys) => void
  expandIcon?: (props: {
    expanded: boolean
    onExpand: () => void
    record: T
  }) => ReactNode
  showExpandColumn?: boolean     // Show expand column (default: true)
  expandColumnWidth?: number     // Expand column width (default: 50)
  defaultExpandAllRows?: boolean // Expand all by default
  accordion?: boolean            // Accordion mode (only one expanded)
}
```

---

### Ellipsis Configuration

```typescript
interface ProColumnEllipsis {
  multiline?: boolean  // Multi-line ellipsis
  rows?: number        // Number of rows (1-5, default: 2)
}
```

**Examples:**

```tsx
// Single-line ellipsis
ellipsis: true

// Multi-line ellipsis (2 lines)
ellipsis: {
  multiline: true,
  rows: 2
}
```

---

### Tooltip Configuration

```typescript
interface ProColumnTooltip<T> {
  content?: ReactNode | ((value, record, index) => ReactNode)
  side?: 'top' | 'right' | 'bottom' | 'left'
  align?: 'start' | 'center' | 'end'
  delayDuration?: number
  disabled?: boolean
}
```

**Examples:**

```tsx
// Simple tooltip (shows cell value)
tooltip: true

// Custom tooltip
tooltip: {
  content: 'Custom tooltip text',
  side: 'top',
  delayDuration: 300
}

// Function-based tooltip
tooltip: {
  content: (value, record) => `Full: ${value} (ID: ${record.id})`,
  side: 'top'
}
```

---

### Action Reference

Access table methods via `actionRef`:

```typescript
interface ProTableAction<T> {
  reload: (resetPageIndex?: boolean) => Promise<void>  // Reload data
  reloadAndRest: () => Promise<void>  // Reload and reset to page 1
  reset: () => void                    // Reset filters and search
  clearSelected: () => void            // Clear row selection
  startEditable: (rowKey) => boolean   // Start editing row
  cancelEditable: (rowKey) => boolean  // Cancel editing row
  saveEditable: (rowKey, record) => boolean  // Save edited row
  getRowData: (rowKey) => T | undefined     // Get row data
  getTableData: () => T[]              // Get all table data
  setTableData: (data: T[]) => void    // Set table data
}
```

**Example:**

```tsx
const actionRef = useRef<ProTableAction<DataType>>(null)

// Reload data
actionRef.current?.reload()

// Reset filters
actionRef.current?.reset()

// Clear selection
actionRef.current?.clearSelected()
```

---

## Advanced Examples

### 1. Remote Data with Search and Pagination

```tsx
<ProTable
  request={async (params) => {
    // params includes: current, pageSize, keyword, ...filters, ...sorter
    const response = await fetch('/api/users', {
      method: 'POST',
      body: JSON.stringify(params)
    })
    const data = await response.json()
    return {
      data: data.items,
      total: data.total,
      success: true
    }
  }}
  columns={[
    { title: 'Name', dataIndex: 'name', search: true },
    { title: 'Email', dataIndex: 'email', search: true },
    {
      title: 'Status',
      dataIndex: 'status',
      valueType: 'select',
      search: true,
      valueEnum: {
        active: { text: 'Active', status: 'success' },
        inactive: { text: 'Inactive', status: 'default' },
      },
    },
  ]}
  search={{ filterType: 'query' }}
  pagination={{
    pageSize: 10,
    showSizeChanger: true,
    showQuickJumper: true,
  }}
  rowKey="id"
/>
```

### 2. Fixed Columns with Horizontal Scroll

```tsx
<ProTable
  columns={[
    {
      title: 'ID',
      dataIndex: 'id',
      width: 80,
      fixed: 'left',  // Pin to left
    },
    {
      title: 'Name',
      dataIndex: 'name',
      width: 150,
      fixed: 'left',
    },
    { title: 'Email', dataIndex: 'email', width: 200 },
    { title: 'Phone', dataIndex: 'phone', width: 150 },
    { title: 'Address', dataIndex: 'address', width: 300 },
    {
      title: 'Actions',
      key: 'actions',
      width: 100,
      fixed: 'right',  // Pin to right
      render: (_, record) => (
        <Button size="sm">Edit</Button>
      ),
    },
  ]}
  dataSource={data}
  scroll={{ x: 1200 }}  // Enable horizontal scroll
  tableLayout="fixed"    // Required for fixed columns
  rowKey="id"
/>
```

### 3. Tree Table with Expandable Rows

```tsx
<ProTable
  columns={[
    { title: 'Name', dataIndex: 'name', width: 200 },
    { title: 'Size', dataIndex: 'size' },
    { title: 'Type', dataIndex: 'type' },
  ]}
  dataSource={[
    {
      id: 1,
      name: 'Folder 1',
      type: 'folder',
      children: [
        { id: 11, name: 'File 1.1', type: 'file', size: '1.2 MB' },
        { id: 12, name: 'File 1.2', type: 'file', size: '2.5 MB' },
      ],
    },
    {
      id: 2,
      name: 'Folder 2',
      type: 'folder',
      children: [
        { id: 21, name: 'File 2.1', type: 'file', size: '3.1 MB' },
      ],
    },
  ]}
  expandable={{
    defaultExpandAllRows: false,
    accordion: false,  // Allow multiple rows expanded
  }}
  rowKey="id"
/>
```

### 4. Inline Editing

```tsx
const [editableKeys, setEditableKeys] = useState<React.Key[]>([])

<ProTable
  columns={[
    {
      title: 'Name',
      dataIndex: 'name',
      editable: true,
    },
    {
      title: 'Age',
      dataIndex: 'age',
      valueType: 'number',
      editable: true,
    },
    {
      title: 'Email',
      dataIndex: 'email',
      editable: true,
    },
  ]}
  dataSource={data}
  editable={{
    type: 'multiple',
    editableKeys,
    onChange: setEditableKeys,
    onSave: async (key, record, originRow) => {
      // Save to backend
      await updateUser(key, record)
      message.success('Saved successfully')
    },
  }}
  rowKey="id"
/>
```

### 5. Drag & Drop Reordering

```tsx
<ProTable
  columns={columns}
  dataSource={data}
  draggable={{
    enabled: true,
    handle: true,  // Show drag handle
    onDragEnd: (result) => {
      const { items, oldIndex, newIndex } = result
      console.log('New order:', items)
      // Update backend order
      updateOrder(items.map(item => item.id))
    },
  }}
  rowKey="id"
/>
```

### 6. Custom Cell Rendering

```tsx
<ProTable
  columns={[
    {
      title: 'Avatar',
      dataIndex: 'avatar',
      valueType: 'avatar',
      render: (_, record) => (
        <Avatar>
          <AvatarImage src={record.avatar} />
          <AvatarFallback>{record.name[0]}</AvatarFallback>
        </Avatar>
      ),
    },
    {
      title: 'Status',
      dataIndex: 'status',
      valueType: 'status',
      valueEnum: {
        active: { text: 'Active', status: 'success' },
        inactive: { text: 'Inactive', status: 'default' },
        pending: { text: 'Pending', status: 'warning' },
      },
      render: (_, record) => (
        <Badge variant={record.status === 'active' ? 'success' : 'secondary'}>
          {record.status}
        </Badge>
      ),
    },
    {
      title: 'Progress',
      dataIndex: 'progress',
      valueType: 'progress',
      render: (_, record) => (
        <div className="flex items-center gap-2">
          <Progress value={record.progress} className="w-20" />
          <span className="text-xs">{record.progress}%</span>
        </div>
      ),
    },
  ]}
  dataSource={data}
  rowKey="id"
/>
```

### 7. Multi-line Ellipsis with Tooltip

```tsx
<ProTable
  columns={[
    {
      title: 'Description',
      dataIndex: 'description',
      width: 300,
      ellipsis: {
        multiline: true,
        rows: 3,  // Show 3 lines max
      },
      tooltip: {
        content: (value) => value,
        side: 'top',
      },
    },
    {
      title: 'Long Title Column with Header Tooltip',
      dataIndex: 'data',
      width: 150,
      headerEllipsis: true,  // Header ellipsis
      headerTooltip: {
        content: 'This is a very long header title that needs tooltip',
        side: 'bottom',
      },
      ellipsis: true,  // Cell ellipsis
      tooltip: true,   // Cell tooltip
    },
  ]}
  dataSource={data}
  rowKey="id"
/>
```

### 8. Custom Search Form

```tsx
<ProTable
  columns={[
    {
      title: 'Name',
      dataIndex: 'name',
      search: true,
    },
    {
      title: 'Date Range',
      dataIndex: 'dateRange',
      valueType: 'dateRange',
      search: true,
      renderFormItem: (value, onChange) => (
        <DateRangePicker
          value={value}
          onChange={onChange}
        />
      ),
    },
    {
      title: 'Custom Filter',
      dataIndex: 'customField',
      search: {
        transform: (value) => {
          // Transform before sending to backend
          return { customFieldQuery: value.toUpperCase() }
        },
      },
      renderFormItem: (value, onChange) => (
        <CustomFilterComponent
          value={value}
          onChange={onChange}
        />
      ),
    },
  ]}
  request={fetchData}
  search={{ filterType: 'query' }}
  rowKey="id"
/>
```

---

## Best Practices

### 1. Performance Optimization

#### Use `rowKey` Correctly
Always provide a stable, unique `rowKey`:

```tsx
// Good: Use unique ID
<ProTable rowKey="id" />

// Good: Use function for complex keys
<ProTable rowKey={(record) => `${record.type}-${record.id}`} />

// Bad: Use index (causes re-render issues)
<ProTable rowKey={(record, index) => index} />
```

#### Memoize Large Data
For large datasets, memoize your data:

```tsx
const data = useMemo(() => generateLargeData(), [])

<ProTable dataSource={data} />
```

#### Optimize Column Rendering
Use `useMemo` for column definitions with complex render functions:

```tsx
const columns = useMemo(() => [
  {
    title: 'Name',
    render: (_, record) => <ComplexComponent record={record} />,
  },
], [dependencies])
```

### 2. Request Best Practices

#### Handle Errors Gracefully
```tsx
<ProTable
  request={async (params) => {
    try {
      const res = await fetchData(params)
      return { data: res.items, total: res.total, success: true }
    } catch (error) {
      message.error('Failed to load data')
      return { data: [], total: 0, success: false }
    }
  }}
  onRequestError={(error) => {
    console.error('Request error:', error)
    // Report to error tracking service
  }}
/>
```

#### Debounce Search Requests
Use `params` prop to trigger re-fetch:

```tsx
const [searchParams, setSearchParams] = useState({})
const debouncedSearch = useMemo(
  () => debounce((value) => setSearchParams({ keyword: value }), 500),
  []
)

<ProTable
  request={fetchData}
  params={searchParams}
  toolbar={{
    search: {
      onSearch: debouncedSearch,
    },
  }}
/>
```

### 3. State Management

#### Controlled Components
For complex state management, use controlled mode:

```tsx
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([])
const [pagination, setPagination] = useState({ current: 1, pageSize: 10 })

<ProTable
  rowSelection={{
    selectedRowKeys,
    onChange: setSelectedRowKeys,
  }}
  pagination={{
    ...pagination,
    onChange: (current, pageSize) => {
      setPagination({ current, pageSize })
    },
  }}
/>
```

#### Use Action Ref
Access table methods via `actionRef`:

```tsx
const actionRef = useRef<ProTableAction<DataType>>(null)

const handleRefresh = () => {
  actionRef.current?.reload()
}

const handleReset = () => {
  actionRef.current?.reset()
  actionRef.current?.clearSelected()
}

<ProTable actionRef={actionRef} />
```

### 4. Accessibility

- Always provide meaningful `title` for columns
- Use `aria-label` for action buttons
- Ensure color contrast for status badges
- Test with keyboard navigation

### 5. Internationalization

```tsx
import { ProTable, zh_CN, en_US } from '@gulibs/react-vtable'
import { useState } from 'react'

function App() {
  const [locale, setLocale] = useState(en_US)

  return (
    <>
      <Button onClick={() => setLocale(zh_CN)}>中文</Button>
      <Button onClick={() => setLocale(en_US)}>English</Button>
      
      <ProTable
        locale={locale}
        columns={columns}
        dataSource={data}
      />
    </>
  )
}
```

### 6. Type Safety

Always provide TypeScript types for your data:

```tsx
interface User {
  id: string
  name: string
  email: string
  age: number
  status: 'active' | 'inactive'
}

const columns: ProColumn<User>[] = [
  {
    title: 'Name',
    dataIndex: 'name',
    // TypeScript will validate dataIndex, valueType, render function params, etc.
  },
]

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

---

## Development

```bash
# Install dependencies
pnpm install

# Start development server
pnpm run dev

# Build library
pnpm run build

# Lint code
pnpm run lint
```

---

## License

MIT © [@gulibs](https://github.com/gulibs)

---

## Chinese

## 中文文档

[查看完整中文文档 ↗](./README.zh-CN.md)

### 快速开始

```tsx
import { ProTable, zh_CN } from '@gulibs/react-vtable'

function App() {
  const columns = [
    { title: '姓名', dataIndex: 'name', key: 'name' },
    { title: '年龄', dataIndex: 'age', key: 'age' },
  ]

  const dataSource = [
    { id: 1, name: '张三', age: 30 },
    { id: 2, name: '李四', age: 25 },
  ]

  return (
    <ProTable
      columns={columns}
      dataSource={dataSource}
      rowKey="id"
      locale={zh_CN}
    />
  )
}
```

### 主要特性

- ✨ 强大的表格：基于 TanStack Table v8
- 🎨 精美组件：shadcn/ui 设计
- 🔍 搜索筛选：自动处理本地/远程数据
- 📄 分页支持：客户端和服务端分页
- ✅ 行选择：支持跨页选中
- 🎯 批量操作：灵活的批量操作配置
- ✏️ 内联编辑：支持验证
- 📌 固定列：左右固定，自动阴影
- 🌳 树形表格：层次数据展示
- 🔄 拖拽排序：行列拖拽重排
- 📱 响应式设计：移动端友好
- 🌐 国际化：内置中英文
- 🎭 丰富类型：20+ 值类型
- 💬 提示框：单元格和表头提示
- 📏 文本省略：单行和多行截断
- 🔧 TypeScript：完整类型支持

### 批量操作示例

```tsx
import { Trash2, Download, FileText } from 'lucide-react'

<ProTable
  rowSelection={{
    type: 'checkbox',
    batchActions: {
      maxVisibleActions: 3,
      actions: [
        {
          key: 'delete',
          label: '删除',
          icon: <Trash2 className="h-3 w-3 mr-1" />,
          onClick: (rows) => console.log('删除:', rows),
          variant: 'destructive',
          clearSelection: true,
        },
        {
          key: 'export',
          label: '导出',
          icon: <Download className="h-3 w-3 mr-1" />,
          onClick: (rows) => console.log('导出:', rows),
          variant: 'outline',
        },
        {
          key: 'exportExcel',
          label: '导出 Excel',
          icon: <FileText className="h-3 w-3 mr-1" />,
          onClick: (rows) => console.log('导出 Excel:', rows),
          variant: 'outline',
          isExportFormat: true,
          showInMore: true,
        },
      ],
    },
  }}
  dataSource={data}
  rowKey="id"
  locale={zh_CN}
/>
```

### 更多文档

完整的中文文档和 API 参考，请查看英文部分。所有功能和 API 在中英文版本中都是一致的。

---

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## Support

If you have any questions or need help, please open an issue on [GitHub](https://github.com/gulibs/react-vtable).