@dynostack/react-grid 0.3.1 → 0.4.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 +88 -6
- package/dist/index.cjs +69 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -11
- package/dist/index.d.ts +24 -11
- package/dist/index.js +70 -28
- package/dist/index.js.map +1 -1
- package/dist/styles.css +8 -0
- package/dist/tailwind-content.js +3 -0
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -13,12 +13,45 @@ Built on [TanStack Table v8](https://tanstack.com/table) · [Radix UI](https://w
|
|
|
13
13
|
|
|
14
14
|
</div>
|
|
15
15
|
|
|
16
|
+
**[Live showcase](https://dynostack-react-grid.vercel.app)** · **[Interactive playground](https://dynostack-react-grid.vercel.app/playground)** · **[Setup and configuration docs](https://dynostack-react-grid.vercel.app/docs)**
|
|
17
|
+
|
|
18
|
+
Try every feature on real sample data, customize themes and density, share a configuration link, and copy the matching React props. The demo includes spring-based interactions and works on mobile.
|
|
19
|
+
|
|
16
20
|
---
|
|
17
21
|
|
|
18
22
|
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
23
|
|
|
24
|
+
## Showcase
|
|
25
|
+
|
|
26
|
+
<p align="center">
|
|
27
|
+
<img src="public/table-images/Screenshot%202026-05-25%20233937.png" alt="React data grid with toolbar, sorting, filters, pagination, export, and add-row controls" width="100%" />
|
|
28
|
+
</p>
|
|
29
|
+
|
|
30
|
+
| Selection + bulk actions | Column menu |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| <img src="public/table-images/Screenshot%202026-05-25%20234013.png" alt="Rows selected with bulk delete and clear actions visible in the toolbar" width="100%" /> | <img src="public/table-images/Screenshot%202026-05-25%20234035.png" alt="Column menu with ascending sort, descending sort, pin, unpin, and hide column actions" width="100%" /> |
|
|
33
|
+
|
|
34
|
+
| Filter builder | Column visibility |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| <img src="public/table-images/Screenshot%202026-05-25%20234221.png" alt="Column filter builder with operator select, value input, add condition, clear, close, and apply controls" width="100%" /> | <img src="public/table-images/Screenshot%202026-05-25%20234246.png" alt="Column visibility popover with checked columns and reset control" width="100%" /> |
|
|
37
|
+
|
|
38
|
+
| Export selected rows | Inline add and edit |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| <img src="public/table-images/Screenshot%202026-05-25%20234301.png" alt="Export menu for selected rows with CSV and Excel options" width="100%" /> | <img src="public/table-images/Screenshot%202026-05-25%20234313.png" alt="Inline add-row editing with text fields, select input, date input, save, and cancel controls" width="100%" /> |
|
|
41
|
+
|
|
42
|
+
| Row actions | Details panel |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| <img src="public/table-images/Screenshot%202026-05-25%20234410.png" alt="Row actions menu with view, edit, duplicate, and delete commands" width="100%" /> | <img src="public/table-images/Screenshot%202026-05-25%20234428.png" alt="Right-side details panel with compact, relaxed, and comfy density controls" width="100%" /> |
|
|
45
|
+
|
|
46
|
+
| Search |
|
|
47
|
+
| --- |
|
|
48
|
+
| <img src="public/table-images/Screenshot%202026-05-25%20234453.png" alt="Search input expanded in the table toolbar" width="100%" /> |
|
|
49
|
+
|
|
20
50
|
## Highlights
|
|
21
51
|
|
|
52
|
+
- **Layout and state controls** — `initialSorting`, `onSelectionChange`, `ariaLabel`, `striped`, `stickyHeader`, and `maxHeight`. Disabling pagination renders all matching loaded rows.
|
|
53
|
+
- **Safer exports and editing** — spreadsheet formulas in untrusted text are neutralized; row editing respects `meta.isEditable`; theme values cannot escape scoped CSS declarations.
|
|
54
|
+
|
|
22
55
|
- **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
56
|
- **Inline editing** — double-click cell to edit, or enter row-edit mode with `Save` / `Cancel`
|
|
24
57
|
- **Add row** — local optimistic insert, edit, then commit on save
|
|
@@ -26,6 +59,8 @@ A single `<DataTable />` component that gives you ag-grid–level functionality
|
|
|
26
59
|
- **Selection + bulk actions** — pinned `__select` column with select-all, clear, bulk delete
|
|
27
60
|
- **Expandable rows** — provide a `renderSubRow` panel or use TanStack's nested `getSubRows`
|
|
28
61
|
- **CSV / Excel export** — selection-aware (export selected vs. all)
|
|
62
|
+
- **Built-in row Details panel** — `View` opens a scoped sheet with compact, relaxed, and comfy field layouts
|
|
63
|
+
- **Scoped delete confirmation** — row and bulk delete confirmations stay inside the table instead of covering the entire app
|
|
29
64
|
- **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
65
|
- **Density** — `compact` · `default` · `comfortable`
|
|
31
66
|
- **i18n / labels** — every visible string is overridable
|
|
@@ -37,6 +72,7 @@ A single `<DataTable />` component that gives you ag-grid–level functionality
|
|
|
37
72
|
## Table of contents
|
|
38
73
|
|
|
39
74
|
- [Install](#install)
|
|
75
|
+
- [Showcase](#showcase)
|
|
40
76
|
- [Tailwind setup](#tailwind-setup)
|
|
41
77
|
- [Theme tokens](#theme-tokens)
|
|
42
78
|
- [Quick start](#quick-start)
|
|
@@ -65,6 +101,34 @@ A single `<DataTable />` component that gives you ag-grid–level functionality
|
|
|
65
101
|
|
|
66
102
|
## Install
|
|
67
103
|
|
|
104
|
+
### Run the showcase locally
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
git clone https://github.com/wanted-coder-vijay/wcv-data-grid.git
|
|
108
|
+
cd wcv-data-grid
|
|
109
|
+
npm install
|
|
110
|
+
npm run build
|
|
111
|
+
npm install --prefix showcase
|
|
112
|
+
npm run showcase:dev
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### New layout controls
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
<DataTable
|
|
119
|
+
data={rows}
|
|
120
|
+
columns={columns}
|
|
121
|
+
initialSorting={[{ id: "name", desc: false }]}
|
|
122
|
+
onSelectionChange={(selectedRows) => setSelectedRows(selectedRows)}
|
|
123
|
+
ariaLabel="Project workspace"
|
|
124
|
+
striped
|
|
125
|
+
stickyHeader
|
|
126
|
+
maxHeight="480px"
|
|
127
|
+
/>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`onSelectionChange` returns selected **loaded** row objects; server-side exports and selection do not fetch unseen pages. Editing flags are UI controls: your server must also validate fields, values, and permissions. CSV/Excel exports neutralize formula prefixes in strings while preserving actual numeric values. Excel output remains an HTML-based `.xls`, not native XLSX. Theme tokens reject declaration delimiters, CSS comments, backslash escapes, and URL expressions.
|
|
131
|
+
|
|
68
132
|
```sh
|
|
69
133
|
npm i @dynostack/react-grid
|
|
70
134
|
# or
|
|
@@ -73,7 +137,7 @@ pnpm add @dynostack/react-grid
|
|
|
73
137
|
yarn add @dynostack/react-grid
|
|
74
138
|
```
|
|
75
139
|
|
|
76
|
-
**Peer deps:** `react >= 18`, `react-dom >= 18`. All other
|
|
140
|
+
**Peer deps:** `react >= 18.2`, `react-dom >= 18.2`. All other runtime dependencies (`@tanstack/react-table`, `radix-ui`, `lucide-react`, `class-variance-authority`, `clsx`, `tailwind-merge`) are installed automatically and remain external to the package bundle.
|
|
77
141
|
|
|
78
142
|
## Tailwind setup
|
|
79
143
|
|
|
@@ -613,7 +677,7 @@ The set filter automatically derives unique values from the visible rows when `s
|
|
|
613
677
|
Selection is on by default (`enableSelection: true`). When any row is selected the toolbar swaps in:
|
|
614
678
|
|
|
615
679
|
- A `<count> selected` badge
|
|
616
|
-
- `Delete` button →
|
|
680
|
+
- `Delete` button → opens the confirmation dialog first, then calls `onBulkDelete?(rows)` after confirm
|
|
617
681
|
- `Clear` button → resets selection
|
|
618
682
|
|
|
619
683
|
```tsx
|
|
@@ -660,7 +724,15 @@ Mark a column non-exportable via `meta.exportable: false`.
|
|
|
660
724
|
|
|
661
725
|
## View sheet
|
|
662
726
|
|
|
663
|
-
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
|
|
727
|
+
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:
|
|
728
|
+
|
|
729
|
+
| Option | Layout | Intended use |
|
|
730
|
+
| --- | --- | --- |
|
|
731
|
+
| `Compact` | 3 columns, tighter cards | Scan more fields at once. |
|
|
732
|
+
| `Relaxed` | 2 columns, medium spacing | Balanced default for mixed values. |
|
|
733
|
+
| `Comfy` | 1 column, roomier cards | Read long values without cramped wrapping. |
|
|
734
|
+
|
|
735
|
+
The sheet is responsive and wider on desktop so the multi-column modes have enough room for real row data.
|
|
664
736
|
|
|
665
737
|
Works out of the box with no props. Customize via `viewSheet`:
|
|
666
738
|
|
|
@@ -668,7 +740,7 @@ Works out of the box with no props. Customize via `viewSheet`:
|
|
|
668
740
|
<DataTable
|
|
669
741
|
viewSheet={{
|
|
670
742
|
side: "right", // or "left"
|
|
671
|
-
defaultDensity: "relaxed", // initial
|
|
743
|
+
defaultDensity: "relaxed", // initial layout density
|
|
672
744
|
hideDensityTabs: true, // hide the layout picker
|
|
673
745
|
fields: ["name", "email", "role"], // limit / reorder shown columns
|
|
674
746
|
renderField: ({ column, value, row }) => // override how a value renders
|
|
@@ -678,7 +750,7 @@ Works out of the box with no props. Customize via `viewSheet`:
|
|
|
678
750
|
title: (row) => `${row.name} (${row.role})`,
|
|
679
751
|
description: (row) => `Joined ${row.joinedAt}`,
|
|
680
752
|
emptyValue: "—",
|
|
681
|
-
density: { compact: "
|
|
753
|
+
density: { compact: "3 cols", relaxed: "2 cols", comfy: "1 col" },
|
|
682
754
|
},
|
|
683
755
|
}}
|
|
684
756
|
onView={(row) => track("user.view", row)} // optional side-effect
|
|
@@ -699,6 +771,8 @@ Disable the built-in sheet entirely:
|
|
|
699
771
|
|
|
700
772
|
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.
|
|
701
773
|
|
|
774
|
+
The dialog is mounted inside the DataTable portal container, so its blur / dim overlay covers only that table instance. It does not block or blur the rest of the page.
|
|
775
|
+
|
|
702
776
|
```tsx
|
|
703
777
|
<DataTable
|
|
704
778
|
onDelete={(row) => api.deleteUser(row.id)}
|
|
@@ -716,6 +790,8 @@ Both the row-action "Delete" and the toolbar "Bulk delete" open a confirmation `
|
|
|
716
790
|
/>
|
|
717
791
|
```
|
|
718
792
|
|
|
793
|
+
Use `onDelete` for the built-in row delete action. Do not put the actual delete mutation in `onRowAction("delete")`, because the built-in delete action is handled by the confirmation flow.
|
|
794
|
+
|
|
719
795
|
Skip the dialog (fire immediately):
|
|
720
796
|
|
|
721
797
|
```tsx
|
|
@@ -740,10 +816,10 @@ Skip the dialog (fire immediately):
|
|
|
740
816
|
{ id: "archive", label: "Archive", icon: <ArchiveIcon /> },
|
|
741
817
|
]}
|
|
742
818
|
onRowAction={(action, row) => {
|
|
743
|
-
if (action === "delete") deleteMutation.mutate([row.id])
|
|
744
819
|
if (action === "suspend") saveMutation.mutate({ ...row, status: "suspended" })
|
|
745
820
|
// ...
|
|
746
821
|
}}
|
|
822
|
+
onDelete={(row) => deleteMutation.mutate([row.id])}
|
|
747
823
|
/>
|
|
748
824
|
```
|
|
749
825
|
|
|
@@ -836,6 +912,12 @@ operations and only renders the returned page.
|
|
|
836
912
|
| `pageSizeOptions` | `number[]` | shadcn defaults | Page-size dropdown options. |
|
|
837
913
|
| `initialColumnPinning` | `ColumnPinningState` | `{ left: [], right: [] }` | Initial pinned columns. |
|
|
838
914
|
| `initialColumnVisibility` | `VisibilityState` | `{}` | Initial hidden columns. |
|
|
915
|
+
| `initialSorting` | `SortingState` | `[]` | Initial column sort order. |
|
|
916
|
+
| `onSelectionChange` | `(rows: TData[]) => void` | — | Observe selected loaded rows. |
|
|
917
|
+
| `ariaLabel` | `string` | `"Data table"` | Accessible name for the table. |
|
|
918
|
+
| `striped` | `boolean` | `false` | Alternate row backgrounds. |
|
|
919
|
+
| `stickyHeader` | `boolean` | `false` | Keep headers visible in the scroll viewport. |
|
|
920
|
+
| `maxHeight` | `CSSProperties["maxHeight"]` | — | Limit the vertical scroll viewport. |
|
|
839
921
|
| `globalFilter` | `string` | uncontrolled | Controlled global filter value. |
|
|
840
922
|
| `onGlobalFilterChange` | `(value: string) => void` | — | Controlled global filter setter. |
|
|
841
923
|
| `className` | `string` | — | Extra classes on the table root. |
|
package/dist/index.cjs
CHANGED
|
@@ -283,15 +283,21 @@ function PopoverContent({
|
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
// src/export.ts
|
|
286
|
+
function sanitizeSpreadsheetValue(value) {
|
|
287
|
+
if (value == null) return "";
|
|
288
|
+
const text = String(value);
|
|
289
|
+
if (typeof value === "string" && (/^[\s\u0000-\u001f]*[=+\-@=+-@]/u.test(text) || /^[\t\r\n]/.test(text))) return `'${text}`;
|
|
290
|
+
return text;
|
|
291
|
+
}
|
|
286
292
|
function escapeCsv(value) {
|
|
287
293
|
if (value === null || value === void 0) return "";
|
|
288
|
-
const s =
|
|
294
|
+
const s = sanitizeSpreadsheetValue(value);
|
|
289
295
|
if (/[",\n\r]/.test(s)) return `"${s.replace(/"/g, '""')}"`;
|
|
290
296
|
return s;
|
|
291
297
|
}
|
|
292
298
|
function escapeHtml(value) {
|
|
293
299
|
if (value === null || value === void 0) return "";
|
|
294
|
-
return
|
|
300
|
+
return sanitizeSpreadsheetValue(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
295
301
|
}
|
|
296
302
|
function getExportRows(table) {
|
|
297
303
|
const visible = table.getVisibleLeafColumns().filter((c) => {
|
|
@@ -1901,7 +1907,7 @@ function EditableCell({
|
|
|
1901
1907
|
if (typeof flag === "function") return flag(row.original);
|
|
1902
1908
|
return flag;
|
|
1903
1909
|
})();
|
|
1904
|
-
if (!isEditing) {
|
|
1910
|
+
if (!isEditing || !isEditable) {
|
|
1905
1911
|
const display = renderDisplay(initial, editor, meta);
|
|
1906
1912
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1907
1913
|
"div",
|
|
@@ -1918,7 +1924,9 @@ function EditableCell({
|
|
|
1918
1924
|
}
|
|
1919
1925
|
);
|
|
1920
1926
|
}
|
|
1921
|
-
const commit = (value) =>
|
|
1927
|
+
const commit = (value) => {
|
|
1928
|
+
if (isEditable) onCommit(value);
|
|
1929
|
+
};
|
|
1922
1930
|
const handleKey = (e) => {
|
|
1923
1931
|
if (e.key === "Enter") commit(local);
|
|
1924
1932
|
else if (e.key === "Escape") onCancel();
|
|
@@ -2048,6 +2056,9 @@ var TOKEN_TO_VAR = {
|
|
|
2048
2056
|
radius: "--radius",
|
|
2049
2057
|
fontFamily: "--font-family"
|
|
2050
2058
|
};
|
|
2059
|
+
function isSafeToken(value) {
|
|
2060
|
+
return typeof value === "string" && !/[;{}<>@\\\u0000-\u001f]/.test(value) && !/\/\*|\*\/|url\s*\(|expression\s*\(/i.test(value);
|
|
2061
|
+
}
|
|
2051
2062
|
function isModed(theme) {
|
|
2052
2063
|
if (!theme) return false;
|
|
2053
2064
|
return "light" in theme || "dark" in theme;
|
|
@@ -2056,8 +2067,8 @@ function tokensToStyle(tokens) {
|
|
|
2056
2067
|
if (!tokens) return {};
|
|
2057
2068
|
const out = {};
|
|
2058
2069
|
for (const [k, v] of Object.entries(tokens)) {
|
|
2059
|
-
if (v
|
|
2060
|
-
const cssVar = TOKEN_TO_VAR[k];
|
|
2070
|
+
if (!isSafeToken(v)) continue;
|
|
2071
|
+
const cssVar = Object.prototype.hasOwnProperty.call(TOKEN_TO_VAR, k) ? TOKEN_TO_VAR[k] : void 0;
|
|
2061
2072
|
if (cssVar) out[cssVar] = v;
|
|
2062
2073
|
}
|
|
2063
2074
|
return out;
|
|
@@ -2131,8 +2142,8 @@ function tokensToCssBlock(tokens) {
|
|
|
2131
2142
|
if (!tokens) return "";
|
|
2132
2143
|
const parts = [];
|
|
2133
2144
|
for (const [k, v] of Object.entries(tokens)) {
|
|
2134
|
-
if (v
|
|
2135
|
-
const cssVar = TOKEN_TO_VAR[k];
|
|
2145
|
+
if (!isSafeToken(v)) continue;
|
|
2146
|
+
const cssVar = Object.prototype.hasOwnProperty.call(TOKEN_TO_VAR, k) ? TOKEN_TO_VAR[k] : void 0;
|
|
2136
2147
|
if (cssVar) parts.push(`${cssVar}:${v}`);
|
|
2137
2148
|
}
|
|
2138
2149
|
return parts.join(";");
|
|
@@ -2216,8 +2227,8 @@ function SheetOverlay({
|
|
|
2216
2227
|
);
|
|
2217
2228
|
}
|
|
2218
2229
|
var SIDE_CLASS = {
|
|
2219
|
-
right: "right-0 top-0 h-full w-
|
|
2220
|
-
left: "left-0 top-0 h-full w-
|
|
2230
|
+
right: "right-0 top-0 h-full w-full border-l sm:w-[min(48rem,calc(100vw-2rem))] data-closed:slide-out-to-right data-open:slide-in-from-right",
|
|
2231
|
+
left: "left-0 top-0 h-full w-full border-r sm:w-[min(48rem,calc(100vw-2rem))] data-closed:slide-out-to-left data-open:slide-in-from-left",
|
|
2221
2232
|
top: "left-0 top-0 w-full max-h-[80vh] border-b data-closed:slide-out-to-top data-open:slide-in-from-top",
|
|
2222
2233
|
bottom: "left-0 bottom-0 w-full max-h-[80vh] border-t data-closed:slide-out-to-bottom data-open:slide-in-from-bottom"
|
|
2223
2234
|
};
|
|
@@ -2292,14 +2303,19 @@ function SheetDescription({
|
|
|
2292
2303
|
);
|
|
2293
2304
|
}
|
|
2294
2305
|
var COLS_CLASS = {
|
|
2295
|
-
compact: "grid-cols-1",
|
|
2306
|
+
compact: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
|
|
2296
2307
|
relaxed: "grid-cols-1 sm:grid-cols-2",
|
|
2297
|
-
comfy: "grid-cols-1
|
|
2308
|
+
comfy: "grid-cols-1"
|
|
2309
|
+
};
|
|
2310
|
+
var CARD_CLASS = {
|
|
2311
|
+
compact: "gap-0.5 p-2.5",
|
|
2312
|
+
relaxed: "gap-1 p-3",
|
|
2313
|
+
comfy: "gap-1.5 p-4"
|
|
2298
2314
|
};
|
|
2299
2315
|
var DENSITY_ICON = {
|
|
2300
|
-
compact: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.
|
|
2316
|
+
compact: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.LayoutGridIcon, { className: "size-3.5" }),
|
|
2301
2317
|
relaxed: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Rows3Icon, { className: "size-3.5" }),
|
|
2302
|
-
comfy: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.
|
|
2318
|
+
comfy: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ListIcon, { className: "size-3.5" })
|
|
2303
2319
|
};
|
|
2304
2320
|
function DataTableViewSheet({
|
|
2305
2321
|
table,
|
|
@@ -2372,10 +2388,13 @@ function DataTableViewSheet({
|
|
|
2372
2388
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2373
2389
|
"div",
|
|
2374
2390
|
{
|
|
2375
|
-
className:
|
|
2391
|
+
className: cn(
|
|
2392
|
+
"flex min-w-0 flex-col rounded-md border bg-card",
|
|
2393
|
+
CARD_CLASS[density]
|
|
2394
|
+
),
|
|
2376
2395
|
children: [
|
|
2377
2396
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] uppercase tracking-wide text-muted-foreground", children: headerLabel }),
|
|
2378
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-foreground
|
|
2397
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "break-words text-sm text-foreground", children: rendered })
|
|
2379
2398
|
]
|
|
2380
2399
|
},
|
|
2381
2400
|
colId
|
|
@@ -2400,11 +2419,15 @@ function AlertDialogContent({
|
|
|
2400
2419
|
...props
|
|
2401
2420
|
}) {
|
|
2402
2421
|
const container = usePortalContainer();
|
|
2422
|
+
const positionClass = container ? "absolute" : "fixed";
|
|
2403
2423
|
return /* @__PURE__ */ jsxRuntime.jsxs(radixUi.AlertDialog.Portal, { container: container ?? void 0, children: [
|
|
2404
2424
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2405
2425
|
radixUi.AlertDialog.Overlay,
|
|
2406
2426
|
{
|
|
2407
|
-
className:
|
|
2427
|
+
className: cn(
|
|
2428
|
+
"inset-0 z-50 bg-foreground/30 backdrop-blur-sm data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
|
|
2429
|
+
positionClass
|
|
2430
|
+
)
|
|
2408
2431
|
}
|
|
2409
2432
|
),
|
|
2410
2433
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2412,7 +2435,8 @@ function AlertDialogContent({
|
|
|
2412
2435
|
{
|
|
2413
2436
|
"data-slot": "alert-dialog-content",
|
|
2414
2437
|
className: cn(
|
|
2415
|
-
"
|
|
2438
|
+
"top-1/2 left-1/2 z-50 grid w-[calc(100%-2rem)] max-w-md -translate-x-1/2 -translate-y-1/2 gap-4 rounded-lg border bg-background p-6 shadow-lg outline-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
2439
|
+
positionClass,
|
|
2416
2440
|
className
|
|
2417
2441
|
),
|
|
2418
2442
|
...props,
|
|
@@ -2543,6 +2567,10 @@ function useEditingContext() {
|
|
|
2543
2567
|
if (!ctx) throw new Error("DataTable editing context missing");
|
|
2544
2568
|
return ctx;
|
|
2545
2569
|
}
|
|
2570
|
+
function DefaultCellHost(ctx) {
|
|
2571
|
+
const value = ctx.getValue();
|
|
2572
|
+
return value == null ? null : String(value);
|
|
2573
|
+
}
|
|
2546
2574
|
function EditableCellHost(ctx) {
|
|
2547
2575
|
const {
|
|
2548
2576
|
editingCell,
|
|
@@ -2631,6 +2659,7 @@ function ActionsCellHost({ row }) {
|
|
|
2631
2659
|
onView?.(row.original);
|
|
2632
2660
|
} else if (a === "delete") {
|
|
2633
2661
|
onRequestDelete?.(row.original);
|
|
2662
|
+
return;
|
|
2634
2663
|
}
|
|
2635
2664
|
onRowAction?.(a, row.original);
|
|
2636
2665
|
}
|
|
@@ -2663,6 +2692,7 @@ var DENSITY_CLASS = {
|
|
|
2663
2692
|
default: "",
|
|
2664
2693
|
comfortable: "[&_tbody_td]:py-2"
|
|
2665
2694
|
};
|
|
2695
|
+
var EMPTY_DATA = [];
|
|
2666
2696
|
function DataTable({
|
|
2667
2697
|
data,
|
|
2668
2698
|
columns,
|
|
@@ -2686,6 +2716,12 @@ function DataTable({
|
|
|
2686
2716
|
pageSizeOptions,
|
|
2687
2717
|
initialColumnPinning,
|
|
2688
2718
|
initialColumnVisibility,
|
|
2719
|
+
initialSorting,
|
|
2720
|
+
onSelectionChange,
|
|
2721
|
+
ariaLabel = "Data table",
|
|
2722
|
+
striped = false,
|
|
2723
|
+
stickyHeader = false,
|
|
2724
|
+
maxHeight,
|
|
2689
2725
|
globalFilter: globalFilterProp,
|
|
2690
2726
|
onGlobalFilterChange,
|
|
2691
2727
|
className,
|
|
@@ -2762,7 +2798,7 @@ function DataTable({
|
|
|
2762
2798
|
}
|
|
2763
2799
|
setConfirmCtx(null);
|
|
2764
2800
|
};
|
|
2765
|
-
const [sorting, setSorting] = react.useState([]);
|
|
2801
|
+
const [sorting, setSorting] = react.useState(initialSorting ?? []);
|
|
2766
2802
|
const [columnFilters, setColumnFilters] = react.useState([]);
|
|
2767
2803
|
const [internalGlobalFilter, setInternalGlobalFilter] = react.useState("");
|
|
2768
2804
|
const isControlledFilter = globalFilterProp !== void 0;
|
|
@@ -2858,7 +2894,7 @@ function DataTable({
|
|
|
2858
2894
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2859
2895
|
...dataSourceDeps
|
|
2860
2896
|
]);
|
|
2861
|
-
const baseData = dataSourceEnabled ? sourceRows : data ??
|
|
2897
|
+
const baseData = dataSourceEnabled ? sourceRows : data ?? EMPTY_DATA;
|
|
2862
2898
|
const effectiveIsLoading = dataSourceEnabled ? sourceLoading : isLoading;
|
|
2863
2899
|
const effectiveIsFetching = dataSourceEnabled ? sourceFetching : isFetching;
|
|
2864
2900
|
const effectiveOnRefresh = dataSourceEnabled ? () => setSourceRefreshKey((key) => key + 1) : onRefresh;
|
|
@@ -2902,7 +2938,7 @@ function DataTable({
|
|
|
2902
2938
|
title: headerLabel
|
|
2903
2939
|
}
|
|
2904
2940
|
),
|
|
2905
|
-
cell: hasEditor ? EditableCellHost : original
|
|
2941
|
+
cell: hasEditor ? EditableCellHost : original ?? DefaultCellHost
|
|
2906
2942
|
};
|
|
2907
2943
|
};
|
|
2908
2944
|
const built = columns.map(wrap);
|
|
@@ -3014,10 +3050,10 @@ function DataTable({
|
|
|
3014
3050
|
enableColumnFilters: f.filtering,
|
|
3015
3051
|
enableColumnResizing: f.resizing,
|
|
3016
3052
|
columnResizeMode: "onChange",
|
|
3017
|
-
enableRowSelection:
|
|
3053
|
+
enableRowSelection: enableSelection,
|
|
3018
3054
|
enableExpanding: !!(renderSubRow || getSubRows),
|
|
3019
3055
|
enablePinning: f.pinning,
|
|
3020
|
-
manualPagination: isServerSideDataSource,
|
|
3056
|
+
manualPagination: isServerSideDataSource || !f.pagination,
|
|
3021
3057
|
manualSorting: isServerSideDataSource,
|
|
3022
3058
|
manualFiltering: isServerSideDataSource,
|
|
3023
3059
|
pageCount: isServerSideDataSource ? Math.max(1, Math.ceil(effectiveTotalRecords / pagination.pageSize)) : void 0,
|
|
@@ -3107,6 +3143,11 @@ function DataTable({
|
|
|
3107
3143
|
setEditingRowId(tempId);
|
|
3108
3144
|
} : void 0;
|
|
3109
3145
|
const rows = table.getRowModel().rows;
|
|
3146
|
+
const selectionCallback = react.useRef(onSelectionChange);
|
|
3147
|
+
selectionCallback.current = onSelectionChange;
|
|
3148
|
+
react.useEffect(() => {
|
|
3149
|
+
selectionCallback.current?.(table.getSelectedRowModel().flatRows.map((row) => row.original));
|
|
3150
|
+
}, [rowSelection, mergedData, table]);
|
|
3110
3151
|
const visibleLeafCount = table.getVisibleLeafColumns().length;
|
|
3111
3152
|
const editingContextValue = react.useMemo(
|
|
3112
3153
|
() => ({
|
|
@@ -3184,17 +3225,18 @@ function DataTable({
|
|
|
3184
3225
|
labels
|
|
3185
3226
|
}
|
|
3186
3227
|
),
|
|
3187
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full overflow-auto", children: [
|
|
3228
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full overflow-auto", style: { maxHeight }, children: [
|
|
3188
3229
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3189
3230
|
"table",
|
|
3190
3231
|
{
|
|
3191
|
-
|
|
3232
|
+
"aria-label": ariaLabel,
|
|
3233
|
+
className: cn("w-full caption-bottom text-sm", striped && "[&_tbody>tr:nth-child(even)]:bg-muted/25"),
|
|
3192
3234
|
style: {
|
|
3193
3235
|
width: table.getCenterTotalSize() ? void 0 : "100%",
|
|
3194
3236
|
minWidth: "100%"
|
|
3195
3237
|
},
|
|
3196
3238
|
children: [
|
|
3197
|
-
/* @__PURE__ */ jsxRuntime.jsx("thead", { className: "sticky top-0 z-10
|
|
3239
|
+
/* @__PURE__ */ jsxRuntime.jsx("thead", { className: cn("bg-card", stickyHeader && "sticky top-0 z-10"), children: table.getHeaderGroups().map((hg) => /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "border-b", children: hg.headers.map((header) => {
|
|
3198
3240
|
const meta = header.column.columnDef.meta;
|
|
3199
3241
|
const isUtility = header.column.id === "__select" || header.column.id === "__expand" || header.column.id === "__actions";
|
|
3200
3242
|
const isPinned = !!header.column.getIsPinned();
|
|
@@ -3387,6 +3429,7 @@ exports.exportToCsv = exportToCsv;
|
|
|
3387
3429
|
exports.exportToExcel = exportToExcel;
|
|
3388
3430
|
exports.isFilterActive = isFilterActive;
|
|
3389
3431
|
exports.numberFilterFn = numberFilterFn;
|
|
3432
|
+
exports.sanitizeSpreadsheetValue = sanitizeSpreadsheetValue;
|
|
3390
3433
|
exports.setFilterFn = setFilterFn;
|
|
3391
3434
|
exports.splitTheme = splitTheme;
|
|
3392
3435
|
exports.textFilterFn = textFilterFn;
|