@stackframe/dashboard-ui-components 2.8.86 → 2.8.89

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 (64) hide show
  1. package/dist/components/analytics-chart/analytics-chart-pie.js +3 -3
  2. package/dist/components/analytics-chart/analytics-chart-pie.js.map +1 -1
  3. package/dist/components/data-grid/data-grid-sizing.d.ts +2 -1
  4. package/dist/components/data-grid/data-grid-sizing.d.ts.map +1 -1
  5. package/dist/components/data-grid/data-grid-sizing.js +33 -4
  6. package/dist/components/data-grid/data-grid-sizing.js.map +1 -1
  7. package/dist/components/data-grid/data-grid-toolbar.js +18 -15
  8. package/dist/components/data-grid/data-grid-toolbar.js.map +1 -1
  9. package/dist/components/data-grid/data-grid.d.ts +35 -1
  10. package/dist/components/data-grid/data-grid.d.ts.map +1 -1
  11. package/dist/components/data-grid/data-grid.js +329 -127
  12. package/dist/components/data-grid/data-grid.js.map +1 -1
  13. package/dist/components/data-grid/data-grid.test.d.ts +1 -0
  14. package/dist/components/data-grid/data-grid.test.js +215 -0
  15. package/dist/components/data-grid/data-grid.test.js.map +1 -0
  16. package/dist/components/data-grid/index.d.ts +3 -2
  17. package/dist/components/data-grid/index.js +13 -0
  18. package/dist/components/data-grid/state.d.ts.map +1 -1
  19. package/dist/components/data-grid/state.js +24 -7
  20. package/dist/components/data-grid/state.js.map +1 -1
  21. package/dist/components/data-grid/types.d.ts +34 -3
  22. package/dist/components/data-grid/types.d.ts.map +1 -1
  23. package/dist/components/data-grid/use-data-source.d.ts +6 -0
  24. package/dist/components/data-grid/use-data-source.d.ts.map +1 -1
  25. package/dist/components/data-grid/use-data-source.js +10 -2
  26. package/dist/components/data-grid/use-data-source.js.map +1 -1
  27. package/dist/components/tabs.d.ts +5 -1
  28. package/dist/components/tabs.d.ts.map +1 -1
  29. package/dist/components/tabs.js +40 -27
  30. package/dist/components/tabs.js.map +1 -1
  31. package/dist/dashboard-ui-components.global.js +672 -368
  32. package/dist/dashboard-ui-components.global.js.map +4 -4
  33. package/dist/esm/components/analytics-chart/analytics-chart-pie.js +3 -3
  34. package/dist/esm/components/analytics-chart/analytics-chart-pie.js.map +1 -1
  35. package/dist/esm/components/data-grid/data-grid-sizing.d.ts +2 -1
  36. package/dist/esm/components/data-grid/data-grid-sizing.d.ts.map +1 -1
  37. package/dist/esm/components/data-grid/data-grid-sizing.js +33 -5
  38. package/dist/esm/components/data-grid/data-grid-sizing.js.map +1 -1
  39. package/dist/esm/components/data-grid/data-grid-toolbar.js +18 -15
  40. package/dist/esm/components/data-grid/data-grid-toolbar.js.map +1 -1
  41. package/dist/esm/components/data-grid/data-grid.d.ts +35 -1
  42. package/dist/esm/components/data-grid/data-grid.d.ts.map +1 -1
  43. package/dist/esm/components/data-grid/data-grid.js +329 -128
  44. package/dist/esm/components/data-grid/data-grid.js.map +1 -1
  45. package/dist/esm/components/data-grid/data-grid.test.d.ts +1 -0
  46. package/dist/esm/components/data-grid/data-grid.test.js +215 -0
  47. package/dist/esm/components/data-grid/data-grid.test.js.map +1 -0
  48. package/dist/esm/components/data-grid/index.d.ts +3 -2
  49. package/dist/esm/components/data-grid/index.js +3 -2
  50. package/dist/esm/components/data-grid/state.d.ts.map +1 -1
  51. package/dist/esm/components/data-grid/state.js +24 -7
  52. package/dist/esm/components/data-grid/state.js.map +1 -1
  53. package/dist/esm/components/data-grid/types.d.ts +34 -3
  54. package/dist/esm/components/data-grid/types.d.ts.map +1 -1
  55. package/dist/esm/components/data-grid/use-data-source.d.ts +6 -0
  56. package/dist/esm/components/data-grid/use-data-source.d.ts.map +1 -1
  57. package/dist/esm/components/data-grid/use-data-source.js +10 -2
  58. package/dist/esm/components/data-grid/use-data-source.js.map +1 -1
  59. package/dist/esm/components/tabs.d.ts +5 -1
  60. package/dist/esm/components/tabs.d.ts.map +1 -1
  61. package/dist/esm/components/tabs.js +40 -27
  62. package/dist/esm/components/tabs.js.map +1 -1
  63. package/dist/index.d.ts +3 -2
  64. package/package.json +4 -4
@@ -2,6 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_chunk = require('../../chunk-BE-pF4vm.js');
3
3
  let __state_js = require("./state.js");
4
4
  let __strings_js = require("./strings.js");
5
+ let __data_grid_sizing_js = require("./data-grid-sizing.js");
5
6
  let __data_grid_toolbar_js = require("./data-grid-toolbar.js");
6
7
  let __data_grid_js = require("./data-grid.js");
7
8
  let __use_data_source_js = require("./use-data-source.js");
@@ -102,6 +103,12 @@ Object.defineProperty(exports, 'formatGridDate', {
102
103
  return __state_js.formatGridDate;
103
104
  }
104
105
  });
