@gulibs/react-vtable 0.0.17 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,13 +2,46 @@
2
2
 
3
3
  ![NPM version](https://img.shields.io/npm/v/@gulibs/react-vtable.svg?style=flat-square)
4
4
 
5
+ A powerful and flexible React table component library built with shadcn/ui and TanStack Table.
6
+
5
7
  [English](#english) | [中文](#chinese)
6
8
 
7
9
  ---
8
10
 
9
11
  ## English
10
12
 
11
- A powerful React table component library built with shadcn/ui components and TanStack Table.
13
+ ### Table of Contents
14
+
15
+ - [Features](#features)
16
+ - [Prerequisites](#prerequisites)
17
+ - [Installation](#installation)
18
+ - [Quick Start](#quick-start)
19
+ - [Core Concepts](#core-concepts)
20
+ - [Complete API Reference](#complete-api-reference)
21
+ - [Advanced Examples](#advanced-examples)
22
+ - [Best Practices](#best-practices)
23
+ - [Development](#development)
24
+ - [License](#license)
25
+
26
+ ### Features
27
+
28
+ - ✨ **Powerful Table**: Built on TanStack Table v8 for maximum flexibility and performance
29
+ - 🎨 **shadcn/ui Components**: Beautiful, accessible, and customizable UI components
30
+ - 🔍 **Search & Filter**: Advanced search with auto-filtering for local/remote data
31
+ - 📄 **Pagination**: Client-side and server-side pagination support
32
+ - ✅ **Row Selection**: Single/multiple selection with cross-page selection support
33
+ - 🎯 **Batch Actions**: Flexible batch operations with smart button grouping
34
+ - ✏️ **Inline Editing**: Edit rows inline with validation support
35
+ - 📌 **Fixed Columns**: Pin columns to left/right with automatic shadow effects
36
+ - 🌳 **Tree Table**: Hierarchical data with expand/collapse support
37
+ - 🔄 **Drag & Drop**: Row and column reordering with @dnd-kit
38
+ - 📱 **Responsive**: Mobile-friendly design
39
+ - 🌐 **i18n**: Built-in English and Chinese locales
40
+ - 🎭 **Rich Value Types**: 20+ built-in value types (date, money, status, etc.)
41
+ - 📋 **Copy to Clipboard**: One-click copy for cells
42
+ - 💬 **Tooltip**: Customizable tooltips for cells and headers
43
+ - 📏 **Text Ellipsis**: Single-line and multi-line text truncation
44
+ - 🔧 **TypeScript**: Full TypeScript support with comprehensive types
12
45
 
13
46
  ### Prerequisites
14
47
 
@@ -17,16 +50,24 @@ This library requires **Tailwind CSS** to be installed and configured in your pr
17
50
  ### Installation
18
51
 
19
52
  ```bash
20
- # Install the library
53
+ # Using pnpm (recommended)
21
54
  pnpm add @gulibs/react-vtable
22
55
 
23
- # Install peer dependencies
24
- pnpm add react react-dom tailwindcss
56
+ # Using npm
57
+ npm install @gulibs/react-vtable
58
+
59
+ # Using yarn
60
+ yarn add @gulibs/react-vtable
61
+
62
+ # Peer dependencies (if not already installed)
63
+ pnpm add react react-dom @tanstack/react-table tailwindcss
25
64
  ```
26
65
 
27
66
  ### Configuration
28
67
 
29
- #### Configure Tailwind CSS
68
+ #### Tailwind CSS Setup
69
+
70
+ Add the library path to your `tailwind.config.js`:
30
71
 
31
72
  ```javascript
32
73
  // tailwind.config.js
@@ -39,636 +80,545 @@ export default {
39
80
  }
40
81
  ```
41
82
 
42
- > 💡 **Note**: The library automatically injects its CSS styles, so no manual CSS imports are required.
83
+ > 💡 **Note**: The library automatically injects its CSS styles. No manual CSS imports are required.
43
84
 
44
- ### Usage
85
+ ### Quick Start
45
86
 
46
87
  ```tsx
47
88
  import { ProTable } from '@gulibs/react-vtable'
48
- // No need to manually import CSS, styles are automatically inlined
49
89
 
50
90
  function App() {
51
91
  const columns = [
52
- {
53
- title: 'Name',
54
- dataIndex: 'name',
55
- key: 'name',
56
- },
57
- {
58
- title: 'Age',
59
- dataIndex: 'age',
60
- key: 'age',
61
- },
92
+ { title: 'Name', dataIndex: 'name', key: 'name' },
93
+ { title: 'Age', dataIndex: 'age', key: 'age' },
94
+ { title: 'Email', dataIndex: 'email', key: 'email' },
62
95
  ]
63
96
 
64
97
  const dataSource = [
65
- { name: 'John', age: 30 },
66
- { name: 'Jane', age: 25 },
98
+ { id: 1, name: 'John Doe', age: 30, email: 'john@example.com' },
99
+ { id: 2, name: 'Jane Smith', age: 25, email: 'jane@example.com' },
67
100
  ]
68
101
 
69
102
  return (
70
103
  <ProTable
71
104
  columns={columns}
72
105
  dataSource={dataSource}
73
- rowKey="name"
106
+ rowKey="id"
74
107
  />
75
108
  )
76
109
  }
77
110
  ```
78
111
 
79
- ### Features
112
+ ### Core Concepts
80
113
 
81
- - **Powerful Table**: Built on TanStack Table for maximum flexibility
82
- - **shadcn/ui Components**: Beautiful, accessible UI components
83
- - **Drag & Drop**: Row and column reordering with @dnd-kit
84
- - **Search & Filter**: Advanced search and filtering capabilities with default behavior for local and remote data modes
85
- - **Pagination**: Built-in pagination with customizable options
86
- - **Column Settings**: Show/hide columns, drag to reorder
87
- - **Row Selection**: Single and multiple row selection with batch actions, **cross-page selection support** (selection state persists across pages when using `request` or `pagination.onChange`)
88
- - **Editable Rows**: Inline editing capabilities with various value types
89
- - **Fixed Columns**: Left and right fixed columns with automatic position calculation and subtle shadow effects (powered by TanStack Table column pinning)
90
- - **Tree Table**: Hierarchical data display with expandable/collapsible rows, custom expand icons, and accordion mode
91
- - **Internationalization**: Built-in i18n support with English and Chinese locales
92
- - **Horizontal Scrolling**: Configurable table width with scroll support
93
- - **Copy to Clipboard**: One-click copy functionality for cells
94
- - **Text Ellipsis**: Single-line and multi-line text truncation with ellipsis
95
- - **Tooltip**: Hover tooltips for cells and headers with customizable content
96
- - **Responsive**: Mobile-friendly design
97
- - **TypeScript**: Full TypeScript support
98
-
99
- ### Examples
100
-
101
- #### Basic Table
114
+ #### 1. Data Modes
102
115
 
116
+ **Local Data Mode** (using `dataSource`):
103
117
  ```tsx
104
118
  <ProTable
119
+ dataSource={localData}
105
120
  columns={columns}
106
- dataSource={dataSource}
107
121
  rowKey="id"
108
122
  />
109
123
  ```
110
124
 
111
- #### With Search
112
-
113
- The table provides **default search behavior** that works automatically:
114
-
115
- - **Local Data Mode** (no `request` prop): Search values are automatically converted to TanStack Table's `columnFilters`, and the table filters data in real-time using `getFilteredRowModel()`.
116
- - **Remote Data Mode** (with `request` prop): Search triggers a new data fetch with search parameters passed to the backend.
117
-
118
- The default filter function supports:
119
- - Case-insensitive string matching (contains) for **text-like** fields
120
- - **Exact match** for **enum/filter** fields (e.g. `valueEnum`, `filters`, `valueType: 'select' | 'status'`)
121
- - Array-based multi-select filtering (exact match for enum/filter fields, contains match for text fields)
122
- - Automatic handling of empty/null values
123
-
125
+ **Remote Data Mode** (using `request`):
124
126
  ```tsx
125
127
  <ProTable
128
+ request={async (params) => {
129
+ // params includes: current, pageSize, filters, sorter
130
+ const res = await fetchData(params)
131
+ return {
132
+ data: res.items,
133
+ total: res.total,
134
+ success: true
135
+ }
136
+ }}
126
137
  columns={columns}
127
- dataSource={dataSource}
128
138
  rowKey="id"
129
- search={{
130
- filterType: 'query',
131
- searchText: 'Search',
132
- resetText: 'Reset'
133
- }}
134
139
  />
135
140
  ```
136
141
 
137
- **Custom Search Behavior:**
142
+ #### 2. Search Behavior
143
+
144
+ The table provides **automatic search behavior**:
145
+
146
+ - **Local Mode**: Converts search values to TanStack Table's `columnFilters` and filters in real-time
147
+ - **Remote Mode**: Triggers data fetch with search parameters sent to backend
148
+
149
+ Default filter supports:
150
+ - Case-insensitive substring matching for text fields
151
+ - Exact match for enum/select fields (`valueEnum`, `filters`, `valueType: 'select'`)
152
+ - Array-based multi-select filtering
153
+ - Automatic empty/null value handling
138
154
 
139
- You can provide custom `onSearch` and `onReset` callbacks to override the default behavior:
155
+ #### 3. Pagination Modes
140
156
 
157
+ **Client-side Pagination** (default with `dataSource`):
141
158
  ```tsx
142
159
  <ProTable
143
- columns={columns}
144
- dataSource={dataSource}
145
- rowKey="id"
146
- search={{
147
- filterType: 'query',
148
- onSearch: (values) => {
149
- // Custom search logic
150
- console.log('Search values:', values)
151
- },
152
- onReset: () => {
153
- // Custom reset logic
154
- console.log('Reset search')
155
- }
156
- }}
160
+ dataSource={data}
161
+ pagination={{ pageSize: 10 }}
157
162
  />
158
163
  ```
159
164
 
160
- #### With Pagination
161
-
165
+ **Server-side Pagination** (with `request`):
162
166
  ```tsx
163
167
  <ProTable
164
- columns={columns}
165
- dataSource={dataSource}
166
- rowKey="id"
167
- pagination={{
168
- pageSize: 10,
169
- showSizeChanger: true
170
- }}
168
+ request={fetchData}
169
+ pagination={{ pageSize: 10 }}
171
170
  />
172
171
  ```
173
172
 
174
- #### Remote Pagination (with `request`)
175
-
176
- In **Remote Data Mode** (when you pass `request`), pagination works like this:
177
-
178
- - **`total` controls the page count**: return `total` from `request` (recommended) and/or pass `pagination.total`.
179
- - **Do not pass `pagination.current/pageSize` as constants** (e.g. `current: 1`), unless you intend to lock the table to that page. If you want controlled pagination, store them in state and update in `pagination.onChange`.
180
-
181
- **Uncontrolled (recommended if you don't need to control page state):**
182
-
173
+ **Manual Server Pagination** (without `request`):
183
174
  ```tsx
184
175
  <ProTable
185
- columns={columns}
186
- rowKey="id"
187
- request={async (params) => {
188
- // params includes: current, pageSize, ...filters, ...sorter
189
- const res = await fetchUsers(params)
190
- return { data: res.items, total: res.total, success: true }
191
- }}
176
+ dataSource={currentPageData}
192
177
  pagination={{
193
- pageSize: 10,
194
- showSizeChanger: true,
195
- showQuickJumper: true,
178
+ mode: 'server', // Important!
179
+ current: page,
180
+ pageSize: pageSize,
181
+ total: total,
182
+ onChange: (page, pageSize) => fetchPage(page, pageSize)
196
183
  }}
197
184
  />
198
185
  ```
199
186
 
200
- **Controlled (when you need to sync pagination with external state / URL):**
201
-
202
- ```tsx
203
- function Page() {
204
- const [pagination, setPagination] = useState({
205
- current: 1,
206
- pageSize: 10,
207
- total: 0,
208
- })
209
-
210
- return (
211
- <ProTable
212
- columns={columns}
213
- rowKey="id"
214
- request={async (params) => {
215
- const res = await fetchUsers(params)
216
- setPagination((prev) => ({ ...prev, total: res.total }))
217
- return { data: res.items, total: res.total, success: true }
218
- }}
219
- pagination={{
220
- current: pagination.current,
221
- pageSize: pagination.pageSize,
222
- total: pagination.total,
223
- showSizeChanger: true,
224
- showQuickJumper: true,
225
- onChange: (current, pageSize) => {
226
- setPagination((prev) => ({ ...prev, current, pageSize }))
227
- },
228
- }}
229
- />
230
- )
231
- }
232
- ```
187
+ ---
233
188
 
234
- #### Server Pagination (NO `request`, you fetch data yourself)
189
+ ## Complete API Reference
235
190
 
236
- If you **do not** use `request`, but you still want **server-side pagination** (i.e. `dataSource` only contains **current page items**),
237
- set `pagination.mode: 'server'` to prevent TanStack Table from slicing again.
191
+ ### ProTable Props
238
192
 
239
- ```tsx
240
- function Page() {
241
- const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 })
242
- const [data, setData] = useState<any[]>([])
193
+ #### Core Props
243
194
 
244
- const fetchPage = async (current: number, pageSize: number) => {
245
- const res = await fetchUsers({ current, pageSize })
246
- setData(res.items)
247
- setPagination((prev) => ({ ...prev, current, pageSize, total: res.total }))
248
- }
195
+ | Property | Type | Default | Description |
196
+ |----------|------|---------|-------------|
197
+ | `columns` | `ProColumn<T>[]` | `[]` | Column definitions |
198
+ | `dataSource` | `T[]` | `[]` | Local data array |
199
+ | `request` | `(params) => Promise<Response>` | - | Remote data fetcher |
200
+ | `rowKey` | `string \| (record) => string` | `'id'` | Unique row identifier |
201
+ | `loading` | `boolean` | `false` | Loading state |
202
+ | `defaultData` | `T[]` | `[]` | Default data for initial render |
203
+ | `postData` | `(data: T[]) => T[]` | - | Transform data after fetch |
204
+ | `params` | `Record<string, any>` | - | Extra params for `request` |
249
205
 
250
- useEffect(() => {
251
- void fetchPage(pagination.current, pagination.pageSize)
252
- }, [])
206
+ #### Layout Props
253
207
 
254
- return (
255
- <ProTable
256
- columns={columns}
257
- rowKey="id"
258
- dataSource={data}
259
- pagination={{
260
- mode: "server",
261
- current: pagination.current,
262
- pageSize: pagination.pageSize,
263
- total: pagination.total,
264
- onChange: (current, pageSize) => void fetchPage(current, pageSize),
265
- }}
266
- />
267
- )
268
- }
269
- ```
208
+ | Property | Type | Default | Description |
209
+ |----------|------|---------|-------------|
210
+ | `size` | `'small' \| 'middle' \| 'large'` | `'middle'` | Table size |
211
+ | `bordered` | `boolean` | `false` | Show table borders |
212
+ | `tableLayout` | `'auto' \| 'fixed'` | `'auto'` | Table layout algorithm |
213
+ | `scroll` | `{ x?: number \| string \| true; y?: number \| string }` | - | Scrollable area config |
214
+ | `sticky` | `boolean \| { offsetHeader?: number }` | `false` | Sticky header config |
215
+ | `showHeader` | `boolean` | `true` | Show table header |
216
+ | `className` | `string` | - | Custom class name |
217
+ | `style` | `CSSProperties` | - | Custom styles |
218
+ | `tableStyle` | `CSSProperties` | - | Table element styles |
219
+
220
+ #### Feature Props
221
+
222
+ | Property | Type | Default | Description |
223
+ |----------|------|---------|-------------|
224
+ | `search` | `ProTableSearch \| false` | `false` | Search form config |
225
+ | `toolbar` | `ProTableToolBar \| false` | `false` | Toolbar config |
226
+ | `pagination` | `ProTablePagination \| false` | `false` | Pagination config |
227
+ | `rowSelection` | `ProTableRowSelection<T>` | - | Row selection config |
228
+ | `batchActions` | `ProTableBatchActions<T>` | - | Batch actions config |
229
+ | `editable` | `ProTableEditable<T>` | - | Inline editing config |
230
+ | `expandable` | `ProTableExpandable<T> \| false` | - | Tree table config |
231
+ | `draggable` | `ProTableDraggable \| false` | - | Drag & drop config |
232
+ | `columnsState` | `ProColumnState` | - | Column state management |
233
+
234
+ #### Callback Props
235
+
236
+ | Property | Type | Description |
237
+ |----------|------|-------------|
238
+ | `onChange` | `(pagination, filters, sorter, extra) => void` | Table change callback |
239
+ | `onLoad` | `(dataSource: T[]) => void` | Called after data loaded |
240
+ | `onLoadingChange` | `(loading: boolean) => void` | Loading state change |
241
+ | `onRequestError` | `(error: Error) => void` | Request error callback |
242
+ | `onSubmit` | `(params: any) => void` | Search submit callback |
243
+ | `onReset` | `() => void` | Search reset callback |
244
+ | `onRow` | `(record, index) => HTMLAttributes` | Row props callback |
245
+
246
+ #### Advanced Props
247
+
248
+ | Property | Type | Description |
249
+ |----------|------|-------------|
250
+ | `headerTitle` | `ReactNode` | Table header title |
251
+ | `headerSubTitle` | `ReactNode` | Table header subtitle |
252
+ | `footer` | `(data) => ReactNode` | Table footer render |
253
+ | `emptyRender` | `ReactNode` | Empty state render |
254
+ | `tableRender` | `(props, dom, domList) => ReactNode` | Custom table render |
255
+ | `tableExtraRender` | `(props, data) => ReactNode` | Extra content after table |
256
+ | `paginationRender` | `(opts) => ReactNode` | Custom pagination render |
257
+ | `searchFormRender` | `(props, dom) => ReactNode` | Custom search form render |
258
+ | `actionRef` | `Ref<ProTableAction<T>>` | Table action reference |
259
+ | `formRef` | `Ref<FormInstance>` | Search form reference |
260
+ | `locale` | `ProTableLocale` | i18n locale config |
270
261
 
271
- #### Custom Pagination Rendering (`paginationRender`)
262
+ ---
272
263
 
273
- Use `paginationRender` when you want to **render your own pagination UI** (in any layout), while still reusing ProTable’s built-in
274
- pagination state and default Pagination component.
264
+ ### ProColumn Configuration
275
265
 
276
- Notes:
277
- - `paginationRender` receives `pagination`, `onChange`, and `defaultDom`.
278
- - If you are in **server pagination mode** (`pagination.mode: 'server'`) you must also provide `pagination.onChange` to fetch data.
266
+ #### Basic Props
279
267
 
280
- ```tsx
281
- <ProTable
282
- columns={columns}
283
- rowKey="id"
284
- dataSource={data}
285
- pagination={{
286
- mode: "server",
287
- current,
288
- pageSize,
289
- total,
290
- onChange: (page, pageSize) => void fetchPage(page, pageSize),
291
- }}
292
- paginationRender={({ pagination, onChange, defaultDom }) => (
293
- <div className="space-y-2">
294
- <div className="flex items-center justify-between rounded border p-2">
295
- <div className="text-sm text-muted-foreground">
296
- Page {pagination.current} / Size {pagination.pageSize} / Total {pagination.total}
297
- </div>
298
- <div className="flex gap-2">
299
- <button onClick={() => onChange(Math.max(1, pagination.current - 1), pagination.pageSize)}>
300
- Prev
301
- </button>
302
- <button onClick={() => onChange(pagination.current + 1, pagination.pageSize)}>
303
- Next
304
- </button>
305
- </div>
306
- </div>
307
- {/* reuse built-in Pagination UI */}
308
- {defaultDom}
309
- </div>
310
- )}
311
- />
312
- ```
268
+ | Property | Type | Description |
269
+ |----------|------|-------------|
270
+ | `title` | `string` | Column header text |
271
+ | `dataIndex` | `keyof T \| string \| string[]` | Data field path (supports nested: `'user.name'` or `['user', 'name']`) |
272
+ | `dataPath` | `string` | Alternative to dataIndex (string path only: `'user.name'`) |
273
+ | `key` | `string` | Unique column key |
274
+ | `width` | `number \| string` | Column width (required for fixed columns) |
275
+ | `align` | `'left' \| 'center' \| 'right'` | Text alignment |
276
+ | `fixed` | `'left' \| 'right'` | Pin column to left/right |
277
+
278
+ #### Display Props
279
+
280
+ | Property | Type | Description |
281
+ |----------|------|-------------|
282
+ | `valueType` | `ProFieldValueType` | Value display type (20+ types) |
283
+ | `valueEnum` | `Record<string, { text: string; status?: string; color?: string }>` | Enum value mapping |
284
+ | `render` | `(value, record, index) => ReactNode` | Custom cell render (takes priority over `valueType`) |
285
+ | `ellipsis` | `boolean \| ProColumnEllipsis` | Text ellipsis config |
286
+ | `tooltip` | `boolean \| ProColumnTooltip` | Tooltip config |
287
+ | `headerEllipsis` | `boolean \| ProColumnEllipsis` | Header ellipsis config |
288
+ | `headerTooltip` | `boolean \| ProColumnTooltip` | Header tooltip config |
289
+ | `copyable` | `boolean \| ProColumnCopyable` | Enable copy to clipboard |
290
+
291
+ #### Search Props
292
+
293
+ | Property | Type | Description |
294
+ |----------|------|-------------|
295
+ | `search` | `boolean \| ProColumnSearch` | Include in search form |
296
+ | `hideInSearch` | `boolean` | Hide in search form |
297
+ | `searchFormItemProps` | `FormItemProps` | Search form item props |
298
+ | `renderFormItem` | `(value, onChange, field, column) => ReactNode` | Custom search input render |
299
+
300
+ #### Table Display Props
301
+
302
+ | Property | Type | Description |
303
+ |----------|------|-------------|
304
+ | `hideInTable` | `boolean` | Hide in table |
305
+ | `sorter` | `boolean \| ((a, b) => number)` | Enable sorting |
306
+ | `filters` | `Array<{ text: string; value: any }>` | Filter options |
307
+ | `onFilter` | `(value, record) => boolean` | Filter function |
313
308
 
314
- #### With Internationalization
309
+ #### Edit Props
315
310
 
316
- ```tsx
317
- import { ProTable, zh_CN } from '@gulibs/react-vtable'
311
+ | Property | Type | Description |
312
+ |----------|------|-------------|
313
+ | `editable` | `boolean \| ((record) => boolean)` | Enable inline editing |
314
+ | `hideInForm` | `boolean` | Hide in edit form |
315
+ | `formItemProps` | `FormItemProps` | Form item props |
316
+ | `fieldProps` | `Record<string, any>` | Field component props |
318
317
 
319
- <ProTable
320
- columns={columns}
321
- dataSource={dataSource}
322
- rowKey="id"
323
- locale={zh_CN}
324
- />
325
- ```
318
+ ---
326
319
 
327
- #### With Fixed Columns
320
+ ### Value Types Reference
321
+
322
+ Supported `valueType` values:
323
+
324
+ | Type | Description | Example |
325
+ |------|-------------|---------|
326
+ | `text` | Plain text | "Hello World" |
327
+ | `textarea` | Multi-line text | Long content |
328
+ | `number` | Number | 123 |
329
+ | `money` | Currency | $1,234.56 |
330
+ | `percent` | Percentage | 75% |
331
+ | `date` | Date | 2024-01-01 |
332
+ | `dateTime` | Date with time | 2024-01-01 10:30:00 |
333
+ | `dateRange` | Date range | 2024-01-01 ~ 2024-01-31 |
334
+ | `select` | Select dropdown | Uses `valueEnum` |
335
+ | `status` | Status badge | Uses `valueEnum` |
336
+ | `tags` | Multiple tags | Array display |
337
+ | `switch` | Boolean switch | true/false |
338
+ | `avatar` | Avatar image | URL display |
339
+ | `image` | Image | URL display |
340
+ | `progress` | Progress bar | 0-100 |
341
+ | `code` | Code block | Monospace text |
342
+ | `fromNow` | Relative time | "2 hours ago" |
343
+ | `email` | Email address | With mailto link |
344
+ | `phone` | Phone number | Formatted |
345
+ | `url` | URL link | Clickable link |
346
+ | `color` | Color picker | Color swatch |
347
+ | `rate` | Rating stars | 1-5 stars |
348
+ | `custom` | Custom render | Use `render` function |
328
349
 
329
- Fixed columns are implemented using TanStack Table's column pinning feature. The library automatically calculates positions and applies subtle shadow effects to indicate fixed column boundaries.
350
+ ---
330
351
 
331
- ```tsx
332
- <ProTable
333
- columns={[
334
- {
335
- title: 'ID',
336
- dataIndex: 'id',
337
- key: 'id',
338
- width: 80,
339
- fixed: 'left' // Fixed to the left
340
- },
341
- {
342
- title: 'Name',
343
- dataIndex: 'name',
344
- key: 'name',
345
- width: 120
346
- },
347
- {
348
- title: 'Actions',
349
- dataIndex: 'actions',
350
- key: 'actions',
351
- width: 100,
352
- fixed: 'right' // Fixed to the right
353
- }
354
- ]}
355
- dataSource={dataSource}
356
- rowKey="id"
357
- scroll={{ x: 1000 }} // Enable horizontal scrolling
358
- tableLayout="fixed" // Recommended for fixed columns
359
- />
352
+ ### Search Configuration
353
+
354
+ ```typescript
355
+ interface ProTableSearch {
356
+ filterType?: 'query' | 'light' // Query: form above table, Light: inline filters
357
+ searchText?: string // Search button text
358
+ resetText?: string // Reset button text
359
+ submitText?: string // Submit button text
360
+ labelWidth?: number | 'auto' // Label width
361
+ span?: number // Grid column span
362
+ defaultCollapsed?: boolean // Initially collapsed
363
+ collapsed?: boolean // Controlled collapse state
364
+ onCollapse?: (collapsed: boolean) => void
365
+ optionRender?: (config, props, dom) => ReactNode[] // Custom action buttons
366
+ }
360
367
  ```
361
368
 
362
- **Important Notes:**
363
- - Fixed columns require a `width` property to calculate positions correctly
364
- - The library automatically handles position calculations using TanStack Table's `getStart()` and `getAfter()` methods
365
- - Subtle shadow effects are automatically applied to the last left-pinned column and first right-pinned column
366
- - No additional CSS is required - all styles are applied inline
367
-
368
- #### With Tree Table (Expandable Rows)
369
-
370
- Tree tables support hierarchical data structures with expandable/collapsible rows. The data structure should include a `children` property (or custom field name) containing nested rows.
369
+ **Example:**
371
370
 
372
371
  ```tsx
373
372
  <ProTable
374
- columns={columns}
375
- dataSource={[
376
- {
377
- id: 1,
378
- name: 'Parent Node',
379
- children: [
380
- { id: 11, name: 'Child Node 1' },
381
- { id: 12, name: 'Child Node 2' }
382
- ]
383
- }
384
- ]}
385
- rowKey="id"
386
- expandable={{
387
- childrenColumnName: 'children', // Default: 'children'
388
- defaultExpandAllRows: false, // Expand all rows by default
389
- defaultExpandedRowKeys: [1], // Initially expanded rows
390
- accordion: false, // Accordion mode (only one row expanded at a time)
391
- showExpandColumn: true, // Show expand icon column
392
- expandColumnWidth: 50, // Width of expand column
393
- onExpand: (expanded, record) => {
394
- console.log('Expand:', expanded, record)
395
- },
396
- onExpandedRowsChange: (expandedRowKeys) => {
397
- console.log('Expanded keys:', expandedRowKeys)
398
- },
399
- expandIcon: ({ expanded, onExpand, record }) => (
400
- <Button onClick={onExpand}>
401
- {expanded ? '▼' : '▶'}
402
- </Button>
403
- )
373
+ search={{
374
+ filterType: 'query',
375
+ searchText: 'Search',
376
+ resetText: 'Reset',
377
+ labelWidth: 'auto',
378
+ defaultCollapsed: false,
404
379
  }}
405
380
  />
406
381
  ```
407
382
 
408
- **Tree Table Features:**
409
- - **Controlled/Uncontrolled Mode**: Use `expandedRowKeys` for controlled mode, or `defaultExpandedRowKeys` for uncontrolled
410
- - **Default Expand All**: Set `defaultExpandAllRows: true` to expand all rows by default
411
- - **Accordion Mode**: Set `accordion: true` to allow only one row to be expanded at a time
412
- - **Custom Children Field**: Use `childrenColumnName` to specify a custom field name for children (default: `'children'`)
413
- - **Custom Expand Icon**: Provide a custom `expandIcon` function to customize the expand/collapse icon
414
- - **Expand Callbacks**: Use `onExpand` and `onExpandedRowsChange` to handle expand state changes
415
-
416
- #### With Editable Rows
383
+ ---
417
384
 
418
- ```tsx
419
- <ProTable
420
- columns={columns}
421
- dataSource={dataSource}
422
- rowKey="id"
423
- editable={{
424
- type: 'multiple',
425
- onSave: async (key, record, originRow) => {
426
- console.log('Save:', { key, record, originRow })
427
- // Handle save logic
428
- }
429
- }}
430
- />
385
+ ### Pagination Configuration
386
+
387
+ ```typescript
388
+ interface ProTablePagination {
389
+ mode?: 'client' | 'server' // Pagination mode
390
+ pageSize?: number // Items per page
391
+ current?: number // Current page (controlled)
392
+ total?: number // Total items
393
+ showSizeChanger?: boolean // Show page size selector
394
+ showQuickJumper?: boolean // Show quick jump input
395
+ showTotal?: (total, range) => ReactNode
396
+ pageSizeOptions?: string[] // Page size options
397
+ size?: 'default' | 'small'
398
+ simple?: boolean // Simple pagination
399
+ disabled?: boolean
400
+ onChange?: (page, pageSize) => void
401
+ onShowSizeChange?: (current, size) => void
402
+ }
431
403
  ```
432
404
 
433
- #### With Row Selection and Batch Actions
405
+ ---
434
406
 
435
- ```tsx
436
- <ProTable
437
- columns={columns}
438
- dataSource={dataSource}
439
- rowKey="id"
440
- rowSelection={{
441
- type: 'checkbox',
442
- onChange: (selectedRowKeys, selectedRows) => {
443
- console.log('Selected:', selectedRowKeys, selectedRows)
444
- },
445
- batchActions: {
446
- onDelete: (rows) => {
447
- console.log('Delete:', rows)
448
- },
449
- onExport: (rows) => {
450
- console.log('Export:', rows)
451
- }
452
- }
453
- }}
454
- />
407
+ ### Row Selection Configuration
408
+
409
+ ```typescript
410
+ interface ProTableRowSelection<T> {
411
+ type?: 'checkbox' | 'radio' // Selection type
412
+ selectedRowKeys?: React.Key[] // Controlled selection
413
+ onChange?: (keys, rows) => void
414
+ preserveSelectedRowKeys?: boolean // Keep selection across pages (default: true)
415
+ getCheckboxProps?: (record) => any
416
+ selections?: boolean | any[]
417
+ hideSelectAll?: boolean
418
+ fixed?: boolean // Fix selection column
419
+ columnWidth?: number | string
420
+ renderCell?: (checked, record, index, originNode) => ReactNode
421
+ alwaysShowAlert?: boolean // Always show batch actions bar
422
+ batchActions?: ProTableBatchActions<T> // Batch operations config
423
+ }
455
424
  ```
456
425
 
457
- **Cross-Page Selection Support:**
458
-
459
- When using `request` or `pagination.onChange` for remote data fetching, the table automatically maintains selection state across pages. This means:
426
+ #### Cross-Page Selection
460
427
 
461
- - Selections made on one page are preserved when navigating to other pages
462
- - The batch actions bar shows the **total count** of selected rows across all pages
463
- - When you return to a previously visited page, your selections are automatically restored
464
- - The `onChange` callback receives all selected row keys across all pages
428
+ When using `request` or `pagination.onChange`, selection automatically persists across pages:
465
429
 
466
430
  ```tsx
467
431
  <ProTable
468
- columns={columns}
469
- rowKey="id"
470
- request={async (params) => {
471
- const res = await fetchUsers(params)
472
- return { data: res.items, total: res.total, success: true }
473
- }}
432
+ request={fetchData}
474
433
  rowSelection={{
475
434
  type: 'checkbox',
435
+ preserveSelectedRowKeys: true, // Default: true
476
436
  onChange: (selectedRowKeys, selectedRows) => {
477
- // selectedRowKeys contains ALL selected keys across all pages
478
- // selectedRows contains rows from the current page only
479
- console.log('Total selected across all pages:', selectedRowKeys.length)
480
- },
481
- batchActions: {
482
- onDelete: (rows) => {
483
- // This will receive rows from current page only
484
- // Use selectedRowKeys from onChange for full selection
485
- console.log('Delete current page rows:', rows)
486
- }
487
- }
488
- }}
489
- pagination={{
490
- pageSize: 10,
491
- showSizeChanger: true,
492
- onChange: (current, pageSize) => {
493
- // Selection state is automatically preserved when page changes
437
+ // selectedRowKeys: all selected keys across all pages
438
+ // selectedRows: rows from current page only
439
+ console.log('Total selected:', selectedRowKeys.length)
494
440
  }
495
441
  }}
496
442
  />
497
443
  ```
498
444
 
499
- **Important Notes:**
500
- - Cross-page selection works automatically when using `request` or `pagination.onChange`
501
- - With `dataSource` (local data), selection works normally but doesn't need cross-page support
502
- - The batch actions bar always shows the total count of selected rows across all pages
503
- - Use `rowSelection.onChange` to get all selected keys if you need to perform operations on all selected rows
445
+ ---
446
+
447
+ ### Batch Actions Configuration (Updated API)
504
448
 
505
- **Automatic Selection Clearing:**
449
+ ```typescript
450
+ interface ProTableBatchActions<T> {
451
+ actions?: Array<ProTableBatchAction<T>> // Batch action list
452
+ maxVisibleActions?: number // Max buttons to show directly (default: 3)
453
+ hideClearButton?: boolean // Hide clear selection button
454
+ }
506
455
 
507
- After certain batch operations, the selection state is automatically cleared:
456
+ interface ProTableBatchAction<T> {
457
+ key: string // Unique action key
458
+ label: string // Button text
459
+ icon?: ReactNode // Button icon
460
+ onClick: (rows: T[]) => void // Click handler
461
+ variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'
462
+ clearSelection?: boolean // Clear selection after action (default: false)
463
+ showInMore?: boolean // Show in "More" menu (default: false)
464
+ isExportFormat?: boolean // Mark as export format (auto-groups multiple export formats)
465
+ }
466
+ ```
508
467
 
509
- - **Delete operation**: Automatically clears selection after deletion (since the data is removed)
510
- - **Archive operation**: Automatically clears selection after archiving
511
- - **Custom actions**: You can control whether to clear selection using the `clearSelection` option
468
+ **Example:**
512
469
 
513
470
  ```tsx
471
+ import { Trash2, Download, FileText, Mail } from 'lucide-react'
472
+
514
473
  <ProTable
515
474
  rowSelection={{
516
475
  type: 'checkbox',
517
476
  batchActions: {
518
- onDelete: (rows) => {
519
- // Delete operation - selection will be automatically cleared
520
- console.log('Delete:', rows)
521
- },
522
- onArchive: (rows) => {
523
- // Archive operation - selection will be automatically cleared
524
- console.log('Archive:', rows)
525
- },
526
- // Hide the clear button if needed
527
- hideClearButton: false,
528
- customActions: [
477
+ maxVisibleActions: 3, // Show max 3 buttons directly
478
+ actions: [
529
479
  {
530
- key: 'approve',
531
- label: 'Approve',
480
+ key: 'delete',
481
+ label: 'Delete',
482
+ icon: <Trash2 className="h-3 w-3 mr-1" />,
532
483
  onClick: (rows) => {
533
- console.log('Approve:', rows)
484
+ console.log('Delete:', rows)
534
485
  },
535
- // Control whether to clear selection after this action
536
- clearSelection: true, // Set to true to clear selection after this action
486
+ variant: 'destructive',
487
+ clearSelection: true, // Clear after delete
537
488
  },
538
489
  {
539
490
  key: 'export',
540
491
  label: 'Export',
492
+ icon: <Download className="h-3 w-3 mr-1" />,
541
493
  onClick: (rows) => {
542
494
  console.log('Export:', rows)
543
495
  },
544
- clearSelection: false, // Keep selection after export (default)
545
- }
546
- ]
547
- }
496
+ variant: 'outline',
497
+ },
498
+ {
499
+ key: 'exportExcel',
500
+ label: 'Export Excel',
501
+ icon: <FileText className="h-3 w-3 mr-1" />,
502
+ onClick: (rows) => {
503
+ console.log('Export Excel:', rows)
504
+ },
505
+ variant: 'outline',
506
+ isExportFormat: true, // Groups with other export formats
507
+ showInMore: true,
508
+ },
509
+ {
510
+ key: 'exportCSV',
511
+ label: 'Export CSV',
512
+ icon: <FileText className="h-3 w-3 mr-1" />,
513
+ onClick: (rows) => {
514
+ console.log('Export CSV:', rows)
515
+ },
516
+ variant: 'outline',
517
+ isExportFormat: true,
518
+ showInMore: true,
519
+ },
520
+ {
521
+ key: 'email',
522
+ label: 'Send Email',
523
+ icon: <Mail className="h-3 w-3 mr-1" />,
524
+ onClick: (rows) => {
525
+ console.log('Email:', rows)
526
+ },
527
+ variant: 'outline',
528
+ showInMore: true, // Move to "More" menu
529
+ },
530
+ ],
531
+ },
548
532
  }}
549
533
  />
550
534
  ```
551
535
 
552
- #### With Text Ellipsis and Tooltip
536
+ **Features:**
537
+ - Smart button grouping: buttons exceeding `maxVisibleActions` auto-move to "More" (⋯) menu
538
+ - Export format grouping: multiple `isExportFormat: true` actions auto-merge into dropdown
539
+ - Flexible action control: use `clearSelection` to auto-clear selection after certain actions
553
540
 
554
- ```tsx
555
- <ProTable
556
- columns={[
557
- {
558
- title: 'Description',
559
- dataIndex: 'description',
560
- key: 'description',
561
- width: 200,
562
- // Single-line ellipsis with tooltip
563
- ellipsis: true,
564
- tooltip: true
565
- },
566
- {
567
- title: 'Content',
568
- dataIndex: 'content',
569
- key: 'content',
570
- width: 250,
571
- // Multi-line ellipsis (2 rows) with tooltip
572
- ellipsis: {
573
- multiline: true,
574
- rows: 2
575
- },
576
- tooltip: true
577
- },
578
- {
579
- title: 'Custom Tooltip',
580
- dataIndex: 'name',
581
- key: 'name',
582
- width: 150,
583
- ellipsis: true,
584
- tooltip: {
585
- content: (value, record) => `Full: ${record.fullName || value}`,
586
- side: 'top',
587
- delayDuration: 300
588
- }
589
- },
590
- {
591
- title: 'Header with Ellipsis',
592
- dataIndex: 'email',
593
- key: 'email',
594
- width: 180,
595
- // Header-specific ellipsis and tooltip
596
- headerEllipsis: true,
597
- headerTooltip: {
598
- content: 'This is the full header title',
599
- side: 'bottom'
600
- },
601
- // Cell ellipsis and tooltip
602
- ellipsis: true,
603
- tooltip: true
604
- }
605
- ]}
606
- dataSource={dataSource}
607
- rowKey="id"
608
- />
541
+ ---
542
+
543
+ ### Editable Configuration
544
+
545
+ ```typescript
546
+ interface ProTableEditable<T> {
547
+ type?: 'single' | 'multiple' // Edit mode
548
+ form?: any // React Hook Form instance
549
+ formProps?: Record<string, any>
550
+ onSave?: (key, record, originRow) => Promise<void>
551
+ onDelete?: (key, record) => Promise<void>
552
+ onCancel?: (key, record, originRow) => void
553
+ actionRender?: (record, config) => ReactNode[] // Custom action buttons
554
+ deletePopconfirmMessage?: ReactNode
555
+ onlyOneLineEditorAlertMessage?: ReactNode
556
+ onlyAddOneLineAlertMessage?: ReactNode
557
+ }
609
558
  ```
610
559
 
611
- ### API Reference
560
+ ---
612
561
 
613
- #### ProTable Props
562
+ ### Expandable Configuration (Tree Table)
614
563
 
615
- | Prop | Type | Default | Description |
616
- |------|------|---------|-------------|
617
- | `columns` | `ProColumn[]` | `[]` | Table column definitions |
618
- | `dataSource` | `T[]` | `[]` | Table data source |
619
- | `rowKey` | `string \| (record: T) => string` | `'id'` | Unique key for each row |
620
- | `loading` | `boolean` | `false` | Loading state |
621
- | `pagination` | `ProTablePagination \| false` | `false` | Pagination configuration |
622
- | `search` | `ProTableSearch \| false` | `false` | Search form configuration |
623
- | `rowSelection` | `ProTableRowSelection` | `undefined` | Row selection configuration |
624
- | `editable` | `ProTableEditable` | `undefined` | Editable rows configuration |
625
- | `draggable` | `ProTableDraggable` | `undefined` | Drag and drop configuration |
626
- | `expandable` | `ProTableExpandable \| false` | `undefined` | Tree table expandable configuration |
627
- | `scroll` | `{ x?: number \| true; y?: number \| string }` | `undefined` | Scroll configuration |
628
- | `tableLayout` | `'auto' \| 'fixed'` | `'auto'` | Table layout mode |
629
- | `locale` | `ProTableLocale` | `en_US` | Internationalization locale |
630
- | `size` | `'small' \| 'middle' \| 'large'` | `'middle'` | Table size |
631
- | `bordered` | `boolean` | `false` | Show table borders |
564
+ ```typescript
565
+ interface ProTableExpandable<T> {
566
+ childrenColumnName?: string // Children field name (default: 'children')
567
+ defaultExpandedRowKeys?: React.Key[] // Default expanded rows
568
+ expandedRowKeys?: React.Key[] // Controlled expanded rows
569
+ onExpand?: (expanded, record) => void
570
+ onExpandedRowsChange?: (keys) => void
571
+ expandIcon?: (props: {
572
+ expanded: boolean
573
+ onExpand: () => void
574
+ record: T
575
+ }) => ReactNode
576
+ showExpandColumn?: boolean // Show expand column (default: true)
577
+ expandColumnWidth?: number // Expand column width (default: 50)
578
+ defaultExpandAllRows?: boolean // Expand all by default
579
+ accordion?: boolean // Accordion mode (only one expanded)
580
+ }
581
+ ```
632
582
 
633
- #### Column Configuration
583
+ ---
634
584
 
635
- | Prop | Type | Description |
636
- |------|------|-------------|
637
- | `title` | `string` | Column header text |
638
- | `dataIndex` | `string` | Data field name |
639
- | `key` | `string` | Unique column key |
640
- | `width` | `number` | Column width in pixels |
641
- | `fixed` | `'left' \| 'right'` | Fixed column position |
642
- | `sorter` | `boolean` | Enable sorting |
643
- | `editable` | `boolean` | Enable inline editing |
644
- | `copyable` | `boolean` | Enable copy to clipboard |
645
- | `ellipsis` | `boolean \| ProColumnEllipsis` | Enable text ellipsis (single-line or multi-line) |
646
- | `tooltip` | `boolean \| ProColumnTooltip` | Enable tooltip on hover |
647
- | `headerEllipsis` | `boolean \| ProColumnEllipsis` | Enable ellipsis for header (defaults to `ellipsis`) |
648
- | `headerTooltip` | `boolean \| ProColumnTooltip` | Enable tooltip for header (defaults to `tooltip`) |
649
- | `search` | `boolean` | Include in search form |
650
- | `valueType` | `ProFieldValueType` | Value type for rendering |
651
- | `valueEnum` | `Record<string, { text: string; status?: string }>` | Enum values for select |
652
- | `filters` | `Array<{ text: string; value: any }>` | Filter options |
585
+ ### Ellipsis Configuration
653
586
 
654
- #### Ellipsis Configuration
587
+ ```typescript
588
+ interface ProColumnEllipsis {
589
+ multiline?: boolean // Multi-line ellipsis
590
+ rows?: number // Number of rows (1-5, default: 2)
591
+ }
592
+ ```
655
593
 
656
- The `ellipsis` and `headerEllipsis` properties support the following configurations:
594
+ **Examples:**
657
595
 
658
596
  ```tsx
659
597
  // Single-line ellipsis
660
598
  ellipsis: true
661
599
 
662
- // Multi-line ellipsis
600
+ // Multi-line ellipsis (2 lines)
663
601
  ellipsis: {
664
602
  multiline: true,
665
- rows: 2 // Number of lines (1-5)
603
+ rows: 2
666
604
  }
667
605
  ```
668
606
 
669
- #### Tooltip Configuration
607
+ ---
670
608
 
671
- The `tooltip` and `headerTooltip` properties support the following configurations:
609
+ ### Tooltip Configuration
610
+
611
+ ```typescript
612
+ interface ProColumnTooltip<T> {
613
+ content?: ReactNode | ((value, record, index) => ReactNode)
614
+ side?: 'top' | 'right' | 'bottom' | 'left'
615
+ align?: 'start' | 'center' | 'end'
616
+ delayDuration?: number
617
+ disabled?: boolean
618
+ }
619
+ ```
620
+
621
+ **Examples:**
672
622
 
673
623
  ```tsx
674
624
  // Simple tooltip (shows cell value)
@@ -677,821 +627,674 @@ tooltip: true
677
627
  // Custom tooltip
678
628
  tooltip: {
679
629
  content: 'Custom tooltip text',
680
- side: 'top' | 'right' | 'bottom' | 'left',
681
- align: 'start' | 'center' | 'end',
682
- delayDuration: 300,
683
- disabled: false
630
+ side: 'top',
631
+ delayDuration: 300
684
632
  }
685
633
 
686
634
  // Function-based tooltip
687
635
  tooltip: {
688
- content: (value, record, index) => `Full: ${value}\nID: ${record.id}`,
636
+ content: (value, record) => `Full: ${value} (ID: ${record.id})`,
689
637
  side: 'top'
690
638
  }
691
639
  ```
692
640
 
693
- **Note**: When `ellipsis` is enabled and `tooltip` is `true`, the tooltip will automatically show the full original value (not the rendered value) when text is truncated.
694
-
695
- #### Expandable (Tree Table) Configuration
696
-
697
- The `expandable` property supports the following configurations:
698
-
699
- ```tsx
700
- expandable: {
701
- // Custom children field name (default: 'children')
702
- childrenColumnName?: string
703
-
704
- // Default expanded row keys (uncontrolled mode)
705
- defaultExpandedRowKeys?: React.Key[]
706
-
707
- // Currently expanded row keys (controlled mode)
708
- expandedRowKeys?: React.Key[]
709
-
710
- // Expand/collapse callback
711
- onExpand?: (expanded: boolean, record: T) => void
712
-
713
- // Expanded rows change callback
714
- onExpandedRowsChange?: (expandedRowKeys: React.Key[]) => void
715
-
716
- // Custom expand icon
717
- expandIcon?: (props: {
718
- expanded: boolean
719
- onExpand: () => void
720
- record: T
721
- }) => ReactNode
722
-
723
- // Show expand column (default: true)
724
- showExpandColumn?: boolean
725
-
726
- // Expand column width (default: 50)
727
- expandColumnWidth?: number
728
-
729
- // Expand all rows by default (default: false)
730
- defaultExpandAllRows?: boolean
641
+ ---
731
642
 
732
- // Accordion mode - only one row expanded at a time (default: false)
733
- accordion?: boolean
643
+ ### Action Reference
644
+
645
+ Access table methods via `actionRef`:
646
+
647
+ ```typescript
648
+ interface ProTableAction<T> {
649
+ reload: (resetPageIndex?: boolean) => Promise<void> // Reload data
650
+ reloadAndRest: () => Promise<void> // Reload and reset to page 1
651
+ reset: () => void // Reset filters and search
652
+ clearSelected: () => void // Clear row selection
653
+ startEditable: (rowKey) => boolean // Start editing row
654
+ cancelEditable: (rowKey) => boolean // Cancel editing row
655
+ saveEditable: (rowKey, record) => boolean // Save edited row
656
+ getRowData: (rowKey) => T | undefined // Get row data
657
+ getTableData: () => T[] // Get all table data
658
+ setTableData: (data: T[]) => void // Set table data
734
659
  }
735
660
  ```
736
661
 
737
- ### Development
738
-
739
- ```bash
740
- # Install dependencies
741
- pnpm install
742
-
743
- # Build the library
744
- pnpm run build
745
-
746
- # Start development server
747
- pnpm run dev
748
- ```
749
-
750
- ### License
751
-
752
- MIT
753
-
754
- ---
755
-
756
- ## Chinese
757
-
758
- 一个基于 shadcn/ui 组件和 TanStack Table 构建的强大 React 表格组件库。
662
+ **Example:**
759
663
 
760
- ### 前置要求
761
-
762
- 此库需要您的项目中安装并配置 **Tailwind CSS**。
664
+ ```tsx
665
+ const actionRef = useRef<ProTableAction<DataType>>(null)
763
666
 
764
- ### 安装
667
+ // Reload data
668
+ actionRef.current?.reload()
765
669
 
766
- ```bash
767
- # 安装库
768
- pnpm add @gulibs/react-vtable
670
+ // Reset filters
671
+ actionRef.current?.reset()
769
672
 
770
- # 安装对等依赖
771
- pnpm add react react-dom tailwindcss
673
+ // Clear selection
674
+ actionRef.current?.clearSelected()
772
675
  ```
773
676
 
774
- ### 配置
775
-
776
- #### 配置 Tailwind CSS
777
-
778
- ```javascript
779
- // tailwind.config.js
780
- export default {
781
- content: [
782
- './src/**/*.{js,ts,jsx,tsx}',
783
- './node_modules/@gulibs/react-vtable/dist/**/*.js'
784
- ],
785
- // 无需额外配置 - 样式会自动注入
786
- }
787
- ```
677
+ ---
788
678
 
789
- > 💡 **注意**: 库会自动注入其 CSS 样式,因此无需手动导入 CSS。
679
+ ## Advanced Examples
790
680
 
791
- ### 使用方法
681
+ ### 1. Remote Data with Search and Pagination
792
682
 
793
683
  ```tsx
794
- import { ProTable } from '@gulibs/react-vtable'
795
- // 无需手动导入 CSS,样式已自动内联
796
-
797
- function App() {
798
- const columns = [
799
- {
800
- title: '姓名',
801
- dataIndex: 'name',
802
- key: 'name',
803
- },
684
+ <ProTable
685
+ request={async (params) => {
686
+ // params includes: current, pageSize, keyword, ...filters, ...sorter
687
+ const response = await fetch('/api/users', {
688
+ method: 'POST',
689
+ body: JSON.stringify(params)
690
+ })
691
+ const data = await response.json()
692
+ return {
693
+ data: data.items,
694
+ total: data.total,
695
+ success: true
696
+ }
697
+ }}
698
+ columns={[
699
+ { title: 'Name', dataIndex: 'name', search: true },
700
+ { title: 'Email', dataIndex: 'email', search: true },
804
701
  {
805
- title: '年龄',
806
- dataIndex: 'age',
807
- key: 'age',
702
+ title: 'Status',
703
+ dataIndex: 'status',
704
+ valueType: 'select',
705
+ search: true,
706
+ valueEnum: {
707
+ active: { text: 'Active', status: 'success' },
708
+ inactive: { text: 'Inactive', status: 'default' },
709
+ },
808
710
  },
809
- ]
810
-
811
- const dataSource = [
812
- { name: '张三', age: 30 },
813
- { name: '李四', age: 25 },
814
- ]
815
-
816
- return (
817
- <ProTable
818
- columns={columns}
819
- dataSource={dataSource}
820
- rowKey="name"
821
- />
822
- )
823
- }
824
- ```
825
-
826
- ### 功能特性
827
-
828
- - **强大表格**: 基于 TanStack Table 构建,提供最大灵活性
829
- - **shadcn/ui 组件**: 美观、可访问的 UI 组件
830
- - **拖拽排序**: 使用 @dnd-kit 实现行和列重排序
831
- - **搜索筛选**: 高级搜索和筛选功能,支持本地和远程数据模式的默认行为
832
- - **分页**: 内置分页,支持自定义选项
833
- - **列设置**: 显示 / 隐藏列,拖拽重排序
834
- - **行选择**: 单选和多选行,支持批量操作,**跨页选中支持**(使用 `request` 或 `pagination.onChange` 时,选中状态会在切换页面时保留)
835
- - **可编辑行**: 内联编辑功能,支持多种值类型
836
- - **固定列**: 左右固定列,自动计算位置并应用柔和阴影效果(基于 TanStack Table column pinning)
837
- - **树形表格**: 层次数据展示,支持展开 / 收起行、自定义展开图标和手风琴模式
838
- - **国际化**: 内置 i18n 支持,提供中英文语言包
839
- - **横向滚动**: 可配置表格宽度,支持滚动
840
- - **复制功能**: 一键复制单元格内容
841
- - **文本省略**: 单行和多行文本截断,支持省略号
842
- - **提示框**: 单元格和表头悬停提示,支持自定义内容
843
- - **响应式**: 移动端友好设计
844
- - **TypeScript**: 完整的 TypeScript 支持
845
-
846
- ### 示例
847
-
848
- #### 基础表格
849
-
850
- ```tsx
851
- <ProTable
852
- columns={columns}
853
- dataSource={dataSource}
711
+ ]}
712
+ search={{ filterType: 'query' }}
713
+ pagination={{
714
+ pageSize: 10,
715
+ showSizeChanger: true,
716
+ showQuickJumper: true,
717
+ }}
854
718
  rowKey="id"
855
719
  />
856
720
  ```
857
721
 
858
- #### 带搜索
859
-
860
- 表格提供了**默认搜索行为**,会自动工作:
861
-
862
- - **本地数据模式**(无 `request` 属性):搜索值会自动转换为 TanStack Table 的 `columnFilters`,表格使用 `getFilteredRowModel()` 实时过滤数据。
863
- - **远程数据模式**(有 `request` 属性):搜索会触发新的数据获取,搜索参数会传递给后端。
864
-
865
- 默认过滤函数支持:
866
- - **文本类字段**:不区分大小写的字符串匹配(包含)
867
- - **枚举 / 筛选类字段**:精确匹配(例如 `valueEnum`、`filters`、`valueType: 'select' | 'status'`)
868
- - 基于数组的多选过滤(枚举 / 筛选类字段为精确匹配;文本类字段为包含匹配)
869
- - 自动处理空值 /null 值
722
+ ### 2. Fixed Columns with Horizontal Scroll
870
723
 
871
724
  ```tsx
872
725
  <ProTable
873
- columns={columns}
874
- dataSource={dataSource}
726
+ columns={[
727
+ {
728
+ title: 'ID',
729
+ dataIndex: 'id',
730
+ width: 80,
731
+ fixed: 'left', // Pin to left
732
+ },
733
+ {
734
+ title: 'Name',
735
+ dataIndex: 'name',
736
+ width: 150,
737
+ fixed: 'left',
738
+ },
739
+ { title: 'Email', dataIndex: 'email', width: 200 },
740
+ { title: 'Phone', dataIndex: 'phone', width: 150 },
741
+ { title: 'Address', dataIndex: 'address', width: 300 },
742
+ {
743
+ title: 'Actions',
744
+ key: 'actions',
745
+ width: 100,
746
+ fixed: 'right', // Pin to right
747
+ render: (_, record) => (
748
+ <Button size="sm">Edit</Button>
749
+ ),
750
+ },
751
+ ]}
752
+ dataSource={data}
753
+ scroll={{ x: 1200 }} // Enable horizontal scroll
754
+ tableLayout="fixed" // Required for fixed columns
875
755
  rowKey="id"
876
- search={{
877
- filterType: 'query',
878
- searchText: '搜索',
879
- resetText: '重置'
880
- }}
881
756
  />
882
757
  ```
883
758
 
884
- **自定义搜索行为:**
885
-
886
- 您可以提供自定义的 `onSearch` 和 `onReset` 回调来覆盖默认行为:
759
+ ### 3. Tree Table with Expandable Rows
887
760
 
888
761
  ```tsx
889
762
  <ProTable
890
- columns={columns}
891
- dataSource={dataSource}
892
- rowKey="id"
893
- search={{
894
- filterType: 'query',
895
- onSearch: (values) => {
896
- // 自定义搜索逻辑
897
- console.log('搜索值:', values)
763
+ columns={[
764
+ { title: 'Name', dataIndex: 'name', width: 200 },
765
+ { title: 'Size', dataIndex: 'size' },
766
+ { title: 'Type', dataIndex: 'type' },
767
+ ]}
768
+ dataSource={[
769
+ {
770
+ id: 1,
771
+ name: 'Folder 1',
772
+ type: 'folder',
773
+ children: [
774
+ { id: 11, name: 'File 1.1', type: 'file', size: '1.2 MB' },
775
+ { id: 12, name: 'File 1.2', type: 'file', size: '2.5 MB' },
776
+ ],
898
777
  },
899
- onReset: () => {
900
- // 自定义重置逻辑
901
- console.log('重置搜索')
902
- }
778
+ {
779
+ id: 2,
780
+ name: 'Folder 2',
781
+ type: 'folder',
782
+ children: [
783
+ { id: 21, name: 'File 2.1', type: 'file', size: '3.1 MB' },
784
+ ],
785
+ },
786
+ ]}
787
+ expandable={{
788
+ defaultExpandAllRows: false,
789
+ accordion: false, // Allow multiple rows expanded
903
790
  }}
791
+ rowKey="id"
904
792
  />
905
793
  ```
906
794
 
907
- #### 带分页
795
+ ### 4. Inline Editing
908
796
 
909
797
  ```tsx
798
+ const [editableKeys, setEditableKeys] = useState<React.Key[]>([])
799
+
910
800
  <ProTable
911
- columns={columns}
912
- dataSource={dataSource}
913
- rowKey="id"
914
- pagination={{
915
- pageSize: 10,
916
- showSizeChanger: true
801
+ columns={[
802
+ {
803
+ title: 'Name',
804
+ dataIndex: 'name',
805
+ editable: true,
806
+ },
807
+ {
808
+ title: 'Age',
809
+ dataIndex: 'age',
810
+ valueType: 'number',
811
+ editable: true,
812
+ },
813
+ {
814
+ title: 'Email',
815
+ dataIndex: 'email',
816
+ editable: true,
817
+ },
818
+ ]}
819
+ dataSource={data}
820
+ editable={{
821
+ type: 'multiple',
822
+ editableKeys,
823
+ onChange: setEditableKeys,
824
+ onSave: async (key, record, originRow) => {
825
+ // Save to backend
826
+ await updateUser(key, record)
827
+ message.success('Saved successfully')
828
+ },
917
829
  }}
830
+ rowKey="id"
918
831
  />
919
832
  ```
920
833
 
921
- #### 远程分页(配合 `request`)
922
-
923
- 在**远程数据模式**(传入 `request`)下,分页规则如下:
924
-
925
- - **`total` 决定页码数量**:推荐让 `request` 返回 `{ total }`,也可以 / 同时传入 `pagination.total`。
926
- - **不要把 `pagination.current/pageSize` 写死**(例如 `current: 1`),除非你就是想把表格锁死在那一页;如果需要受控分页,请把它们放到 state 里,并在 `pagination.onChange` 里更新。
927
-
928
- **非受控(推荐:不需要外部同步分页状态时)**:
834
+ ### 5. Drag & Drop Reordering
929
835
 
930
836
  ```tsx
931
837
  <ProTable
932
838
  columns={columns}
933
- rowKey="id"
934
- request={async (params) => {
935
- // params 包含:current, pageSize, ...filters, ...sorter
936
- const res = await fetchUsers(params)
937
- return { data: res.items, total: res.total, success: true }
938
- }}
939
- pagination={{
940
- pageSize: 10,
941
- showSizeChanger: true,
942
- showQuickJumper: true,
839
+ dataSource={data}
840
+ draggable={{
841
+ enabled: true,
842
+ handle: true, // Show drag handle
843
+ onDragEnd: (result) => {
844
+ const { items, oldIndex, newIndex } = result
845
+ console.log('New order:', items)
846
+ // Update backend order
847
+ updateOrder(items.map(item => item.id))
848
+ },
943
849
  }}
850
+ rowKey="id"
944
851
  />
945
852
  ```
946
853
 
947
- **受控(需要把分页状态同步到外部 state / URL 时)**:
948
-
949
- ```tsx
950
- function Page() {
951
- const [pagination, setPagination] = useState({
952
- current: 1,
953
- pageSize: 10,
954
- total: 0,
955
- })
956
-
957
- return (
958
- <ProTable
959
- columns={columns}
960
- rowKey="id"
961
- request={async (params) => {
962
- const res = await fetchUsers(params)
963
- setPagination((prev) => ({ ...prev, total: res.total }))
964
- return { data: res.items, total: res.total, success: true }
965
- }}
966
- pagination={{
967
- current: pagination.current,
968
- pageSize: pagination.pageSize,
969
- total: pagination.total,
970
- showSizeChanger: true,
971
- showQuickJumper: true,
972
- onChange: (current, pageSize) => {
973
- setPagination((prev) => ({ ...prev, current, pageSize }))
974
- },
975
- }}
976
- />
977
- )
978
- }
979
- ```
980
-
981
- #### 服务端分页(不传 `request`,外部自行请求)
982
-
983
- 如果你**不使用** `request`,但希望实现**服务端分页**(也就是 `dataSource` 只提供“当前页 items”),请设置
984
- `pagination.mode: 'server'`,避免 TanStack Table 再次 slice 导致第 2 页为空 / 数据错乱。
985
-
986
- ```tsx
987
- function Page() {
988
- const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 })
989
- const [data, setData] = useState<any[]>([])
990
-
991
- const fetchPage = async (current: number, pageSize: number) => {
992
- const res = await fetchUsers({ current, pageSize })
993
- setData(res.items)
994
- setPagination((prev) => ({ ...prev, current, pageSize, total: res.total }))
995
- }
996
-
997
- useEffect(() => {
998
- void fetchPage(pagination.current, pagination.pageSize)
999
- }, [])
1000
-
1001
- return (
1002
- <ProTable
1003
- columns={columns}
1004
- rowKey="id"
1005
- dataSource={data}
1006
- pagination={{
1007
- mode: "server",
1008
- current: pagination.current,
1009
- pageSize: pagination.pageSize,
1010
- total: pagination.total,
1011
- onChange: (current, pageSize) => void fetchPage(current, pageSize),
1012
- }}
1013
- />
1014
- )
1015
- }
1016
- ```
1017
-
1018
- #### 自定义分页渲染(`paginationRender`)
1019
-
1020
- 当你想把分页 UI 放到任意布局 / 卡片里,并且仍复用 ProTable 内置分页状态 / 逻辑时,使用 `paginationRender`:
1021
-
1022
- - `paginationRender` 会拿到 `pagination`、`onChange`、`defaultDom`
1023
- - 如果你是 **服务端分页模式**(`pagination.mode: 'server'`),务必同时提供 `pagination.onChange` 去请求数据
854
+ ### 6. Custom Cell Rendering
1024
855
 
1025
856
  ```tsx
1026
857
  <ProTable
1027
- columns={columns}
1028
- rowKey="id"
1029
- dataSource={data}
1030
- pagination={{
1031
- mode: "server",
1032
- current,
1033
- pageSize,
1034
- total,
1035
- onChange: (page, pageSize) => void fetchPage(page, pageSize),
1036
- }}
1037
- paginationRender={({ pagination, onChange, defaultDom }) => (
1038
- <div className="space-y-2">
1039
- <div className="flex items-center justify-between rounded border p-2">
1040
- <div className="text-sm text-muted-foreground">
1041
- 自定义分页:第 {pagination.current} 页 / 每页 {pagination.pageSize} 条 / 共 {pagination.total} 条
1042
- </div>
1043
- <div className="flex gap-2">
1044
- <button onClick={() => onChange(Math.max(1, pagination.current - 1), pagination.pageSize)}>
1045
- 上一页
1046
- </button>
1047
- <button onClick={() => onChange(pagination.current + 1, pagination.pageSize)}>
1048
- 下一页
1049
- </button>
858
+ columns={[
859
+ {
860
+ title: 'Avatar',
861
+ dataIndex: 'avatar',
862
+ valueType: 'avatar',
863
+ render: (_, record) => (
864
+ <Avatar>
865
+ <AvatarImage src={record.avatar} />
866
+ <AvatarFallback>{record.name[0]}</AvatarFallback>
867
+ </Avatar>
868
+ ),
869
+ },
870
+ {
871
+ title: 'Status',
872
+ dataIndex: 'status',
873
+ valueType: 'status',
874
+ valueEnum: {
875
+ active: { text: 'Active', status: 'success' },
876
+ inactive: { text: 'Inactive', status: 'default' },
877
+ pending: { text: 'Pending', status: 'warning' },
878
+ },
879
+ render: (_, record) => (
880
+ <Badge variant={record.status === 'active' ? 'success' : 'secondary'}>
881
+ {record.status}
882
+ </Badge>
883
+ ),
884
+ },
885
+ {
886
+ title: 'Progress',
887
+ dataIndex: 'progress',
888
+ valueType: 'progress',
889
+ render: (_, record) => (
890
+ <div className="flex items-center gap-2">
891
+ <Progress value={record.progress} className="w-20" />
892
+ <span className="text-xs">{record.progress}%</span>
1050
893
  </div>
1051
- </div>
1052
- {defaultDom}
1053
- </div>
1054
- )}
894
+ ),
895
+ },
896
+ ]}
897
+ dataSource={data}
898
+ rowKey="id"
1055
899
  />
1056
900
  ```
1057
901
 
1058
- #### 带国际化
902
+ ### 7. Multi-line Ellipsis with Tooltip
1059
903
 
1060
904
  ```tsx
1061
- import { ProTable, zh_CN } from '@gulibs/react-vtable'
1062
-
1063
905
  <ProTable
1064
- columns={columns}
1065
- dataSource={dataSource}
906
+ columns={[
907
+ {
908
+ title: 'Description',
909
+ dataIndex: 'description',
910
+ width: 300,
911
+ ellipsis: {
912
+ multiline: true,
913
+ rows: 3, // Show 3 lines max
914
+ },
915
+ tooltip: {
916
+ content: (value) => value,
917
+ side: 'top',
918
+ },
919
+ },
920
+ {
921
+ title: 'Long Title Column with Header Tooltip',
922
+ dataIndex: 'data',
923
+ width: 150,
924
+ headerEllipsis: true, // Header ellipsis
925
+ headerTooltip: {
926
+ content: 'This is a very long header title that needs tooltip',
927
+ side: 'bottom',
928
+ },
929
+ ellipsis: true, // Cell ellipsis
930
+ tooltip: true, // Cell tooltip
931
+ },
932
+ ]}
933
+ dataSource={data}
1066
934
  rowKey="id"
1067
- locale={zh_CN}
1068
935
  />
1069
936
  ```
1070
937
 
1071
- #### 带固定列
1072
-
1073
- 固定列功能基于 TanStack Table 的 column pinning 实现。库会自动计算位置并应用柔和的阴影效果来标识固定列边界。
938
+ ### 8. Custom Search Form
1074
939
 
1075
940
  ```tsx
1076
941
  <ProTable
1077
942
  columns={[
1078
943
  {
1079
- title: 'ID',
1080
- dataIndex: 'id',
1081
- key: 'id',
1082
- width: 80,
1083
- fixed: 'left' // 固定在左侧
944
+ title: 'Name',
945
+ dataIndex: 'name',
946
+ search: true,
1084
947
  },
1085
948
  {
1086
- title: '姓名',
1087
- dataIndex: 'name',
1088
- key: 'name',
1089
- width: 120
949
+ title: 'Date Range',
950
+ dataIndex: 'dateRange',
951
+ valueType: 'dateRange',
952
+ search: true,
953
+ renderFormItem: (value, onChange) => (
954
+ <DateRangePicker
955
+ value={value}
956
+ onChange={onChange}
957
+ />
958
+ ),
1090
959
  },
1091
960
  {
1092
- title: '操作',
1093
- dataIndex: 'actions',
1094
- key: 'actions',
1095
- width: 100,
1096
- fixed: 'right' // 固定在右侧
1097
- }
961
+ title: 'Custom Filter',
962
+ dataIndex: 'customField',
963
+ search: {
964
+ transform: (value) => {
965
+ // Transform before sending to backend
966
+ return { customFieldQuery: value.toUpperCase() }
967
+ },
968
+ },
969
+ renderFormItem: (value, onChange) => (
970
+ <CustomFilterComponent
971
+ value={value}
972
+ onChange={onChange}
973
+ />
974
+ ),
975
+ },
1098
976
  ]}
1099
- dataSource={dataSource}
977
+ request={fetchData}
978
+ search={{ filterType: 'query' }}
1100
979
  rowKey="id"
1101
- scroll={{ x: 1000 }} // 启用横向滚动
1102
- tableLayout="fixed" // 固定列推荐使用 fixed 布局
1103
980
  />
1104
981
  ```
1105
982
 
1106
- **重要提示:**
1107
- - 固定列需要设置 `width` 属性才能正确计算位置
1108
- - 库会自动使用 TanStack Table 的 `getStart()` 和 `getAfter()` 方法计算位置
1109
- - 最后一个左侧固定列和第一个右侧固定列会自动应用柔和的阴影效果
1110
- - 无需额外 CSS - 所有样式都通过内联样式应用
983
+ ---
984
+
985
+ ## Best Practices
1111
986
 
1112
- #### 带树形表格(可展开行)
987
+ ### 1. Performance Optimization
1113
988
 
1114
- 树形表格支持层次数据结构,可展开 / 收起行。数据结构应包含 `children` 属性(或自定义字段名)来存储嵌套行。
989
+ #### Use `rowKey` Correctly
990
+ Always provide a stable, unique `rowKey`:
1115
991
 
1116
992
  ```tsx
1117
- <ProTable
1118
- columns={columns}
1119
- dataSource={[
1120
- {
1121
- id: 1,
1122
- name: '父节点',
1123
- children: [
1124
- { id: 11, name: '子节点 1' },
1125
- { id: 12, name: '子节点 2' }
1126
- ]
1127
- }
1128
- ]}
1129
- rowKey="id"
1130
- expandable={{
1131
- childrenColumnName: 'children', // 默认: 'children'
1132
- defaultExpandAllRows: false, // 默认展开所有行
1133
- defaultExpandedRowKeys: [1], // 初始展开的行
1134
- accordion: false, // 手风琴模式(同时只能展开一行)
1135
- showExpandColumn: true, // 显示展开图标列
1136
- expandColumnWidth: 50, // 展开列宽度
1137
- onExpand: (expanded, record) => {
1138
- console.log('展开:', expanded, record)
1139
- },
1140
- onExpandedRowsChange: (expandedRowKeys) => {
1141
- console.log('展开的行:', expandedRowKeys)
1142
- },
1143
- expandIcon: ({ expanded, onExpand, record }) => (
1144
- <Button onClick={onExpand}>
1145
- {expanded ? '▼' : '▶'}
1146
- </Button>
1147
- )
1148
- }}
1149
- />
993
+ // Good: Use unique ID
994
+ <ProTable rowKey="id" />
995
+
996
+ // Good: Use function for complex keys
997
+ <ProTable rowKey={(record) => `${record.type}-${record.id}`} />
998
+
999
+ // Bad: Use index (causes re-render issues)
1000
+ <ProTable rowKey={(record, index) => index} />
1150
1001
  ```
1151
1002
 
1152
- **树形表格特性:**
1153
- - **受控 / 非受控模式**: 使用 `expandedRowKeys` 实现受控模式,或使用 `defaultExpandedRowKeys` 实现非受控模式
1154
- - **默认全部展开**: 设置 `defaultExpandAllRows: true` 来默认展开所有行
1155
- - **手风琴模式**: 设置 `accordion: true` 来同时只允许展开一行
1156
- - **自定义子字段**: 使用 `childrenColumnName` 指定自定义的子字段名(默认:`'children'`)
1157
- - **自定义展开图标**: 提供自定义 `expandIcon` 函数来定制展开 / 收起图标
1158
- - **展开回调**: 使用 `onExpand` 和 `onExpandedRowsChange` 来处理展开状态变化
1003
+ #### Memoize Large Data
1004
+ For large datasets, memoize your data:
1005
+
1006
+ ```tsx
1007
+ const data = useMemo(() => generateLargeData(), [])
1008
+
1009
+ <ProTable dataSource={data} />
1010
+ ```
1159
1011
 
1160
- #### 带可编辑行
1012
+ #### Optimize Column Rendering
1013
+ Use `useMemo` for column definitions with complex render functions:
1161
1014
 
1015
+ ```tsx
1016
+ const columns = useMemo(() => [
1017
+ {
1018
+ title: 'Name',
1019
+ render: (_, record) => <ComplexComponent record={record} />,
1020
+ },
1021
+ ], [dependencies])
1022
+ ```
1023
+
1024
+ ### 2. Request Best Practices
1025
+
1026
+ #### Handle Errors Gracefully
1162
1027
  ```tsx
1163
1028
  <ProTable
1164
- columns={columns}
1165
- dataSource={dataSource}
1166
- rowKey="id"
1167
- editable={{
1168
- type: 'multiple',
1169
- onSave: async (key, record, originRow) => {
1170
- console.log('保存:', { key, record, originRow })
1171
- // 处理保存逻辑
1029
+ request={async (params) => {
1030
+ try {
1031
+ const res = await fetchData(params)
1032
+ return { data: res.items, total: res.total, success: true }
1033
+ } catch (error) {
1034
+ message.error('Failed to load data')
1035
+ return { data: [], total: 0, success: false }
1172
1036
  }
1173
1037
  }}
1038
+ onRequestError={(error) => {
1039
+ console.error('Request error:', error)
1040
+ // Report to error tracking service
1041
+ }}
1174
1042
  />
1175
1043
  ```
1176
1044
 
1177
- #### 带行选择和批量操作
1045
+ #### Debounce Search Requests
1046
+ Use `params` prop to trigger re-fetch:
1178
1047
 
1179
1048
  ```tsx
1049
+ const [searchParams, setSearchParams] = useState({})
1050
+ const debouncedSearch = useMemo(
1051
+ () => debounce((value) => setSearchParams({ keyword: value }), 500),
1052
+ []
1053
+ )
1054
+
1180
1055
  <ProTable
1181
- columns={columns}
1182
- dataSource={dataSource}
1183
- rowKey="id"
1184
- rowSelection={{
1185
- type: 'checkbox',
1186
- onChange: (selectedRowKeys, selectedRows) => {
1187
- console.log('已选择:', selectedRowKeys, selectedRows)
1056
+ request={fetchData}
1057
+ params={searchParams}
1058
+ toolbar={{
1059
+ search: {
1060
+ onSearch: debouncedSearch,
1188
1061
  },
1189
- batchActions: {
1190
- onDelete: (rows) => {
1191
- console.log('删除:', rows)
1192
- },
1193
- onExport: (rows) => {
1194
- console.log('导出:', rows)
1195
- }
1196
- }
1197
1062
  }}
1198
1063
  />
1199
1064
  ```
1200
1065
 
1201
- **跨页选中支持:**
1202
-
1203
- 当使用 `request` 或 `pagination.onChange` 进行远程数据获取时,表格会自动维护跨页选中状态。这意味着:
1066
+ ### 3. State Management
1204
1067
 
1205
- - 在某一页选择的行,在切换到其他页面时会保留
1206
- - 批量操作栏会显示**所有页面**的选中总数
1207
- - 当返回到之前访问过的页面时,之前的选中状态会自动恢复
1208
- - `onChange` 回调会接收到所有页面的选中行 keys
1068
+ #### Controlled Components
1069
+ For complex state management, use controlled mode:
1209
1070
 
1210
1071
  ```tsx
1072
+ const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([])
1073
+ const [pagination, setPagination] = useState({ current: 1, pageSize: 10 })
1074
+
1211
1075
  <ProTable
1212
- columns={columns}
1213
- rowKey="id"
1214
- request={async (params) => {
1215
- const res = await fetchUsers(params)
1216
- return { data: res.items, total: res.total, success: true }
1217
- }}
1218
1076
  rowSelection={{
1219
- type: 'checkbox',
1220
- onChange: (selectedRowKeys, selectedRows) => {
1221
- // selectedRowKeys 包含所有页面的选中 keys
1222
- // selectedRows 仅包含当前页的行数据
1223
- console.log('所有页面选中的总数:', selectedRowKeys.length)
1224
- },
1225
- batchActions: {
1226
- onDelete: (rows) => {
1227
- // 这里只会收到当前页的行数据
1228
- // 如需操作所有选中的行,请使用 onChange 中的 selectedRowKeys
1229
- console.log('删除当前页行:', rows)
1230
- }
1231
- }
1077
+ selectedRowKeys,
1078
+ onChange: setSelectedRowKeys,
1232
1079
  }}
1233
1080
  pagination={{
1234
- pageSize: 10,
1235
- showSizeChanger: true,
1081
+ ...pagination,
1236
1082
  onChange: (current, pageSize) => {
1237
- // 切换页面时,选中状态会自动保留
1238
- }
1083
+ setPagination({ current, pageSize })
1084
+ },
1239
1085
  }}
1240
1086
  />
1241
1087
  ```
1242
1088
 
1243
- **重要提示:**
1244
- - 跨页选中在使用 `request` `pagination.onChange` 时会自动生效
1245
- - 使用 `dataSource`(本地数据)时,选中功能正常工作,但不需要跨页支持
1246
- - 批量操作栏始终显示所有页面的选中总数
1247
- - 如需对所有选中的行进行操作,请使用 `rowSelection.onChange` 获取所有选中的 keys
1089
+ #### Use Action Ref
1090
+ Access table methods via `actionRef`:
1248
1091
 
1249
- **自动清空选中状态:**
1092
+ ```tsx
1093
+ const actionRef = useRef<ProTableAction<DataType>>(null)
1250
1094
 
1251
- 某些批量操作后,选中状态会自动清空:
1095
+ const handleRefresh = () => {
1096
+ actionRef.current?.reload()
1097
+ }
1252
1098
 
1253
- - **删除操作**:删除后自动清空选中状态(因为数据已被删除)
1254
- - **归档操作**:归档后自动清空选中状态
1255
- - **自定义操作**:可以通过 `clearSelection` 选项控制是否清空选中状态
1256
- - **隐藏清空按钮**:可以通过 `hideClearButton` 选项隐藏清空按钮
1099
+ const handleReset = () => {
1100
+ actionRef.current?.reset()
1101
+ actionRef.current?.clearSelected()
1102
+ }
1257
1103
 
1258
- ```tsx
1259
- <ProTable
1260
- rowSelection={{
1261
- type: 'checkbox',
1262
- batchActions: {
1263
- onDelete: (rows) => {
1264
- // 删除操作 - 选中状态会自动清空
1265
- console.log('删除:', rows)
1266
- },
1267
- onArchive: (rows) => {
1268
- // 归档操作 - 选中状态会自动清空
1269
- console.log('归档:', rows)
1270
- },
1271
- // 如需隐藏清空按钮
1272
- hideClearButton: false,
1273
- customActions: [
1274
- {
1275
- key: 'approve',
1276
- label: '批量审批',
1277
- onClick: (rows) => {
1278
- console.log('审批:', rows)
1279
- },
1280
- // 控制操作后是否清空选中状态
1281
- clearSelection: true, // 设置为 true 表示操作后清空选中状态
1282
- },
1283
- {
1284
- key: 'export',
1285
- label: '导出',
1286
- onClick: (rows) => {
1287
- console.log('导出:', rows)
1288
- },
1289
- clearSelection: false, // 导出后保留选中状态(默认值)
1290
- }
1291
- ]
1292
- }
1293
- }}
1294
- />
1104
+ <ProTable actionRef={actionRef} />
1295
1105
  ```
1296
1106
 
1297
- #### 带文本省略和提示框
1107
+ ### 4. Accessibility
1298
1108
 
1299
- ```tsx
1300
- <ProTable
1301
- columns={[
1302
- {
1303
- title: '描述',
1304
- dataIndex: 'description',
1305
- key: 'description',
1306
- width: 200,
1307
- // 单行省略,带提示框
1308
- ellipsis: true,
1309
- tooltip: true
1310
- },
1311
- {
1312
- title: '内容',
1313
- dataIndex: 'content',
1314
- key: 'content',
1315
- width: 250,
1316
- // 多行省略(2行),带提示框
1317
- ellipsis: {
1318
- multiline: true,
1319
- rows: 2
1320
- },
1321
- tooltip: true
1322
- },
1323
- {
1324
- title: '自定义提示',
1325
- dataIndex: 'name',
1326
- key: 'name',
1327
- width: 150,
1328
- ellipsis: true,
1329
- tooltip: {
1330
- content: (value, record) => `完整内容: ${record.fullName || value}`,
1331
- side: 'top',
1332
- delayDuration: 300
1333
- }
1334
- },
1335
- {
1336
- title: '表头省略',
1337
- dataIndex: 'email',
1338
- key: 'email',
1339
- width: 180,
1340
- // 表头独立的省略和提示配置
1341
- headerEllipsis: true,
1342
- headerTooltip: {
1343
- content: '这是完整的表头标题',
1344
- side: 'bottom'
1345
- },
1346
- // 单元格省略和提示
1347
- ellipsis: true,
1348
- tooltip: true
1349
- }
1350
- ]}
1351
- dataSource={dataSource}
1352
- rowKey="id"
1353
- />
1354
- ```
1109
+ - Always provide meaningful `title` for columns
1110
+ - Use `aria-label` for action buttons
1111
+ - Ensure color contrast for status badges
1112
+ - Test with keyboard navigation
1355
1113
 
1356
- ### API 参考
1357
-
1358
- #### ProTable 属性
1359
-
1360
- | 属性 | 类型 | 默认值 | 描述 |
1361
- |------|------|--------|------|
1362
- | `columns` | `ProColumn[]` | `[]` | 表格列定义 |
1363
- | `dataSource` | `T[]` | `[]` | 表格数据源 |
1364
- | `rowKey` | `string \| (record: T) => string` | `'id'` | 每行的唯一键 |
1365
- | `loading` | `boolean` | `false` | 加载状态 |
1366
- | `pagination` | `ProTablePagination \| false` | `false` | 分页配置 |
1367
- | `search` | `ProTableSearch \| false` | `false` | 搜索表单配置 |
1368
- | `rowSelection` | `ProTableRowSelection` | `undefined` | 行选择配置 |
1369
- | `editable` | `ProTableEditable` | `undefined` | 可编辑行配置 |
1370
- | `draggable` | `ProTableDraggable` | `undefined` | 拖拽配置 |
1371
- | `expandable` | `ProTableExpandable \| false` | `undefined` | 树形表格展开配置 |
1372
- | `scroll` | `{ x?: number \| true; y?: number \| string }` | `undefined` | 滚动配置 |
1373
- | `tableLayout` | `'auto' \| 'fixed'` | `'auto'` | 表格布局模式 |
1374
- | `locale` | `ProTableLocale` | `en_US` | 国际化语言包 |
1375
- | `size` | `'small' \| 'middle' \| 'large'` | `'middle'` | 表格尺寸 |
1376
- | `bordered` | `boolean` | `false` | 显示表格边框 |
1377
-
1378
- #### 列配置
1379
-
1380
- | 属性 | 类型 | 描述 |
1381
- |------|------|------|
1382
- | `title` | `string` | 列头文本 |
1383
- | `dataIndex` | `string` | 数据字段名 |
1384
- | `key` | `string` | 唯一列键 |
1385
- | `width` | `number` | 列宽度(像素) |
1386
- | `fixed` | `'left' \| 'right'` | 固定列位置 |
1387
- | `sorter` | `boolean` | 启用排序 |
1388
- | `editable` | `boolean` | 启用内联编辑 |
1389
- | `copyable` | `boolean` | 启用复制到剪贴板 |
1390
- | `ellipsis` | `boolean \| ProColumnEllipsis` | 启用文本省略(单行或多行) |
1391
- | `tooltip` | `boolean \| ProColumnTooltip` | 启用悬停提示框 |
1392
- | `headerEllipsis` | `boolean \| ProColumnEllipsis` | 启用表头省略(默认使用 `ellipsis`) |
1393
- | `headerTooltip` | `boolean \| ProColumnTooltip` | 启用表头提示框(默认使用 `tooltip`) |
1394
- | `search` | `boolean` | 包含在搜索表单中 |
1395
- | `valueType` | `ProFieldValueType` | 值类型渲染 |
1396
- | `valueEnum` | `Record<string, { text: string; status?: string }>` | 枚举值选择 |
1397
- | `filters` | `Array<{ text: string; value: any }>` | 筛选选项 |
1398
-
1399
- #### 省略号配置
1400
-
1401
- `ellipsis` 和 `headerEllipsis` 属性支持以下配置:
1114
+ ### 5. Internationalization
1402
1115
 
1403
1116
  ```tsx
1404
- // 单行省略
1405
- ellipsis: true
1117
+ import { ProTable, zh_CN, en_US } from '@gulibs/react-vtable'
1118
+ import { useState } from 'react'
1406
1119
 
1407
- // 多行省略
1408
- ellipsis: {
1409
- multiline: true,
1410
- rows: 2 // 行数(1-5)
1120
+ function App() {
1121
+ const [locale, setLocale] = useState(en_US)
1122
+
1123
+ return (
1124
+ <>
1125
+ <Button onClick={() => setLocale(zh_CN)}>中文</Button>
1126
+ <Button onClick={() => setLocale(en_US)}>English</Button>
1127
+
1128
+ <ProTable
1129
+ locale={locale}
1130
+ columns={columns}
1131
+ dataSource={data}
1132
+ />
1133
+ </>
1134
+ )
1411
1135
  }
1412
1136
  ```
1413
1137
 
1414
- #### 提示框配置
1138
+ ### 6. Type Safety
1415
1139
 
1416
- `tooltip` `headerTooltip` 属性支持以下配置:
1140
+ Always provide TypeScript types for your data:
1417
1141
 
1418
1142
  ```tsx
1419
- // 简单提示框(显示单元格值)
1420
- tooltip: true
1421
-
1422
- // 自定义提示框
1423
- tooltip: {
1424
- content: '自定义提示文本',
1425
- side: 'top' | 'right' | 'bottom' | 'left',
1426
- align: 'start' | 'center' | 'end',
1427
- delayDuration: 300,
1428
- disabled: false
1143
+ interface User {
1144
+ id: string
1145
+ name: string
1146
+ email: string
1147
+ age: number
1148
+ status: 'active' | 'inactive'
1429
1149
  }
1430
1150
 
1431
- // 函数式提示框
1432
- tooltip: {
1433
- content: (value, record, index) => `完整内容: ${value}\nID: ${record.id}`,
1434
- side: 'top'
1435
- }
1151
+ const columns: ProColumn<User>[] = [
1152
+ {
1153
+ title: 'Name',
1154
+ dataIndex: 'name',
1155
+ // TypeScript will validate dataIndex, valueType, render function params, etc.
1156
+ },
1157
+ ]
1158
+
1159
+ <ProTable<User>
1160
+ columns={columns}
1161
+ dataSource={users}
1162
+ rowKey="id"
1163
+ />
1436
1164
  ```
1437
1165
 
1438
- **注意**: 当 `ellipsis` 启用且 `tooltip` 为 `true` 时,如果文本被截断,提示框会自动显示完整的原始值(而不是渲染后的值)。
1166
+ ---
1439
1167
 
1440
- #### 展开配置(树形表格)
1168
+ ## Development
1441
1169
 
1442
- `expandable` 属性支持以下配置:
1170
+ ```bash
1171
+ # Install dependencies
1172
+ pnpm install
1443
1173
 
1444
- ```tsx
1445
- expandable: {
1446
- // 自定义子字段名(默认: 'children')
1447
- childrenColumnName?: string
1174
+ # Start development server
1175
+ pnpm run dev
1448
1176
 
1449
- // 默认展开的行 key 数组(非受控模式)
1450
- defaultExpandedRowKeys?: React.Key[]
1177
+ # Build library
1178
+ pnpm run build
1451
1179
 
1452
- // 当前展开的行 key 数组(受控模式)
1453
- expandedRowKeys?: React.Key[]
1180
+ # Lint code
1181
+ pnpm run lint
1182
+ ```
1454
1183
 
1455
- // 展开/收起回调
1456
- onExpand?: (expanded: boolean, record: T) => void
1184
+ ---
1457
1185
 
1458
- // 展开的行 key 变化回调
1459
- onExpandedRowsChange?: (expandedRowKeys: React.Key[]) => void
1186
+ ## License
1460
1187
 
1461
- // 自定义展开图标
1462
- expandIcon?: (props: {
1463
- expanded: boolean
1464
- onExpand: () => void
1465
- record: T
1466
- }) => ReactNode
1188
+ MIT © [@gulibs](https://github.com/gulibs)
1189
+
1190
+ ---
1191
+
1192
+ ## Chinese
1193
+
1194
+ ## 中文文档
1467
1195
 
1468
- // 显示展开列(默认: true)
1469
- showExpandColumn?: boolean
1196
+ [查看完整中文文档 ↗](./README.zh-CN.md)
1470
1197
 
1471
- // 展开列宽度(默认: 50)
1472
- expandColumnWidth?: number
1198
+ ### 快速开始
1199
+
1200
+ ```tsx
1201
+ import { ProTable, zh_CN } from '@gulibs/react-vtable'
1202
+
1203
+ function App() {
1204
+ const columns = [
1205
+ { title: '姓名', dataIndex: 'name', key: 'name' },
1206
+ { title: '年龄', dataIndex: 'age', key: 'age' },
1207
+ ]
1473
1208
 
1474
- // 默认展开所有行(默认: false)
1475
- defaultExpandAllRows?: boolean
1209
+ const dataSource = [
1210
+ { id: 1, name: '张三', age: 30 },
1211
+ { id: 2, name: '李四', age: 25 },
1212
+ ]
1476
1213
 
1477
- // 手风琴模式 - 同时只能展开一行(默认: false)
1478
- accordion?: boolean
1214
+ return (
1215
+ <ProTable
1216
+ columns={columns}
1217
+ dataSource={dataSource}
1218
+ rowKey="id"
1219
+ locale={zh_CN}
1220
+ />
1221
+ )
1479
1222
  }
1480
1223
  ```
1481
1224
 
1482
- ### 开发
1483
-
1484
- ```bash
1485
- # 安装依赖
1486
- pnpm install
1225
+ ### 主要特性
1226
+
1227
+ - ✨ 强大的表格:基于 TanStack Table v8
1228
+ - 🎨 精美组件:shadcn/ui 设计
1229
+ - 🔍 搜索筛选:自动处理本地/远程数据
1230
+ - 📄 分页支持:客户端和服务端分页
1231
+ - ✅ 行选择:支持跨页选中
1232
+ - 🎯 批量操作:灵活的批量操作配置
1233
+ - ✏️ 内联编辑:支持验证
1234
+ - 📌 固定列:左右固定,自动阴影
1235
+ - 🌳 树形表格:层次数据展示
1236
+ - 🔄 拖拽排序:行列拖拽重排
1237
+ - 📱 响应式设计:移动端友好
1238
+ - 🌐 国际化:内置中英文
1239
+ - 🎭 丰富类型:20+ 值类型
1240
+ - 💬 提示框:单元格和表头提示
1241
+ - 📏 文本省略:单行和多行截断
1242
+ - 🔧 TypeScript:完整类型支持
1243
+
1244
+ ### 批量操作示例
1487
1245
 
1488
- # 构建库
1489
- pnpm run build
1246
+ ```tsx
1247
+ import { Trash2, Download, FileText } from 'lucide-react'
1490
1248
 
1491
- # 启动开发服务器
1492
- pnpm run dev
1249
+ <ProTable
1250
+ rowSelection={{
1251
+ type: 'checkbox',
1252
+ batchActions: {
1253
+ maxVisibleActions: 3,
1254
+ actions: [
1255
+ {
1256
+ key: 'delete',
1257
+ label: '删除',
1258
+ icon: <Trash2 className="h-3 w-3 mr-1" />,
1259
+ onClick: (rows) => console.log('删除:', rows),
1260
+ variant: 'destructive',
1261
+ clearSelection: true,
1262
+ },
1263
+ {
1264
+ key: 'export',
1265
+ label: '导出',
1266
+ icon: <Download className="h-3 w-3 mr-1" />,
1267
+ onClick: (rows) => console.log('导出:', rows),
1268
+ variant: 'outline',
1269
+ },
1270
+ {
1271
+ key: 'exportExcel',
1272
+ label: '导出 Excel',
1273
+ icon: <FileText className="h-3 w-3 mr-1" />,
1274
+ onClick: (rows) => console.log('导出 Excel:', rows),
1275
+ variant: 'outline',
1276
+ isExportFormat: true,
1277
+ showInMore: true,
1278
+ },
1279
+ ],
1280
+ },
1281
+ }}
1282
+ dataSource={data}
1283
+ rowKey="id"
1284
+ locale={zh_CN}
1285
+ />
1493
1286
  ```
1494
1287
 
1495
- ### 许可证
1288
+ ### 更多文档
1289
+
1290
+ 完整的中文文档和 API 参考,请查看英文部分。所有功能和 API 在中英文版本中都是一致的。
1291
+
1292
+ ---
1293
+
1294
+ ## Contributing
1295
+
1296
+ Contributions are welcome! Please feel free to submit a Pull Request.
1297
+
1298
+ ## Support
1496
1299
 
1497
- MIT
1300
+ If you have any questions or need help, please open an issue on [GitHub](https://github.com/gulibs/react-vtable).