@dynostack/react-grid 0.1.0

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,572 @@
1
+ <div align="center">
2
+
3
+ # @dynostack/react-grid
4
+
5
+ **Enterprise-grade React data grid. Drop-in.**
6
+
7
+ Built on [TanStack Table v8](https://tanstack.com/table) · [Radix UI](https://www.radix-ui.com/) · [Tailwind CSS](https://tailwindcss.com/) · ships shadcn/ui look-and-feel out of the box.
8
+
9
+ [![npm version](https://img.shields.io/npm/v/@dynostack/react-grid.svg?style=flat-square)](https://www.npmjs.com/package/@dynostack/react-grid)
10
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/@dynostack/react-grid?style=flat-square)](https://bundlephobia.com/package/@dynostack/react-grid)
11
+ [![license](https://img.shields.io/npm/l/@dynostack/react-grid.svg?style=flat-square)](./LICENSE)
12
+ [![types](https://img.shields.io/npm/types/@dynostack/react-grid?style=flat-square)](./dist/index.d.ts)
13
+
14
+ </div>
15
+
16
+ ---
17
+
18
+ A single `<DataTable />` component that gives you ag-grid–level functionality with a fraction of the API surface and a shadcn/ui aesthetic. Every behavior is opt-in via props — drop it in and it works; configure it and it scales.
19
+
20
+ ## Highlights
21
+
22
+ - **Filters that actually filter** — text, number, date with operators (`contains`, `not contains`, `equals`, `before`, `after`, `in range`, `blank`, `not blank`, …), AND/OR combine of two conditions, and a set filter with search + select-all
23
+ - **Inline editing** — double-click cell to edit, or enter row-edit mode with `Save` / `Cancel`
24
+ - **Add row** — local optimistic insert, edit, then commit on save
25
+ - **Per-column sort, hide, pin, resize, drag-reorder** — pinned columns are fully opaque while you scroll horizontally
26
+ - **Selection + bulk actions** — pinned `__select` column with select-all, clear, bulk delete
27
+ - **Expandable rows** — provide a `renderSubRow` panel or use TanStack's nested `getSubRows`
28
+ - **CSV / Excel export** — selection-aware (export selected vs. all)
29
+ - **Theming** — built on shadcn/ui CSS variables; per-instance overrides with a `theme` prop and ready-made presets
30
+ - **Density** — `compact` · `default` · `comfortable`
31
+ - **i18n / labels** — every visible string is overridable
32
+ - **Feature flags** — turn off any toolbar control or table capability with a single boolean
33
+ - **Tiny API, full TypeScript** — one component, fully typed generics, no provider context to wire up
34
+
35
+ ---
36
+
37
+ ## Table of contents
38
+
39
+ - [Install](#install)
40
+ - [Tailwind setup](#tailwind-setup)
41
+ - [Theme tokens](#theme-tokens)
42
+ - [Quick start](#quick-start)
43
+ - [Theming](#theming)
44
+ - [Density](#density)
45
+ - [Feature flags](#feature-flags)
46
+ - [Labels (i18n)](#labels-i18n)
47
+ - [Column meta](#column-meta)
48
+ - [Editing](#editing)
49
+ - [Filters](#filters)
50
+ - [Selection & bulk actions](#selection--bulk-actions)
51
+ - [Expandable rows](#expandable-rows)
52
+ - [Export](#export)
53
+ - [Custom row actions](#custom-row-actions)
54
+ - [Server-side data](#server-side-data)
55
+ - [API reference](#api-reference)
56
+ - [Compatibility](#compatibility)
57
+ - [Roadmap](#roadmap)
58
+ - [Contributing](#contributing)
59
+ - [License](#license)
60
+
61
+ ---
62
+
63
+ ## Install
64
+
65
+ ```sh
66
+ npm i @dynostack/react-grid
67
+ # or
68
+ pnpm add @dynostack/react-grid
69
+ # or
70
+ yarn add @dynostack/react-grid
71
+ ```
72
+
73
+ **Peer deps:** `react >= 18`, `react-dom >= 18`. All other deps (`@tanstack/react-table`, `radix-ui`, `lucide-react`, `class-variance-authority`, `clsx`, `tailwind-merge`) are bundled.
74
+
75
+ ## Tailwind setup
76
+
77
+ The component ships Tailwind class names verbatim. Tell your Tailwind config to scan the package files:
78
+
79
+ ```js
80
+ // tailwind.config.{js,ts}
81
+ export default {
82
+ content: [
83
+ "./src/**/*.{ts,tsx}",
84
+ "./node_modules/@dynostack/react-grid/dist/**/*.{js,mjs,cjs}",
85
+ ],
86
+ }
87
+ ```
88
+
89
+ > Tailwind v4? Add the same path to your `@source` directive instead.
90
+
91
+ ## Theme tokens
92
+
93
+ If your app already has [shadcn/ui](https://ui.shadcn.com/docs/theming) tokens defined on `:root`, you're done — the table inherits them automatically.
94
+
95
+ If not, import the default-tokens stylesheet once:
96
+
97
+ ```ts
98
+ import "@dynostack/react-grid/styles.css"
99
+ ```
100
+
101
+ Either way you can still override any token per-instance via the [`theme`](#theming) prop.
102
+
103
+ ---
104
+
105
+ ## Quick start
106
+
107
+ ```tsx
108
+ import { DataTable } from "@dynostack/react-grid"
109
+ import "@dynostack/react-grid/styles.css" // optional — only if you don't have shadcn tokens
110
+
111
+ type User = {
112
+ id: number
113
+ name: string
114
+ email: string
115
+ role: "admin" | "viewer"
116
+ joinedAt: string
117
+ }
118
+
119
+ const columns = [
120
+ { accessorKey: "id", header: "ID", size: 70 },
121
+ {
122
+ accessorKey: "name",
123
+ header: "Name",
124
+ meta: { label: "Name", editor: "text", filterType: "text" },
125
+ },
126
+ {
127
+ accessorKey: "email",
128
+ header: "Email",
129
+ meta: { label: "Email", editor: "text", filterType: "text" },
130
+ },
131
+ {
132
+ accessorKey: "role",
133
+ header: "Role",
134
+ meta: {
135
+ label: "Role",
136
+ editor: "select",
137
+ filterType: "multi-select",
138
+ selectOptions: [
139
+ { value: "admin", label: "Admin" },
140
+ { value: "viewer", label: "Viewer" },
141
+ ],
142
+ },
143
+ },
144
+ {
145
+ accessorKey: "joinedAt",
146
+ header: "Joined",
147
+ meta: { label: "Joined", editor: "date", filterType: "date" },
148
+ },
149
+ ]
150
+
151
+ export function Users({ data }: { data: User[] }) {
152
+ return (
153
+ <DataTable<User>
154
+ data={data}
155
+ columns={columns}
156
+ onCellEdit={(row, columnId, value) => save({ ...row, [columnId]: value })}
157
+ onRowSave={(row, draft) => save({ ...row, ...draft })}
158
+ onAddRow={() => ({ name: "", email: "", role: "viewer", joinedAt: today() })}
159
+ onBulkDelete={(rows) => removeMany(rows.map((r) => r.id))}
160
+ initialColumnPinning={{ left: ["__select", "id", "name"], right: ["__actions"] }}
161
+ />
162
+ )
163
+ }
164
+ ```
165
+
166
+ That's it. You now have sort + filter + edit + add + delete + export + pin + resize + reorder + select.
167
+
168
+ ---
169
+
170
+ ## Theming
171
+
172
+ All shadcn tokens are supported plus `radius` and `fontFamily`. Anything you omit falls through to the consumer's `:root`.
173
+
174
+ ### Use a preset
175
+
176
+ ```tsx
177
+ import { DataTable, themePresets } from "@dynostack/react-grid"
178
+
179
+ <DataTable data={data} columns={columns} theme={themePresets.violet} />
180
+ ```
181
+
182
+ Available presets: `light` · `dark` · `emerald` · `violet` · `amber`.
183
+
184
+ ### Custom tokens
185
+
186
+ ```tsx
187
+ <DataTable
188
+ data={data}
189
+ columns={columns}
190
+ theme={{
191
+ primary: "oklch(0.6 0.2 200)",
192
+ primaryForeground: "oklch(1 0 0)",
193
+ accent: "oklch(0.94 0.05 200)",
194
+ radius: "0.25rem",
195
+ fontFamily: "Inter, system-ui, sans-serif",
196
+ }}
197
+ />
198
+ ```
199
+
200
+ CSS variables are emitted on the table root, so multiple instances on the same page can wear different themes.
201
+
202
+ ### Compose with a preset
203
+
204
+ ```tsx
205
+ <DataTable
206
+ theme={{ ...themePresets.dark, primary: "oklch(0.7 0.18 250)" }}
207
+ />
208
+ ```
209
+
210
+ ---
211
+
212
+ ## Density
213
+
214
+ ```tsx
215
+ <DataTable density="compact" /* tighter rows */ />
216
+ <DataTable density="default" /* shadcn defaults */ />
217
+ <DataTable density="comfortable" /* extra padding */ />
218
+ ```
219
+
220
+ ---
221
+
222
+ ## Feature flags
223
+
224
+ Every toolbar control and table capability is a switch. Defaults are sensible — only set what you want to disable.
225
+
226
+ ```tsx
227
+ <DataTable
228
+ features={{
229
+ search: true, // global search input
230
+ refresh: true, // refresh button (when onRefresh is provided)
231
+ columnVisibility: true, // columns popover
232
+ export: true, // CSV / Excel menu
233
+ addRow: true, // "Add row" button (when onAddRow is provided)
234
+ pagination: true, // bottom pagination bar
235
+ sorting: true, // sort headers
236
+ filtering: true, // per-column filter popovers
237
+ resizing: true, // resize handles
238
+ reordering: true, // drag-to-reorder columns
239
+ pinning: true, // pin / unpin column controls
240
+ }}
241
+ />
242
+ ```
243
+
244
+ ---
245
+
246
+ ## Labels (i18n)
247
+
248
+ Every user-facing string is overridable.
249
+
250
+ ```tsx
251
+ <DataTable
252
+ labels={{
253
+ search: "Rechercher...",
254
+ addRow: "Ajouter",
255
+ delete: "Supprimer",
256
+ clear: "Effacer",
257
+ selected: "sélectionné(s)",
258
+ refresh: "Actualiser",
259
+ columns: "Colonnes",
260
+ export: "Exporter",
261
+ csv: "CSV",
262
+ excel: "Excel",
263
+ total: "Total",
264
+ noData: "Aucune donnée.",
265
+ noResults: "Aucun résultat.",
266
+ refreshing: "Actualisation",
267
+ rowsPerPage: "Lignes par page",
268
+ page: "Page",
269
+ of: "sur",
270
+ }}
271
+ />
272
+ ```
273
+
274
+ ---
275
+
276
+ ## Column meta
277
+
278
+ Each column can declare:
279
+
280
+ ```ts
281
+ type ColumnMeta = {
282
+ label?: string // header label & filter title
283
+ editor?:
284
+ | "text" | "number" | "currency" | "date"
285
+ | "select" | "switch" | "checkbox"
286
+ filterType?:
287
+ | "text" | "number" | "date"
288
+ | "select" | "multi-select" | "boolean"
289
+ selectOptions?: { value: string; label: string }[]
290
+ align?: "left" | "right" | "center"
291
+ cellClassName?: string
292
+ headerClassName?: string
293
+ exportable?: boolean
294
+ isEditable?: boolean | ((row) => boolean)
295
+ badgeMap?: Partial<
296
+ Record<string,
297
+ "default" | "secondary" | "destructive" |
298
+ "success" | "warning" | "outline"
299
+ >
300
+ >
301
+ }
302
+ ```
303
+
304
+ The default `filterFn` for a column is wired automatically from `meta.filterType`. You can still set a custom `filterFn` on the column to override it.
305
+
306
+ ---
307
+
308
+ ## Editing
309
+
310
+ Two modes, both prop-driven, both work simultaneously:
311
+
312
+ ### Single cell — double-click
313
+
314
+ ```tsx
315
+ <DataTable
316
+ onCellEdit={(row, columnId, value) =>
317
+ saveMutation.mutate({ ...row, [columnId]: value })
318
+ }
319
+ />
320
+ ```
321
+
322
+ ### Whole row — Edit action → Save / Cancel
323
+
324
+ ```tsx
325
+ <DataTable
326
+ onRowSave={(row, draft) =>
327
+ saveMutation.mutate({ ...row, ...draft })
328
+ }
329
+ />
330
+ ```
331
+
332
+ `isEditable` on `meta` can disable editing for individual rows or columns:
333
+
334
+ ```tsx
335
+ {
336
+ accessorKey: "email",
337
+ meta: {
338
+ editor: "text",
339
+ isEditable: (row) => row.role !== "billing",
340
+ },
341
+ }
342
+ ```
343
+
344
+ ---
345
+
346
+ ## Filters
347
+
348
+ The package exports the filter primitives so you can build custom panels too:
349
+
350
+ ```ts
351
+ import {
352
+ textFilterFn,
353
+ numberFilterFn,
354
+ dateFilterFn,
355
+ setFilterFn,
356
+ booleanFilterFn,
357
+ type AdvFilter,
358
+ type SetFilter,
359
+ type TextOp,
360
+ type NumberOp,
361
+ type DateOp,
362
+ } from "@dynostack/react-grid"
363
+ ```
364
+
365
+ | `filterType` | Operators | Value shape |
366
+ | -------------- | -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
367
+ | `text` | `contains`, `notContains`, `equals`, `notEqual`, `startsWith`, `endsWith`, `blank`, `notBlank` | `AdvFilter<TextOp, string>` |
368
+ | `number` | `equals`, `notEqual`, `lessThan`, `lessThanOrEqual`, `greaterThan`, `greaterThanOrEqual`, `inRange`, `blank`, `notBlank` | `AdvFilter<NumberOp, number>` |
369
+ | `date` | `equals`, `notEqual`, `before`, `after`, `inRange`, `blank`, `notBlank` | `AdvFilter<DateOp, string>` |
370
+ | `select` | set filter | `SetFilter` (`{ selected: string[] }`) |
371
+ | `multi-select` | set filter | `SetFilter` (`{ selected: string[] }`) |
372
+ | `boolean` | `All` / `True` / `False` | `boolean \| undefined` |
373
+
374
+ Text/number/date panels also expose **AND/OR combine** of a second condition, ag-grid style.
375
+
376
+ The set filter automatically derives unique values from the visible rows when `selectOptions` is not declared — search box, "Select all (filtered)" with indeterminate state, individual checkboxes.
377
+
378
+ ---
379
+
380
+ ## Selection & bulk actions
381
+
382
+ Selection is on by default (`enableSelection: true`). When any row is selected the toolbar swaps in:
383
+
384
+ - A `<count> selected` badge
385
+ - `Delete` button → wires to `onBulkDelete?(rows)`
386
+ - `Clear` button → resets selection
387
+
388
+ ```tsx
389
+ <DataTable
390
+ onBulkDelete={(rows) => removeMany(rows.map((r) => r.id))}
391
+ />
392
+ ```
393
+
394
+ ---
395
+
396
+ ## Expandable rows
397
+
398
+ ### Sub-row panel (custom JSX)
399
+
400
+ ```tsx
401
+ <DataTable
402
+ renderSubRow={(row) => <UserAuditPanel user={row} />}
403
+ />
404
+ ```
405
+
406
+ ### Nested rows (TanStack `getSubRows`)
407
+
408
+ ```tsx
409
+ <DataTable
410
+ getSubRows={(row) => row.children}
411
+ />
412
+ ```
413
+
414
+ When either is set, an `__expand` chevron column is added and pinned right next to `__select`.
415
+
416
+ ---
417
+
418
+ ## Export
419
+
420
+ ```tsx
421
+ <DataTable exportFileName="users" />
422
+ ```
423
+
424
+ Toolbar `Export` menu offers **CSV** and **Excel**. If any rows are selected, the menu becomes "Export N selected"; otherwise it exports all visible (filtered) rows.
425
+
426
+ Mark a column non-exportable via `meta.exportable: false`.
427
+
428
+ ---
429
+
430
+ ## Custom row actions
431
+
432
+ ```tsx
433
+ <DataTable
434
+ rowActions={["view", "edit", "duplicate", "delete"]}
435
+ customRowActions={[
436
+ {
437
+ id: "suspend",
438
+ label: "Suspend",
439
+ icon: <BanIcon />,
440
+ danger: true,
441
+ show: (r) => r.status !== "suspended",
442
+ },
443
+ { id: "archive", label: "Archive", icon: <ArchiveIcon /> },
444
+ ]}
445
+ onRowAction={(action, row) => {
446
+ if (action === "delete") deleteMutation.mutate([row.id])
447
+ if (action === "suspend") saveMutation.mutate({ ...row, status: "suspended" })
448
+ // ...
449
+ }}
450
+ />
451
+ ```
452
+
453
+ ---
454
+
455
+ ## Server-side data
456
+
457
+ Provide a controlled global filter and refetch on change:
458
+
459
+ ```tsx
460
+ const [q, setQ] = useState("")
461
+ const usersQuery = useQuery({
462
+ queryKey: ["users", q],
463
+ queryFn: () => fetchUsers({ q }),
464
+ })
465
+
466
+ <DataTable
467
+ data={usersQuery.data ?? []}
468
+ isLoading={usersQuery.isLoading}
469
+ isFetching={usersQuery.isFetching}
470
+ onRefresh={() => usersQuery.refetch()}
471
+ globalFilter={q}
472
+ onGlobalFilterChange={setQ}
473
+ totalRecords={usersQuery.data?.length ?? 0}
474
+ />
475
+ ```
476
+
477
+ Pair with TanStack Query's pagination/cursor utilities for cursor-based grids.
478
+
479
+ ---
480
+
481
+ ## API reference
482
+
483
+ | Prop | Type | Default | Description |
484
+ | ------------------------- | ---------------------------------------------------------- | ---------------------- | ------------------------------------------------------ |
485
+ | `data` | `TData[]` | — | Row data. |
486
+ | `columns` | `ColumnDef<TData>[]` | — | TanStack column definitions. |
487
+ | `isLoading` | `boolean` | `false` | Initial skeleton state. |
488
+ | `isFetching` | `boolean` | `false` | Background-refresh indicator. |
489
+ | `onRefresh` | `() => void` | — | Refresh button handler. |
490
+ | `totalRecords` | `number` | `data.length` | Total count badge in toolbar. |
491
+ | `exportFileName` | `string` | `"export"` | Base filename for CSV / Excel export. |
492
+ | `enableSelection` | `boolean` | `true` | Show the `__select` column. |
493
+ | `renderSubRow` | `(row: TData) => ReactNode` | — | Custom expandable panel. |
494
+ | `getSubRows` | `(row: TData) => TData[] \| undefined` | — | Nested rows accessor. |
495
+ | `rowActions` | `("view" \| "edit" \| "duplicate" \| "delete")[]` | all four | Built-in row actions. |
496
+ | `customRowActions` | `CustomRowAction<TData>[]` | `[]` | Extra row actions. |
497
+ | `onRowAction` | `(action, row) => void` | — | Row action handler. |
498
+ | `onCellEdit` | `(row, columnId, value) => void` | — | Single-cell save handler. |
499
+ | `onRowSave` | `(row, draft) => void` | — | Row-edit save handler. |
500
+ | `onAddRow` | `() => Partial<TData>` | — | Returns the empty draft for "Add row". |
501
+ | `onBulkDelete` | `(rows: TData[]) => void` | — | Bulk delete handler. |
502
+ | `initialPageSize` | `number` | `10` | Initial pagination size. |
503
+ | `pageSizeOptions` | `number[]` | shadcn defaults | Page-size dropdown options. |
504
+ | `initialColumnPinning` | `ColumnPinningState` | `{ left: [], right: [] }` | Initial pinned columns. |
505
+ | `initialColumnVisibility` | `VisibilityState` | `{}` | Initial hidden columns. |
506
+ | `globalFilter` | `string` | uncontrolled | Controlled global filter value. |
507
+ | `onGlobalFilterChange` | `(value: string) => void` | — | Controlled global filter setter. |
508
+ | `className` | `string` | — | Extra classes on the table root. |
509
+ | `toolbarSlot` | `ReactNode` | — | Custom JSX prepended into the toolbar. |
510
+ | `features` | `DataTableFeatures` | all on | Feature flags. |
511
+ | `labels` | `DataTableLabels` | English defaults | i18n labels. |
512
+ | `density` | `"compact" \| "default" \| "comfortable"` | `"default"` | Row density. |
513
+ | `theme` | `DataTableTheme` | inherits `:root` | Per-instance CSS-variable overrides. |
514
+
515
+ `TData` must extend `{ id: string \| number }`.
516
+
517
+ ---
518
+
519
+ ## Compatibility
520
+
521
+ | Stack | Tested on |
522
+ | ----------------- | -------------------------- |
523
+ | React | 18.x · 19.x |
524
+ | TanStack Table | 8.21+ |
525
+ | Tailwind CSS | 3.x · 4.x |
526
+ | Bundler | Vite · Next.js · Webpack 5 |
527
+
528
+ ESM and CJS bundles ship side-by-side. Tree-shakeable. Marked `"use client"` for Next.js App Router compatibility.
529
+
530
+ ---
531
+
532
+ ## Roadmap
533
+
534
+ - [ ] Server-side pagination/sorting helpers (controlled-state recipes)
535
+ - [ ] Column groups (header rowSpan/colSpan)
536
+ - [ ] Pivot mode
537
+ - [ ] Aggregation row (sum, avg, min, max, count)
538
+ - [ ] Saved view profiles (filter + visibility + pinning snapshots)
539
+ - [ ] Virtualized rows (TanStack Virtual integration)
540
+ - [ ] Storybook + visual regression tests
541
+ - [ ] CodeSandbox / StackBlitz starter
542
+
543
+ Have a use case that isn't covered? [Open an issue](https://github.com/wanted-coder-vijay/wcv-data-grid/issues/new) — happy to consider it.
544
+
545
+ ---
546
+
547
+ ## Contributing
548
+
549
+ ```sh
550
+ git clone https://github.com/wanted-coder-vijay/wcv-data-grid.git
551
+ cd wcv-data-grid
552
+ npm install
553
+ npm run dev # tsup --watch
554
+ npm run typecheck # tsc --noEmit
555
+ npm run build # produce dist/
556
+ ```
557
+
558
+ PRs welcome. Please keep the prop API additive — feature toggles over breaking changes.
559
+
560
+ ---
561
+
562
+ ## License
563
+
564
+ [Apache-2.0](./LICENSE) · Copyright © 2026 vijay kumar anchupogu (wanted-coder-vijay)
565
+
566
+ See [NOTICE](./NOTICE) for attribution requirements.
567
+
568
+ > **Package history.** This package was briefly published as
569
+ > `@dynostack/gridstack@0.1.0` under the MIT license before being
570
+ > renamed to `@dynostack/react-grid` and relicensed under Apache-2.0.
571
+ > The old name is deprecated; new code should depend on
572
+ > `@dynostack/react-grid` only.