@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
@@ -37,8 +37,9 @@ import React, {
37
37
 
38
38
  import { DesignSkeleton } from "../skeleton";
39
39
  import { DEFAULT_COL_WIDTH, clampColumnWidth, getEffectiveMaxWidth, getEffectiveMinWidth } from "./data-grid-sizing";
40
+ import { DataGridExportDialog } from "./data-grid-export-dialog";
40
41
  import { DataGridToolbar } from "./data-grid-toolbar";
41
- import { exportToCsv, formatGridDate, resolveColumnValue } from "./state";
42
+ import { formatGridDate, resolveColumnValue } from "./state";
42
43
  import { resolveDataGridStrings } from "./strings";
43
44
  import type {
44
45
  DataGridCellContext,
@@ -628,6 +629,7 @@ export function DataGrid<TRow>(props: DataGridProps<TRow>) {
628
629
  footer,
629
630
  footerExtra,
630
631
  exportFilename = "export",
632
+ exportOptions,
631
633
  strings: stringsOverride,
632
634
  className,
633
635
  onRowClick,
@@ -894,24 +896,11 @@ export function DataGrid<TRow>(props: DataGridProps<TRow>) {
894
896
  );
895
897
  }, [rowIds, state.selection.selectedIds, fireSelection]);
896
898
 
897
- // ── CSV export ───────────────────────────────────────────────
898
- // The grid only knows about rows currently in memory (the visible page in
899
- // paginated mode, or the loaded prefix in infinite mode). To avoid users
900
- // assuming "Export CSV" means "everything that exists on the server", we
901
- // confirm with the loaded-row count before downloading. Consumers that
902
- // want true full-dataset export can override this via a parent toolbar.
899
+ // ── Export ───────────────────────────────────────────────────
900
+ const [exportDialogOpen, setExportDialogOpen] = useState(false);
903
901
  const handleExportCsv = useCallback(() => {
904
- if (typeof window !== "undefined" && rows.length > 0) {
905
- const totalSuffix = totalRowCount != null && totalRowCount > rows.length
906
- ? ` of ${totalRowCount} total — load more rows first to include them`
907
- : "";
908
- const confirmed = window.confirm(
909
- `Export ${rows.length.toLocaleString()} loaded row${rows.length === 1 ? "" : "s"}${totalSuffix}?`,
910
- );
911
- if (!confirmed) return;
912
- }
913
- exportToCsv(rows, visibleColumns, exportFilename);
914
- }, [rows, visibleColumns, exportFilename, totalRowCount]);
902
+ setExportDialogOpen(true);
903
+ }, []);
915
904
 
916
905
  // ── Virtualizer ──────────────────────────────────────────────
917
906
  const scrollContainerRef = useRef<HTMLDivElement>(null);
