@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 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