react-glide-table 1.0.2 → 1.1.1
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 +89 -470
- package/dist/index.cjs +1293 -841
- package/dist/index.d.cts +367 -31
- package/dist/index.d.ts +367 -31
- package/dist/index.js +1248 -818
- package/package.json +10 -4
- package/dist/style.css +0 -404
package/README.md
CHANGED
|
@@ -1,56 +1,10 @@
|
|
|
1
1
|
# react-glide-table
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Headless React table built on [@tanstack/react-table](https://tanstack.com/table) and [@tanstack/react-virtual](https://tanstack.com/virtual).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Primary DX is a **compound API** (`createTable` / `Table.Column`) with an unstyled semantic HTML shell. Customize with `className` hooks, `slots`, and column `render`. `useGlideTable` remains the lower-level escape hatch when you need full control of markup.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
[Glide Data Grid](https://github.com/glideapps/glide-data-grid) (`@glideapps/glide-data-grid`) is a Canvas-based spreadsheet-style grid. This library is **not** a wrapper around it — it reimplements the basic interaction patterns on top of a normal HTML `<table>` + TanStack stack, so you keep DOM accessibility, CSS styling, and a React-friendly column API.
|
|
10
|
-
|
|
11
|
-
### Glide-inspired capabilities layered in
|
|
12
|
-
|
|
13
|
-
| Capability | What Glide provides | How `react-glide-table` adopts it |
|
|
14
|
-
| --- | --- | --- |
|
|
15
|
-
| **Cell range selection** | Drag / keyboard ranges (`gridSelection`, `rangeSelect`) | Click a cell, drag across cells to select a rectangular range |
|
|
16
|
-
| **Fill handle** | `fillHandle` — copy values into adjacent cells | Selection corner handle fills neighboring cells; commits via `onDataChange` |
|
|
17
|
-
| **Inline editing** | Built-in double-click edit with commit / cancel | Double-click editable cells; **Enter** commits, **Escape** cancels (`editable` / `editType`) |
|
|
18
|
-
| **Row selection** | `rowSelect`: none / single / multi | `rowSelectionMode`: `"none"` \| `"single"` \| `"multi"` (controlled or uncontrolled) |
|
|
19
|
-
| **Large-list scrolling** | Canvas lazy paint for millions of rows | DOM **row virtualization** via `@tanstack/react-virtual` (spacer rows inside `<tbody>`) |
|
|
20
|
-
| **Merged cells** | Span / merge support | `enableRowSpan` + column `rowSpan` / `rowSpanKey` for consecutive vertical merges |
|
|
21
|
-
|
|
22
|
-
### Intentionally different from Glide
|
|
23
|
-
|
|
24
|
-
- **Rendering**: HTML table DOM instead of HTML5 Canvas — easier theming, custom React cell renderers, and standard CSS.
|
|
25
|
-
- **Column model**: TanStack `ColumnDef` or declarative `Table.Column` / `createTable`, not Glide’s canvas cell draw API.
|
|
26
|
-
- **Extra app-table features** (beyond the Glide interaction set): compound components, sorting, controlled pagination, toolbar slots, and tree / expandable rows.
|
|
27
|
-
|
|
28
|
-
### Not ported (yet)
|
|
29
|
-
|
|
30
|
-
These Glide features are **not** included: Canvas custom cell drawing, frozen/pinned columns, column reorder / resize as first-class APIs, multi-rect / column-range selection stacks, markdown / bubble / image / sparkline cell types, and million-row Canvas-level scale. Use this library when you want Glide-like **edit / select / fill** UX on a conventional React table.
|
|
31
|
-
|
|
32
|
-
## Features
|
|
33
|
-
|
|
34
|
-
- **Declarative columns** via `Table` / `createTable` compound components
|
|
35
|
-
- **Low-level API** via `DataTable` + TanStack `ColumnDef`
|
|
36
|
-
- **Row virtualization** (HTML table + spacer rows; disabled automatically when row spanning is on)
|
|
37
|
-
- **Row selection** — none / single / multi *(Glide-inspired)*
|
|
38
|
-
- **Inline cell editing** — double-click to edit (`text` | `number`) *(Glide-inspired)*
|
|
39
|
-
- **Cell range selection & fill handle** — drag to select, fill adjacent cells (Excel-like) *(Glide-inspired)*
|
|
40
|
-
- **Row spanning** — merge consecutive cells by key *(Glide-inspired)*
|
|
41
|
-
- **Tree / expandable rows** — nested or flat parent–child data
|
|
42
|
-
- **Sorting** — clickable headers through `Table.Column sortable`
|
|
43
|
-
- **Pagination** — controlled `Table.Pagination`
|
|
44
|
-
- **Toolbar slots** — counts, summary, custom actions
|
|
45
|
-
|
|
46
|
-
## Requirements
|
|
47
|
-
|
|
48
|
-
| Package | Version | Notes |
|
|
49
|
-
| --- | --- | --- |
|
|
50
|
-
| `react` | `^18` or `^19` | Peer dependency — provided by your app |
|
|
51
|
-
| `react-dom` | `^18` or `^19` | Peer dependency — provided by your app |
|
|
52
|
-
|
|
53
|
-
`@tanstack/react-table` and `@tanstack/react-virtual` are installed automatically with this package.
|
|
7
|
+
The package ships **no CSS** — class names like `DataTableJSX` / `data-table` are opt-in hooks for your own styles.
|
|
54
8
|
|
|
55
9
|
## Installation
|
|
56
10
|
|
|
@@ -58,476 +12,141 @@ These Glide features are **not** included: Canvas custom cell drawing, frozen/pi
|
|
|
58
12
|
npm install react-glide-table
|
|
59
13
|
# or
|
|
60
14
|
pnpm add react-glide-table
|
|
61
|
-
# or
|
|
62
|
-
yarn add react-glide-table
|
|
63
15
|
```
|
|
64
16
|
|
|
65
|
-
|
|
17
|
+
Peer dependencies: `react` and `react-dom` (`^18` or `^19`).
|
|
66
18
|
|
|
67
|
-
|
|
68
|
-
import "react-glide-table/style.css"
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
## Quick start
|
|
72
|
-
|
|
73
|
-
### Recommended: typed compound table with `createTable`
|
|
74
|
-
|
|
75
|
-
`createTable<T>()` returns a typed compound component (`Header`, `Column`, `Body`, `Pagination`) so `field` and `render` stay type-safe.
|
|
19
|
+
## Quick start (compound)
|
|
76
20
|
|
|
77
21
|
```tsx
|
|
78
|
-
import { useState } from "react"
|
|
79
22
|
import { createTable } from "react-glide-table"
|
|
80
|
-
import "react
|
|
23
|
+
import { useState } from "react"
|
|
81
24
|
|
|
82
|
-
type Product = {
|
|
83
|
-
id: string
|
|
84
|
-
name: string
|
|
85
|
-
qty: number
|
|
86
|
-
price: number
|
|
87
|
-
}
|
|
25
|
+
type Product = { id: string; name: string; qty: number }
|
|
88
26
|
|
|
89
27
|
const ProductTable = createTable<Product>()
|
|
90
28
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
{ id: "2", name: "Gadget", qty: 4, price: 3400 },
|
|
94
|
-
]
|
|
95
|
-
|
|
96
|
-
export function ProductList() {
|
|
97
|
-
const [data, setData] = useState(INITIAL)
|
|
29
|
+
export function Products({ data }: { data: Product[] }) {
|
|
30
|
+
const [page, setPage] = useState(1)
|
|
98
31
|
|
|
99
32
|
return (
|
|
100
33
|
<ProductTable
|
|
101
34
|
data={data}
|
|
102
35
|
getRowId={(row) => row.id}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
totalCount={data.length}
|
|
107
|
-
toolbar={<button type="button">Export</button>}
|
|
36
|
+
className="my-table"
|
|
37
|
+
// Optional: replace Toolbar / Row / Pending / Empty
|
|
38
|
+
// slots={{ Toolbar: MyToolbar, Row: MyRow, Empty: MyEmpty }}
|
|
108
39
|
>
|
|
109
40
|
<ProductTable.Header>
|
|
110
|
-
<ProductTable.Column field="name"
|
|
111
|
-
|
|
112
|
-
</ProductTable.Column>
|
|
113
|
-
<ProductTable.Column field="qty" sortable align="right" editable editType="number">
|
|
41
|
+
<ProductTable.Column field="name">Name</ProductTable.Column>
|
|
42
|
+
<ProductTable.Column field="qty" editable>
|
|
114
43
|
Qty
|
|
115
44
|
</ProductTable.Column>
|
|
116
|
-
<ProductTable.Column
|
|
117
|
-
field="price"
|
|
118
|
-
align="right"
|
|
119
|
-
render={(value) => `$${Number(value).toLocaleString()}`}
|
|
120
|
-
>
|
|
121
|
-
Price
|
|
122
|
-
</ProductTable.Column>
|
|
123
45
|
</ProductTable.Header>
|
|
46
|
+
<ProductTable.Pagination page={page} pageSize={10} onChange={setPage} />
|
|
124
47
|
</ProductTable>
|
|
125
48
|
)
|
|
126
49
|
}
|
|
127
50
|
```
|
|
128
51
|
|
|
129
|
-
###
|
|
52
|
+
### Customization surface
|
|
130
53
|
|
|
131
|
-
|
|
132
|
-
import { Table } from "react-glide-table"
|
|
133
|
-
|
|
134
|
-
<Table data={rows} getRowId={(row) => String(row.id)}>
|
|
135
|
-
<Table.Header>
|
|
136
|
-
<Table.Column field="name">Name</Table.Column>
|
|
137
|
-
<Table.Column field="amount" sortable>
|
|
138
|
-
Amount
|
|
139
|
-
</Table.Column>
|
|
140
|
-
</Table.Header>
|
|
141
|
-
</Table>
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### Low-level: `DataTable` + `ColumnDef`
|
|
145
|
-
|
|
146
|
-
Use this when you already build TanStack column definitions yourself.
|
|
147
|
-
|
|
148
|
-
```tsx
|
|
149
|
-
import { DataTable, type ColumnDef } from "react-glide-table"
|
|
150
|
-
|
|
151
|
-
type Row = { id: string; name: string; amount: number }
|
|
152
|
-
|
|
153
|
-
const columns: ColumnDef<Row, unknown>[] = [
|
|
154
|
-
{ id: "name", accessorKey: "name", header: "Name" },
|
|
155
|
-
{
|
|
156
|
-
id: "amount",
|
|
157
|
-
accessorKey: "amount",
|
|
158
|
-
header: "Amount",
|
|
159
|
-
meta: { align: "right", editable: true, editType: "number" },
|
|
160
|
-
},
|
|
161
|
-
]
|
|
162
|
-
|
|
163
|
-
<DataTable data={rows} columns={columns} getRowId={(row) => row.id} />
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
---
|
|
167
|
-
|
|
168
|
-
## APIs
|
|
169
|
-
|
|
170
|
-
### `createTable<T>()` / `Table`
|
|
171
|
-
|
|
172
|
-
Compound wrapper around `DataTable`. It:
|
|
173
|
-
|
|
174
|
-
1. Reads `Table.Column` children inside `Table.Header` and builds column defs
|
|
175
|
-
2. Applies client-side sorting when a column is `sortable`
|
|
176
|
-
3. Applies client-side pagination when `Table.Pagination` is present
|
|
177
|
-
4. Forwards the rest of the props to `DataTable`
|
|
178
|
-
|
|
179
|
-
| Subcomponent | Role |
|
|
54
|
+
| Slot / prop | Role |
|
|
180
55
|
| --- | --- |
|
|
181
|
-
| `
|
|
182
|
-
| `
|
|
183
|
-
| `
|
|
184
|
-
| `
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
---
|
|
189
|
-
|
|
190
|
-
### `Table.Column` props
|
|
191
|
-
|
|
192
|
-
| Prop | Type | Default | Description |
|
|
193
|
-
| --- | --- | --- | --- |
|
|
194
|
-
| `field` | `string` | — | Column id / accessor key |
|
|
195
|
-
| `virtual` | `boolean` | `false` | If `true`, no `accessorKey` (for checkbox / index columns) |
|
|
196
|
-
| `children` | `ReactNode` | — | Header label |
|
|
197
|
-
| `sortable` | `boolean` | `false` | Clickable sort header (asc → desc → clear) |
|
|
198
|
-
| `width` | `number` | `150` | Column width (px) |
|
|
199
|
-
| `align` | `"left" \| "center" \| "right"` | — | Cell / header alignment |
|
|
200
|
-
| `rowSpan` | `boolean` | — | Enable vertical merge for this column |
|
|
201
|
-
| `rowSpanKey` | `string` | column `field` | Field used to decide merge groups |
|
|
202
|
-
| `editable` | `boolean` | — | Double-click to edit |
|
|
203
|
-
| `editType` | `"text" \| "number"` | `"text"` | Input type while editing |
|
|
204
|
-
| `className` | `string` | — | Body cell class |
|
|
205
|
-
| `headerClassName` | `string` | — | Header cell class |
|
|
206
|
-
| `render` | `(value, row, index) => ReactNode` | — | Custom cell renderer |
|
|
207
|
-
|
|
208
|
-
---
|
|
209
|
-
|
|
210
|
-
### Shared table props (`Table` / `DataTable`)
|
|
211
|
-
|
|
212
|
-
All of these (except `columns` / `children`) work on both APIs.
|
|
213
|
-
|
|
214
|
-
#### Data & identity
|
|
215
|
-
|
|
216
|
-
| Prop | Type | Default | Description |
|
|
217
|
-
| --- | --- | --- | --- |
|
|
218
|
-
| `data` | `T[]` | — | Row data |
|
|
219
|
-
| `getRowId` | `(row, index) => string` | index as string | Stable row id (recommended) |
|
|
220
|
-
| `columns` | `ColumnDef[]` | — | **`DataTable` only** |
|
|
221
|
-
| `children` | `ReactNode` | — | **`Table` only** — Header / Pagination |
|
|
222
|
-
| `onDataChange` | `(data: T[]) => void` | — | Called after cell edit or fill |
|
|
223
|
-
|
|
224
|
-
#### Loading & empty
|
|
225
|
-
|
|
226
|
-
| Prop | Type | Default | Description |
|
|
227
|
-
| --- | --- | --- | --- |
|
|
228
|
-
| `isPending` | `boolean` | `false` | Shows a loading placeholder instead of the table |
|
|
229
|
-
| `emptyText` | `string` | `"데이터가 없습니다."` | Message when there are no rows |
|
|
230
|
-
| `className` | `string` | — | Root class name |
|
|
231
|
-
|
|
232
|
-
#### Toolbar
|
|
233
|
-
|
|
234
|
-
| Prop | Type | Description |
|
|
235
|
-
| --- | --- | --- |
|
|
236
|
-
| `totalCount` | `number` | Total count before filter (shown as `/ N`) |
|
|
237
|
-
| `filteredCount` | `number` | Current visible count (defaults to current data length) |
|
|
238
|
-
| `summary` | `ReactNode` | Left-side summary slot |
|
|
239
|
-
| `toolbar` | `ReactNode` | Right-side actions (export, delete, …) |
|
|
240
|
-
| `selectionLabel` | `(count) => ReactNode` | Custom selection label (default: `"N개 선택됨"`) |
|
|
241
|
-
|
|
242
|
-
#### Row selection
|
|
243
|
-
|
|
244
|
-
| Prop | Type | Default | Description |
|
|
245
|
-
| --- | --- | --- | --- |
|
|
246
|
-
| `rowSelectionMode` | `"none" \| "single" \| "multi"` | `"none"` | Selection mode |
|
|
247
|
-
| `rowSelection` | `RowSelectionState` | — | Controlled selection map (`{ [rowId]: true }`) |
|
|
248
|
-
| `onRowSelectionChange` | `(updater) => void` | — | Controlled selection updater |
|
|
249
|
-
| `getRowCanSelect` | `(row, index) => boolean` | all selectable | Return `false` to disable selection for a row |
|
|
250
|
-
| `selectOnRowClick` | `boolean` | `true` | If `false`, only checkbox / explicit controls select |
|
|
251
|
-
| `preserveRowSelection` | `boolean` | `false` | If `true`, clicking a selected row does not deselect it |
|
|
252
|
-
| `onRowClick` | `(row, index) => void` | — | Extra row click handler |
|
|
253
|
-
| `getRowClassName` | `(row, index) => string \| undefined` | — | Per-row class names |
|
|
254
|
-
|
|
255
|
-
```tsx
|
|
256
|
-
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
|
257
|
-
|
|
258
|
-
<ProductTable
|
|
259
|
-
data={data}
|
|
260
|
-
getRowId={(row) => row.id}
|
|
261
|
-
rowSelectionMode="multi"
|
|
262
|
-
rowSelection={rowSelection}
|
|
263
|
-
onRowSelectionChange={setRowSelection}
|
|
264
|
-
getRowCanSelect={(row) => row.qty > 0}
|
|
265
|
-
selectOnRowClick={false}
|
|
266
|
-
/>
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
#### Row spanning
|
|
56
|
+
| `slots.Toolbar` | Top summary / actions region |
|
|
57
|
+
| `slots.Row` | Full row replacement (cells, selection, edit UI) |
|
|
58
|
+
| `slots.Pending` / `slots.Empty` | Loading and empty states |
|
|
59
|
+
| `className` / column `className` / `headerClassName` | Class hooks (style yourself) |
|
|
60
|
+
| `labels` / `summary` / `toolbar` | Copy and slot nodes |
|
|
61
|
+
| `Column.render` | Cell content custom render |
|
|
270
62
|
|
|
271
|
-
|
|
272
|
-
| --- | --- | --- | --- |
|
|
273
|
-
| `enableRowSpan` | `boolean` | `false` | Turn on vertical cell merge |
|
|
63
|
+
Row-level UI → `slots.Row`. Cell content → `Column.render`. Header/Cell are not separate slots.
|
|
274
64
|
|
|
275
|
-
|
|
65
|
+
## Escape hatch (`useGlideTable`)
|
|
276
66
|
|
|
277
67
|
```tsx
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
Group
|
|
282
|
-
</ProductTable.Column>
|
|
283
|
-
<ProductTable.Column field="name">Name</ProductTable.Column>
|
|
284
|
-
</ProductTable.Header>
|
|
285
|
-
</ProductTable>
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
> When `enableRowSpan` is `true`, virtualization is **forced off** so merged cells stay correct.
|
|
68
|
+
import { flexRender } from "@tanstack/react-table"
|
|
69
|
+
import { useGlideTable } from "react-glide-table"
|
|
70
|
+
import type { ColumnDef } from "react-glide-table"
|
|
289
71
|
|
|
290
|
-
|
|
72
|
+
type Product = { id: string; name: string; qty: number }
|
|
291
73
|
|
|
292
|
-
|
|
74
|
+
const columns: ColumnDef<Product, unknown>[] = [
|
|
75
|
+
{ accessorKey: "name", header: "Name" },
|
|
76
|
+
{ accessorKey: "qty", header: "Qty" },
|
|
77
|
+
]
|
|
293
78
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
| `preventExpand` | `boolean` | `false` | Always show children; hide toggle |
|
|
79
|
+
export function ProductTable({ data }: { data: Product[] }) {
|
|
80
|
+
const { table, rows, scrollRef } = useGlideTable({
|
|
81
|
+
data,
|
|
82
|
+
columns,
|
|
83
|
+
getRowId: (row) => row.id,
|
|
84
|
+
rowSelectionMode: "multi",
|
|
85
|
+
})
|
|
302
86
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
87
|
+
return (
|
|
88
|
+
<div ref={scrollRef}>
|
|
89
|
+
<table>
|
|
90
|
+
<thead>
|
|
91
|
+
{table.getHeaderGroups().map((headerGroup) => (
|
|
92
|
+
<tr key={headerGroup.id}>
|
|
93
|
+
{headerGroup.headers.map((header) => (
|
|
94
|
+
<th key={header.id}>
|
|
95
|
+
{header.isPlaceholder
|
|
96
|
+
? null
|
|
97
|
+
: flexRender(header.column.columnDef.header, header.getContext())}
|
|
98
|
+
</th>
|
|
99
|
+
))}
|
|
100
|
+
</tr>
|
|
101
|
+
))}
|
|
102
|
+
</thead>
|
|
103
|
+
<tbody>
|
|
104
|
+
{rows.map((row) => (
|
|
105
|
+
<tr key={row.id}>
|
|
106
|
+
{row.getVisibleCells().map((cell) => (
|
|
107
|
+
<td key={cell.id}>
|
|
108
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
109
|
+
</td>
|
|
110
|
+
))}
|
|
111
|
+
</tr>
|
|
112
|
+
))}
|
|
113
|
+
</tbody>
|
|
114
|
+
</table>
|
|
115
|
+
</div>
|
|
116
|
+
)
|
|
309
117
|
}
|
|
310
|
-
|
|
311
|
-
const [expandedRows, setExpandedRows] = useState<Set<string>>(() => new Set())
|
|
312
|
-
|
|
313
|
-
<Table
|
|
314
|
-
data={bom}
|
|
315
|
-
toggleField="materialCode"
|
|
316
|
-
flattenField="assemblyMaterials"
|
|
317
|
-
expandedRows={expandedRows}
|
|
318
|
-
onExpandedRowsChange={setExpandedRows}
|
|
319
|
-
getRowId={(row) => row.id}
|
|
320
|
-
>
|
|
321
|
-
<Table.Header>
|
|
322
|
-
<Table.Column field="materialCode">Code</Table.Column>
|
|
323
|
-
<Table.Column field="materialName">Name</Table.Column>
|
|
324
|
-
</Table.Header>
|
|
325
|
-
</Table>
|
|
326
118
|
```
|
|
327
119
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
#### Virtualization
|
|
331
|
-
|
|
332
|
-
| Prop | Type | Default | Description |
|
|
333
|
-
| --- | --- | --- | --- |
|
|
334
|
-
| `enableVirtualization` | `boolean` | `true` | Virtualize body rows for large lists |
|
|
335
|
-
| `estimateRowHeight` | `number` | `44` | Estimated row height (px); measured and corrected at runtime |
|
|
336
|
-
| `virtualOverscan` | `number` | `8` | Extra rows rendered above/below the viewport |
|
|
337
|
-
|
|
338
|
-
```tsx
|
|
339
|
-
<ProductTable
|
|
340
|
-
data={largeDataset}
|
|
341
|
-
enableVirtualization
|
|
342
|
-
estimateRowHeight={44}
|
|
343
|
-
virtualOverscan={12}
|
|
344
|
-
getRowId={(row) => row.id}
|
|
345
|
-
>
|
|
346
|
-
...
|
|
347
|
-
</ProductTable>
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
---
|
|
351
|
-
|
|
352
|
-
## Sorting
|
|
353
|
-
|
|
354
|
-
Set `sortable` on a column. Click cycle: **ascending → descending → unsorted**.
|
|
355
|
-
|
|
356
|
-
Sorting is applied client-side inside `Table` / `createTable` before pagination.
|
|
357
|
-
|
|
358
|
-
```tsx
|
|
359
|
-
<ProductTable.Column field="name" sortable>
|
|
360
|
-
Name
|
|
361
|
-
</ProductTable.Column>
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
---
|
|
365
|
-
|
|
366
|
-
## Pagination
|
|
367
|
-
|
|
368
|
-
`Table.Pagination` is **controlled**. Pass `page`, `onChange`, and usually `pageSize` / `totalCount`.
|
|
369
|
-
|
|
370
|
-
The table slices `data` client-side to the current page. The toolbar still uses `filteredCount` / `totalCount` for the full dataset size.
|
|
371
|
-
|
|
372
|
-
```tsx
|
|
373
|
-
const [page, setPage] = useState(1)
|
|
374
|
-
|
|
375
|
-
<ProductTable
|
|
376
|
-
data={allRows}
|
|
377
|
-
filteredCount={allRows.length}
|
|
378
|
-
totalCount={allRows.length}
|
|
379
|
-
getRowId={(row) => row.id}
|
|
380
|
-
>
|
|
381
|
-
<ProductTable.Header>
|
|
382
|
-
<ProductTable.Column field="name">Name</ProductTable.Column>
|
|
383
|
-
</ProductTable.Header>
|
|
384
|
-
<ProductTable.Pagination
|
|
385
|
-
page={page}
|
|
386
|
-
pageSize={10}
|
|
387
|
-
totalCount={allRows.length}
|
|
388
|
-
onChange={setPage}
|
|
389
|
-
/>
|
|
390
|
-
</ProductTable>
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
| Prop | Type | Default | Description |
|
|
394
|
-
| --- | --- | --- | --- |
|
|
395
|
-
| `page` | `number` | — | Current page (1-based) |
|
|
396
|
-
| `pageSize` | `number` | `10` | Rows per page |
|
|
397
|
-
| `totalCount` | `number` | `0` | Total items for page math |
|
|
398
|
-
| `onChange` | `(page: number) => void` | — | Page change handler |
|
|
399
|
-
| `className` | `string` | — | Wrapper class |
|
|
400
|
-
|
|
401
|
-
For server-side pagination, pass only the current page’s `data` and set `totalCount` / `filteredCount` from the server; keep `page` / `onChange` in sync with your fetch.
|
|
402
|
-
|
|
403
|
-
---
|
|
404
|
-
|
|
405
|
-
## Inline editing
|
|
120
|
+
Wire `rowContextValue` into your own row/cell components for edit, selection, expand, and row-span behavior.
|
|
406
121
|
|
|
407
|
-
|
|
408
|
-
2. Provide `onDataChange` so edits update your state
|
|
409
|
-
3. **Double-click** a cell to edit
|
|
410
|
-
4. Press **Enter** to commit, **Escape** to cancel
|
|
122
|
+
## Public API
|
|
411
123
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
Qty
|
|
422
|
-
</ProductTable.Column>
|
|
423
|
-
</ProductTable.Header>
|
|
424
|
-
</ProductTable>
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
With `DataTable`, set the same flags on `columnDef.meta`:
|
|
428
|
-
|
|
429
|
-
```ts
|
|
430
|
-
meta: { editable: true, editType: "number" }
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
---
|
|
434
|
-
|
|
435
|
-
## Cell selection & fill
|
|
436
|
-
|
|
437
|
-
When `onDataChange` is provided, users can:
|
|
438
|
-
|
|
439
|
-
1. Click a cell to select it
|
|
440
|
-
2. Drag across cells to select a range
|
|
441
|
-
3. Use the fill handle on the selection to copy values into adjacent cells
|
|
442
|
-
|
|
443
|
-
This mirrors spreadsheet-style fill behavior. Keep `data` controlled via `onDataChange`.
|
|
444
|
-
|
|
445
|
-
---
|
|
446
|
-
|
|
447
|
-
## Column meta (`DataTable` / TanStack)
|
|
448
|
-
|
|
449
|
-
When using `DataTable` directly, configure columns through TanStack `ColumnDef` and `meta`:
|
|
450
|
-
|
|
451
|
-
```ts
|
|
452
|
-
import type { ColumnDef } from "react-glide-table"
|
|
453
|
-
|
|
454
|
-
const columns: ColumnDef<Row, unknown>[] = [
|
|
455
|
-
{
|
|
456
|
-
id: "name",
|
|
457
|
-
accessorKey: "name",
|
|
458
|
-
header: "Name",
|
|
459
|
-
size: 200,
|
|
460
|
-
meta: {
|
|
461
|
-
align: "left",
|
|
462
|
-
editable: true,
|
|
463
|
-
editType: "text",
|
|
464
|
-
rowSpan: false,
|
|
465
|
-
className: "my-cell",
|
|
466
|
-
headerClassName: "my-header",
|
|
467
|
-
},
|
|
468
|
-
},
|
|
469
|
-
]
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
| `meta` key | Type | Description |
|
|
473
|
-
| --- | --- | --- |
|
|
474
|
-
| `align` | `"left" \| "center" \| "right"` | Alignment |
|
|
475
|
-
| `className` | `string` | Body cell class |
|
|
476
|
-
| `headerClassName` | `string` | Header class |
|
|
477
|
-
| `editable` | `boolean` | Inline edit |
|
|
478
|
-
| `editType` | `"text" \| "number"` | Edit input type |
|
|
479
|
-
| `rowSpan` | `boolean` | Vertical merge |
|
|
480
|
-
| `rowSpanKey` | `string` | Merge key field |
|
|
481
|
-
|
|
482
|
-
---
|
|
483
|
-
|
|
484
|
-
## Exports
|
|
485
|
-
|
|
486
|
-
```ts
|
|
487
|
-
import {
|
|
488
|
-
createTable,
|
|
489
|
-
Table,
|
|
490
|
-
DataTable,
|
|
491
|
-
type TableCompoundComponent,
|
|
492
|
-
type TableProps,
|
|
493
|
-
type TableColumnProps,
|
|
494
|
-
type DataTableProps,
|
|
495
|
-
type ColumnDef,
|
|
496
|
-
type RowSelectionState,
|
|
497
|
-
type RowSelectionMode,
|
|
498
|
-
} from "react-glide-table"
|
|
499
|
-
|
|
500
|
-
import "react-glide-table/style.css"
|
|
501
|
-
```
|
|
502
|
-
|
|
503
|
-
---
|
|
124
|
+
| Export | Role |
|
|
125
|
+
| --- | --- |
|
|
126
|
+
| `createTable` / `Table` | Compound column DSL (`Header` / `Column` / `Body` / `Pagination`) |
|
|
127
|
+
| `DataTable` | Unstyled default renderer (semantic HTML + slots/props) |
|
|
128
|
+
| `useGlideTable` | Headless engine escape hatch |
|
|
129
|
+
| `useCellEdit` / `useCellSelection` / `useConvertTreeData` | Feature hooks |
|
|
130
|
+
| `applyCellEdit`, `applyFillData`, `buildColumnRowSpanMap`, … | Pure helpers |
|
|
131
|
+
| `DEFAULT_DATA_TABLE_LABELS` / `resolveDataTableLabels` | Optional English UI copy helpers |
|
|
132
|
+
| Tree field defaults | `id` / `parentId` / `children` / `qty` |
|
|
504
133
|
|
|
505
|
-
|
|
134
|
+
Related types: `TableProps`, `TableColumnProps`, `DataTableProps`, `DataTableSlots`, `TableCompoundComponent`, `ColumnDef`, …
|
|
506
135
|
|
|
507
|
-
|
|
508
|
-
2. Prefer **`createTable<YourRowType>()`** for end-to-end TypeScript safety.
|
|
509
|
-
3. Import **`react-glide-table/style.css`** or the table will be unstyled.
|
|
510
|
-
4. Prefer **virtualization on** for large lists; turn **`enableRowSpan` off** if you need virtualization.
|
|
511
|
-
5. Keep **`data` immutable** when handling `onDataChange` (replace the array / row objects).
|
|
512
|
-
6. For checkbox-only selection, set **`selectOnRowClick={false}`** and render a virtual checkbox column with your own UI if needed.
|
|
136
|
+
## Notable constraints
|
|
513
137
|
|
|
514
|
-
|
|
138
|
+
- **Row span + virtualization**: when `enableRowSpan` is on, virtualization is forced off (HTML `<table>` + `rowspan` cannot safely share a virtual window).
|
|
139
|
+
- **No shipped CSS**: the default renderer emits class hooks only. Bring your own styles (see playground for a CSS-skinned example).
|
|
515
140
|
|
|
516
|
-
##
|
|
141
|
+
## Local playground
|
|
517
142
|
|
|
518
143
|
```bash
|
|
519
144
|
pnpm install
|
|
520
|
-
pnpm
|
|
145
|
+
pnpm dev
|
|
521
146
|
```
|
|
522
147
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
- `dist/index.js` / `dist/index.cjs` — ESM / CJS bundles
|
|
526
|
-
- `dist/index.d.ts` — TypeScript declarations
|
|
527
|
-
- `dist/style.css` — bundled styles (`react-glide-table/style.css`)
|
|
528
|
-
|
|
529
|
-
---
|
|
148
|
+
The playground uses `createTable` with a local CSS skin (`src/styles/index.css`) to exercise the compound API and headless core.
|
|
530
149
|
|
|
531
150
|
## License
|
|
532
151
|
|
|
533
|
-
|
|
152
|
+
MIT
|