106
+ Object.defineProperty(exports, 'getEffectiveMinWidth', {
107
+ enumerable: true,
108
+ get: function () {
109
+ return __data_grid_sizing_js.getEffectiveMinWidth;
110
+ }
111
+ });
105
112
  Object.defineProperty(exports, 'getSortDirection', {
106
113
  enumerable: true,
107
114
  get: function () {
@@ -126,6 +133,12 @@ Object.defineProperty(exports, 'isColumnVisible', {
126
133
  return __state_js.isColumnVisible;
127
134
  }
128
135
  });
136
+ Object.defineProperty(exports, 'isDataGridInteractiveRowClickTarget', {
137
+ enumerable: true,
138
+ get: function () {
139
+ return __data_grid_js.isDataGridInteractiveRowClickTarget;
140
+ }
141
+ });
129
142
  Object.defineProperty(exports, 'paginateRows', {
130
143
  enumerable: true,
131
144
  get: function () {
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","names":[],"sources":["../../../src/components/data-grid/state.ts"],"mappings":";;;cAaa,gBAAA,EAAkB,iBAAA;AAAA,cAClB,eAAA,EAAiB,sBAAA;AAAA,cAIjB,kBAAA,EAAoB,uBAAA;;;;;AAJjC;;;;;AAIA;;;;;AAoBA;iBAAgB,0BAAA,CACd,OAAA,WAAkB,iBAAA,UACjB,aAAA;AAAA,iBAwBa,kBAAA,MAAA,CACd,GAAA,EAAK,iBAAA,CAAkB,IAAA,GACvB,GAAA,EAAK,IAAA;AAAA,iBAOS,kBAAA,CACd,GAAA,EAAK,iBAAA,OACL,WAAA;AAAA,iBAKc,eAAA,CACd,QAAA,UACA,UAAA,EAAY,MAAA;AAAA,iBAOE,UAAA,CACd,KAAA,EAAO,iBAAA,EACP,QAAA,UACA,SAAA,YACC,iBAAA;AAAA,iBAiBa,gBAAA,CACd,KAAA,EAAO,iBAAA,EACP,QAAA;AAAA,iBAMc,YAAA,CACd,KAAA,EAAO,iBAAA,EACP,QAAA;AAAA,iBAmBc,kBAAA,MAAA,CACd,SAAA,EAAW,iBAAA,EACX,OAAA,WAAkB,iBAAA,CAAkB,IAAA,QACjC,CAAA,EAAG,IAAA,EAAM,CAAA,EAAG,IAAA;AAAA,iBAuBD,YAAA,MAAA,CACd,IAAA,WAAe,IAAA,IACf,UAAA,EAAY,uBAAA,GACX,IAAA;AAAA,iBAKa,aAAA,CACd,SAAA,UACA,QAAA;AAAA,iBAOc,kBAAA,CACd,SAAA,EAAW,sBAAA,EACX,KAAA,UACA,IAAA,yBACA,QAAA,WACA,OAAA,WACA,SAAA,sBACC,sBAAA;AAAA,iBA2Ca,SAAA,CACd,SAAA,sBACC,sBAAA;AAAA,iBAOa,cAAA,CAAA,GAAkB,sBAAA;;;;;;;iBAYlB,eAAA,MAAA,CACd,GAAA,EAAK,IAAA,EACL,KAAA,UACA,OAAA,WAAkB,iBAAA,CAAkB,IAAA;;;;;AAvLtC;;;;;;;iBA4MgB,gBAAA,MAAA,CACd,IAAA,WAAe,IAAA,IACf,KAAA,UACA,OAAA,WAAkB,iBAAA,CAAkB,IAAA,KACpC,QAAA,IACE,GAAA,EAAK,IAAA,EACL,KAAA,UACA,OAAA,WAAkB,iBAAA,CAAkB,IAAA,2BAE5B,IAAA;;AA9MZ;;;;iBA2NgB,gBAAA,CAAiB,KAAA,YAAiB,IAAA;;;;iBA2BlC,qBAAA,CAAsB,IAAA,EAAM,IAAA;AA7O5C;AAAA,iBA0PgB,qBAAA,CAAsB,IAAA,EAAM,IAAA;;;;;;;;;;AArO5C;;;;;;iBAwPgB,cAAA,CACd,KAAA,WACA,IAAA,EAAM,mBAAA,EACN,IAAA;EACE,UAAA,IAAc,KAAA,cAAmB,IAAA;EACjC,UAAA,GAAa,kBAAA;AAAA;EAEZ,OAAA;EAAwB,OAAA;AAAA;AAAA,iBAeb,WAAA,MAAA,CACd,IAAA,WAAe,IAAA,IACf,OAAA,WAAkB,iBAAA,CAAkB,IAAA,KACpC,QAAA"}
1
+ {"version":3,"file":"state.d.ts","names":[],"sources":["../../../src/components/data-grid/state.ts"],"mappings":";;;cAca,gBAAA,EAAkB,iBAAA;AAAA,cAClB,eAAA,EAAiB,sBAAA;AAAA,cAIjB,kBAAA,EAAoB,uBAAA;;;;;AAJjC;;;;;AAIA;;;;;AAoBA;iBAAgB,0BAAA,CACd,OAAA,WAAkB,iBAAA,UACjB,aAAA;AAAA,iBAyBa,kBAAA,MAAA,CACd,GAAA,EAAK,iBAAA,CAAkB,IAAA,GACvB,GAAA,EAAK,IAAA;AAAA,iBAOS,kBAAA,CACd,GAAA,EAAK,iBAAA,OACL,WAAA;AAAA,iBAMc,eAAA,CACd,QAAA,UACA,UAAA,EAAY,MAAA;AAAA,iBAOE,UAAA,CACd,KAAA,EAAO,iBAAA,EACP,QAAA,UACA,SAAA,YACC,iBAAA;AAAA,iBAiBa,gBAAA,CACd,KAAA,EAAO,iBAAA,EACP,QAAA;AAAA,iBAMc,YAAA,CACd,KAAA,EAAO,iBAAA,EACP,QAAA;AAAA,iBAmBc,kBAAA,MAAA,CACd,SAAA,EAAW,iBAAA,EACX,OAAA,WAAkB,iBAAA,CAAkB,IAAA,QACjC,CAAA,EAAG,IAAA,EAAM,CAAA,EAAG,IAAA;AAAA,iBAuBD,YAAA,MAAA,CACd,IAAA,WAAe,IAAA,IACf,UAAA,EAAY,uBAAA,GACX,IAAA;AAAA,iBAKa,aAAA,CACd,SAAA,UACA,QAAA;AAAA,iBAOc,kBAAA,CACd,SAAA,EAAW,sBAAA,EACX,KAAA,UACA,IAAA,yBACA,QAAA,WACA,OAAA,WACA,SAAA,sBACC,sBAAA;AAAA,iBA2Ca,SAAA,CACd,SAAA,sBACC,sBAAA;AAAA,iBAOa,cAAA,CAAA,GAAkB,sBAAA;;;;;;;iBAYlB,eAAA,MAAA,CACd,GAAA,EAAK,IAAA,EACL,KAAA,UACA,OAAA,WAAkB,iBAAA,CAAkB,IAAA;;;;;AAxLtC;;;;;;;iBA6MgB,gBAAA,MAAA,CACd,IAAA,WAAe,IAAA,IACf,KAAA,UACA,OAAA,WAAkB,iBAAA,CAAkB,IAAA,KACpC,QAAA,IACE,GAAA,EAAK,IAAA,EACL,KAAA,UACA,OAAA,WAAkB,iBAAA,CAAkB,IAAA,2BAE5B,IAAA;;AA9MZ;;;;iBA2NgB,gBAAA,CAAiB,KAAA,YAAiB,IAAA;;;;iBAyClC,qBAAA,CAAsB,IAAA,EAAM,IAAA;AA3P5C;AAAA,iBAwQgB,qBAAA,CAAsB,IAAA,EAAM,IAAA;;;;;;;;;;AAnP5C;;;;;;iBAsQgB,cAAA,CACd,KAAA,WACA,IAAA,EAAM,mBAAA,EACN,IAAA;EACE,UAAA,IAAc,KAAA,cAAmB,IAAA;EACjC,UAAA,GAAa,kBAAA;AAAA;EAEZ,OAAA;EAAwB,OAAA;AAAA;AAAA,iBAeb,WAAA,MAAA,CACd,IAAA,WAAe,IAAA,IACf,OAAA,WAAkB,iBAAA,CAAkB,IAAA,KACpC,QAAA"}
@@ -1,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_chunk = require('../../chunk-BE-pF4vm.js');
3
+ let __data_grid_sizing_js = require("./data-grid-sizing.js");
3
4
  let _stackframe_stack_shared_dist_utils_strings = require("@stackframe/stack-shared/dist/utils/strings");
4
5
 
5
6
  //#region src/components/data-grid/state.ts
@@ -31,7 +32,8 @@ function createDefaultDataGridState(columns) {
31
32
  const columnWidths = {};
32
33
  const columnOrder = [];
33
34
  for (const col of columns) {
34
- columnWidths[col.id] = col.width ?? 150;
35
+ const raw = col.width ?? 150;
36
+ columnWidths[col.id] = (0, __data_grid_sizing_js.clampColumnWidth)(col, raw);
35
37
  columnOrder.push(col.id);
36
38
  }
37
39
  return {
@@ -54,7 +56,7 @@ function resolveColumnValue(col, row) {
54
56
  return row[col.accessor ?? col.id];
55
57
  }
56
58
  function resolveColumnWidth(col, storedWidth) {
57
- return storedWidth ?? col.width ?? 150;
59
+ return (0, __data_grid_sizing_js.clampColumnWidth)(col, storedWidth ?? col.width ?? 150);
58
60
  }
59
61
  function isColumnVisible(columnId, visibility) {
60
62
  return visibility[columnId] !== false;
@@ -240,11 +242,21 @@ const DIVISIONS = [
240
242
  unit: "year"
241
243
  }
242
244
  ];
245
+ const relativeTimeFormatterCache = /* @__PURE__ */ new Map();
246
+ function getRelativeTimeFormatter(locale) {
247
+ const key = locale ?? "__default__";
248
+ let cached = relativeTimeFormatterCache.get(key);
249
+ if (cached == null) {
250
+ cached = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
251
+ relativeTimeFormatterCache.set(key, cached);
252
+ }
253
+ return cached;
254
+ }
243
255
  /** Default relative formatter — "1 day ago" / "in 2 hours" via
244
256
  * `Intl.RelativeTimeFormat`. Pure function of the date; does NOT
245
257
  * re-render as real time passes. */
246
258
  function defaultFormatRelative(date) {
247
- const rtf = new Intl.RelativeTimeFormat(void 0, { numeric: "auto" });
259
+ const rtf = getRelativeTimeFormatter();
248
260
  let duration = (date.getTime() - Date.now()) / 1e3;
249
261
  for (const div of DIVISIONS) {
250
262
  if (Math.abs(duration) < div.amount) return rtf.format(Math.round(duration), div.unit);
@@ -288,18 +300,23 @@ function exportToCsv(rows, columns, filename) {
288
300
  const header = columns.map((col) => typeof col.header === "string" ? col.header : col.id);
289
301
  const csvRows = rows.map((row) => columns.map((col) => {
290
302
  const val = resolveColumnValue(col, row);
291
- const formatted = col.formatValue ? col.formatValue(val, row) : String(val ?? "");
303
+ const formatted = col.formatValue ? String(col.formatValue(val, row) ?? "") : String(val ?? "");
292
304
  if (formatted.includes(",") || formatted.includes("\"") || formatted.includes("\n")) return `"${formatted.replace(/"/g, "\"\"")}"`;
293
305
  return formatted;
294
306
  }));
295
- const csvContent = [header.join(","), ...csvRows.map((row) => row.join(","))].join("\n");
307
+ const csvContent = "" + [header.join(","), ...csvRows.map((row) => row.join(","))].join("\n");
296
308
  const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
297
309
  const url = URL.createObjectURL(blob);
298
310
  const link = document.createElement("a");
299
311
  link.href = url;
300
312
  link.download = `${filename}.csv`;
301
- link.click();
302
- URL.revokeObjectURL(url);
313
+ document.body.appendChild(link);
314
+ try {
315
+ link.click();
316
+ } finally {
317
+ link.remove();
318
+ URL.revokeObjectURL(url);
319
+ }
303
320
  }
304
321
 
305
322
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"state.js","names":[],"sources":["../../../src/components/data-grid/state.ts"],"sourcesContent":["import { stringCompare } from \"@stackframe/stack-shared/dist/utils/strings\";\nimport type {\n DataGridColumnDef,\n DataGridDateDisplay,\n DataGridDateFormat,\n DataGridPaginationModel,\n DataGridSelectionModel,\n DataGridSortModel,\n DataGridState,\n} from \"./types\";\n\n// ─── Default state ───────────────────────────────────────────────────\n\nexport const EMPTY_SORT_MODEL: DataGridSortModel = [];\nexport const EMPTY_SELECTION: DataGridSelectionModel = {\n selectedIds: new Set(),\n anchorId: null,\n};\nexport const DEFAULT_PAGINATION: DataGridPaginationModel = {\n pageIndex: 0,\n pageSize: 50,\n};\n\n/**\n * Build the initial `DataGridState` for a set of columns. Pass this as the\n * lazy initializer to `useState` — NEVER hand-assemble the state object.\n *\n * ```tsx\n * const [gridState, setGridState] = React.useState(() =>\n * createDefaultDataGridState(columns)\n * );\n * ```\n *\n * `columns` must be defined BEFORE this call (obvious, but a common TDZ\n * mistake: if you declare columns after the `useState`, you'll crash on\n * the first render). Keep the columns reference stable across renders\n * (define them outside the component or wrap in `React.useMemo`).\n */\nexport function createDefaultDataGridState(\n columns: readonly DataGridColumnDef<any>[],\n): DataGridState {\n const columnWidths: Record<string, number> = {};\n const columnOrder: string[] = [];\n\n for (const col of columns) {\n columnWidths[col.id] = col.width ?? 150;\n columnOrder.push(col.id);\n }\n\n return {\n sorting: EMPTY_SORT_MODEL,\n columnVisibility: {},\n columnWidths,\n columnPinning: { left: [], right: [] },\n columnOrder,\n pagination: DEFAULT_PAGINATION,\n selection: EMPTY_SELECTION,\n dateDisplay: \"relative\",\n quickSearch: \"\",\n };\n}\n\n// ─── Column helpers ──────────────────────────────────────────────────\n\nexport function resolveColumnValue<TRow>(\n col: DataGridColumnDef<TRow>,\n row: TRow,\n): unknown {\n if (typeof col.accessor === \"function\") return col.accessor(row);\n const key = (col.accessor ?? col.id) as keyof TRow;\n return row[key];\n}\n\nexport function resolveColumnWidth(\n col: DataGridColumnDef<any>,\n storedWidth: number | undefined,\n): number {\n return storedWidth ?? col.width ?? 150;\n}\n\nexport function isColumnVisible(\n columnId: string,\n visibility: Record<string, boolean>,\n): boolean {\n return visibility[columnId] !== false;\n}\n\n// ─── Sort helpers ────────────────────────────────────────────────────\n\nexport function toggleSort(\n model: DataGridSortModel,\n columnId: string,\n multiSort: boolean,\n): DataGridSortModel {\n const existing = model.find((s) => s.columnId === columnId);\n\n if (!existing) {\n const item = { columnId, direction: \"asc\" as const };\n return multiSort ? [...model, item] : [item];\n }\n\n if (existing.direction === \"asc\") {\n const updated = { columnId, direction: \"desc\" as const };\n return model.map((s) => (s.columnId === columnId ? updated : s));\n }\n\n // desc → remove\n return model.filter((s) => s.columnId !== columnId);\n}\n\nexport function getSortDirection(\n model: DataGridSortModel,\n columnId: string,\n): false | \"asc\" | \"desc\" {\n const item = model.find((s) => s.columnId === columnId);\n return item ? item.direction : false;\n}\n\nexport function getSortIndex(\n model: DataGridSortModel,\n columnId: string,\n): number | null {\n if (model.length <= 1) return null;\n const idx = model.findIndex((s) => s.columnId === columnId);\n return idx >= 0 ? idx + 1 : null;\n}\n\n// ─── Default sort comparator ─────────────────────────────────────────\n\nfunction defaultComparator(a: unknown, b: unknown): number {\n if (a == null && b == null) return 0;\n if (a == null) return -1;\n if (b == null) return 1;\n\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();\n return stringCompare(String(a), String(b));\n}\n\nexport function buildRowComparator<TRow>(\n sortModel: DataGridSortModel,\n columns: readonly DataGridColumnDef<TRow>[],\n): ((a: TRow, b: TRow) => number) | null {\n if (sortModel.length === 0) return null;\n\n const colMap = new Map(columns.map((c) => [c.id, c]));\n\n return (a, b) => {\n for (const { columnId, direction } of sortModel) {\n const col = colMap.get(columnId);\n if (!col) continue;\n\n const va = resolveColumnValue(col, a);\n const vb = resolveColumnValue(col, b);\n const cmp = col.sortComparator\n ? col.sortComparator(va, vb)\n : defaultComparator(va, vb);\n if (cmp !== 0) return direction === \"asc\" ? cmp : -cmp;\n }\n return 0;\n };\n}\n\n// ─── Pagination helpers ──────────────────────────────────────────────\n\nexport function paginateRows<TRow>(\n rows: readonly TRow[],\n pagination: DataGridPaginationModel,\n): TRow[] {\n const start = pagination.pageIndex * pagination.pageSize;\n return rows.slice(start, start + pagination.pageSize) as TRow[];\n}\n\nexport function getTotalPages(\n totalRows: number,\n pageSize: number,\n): number {\n return Math.max(1, Math.ceil(totalRows / pageSize));\n}\n\n// ─── Selection helpers ───────────────────────────────────────────────\n\nexport function toggleRowSelection(\n selection: DataGridSelectionModel,\n rowId: string,\n mode: \"single\" | \"multiple\",\n shiftKey: boolean,\n ctrlKey: boolean,\n allRowIds: readonly string[],\n): DataGridSelectionModel {\n if (mode === \"single\") {\n const isSelected = selection.selectedIds.has(rowId);\n return {\n selectedIds: isSelected ? new Set() : new Set([rowId]),\n anchorId: isSelected ? null : rowId,\n };\n }\n\n // Multiple mode\n if (shiftKey && selection.anchorId != null) {\n const anchorIdx = allRowIds.indexOf(selection.anchorId);\n const currentIdx = allRowIds.indexOf(rowId);\n if (anchorIdx >= 0 && currentIdx >= 0) {\n const start = Math.min(anchorIdx, currentIdx);\n const end = Math.max(anchorIdx, currentIdx);\n const rangeIds = allRowIds.slice(start, end + 1);\n\n const next = ctrlKey ? new Set(selection.selectedIds) : new Set<string>();\n for (const id of rangeIds) next.add(id);\n\n return { selectedIds: next, anchorId: selection.anchorId };\n }\n }\n\n if (ctrlKey) {\n // Toggle single in multi mode\n const next = new Set(selection.selectedIds);\n if (next.has(rowId)) {\n next.delete(rowId);\n } else {\n next.add(rowId);\n }\n return { selectedIds: next, anchorId: rowId };\n }\n\n // Plain click in multi mode — select only this row\n return {\n selectedIds: new Set([rowId]),\n anchorId: rowId,\n };\n}\n\nexport function selectAll(\n allRowIds: readonly string[],\n): DataGridSelectionModel {\n return {\n selectedIds: new Set(allRowIds),\n anchorId: null,\n };\n}\n\nexport function clearSelection(): DataGridSelectionModel {\n return EMPTY_SELECTION;\n}\n\n// ─── Quick search ────────────────────────────────────────────────────\n\n/** Default row matcher used by `applyQuickSearch`. Case-insensitive\n * substring match across every column's resolved cell value. Columns\n * with `null` / `undefined` values are skipped. The query is expected\n * to be pre-trimmed and lowercased by `applyQuickSearch` — this helper\n * does NOT trim or lowercase it again, so if you wire it up yourself,\n * do that first. */\nexport function defaultMatchRow<TRow>(\n row: TRow,\n query: string,\n columns: readonly DataGridColumnDef<TRow>[],\n): boolean {\n for (const col of columns) {\n const v = resolveColumnValue(col, row);\n if (v == null) continue;\n if (String(v).toLowerCase().includes(query)) return true;\n }\n return false;\n}\n\n/** Client-side quick-search filter. Returns the original array\n * reference when `query` is empty, so calling this in a hot `useMemo`\n * is cheap in the common \"no search\" case.\n *\n * Used by `useDataSource` in client mode. Exported so consumers driving\n * the grid manually (or doing their own pre-filtering before feeding\n * rows to an async data source) can stay consistent with the built-in\n * search behaviour.\n *\n * Override `matchRow` for custom matching logic — e.g. fuzzy matching,\n * field-specific weighting, or skipping some columns. */\nexport function applyQuickSearch<TRow>(\n rows: readonly TRow[],\n query: string,\n columns: readonly DataGridColumnDef<TRow>[],\n matchRow: (\n row: TRow,\n query: string,\n columns: readonly DataGridColumnDef<TRow>[],\n ) => boolean = defaultMatchRow,\n): readonly TRow[] {\n const trimmed = query.trim().toLowerCase();\n if (!trimmed) return rows;\n return rows.filter((r) => matchRow(r, trimmed, columns));\n}\n\n// ─── Date helpers ────────────────────────────────────────────────────\n\n/** Parse a raw cell value into a `Date`. Returns `null` for nullish,\n * unparseable, or invalid dates. Accepts strings (including ISO and\n * \"YYYY-MM-DD HH:MM:SS\"-style ClickHouse output), numbers (ms since\n * epoch), and `Date` instances. For truly weird formats, override via\n * `col.parseValue`. */\nexport function defaultParseDate(value: unknown): Date | null {\n if (value == null) return null;\n if (value instanceof Date) return isNaN(value.getTime()) ? null : value;\n if (typeof value === \"number\") {\n const d = new Date(value);\n return isNaN(d.getTime()) ? null : d;\n }\n if (typeof value === \"string\") {\n const d = new Date(value);\n return isNaN(d.getTime()) ? null : d;\n }\n return null;\n}\n\nconst DIVISIONS: Array<{ amount: number; unit: Intl.RelativeTimeFormatUnit }> = [\n { amount: 60, unit: \"second\" },\n { amount: 60, unit: \"minute\" },\n { amount: 24, unit: \"hour\" },\n { amount: 7, unit: \"day\" },\n { amount: 4.34524, unit: \"week\" },\n { amount: 12, unit: \"month\" },\n { amount: Number.POSITIVE_INFINITY, unit: \"year\" },\n];\n\n/** Default relative formatter — \"1 day ago\" / \"in 2 hours\" via\n * `Intl.RelativeTimeFormat`. Pure function of the date; does NOT\n * re-render as real time passes. */\nexport function defaultFormatRelative(date: Date): string {\n const rtf = new Intl.RelativeTimeFormat(undefined, { numeric: \"auto\" });\n let duration = (date.getTime() - Date.now()) / 1000;\n for (const div of DIVISIONS) {\n if (Math.abs(duration) < div.amount) {\n return rtf.format(Math.round(duration), div.unit);\n }\n duration /= div.amount;\n }\n return rtf.format(Math.round(duration), \"year\");\n}\n\n/** Default absolute formatter — full locale date + time. */\nexport function defaultFormatAbsolute(date: Date): string {\n return date.toLocaleString();\n}\n\n/** Format a raw cell value for display in a `date` / `dateTime` column.\n * Returns both the inline display string and the tooltip string (which\n * is always the absolute form so users can read the exact datetime).\n *\n * Used internally by the grid's default date cell renderer, and exported\n * so consumers writing a custom `renderCell` for a date column can stay\n * visually consistent with the built-in behaviour.\n *\n * ```tsx\n * renderCell: ({ value, dateDisplay }) => {\n * const { display, tooltip } = formatGridDate(value, dateDisplay);\n * if (!display) return <span className=\"text-muted-foreground/40\">—</span>;\n * return <span title={tooltip ?? undefined}>{display}</span>;\n * }\n * ``` */\nexport function formatGridDate(\n value: unknown,\n mode: DataGridDateDisplay,\n opts?: {\n parseValue?: (value: unknown) => Date | null;\n dateFormat?: DataGridDateFormat;\n },\n): { display: string | null; tooltip: string | null } {\n const parse = opts?.parseValue ?? defaultParseDate;\n const date = parse(value);\n if (!date) return { display: null, tooltip: null };\n\n const relative = opts?.dateFormat?.relative ?? defaultFormatRelative;\n const absolute = opts?.dateFormat?.absolute ?? defaultFormatAbsolute;\n\n const tooltip = absolute(date);\n const display = mode === \"relative\" ? relative(date) : tooltip;\n return { display, tooltip };\n}\n\n// ─── CSV Export ──────────────────────────────────────────────────────\n\nexport function exportToCsv<TRow>(\n rows: readonly TRow[],\n columns: readonly DataGridColumnDef<TRow>[],\n filename: string,\n): void {\n const header = columns.map((col) =>\n typeof col.header === \"string\" ? col.header : col.id,\n );\n\n const csvRows = rows.map((row) =>\n columns.map((col) => {\n const val = resolveColumnValue(col, row);\n const formatted = col.formatValue ? col.formatValue(val, row) : String(val ?? \"\");\n // Escape CSV special characters\n if (formatted.includes(\",\") || formatted.includes('\"') || formatted.includes(\"\\n\")) {\n return `\"${formatted.replace(/\"/g, '\"\"')}\"`;\n }\n return formatted;\n }),\n );\n\n const csvContent = [\n header.join(\",\"),\n ...csvRows.map((row) => row.join(\",\")),\n ].join(\"\\n\");\n\n const blob = new Blob([csvContent], { type: \"text/csv;charset=utf-8;\" });\n const url = URL.createObjectURL(blob);\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = `${filename}.csv`;\n link.click();\n URL.revokeObjectURL(url);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"state.js","names":[],"sources":["../../../src/components/data-grid/state.ts"],"sourcesContent":["import { stringCompare } from \"@stackframe/stack-shared/dist/utils/strings\";\nimport { clampColumnWidth } from \"./data-grid-sizing\";\nimport type {\n DataGridColumnDef,\n DataGridDateDisplay,\n DataGridDateFormat,\n DataGridPaginationModel,\n DataGridSelectionModel,\n DataGridSortModel,\n DataGridState,\n} from \"./types\";\n\n// ─── Default state ───────────────────────────────────────────────────\n\nexport const EMPTY_SORT_MODEL: DataGridSortModel = [];\nexport const EMPTY_SELECTION: DataGridSelectionModel = {\n selectedIds: new Set(),\n anchorId: null,\n};\nexport const DEFAULT_PAGINATION: DataGridPaginationModel = {\n pageIndex: 0,\n pageSize: 50,\n};\n\n/**\n * Build the initial `DataGridState` for a set of columns. Pass this as the\n * lazy initializer to `useState` — NEVER hand-assemble the state object.\n *\n * ```tsx\n * const [gridState, setGridState] = React.useState(() =>\n * createDefaultDataGridState(columns)\n * );\n * ```\n *\n * `columns` must be defined BEFORE this call (obvious, but a common TDZ\n * mistake: if you declare columns after the `useState`, you'll crash on\n * the first render). Keep the columns reference stable across renders\n * (define them outside the component or wrap in `React.useMemo`).\n */\nexport function createDefaultDataGridState(\n columns: readonly DataGridColumnDef<any>[],\n): DataGridState {\n const columnWidths: Record<string, number> = {};\n const columnOrder: string[] = [];\n\n for (const col of columns) {\n const raw = col.width ?? 150;\n columnWidths[col.id] = clampColumnWidth(col, raw);\n columnOrder.push(col.id);\n }\n\n return {\n sorting: EMPTY_SORT_MODEL,\n columnVisibility: {},\n columnWidths,\n columnPinning: { left: [], right: [] },\n columnOrder,\n pagination: DEFAULT_PAGINATION,\n selection: EMPTY_SELECTION,\n dateDisplay: \"relative\",\n quickSearch: \"\",\n };\n}\n\n// ─── Column helpers ──────────────────────────────────────────────────\n\nexport function resolveColumnValue<TRow>(\n col: DataGridColumnDef<TRow>,\n row: TRow,\n): unknown {\n if (typeof col.accessor === \"function\") return col.accessor(row);\n const key = (col.accessor ?? col.id) as keyof TRow;\n return row[key];\n}\n\nexport function resolveColumnWidth(\n col: DataGridColumnDef<any>,\n storedWidth: number | undefined,\n): number {\n const raw = storedWidth ?? col.width ?? 150;\n return clampColumnWidth(col, raw);\n}\n\nexport function isColumnVisible(\n columnId: string,\n visibility: Record<string, boolean>,\n): boolean {\n return visibility[columnId] !== false;\n}\n\n// ─── Sort helpers ────────────────────────────────────────────────────\n\nexport function toggleSort(\n model: DataGridSortModel,\n columnId: string,\n multiSort: boolean,\n): DataGridSortModel {\n const existing = model.find((s) => s.columnId === columnId);\n\n if (!existing) {\n const item = { columnId, direction: \"asc\" as const };\n return multiSort ? [...model, item] : [item];\n }\n\n if (existing.direction === \"asc\") {\n const updated = { columnId, direction: \"desc\" as const };\n return model.map((s) => (s.columnId === columnId ? updated : s));\n }\n\n // desc → remove\n return model.filter((s) => s.columnId !== columnId);\n}\n\nexport function getSortDirection(\n model: DataGridSortModel,\n columnId: string,\n): false | \"asc\" | \"desc\" {\n const item = model.find((s) => s.columnId === columnId);\n return item ? item.direction : false;\n}\n\nexport function getSortIndex(\n model: DataGridSortModel,\n columnId: string,\n): number | null {\n if (model.length <= 1) return null;\n const idx = model.findIndex((s) => s.columnId === columnId);\n return idx >= 0 ? idx + 1 : null;\n}\n\n// ─── Default sort comparator ─────────────────────────────────────────\n\nfunction defaultComparator(a: unknown, b: unknown): number {\n if (a == null && b == null) return 0;\n if (a == null) return -1;\n if (b == null) return 1;\n\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();\n return stringCompare(String(a), String(b));\n}\n\nexport function buildRowComparator<TRow>(\n sortModel: DataGridSortModel,\n columns: readonly DataGridColumnDef<TRow>[],\n): ((a: TRow, b: TRow) => number) | null {\n if (sortModel.length === 0) return null;\n\n const colMap = new Map(columns.map((c) => [c.id, c]));\n\n return (a, b) => {\n for (const { columnId, direction } of sortModel) {\n const col = colMap.get(columnId);\n if (!col) continue;\n\n const va = resolveColumnValue(col, a);\n const vb = resolveColumnValue(col, b);\n const cmp = col.sortComparator\n ? col.sortComparator(va, vb)\n : defaultComparator(va, vb);\n if (cmp !== 0) return direction === \"asc\" ? cmp : -cmp;\n }\n return 0;\n };\n}\n\n// ─── Pagination helpers ──────────────────────────────────────────────\n\nexport function paginateRows<TRow>(\n rows: readonly TRow[],\n pagination: DataGridPaginationModel,\n): TRow[] {\n const start = pagination.pageIndex * pagination.pageSize;\n return rows.slice(start, start + pagination.pageSize) as TRow[];\n}\n\nexport function getTotalPages(\n totalRows: number,\n pageSize: number,\n): number {\n return Math.max(1, Math.ceil(totalRows / pageSize));\n}\n\n// ─── Selection helpers ───────────────────────────────────────────────\n\nexport function toggleRowSelection(\n selection: DataGridSelectionModel,\n rowId: string,\n mode: \"single\" | \"multiple\",\n shiftKey: boolean,\n ctrlKey: boolean,\n allRowIds: readonly string[],\n): DataGridSelectionModel {\n if (mode === \"single\") {\n const isSelected = selection.selectedIds.has(rowId);\n return {\n selectedIds: isSelected ? new Set() : new Set([rowId]),\n anchorId: isSelected ? null : rowId,\n };\n }\n\n // Multiple mode\n if (shiftKey && selection.anchorId != null) {\n const anchorIdx = allRowIds.indexOf(selection.anchorId);\n const currentIdx = allRowIds.indexOf(rowId);\n if (anchorIdx >= 0 && currentIdx >= 0) {\n const start = Math.min(anchorIdx, currentIdx);\n const end = Math.max(anchorIdx, currentIdx);\n const rangeIds = allRowIds.slice(start, end + 1);\n\n const next = ctrlKey ? new Set(selection.selectedIds) : new Set<string>();\n for (const id of rangeIds) next.add(id);\n\n return { selectedIds: next, anchorId: selection.anchorId };\n }\n }\n\n if (ctrlKey) {\n // Toggle single in multi mode\n const next = new Set(selection.selectedIds);\n if (next.has(rowId)) {\n next.delete(rowId);\n } else {\n next.add(rowId);\n }\n return { selectedIds: next, anchorId: rowId };\n }\n\n // Plain click in multi mode — select only this row\n return {\n selectedIds: new Set([rowId]),\n anchorId: rowId,\n };\n}\n\nexport function selectAll(\n allRowIds: readonly string[],\n): DataGridSelectionModel {\n return {\n selectedIds: new Set(allRowIds),\n anchorId: null,\n };\n}\n\nexport function clearSelection(): DataGridSelectionModel {\n return EMPTY_SELECTION;\n}\n\n// ─── Quick search ────────────────────────────────────────────────────\n\n/** Default row matcher used by `applyQuickSearch`. Case-insensitive\n * substring match across every column's resolved cell value. Columns\n * with `null` / `undefined` values are skipped. The query is expected\n * to be pre-trimmed and lowercased by `applyQuickSearch` — this helper\n * does NOT trim or lowercase it again, so if you wire it up yourself,\n * do that first. */\nexport function defaultMatchRow<TRow>(\n row: TRow,\n query: string,\n columns: readonly DataGridColumnDef<TRow>[],\n): boolean {\n for (const col of columns) {\n const v = resolveColumnValue(col, row);\n if (v == null) continue;\n if (String(v).toLowerCase().includes(query)) return true;\n }\n return false;\n}\n\n/** Client-side quick-search filter. Returns the original array\n * reference when `query` is empty, so calling this in a hot `useMemo`\n * is cheap in the common \"no search\" case.\n *\n * Used by `useDataSource` in client mode. Exported so consumers driving\n * the grid manually (or doing their own pre-filtering before feeding\n * rows to an async data source) can stay consistent with the built-in\n * search behaviour.\n *\n * Override `matchRow` for custom matching logic — e.g. fuzzy matching,\n * field-specific weighting, or skipping some columns. */\nexport function applyQuickSearch<TRow>(\n rows: readonly TRow[],\n query: string,\n columns: readonly DataGridColumnDef<TRow>[],\n matchRow: (\n row: TRow,\n query: string,\n columns: readonly DataGridColumnDef<TRow>[],\n ) => boolean = defaultMatchRow,\n): readonly TRow[] {\n const trimmed = query.trim().toLowerCase();\n if (!trimmed) return rows;\n return rows.filter((r) => matchRow(r, trimmed, columns));\n}\n\n// ─── Date helpers ────────────────────────────────────────────────────\n\n/** Parse a raw cell value into a `Date`. Returns `null` for nullish,\n * unparseable, or invalid dates. Accepts strings (including ISO and\n * \"YYYY-MM-DD HH:MM:SS\"-style ClickHouse output), numbers (ms since\n * epoch), and `Date` instances. For truly weird formats, override via\n * `col.parseValue`. */\nexport function defaultParseDate(value: unknown): Date | null {\n if (value == null) return null;\n if (value instanceof Date) return isNaN(value.getTime()) ? null : value;\n if (typeof value === \"number\") {\n const d = new Date(value);\n return isNaN(d.getTime()) ? null : d;\n }\n if (typeof value === \"string\") {\n const d = new Date(value);\n return isNaN(d.getTime()) ? null : d;\n }\n return null;\n}\n\nconst DIVISIONS: Array<{ amount: number; unit: Intl.RelativeTimeFormatUnit }> = [\n { amount: 60, unit: \"second\" },\n { amount: 60, unit: \"minute\" },\n { amount: 24, unit: \"hour\" },\n { amount: 7, unit: \"day\" },\n { amount: 4.34524, unit: \"week\" },\n { amount: 12, unit: \"month\" },\n { amount: Number.POSITIVE_INFINITY, unit: \"year\" },\n];\n\n// Memoized per-locale formatter. `Intl.RelativeTimeFormat` construction\n// shows up as a real cost in flamegraphs for grids with many date cells,\n// so cache one instance per locale (\"undefined\" = default).\nconst relativeTimeFormatterCache = new Map<string, Intl.RelativeTimeFormat>();\nfunction getRelativeTimeFormatter(locale?: string): Intl.RelativeTimeFormat {\n const key = locale ?? \"__default__\";\n let cached = relativeTimeFormatterCache.get(key);\n if (cached == null) {\n cached = new Intl.RelativeTimeFormat(locale, { numeric: \"auto\" });\n relativeTimeFormatterCache.set(key, cached);\n }\n return cached;\n}\n\n/** Default relative formatter — \"1 day ago\" / \"in 2 hours\" via\n * `Intl.RelativeTimeFormat`. Pure function of the date; does NOT\n * re-render as real time passes. */\nexport function defaultFormatRelative(date: Date): string {\n const rtf = getRelativeTimeFormatter();\n let duration = (date.getTime() - Date.now()) / 1000;\n for (const div of DIVISIONS) {\n if (Math.abs(duration) < div.amount) {\n return rtf.format(Math.round(duration), div.unit);\n }\n duration /= div.amount;\n }\n return rtf.format(Math.round(duration), \"year\");\n}\n\n/** Default absolute formatter — full locale date + time. */\nexport function defaultFormatAbsolute(date: Date): string {\n return date.toLocaleString();\n}\n\n/** Format a raw cell value for display in a `date` / `dateTime` column.\n * Returns both the inline display string and the tooltip string (which\n * is always the absolute form so users can read the exact datetime).\n *\n * Used internally by the grid's default date cell renderer, and exported\n * so consumers writing a custom `renderCell` for a date column can stay\n * visually consistent with the built-in behaviour.\n *\n * ```tsx\n * renderCell: ({ value, dateDisplay }) => {\n * const { display, tooltip } = formatGridDate(value, dateDisplay);\n * if (!display) return <span className=\"text-muted-foreground/40\">—</span>;\n * return <span title={tooltip ?? undefined}>{display}</span>;\n * }\n * ``` */\nexport function formatGridDate(\n value: unknown,\n mode: DataGridDateDisplay,\n opts?: {\n parseValue?: (value: unknown) => Date | null;\n dateFormat?: DataGridDateFormat;\n },\n): { display: string | null; tooltip: string | null } {\n const parse = opts?.parseValue ?? defaultParseDate;\n const date = parse(value);\n if (!date) return { display: null, tooltip: null };\n\n const relative = opts?.dateFormat?.relative ?? defaultFormatRelative;\n const absolute = opts?.dateFormat?.absolute ?? defaultFormatAbsolute;\n\n const tooltip = absolute(date);\n const display = mode === \"relative\" ? relative(date) : tooltip;\n return { display, tooltip };\n}\n\n// ─── CSV Export ──────────────────────────────────────────────────────\n\nexport function exportToCsv<TRow>(\n rows: readonly TRow[],\n columns: readonly DataGridColumnDef<TRow>[],\n filename: string,\n): void {\n const header = columns.map((col) =>\n typeof col.header === \"string\" ? col.header : col.id,\n );\n\n const csvRows = rows.map((row) =>\n columns.map((col) => {\n const val = resolveColumnValue(col, row);\n // Coerce through `?? \"\"` so a `formatValue` that returns undefined/null\n // (easy to do from a ternary) doesn't crash `.includes` below.\n // The type says `formatValue` returns string, but a consumer can\n // easily return undefined/null from a ternary. Guard at runtime.\n const formatted = col.formatValue\n ? String((col.formatValue(val, row) as string | null | undefined) ?? \"\")\n : String(val ?? \"\");\n // Escape CSV special characters\n if (formatted.includes(\",\") || formatted.includes('\"') || formatted.includes(\"\\n\")) {\n return `\"${formatted.replace(/\"/g, '\"\"')}\"`;\n }\n return formatted;\n }),\n );\n\n // Prepend a UTF-8 BOM so Excel (Windows) opens the CSV as UTF-8 instead of\n // falling back to latin-1 and mangling every display name with a non-ascii\n // character.\n const csvContent = \"\\ufeff\" + [\n header.join(\",\"),\n ...csvRows.map((row) => row.join(\",\")),\n ].join(\"\\n\");\n\n const blob = new Blob([csvContent], { type: \"text/csv;charset=utf-8;\" });\n const url = URL.createObjectURL(blob);\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = `${filename}.csv`;\n // Safari / older Firefox need the link in the DOM to honour `.click()`.\n document.body.appendChild(link);\n try {\n link.click();\n } finally {\n link.remove();\n URL.revokeObjectURL(url);\n }\n}\n"],"mappings":"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"}
@@ -59,6 +59,12 @@ type DataGridColumnDef<TRow> = {
59
59
  align?: DataGridColumnAlign; /** Column type affects default sorting. */
60
60
  type?: DataGridColumnType; /** For `singleSelect` type — available value options. */
61
61
  valueOptions?: readonly DataGridSelectOption[];
62
+ /** How cell content handles overflow.
63
+ * - `"truncate"` (default): single-line with text-overflow ellipsis.
64
+ * - `"wrap"`: content wraps naturally. When combined with
65
+ * `rowHeight="auto"` on the grid, the row grows to fit. With a
66
+ * fixed `rowHeight`, wrapped content is clipped at the row boundary. */
67
+ cellOverflow?: "truncate" | "wrap";
62
68
  /** Custom sort comparator. Receives two resolved cell values.
63
69
  * Return negative if a < b, positive if a > b, 0 if equal. */
64
70
  sortComparator?: (a: unknown, b: unknown) => number;
@@ -171,11 +177,36 @@ type DataGridProps<TRow> = {
171
177
  * "paginated". */
172
178
  paginationMode?: "paginated" | "infinite"; /** Selection behaviour. Defaults to "none". */
173
179
  selectionMode?: DataGridSelectionMode; /** Whether columns can be resized by dragging. Defaults to true. */
174
- resizable?: boolean; /** Row height in pixels. Defaults to 44. */
175
- rowHeight?: number; /** Header row height in pixels. Defaults to 44. */
180
+ resizable?: boolean;
181
+ /** Row height in pixels, or `"auto"` for dynamic measurement.
182
+ *
183
+ * - **number** (default `44`): every row is exactly this tall.
184
+ * Fast and predictable; content that overflows is clipped.
185
+ * - **`"auto"`**: each row is measured by the browser after render
186
+ * and the virtualizer adjusts positions accordingly. Columns
187
+ * with `cellOverflow: "wrap"` will push the row taller; columns
188
+ * with `cellOverflow: "truncate"` (or the default) stay
189
+ * single-line. Use `estimatedRowHeight` to reduce scroll-jank
190
+ * when rows haven't been measured yet. */
191
+ rowHeight?: number | "auto";
192
+ /** Estimated row height used by the virtualizer when
193
+ * `rowHeight="auto"`. Better estimates = less scroll-position
194
+ * jank before rows are measured. Defaults to 44. Ignored when
195
+ * `rowHeight` is a number. */
196
+ estimatedRowHeight?: number; /** Header row height in pixels. Defaults to 44. */
176
197
  headerHeight?: number; /** Number of rows to render outside the visible area. Defaults to 5. */
177
- overscan?: number; /** Grid max height. If omitted, grid takes available space. */
198
+ overscan?: number; /** Grid max height. If omitted, grid takes available space (when `fillHeight`). */
178
199
  maxHeight?: number | string;
200
+ /**
201
+ * When `true` (default), the grid uses `h-full` and the row area scrolls inside the grid.
202
+ * When `false`, the grid is only as tall as toolbar + header + rows (`h-auto`), so sibling
203
+ * content (e.g. metadata) sits directly under the table without a large empty gap.
204
+ */
205
+ fillHeight?: boolean;
206
+ /** Top offset for the sticky toolbar + header (px or CSS string).
207
+ * Set this to the page header height so the grid chrome sticks
208
+ * below it instead of overlapping. Defaults to 0. */
209
+ stickyTop?: number | string;
179
210
  } & DataGridCallbacks<TRow> & {
180
211
  /** Custom toolbar renderer. When `false`, toolbar is hidden entirely. */toolbar?: false | ((ctx: DataGridToolbarContext<TRow>) => ReactNode);
181
212
  /** Extra content rendered inside the default toolbar row, to the left of
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/components/data-grid/types.ts"],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/components/data-grid/types.ts"],"mappings":"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"}
@@ -10,6 +10,12 @@ type UseDataSourceResult<TRow> = {
10
10
  loadMore: () => void; /** Whether there are more pages to load. */
11
11
  hasMore: boolean; /** Reload from scratch. */
12
12
  reload: () => void;
13
+ /**
14
+ * Error from the most recent async fetch, if any. Consumers should render
15
+ * an error UI and offer a `reload()` button when this is non-null. Client
16
+ * mode never sets this (no fetching). Cleared on next successful fetch.
17
+ */
18
+ error: Error | null;
13
19
  };
14
20
  /**
15
21
  * Hook that processes raw data through the grid's sort/pagination state
@@ -1 +1 @@
1
- {"version":3,"file":"use-data-source.d.ts","names":[],"sources":["../../../src/components/data-grid/use-data-source.ts"],"mappings":";;;KAiBY,mBAAA;4EAEV,IAAA,WAAe,IAAA,IAFc;EAI7B,aAAA,sBAFmB;EAInB,SAAA,WAJA;EAMA,YAAA,WAJA;EAMA,aAAA,WAFA;EAIA,QAAA;EAEA,OAAA,WAEA;EAAA,MAAA;AAAA;AAoRF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,iBAAgB,aAAA,MAAA,CAAoB,IAAA;EAClC,IAAA,YAAgB,IAAA;EAChB,UAAA,GAAa,kBAAA,CAAmB,IAAA;EAChC,OAAA,WAAkB,iBAAA,CAAkB,IAAA;EACpC,QAAA,GAAW,GAAA,EAAK,IAAA,KAAS,KAAA;EACzB,OAAA,EAAS,iBAAA;EAET,WAAA;;;EAGA,QAAA,IACE,GAAA,EAAK,IAAA,EACL,KAAA,UACA,OAAA,WAAkB,iBAAA,CAAkB,IAAA;EAEtC,UAAA,EAAY,uBAAA;EACZ,cAAA,EAAgB,0BAAA;AAAA,IACd,mBAAA,CAAoB,IAAA"}
1
+ {"version":3,"file":"use-data-source.d.ts","names":[],"sources":["../../../src/components/data-grid/use-data-source.ts"],"mappings":";;;KAiBY,mBAAA;4EAEV,IAAA,WAAe,IAAA,IAFc;EAI7B,aAAA,sBAkBY;EAhBZ,SAAA,WAJA;EAMA,YAAA,WAJA;EAMA,aAAA,WAFA;EAIA,QAAA;EAEA,OAAA,WAEA;EAAA,MAAA;EAMO;;;AAkST;;EAlSE,KAAA,EAAO,KAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkSO,aAAA,MAAA,CAAoB,IAAA;EAClC,IAAA,YAAgB,IAAA;EAChB,UAAA,GAAa,kBAAA,CAAmB,IAAA;EAChC,OAAA,WAAkB,iBAAA,CAAkB,IAAA;EACpC,QAAA,GAAW,GAAA,EAAK,IAAA,KAAS,KAAA;EACzB,OAAA,EAAS,iBAAA;EAET,WAAA;;;EAGA,QAAA,IACE,GAAA,EAAK,IAAA,EACL,KAAA,UACA,OAAA,WAAkB,iBAAA,CAAkB,IAAA;EAEtC,UAAA,EAAY,uBAAA;EACZ,cAAA,EAAgB,0BAAA;AAAA,IACd,mBAAA,CAAoB,IAAA"}
@@ -33,7 +33,8 @@ function useClientDataSource(opts) {
33
33
  isLoadingMore: false,
34
34
  loadMore: () => {},
35
35
  hasMore: false,
36
- reload: () => {}
36
+ reload: () => {},
37
+ error: null
37
38
  }), [processed]);
38
39
  }
39
40
  function useAsyncDataSource(opts) {
@@ -44,6 +45,7 @@ function useAsyncDataSource(opts) {
44
45
  const [isRefetching, setIsRefetching] = (0, react.useState)(false);
45
46
  const [isLoadingMore, setIsLoadingMore] = (0, react.useState)(false);
46
47
  const [hasMore, setHasMore] = (0, react.useState)(true);
48
+ const [error, setError] = (0, react.useState)(null);
47
49
  const cursorRef = (0, react.useRef)(void 0);
48
50
  const abortRef = (0, react.useRef)(null);
49
51
  const pageIndexRef = (0, react.useRef)(0);
@@ -77,6 +79,7 @@ function useAsyncDataSource(opts) {
77
79
  cursorRef.current = void 0;
78
80
  pageIndexRef.current = 0;
79
81
  }
82
+ setError(null);
80
83
  try {
81
84
  const gen = currentDataSource({
82
85
  sorting: currentSorting,
@@ -104,6 +107,7 @@ function useAsyncDataSource(opts) {
104
107
  } catch (err) {
105
108
  if (controller.signal.aborted) return;
106
109
  console.error("[DataGrid] Data source error:", err);
110
+ setError(err instanceof Error ? err : new Error(String(err)));
107
111
  } finally {
108
112
  if (!controller.signal.aborted) {
109
113
  setIsLoading(false);
@@ -117,6 +121,7 @@ function useAsyncDataSource(opts) {
117
121
  return () => abortRef.current?.abort();
118
122
  }, [
119
123
  fetchPage,
124
+ dataSource,
120
125
  sortingKey,
121
126
  quickSearchKey,
122
127
  pagination.pageSize
@@ -153,7 +158,8 @@ function useAsyncDataSource(opts) {
153
158
  hasMore,
154
159
  reload: (0, react.useCallback)(() => {
155
160
  fetchPage(false).catch(() => {});
156
- }, [fetchPage])
161
+ }, [fetchPage]),
162
+ error
157
163
  };
158
164
  }
159
165
  const NOOP_DATA_SOURCE = async function* () {};
@@ -211,6 +217,8 @@ const NOOP_GET_ROW_ID = () => "";
211
217
  function useDataSource(opts) {
212
218
  const { data, dataSource, columns, getRowId, sorting, quickSearch, matchRow = __state_js.defaultMatchRow, pagination, paginationMode } = opts;
213
219
  const isClientMode = data != null && !dataSource;
220
+ if (process.env.NODE_ENV !== "production" && data == null && dataSource == null) console.warn("[useDataSource] neither `data` nor `dataSource` was provided — the grid will render empty indefinitely. Pass one or the other.");
221
+ if (process.env.NODE_ENV !== "production" && isClientMode && paginationMode === "infinite" && data.length > 0) console.warn("[useDataSource] `paginationMode: \"infinite\"` with a `data` array skips pagination entirely. Prefer `\"client\"` for in-memory lists or `\"server\"` + a `dataSource` generator for real paging.");
214
222
  const clientResult = useClientDataSource({
215
223
  data: data ?? [],
216
224
  columns,
@@ -1 +1 @@
1
- {"version":3,"file":"use-data-source.js","names":["defaultMatchRow"],"sources":["../../../src/components/data-grid/use-data-source.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type {\n DataGridColumnDef,\n DataGridDataSource,\n DataGridFetchParams,\n DataGridDataPaginationMode,\n DataGridPaginationModel,\n DataGridSortModel,\n RowId,\n} from \"./types\";\nimport {\n applyQuickSearch,\n buildRowComparator,\n defaultMatchRow,\n paginateRows,\n} from \"./state\";\n\nexport type UseDataSourceResult<TRow> = {\n /** All rows currently loaded (for infinite mode, the accumulated set). */\n rows: readonly TRow[];\n /** Total row count if known. */\n totalRowCount: number | undefined;\n /** Whether the initial load is in progress (no data at all yet). */\n isLoading: boolean;\n /** Whether a background refetch is happening (data already shown). */\n isRefetching: boolean;\n /** Whether more rows are being fetched (infinite scroll). */\n isLoadingMore: boolean;\n /** Request the next page (infinite scroll). */\n loadMore: () => void;\n /** Whether there are more pages to load. */\n hasMore: boolean;\n /** Reload from scratch. */\n reload: () => void;\n};\n\n// ─── Client-side hook ────────────────────────────────────────────────\n// Memoised so resize / selection / other unrelated state changes\n// don't recompute or create new array references.\n\nfunction useClientDataSource<TRow>(opts: {\n data: readonly TRow[];\n columns: readonly DataGridColumnDef<TRow>[];\n sorting: DataGridSortModel;\n quickSearch: string;\n matchRow: (\n row: TRow,\n query: string,\n columns: readonly DataGridColumnDef<TRow>[],\n ) => boolean;\n pagination: DataGridPaginationModel;\n paginationMode: DataGridDataPaginationMode;\n}): UseDataSourceResult<TRow> {\n const { data, columns, sorting, quickSearch, matchRow, pagination, paginationMode } = opts;\n\n // Stable serialised keys so useMemo only fires on real changes\n const sortingKey = JSON.stringify(sorting);\n\n const processed = useMemo(() => {\n // Quick search is applied FIRST, on the full input. If nothing is\n // typed this is a zero-cost no-op (applyQuickSearch returns the\n // original array reference). Sort and paginate operate on the\n // already-filtered set so the result counts are search-aware.\n const searched = applyQuickSearch(data, quickSearch, columns, matchRow);\n const comparator = buildRowComparator(sorting, columns);\n const sorted = comparator ? [...searched].sort(comparator) : searched;\n const totalRowCount = sorted.length;\n const paged =\n paginationMode === \"client\"\n ? paginateRows(sorted as readonly TRow[], pagination)\n : sorted;\n return { rows: paged, totalRowCount };\n }, [data, sortingKey, quickSearch, matchRow, pagination.pageIndex, pagination.pageSize, paginationMode, columns]);\n\n return useMemo(() => ({\n rows: processed.rows,\n totalRowCount: processed.totalRowCount,\n isLoading: false,\n isRefetching: false,\n isLoadingMore: false,\n loadMore: () => {},\n hasMore: false,\n reload: () => {},\n }), [processed]);\n}\n\n// ─── Async data source hook ──────────────────────────────────────────\n// Key behaviour: when refetching (sort change), we keep showing the old\n// rows and set `isRefetching` instead of `isLoading`. This avoids the\n// jarring flash-to-skeleton on every sort toggle.\n\nfunction useAsyncDataSource<TRow>(opts: {\n dataSource: DataGridDataSource<TRow>;\n getRowId: (row: TRow) => RowId;\n sorting: DataGridSortModel;\n quickSearch: string;\n pagination: DataGridPaginationModel;\n paginationMode: DataGridDataPaginationMode;\n}): UseDataSourceResult<TRow> {\n const {\n dataSource,\n getRowId,\n sorting,\n quickSearch,\n pagination,\n paginationMode,\n } = opts;\n\n const [rows, setRows] = useState<TRow[]>([]);\n const [totalRowCount, setTotalRowCount] = useState<number | undefined>(undefined);\n const [isLoading, setIsLoading] = useState(true);\n const [isRefetching, setIsRefetching] = useState(false);\n const [isLoadingMore, setIsLoadingMore] = useState(false);\n const [hasMore, setHasMore] = useState(true);\n\n const cursorRef = useRef<unknown>(undefined);\n const abortRef = useRef<AbortController | null>(null);\n const pageIndexRef = useRef(0);\n const hasDataRef = useRef(false);\n const hasMountedServerPaginationRef = useRef(false);\n\n const latestArgsRef = useRef({\n dataSource,\n getRowId,\n sorting,\n quickSearch,\n pagination,\n });\n latestArgsRef.current = { dataSource, getRowId, sorting, quickSearch, pagination };\n\n const sortingKey = JSON.stringify(sorting);\n const quickSearchKey = quickSearch;\n\n const fetchPage = useCallback(\n async (append: boolean) => {\n const {\n dataSource: currentDataSource,\n getRowId: currentGetRowId,\n sorting: currentSorting,\n quickSearch: currentQuickSearch,\n pagination: currentPagination,\n } = latestArgsRef.current;\n\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n\n if (append) {\n setIsLoadingMore(true);\n } else {\n // First load → skeleton. Subsequent → subtle refetch indicator.\n if (hasDataRef.current) {\n setIsRefetching(true);\n } else {\n setIsLoading(true);\n }\n cursorRef.current = undefined;\n pageIndexRef.current = 0;\n }\n\n try {\n const params: DataGridFetchParams = {\n sorting: currentSorting,\n quickSearch: currentQuickSearch,\n pagination: append\n ? { pageIndex: pageIndexRef.current, pageSize: currentPagination.pageSize }\n : currentPagination,\n cursor: cursorRef.current,\n };\n\n const gen = currentDataSource(params);\n\n for await (const result of gen) {\n if (controller.signal.aborted) return;\n\n if (result.totalRowCount != null) {\n setTotalRowCount(result.totalRowCount);\n }\n if (result.nextCursor !== undefined) {\n cursorRef.current = result.nextCursor;\n }\n setHasMore(result.hasMore !== false);\n\n if (append) {\n setRows((prev) => {\n const existingIds = new Set(prev.map(currentGetRowId));\n const newRows = result.rows.filter(\n (r) => !existingIds.has(currentGetRowId(r)),\n );\n return [...prev, ...newRows];\n });\n } else {\n setRows(result.rows);\n }\n\n hasDataRef.current = true;\n pageIndexRef.current++;\n }\n } catch (err) {\n if (controller.signal.aborted) return;\n console.error(\"[DataGrid] Data source error:\", err);\n } finally {\n if (!controller.signal.aborted) {\n setIsLoading(false);\n setIsRefetching(false);\n setIsLoadingMore(false);\n }\n }\n },\n [],\n );\n\n useEffect(() => {\n fetchPage(false).catch(() => {});\n return () => abortRef.current?.abort();\n }, [fetchPage, sortingKey, quickSearchKey, pagination.pageSize]);\n\n useEffect(() => {\n if (paginationMode !== \"server\") {\n hasMountedServerPaginationRef.current = false;\n return;\n }\n if (!hasMountedServerPaginationRef.current) {\n hasMountedServerPaginationRef.current = true;\n return;\n }\n fetchPage(false).catch(() => {});\n }, [fetchPage, paginationMode, pagination.pageIndex]);\n\n const loadMore = useCallback(() => {\n if (!isLoadingMore && hasMore && paginationMode === \"infinite\") {\n fetchPage(true).catch(() => {});\n }\n }, [isLoadingMore, hasMore, paginationMode, fetchPage]);\n\n const reload = useCallback(() => {\n fetchPage(false).catch(() => {});\n }, [fetchPage]);\n\n return {\n rows,\n totalRowCount,\n isLoading,\n isRefetching,\n isLoadingMore,\n loadMore,\n hasMore,\n reload,\n };\n}\n\n// ─── Noop data source (stable reference) ─────────────────────────────\nconst NOOP_DATA_SOURCE: DataGridDataSource<any> = async function* () {};\nconst NOOP_GET_ROW_ID = () => \"\";\n\n// ─── Public hook ─────────────────────────────────────────────────────\n// Both inner hooks are always called (React rules-of-hooks) but only\n// one provides the returned result.\n\n/**\n * Hook that processes raw data through the grid's sort/pagination state\n * and returns the `rows` slice ready to pass to `DataGrid`. This is the\n * only correct way to feed client-side data into a grid.\n *\n * Two modes, picked by which prop you pass:\n * - `data: TRow[]` → client-side mode. In-memory sort + paginate.\n * - `dataSource: (params) => AsyncGenerator` → server / infinite mode.\n * The generator yields pages as you scroll or change pages.\n *\n * ```tsx\n * // Client-side (most common):\n * const gridData = useDataSource({\n * data: users,\n * columns,\n * getRowId: (row) => row.id,\n * sorting: gridState.sorting,\n * quickSearch: gridState.quickSearch,\n * pagination: gridState.pagination,\n * paginationMode: \"client\",\n * });\n *\n * <DataGrid\n * columns={columns}\n * rows={gridData.rows}\n * totalRowCount={gridData.totalRowCount}\n * isLoading={gridData.isLoading}\n * state={gridState}\n * onChange={setGridState}\n * getRowId={(row) => row.id}\n * />\n * ```\n *\n * Rules:\n * - Call this hook unconditionally at the top level, before any early return.\n * - `rows` on `DataGrid` must ALWAYS be `gridData.rows`, never your raw array.\n * - For server or infinite pagination, use `dataSource` — see the\n * `DataGridDataSource` type for the generator signature.\n *\n * Quick search:\n * - Client mode (`data` prop): the hook auto-filters rows via\n * `applyQuickSearch` using a default case-insensitive substring match\n * across every column. Override with `matchRow` for custom matching\n * (fuzzy, weighted, field-specific, etc.).\n * - Async mode (`dataSource` prop): the hook passes `quickSearch` into\n * `params.quickSearch` and re-runs the generator whenever the search\n * string changes. The consumer owns the matching logic (typically by\n * folding it into a backend query). The grid performs NO client-side\n * filtering in async mode.\n */\nexport function useDataSource<TRow>(opts: {\n data?: readonly TRow[];\n dataSource?: DataGridDataSource<TRow>;\n columns: readonly DataGridColumnDef<TRow>[];\n getRowId: (row: TRow) => RowId;\n sorting: DataGridSortModel;\n /** Current quick-search text, typically `gridState.quickSearch`. */\n quickSearch: string;\n /** Override the default client-mode matcher. Ignored in async mode\n * (there the generator is the matcher). */\n matchRow?: (\n row: TRow,\n query: string,\n columns: readonly DataGridColumnDef<TRow>[],\n ) => boolean;\n pagination: DataGridPaginationModel;\n paginationMode: DataGridDataPaginationMode;\n}): UseDataSourceResult<TRow> {\n const {\n data,\n dataSource,\n columns,\n getRowId,\n sorting,\n quickSearch,\n matchRow = defaultMatchRow,\n pagination,\n paginationMode,\n } = opts;\n\n const isClientMode = data != null && !dataSource;\n\n const clientResult = useClientDataSource({\n data: data ?? [],\n columns,\n sorting,\n quickSearch,\n matchRow,\n pagination,\n paginationMode,\n });\n\n const asyncResult = useAsyncDataSource({\n dataSource: dataSource ?? NOOP_DATA_SOURCE,\n getRowId: dataSource ? getRowId : NOOP_GET_ROW_ID,\n sorting,\n quickSearch,\n pagination,\n paginationMode,\n });\n\n return isClientMode ? clientResult : asyncResult;\n}\n"],"mappings":";;;;;;AAwCA,SAAS,oBAA0B,MAYL;CAC5B,MAAM,EAAE,MAAM,SAAS,SAAS,aAAa,UAAU,YAAY,mBAAmB;CAKtF,MAAM,qCAA0B;EAK9B,MAAM,4CAA4B,MAAM,aAAa,SAAS,SAAS;EACvE,MAAM,gDAAgC,SAAS,QAAQ;EACvD,MAAM,SAAS,aAAa,CAAC,GAAG,SAAS,CAAC,KAAK,WAAW,GAAG;EAC7D,MAAM,gBAAgB,OAAO;AAK7B,SAAO;GAAE,MAHP,mBAAmB,wCACF,QAA2B,WAAW,GACnD;GACgB;GAAe;IACpC;EAAC;EAhBe,KAAK,UAAU,QAAQ;EAgBpB;EAAa;EAAU,WAAW;EAAW,WAAW;EAAU;EAAgB;EAAQ,CAAC;AAEjH,kCAAsB;EACpB,MAAM,UAAU;EAChB,eAAe,UAAU;EACzB,WAAW;EACX,cAAc;EACd,eAAe;EACf,gBAAgB;EAChB,SAAS;EACT,cAAc;EACf,GAAG,CAAC,UAAU,CAAC;;AAQlB,SAAS,mBAAyB,MAOJ;CAC5B,MAAM,EACJ,YACA,UACA,SACA,aACA,YACA,mBACE;CAEJ,MAAM,CAAC,MAAM,+BAA4B,EAAE,CAAC;CAC5C,MAAM,CAAC,eAAe,wCAAiD,OAAU;CACjF,MAAM,CAAC,WAAW,oCAAyB,KAAK;CAChD,MAAM,CAAC,cAAc,uCAA4B,MAAM;CACvD,MAAM,CAAC,eAAe,wCAA6B,MAAM;CACzD,MAAM,CAAC,SAAS,kCAAuB,KAAK;CAE5C,MAAM,8BAA4B,OAAU;CAC5C,MAAM,6BAA0C,KAAK;CACrD,MAAM,iCAAsB,EAAE;CAC9B,MAAM,+BAAoB,MAAM;CAChC,MAAM,kDAAuC,MAAM;CAEnD,MAAM,kCAAuB;EAC3B;EACA;EACA;EACA;EACA;EACD,CAAC;AACF,eAAc,UAAU;EAAE;EAAY;EAAU;EAAS;EAAa;EAAY;CAElF,MAAM,aAAa,KAAK,UAAU,QAAQ;CAC1C,MAAM,iBAAiB;CAEvB,MAAM,mCACJ,OAAO,WAAoB;EACzB,MAAM,EACJ,YAAY,mBACZ,UAAU,iBACV,SAAS,gBACT,aAAa,oBACb,YAAY,sBACV,cAAc;AAElB,WAAS,SAAS,OAAO;EACzB,MAAM,aAAa,IAAI,iBAAiB;AACxC,WAAS,UAAU;AAEnB,MAAI,OACF,kBAAiB,KAAK;OACjB;AAEL,OAAI,WAAW,QACb,iBAAgB,KAAK;OAErB,cAAa,KAAK;AAEpB,aAAU,UAAU;AACpB,gBAAa,UAAU;;AAGzB,MAAI;GAUF,MAAM,MAAM,kBATwB;IAClC,SAAS;IACT,aAAa;IACb,YAAY,SACR;KAAE,WAAW,aAAa;KAAS,UAAU,kBAAkB;KAAU,GACzE;IACJ,QAAQ,UAAU;IACnB,CAEoC;AAErC,cAAW,MAAM,UAAU,KAAK;AAC9B,QAAI,WAAW,OAAO,QAAS;AAE/B,QAAI,OAAO,iBAAiB,KAC1B,kBAAiB,OAAO,cAAc;AAExC,QAAI,OAAO,eAAe,OACxB,WAAU,UAAU,OAAO;AAE7B,eAAW,OAAO,YAAY,MAAM;AAEpC,QAAI,OACF,UAAS,SAAS;KAChB,MAAM,cAAc,IAAI,IAAI,KAAK,IAAI,gBAAgB,CAAC;KACtD,MAAM,UAAU,OAAO,KAAK,QACzB,MAAM,CAAC,YAAY,IAAI,gBAAgB,EAAE,CAAC,CAC5C;AACD,YAAO,CAAC,GAAG,MAAM,GAAG,QAAQ;MAC5B;QAEF,SAAQ,OAAO,KAAK;AAGtB,eAAW,UAAU;AACrB,iBAAa;;WAER,KAAK;AACZ,OAAI,WAAW,OAAO,QAAS;AAC/B,WAAQ,MAAM,iCAAiC,IAAI;YAC3C;AACR,OAAI,CAAC,WAAW,OAAO,SAAS;AAC9B,iBAAa,MAAM;AACnB,oBAAgB,MAAM;AACtB,qBAAiB,MAAM;;;IAI7B,EAAE,CACH;AAED,4BAAgB;AACd,YAAU,MAAM,CAAC,YAAY,GAAG;AAChC,eAAa,SAAS,SAAS,OAAO;IACrC;EAAC;EAAW;EAAY;EAAgB,WAAW;EAAS,CAAC;AAEhE,4BAAgB;AACd,MAAI,mBAAmB,UAAU;AAC/B,iCAA8B,UAAU;AACxC;;AAEF,MAAI,CAAC,8BAA8B,SAAS;AAC1C,iCAA8B,UAAU;AACxC;;AAEF,YAAU,MAAM,CAAC,YAAY,GAAG;IAC/B;EAAC;EAAW;EAAgB,WAAW;EAAU,CAAC;AAYrD,QAAO;EACL;EACA;EACA;EACA;EACA;EACA,uCAhBiC;AACjC,OAAI,CAAC,iBAAiB,WAAW,mBAAmB,WAClD,WAAU,KAAK,CAAC,YAAY,GAAG;KAEhC;GAAC;GAAe;GAAS;GAAgB;GAAU,CAAC;EAarD;EACA,qCAZ+B;AAC/B,aAAU,MAAM,CAAC,YAAY,GAAG;KAC/B,CAAC,UAAU,CAAC;EAWd;;AAIH,MAAM,mBAA4C,mBAAmB;AACrE,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwD9B,SAAgB,cAAoB,MAiBN;CAC5B,MAAM,EACJ,MACA,YACA,SACA,UACA,SACA,aACA,WAAWA,4BACX,YACA,mBACE;CAEJ,MAAM,eAAe,QAAQ,QAAQ,CAAC;CAEtC,MAAM,eAAe,oBAAoB;EACvC,MAAM,QAAQ,EAAE;EAChB;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;CAEF,MAAM,cAAc,mBAAmB;EACrC,YAAY,cAAc;EAC1B,UAAU,aAAa,WAAW;EAClC;EACA;EACA;EACA;EACD,CAAC;AAEF,QAAO,eAAe,eAAe"}
1
+ {"version":3,"file":"use-data-source.js","names":["defaultMatchRow"],"sources":["../../../src/components/data-grid/use-data-source.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type {\n DataGridColumnDef,\n DataGridDataSource,\n DataGridFetchParams,\n DataGridDataPaginationMode,\n DataGridPaginationModel,\n DataGridSortModel,\n RowId,\n} from \"./types\";\nimport {\n applyQuickSearch,\n buildRowComparator,\n defaultMatchRow,\n paginateRows,\n} from \"./state\";\n\nexport type UseDataSourceResult<TRow> = {\n /** All rows currently loaded (for infinite mode, the accumulated set). */\n rows: readonly TRow[];\n /** Total row count if known. */\n totalRowCount: number | undefined;\n /** Whether the initial load is in progress (no data at all yet). */\n isLoading: boolean;\n /** Whether a background refetch is happening (data already shown). */\n isRefetching: boolean;\n /** Whether more rows are being fetched (infinite scroll). */\n isLoadingMore: boolean;\n /** Request the next page (infinite scroll). */\n loadMore: () => void;\n /** Whether there are more pages to load. */\n hasMore: boolean;\n /** Reload from scratch. */\n reload: () => void;\n /**\n * Error from the most recent async fetch, if any. Consumers should render\n * an error UI and offer a `reload()` button when this is non-null. Client\n * mode never sets this (no fetching). Cleared on next successful fetch.\n */\n error: Error | null;\n};\n\n// ─── Client-side hook ────────────────────────────────────────────────\n// Memoised so resize / selection / other unrelated state changes\n// don't recompute or create new array references.\n\nfunction useClientDataSource<TRow>(opts: {\n data: readonly TRow[];\n columns: readonly DataGridColumnDef<TRow>[];\n sorting: DataGridSortModel;\n quickSearch: string;\n matchRow: (\n row: TRow,\n query: string,\n columns: readonly DataGridColumnDef<TRow>[],\n ) => boolean;\n pagination: DataGridPaginationModel;\n paginationMode: DataGridDataPaginationMode;\n}): UseDataSourceResult<TRow> {\n const { data, columns, sorting, quickSearch, matchRow, pagination, paginationMode } = opts;\n\n // Stable serialised keys so useMemo only fires on real changes\n const sortingKey = JSON.stringify(sorting);\n\n const processed = useMemo(() => {\n // Quick search is applied FIRST, on the full input. If nothing is\n // typed this is a zero-cost no-op (applyQuickSearch returns the\n // original array reference). Sort and paginate operate on the\n // already-filtered set so the result counts are search-aware.\n const searched = applyQuickSearch(data, quickSearch, columns, matchRow);\n const comparator = buildRowComparator(sorting, columns);\n const sorted = comparator ? [...searched].sort(comparator) : searched;\n const totalRowCount = sorted.length;\n const paged =\n paginationMode === \"client\"\n ? paginateRows(sorted as readonly TRow[], pagination)\n : sorted;\n return { rows: paged, totalRowCount };\n }, [data, sortingKey, quickSearch, matchRow, pagination.pageIndex, pagination.pageSize, paginationMode, columns]);\n\n return useMemo(() => ({\n rows: processed.rows,\n totalRowCount: processed.totalRowCount,\n isLoading: false,\n isRefetching: false,\n isLoadingMore: false,\n loadMore: () => {},\n hasMore: false,\n reload: () => {},\n error: null,\n }), [processed]);\n}\n\n// ─── Async data source hook ──────────────────────────────────────────\n// Key behaviour: when refetching (sort change), we keep showing the old\n// rows and set `isRefetching` instead of `isLoading`. This avoids the\n// jarring flash-to-skeleton on every sort toggle.\n\nfunction useAsyncDataSource<TRow>(opts: {\n dataSource: DataGridDataSource<TRow>;\n getRowId: (row: TRow) => RowId;\n sorting: DataGridSortModel;\n quickSearch: string;\n pagination: DataGridPaginationModel;\n paginationMode: DataGridDataPaginationMode;\n}): UseDataSourceResult<TRow> {\n const {\n dataSource,\n getRowId,\n sorting,\n quickSearch,\n pagination,\n paginationMode,\n } = opts;\n\n const [rows, setRows] = useState<TRow[]>([]);\n const [totalRowCount, setTotalRowCount] = useState<number | undefined>(undefined);\n const [isLoading, setIsLoading] = useState(true);\n const [isRefetching, setIsRefetching] = useState(false);\n const [isLoadingMore, setIsLoadingMore] = useState(false);\n const [hasMore, setHasMore] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const cursorRef = useRef<unknown>(undefined);\n const abortRef = useRef<AbortController | null>(null);\n const pageIndexRef = useRef(0);\n const hasDataRef = useRef(false);\n const hasMountedServerPaginationRef = useRef(false);\n\n const latestArgsRef = useRef({\n dataSource,\n getRowId,\n sorting,\n quickSearch,\n pagination,\n });\n latestArgsRef.current = { dataSource, getRowId, sorting, quickSearch, pagination };\n\n const sortingKey = JSON.stringify(sorting);\n const quickSearchKey = quickSearch;\n\n const fetchPage = useCallback(\n async (append: boolean) => {\n const {\n dataSource: currentDataSource,\n getRowId: currentGetRowId,\n sorting: currentSorting,\n quickSearch: currentQuickSearch,\n pagination: currentPagination,\n } = latestArgsRef.current;\n\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n\n if (append) {\n setIsLoadingMore(true);\n } else {\n // First load → skeleton. Subsequent → subtle refetch indicator.\n if (hasDataRef.current) {\n setIsRefetching(true);\n } else {\n setIsLoading(true);\n }\n cursorRef.current = undefined;\n pageIndexRef.current = 0;\n }\n // Clear previous error at the start of a new attempt; we'll set it\n // again if this attempt fails.\n setError(null);\n\n try {\n const params: DataGridFetchParams = {\n sorting: currentSorting,\n quickSearch: currentQuickSearch,\n pagination: append\n ? { pageIndex: pageIndexRef.current, pageSize: currentPagination.pageSize }\n : currentPagination,\n cursor: cursorRef.current,\n };\n\n const gen = currentDataSource(params);\n\n for await (const result of gen) {\n if (controller.signal.aborted) return;\n\n if (result.totalRowCount != null) {\n setTotalRowCount(result.totalRowCount);\n }\n if (result.nextCursor !== undefined) {\n cursorRef.current = result.nextCursor;\n }\n setHasMore(result.hasMore !== false);\n\n if (append) {\n setRows((prev) => {\n const existingIds = new Set(prev.map(currentGetRowId));\n const newRows = result.rows.filter(\n (r) => !existingIds.has(currentGetRowId(r)),\n );\n return [...prev, ...newRows];\n });\n } else {\n setRows(result.rows);\n }\n\n hasDataRef.current = true;\n pageIndexRef.current++;\n }\n } catch (err) {\n if (controller.signal.aborted) return;\n // Surface the error on the result so consumers can render retry UI.\n // Still log to console so it's visible in dev without forcing every\n // consumer to wire up error rendering.\n // eslint-disable-next-line no-console\n console.error(\"[DataGrid] Data source error:\", err);\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n if (!controller.signal.aborted) {\n setIsLoading(false);\n setIsRefetching(false);\n setIsLoadingMore(false);\n }\n }\n },\n [],\n );\n\n useEffect(() => {\n fetchPage(false).catch(() => {});\n return () => abortRef.current?.abort();\n // Also refetches when `dataSource` identity changes — consumers encode\n // external filter state into the generator's closure, so a new\n // generator reference is the signal that the query changed.\n }, [fetchPage, dataSource, sortingKey, quickSearchKey, pagination.pageSize]);\n\n useEffect(() => {\n if (paginationMode !== \"server\") {\n hasMountedServerPaginationRef.current = false;\n return;\n }\n if (!hasMountedServerPaginationRef.current) {\n hasMountedServerPaginationRef.current = true;\n return;\n }\n fetchPage(false).catch(() => {});\n }, [fetchPage, paginationMode, pagination.pageIndex]);\n\n const loadMore = useCallback(() => {\n if (!isLoadingMore && hasMore && paginationMode === \"infinite\") {\n fetchPage(true).catch(() => {});\n }\n }, [isLoadingMore, hasMore, paginationMode, fetchPage]);\n\n const reload = useCallback(() => {\n fetchPage(false).catch(() => {});\n }, [fetchPage]);\n\n return {\n rows,\n totalRowCount,\n isLoading,\n isRefetching,\n isLoadingMore,\n loadMore,\n hasMore,\n reload,\n error,\n };\n}\n\n// ─── Noop data source (stable reference) ─────────────────────────────\nconst NOOP_DATA_SOURCE: DataGridDataSource<any> = async function* () {};\nconst NOOP_GET_ROW_ID = () => \"\";\n\n// ─── Public hook ─────────────────────────────────────────────────────\n// Both inner hooks are always called (React rules-of-hooks) but only\n// one provides the returned result.\n\n/**\n * Hook that processes raw data through the grid's sort/pagination state\n * and returns the `rows` slice ready to pass to `DataGrid`. This is the\n * only correct way to feed client-side data into a grid.\n *\n * Two modes, picked by which prop you pass:\n * - `data: TRow[]` → client-side mode. In-memory sort + paginate.\n * - `dataSource: (params) => AsyncGenerator` → server / infinite mode.\n * The generator yields pages as you scroll or change pages.\n *\n * ```tsx\n * // Client-side (most common):\n * const gridData = useDataSource({\n * data: users,\n * columns,\n * getRowId: (row) => row.id,\n * sorting: gridState.sorting,\n * quickSearch: gridState.quickSearch,\n * pagination: gridState.pagination,\n * paginationMode: \"client\",\n * });\n *\n * <DataGrid\n * columns={columns}\n * rows={gridData.rows}\n * totalRowCount={gridData.totalRowCount}\n * isLoading={gridData.isLoading}\n * state={gridState}\n * onChange={setGridState}\n * getRowId={(row) => row.id}\n * />\n * ```\n *\n * Rules:\n * - Call this hook unconditionally at the top level, before any early return.\n * - `rows` on `DataGrid` must ALWAYS be `gridData.rows`, never your raw array.\n * - For server or infinite pagination, use `dataSource` — see the\n * `DataGridDataSource` type for the generator signature.\n *\n * Quick search:\n * - Client mode (`data` prop): the hook auto-filters rows via\n * `applyQuickSearch` using a default case-insensitive substring match\n * across every column. Override with `matchRow` for custom matching\n * (fuzzy, weighted, field-specific, etc.).\n * - Async mode (`dataSource` prop): the hook passes `quickSearch` into\n * `params.quickSearch` and re-runs the generator whenever the search\n * string changes. The consumer owns the matching logic (typically by\n * folding it into a backend query). The grid performs NO client-side\n * filtering in async mode.\n */\nexport function useDataSource<TRow>(opts: {\n data?: readonly TRow[];\n dataSource?: DataGridDataSource<TRow>;\n columns: readonly DataGridColumnDef<TRow>[];\n getRowId: (row: TRow) => RowId;\n sorting: DataGridSortModel;\n /** Current quick-search text, typically `gridState.quickSearch`. */\n quickSearch: string;\n /** Override the default client-mode matcher. Ignored in async mode\n * (there the generator is the matcher). */\n matchRow?: (\n row: TRow,\n query: string,\n columns: readonly DataGridColumnDef<TRow>[],\n ) => boolean;\n pagination: DataGridPaginationModel;\n paginationMode: DataGridDataPaginationMode;\n}): UseDataSourceResult<TRow> {\n const {\n data,\n dataSource,\n columns,\n getRowId,\n sorting,\n quickSearch,\n matchRow = defaultMatchRow,\n pagination,\n paginationMode,\n } = opts;\n\n const isClientMode = data != null && !dataSource;\n\n if (process.env.NODE_ENV !== \"production\" && data == null && dataSource == null) {\n // eslint-disable-next-line no-console\n console.warn(\n \"[useDataSource] neither `data` nor `dataSource` was provided — \"\n + \"the grid will render empty indefinitely. Pass one or the other.\"\n );\n }\n\n // Common footgun: consumers pass `data` as a fully-materialized array and\n // set `paginationMode: \"infinite\"` expecting the grid to page through it.\n // In client mode \"infinite\" skips `paginateRows` and returns every row;\n // `hasMore` / `loadMore` on the result are always false/no-ops. If you\n // want real paging, switch to `paginationMode: \"server\"` with a\n // `dataSource` generator. If you want in-memory slicing, use `\"client\"`.\n // If you're manually accumulating rows into `data` and driving the grid's\n // sentinel via your own `hasMore`/`onLoadMore`, this warning is a hint —\n // but current behavior (full list + external sentinel) is intentional.\n if (\n process.env.NODE_ENV !== \"production\"\n && isClientMode\n && paginationMode === \"infinite\"\n && data.length > 0\n ) {\n // eslint-disable-next-line no-console\n console.warn(\n \"[useDataSource] `paginationMode: \\\"infinite\\\"` with a `data` array \"\n + \"skips pagination entirely. Prefer `\\\"client\\\"` for in-memory lists \"\n + \"or `\\\"server\\\"` + a `dataSource` generator for real paging.\"\n );\n }\n\n const clientResult = useClientDataSource({\n data: data ?? [],\n columns,\n sorting,\n quickSearch,\n matchRow,\n pagination,\n paginationMode,\n });\n\n const asyncResult = useAsyncDataSource({\n dataSource: dataSource ?? NOOP_DATA_SOURCE,\n getRowId: dataSource ? getRowId : NOOP_GET_ROW_ID,\n sorting,\n quickSearch,\n pagination,\n paginationMode,\n });\n\n return isClientMode ? clientResult : asyncResult;\n}\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import * as react_jsx_runtime0 from "react/jsx-runtime";
2
+ import { ReactNode } from "react";
2
3
 
3
4
  //#region src/components/tabs.d.ts
4
5
  type DesignTabsSize = "sm" | "md";
@@ -8,6 +9,7 @@ type DesignCategoryTabItem = {
8
9
  label: string;
9
10
  count?: number;
10
11
  badgeCount?: number;
12
+ icon?: React.ComponentType<React.SVGProps<SVGSVGElement>>;
11
13
  };
12
14
  type DesignCategoryTabsProps = Omit<React.ComponentProps<"div">, "onSelect"> & {
13
15
  categories: DesignCategoryTabItem[];
@@ -16,7 +18,8 @@ type DesignCategoryTabsProps = Omit<React.ComponentProps<"div">, "onSelect"> & {
16
18
  showBadge?: boolean;
17
19
  size?: DesignTabsSize;
18
20
  glassmorphic?: boolean;
19
- gradient?: DesignTabsGradient;
21
+ gradient?: DesignTabsGradient; /** Renders inside the tab bar after the tab buttons (not a tab). */
22
+ trailing?: ReactNode;
20
23
  };
21
24
  declare function DesignCategoryTabs({
22
25
  categories,
@@ -26,6 +29,7 @@ declare function DesignCategoryTabs({
26
29
  size,
27
30
  glassmorphic: glassmorphicProp,
28
31
  gradient,
32
+ trailing,
29
33
  className,
30
34
  ...props
31
35
  }: DesignCategoryTabsProps): react_jsx_runtime0.JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"tabs.d.ts","names":[],"sources":["../../src/components/tabs.tsx"],"mappings":";;;KAOK,cAAA;AAAA,KACA,kBAAA;AAAA,KAEO,qBAAA;EACV,EAAA;EACA,KAAA;EACA,KAAA;EACA,UAAA;AAAA;AAAA,KAGU,uBAAA,GAA0B,IAAA,CAAK,KAAA,CAAM,cAAA;EAC/C,UAAA,EAAY,qBAAA;EACZ,gBAAA;EACA,QAAA,GAAW,EAAA,oBAAsB,OAAA;EACjC,SAAA;EACA,IAAA,GAAO,cAAA;EACP,YAAA;EACA,QAAA,GAAW,kBAAA;AAAA;AAAA,iBA8EG,kBAAA,CAAA;EACd,UAAA;EACA,gBAAA;EACA,QAAA;EACA,SAAA;EACA,IAAA;EACA,YAAA,EAAc,gBAAA;EACd,QAAA;EACA,SAAA;EAAA,GACG;AAAA,GACF,uBAAA,GAAuB,kBAAA,CAAA,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"tabs.d.ts","names":[],"sources":["../../src/components/tabs.tsx"],"mappings":";;;;KAOK,cAAA;AAAA,KACA,kBAAA;AAAA,KAEO,qBAAA;EACV,EAAA;EACA,KAAA;EACA,KAAA;EACA,UAAA;EACA,IAAA,GAAO,KAAA,CAAM,aAAA,CAAc,KAAA,CAAM,QAAA,CAAS,aAAA;AAAA;AAAA,KAGhC,uBAAA,GAA0B,IAAA,CAAK,KAAA,CAAM,cAAA;EAC/C,UAAA,EAAY,qBAAA;EACZ,gBAAA;EACA,QAAA,GAAW,EAAA,oBAAsB,OAAA;EACjC,SAAA;EACA,IAAA,GAAO,cAAA;EACP,YAAA;EACA,QAAA,GAAW,kBAAA,EAVgB;EAY3B,QAAA,GAAW,SAAA;AAAA;AAAA,iBA8EG,kBAAA,CAAA;EACd,UAAA;EACA,gBAAA;EACA,QAAA;EACA,SAAA;EACA,IAAA;EACA,YAAA,EAAc,gBAAA;EACd,QAAA;EACA,QAAA;EACA,SAAA;EAAA,GACG;AAAA,GACF,uBAAA,GAAuB,kBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -53,7 +53,7 @@ function getMapValueOrThrow(map, key, mapName) {
53
53
  if (!value) throw new Error(`Missing ${mapName} entry for key "${String(key)}"`);
54
54
  return value;
55
55
  }
56
- function DesignCategoryTabs({ categories, selectedCategory, onSelect, showBadge = true, size = "sm", glassmorphic: glassmorphicProp, gradient = "blue", className, ...props }) {
56
+ function DesignCategoryTabs({ categories, selectedCategory, onSelect, showBadge = true, size = "sm", glassmorphic: glassmorphicProp, gradient = "blue", trailing, className, ...props }) {
57
57
  const glassmorphic = (0, __card_js.useGlassmorphicDefault)(glassmorphicProp);
58
58
  const sizeClass = getMapValueOrThrow(tabSizeClasses, size, "tabSizeClasses");
59
59
  const gradientClass = getMapValueOrThrow(gradientClasses, gradient, "gradientClasses");
@@ -65,33 +65,46 @@ function DesignCategoryTabs({ categories, selectedCategory, onSelect, showBadge
65
65
  (0, _stackframe_stack_shared_dist_utils_promises.runAsynchronouslyWithAlert)(Promise.resolve(result).finally(() => setLoadingCategoryId(null)));
66
66
  }
67
67
  };
68
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
69
- className: (0, _stackframe_stack_ui.cn)("flex items-center gap-1 overflow-x-auto flex-nowrap [&::-webkit-scrollbar]:hidden", glassmorphic ? "rounded-xl bg-black/[0.08] dark:bg-white/[0.04] p-1 backdrop-blur-sm" : "border-b border-gray-300 dark:border-gray-800", className),
68
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
69
+ className: (0, _stackframe_stack_ui.cn)("flex w-full min-w-0 items-center gap-2", glassmorphic ? "rounded-xl bg-black/[0.08] dark:bg-white/[0.04] p-1 backdrop-blur-sm" : "border-b border-gray-300 dark:border-gray-800", className),
70
70
  ...props,
71
- children: categories.map((category) => {
72
- const isActive = selectedCategory === category.id;
73
- const badgeValue = category.badgeCount ?? category.count;
74
- const shouldShowBadge = showBadge && badgeValue !== void 0;
75
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
76
- onClick: () => handleSelect(category.id),
77
- disabled: loadingCategoryId !== null,
78
- className: (0, _stackframe_stack_ui.cn)("font-medium transition-all duration-150 hover:transition-none relative flex flex-shrink-0 items-center justify-center gap-2 whitespace-nowrap", "hover:text-gray-900 dark:hover:text-gray-100", sizeClass.button, glassmorphic ? "rounded-lg" : "", isActive ? (0, _stackframe_stack_ui.cn)(gradientClass.activeText, glassmorphic && "bg-background shadow-sm ring-1 ring-black/[0.12] dark:ring-white/[0.06]") : "text-gray-700 dark:text-gray-400"),
79
- children: [
80
- loadingCategoryId === category.id && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_stackframe_stack_ui.Spinner, {
81
- size: 12,
82
- className: "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none"
83
- }),
84
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
85
- className: (0, _stackframe_stack_ui.cn)("flex items-center gap-2", loadingCategoryId === category.id && "invisible"),
86
- children: [category.label, shouldShowBadge && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
87
- className: (0, _stackframe_stack_ui.cn)("rounded-full", sizeClass.badge, isActive ? gradientClass.activeBadge : "bg-gray-200 dark:bg-gray-800 text-gray-600 dark:text-gray-400"),
88
- children: badgeValue
89
- })]
90
- }),
91
- !glassmorphic && isActive && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: (0, _stackframe_stack_ui.cn)("absolute bottom-0 left-0 right-0 h-0.5", gradientClass.underline) })
92
- ]
93
- }, category.id);
94
- })
71
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
72
+ className: (0, _stackframe_stack_ui.cn)("flex min-h-0 min-w-0 flex-1 items-center gap-1 overflow-x-auto flex-nowrap [&::-webkit-scrollbar]:hidden"),
73
+ children: categories.map((category) => {
74
+ const isActive = selectedCategory === category.id;
75
+ const badgeValue = category.badgeCount ?? category.count;
76
+ const shouldShowBadge = showBadge && badgeValue !== void 0;
77
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
78
+ onClick: () => handleSelect(category.id),
79
+ disabled: loadingCategoryId !== null,
80
+ className: (0, _stackframe_stack_ui.cn)("font-medium transition-all duration-150 hover:transition-none relative flex flex-shrink-0 items-center justify-center gap-2 whitespace-nowrap", "hover:text-gray-900 dark:hover:text-gray-100", sizeClass.button, glassmorphic ? "rounded-lg" : "", isActive ? (0, _stackframe_stack_ui.cn)(gradientClass.activeText, glassmorphic && "bg-background shadow-sm ring-1 ring-black/[0.12] dark:ring-white/[0.06]") : "text-gray-700 dark:text-gray-400"),
81
+ children: [
82
+ loadingCategoryId === category.id && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_stackframe_stack_ui.Spinner, {
83
+ size: 12,
84
+ className: "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none"
85
+ }),
86
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
87
+ className: (0, _stackframe_stack_ui.cn)("flex items-center gap-2", loadingCategoryId === category.id && "invisible"),
88
+ children: [
89
+ category.icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(category.icon, {
90
+ className: "h-4 w-4 shrink-0",
91
+ "aria-hidden": true
92
+ }),
93
+ category.label,
94
+ shouldShowBadge && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
95
+ className: (0, _stackframe_stack_ui.cn)("rounded-full", sizeClass.badge, isActive ? gradientClass.activeBadge : "bg-gray-200 dark:bg-gray-800 text-gray-600 dark:text-gray-400"),
96
+ children: badgeValue
97
+ })
98
+ ]
99
+ }),
100
+ !glassmorphic && isActive && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: (0, _stackframe_stack_ui.cn)("absolute bottom-0 left-0 right-0 h-0.5", gradientClass.underline) })
101
+ ]
102
+ }, category.id);
103
+ })
104
+ }), trailing != null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
105
+ className: "flex shrink-0 items-center",
106
+ children: trailing
107
+ }) : null]
95
108
  });
96
109
  }
97
110