@hexclave/dashboard-ui-components 1.0.30 → 1.0.32

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.
Files changed (32) hide show
  1. package/dist/components/button.d.ts +2 -2
  2. package/dist/components/chart-legend.d.ts +1 -1
  3. package/dist/components/data-grid/data-grid-export-dialog.d.ts +23 -0
  4. package/dist/components/data-grid/data-grid-export-dialog.d.ts.map +1 -0
  5. package/dist/components/data-grid/data-grid-export-dialog.js +406 -0
  6. package/dist/components/data-grid/data-grid-export-dialog.js.map +1 -0
  7. package/dist/components/data-grid/data-grid.d.ts.map +1 -1
  8. package/dist/components/data-grid/data-grid.js +14 -14
  9. package/dist/components/data-grid/data-grid.js.map +1 -1
  10. package/dist/components/data-grid/index.d.ts +2 -2
  11. package/dist/components/data-grid/types.d.ts +31 -2
  12. package/dist/components/data-grid/types.d.ts.map +1 -1
  13. package/dist/dashboard-ui-components.global.js +904 -535
  14. package/dist/dashboard-ui-components.global.js.map +4 -4
  15. package/dist/esm/components/button.d.ts +2 -2
  16. package/dist/esm/components/chart-legend.d.ts +1 -1
  17. package/dist/esm/components/data-grid/data-grid-export-dialog.d.ts +23 -0
  18. package/dist/esm/components/data-grid/data-grid-export-dialog.d.ts.map +1 -0
  19. package/dist/esm/components/data-grid/data-grid-export-dialog.js +403 -0
  20. package/dist/esm/components/data-grid/data-grid-export-dialog.js.map +1 -0
  21. package/dist/esm/components/data-grid/data-grid.d.ts.map +1 -1
  22. package/dist/esm/components/data-grid/data-grid.js +16 -16
  23. package/dist/esm/components/data-grid/data-grid.js.map +1 -1
  24. package/dist/esm/components/data-grid/index.d.ts +2 -2
  25. package/dist/esm/components/data-grid/types.d.ts +31 -2
  26. package/dist/esm/components/data-grid/types.d.ts.map +1 -1
  27. package/dist/index.d.ts +2 -2
  28. package/package.json +3 -3
  29. package/src/components/data-grid/data-grid-export-dialog.tsx +479 -0
  30. package/src/components/data-grid/data-grid.tsx +192 -193
  31. package/src/components/data-grid/index.ts +5 -0
  32. package/src/components/data-grid/types.ts +35 -0
@@ -4,7 +4,7 @@ import * as class_variance_authority_types0 from "class-variance-authority/types
4
4
 
5
5
  //#region src/components/button.d.ts
6
6
  declare const designButtonVariants: (props?: ({
7
- variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | "plain" | null | undefined;
7
+ variant?: "link" | "plain" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
8
8
  size?: "default" | "sm" | "lg" | "icon" | null | undefined;
9
9
  } & class_variance_authority_types0.ClassProp) | undefined) => string;
