@loykin/gridkit 0.0.1-beta.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 +316 -0
- package/dist/index.cjs +2485 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +266 -0
- package/dist/index.d.ts +266 -0
- package/dist/index.js +2454 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +2 -0
- package/dist/styles.d.ts +1 -0
- package/package.json +72 -0
package/README.md
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# @loykin/data-grid
|
|
2
|
+
|
|
3
|
+
A feature-rich React DataGrid component with virtualization, sorting, filtering, pagination, and infinite scroll.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Virtualization** — renders only visible rows via `@tanstack/react-virtual` for large datasets
|
|
8
|
+
- **Sorting** — client-side and server-side (manual) sorting
|
|
9
|
+
- **Column Filters** — per-column filter row with `text`, `select`, `number` types (AG Grid style)
|
|
10
|
+
- **Global Search** — searchable columns with a toolbar search input
|
|
11
|
+
- **Pagination** — built-in pagination bar with configurable page sizes
|
|
12
|
+
- **Infinite Scroll** — `DataGridInfinity` with IntersectionObserver-based next-page loading
|
|
13
|
+
- **Column Resizing** — drag-to-resize column widths
|
|
14
|
+
- **Column Pinning** — pin columns left or right
|
|
15
|
+
- **Column Visibility** — show/hide columns via toolbar dropdown
|
|
16
|
+
- **Row Selection** — checkbox selection with select-all support
|
|
17
|
+
- **Row Actions** — per-row action menu (`⋯` button) defined at the column level
|
|
18
|
+
- **Custom Scrollbars** — consistent cross-platform scrollbars (Windows & Mac)
|
|
19
|
+
- **Row Wrap** — per-column `meta.wrap` for multi-line cell content
|
|
20
|
+
- **State Persistence** — optional Zustand-based persistence of column sizing/visibility
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @loykin/data-grid
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Peer Dependencies
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install react react-dom
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## CSS Setup
|
|
39
|
+
|
|
40
|
+
Import the stylesheet once in your app entry point:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import '@loykin/data-grid/styles'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Theming with CSS Variables
|
|
47
|
+
|
|
48
|
+
The library uses a `--dg-*` CSS variable namespace to avoid conflicts with your app's global styles.
|
|
49
|
+
|
|
50
|
+
**With shadcn/ui** — works out of the box. The `--dg-*` variables automatically fall back to your existing shadcn CSS variables (`--background`, `--foreground`, `--border`, etc.).
|
|
51
|
+
|
|
52
|
+
**Standalone (no shadcn/ui)** — hardcoded defaults are applied automatically.
|
|
53
|
+
|
|
54
|
+
**Custom theme** — override only the `--dg-*` variables you need:
|
|
55
|
+
|
|
56
|
+
```css
|
|
57
|
+
:root {
|
|
58
|
+
--dg-background: #ffffff;
|
|
59
|
+
--dg-foreground: #0a0a0a;
|
|
60
|
+
--dg-border: #e5e7eb;
|
|
61
|
+
--dg-primary: #3b82f6;
|
|
62
|
+
--dg-muted: #f5f5f5;
|
|
63
|
+
--dg-muted-foreground: #6b7280;
|
|
64
|
+
--dg-radius: 0.5rem;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.dark {
|
|
68
|
+
--dg-background: #0a0a0a;
|
|
69
|
+
--dg-foreground: #fafafa;
|
|
70
|
+
--dg-border: rgba(255, 255, 255, 0.1);
|
|
71
|
+
--dg-primary: #6366f1;
|
|
72
|
+
--dg-muted: #1a1a1a;
|
|
73
|
+
--dg-muted-foreground: #a1a1aa;
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### All Available `--dg-*` Variables
|
|
78
|
+
|
|
79
|
+
| Variable | Description |
|
|
80
|
+
|-----------------------------|------------------------------------|
|
|
81
|
+
| `--dg-background` | Table / cell background |
|
|
82
|
+
| `--dg-foreground` | Default text color |
|
|
83
|
+
| `--dg-popover` | Dropdown / popover background |
|
|
84
|
+
| `--dg-popover-foreground` | Dropdown text color |
|
|
85
|
+
| `--dg-primary` | Primary accent (checkboxes, etc.) |
|
|
86
|
+
| `--dg-primary-foreground` | Text on primary backgrounds |
|
|
87
|
+
| `--dg-secondary` | Secondary background |
|
|
88
|
+
| `--dg-secondary-foreground` | Secondary text |
|
|
89
|
+
| `--dg-muted` | Muted / subtle background |
|
|
90
|
+
| `--dg-muted-foreground` | Muted text (placeholders, hints) |
|
|
91
|
+
| `--dg-accent` | Hover / accent background |
|
|
92
|
+
| `--dg-accent-foreground` | Accent text |
|
|
93
|
+
| `--dg-destructive` | Destructive action color |
|
|
94
|
+
| `--dg-border` | Border color |
|
|
95
|
+
| `--dg-input` | Input border color |
|
|
96
|
+
| `--dg-ring` | Focus ring color |
|
|
97
|
+
| `--dg-radius` | Border radius base value |
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Basic Usage
|
|
102
|
+
|
|
103
|
+
### DataGrid (with Pagination)
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
import { DataGrid } from '@loykin/data-grid'
|
|
107
|
+
import '@loykin/data-grid/styles'
|
|
108
|
+
|
|
109
|
+
const columns = [
|
|
110
|
+
{ accessorKey: 'id', header: 'ID' },
|
|
111
|
+
{ accessorKey: 'name', header: 'Name' },
|
|
112
|
+
{ accessorKey: 'email',header: 'Email'},
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
export function MyTable() {
|
|
116
|
+
return (
|
|
117
|
+
<DataGrid
|
|
118
|
+
data={rows}
|
|
119
|
+
columns={columns}
|
|
120
|
+
enablePagination
|
|
121
|
+
tableHeight={400}
|
|
122
|
+
/>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### DataGridInfinity (Infinite Scroll)
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
import { DataGridInfinity } from '@loykin/data-grid'
|
|
131
|
+
|
|
132
|
+
export function MyInfiniteTable() {
|
|
133
|
+
const { data, hasNextPage, isFetchingNextPage, fetchNextPage } = useInfiniteQuery(...)
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<DataGridInfinity
|
|
137
|
+
data={data}
|
|
138
|
+
columns={columns}
|
|
139
|
+
hasNextPage={hasNextPage}
|
|
140
|
+
isFetchingNextPage={isFetchingNextPage}
|
|
141
|
+
fetchNextPage={fetchNextPage}
|
|
142
|
+
tableHeight={500}
|
|
143
|
+
/>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Column Definition
|
|
151
|
+
|
|
152
|
+
Columns follow `@tanstack/react-table`'s `ColumnDef` with additional `meta` options:
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
const columns: DataGridColumnDef<User>[] = [
|
|
156
|
+
{
|
|
157
|
+
accessorKey: 'name',
|
|
158
|
+
header: 'Name',
|
|
159
|
+
size: 200,
|
|
160
|
+
meta: {
|
|
161
|
+
flex: 1, // stretch to fill remaining width proportionally
|
|
162
|
+
autoSize: true, // auto-fit to content width
|
|
163
|
+
minWidth: 100,
|
|
164
|
+
maxWidth: 400,
|
|
165
|
+
align: 'left', // 'left' | 'center' | 'right'
|
|
166
|
+
pin: 'left', // pin column: 'left' | 'right'
|
|
167
|
+
wrap: true, // allow multi-line cell content
|
|
168
|
+
filterType: 'text',// 'text' | 'select' | 'number' | false
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
accessorKey: 'status',
|
|
173
|
+
header: 'Status',
|
|
174
|
+
meta: {
|
|
175
|
+
filterType: 'select', // dropdown of unique values (lazy-loaded on first open)
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: 'actions',
|
|
180
|
+
header: '',
|
|
181
|
+
meta: {
|
|
182
|
+
actions: (row) => [
|
|
183
|
+
{ label: 'Edit', onClick: (row) => openEdit(row) },
|
|
184
|
+
{ label: 'Delete', onClick: (row) => deleteRow(row), variant: 'destructive' },
|
|
185
|
+
],
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
]
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Props
|
|
194
|
+
|
|
195
|
+
### Shared Props (`DataGrid` and `DataGridInfinity`)
|
|
196
|
+
|
|
197
|
+
| Prop | Type | Default | Description |
|
|
198
|
+
|------|------|---------|-------------|
|
|
199
|
+
| `data` | `T[]` | `[]` | Row data |
|
|
200
|
+
| `columns` | `DataGridColumnDef<T>[]` | — | Column definitions |
|
|
201
|
+
| `error` | `Error \| null` | — | Display error state |
|
|
202
|
+
| `isLoading` | `boolean` | — | Show loading skeleton |
|
|
203
|
+
| `emptyMessage` | `string` | — | Message when data is empty |
|
|
204
|
+
| `tableHeight` | `string \| number \| 'auto'` | `'auto'` | Fixed height for the table body. Set a value to enable internal scrolling and custom scrollbars |
|
|
205
|
+
| `rowHeight` | `number` | `33` | Row height in px (also sets virtualizer estimate) |
|
|
206
|
+
| `estimateRowHeight` | `number` | — | Override virtualizer estimate independently of `rowHeight` |
|
|
207
|
+
| `overscan` | `number` | `10` | Rows to render outside the visible area |
|
|
208
|
+
| `bordered` | `boolean` | `false` | Show vertical dividers between columns |
|
|
209
|
+
| `enableSorting` | `boolean` | `false` | Enable column sorting |
|
|
210
|
+
| `initialSorting` | `SortingState` | — | Initial sort state |
|
|
211
|
+
| `onSortingChange` | `(s: SortingState) => void` | — | Called on sort change |
|
|
212
|
+
| `manualSorting` | `boolean` | `false` | Server-side sorting |
|
|
213
|
+
| `enableColumnFilters` | `boolean` | `false` | Show per-column filter row |
|
|
214
|
+
| `columnFilters` | `ColumnFiltersState` | — | Controlled filter state |
|
|
215
|
+
| `globalFilter` | `string` | — | Controlled global search value |
|
|
216
|
+
| `onGlobalFilterChange` | `(v: string) => void` | — | Called on global search change |
|
|
217
|
+
| `searchableColumns` | `string[]` | — | Column keys included in global search |
|
|
218
|
+
| `leftFilters` | `(table) => ReactNode` | — | Custom filter UI on the left side of the toolbar |
|
|
219
|
+
| `rightFilters` | `(table) => ReactNode` | — | Custom filter UI on the right side of the toolbar |
|
|
220
|
+
| `enableColumnResizing` | `boolean` | `false` | Enable drag-to-resize columns |
|
|
221
|
+
| `columnSizingMode` | `'auto' \| 'flex' \| 'fixed'` | `'auto'` | Column width strategy |
|
|
222
|
+
| `tableWidthMode` | `'spacer' \| 'fill-last' \| 'independent'` | `'spacer'` | How remaining width is distributed |
|
|
223
|
+
| `visibilityState` | `VisibilityState` | — | Controlled column visibility |
|
|
224
|
+
| `initialPinning` | `ColumnPinningState` | — | Initial pinned columns `{ left: [...], right: [...] }` |
|
|
225
|
+
| `checkboxConfig` | `CheckboxConfig<T>` | — | Row selection configuration |
|
|
226
|
+
| `onRowClick` | `(row: T) => void` | — | Row click handler |
|
|
227
|
+
| `rowCursor` | `boolean` | `false` | Show pointer cursor on rows |
|
|
228
|
+
| `tableKey` | `string` | — | Key for state persistence |
|
|
229
|
+
| `persistState` | `boolean` | `false` | Persist column sizing/visibility via Zustand |
|
|
230
|
+
| `onTableReady` | `(table: Table<T>) => void` | — | Called when TanStack Table instance is ready |
|
|
231
|
+
| `onColumnSizingChange` | `(sizing: ColumnSizingState) => void` | — | Called on column resize |
|
|
232
|
+
|
|
233
|
+
### `DataGrid`-only Props
|
|
234
|
+
|
|
235
|
+
| Prop | Type | Default | Description |
|
|
236
|
+
|------|------|---------|-------------|
|
|
237
|
+
| `enablePagination` | `boolean` | `false` | Show pagination bar |
|
|
238
|
+
| `paginationConfig` | `{ pageSize?: number; initialPageIndex?: number }` | — | Pagination defaults |
|
|
239
|
+
| `pageSizes` | `number[]` | — | Available page size options |
|
|
240
|
+
| `totalCount` | `number` | — | Server-side total row count for manual pagination |
|
|
241
|
+
| `onPageChange` | `(pageIndex, pageSize) => void` | — | Called on page change |
|
|
242
|
+
|
|
243
|
+
### `DataGridInfinity`-only Props
|
|
244
|
+
|
|
245
|
+
| Prop | Type | Default | Description |
|
|
246
|
+
|------|------|---------|-------------|
|
|
247
|
+
| `hasNextPage` | `boolean` | — | Whether more pages exist |
|
|
248
|
+
| `isFetchingNextPage` | `boolean` | — | Show loading indicator at bottom |
|
|
249
|
+
| `fetchNextPage` | `() => void` | — | Called to load next page |
|
|
250
|
+
| `rootMargin` | `string` | — | IntersectionObserver `rootMargin` to trigger early loading |
|
|
251
|
+
|
|
252
|
+
### `CheckboxConfig<T>`
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
interface CheckboxConfig<T> {
|
|
256
|
+
getRowId: (row: T) => string
|
|
257
|
+
selectedIds: Set<string>
|
|
258
|
+
onSelectAll: (rows: Row<T>[], checked: boolean) => void
|
|
259
|
+
onSelectOne: (rowId: string, checked: boolean) => void
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Column `meta` Reference
|
|
266
|
+
|
|
267
|
+
| Field | Type | Description |
|
|
268
|
+
|-------|------|-------------|
|
|
269
|
+
| `flex` | `number` | Flex ratio — distributes remaining container width proportionally |
|
|
270
|
+
| `autoSize` | `boolean` | Auto-fit column to content width via canvas text measurement |
|
|
271
|
+
| `minWidth` | `number` | Minimum column width in px |
|
|
272
|
+
| `maxWidth` | `number` | Maximum column width in px |
|
|
273
|
+
| `align` | `'left' \| 'center' \| 'right'` | Cell text alignment |
|
|
274
|
+
| `pin` | `'left' \| 'right'` | Pin column (fixed at column definition level) |
|
|
275
|
+
| `wrap` | `boolean` | Allow multi-line content; row height adjusts automatically |
|
|
276
|
+
| `filterType` | `'text' \| 'select' \| 'number' \| false` | Filter input type for this column |
|
|
277
|
+
| `actions` | `(row: T) => Action[]` | Row action menu items |
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Server-Side Data Example
|
|
282
|
+
|
|
283
|
+
```tsx
|
|
284
|
+
export function ServerSideTable() {
|
|
285
|
+
const [sorting, setSorting] = useState<SortingState>([])
|
|
286
|
+
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
|
287
|
+
const [globalFilter, setGlobalFilter] = useState('')
|
|
288
|
+
const [page, setPage] = useState({ index: 0, size: 20 })
|
|
289
|
+
|
|
290
|
+
const { data, total } = useServerData({ sorting, columnFilters, globalFilter, ...page })
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<DataGrid
|
|
294
|
+
data={data}
|
|
295
|
+
columns={columns}
|
|
296
|
+
totalCount={total}
|
|
297
|
+
enablePagination
|
|
298
|
+
manualSorting
|
|
299
|
+
enableSorting
|
|
300
|
+
initialSorting={sorting}
|
|
301
|
+
onSortingChange={setSorting}
|
|
302
|
+
columnFilters={columnFilters}
|
|
303
|
+
globalFilter={globalFilter}
|
|
304
|
+
onGlobalFilterChange={setGlobalFilter}
|
|
305
|
+
onPageChange={(index, size) => setPage({ index, size })}
|
|
306
|
+
tableHeight={500}
|
|
307
|
+
/>
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## License
|
|
315
|
+
|
|
316
|
+
MIT
|