@dynostack/react-grid 0.1.3 → 0.3.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 +281 -20
- package/dist/index.cjs +607 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +204 -42
- package/dist/index.d.ts +204 -42
- package/dist/index.js +603 -62
- package/dist/index.js.map +1 -1
- package/dist/page.css +24 -0
- package/dist/styles.css +145 -46
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ A single `<DataTable />` component that gives you ag-grid–level functionality
|
|
|
26
26
|
- **Selection + bulk actions** — pinned `__select` column with select-all, clear, bulk delete
|
|
27
27
|
- **Expandable rows** — provide a `renderSubRow` panel or use TanStack's nested `getSubRows`
|
|
28
28
|
- **CSV / Excel export** — selection-aware (export selected vs. all)
|
|
29
|
-
- **Theming** —
|
|
29
|
+
- **Theming that just works** — shadcn-compatible CSS variables, automatic OS dark-mode follow, cascade-layered defaults that never overwrite your app theme, full-repaint moded presets (`violet`, `emerald`, `amber`, `rose`, `sky`, `slate`, …), `buildPreset(hue)` for custom hues, and `isolate` to opt out of inheriting the app theme
|
|
30
30
|
- **Density** — `compact` · `default` · `comfortable`
|
|
31
31
|
- **i18n / labels** — every visible string is overridable
|
|
32
32
|
- **Feature flags** — turn off any toolbar control or table capability with a single boolean
|
|
@@ -51,6 +51,8 @@ A single `<DataTable />` component that gives you ag-grid–level functionality
|
|
|
51
51
|
- [Selection & bulk actions](#selection--bulk-actions)
|
|
52
52
|
- [Expandable rows](#expandable-rows)
|
|
53
53
|
- [Export](#export)
|
|
54
|
+
- [View sheet](#view-sheet)
|
|
55
|
+
- [Delete confirmation](#delete-confirmation)
|
|
54
56
|
- [Custom row actions](#custom-row-actions)
|
|
55
57
|
- [Server-side data](#server-side-data)
|
|
56
58
|
- [API reference](#api-reference)
|
|
@@ -91,14 +93,44 @@ export default {
|
|
|
91
93
|
|
|
92
94
|
## Theme tokens
|
|
93
95
|
|
|
94
|
-
|
|
96
|
+
The grid is built on **shadcn/ui CSS variables**. It auto-adjusts to whatever theme your app already has:
|
|
95
97
|
|
|
96
|
-
|
|
98
|
+
| Your app has… | What you do | What you get |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| Nothing (bare React) | `import "@dynostack/react-grid/styles.css"` | Clean light theme, auto-switches to dark on OS preference. |
|
|
101
|
+
| shadcn/ui (default theme) | Nothing | Grid inherits your `:root` tokens automatically. |
|
|
102
|
+
| shadcn/ui with a custom theme (Stone / Zinc / your own hue) | Nothing | Grid picks up your custom tokens automatically. |
|
|
103
|
+
| Custom theme using shadcn token names | Nothing | Same as above. |
|
|
104
|
+
| Custom theme with non-shadcn names | Pass [`theme` prop](#theming) | Per-instance override mapped to shadcn vars. |
|
|
105
|
+
| Want one grid to ignore the app theme | Pass `isolate` | Grid uses bundled defaults regardless of `:root`. |
|
|
106
|
+
|
|
107
|
+
**Why this just works.** The bundled `styles.css` declares its defaults inside the `dynostack-grid-defaults` cascade layer. Any unlayered consumer rule (which is where shadcn and most app CSS lives) automatically wins — import order doesn't matter, and you can't accidentally overwrite your app's theme by importing the grid's stylesheet.
|
|
108
|
+
|
|
109
|
+
### Minimal install
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
// main.tsx — once per app
|
|
113
|
+
import "@dynostack/react-grid/styles.css"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Optional: extend the theme to the page
|
|
117
|
+
|
|
118
|
+
By default the grid only styles itself, not the surrounding page. If you want `<body>` to use the same background/foreground as the grid:
|
|
97
119
|
|
|
98
120
|
```ts
|
|
99
121
|
import "@dynostack/react-grid/styles.css"
|
|
122
|
+
import "@dynostack/react-grid/page.css" // optional
|
|
100
123
|
```
|
|
101
124
|
|
|
125
|
+
### Dark mode
|
|
126
|
+
|
|
127
|
+
| Mode | How to enable | Behavior |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| Follow OS | Default — no action required | Light by day, dark by night via `prefers-color-scheme`. |
|
|
130
|
+
| Force light | Add `class="light"` to `<html>` | Stays light regardless of OS. |
|
|
131
|
+
| Force dark | Add `class="dark"` to `<html>` | Stays dark regardless of OS. |
|
|
132
|
+
| Per-instance | `<DataTable theme={themePresets.violet}>` | Grid auto-flips light/dark inside the moded preset. |
|
|
133
|
+
|
|
102
134
|
Either way you can still override any token per-instance via the [`theme`](#theming) prop.
|
|
103
135
|
|
|
104
136
|
---
|
|
@@ -248,24 +280,12 @@ rollback, toast notifications, and validation.
|
|
|
248
280
|
|
|
249
281
|
## Theming
|
|
250
282
|
|
|
251
|
-
|
|
283
|
+
The `theme` prop accepts **two shapes**. Pick whichever fits your use case.
|
|
252
284
|
|
|
253
|
-
###
|
|
254
|
-
|
|
255
|
-
```tsx
|
|
256
|
-
import { DataTable, themePresets } from "@dynostack/react-grid"
|
|
257
|
-
|
|
258
|
-
<DataTable data={data} columns={columns} theme={themePresets.violet} />
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
Available presets: `light` · `dark` · `emerald` · `violet` · `amber`.
|
|
262
|
-
|
|
263
|
-
### Custom tokens
|
|
285
|
+
### Shape 1 — Flat tokens
|
|
264
286
|
|
|
265
287
|
```tsx
|
|
266
288
|
<DataTable
|
|
267
|
-
data={data}
|
|
268
|
-
columns={columns}
|
|
269
289
|
theme={{
|
|
270
290
|
primary: "oklch(0.6 0.2 200)",
|
|
271
291
|
primaryForeground: "oklch(1 0 0)",
|
|
@@ -276,16 +296,102 @@ Available presets: `light` · `dark` · `emerald` · `violet` · `amber`.
|
|
|
276
296
|
/>
|
|
277
297
|
```
|
|
278
298
|
|
|
279
|
-
|
|
299
|
+
All shadcn tokens are supported plus `radius` and `fontFamily`. Anything you omit falls through to whatever your app's `:root` provides.
|
|
300
|
+
|
|
301
|
+
### Shape 2 — Moded `{ light, dark }`
|
|
302
|
+
|
|
303
|
+
A moded theme repaints the whole table **and** auto-flips on dark mode (OS preference *or* a `.dark` ancestor):
|
|
304
|
+
|
|
305
|
+
```tsx
|
|
306
|
+
<DataTable
|
|
307
|
+
theme={{
|
|
308
|
+
light: { background: "oklch(0.99 0.005 285)", primary: "oklch(0.55 0.22 285)", /* … */ },
|
|
309
|
+
dark: { background: "oklch(0.16 0.012 285)", primary: "oklch(0.7 0.18 285)", /* … */ },
|
|
310
|
+
}}
|
|
311
|
+
/>
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
The grid emits a tiny scoped `<style>` block that targets only this instance — multiple grids on the same page can wear different moded themes without interfering.
|
|
315
|
+
|
|
316
|
+
### Use a preset
|
|
317
|
+
|
|
318
|
+
Presets ship in **moded shape** — passing one repaints the entire table and follows dark mode automatically:
|
|
319
|
+
|
|
320
|
+
```tsx
|
|
321
|
+
import { DataTable, themePresets } from "@dynostack/react-grid"
|
|
322
|
+
|
|
323
|
+
<DataTable theme={themePresets.violet} />
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Available presets:
|
|
327
|
+
|
|
328
|
+
| Preset | Hue |
|
|
329
|
+
|---|---|
|
|
330
|
+
| `neutral` | Grayscale (default appearance) |
|
|
331
|
+
| `light` | Force light, no dark variant |
|
|
332
|
+
| `dark` | Force dark, no light variant |
|
|
333
|
+
| `violet` | 285° |
|
|
334
|
+
| `emerald` | 162° |
|
|
335
|
+
| `amber` | 65° |
|
|
336
|
+
| `rose` | 15° |
|
|
337
|
+
| `sky` | 235° |
|
|
338
|
+
| `slate` | 240° (low chroma) |
|
|
339
|
+
|
|
340
|
+
### Build a custom preset from a single hue
|
|
341
|
+
|
|
342
|
+
```tsx
|
|
343
|
+
import { DataTable, buildPreset } from "@dynostack/react-grid"
|
|
344
|
+
|
|
345
|
+
const teal = buildPreset(180) // hue only
|
|
346
|
+
const subtleTeal = buildPreset(180, 0.015) // hue + custom chroma
|
|
347
|
+
|
|
348
|
+
<DataTable theme={teal} />
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
`buildPreset(hue, chroma?)` returns a full `{ light, dark }` token set tinted around the given OKLCH hue.
|
|
280
352
|
|
|
281
353
|
### Compose with a preset
|
|
282
354
|
|
|
283
355
|
```tsx
|
|
284
356
|
<DataTable
|
|
285
|
-
theme={{
|
|
357
|
+
theme={{
|
|
358
|
+
...themePresets.violet,
|
|
359
|
+
light: { ...themePresets.violet.light, primary: "oklch(0.7 0.18 250)" },
|
|
360
|
+
}}
|
|
286
361
|
/>
|
|
287
362
|
```
|
|
288
363
|
|
|
364
|
+
### Isolate a grid from the app theme
|
|
365
|
+
|
|
366
|
+
When embedding inside a heavily-themed shell where you want the table to keep its own look:
|
|
367
|
+
|
|
368
|
+
```tsx
|
|
369
|
+
<DataTable isolate /* uses bundled neutral tokens, ignores app :root */ />
|
|
370
|
+
<DataTable isolate theme={themePresets.violet} /* isolated AND violet */ />
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Multiple grids, different themes
|
|
374
|
+
|
|
375
|
+
CSS variables are emitted on each table root, so this works:
|
|
376
|
+
|
|
377
|
+
```tsx
|
|
378
|
+
<DataTable theme={themePresets.violet} />
|
|
379
|
+
<DataTable theme={themePresets.emerald} />
|
|
380
|
+
<DataTable theme={{ primary: "oklch(0.6 0.2 200)" }} />
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Precedence summary
|
|
384
|
+
|
|
385
|
+
```
|
|
386
|
+
┌──────────────────────────────────────────────────────────┐
|
|
387
|
+
│ Inline style on the grid root (per-instance `theme`) │ ← highest
|
|
388
|
+
├──────────────────────────────────────────────────────────┤
|
|
389
|
+
│ Consumer's :root rules (shadcn, custom app CSS) │
|
|
390
|
+
├──────────────────────────────────────────────────────────┤
|
|
391
|
+
│ @layer dynostack-grid-defaults (bundled styles.css) │ ← lowest
|
|
392
|
+
└──────────────────────────────────────────────────────────┘
|
|
393
|
+
```
|
|
394
|
+
|
|
289
395
|
---
|
|
290
396
|
|
|
291
397
|
## Density
|
|
@@ -506,6 +612,72 @@ Mark a column non-exportable via `meta.exportable: false`.
|
|
|
506
612
|
|
|
507
613
|
---
|
|
508
614
|
|
|
615
|
+
## View sheet
|
|
616
|
+
|
|
617
|
+
Click the row action "View" → a right-side `Sheet` slides in showing every visible column as a `{Label}: {value}` card. The user can switch layout density inline (Compact 1 col / Relaxed 2 col / Comfy 3 col).
|
|
618
|
+
|
|
619
|
+
Works out of the box with no props. Customize via `viewSheet`:
|
|
620
|
+
|
|
621
|
+
```tsx
|
|
622
|
+
<DataTable
|
|
623
|
+
viewSheet={{
|
|
624
|
+
side: "right", // or "left"
|
|
625
|
+
defaultDensity: "relaxed", // initial column count
|
|
626
|
+
hideDensityTabs: true, // hide the layout picker
|
|
627
|
+
fields: ["name", "email", "role"], // limit / reorder shown columns
|
|
628
|
+
renderField: ({ column, value, row }) => // override how a value renders
|
|
629
|
+
column.id === "phone" ? <a href={`tel:${value}`}>{String(value)}</a> : null,
|
|
630
|
+
renderHeader: (row) => <YourCustomHeader row={row} />,
|
|
631
|
+
labels: {
|
|
632
|
+
title: (row) => `${row.name} (${row.role})`,
|
|
633
|
+
description: (row) => `Joined ${row.joinedAt}`,
|
|
634
|
+
emptyValue: "—",
|
|
635
|
+
density: { compact: "1 col", relaxed: "2 cols", comfy: "3 cols" },
|
|
636
|
+
},
|
|
637
|
+
}}
|
|
638
|
+
onView={(row) => track("user.view", row)} // optional side-effect
|
|
639
|
+
/>
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
Disable the built-in sheet entirely:
|
|
643
|
+
|
|
644
|
+
```tsx
|
|
645
|
+
<DataTable viewSheet={false} onView={(row) => router.push(`/users/${row.id}`)} />
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
`onView` fires before the sheet opens, so you can navigate / log / fetch alongside it.
|
|
649
|
+
|
|
650
|
+
---
|
|
651
|
+
|
|
652
|
+
## Delete confirmation
|
|
653
|
+
|
|
654
|
+
Both the row-action "Delete" and the toolbar "Bulk delete" open a confirmation `AlertDialog` by default. The user must confirm before `onDelete` or `onBulkDelete` fires.
|
|
655
|
+
|
|
656
|
+
```tsx
|
|
657
|
+
<DataTable
|
|
658
|
+
onDelete={(row) => api.deleteUser(row.id)}
|
|
659
|
+
onBulkDelete={(rows) => api.bulkDelete(rows.map(r => r.id))}
|
|
660
|
+
confirmDelete={{
|
|
661
|
+
title: ({ rows, source }) =>
|
|
662
|
+
source === "bulk"
|
|
663
|
+
? `Delete ${rows.length} users?`
|
|
664
|
+
: `Delete ${rows[0].name}?`,
|
|
665
|
+
description: ({ rows }) =>
|
|
666
|
+
`${rows.length === 1 ? "This user" : "These users"} will be permanently removed. This cannot be undone.`,
|
|
667
|
+
confirmLabel: "Yes, delete",
|
|
668
|
+
cancelLabel: "Keep",
|
|
669
|
+
}}
|
|
670
|
+
/>
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
Skip the dialog (fire immediately):
|
|
674
|
+
|
|
675
|
+
```tsx
|
|
676
|
+
<DataTable confirmDelete={false} onDelete={(row) => softDelete(row)} />
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
---
|
|
680
|
+
|
|
509
681
|
## Custom row actions
|
|
510
682
|
|
|
511
683
|
```tsx
|
|
@@ -625,7 +797,12 @@ operations and only renders the returned page.
|
|
|
625
797
|
| `features` | `DataTableFeatures` | all on | Feature flags. |
|
|
626
798
|
| `labels` | `DataTableLabels` | English defaults | i18n labels. |
|
|
627
799
|
| `density` | `"compact" \| "default" \| "comfortable"` | `"default"` | Row density. |
|
|
628
|
-
| `theme` | `DataTableTheme` | inherits `:root` | Per-instance CSS-variable overrides.
|
|
800
|
+
| `theme` | `DataTableTheme` | inherits `:root` | Per-instance CSS-variable overrides. Accepts flat tokens **or** `{ light, dark }`. |
|
|
801
|
+
| `isolate` | `boolean` | `false` | Ignore the app's `:root` and render with bundled defaults. |
|
|
802
|
+
| `onView` | `(row: TData) => void` | — | Side-effect when "View" is clicked. Fires *before* the sheet opens. |
|
|
803
|
+
| `onDelete` | `(row: TData) => void` | — | Single-row delete handler. Fires *after* the confirm modal (or immediately if `confirmDelete={false}`). |
|
|
804
|
+
| `viewSheet` | `ViewSheetConfig<TData> \| false` | enabled | Configure or disable the built-in View sheet. |
|
|
805
|
+
| `confirmDelete` | `ConfirmDeleteConfig<TData> \| boolean` | `true` | Configure or disable the delete confirmation modal (applies to single + bulk). |
|
|
629
806
|
|
|
630
807
|
`TData` must extend `{ id: string \| number }`.
|
|
631
808
|
|
|
@@ -649,6 +826,90 @@ type DataTableDataSource<TData> = {
|
|
|
649
826
|
}
|
|
650
827
|
```
|
|
651
828
|
|
|
829
|
+
```ts
|
|
830
|
+
// Theme types
|
|
831
|
+
type DataTableTokens = {
|
|
832
|
+
background?: string
|
|
833
|
+
foreground?: string
|
|
834
|
+
card?: string
|
|
835
|
+
cardForeground?: string
|
|
836
|
+
popover?: string
|
|
837
|
+
popoverForeground?: string
|
|
838
|
+
primary?: string
|
|
839
|
+
primaryForeground?: string
|
|
840
|
+
secondary?: string
|
|
841
|
+
secondaryForeground?: string
|
|
842
|
+
muted?: string
|
|
843
|
+
mutedForeground?: string
|
|
844
|
+
accent?: string
|
|
845
|
+
accentForeground?: string
|
|
846
|
+
destructive?: string
|
|
847
|
+
destructiveForeground?: string
|
|
848
|
+
border?: string
|
|
849
|
+
input?: string
|
|
850
|
+
ring?: string
|
|
851
|
+
radius?: string
|
|
852
|
+
fontFamily?: string
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
type DataTableModedTheme = {
|
|
856
|
+
light?: DataTableTokens
|
|
857
|
+
dark?: DataTableTokens
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
type DataTableTheme = DataTableTokens | DataTableModedTheme
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
```ts
|
|
864
|
+
// Theme exports
|
|
865
|
+
import {
|
|
866
|
+
themePresets, // ready-made moded presets
|
|
867
|
+
buildPreset, // (hue, chroma?) => DataTableModedTheme
|
|
868
|
+
splitTheme, // (theme) => { light, dark }
|
|
869
|
+
tokensToStyle, // (tokens) => React.CSSProperties
|
|
870
|
+
tokensToCssBlock, // (tokens) => "var:val;var:val" string
|
|
871
|
+
ISOLATE_LIGHT_TOKENS,
|
|
872
|
+
ISOLATE_DARK_TOKENS,
|
|
873
|
+
} from "@dynostack/react-grid"
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
```ts
|
|
877
|
+
// View sheet types
|
|
878
|
+
type ViewSheetDensity = "compact" | "relaxed" | "comfy"
|
|
879
|
+
|
|
880
|
+
type ViewSheetConfig<TData> = {
|
|
881
|
+
side?: "right" | "left" | "top" | "bottom"
|
|
882
|
+
defaultDensity?: ViewSheetDensity
|
|
883
|
+
hideDensityTabs?: boolean
|
|
884
|
+
fields?: string[]
|
|
885
|
+
renderField?: (args: {
|
|
886
|
+
column: Column<TData, unknown>
|
|
887
|
+
value: unknown
|
|
888
|
+
row: TData
|
|
889
|
+
}) => React.ReactNode
|
|
890
|
+
renderHeader?: (row: TData) => React.ReactNode
|
|
891
|
+
labels?: {
|
|
892
|
+
title?: (row: TData) => React.ReactNode
|
|
893
|
+
description?: (row: TData) => React.ReactNode
|
|
894
|
+
emptyValue?: string
|
|
895
|
+
density?: { compact?: string; relaxed?: string; comfy?: string }
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// Confirm-delete types
|
|
900
|
+
type ConfirmDeleteContext<TData> = {
|
|
901
|
+
rows: TData[]
|
|
902
|
+
source: "single" | "bulk"
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
type ConfirmDeleteConfig<TData> = {
|
|
906
|
+
title?: (ctx: ConfirmDeleteContext<TData>) => React.ReactNode
|
|
907
|
+
description?: (ctx: ConfirmDeleteContext<TData>) => React.ReactNode
|
|
908
|
+
confirmLabel?: string
|
|
909
|
+
cancelLabel?: string
|
|
910
|
+
}
|
|
911
|
+
```
|
|
912
|
+
|
|
652
913
|
---
|
|
653
914
|
|
|
654
915
|
## Compatibility
|