10
10
  type DesignOriginalButtonProps = {
@@ -36,7 +36,7 @@ declare const DesignButton: React.FC<{
36
36
  } & {
37
37
  asChild?: boolean;
38
38
  } & React.ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<(props?: ({
39
- variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | "plain" | null | undefined;
39
+ variant?: "link" | "plain" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
40
40
  size?: "default" | "sm" | "lg" | "icon" | null | undefined;
41
41
  } & class_variance_authority_types0.ClassProp) | undefined) => string> & {
42
42
  ref?: React.Ref<HTMLButtonElement> | undefined;
@@ -2,7 +2,7 @@ import * as React$1 from "react";
2
2
  import * as RechartsPrimitive from "recharts";
3
3
 
4
4
  //#region src/components/chart-legend.d.ts
5
- declare const DesignChartLegendContent: React$1.ForwardRefExoticComponent<Omit<React$1.ClassAttributes<HTMLDivElement> & React$1.HTMLAttributes<HTMLDivElement> & Pick<RechartsPrimitive.LegendProps, "verticalAlign" | "payload"> & {
5
+ declare const DesignChartLegendContent: React$1.ForwardRefExoticComponent<Omit<React$1.ClassAttributes<HTMLDivElement> & React$1.HTMLAttributes<HTMLDivElement> & Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
6
6
  hideIcon?: boolean;
7
7
  nameKey?: string;
8
8
  }, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,23 @@
1
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
2
+ import { DataGridColumnDef, DataGridExportOptions } from "./types";
3
+
4
+ //#region src/components/data-grid/data-grid-export-dialog.d.ts
5
+ type DataGridExportDialogProps<TRow> = {
6
+ open: boolean;
7
+ onOpenChange: (open: boolean) => void;
8
+ rows: readonly TRow[];
9
+ columns: readonly DataGridColumnDef<TRow>[];
10
+ exportFilename: string;
11
+ exportOptions?: DataGridExportOptions<TRow>;
12
+ };
13
+ declare function DataGridExportDialog<TRow>({
14
+ open,
15
+ onOpenChange,
16
+ rows,
17
+ columns,
18
+ exportFilename,
19
+ exportOptions
20
+ }: DataGridExportDialogProps<TRow>): react_jsx_runtime0.JSX.Element;
21
+ //#endregion
22
+ export { DataGridExportDialog };
23
+ //# sourceMappingURL=data-grid-export-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-grid-export-dialog.d.ts","names":[],"sources":["../../../../src/components/data-grid/data-grid-export-dialog.tsx"],"mappings":";;;;KA4BK,yBAAA;EACH,IAAA;EACA,YAAA,GAAe,IAAA;EACf,IAAA,WAAe,IAAA;EACf,OAAA,WAAkB,iBAAA,CAAkB,IAAA;EACpC,cAAA;EACA,aAAA,GAAgB,qBAAA,CAAsB,IAAA;AAAA;AAAA,iBASxB,oBAAA,MAAA,CAAA;EACd,IAAA;EACA,YAAA;EACA,IAAA;EACA,OAAA;EACA,cAAA;EACA;AAAA,GACC,yBAAA,CAA0B,IAAA,IAAK,kBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -0,0 +1,403 @@
1
+ "use client";
2
+
3
+ import { DownloadSimpleIcon } from "@phosphor-icons/react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import React, { useCallback, useEffect, useMemo, useState } from "react";
6
+ import { DesignButton } from "../button.js";
7
+ import { formatGridDate, resolveColumnValue } from "./state.js";
8
+ import { DesignDialog } from "../dialog.js";
9
+
10
+ //#region src/components/data-grid/data-grid-export-dialog.tsx
11
+ const idleExportProgress = {
12
+ phase: "idle",
13
+ fetched: 0
14
+ };
15
+ const exportCompletionDisplayMs = 800;
16
+ function DataGridExportDialog({ open, onOpenChange, rows, columns, exportFilename, exportOptions }) {
17
+ const hasServerExport = exportOptions?.fetchRows != null;
18
+ const resolvedFields = useMemo(() => exportOptions?.fields ?? buildColumnExportFields(columns), [exportOptions?.fields, columns]);
19
+ const [format, setFormat] = useState("csv");
20
+ const [scope, setScope] = useState("all");
21
+ const [fields, setFields] = useState(resolvedFields);
22
+ const [isExporting, setIsExporting] = useState(false);
23
+ const [progress, setProgress] = useState(idleExportProgress);
24
+ const [errorMessage, setErrorMessage] = useState(null);
25
+ useEffect(() => {
26
+ if (!isExporting) setFields(resolvedFields);
27
+ }, [isExporting, resolvedFields]);
28
+ exportOptions?.entityName;
29
+ const entityNamePlural = exportOptions?.entityNamePlural ?? "rows";
30
+ const filenamePrefix = exportOptions?.filenamePrefix ?? exportFilename;
31
+ const title = exportOptions?.title ?? "Export data";
32
+ const description = exportOptions?.description ?? (hasServerExport ? "Configure and download data from this table" : "Configure and download the rows currently loaded in this table");
33
+ const allScopeLabel = exportOptions?.allScopeLabel ?? `Export all ${entityNamePlural} in the project`;
34
+ const filteredScopeLabel = exportOptions?.filteredScopeLabel ?? `Export only filtered/searched ${entityNamePlural}`;
35
+ const progressSubjectLabel = exportOptions?.progressSubjectLabel ?? entityNamePlural;
36
+ const progressTitle = progress.phase === "complete" ? "Export complete" : `Exporting ${progressSubjectLabel}`;
37
+ const fetchExportRows = exportOptions?.fetchRows;
38
+ const closeDialog = useCallback(() => {
39
+ onOpenChange(false);
40
+ setErrorMessage(null);
41
+ }, [onOpenChange]);
42
+ const handleOpenChange = useCallback((nextOpen) => {
43
+ if (isExporting && !nextOpen) return;
44
+ if (nextOpen) onOpenChange(true);
45
+ else closeDialog();
46
+ }, [
47
+ closeDialog,
48
+ isExporting,
49
+ onOpenChange
50
+ ]);
51
+ const toggleField = useCallback((key) => {
52
+ setFields((prev) => prev.map((field) => field.key === key ? {
53
+ ...field,
54
+ enabled: !field.enabled
55
+ } : field));
56
+ }, []);
57
+ const selectAllFields = useCallback(() => {
58
+ setFields((prev) => prev.map((field) => ({
59
+ ...field,
60
+ enabled: true
61
+ })));
62
+ }, []);
63
+ const deselectAllFields = useCallback(() => {
64
+ setFields((prev) => prev.map((field) => ({
65
+ ...field,
66
+ enabled: false
67
+ })));
68
+ }, []);
69
+ const fetchRows = useCallback(async () => {
70
+ if (fetchExportRows != null) return await fetchExportRows({
71
+ scope,
72
+ onProgress: (fetched) => setProgress({
73
+ phase: "fetching",
74
+ fetched
75
+ })
76
+ });
77
+ setProgress({
78
+ phase: "fetching",
79
+ fetched: rows.length
80
+ });
81
+ return rows;
82
+ }, [
83
+ fetchExportRows,
84
+ rows,
85
+ scope
86
+ ]);
87
+ const handleExport = async () => {
88
+ const enabledFields = fields.filter((field) => field.enabled);
89
+ if (enabledFields.length === 0) {
90
+ setErrorMessage("Select at least one field to export.");
91
+ return;
92
+ }
93
+ setErrorMessage(null);
94
+ setIsExporting(true);
95
+ setProgress({
96
+ phase: "fetching",
97
+ fetched: 0
98
+ });
99
+ try {
100
+ const exportRows = await fetchRows();
101
+ if (exportRows.length === 0) {
102
+ setErrorMessage(exportOptions?.emptyExportDescription ?? `There are no ${entityNamePlural} to export.`);
103
+ setIsExporting(false);
104
+ setProgress(idleExportProgress);
105
+ return;
106
+ }
107
+ setProgress({
108
+ phase: "generating",
109
+ fetched: exportRows.length
110
+ });
111
+ const transformedData = buildExportTable(exportRows, enabledFields);
112
+ if (format === "csv") exportToCsv(transformedData, filenamePrefix);
113
+ else exportToJson(transformedData, filenamePrefix);
114
+ setProgress({
115
+ phase: "complete",
116
+ fetched: exportRows.length
117
+ });
118
+ await new Promise((resolve) => setTimeout(resolve, exportCompletionDisplayMs));
119
+ closeDialog();
120
+ } catch {
121
+ setErrorMessage("Something went wrong while exporting. Please try again.");
122
+ } finally {
123
+ setIsExporting(false);
124
+ setProgress(idleExportProgress);
125
+ }
126
+ };
127
+ return /* @__PURE__ */ jsx(DesignDialog, {
128
+ open,
129
+ onOpenChange: handleOpenChange,
130
+ title: isExporting ? progressTitle : title,
131
+ description: isExporting ? `Preparing export for ${progressSubjectLabel}.` : description,
132
+ size: "2xl",
133
+ variant: "plain",
134
+ headerClassName: isExporting ? "sr-only" : void 0,
135
+ hideTopCloseButton: isExporting,
136
+ children: isExporting ? /* @__PURE__ */ jsx(ExportProgressContent, {
137
+ progress,
138
+ format,
139
+ subjectLabel: progressSubjectLabel
140
+ }) : /* @__PURE__ */ jsxs("div", {
141
+ className: "space-y-6",
142
+ children: [
143
+ /* @__PURE__ */ jsxs("div", {
144
+ className: "space-y-2",
145
+ children: [/* @__PURE__ */ jsx("label", {
146
+ className: "text-sm font-medium",
147
+ htmlFor: `${filenamePrefix}-export-format`,
148
+ children: "Export Format"
149
+ }), /* @__PURE__ */ jsxs("select", {
150
+ id: `${filenamePrefix}-export-format`,
151
+ value: format,
152
+ onChange: (event) => setFormat(event.currentTarget.value === "json" ? "json" : "csv"),
153
+ className: "h-9 w-full rounded-md border border-input bg-white px-3 text-sm dark:bg-background",
154
+ children: [/* @__PURE__ */ jsx("option", {
155
+ value: "csv",
156
+ children: "CSV (Comma-separated values)"
157
+ }), /* @__PURE__ */ jsx("option", {
158
+ value: "json",
159
+ children: "JSON (JavaScript Object Notation)"
160
+ })]
161
+ })]
162
+ }),
163
+ hasServerExport ? /* @__PURE__ */ jsxs("fieldset", {
164
+ className: "space-y-2",
165
+ children: [
166
+ /* @__PURE__ */ jsx("legend", {
167
+ className: "text-sm font-medium",
168
+ children: "Export Scope"
169
+ }),
170
+ /* @__PURE__ */ jsxs("label", {
171
+ className: "flex cursor-pointer items-center gap-2 text-sm",
172
+ children: [/* @__PURE__ */ jsx("input", {
173
+ type: "radio",
174
+ name: `${filenamePrefix}-export-scope`,
175
+ value: "all",
176
+ checked: scope === "all",
177
+ onChange: () => setScope("all")
178
+ }), /* @__PURE__ */ jsx("span", { children: allScopeLabel })]
179
+ }),
180
+ /* @__PURE__ */ jsxs("label", {
181
+ className: "flex cursor-pointer items-center gap-2 text-sm",
182
+ children: [/* @__PURE__ */ jsx("input", {
183
+ type: "radio",
184
+ name: `${filenamePrefix}-export-scope`,
185
+ value: "filtered",
186
+ checked: scope === "filtered",
187
+ onChange: () => setScope("filtered")
188
+ }), /* @__PURE__ */ jsx("span", { children: filteredScopeLabel })]
189
+ })
190
+ ]
191
+ }) : null,
192
+ /* @__PURE__ */ jsxs("div", {
193
+ className: "space-y-3",
194
+ children: [/* @__PURE__ */ jsxs("div", {
195
+ className: "flex items-center justify-between gap-3",
196
+ children: [/* @__PURE__ */ jsx("label", {
197
+ className: "text-sm font-medium",
198
+ children: "Fields to Export"
199
+ }), /* @__PURE__ */ jsxs("div", {
200
+ className: "flex gap-2",
201
+ children: [/* @__PURE__ */ jsx(DesignButton, {
202
+ type: "button",
203
+ variant: "ghost",
204
+ size: "sm",
205
+ onClick: selectAllFields,
206
+ className: "h-7 text-xs",
207
+ children: "Select All"
208
+ }), /* @__PURE__ */ jsx(DesignButton, {
209
+ type: "button",
210
+ variant: "ghost",
211
+ size: "sm",
212
+ onClick: deselectAllFields,
213
+ className: "h-7 text-xs",
214
+ children: "Deselect All"
215
+ })]
216
+ })]
217
+ }), /* @__PURE__ */ jsx("div", {
218
+ className: "grid max-h-[300px] grid-cols-1 gap-3 overflow-y-auto rounded-lg border border-border p-4 sm:grid-cols-2",
219
+ children: fields.map((field) => /* @__PURE__ */ jsxs("label", {
220
+ className: "flex cursor-pointer items-center gap-2 text-sm font-normal",
221
+ children: [/* @__PURE__ */ jsx("input", {
222
+ type: "checkbox",
223
+ checked: field.enabled,
224
+ onChange: () => toggleField(field.key)
225
+ }), /* @__PURE__ */ jsx("span", { children: field.label })]
226
+ }, field.key))
227
+ })]
228
+ }),
229
+ errorMessage != null ? /* @__PURE__ */ jsxs("div", {
230
+ className: "rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive",
231
+ children: [/* @__PURE__ */ jsx("div", {
232
+ className: "font-medium",
233
+ children: exportOptions?.emptyExportTitle ?? "Export unavailable"
234
+ }), /* @__PURE__ */ jsx("div", { children: errorMessage })]
235
+ }) : null,
236
+ /* @__PURE__ */ jsxs("div", {
237
+ className: "flex justify-end gap-3 pt-2",
238
+ children: [/* @__PURE__ */ jsx(DesignButton, {
239
+ variant: "outline",
240
+ onClick: closeDialog,
241
+ children: "Cancel"
242
+ }), /* @__PURE__ */ jsxs(DesignButton, {
243
+ onClick: handleExport,
244
+ children: [
245
+ /* @__PURE__ */ jsx(DownloadSimpleIcon, { className: "mr-2 h-4 w-4" }),
246
+ "Export ",
247
+ titleCase(entityNamePlural)
248
+ ]
249
+ })]
250
+ })
251
+ ]
252
+ })
253
+ });
254
+ }
255
+ function ExportProgressContent(props) {
256
+ const { progress, format, subjectLabel } = props;
257
+ const fileLabel = format.toUpperCase();
258
+ const isComplete = progress.phase === "complete";
259
+ const title = isComplete ? "Export complete" : `Exporting ${subjectLabel}`;
260
+ const description = isComplete ? `Your ${fileLabel} is ready and the download should begin automatically.` : `Your ${fileLabel} is being prepared from matching ${subjectLabel}.`;
261
+ const statusLabel = progress.phase === "complete" ? "Download ready" : progress.phase === "generating" ? `Preparing ${fileLabel}` : `Fetching ${subjectLabel}`;
262
+ const countLabel = `${progress.fetched.toLocaleString()} ${isComplete ? "exported" : "fetched"}`;
263
+ return /* @__PURE__ */ jsxs("div", {
264
+ className: "space-y-4",
265
+ children: [
266
+ /* @__PURE__ */ jsxs("div", {
267
+ className: "space-y-1",
268
+ children: [/* @__PURE__ */ jsx("h2", {
269
+ className: "text-base font-semibold leading-snug",
270
+ children: title
271
+ }), /* @__PURE__ */ jsx("p", {
272
+ className: "text-sm text-muted-foreground",
273
+ children: description
274
+ })]
275
+ }),
276
+ /* @__PURE__ */ jsxs("div", {
277
+ className: "rounded-xl border border-border bg-muted/35 p-4",
278
+ children: [/* @__PURE__ */ jsxs("div", {
279
+ className: "mb-3 flex items-center justify-between gap-4 text-sm",
280
+ children: [/* @__PURE__ */ jsx("span", {
281
+ className: "font-medium text-foreground",
282
+ children: statusLabel
283
+ }), /* @__PURE__ */ jsx("span", {
284
+ className: "shrink-0 tabular-nums text-muted-foreground",
285
+ children: countLabel
286
+ })]
287
+ }), /* @__PURE__ */ jsx("div", {
288
+ className: "relative h-2 overflow-hidden rounded-full bg-foreground/10",
289
+ children: isComplete ? /* @__PURE__ */ jsx("div", { className: "h-full w-full rounded-full bg-emerald-500/80" }) : /* @__PURE__ */ jsx("div", { className: "data-grid-export-progress-shimmer absolute inset-y-0 left-0 w-2/5 rounded-full bg-gradient-to-r from-transparent via-foreground/65 to-transparent" })
290
+ })]
291
+ }),
292
+ /* @__PURE__ */ jsx("div", {
293
+ className: "rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-900 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-200",
294
+ children: "Do not reload this page until the export finishes. The download will start automatically."
295
+ }),
296
+ /* @__PURE__ */ jsx("div", {
297
+ className: "flex justify-end gap-3 pt-2",
298
+ children: /* @__PURE__ */ jsx(DesignButton, {
299
+ variant: "outline",
300
+ disabled: true,
301
+ children: "Cancel"
302
+ })
303
+ })
304
+ ]
305
+ });
306
+ }
307
+ function buildColumnExportFields(columns) {
308
+ const fields = [];
309
+ for (const column of columns) {
310
+ const label = typeof column.header === "string" ? column.header.trim() : column.id;
311
+ if (label.length === 0) continue;
312
+ fields.push({
313
+ key: column.id,
314
+ label,
315
+ enabled: true,
316
+ getValue: (row) => formatColumnExportValue(column, row)
317
+ });
318
+ }
319
+ return fields;
320
+ }
321
+ function formatColumnExportValue(column, row) {
322
+ const value = resolveColumnValue(column, row);
323
+ if (column.formatValue != null) return column.formatValue(value, row);
324
+ if (column.type === "date" || column.type === "dateTime") return formatGridDate(value, "absolute", {
325
+ parseValue: column.parseValue,
326
+ dateFormat: column.dateFormat
327
+ }).display ?? "";
328
+ return value;
329
+ }
330
+ function buildExportTable(rows, enabledFields) {
331
+ return {
332
+ csvHeaders: enabledFields.map((field) => field.label),
333
+ jsonKeys: buildJsonKeys(enabledFields),
334
+ rows: rows.map((row) => enabledFields.map((field) => toExportCellValue(field.getValue(row))))
335
+ };
336
+ }
337
+ function buildJsonKeys(fields) {
338
+ const labelCounts = /* @__PURE__ */ new Map();
339
+ for (const field of fields) labelCounts.set(field.label, (labelCounts.get(field.label) ?? 0) + 1);
340
+ const usedKeys = /* @__PURE__ */ new Map();
341
+ const keys = [];
342
+ for (const field of fields) {
343
+ const baseKey = labelCounts.get(field.label) === 1 ? field.label : `${field.label} (${field.key})`;
344
+ let key = baseKey;
345
+ let suffix = 2;
346
+ while (usedKeys.has(key)) {
347
+ key = `${baseKey} ${suffix}`;
348
+ suffix++;
349
+ }
350
+ usedKeys.set(key, true);
351
+ keys.push(key);
352
+ }
353
+ return keys;
354
+ }
355
+ function toExportCellValue(value) {
356
+ if (value == null) return "";
357
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
358
+ if (typeof value === "bigint") return value.toString();
359
+ if (value instanceof Date) return value.toISOString();
360
+ if (typeof value === "object") return JSON.stringify(value);
361
+ return String(value);
362
+ }
363
+ function exportToCsv(data, filenamePrefix) {
364
+ downloadFile("" + [data.csvHeaders.map(escapeCsvCell).join(","), ...data.rows.map((row) => row.map(escapeCsvCell).join(","))].join("\n"), `${buildExportFilename(filenamePrefix)}.csv`, "text/csv;charset=utf-8;");
365
+ }
366
+ function escapeCsvCell(value) {
367
+ const rawText = String(value ?? "");
368
+ const text = typeof value === "string" && /^[=+\-@\t\r]/.test(rawText.trimStart()) ? `'${rawText}` : rawText;
369
+ if (text.includes(",") || text.includes("\"") || text.includes("\n") || text.includes("\r")) return `"${text.replace(/"/g, "\"\"")}"`;
370
+ return text;
371
+ }
372
+ function exportToJson(data, filenamePrefix) {
373
+ const rows = data.rows.map((row) => {
374
+ const jsonRow = {};
375
+ for (let i = 0; i < data.jsonKeys.length; i++) jsonRow[data.jsonKeys[i]] = row[i] ?? "";
376
+ return jsonRow;
377
+ });
378
+ downloadFile(JSON.stringify(rows, null, 2), `${buildExportFilename(filenamePrefix)}.json`, "application/json");
379
+ }
380
+ function downloadFile(content, filename, type) {
381
+ const blob = new Blob([content], { type });
382
+ const url = URL.createObjectURL(blob);
383
+ const link = document.createElement("a");
384
+ link.href = url;
385
+ link.download = filename;
386
+ document.body.appendChild(link);
387
+ try {
388
+ link.click();
389
+ } finally {
390
+ link.remove();
391
+ URL.revokeObjectURL(url);
392
+ }
393
+ }
394
+ function buildExportFilename(prefix) {
395
+ return `${prefix}-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`;
396
+ }
397
+ function titleCase(value) {
398
+ return value.charAt(0).toUpperCase() + value.slice(1);
399
+ }
400
+
401
+ //#endregion
402
+ export { DataGridExportDialog };
403
+ //# sourceMappingURL=data-grid-export-dialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-grid-export-dialog.js","names":[],"sources":["../../../../src/components/data-grid/data-grid-export-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { DownloadSimpleIcon } from \"@phosphor-icons/react\";\nimport React, { useCallback, useEffect, useMemo, useState } from \"react\";\n\nimport { DesignButton } from \"../button\";\nimport { DesignDialog } from \"../dialog\";\nimport { formatGridDate, resolveColumnValue } from \"./state\";\nimport type {\n DataGridColumnDef,\n DataGridExportField,\n DataGridExportFormat,\n DataGridExportOptions,\n DataGridExportScope,\n} from \"./types\";\n\ntype ExportProgress = {\n phase: \"idle\" | \"fetching\" | \"generating\" | \"complete\";\n fetched: number;\n};\n\ntype ExportCellValue = string | number | boolean | null | undefined;\ntype ExportTable = {\n csvHeaders: string[];\n jsonKeys: string[];\n rows: ExportCellValue[][];\n};\n\ntype DataGridExportDialogProps<TRow> = {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n rows: readonly TRow[];\n columns: readonly DataGridColumnDef<TRow>[];\n exportFilename: string;\n exportOptions?: DataGridExportOptions<TRow>;\n};\n\nconst idleExportProgress: ExportProgress = {\n phase: \"idle\",\n fetched: 0,\n};\nconst exportCompletionDisplayMs = 800;\n\nexport function DataGridExportDialog<TRow>({\n open,\n onOpenChange,\n rows,\n columns,\n exportFilename,\n exportOptions,\n}: DataGridExportDialogProps<TRow>) {\n const hasServerExport = exportOptions?.fetchRows != null;\n const resolvedFields = useMemo(\n () => exportOptions?.fields ?? buildColumnExportFields(columns),\n [exportOptions?.fields, columns],\n );\n const [format, setFormat] = useState<DataGridExportFormat>(\"csv\");\n const [scope, setScope] = useState<DataGridExportScope>(\"all\");\n const [fields, setFields] = useState<readonly DataGridExportField<TRow>[]>(resolvedFields);\n const [isExporting, setIsExporting] = useState(false);\n const [progress, setProgress] = useState<ExportProgress>(idleExportProgress);\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n\n useEffect(() => {\n if (!isExporting) {\n setFields(resolvedFields);\n }\n }, [isExporting, resolvedFields]);\n\n const entityName = exportOptions?.entityName ?? \"row\";\n const entityNamePlural = exportOptions?.entityNamePlural ?? \"rows\";\n const filenamePrefix = exportOptions?.filenamePrefix ?? exportFilename;\n const title = exportOptions?.title ?? \"Export data\";\n const description = exportOptions?.description ?? (\n hasServerExport\n ? \"Configure and download data from this table\"\n : \"Configure and download the rows currently loaded in this table\"\n );\n const allScopeLabel = exportOptions?.allScopeLabel ?? `Export all ${entityNamePlural} in the project`;\n const filteredScopeLabel = exportOptions?.filteredScopeLabel ?? `Export only filtered/searched ${entityNamePlural}`;\n const progressSubjectLabel = exportOptions?.progressSubjectLabel ?? entityNamePlural;\n const progressTitle = progress.phase === \"complete\" ? \"Export complete\" : `Exporting ${progressSubjectLabel}`;\n const fetchExportRows = exportOptions?.fetchRows;\n\n const closeDialog = useCallback(() => {\n onOpenChange(false);\n setErrorMessage(null);\n }, [onOpenChange]);\n\n const handleOpenChange = useCallback((nextOpen: boolean) => {\n if (isExporting && !nextOpen) {\n return;\n }\n if (nextOpen) {\n onOpenChange(true);\n } else {\n closeDialog();\n }\n }, [closeDialog, isExporting, onOpenChange]);\n\n const toggleField = useCallback((key: string) => {\n setFields((prev) =>\n prev.map((field) =>\n field.key === key ? { ...field, enabled: !field.enabled } : field\n )\n );\n }, []);\n\n const selectAllFields = useCallback(() => {\n setFields((prev) => prev.map((field) => ({ ...field, enabled: true })));\n }, []);\n\n const deselectAllFields = useCallback(() => {\n setFields((prev) => prev.map((field) => ({ ...field, enabled: false })));\n }, []);\n\n const fetchRows = useCallback(async () => {\n if (fetchExportRows != null) {\n return await fetchExportRows({\n scope,\n onProgress: (fetched) => setProgress({ phase: \"fetching\", fetched }),\n });\n }\n\n setProgress({ phase: \"fetching\", fetched: rows.length });\n return rows;\n }, [fetchExportRows, rows, scope]);\n\n const handleExport = async () => {\n const enabledFields = fields.filter((field) => field.enabled);\n if (enabledFields.length === 0) {\n setErrorMessage(\"Select at least one field to export.\");\n return;\n }\n\n setErrorMessage(null);\n setIsExporting(true);\n setProgress({ phase: \"fetching\", fetched: 0 });\n try {\n const exportRows = await fetchRows();\n\n if (exportRows.length === 0) {\n setErrorMessage(\n exportOptions?.emptyExportDescription\n ?? `There are no ${entityNamePlural} to export.`,\n );\n setIsExporting(false);\n setProgress(idleExportProgress);\n return;\n }\n\n setProgress({ phase: \"generating\", fetched: exportRows.length });\n const transformedData = buildExportTable(exportRows, enabledFields);\n\n if (format === \"csv\") {\n exportToCsv(transformedData, filenamePrefix);\n } else {\n exportToJson(transformedData, filenamePrefix);\n }\n\n setProgress({ phase: \"complete\", fetched: exportRows.length });\n await new Promise<void>((resolve) => setTimeout(resolve, exportCompletionDisplayMs));\n closeDialog();\n } catch {\n setErrorMessage(\"Something went wrong while exporting. Please try again.\");\n } finally {\n setIsExporting(false);\n setProgress(idleExportProgress);\n }\n };\n\n return (\n <DesignDialog\n open={open}\n onOpenChange={handleOpenChange}\n title={isExporting ? progressTitle : title}\n description={isExporting ? `Preparing export for ${progressSubjectLabel}.` : description}\n size=\"2xl\"\n variant=\"plain\"\n headerClassName={isExporting ? \"sr-only\" : undefined}\n hideTopCloseButton={isExporting}\n >\n {isExporting ? (\n <ExportProgressContent\n progress={progress}\n format={format}\n subjectLabel={progressSubjectLabel}\n />\n ) : (\n <div className=\"space-y-6\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor={`${filenamePrefix}-export-format`}>\n Export Format\n </label>\n <select\n id={`${filenamePrefix}-export-format`}\n value={format}\n onChange={(event) => setFormat(event.currentTarget.value === \"json\" ? \"json\" : \"csv\")}\n className=\"h-9 w-full rounded-md border border-input bg-white px-3 text-sm dark:bg-background\"\n >\n <option value=\"csv\">CSV (Comma-separated values)</option>\n <option value=\"json\">JSON (JavaScript Object Notation)</option>\n </select>\n </div>\n\n {hasServerExport ? (\n <fieldset className=\"space-y-2\">\n <legend className=\"text-sm font-medium\">Export Scope</legend>\n <label className=\"flex cursor-pointer items-center gap-2 text-sm\">\n <input\n type=\"radio\"\n name={`${filenamePrefix}-export-scope`}\n value=\"all\"\n checked={scope === \"all\"}\n onChange={() => setScope(\"all\")}\n />\n <span>{allScopeLabel}</span>\n </label>\n <label className=\"flex cursor-pointer items-center gap-2 text-sm\">\n <input\n type=\"radio\"\n name={`${filenamePrefix}-export-scope`}\n value=\"filtered\"\n checked={scope === \"filtered\"}\n onChange={() => setScope(\"filtered\")}\n />\n <span>{filteredScopeLabel}</span>\n </label>\n </fieldset>\n ) : null}\n\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between gap-3\">\n <label className=\"text-sm font-medium\">Fields to Export</label>\n <div className=\"flex gap-2\">\n <DesignButton type=\"button\" variant=\"ghost\" size=\"sm\" onClick={selectAllFields} className=\"h-7 text-xs\">\n Select All\n </DesignButton>\n <DesignButton type=\"button\" variant=\"ghost\" size=\"sm\" onClick={deselectAllFields} className=\"h-7 text-xs\">\n Deselect All\n </DesignButton>\n </div>\n </div>\n <div className=\"grid max-h-[300px] grid-cols-1 gap-3 overflow-y-auto rounded-lg border border-border p-4 sm:grid-cols-2\">\n {fields.map((field) => (\n <label key={field.key} className=\"flex cursor-pointer items-center gap-2 text-sm font-normal\">\n <input\n type=\"checkbox\"\n checked={field.enabled}\n onChange={() => toggleField(field.key)}\n />\n <span>{field.label}</span>\n </label>\n ))}\n </div>\n </div>\n\n {errorMessage != null ? (\n <div className=\"rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive\">\n <div className=\"font-medium\">{exportOptions?.emptyExportTitle ?? \"Export unavailable\"}</div>\n <div>{errorMessage}</div>\n </div>\n ) : null}\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <DesignButton variant=\"outline\" onClick={closeDialog}>\n Cancel\n </DesignButton>\n <DesignButton onClick={handleExport}>\n <DownloadSimpleIcon className=\"mr-2 h-4 w-4\" />\n Export {titleCase(entityNamePlural)}\n </DesignButton>\n </div>\n </div>\n )}\n </DesignDialog>\n );\n}\n\nfunction ExportProgressContent(props: {\n progress: ExportProgress;\n format: DataGridExportFormat;\n subjectLabel: string;\n}) {\n const { progress, format, subjectLabel } = props;\n const fileLabel = format.toUpperCase();\n const isComplete = progress.phase === \"complete\";\n const title = isComplete ? \"Export complete\" : `Exporting ${subjectLabel}`;\n const description = isComplete\n ? `Your ${fileLabel} is ready and the download should begin automatically.`\n : `Your ${fileLabel} is being prepared from matching ${subjectLabel}.`;\n const statusLabel = progress.phase === \"complete\"\n ? \"Download ready\"\n : progress.phase === \"generating\"\n ? `Preparing ${fileLabel}`\n : `Fetching ${subjectLabel}`;\n const countLabel = `${progress.fetched.toLocaleString()} ${isComplete ? \"exported\" : \"fetched\"}`;\n\n return (\n <div className=\"space-y-4\">\n <div className=\"space-y-1\">\n <h2 className=\"text-base font-semibold leading-snug\">{title}</h2>\n <p className=\"text-sm text-muted-foreground\">{description}</p>\n </div>\n\n <div className=\"rounded-xl border border-border bg-muted/35 p-4\">\n <div className=\"mb-3 flex items-center justify-between gap-4 text-sm\">\n <span className=\"font-medium text-foreground\">{statusLabel}</span>\n <span className=\"shrink-0 tabular-nums text-muted-foreground\">\n {countLabel}\n </span>\n </div>\n <div className=\"relative h-2 overflow-hidden rounded-full bg-foreground/10\">\n {isComplete ? (\n <div className=\"h-full w-full rounded-full bg-emerald-500/80\" />\n ) : (\n <div className=\"data-grid-export-progress-shimmer absolute inset-y-0 left-0 w-2/5 rounded-full bg-gradient-to-r from-transparent via-foreground/65 to-transparent\" />\n )}\n </div>\n </div>\n\n <div className=\"rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-900 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-200\">\n Do not reload this page until the export finishes. The download will start automatically.\n </div>\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <DesignButton variant=\"outline\" disabled>\n Cancel\n </DesignButton>\n </div>\n </div>\n );\n}\n\nfunction buildColumnExportFields<TRow>(\n columns: readonly DataGridColumnDef<TRow>[],\n): readonly DataGridExportField<TRow>[] {\n const fields: DataGridExportField<TRow>[] = [];\n\n for (const column of columns) {\n const label = typeof column.header === \"string\" ? column.header.trim() : column.id;\n if (label.length === 0) {\n continue;\n }\n\n fields.push({\n key: column.id,\n label,\n enabled: true,\n getValue: (row) => formatColumnExportValue(column, row),\n });\n }\n\n return fields;\n}\n\nfunction formatColumnExportValue<TRow>(\n column: DataGridColumnDef<TRow>,\n row: TRow,\n): unknown {\n const value = resolveColumnValue(column, row);\n if (column.formatValue != null) {\n return column.formatValue(value, row);\n }\n if (column.type === \"date\" || column.type === \"dateTime\") {\n return formatGridDate(value, \"absolute\", {\n parseValue: column.parseValue,\n dateFormat: column.dateFormat,\n }).display ?? \"\";\n }\n return value;\n}\n\nfunction buildExportTable<TRow>(\n rows: readonly TRow[],\n enabledFields: readonly DataGridExportField<TRow>[],\n): ExportTable {\n return {\n csvHeaders: enabledFields.map((field) => field.label),\n jsonKeys: buildJsonKeys(enabledFields),\n rows: rows.map((row) => enabledFields.map((field) => toExportCellValue(field.getValue(row)))),\n };\n}\n\nfunction buildJsonKeys<TRow>(\n fields: readonly DataGridExportField<TRow>[],\n): string[] {\n const labelCounts = new Map<string, number>();\n for (const field of fields) {\n labelCounts.set(field.label, (labelCounts.get(field.label) ?? 0) + 1);\n }\n\n const usedKeys = new Map<string, true>();\n const keys: string[] = [];\n for (const field of fields) {\n const baseKey = labelCounts.get(field.label) === 1 ? field.label : `${field.label} (${field.key})`;\n let key = baseKey;\n let suffix = 2;\n while (usedKeys.has(key)) {\n key = `${baseKey} ${suffix}`;\n suffix++;\n }\n usedKeys.set(key, true);\n keys.push(key);\n }\n\n return keys;\n}\n\nfunction toExportCellValue(value: unknown): ExportCellValue {\n if (value == null) {\n return \"\";\n }\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n return value;\n }\n if (typeof value === \"bigint\") {\n return value.toString();\n }\n if (value instanceof Date) {\n return value.toISOString();\n }\n if (typeof value === \"object\") {\n return JSON.stringify(value);\n }\n return String(value);\n}\n\nfunction exportToCsv(data: ExportTable, filenamePrefix: string) {\n const csvContent = \"\\uFEFF\" + [\n data.csvHeaders.map(escapeCsvCell).join(\",\"),\n ...data.rows.map((row) => row.map(escapeCsvCell).join(\",\")),\n ].join(\"\\n\");\n downloadFile(csvContent, `${buildExportFilename(filenamePrefix)}.csv`, \"text/csv;charset=utf-8;\");\n}\n\nfunction escapeCsvCell(value: ExportCellValue): string {\n const rawText = String(value ?? \"\");\n const text = typeof value === \"string\" && /^[=+\\-@\\t\\r]/.test(rawText.trimStart()) ? `'${rawText}` : rawText;\n if (text.includes(\",\") || text.includes('\"') || text.includes(\"\\n\") || text.includes(\"\\r\")) {\n return `\"${text.replace(/\"/g, '\"\"')}\"`;\n }\n return text;\n}\n\nfunction exportToJson(data: ExportTable, filenamePrefix: string) {\n const rows = data.rows.map((row) => {\n const jsonRow: Record<string, ExportCellValue> = {};\n for (let i = 0; i < data.jsonKeys.length; i++) {\n jsonRow[data.jsonKeys[i]] = row[i] ?? \"\";\n }\n return jsonRow;\n });\n const jsonString = JSON.stringify(rows, null, 2);\n downloadFile(jsonString, `${buildExportFilename(filenamePrefix)}.json`, \"application/json\");\n}\n\nfunction downloadFile(content: string, filename: string, type: string) {\n const blob = new Blob([content], { type });\n const url = URL.createObjectURL(blob);\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = filename;\n document.body.appendChild(link);\n try {\n link.click();\n } finally {\n link.remove();\n URL.revokeObjectURL(url);\n }\n}\n\nfunction buildExportFilename(prefix: string) {\n return `${prefix}-${new Date().toISOString().split(\"T\")[0]}`;\n}\n\nfunction titleCase(value: string) {\n return value.charAt(0).toUpperCase() + value.slice(1);\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"data-grid.d.ts","names":[],"sources":["../../../../src/components/data-grid/data-grid.tsx"],"mappings":";;;;iBAmEgB,mCAAA,CAAoC,MAAA,EAAQ,WAAA;;;AAA5D;;;;;AAqhBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,QAAA,MAAA,CAAe,KAAA,EAAO,aAAA,CAAc,IAAA,IAAK,kBAAA,CAAA,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"data-grid.d.ts","names":[],"sources":["../../../../src/components/data-grid/data-grid.tsx"],"mappings":";;;;iBAoEgB,mCAAA,CAAoC,MAAA,EAAQ,WAAA;;;AAA5D;;;;;AAqhBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,QAAA,MAAA,CAAe,KAAA,EAAO,aAAA,CAAc,IAAA,IAAK,kBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -2,15 +2,16 @@
2
2
 
3
3
  import { ArrowDown, ArrowUp, CaretDown, CaretUp, CheckSquare, MinusSquare, Square } from "@phosphor-icons/react";
4
4
  import { cn } from "@hexclave/ui";
5
- import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
7
- import { exportToCsv, formatGridDate, resolveColumnValue } from "./state.js";
7
+ import { formatGridDate, resolveColumnValue } from "./state.js";
8
8
  import { resolveDataGridStrings } from "./strings.js";
9
9
  import { throwErr } from "@hexclave/shared/dist/utils/errors";
10
10
  import { getCoreRowModel, useReactTable } from "@tanstack/react-table";
11
11
  import { useVirtualizer } from "@tanstack/react-virtual";
12
12
  import { DesignSkeleton } from "../skeleton.js";
13
13
  import { DEFAULT_COL_WIDTH, clampColumnWidth, getEffectiveMaxWidth, getEffectiveMinWidth } from "./data-grid-sizing.js";
14
+ import { DataGridExportDialog } from "./data-grid-export-dialog.js";
14
15
  import { DataGridToolbar } from "./data-grid-toolbar.js";
15
16
 
16
17
  //#region src/components/data-grid/data-grid.tsx
@@ -428,7 +429,7 @@ function DefaultFooter({ ctx, pagination, onChange }) {
428
429
  * - `renderCell` must be a pure function — no React hooks inside.
429
430
  */
430
431
  function DataGrid(props) {
431
- const { columns: allColumns, rows, getRowId, totalRowCount, isLoading = false, isRefetching = false, hasMore = false, isLoadingMore = false, onLoadMore, state, onChange, paginationMode = "paginated", selectionMode = "none", resizable = true, rowHeight: rowHeightProp = 44, estimatedRowHeight: estimatedRowHeightProp, headerHeight = 44, overscan = 5, maxHeight, fillHeight = true, stickyTop, toolbar, toolbarExtra, emptyState, loadingState, footer, footerExtra, exportFilename = "export", strings: stringsOverride, className, onRowClick, onRowDoubleClick, onSelectionChange, onSortChange, onColumnResize, onColumnVisibilityChange } = props;
432
+ const { columns: allColumns, rows, getRowId, totalRowCount, isLoading = false, isRefetching = false, hasMore = false, isLoadingMore = false, onLoadMore, state, onChange, paginationMode = "paginated", selectionMode = "none", resizable = true, rowHeight: rowHeightProp = 44, estimatedRowHeight: estimatedRowHeightProp, headerHeight = 44, overscan = 5, maxHeight, fillHeight = true, stickyTop, toolbar, toolbarExtra, emptyState, loadingState, footer, footerExtra, exportFilename = "export", exportOptions, strings: stringsOverride, className, onRowClick, onRowDoubleClick, onSelectionChange, onSortChange, onColumnResize, onColumnVisibilityChange } = props;
432
433
  const isDynamicRowHeight = rowHeightProp === "auto";
433
434
  const fixedRowHeight = isDynamicRowHeight ? void 0 : rowHeightProp;
434
435
  const estimatedRowHeight = estimatedRowHeightProp ?? fixedRowHeight ?? 44;
@@ -665,18 +666,10 @@ function DataGrid(props) {
665
666
  state.selection.selectedIds,
666
667
  fireSelection
667
668
  ]);
669
+ const [exportDialogOpen, setExportDialogOpen] = useState(false);
668
670
  const handleExportCsv = useCallback(() => {
669
- if (typeof window !== "undefined" && rows.length > 0) {
670
- const totalSuffix = totalRowCount != null && totalRowCount > rows.length ? ` of ${totalRowCount} total — load more rows first to include them` : "";
671
- if (!window.confirm(`Export ${rows.length.toLocaleString()} loaded row${rows.length === 1 ? "" : "s"}${totalSuffix}?`)) return;
672
- }
673
- exportToCsv(rows, visibleColumns, exportFilename);
674
- }, [
675
- rows,
676
- visibleColumns,
677
- exportFilename,
678
- totalRowCount
679
- ]);
671
+ setExportDialogOpen(true);
672
+ }, []);
680
673
  const scrollContainerRef = useRef(null);
681
674
  const headerScrollRef = useRef(null);
682
675
  const stickyChromeRef = useRef(null);
@@ -768,7 +761,14 @@ function DataGrid(props) {
768
761
  return m;
769
762
  }, [headers]);
770
763
  const isBounded = fillHeight || maxHeight != null;
771
- return /* @__PURE__ */ jsxs("div", {
764
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(DataGridExportDialog, {
765
+ open: exportDialogOpen,
766
+ onOpenChange: setExportDialogOpen,
767
+ rows,
768
+ columns: visibleColumns,
769
+ exportFilename,
770
+ exportOptions
771
+ }), /* @__PURE__ */ jsxs("div", {
772
772
  ref: gridRef,
773
773
  className: cn("isolate flex w-full min-w-0 max-w-full flex-col bg-transparent rounded-[calc(var(--radius)*2)]", fillHeight ? "min-h-0 h-full" : "min-h-0 h-auto", isBounded && "overflow-hidden", className),
774
774
  style: maxHeight != null ? {
@@ -921,7 +921,7 @@ function DataGrid(props) {
921
921
  }), footerExtra && (typeof footerExtra === "function" ? footerExtra(footerCtx) : footerExtra)]
922
922
  })
923
923
  ]
924
- });
924
+ })] });
925
925
  }
926
926
 
927
927
  //#endregion