@@ -1019,81 +1008,90 @@ export function DataGrid<TRow>(props: DataGridProps<TRow>) {
1019
1008
  const isBounded = fillHeight || maxHeight != null;
1020
1009
 
1021
1010
  return (
1022
- <div
1023
- ref={gridRef}
1024
- className={cn(
1025
- "isolate flex w-full min-w-0 max-w-full flex-col bg-transparent rounded-[calc(var(--radius)*2)]",
1026
- fillHeight ? "min-h-0 h-full" : "min-h-0 h-auto",
1027
- isBounded && "overflow-hidden",
1028
- className,
1029
- )}
1030
- style={maxHeight != null ? { ...cssVars, maxHeight } : cssVars}
1031
- role="grid"
1032
- aria-rowcount={totalRowCount ?? rows.length}
1033
- aria-colcount={visibleColumns.length}
1034
- >
1011
+ <>
1012
+ <DataGridExportDialog
1013
+ open={exportDialogOpen}
1014
+ onOpenChange={setExportDialogOpen}
1015
+ rows={rows}
1016
+ columns={visibleColumns}
1017
+ exportFilename={exportFilename}
1018
+ exportOptions={exportOptions}
1019
+ />
1035
1020
  <div
1036
- ref={stickyChromeRef}
1037
- className="sticky z-30 w-full min-w-0 shrink-0 overflow-visible rounded-t-[calc(var(--radius)*2)] bg-white/90 dark:bg-background/60 backdrop-blur-xl"
1038
- style={{ top: stickyTop ?? (maxHeight != null ? 0 : "var(--data-grid-sticky-top, 0px)") }}
1039
- >
1040
- {toolbar !== false && (
1041
- <div className="relative bg-transparent">
1042
- {toolbar
1043
- ? toolbar(toolbarCtx)
1044
- : (
1045
- <DataGridToolbar
1046
- ctx={toolbarCtx}
1047
- extra={typeof toolbarExtra === "function" ? toolbarExtra(toolbarCtx) : toolbarExtra}
1048
- />
1049
- )}
1050
- </div>
1021
+ ref={gridRef}
1022
+ className={cn(
1023
+ "isolate flex w-full min-w-0 max-w-full flex-col bg-transparent rounded-[calc(var(--radius)*2)]",
1024
+ fillHeight ? "min-h-0 h-full" : "min-h-0 h-auto",
1025
+ isBounded && "overflow-hidden",
1026
+ className,
1051
1027
  )}
1052
-
1053
- <div className="relative">
1054
- {isRefetching && (
1055
- <div className="absolute top-0 left-0 right-0 h-0.5 z-30 bg-foreground/[0.04] overflow-hidden">
1056
- <div className="h-full w-1/3 bg-blue-500/60 rounded-full animate-pulse" />
1028
+ style={maxHeight != null ? { ...cssVars, maxHeight } : cssVars}
1029
+ role="grid"
1030
+ aria-rowcount={totalRowCount ?? rows.length}
1031
+ aria-colcount={visibleColumns.length}
1032
+ >
1033
+ <div
1034
+ ref={stickyChromeRef}
1035
+ className="sticky z-30 w-full min-w-0 shrink-0 overflow-visible rounded-t-[calc(var(--radius)*2)] bg-white/90 dark:bg-background/60 backdrop-blur-xl"
1036
+ style={{ top: stickyTop ?? (maxHeight != null ? 0 : "var(--data-grid-sticky-top, 0px)") }}
1037
+ >
1038
+ {toolbar !== false && (
1039
+ <div className="relative bg-transparent">
1040
+ {toolbar
1041
+ ? toolbar(toolbarCtx)
1042
+ : (
1043
+ <DataGridToolbar
1044
+ ctx={toolbarCtx}
1045
+ extra={typeof toolbarExtra === "function" ? toolbarExtra(toolbarCtx) : toolbarExtra}
1046
+ />
1047
+ )}
1057
1048
  </div>
1058
1049
  )}
1059
- <div
1060
- ref={headerScrollRef}
1061
- className="w-full min-w-0 shrink-0 overflow-hidden border-b border-foreground/[0.06]"
1062
- >
1050
+
1051
+ <div className="relative">
1052
+ {isRefetching && (
1053
+ <div className="absolute top-0 left-0 right-0 h-0.5 z-30 bg-foreground/[0.04] overflow-hidden">
1054
+ <div className="h-full w-1/3 bg-blue-500/60 rounded-full animate-pulse" />
1055
+ </div>
1056
+ )}
1063
1057
  <div
1064
- className="flex"
1065
- style={{ height: headerHeight, minWidth: totalContentWidth }}
1066
- role="row"
1058
+ ref={headerScrollRef}
1059
+ className="w-full min-w-0 shrink-0 overflow-hidden border-b border-foreground/[0.06]"
1067
1060
  >
1068
- {selectionMode !== "none" && (
1069
- <div
1070
- className="flex items-center justify-center border-r border-foreground/[0.04]"
1071
- style={{ width: 44 }}
1072
- >
1073
- {selectionMode === "multiple" && (
1074
- <SelectionCheckbox
1075
- checked={allSelected}
1076
- indeterminate={someSelected}
1077
- onChange={handleSelectAll}
1078
- ariaLabel="Select all rows on this page"
1079
- title="Select all rows on this page"
1080
- />
1081
- )}
1082
- </div>
1083
- )}
1084
- {visibleColumns.map((col) => {
1085
- const header = headerByColId.get(col.id);
1086
- if (!header) return null;
1087
- return <HeaderCell key={col.id} header={header} col={col} resizable={resizable} />;
1088
- })}
1061
+ <div
1062
+ className="flex"
1063
+ style={{ height: headerHeight, minWidth: totalContentWidth }}
1064
+ role="row"
1065
+ >
1066
+ {selectionMode !== "none" && (
1067
+ <div
1068
+ className="flex items-center justify-center border-r border-foreground/[0.04]"
1069
+ style={{ width: 44 }}
1070
+ >
1071
+ {selectionMode === "multiple" && (
1072
+ <SelectionCheckbox
1073
+ checked={allSelected}
1074
+ indeterminate={someSelected}
1075
+ onChange={handleSelectAll}
1076
+ ariaLabel="Select all rows on this page"
1077
+ title="Select all rows on this page"
1078
+ />
1079
+ )}
1080
+ </div>
1081
+ )}
1082
+ {visibleColumns.map((col) => {
1083
+ const header = headerByColId.get(col.id);
1084
+ if (!header) return null;
1085
+ return <HeaderCell key={col.id} header={header} col={col} resizable={resizable} />;
1086
+ })}
1087
+ </div>
1089
1088
  </div>
1090
1089
  </div>
1091
1090
  </div>
1092
- </div>
1093
1091
 
1094
- <div
1095
- ref={scrollContainerRef}
1096
- className={cn(
1092
+ <div
1093
+ ref={scrollContainerRef}
1094
+ className={cn(
1097
1095
  "relative z-0 w-full min-w-0 overflow-auto bg-transparent",
1098
1096
  isBounded ? "min-h-0 flex-1" : "flex-none",
1099
1097
  "[&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar]:h-1.5",
@@ -1101,61 +1099,61 @@ export function DataGrid<TRow>(props: DataGridProps<TRow>) {
1101
1099
  "[&::-webkit-scrollbar-thumb]:bg-foreground/[0.08] [&::-webkit-scrollbar-thumb]:rounded-full",
1102
1100
  "[&::-webkit-scrollbar-thumb]:hover:bg-foreground/[0.15]",
1103
1101
  )}
1104
- onScroll={handleBodyScroll}
1105
- >
1106
- <div
1107
- ref={rowsClipRef}
1108
- data-data-grid-rows-clip=""
1109
- className="relative z-0"
1110
- style={{
1111
- minWidth: totalContentWidth,
1112
- clipPath: "inset(var(--data-grid-sticky-overlap, 0px) 0 0 0)",
1113
- }}
1102
+ onScroll={handleBodyScroll}
1114
1103
  >
1115
- {isLoading && (
1116
- <div style={{ minWidth: totalContentWidth }}>
1117
- {loadingState ?? Array.from({ length: 8 }).map((_, i) => (
1118
- <SkeletonRow
1119
- key={i}
1120
- columns={visibleColumns}
1121
- height={estimatedRowHeight}
1122
- showCheckbox={selectionMode !== "none"}
1123
- />
1124
- ))}
1125
- </div>
1126
- )}
1127
-
1128
- {!isLoading && rows.length === 0 && (
1129
- <div
1130
- className="flex items-center justify-center py-16 text-sm text-muted-foreground"
1131
- style={{ minWidth: totalContentWidth }}
1132
- >
1133
- {emptyState ?? strings.noData}
1134
- </div>
1135
- )}
1136
-
1137
- {!isLoading && rows.length > 0 && (
1138
- <div
1139
- style={{
1140
- height: rowVirtualizer.getTotalSize(),
1141
- width: "100%",
1142
- minWidth: totalContentWidth,
1143
- position: "relative",
1144
- }}
1145
- >
1146
- {rowVirtualizer.getVirtualItems().map((virtualRow: VirtualItem) => {
1147
- const row = rows[virtualRow.index] ?? throwErr(
1104
+ <div
1105
+ ref={rowsClipRef}
1106
+ data-data-grid-rows-clip=""
1107
+ className="relative z-0"
1108
+ style={{
1109
+ minWidth: totalContentWidth,
1110
+ clipPath: "inset(var(--data-grid-sticky-overlap, 0px) 0 0 0)",
1111
+ }}
1112
+ >
1113
+ {isLoading && (
1114
+ <div style={{ minWidth: totalContentWidth }}>
1115
+ {loadingState ?? Array.from({ length: 8 }).map((_, i) => (
1116
+ <SkeletonRow
1117
+ key={i}
1118
+ columns={visibleColumns}
1119
+ height={estimatedRowHeight}
1120
+ showCheckbox={selectionMode !== "none"}
1121
+ />
1122
+ ))}
1123
+ </div>
1124
+ )}
1125
+
1126
+ {!isLoading && rows.length === 0 && (
1127
+ <div
1128
+ className="flex items-center justify-center py-16 text-sm text-muted-foreground"
1129
+ style={{ minWidth: totalContentWidth }}
1130
+ >
1131
+ {emptyState ?? strings.noData}
1132
+ </div>
1133
+ )}
1134
+
1135
+ {!isLoading && rows.length > 0 && (
1136
+ <div
1137
+ style={{
1138
+ height: rowVirtualizer.getTotalSize(),
1139
+ width: "100%",
1140
+ minWidth: totalContentWidth,
1141
+ position: "relative",
1142
+ }}
1143
+ >
1144
+ {rowVirtualizer.getVirtualItems().map((virtualRow: VirtualItem) => {
1145
+ const row = rows[virtualRow.index] ?? throwErr(
1148
1146
  `DataGrid: virtualized row index ${virtualRow.index} out of range (rows.length=${rows.length})`,
1149
1147
  );
1150
- const rowId = getRowId(row);
1151
- const isSelected = state.selection.selectedIds.has(rowId);
1152
- const isOddRow = virtualRow.index % 2 === 1;
1153
- return (
1154
- <div
1155
- key={rowId}
1156
- ref={isDynamicRowHeight ? rowVirtualizer.measureElement : undefined}
1157
- data-index={virtualRow.index}
1158
- className={cn(
1148
+ const rowId = getRowId(row);
1149
+ const isSelected = state.selection.selectedIds.has(rowId);
1150
+ const isOddRow = virtualRow.index % 2 === 1;
1151
+ return (
1152
+ <div
1153
+ key={rowId}
1154
+ ref={isDynamicRowHeight ? rowVirtualizer.measureElement : undefined}
1155
+ data-index={virtualRow.index}
1156
+ className={cn(
1159
1157
  "absolute left-0 w-full flex",
1160
1158
  "border-b border-black/[0.03] dark:border-white/[0.03]",
1161
1159
  "transition-colors duration-75",
@@ -1166,66 +1164,67 @@ export function DataGrid<TRow>(props: DataGridProps<TRow>) {
1166
1164
  : "hover:bg-foreground/[0.025] dark:hover:bg-foreground/[0.04]",
1167
1165
  (selectionMode !== "none" || onRowClick) && "cursor-pointer",
1168
1166
  )}
1169
- style={{
1170
- ...(isDynamicRowHeight
1171
- ? { minHeight: estimatedRowHeight }
1172
- : { height: fixedRowHeight }),
1173
- transform: `translateY(${virtualRow.start}px)`,
1174
- }}
1175
- onClick={(e) => { if (!shouldIgnoreRowClick(e)) handleRowClick(row, rowId, e); }}
1176
- onDoubleClick={(e) => { if (!shouldIgnoreRowClick(e)) onRowDoubleClick?.(row, rowId, e); }}
1177
- role="row"
1178
- aria-rowindex={virtualRow.index + 2}
1179
- aria-selected={isSelected}
1180
- data-row-id={rowId}
1181
- data-state={isSelected ? "selected" : undefined}
1182
- >
1183
- {selectionMode !== "none" && (
1184
- <div
1185
- className="flex items-center justify-center border-r border-black/[0.04] dark:border-white/[0.04]"
1186
- style={{ width: 44 }}
1187
- >
1188
- <SelectionCheckbox
1189
- checked={isSelected}
1190
- onChange={(event) => handleRowClick(row, rowId, event)}
1191
- ariaLabel={`Select row ${rowId}`}
1167
+ style={{
1168
+ ...(isDynamicRowHeight
1169
+ ? { minHeight: estimatedRowHeight }
1170
+ : { height: fixedRowHeight }),
1171
+ transform: `translateY(${virtualRow.start}px)`,
1172
+ }}
1173
+ onClick={(e) => { if (!shouldIgnoreRowClick(e)) handleRowClick(row, rowId, e); }}
1174
+ onDoubleClick={(e) => { if (!shouldIgnoreRowClick(e)) onRowDoubleClick?.(row, rowId, e); }}
1175
+ role="row"
1176
+ aria-rowindex={virtualRow.index + 2}
1177
+ aria-selected={isSelected}
1178
+ data-row-id={rowId}
1179
+ data-state={isSelected ? "selected" : undefined}
1180
+ >
1181
+ {selectionMode !== "none" && (
1182
+ <div
1183
+ className="flex items-center justify-center border-r border-black/[0.04] dark:border-white/[0.04]"
1184
+ style={{ width: 44 }}
1185
+ >
1186
+ <SelectionCheckbox
1187
+ checked={isSelected}
1188
+ onChange={(event) => handleRowClick(row, rowId, event)}
1189
+ ariaLabel={`Select row ${rowId}`}
1190
+ />
1191
+ </div>
1192
+ )}
1193
+ {visibleColumns.map((col) => (
1194
+ <DataCell
1195
+ key={col.id}
1196
+ col={col}
1197
+ row={row}
1198
+ rowId={rowId}
1199
+ rowIndex={virtualRow.index}
1200
+ isSelected={isSelected}
1201
+ dateDisplay={state.dateDisplay}
1192
1202
  />
1193
- </div>
1194
- )}
1195
- {visibleColumns.map((col) => (
1196
- <DataCell
1197
- key={col.id}
1198
- col={col}
1199
- row={row}
1200
- rowId={rowId}
1201
- rowIndex={virtualRow.index}
1202
- isSelected={isSelected}
1203
- dateDisplay={state.dateDisplay}
1204
- />
1205
- ))}
1206
- </div>
1207
- );
1208
- })}
1209
- </div>
1210
- )}
1211
-
1212
- {paginationMode === "infinite" && hasMore && !isLoading && (
1213
- <InfiniteScrollSentinel
1214
- onIntersect={onLoadMore ?? NOOP}
1215
- isLoading={isLoadingMore}
1216
- rootRef={infiniteScrollRootRef}
1217
- strings={strings}
1218
- />
1219
- )}
1203
+ ))}
1204
+ </div>
1205
+ );
1206
+ })}
1207
+ </div>
1208
+ )}
1209
+
1210
+ {paginationMode === "infinite" && hasMore && !isLoading && (
1211
+ <InfiniteScrollSentinel
1212
+ onIntersect={onLoadMore ?? NOOP}
1213
+ isLoading={isLoadingMore}
1214
+ rootRef={infiniteScrollRootRef}
1215
+ strings={strings}
1216
+ />
1217
+ )}
1218
+ </div>
1220
1219
  </div>
1221
- </div>
1222
1220
 
1223
- {footer !== false && (
1224
- <div className="sticky bottom-0 z-30 shrink-0 overflow-hidden rounded-b-[calc(var(--radius)*2)] bg-white/90 dark:bg-background/60 backdrop-blur-xl">
1225
- {footer ? footer(footerCtx) : <DefaultFooter ctx={footerCtx} pagination={paginationMode} onChange={onChange} />}
1226
- {footerExtra && (typeof footerExtra === "function" ? footerExtra(footerCtx) : footerExtra)}
1227
- </div>
1228
- )}
1229
- </div>
1221
+ {footer !== false && (
1222
+ <div className="sticky bottom-0 z-30 shrink-0 overflow-hidden rounded-b-[calc(var(--radius)*2)] bg-white/90 dark:bg-background/60 backdrop-blur-xl">
1223
+ {footer ? footer(footerCtx) : <DefaultFooter ctx={footerCtx} pagination={paginationMode} onChange={onChange} />}
1224
+ {footerExtra && (typeof footerExtra === "function" ? footerExtra(footerCtx) : footerExtra)}
1225
+ </div>
1226
+ )}
1227
+ </div>
1228
+ </>
1230
1229
  );
1231
1230
  }
@@ -56,6 +56,11 @@ export type {
56
56
  DataGridFetchParams,
57
57
  DataGridFetchResult,
58
58
  DataGridDataSource,
59
+ DataGridExportField,
60
+ DataGridExportFormat,
61
+ DataGridExportOptions,
62
+ DataGridExportRowsOptions,
63
+ DataGridExportScope,
59
64
  DataGridCallbacks,
60
65
  DataGridProps,
61
66
  DataGridToolbarContext,
@@ -221,6 +221,38 @@ export type DataGridDataSource<TRow> = (
221
221
  params: DataGridFetchParams,
222
222
  ) => AsyncGenerator<DataGridFetchResult<TRow>, void, undefined>;
223
223
 
224
+ // ─── Export ─────────────────────────────────────────────────────────
225
+ export type DataGridExportFormat = "csv" | "json";
226
+
227
+ export type DataGridExportScope = "all" | "filtered";
228
+
229
+ export type DataGridExportField<TRow> = {
230
+ key: string;
231
+ label: string;
232
+ enabled: boolean;
233
+ getValue: (row: TRow) => unknown;
234
+ };
235
+
236
+ export type DataGridExportRowsOptions = {
237
+ scope: DataGridExportScope;
238
+ onProgress: (fetched: number) => void;
239
+ };
240
+
241
+ export type DataGridExportOptions<TRow> = {
242
+ title?: string;
243
+ description?: ReactNode;
244
+ entityName?: string;
245
+ entityNamePlural?: string;
246
+ filenamePrefix?: string;
247
+ fields?: readonly DataGridExportField<TRow>[];
248
+ fetchRows?: (options: DataGridExportRowsOptions) => Promise<readonly TRow[]>;
249
+ emptyExportTitle?: string;
250
+ emptyExportDescription?: string;
251
+ allScopeLabel?: ReactNode;
252
+ filteredScopeLabel?: ReactNode;
253
+ progressSubjectLabel?: string;
254
+ };
255
+
224
256
  // ─── Callbacks ───────────────────────────────────────────────────────
225
257
  export type DataGridCallbacks<TRow> = {
226
258
  onRowClick?: (row: TRow, rowId: RowId, event: React.MouseEvent) => void;
@@ -334,6 +366,9 @@ export type DataGridProps<TRow> = {
334
366
 
335
367
  /** Filename stem for CSV export (without extension). */
336
368
  exportFilename?: string;
369
+ /** Full export dialog configuration. If omitted, the dialog exports the
370
+ * currently loaded rows using the grid's visible columns. */
371
+ exportOptions?: DataGridExportOptions<TRow>;
337
372
  /** i18n overrides. */
338
373
  strings?: Partial<DataGridStrings>;
339
374