@hexclave/dashboard-ui-components 1.0.53 → 1.0.55

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 (54) hide show
  1. package/dist/components/data-grid/data-grid-export-dialog.d.ts +1 -1
  2. package/dist/components/data-grid/data-grid-sizing.d.ts +1 -1
  3. package/dist/components/data-grid/data-grid-toolbar.d.ts +1 -1
  4. package/dist/components/data-grid/data-grid-toolbar.js +37 -29
  5. package/dist/components/data-grid/data-grid-toolbar.js.map +1 -1
  6. package/dist/components/data-grid/data-grid.d.ts +1 -1
  7. package/dist/components/data-grid/data-grid.js +96 -12
  8. package/dist/components/data-grid/data-grid.js.map +1 -1
  9. package/dist/components/data-grid/data-grid.test.js +101 -2
  10. package/dist/components/data-grid/data-grid.test.js.map +1 -1
  11. package/dist/components/data-grid/index.d.ts +8 -8
  12. package/dist/components/data-grid/state.d.ts +1 -1
  13. package/dist/components/data-grid/strings.d.ts +1 -1
  14. package/dist/components/data-grid/types.d.ts +1 -1
  15. package/dist/components/data-grid/use-data-source.d.ts +1 -1
  16. package/dist/components/data-grid/use-url-state.d.ts +1 -1
  17. package/dist/dashboard-ui-components.global.js +21283 -4277
  18. package/dist/dashboard-ui-components.global.js.map +4 -4
  19. package/dist/{data-grid-AJi1TNDa.d.ts → data-grid-DWHAjaNo.d.ts} +2 -2
  20. package/dist/data-grid-DWHAjaNo.d.ts.map +1 -0
  21. package/dist/{data-grid-sizing-S1ixcZKo.d.ts → data-grid-sizing-D9y1xwaq.d.ts} +2 -2
  22. package/dist/{data-grid-sizing-S1ixcZKo.d.ts.map → data-grid-sizing-D9y1xwaq.d.ts.map} +1 -1
  23. package/dist/{data-grid-toolbar-BGSNGDpT.d.ts → data-grid-toolbar-dgvnYEpC.d.ts} +10 -6
  24. package/dist/data-grid-toolbar-dgvnYEpC.d.ts.map +1 -0
  25. package/dist/esm/components/data-grid/data-grid-toolbar.d.ts +8 -4
  26. package/dist/esm/components/data-grid/data-grid-toolbar.d.ts.map +1 -1
  27. package/dist/esm/components/data-grid/data-grid-toolbar.js +38 -30
  28. package/dist/esm/components/data-grid/data-grid-toolbar.js.map +1 -1
  29. package/dist/esm/components/data-grid/data-grid.d.ts.map +1 -1
  30. package/dist/esm/components/data-grid/data-grid.js +96 -12
  31. package/dist/esm/components/data-grid/data-grid.js.map +1 -1
  32. package/dist/esm/components/data-grid/data-grid.test.js +102 -3
  33. package/dist/esm/components/data-grid/data-grid.test.js.map +1 -1
  34. package/dist/esm/components/data-grid/types.d.ts +7 -0
  35. package/dist/esm/components/data-grid/types.d.ts.map +1 -1
  36. package/dist/esm/package.json +12 -0
  37. package/dist/index.d.ts +8 -8
  38. package/dist/{state-6LW8ACh5.d.ts → state-D5wpAg_G.d.ts} +2 -2
  39. package/dist/{state-6LW8ACh5.d.ts.map → state-D5wpAg_G.d.ts.map} +1 -1
  40. package/dist/{strings-CACVtKlq.d.ts → strings-DsIJ_o4-.d.ts} +2 -2
  41. package/dist/{strings-CACVtKlq.d.ts.map → strings-DsIJ_o4-.d.ts.map} +1 -1
  42. package/dist/{types-1-eOd1JF.d.ts → types-D8o47V7M.d.ts} +8 -1
  43. package/dist/{types-1-eOd1JF.d.ts.map → types-D8o47V7M.d.ts.map} +1 -1
  44. package/dist/{use-data-source-BVFynerX.d.ts → use-data-source-CAZPhl8e.d.ts} +2 -2
  45. package/dist/{use-data-source-BVFynerX.d.ts.map → use-data-source-CAZPhl8e.d.ts.map} +1 -1
  46. package/dist/{use-url-state-B4_wnBjm.d.ts → use-url-state-97YdV81Q.d.ts} +2 -2
  47. package/dist/{use-url-state-B4_wnBjm.d.ts.map → use-url-state-97YdV81Q.d.ts.map} +1 -1
  48. package/package.json +3 -3
  49. package/src/components/data-grid/data-grid-toolbar.tsx +20 -6
  50. package/src/components/data-grid/data-grid.test.tsx +127 -1
  51. package/src/components/data-grid/data-grid.tsx +174 -37
  52. package/src/components/data-grid/types.ts +7 -0
  53. package/dist/data-grid-AJi1TNDa.d.ts.map +0 -1
  54. package/dist/data-grid-toolbar-BGSNGDpT.d.ts.map +0 -1
@@ -62,6 +62,12 @@ import type {
62
62
  // (no `fillHeight`, no `maxHeight`). Leaves ~16rem of room for the top bar, page header, and grid
63
63
  // toolbar. See the `effectiveMaxHeight` comment in DataGrid for why an infinite grid must be bounded.
64
64
  const DEFAULT_INFINITE_MAX_HEIGHT = "calc(100dvh - 16rem)";
65
+ const DATA_GRID_SCROLLBAR_CLASS_NAME = cn(
66
+ "[&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar]:h-1.5",
67
+ "[&::-webkit-scrollbar-track]:bg-transparent",
68
+ "[&::-webkit-scrollbar-thumb]:bg-foreground/[0.08] [&::-webkit-scrollbar-thumb]:rounded-full",
69
+ "[&::-webkit-scrollbar-thumb]:hover:bg-foreground/[0.15]",
70
+ );
65
71
 
66
72
  // ─── Row click target ────────────────────────────────────────────────
67
73
 
@@ -376,6 +382,15 @@ function hashStringToInt(value: string): number {
376
382
  return Math.abs(hash);
377
383
  }
378
384
 
385
+ /**
386
+ * Column flex fractions used when the grid is loading before any schema is
387
+ * known (e.g. QueryDataGrid discovers columns from the first result page).
388
+ * Without these, `SkeletonRow` would render zero cells and the body looks
389
+ * like an empty black void instead of a shimmer.
390
+ */
391
+ const SCHEMA_PENDING_COL_FRACTIONS = [0.18, 0.16, 0.34, 0.2, 0.12] as const;
392
+ const SCHEMA_PENDING_SKELETON_ROWS = 10;
393
+
379
394
  function SkeletonRow({
380
395
  columns,
381
396
  height,
@@ -405,6 +420,72 @@ function SkeletonRow({
405
420
  );
406
421
  }
407
422
 
423
+ /** Sticky header shimmer when columns haven't been discovered yet. */
424
+ function SchemaPendingHeaderCells({ showCheckbox }: { showCheckbox?: boolean }) {
425
+ return (
426
+ <>
427
+ {showCheckbox && (
428
+ <div
429
+ className="flex shrink-0 items-center justify-center border-r border-foreground/[0.04]"
430
+ style={{ width: 44 }}
431
+ />
432
+ )}
433
+ {SCHEMA_PENDING_COL_FRACTIONS.map((frac, colIdx) => (
434
+ <div
435
+ key={colIdx}
436
+ className="flex items-center px-3 border-r border-black/[0.04] dark:border-white/[0.04] last:border-r-0"
437
+ style={{ width: `${frac * 100}%` }}
438
+ >
439
+ <DesignSkeleton
440
+ className="h-3 rounded-md"
441
+ style={{ width: `${45 + (colIdx * 13) % 30}%` }}
442
+ />
443
+ </div>
444
+ ))}
445
+ </>
446
+ );
447
+ }
448
+
449
+ /** Full-width body shimmer when columns haven't been discovered yet. */
450
+ function SchemaPendingSkeleton({
451
+ rowHeight,
452
+ showCheckbox,
453
+ }: {
454
+ rowHeight: number,
455
+ showCheckbox?: boolean,
456
+ }) {
457
+ return (
458
+ <div className="w-full" data-data-grid-schema-pending-skeleton="" role="status" aria-label="Loading">
459
+ {Array.from({ length: SCHEMA_PENDING_SKELETON_ROWS }).map((_, rowIdx) => (
460
+ <div
461
+ key={rowIdx}
462
+ className="flex w-full border-b border-black/[0.04] dark:border-white/[0.04]"
463
+ style={{ height: rowHeight }}
464
+ role="row"
465
+ >
466
+ {showCheckbox && (
467
+ <div className="flex shrink-0 items-center justify-center border-r border-black/[0.04] dark:border-white/[0.04]" style={{ width: 44 }}>
468
+ <DesignSkeleton className="h-4 w-4 rounded" />
469
+ </div>
470
+ )}
471
+ {SCHEMA_PENDING_COL_FRACTIONS.map((frac, colIdx) => (
472
+ <div
473
+ key={colIdx}
474
+ className="flex items-center px-3 border-r border-black/[0.04] dark:border-white/[0.04] last:border-r-0"
475
+ style={{ width: `${frac * 100}%` }}
476
+ >
477
+ <DesignSkeleton
478
+ className="h-3.5 rounded-md"
479
+ style={{ width: `${50 + ((rowIdx * 7 + colIdx * 19) % 40)}%` }}
480
+ />
481
+ </div>
482
+ ))}
483
+ </div>
484
+ ))}
485
+ </div>
486
+ );
487
+ }
488
+
408
489
  // ─── Selection checkbox ──────────────────────────────────────────────
409
490
 
410
491
  function SelectionCheckbox({
@@ -640,6 +721,7 @@ export function DataGrid<TRow>(props: DataGridProps<TRow>) {
640
721
  maxHeight,
641
722
  fillHeight = true,
642
723
  stickyTop,
724
+ horizontalScrollbarPosition = "bottom",
643
725
  toolbar,
644
726
  toolbarExtra,
645
727
  emptyState,
@@ -970,12 +1052,42 @@ export function DataGrid<TRow>(props: DataGridProps<TRow>) {
970
1052
  };
971
1053
  }, []);
972
1054
 
973
- const handleBodyScroll = useCallback(() => {
1055
+ // Keep header + body horizontal offsets locked. When the scrollbar lives under
1056
+ // the column headers (`horizontalScrollbarPosition="top"`), the header is the
1057
+ // scroll owner and the body mirrors it (and vice versa for the default bottom bar).
1058
+ const isSyncingHorizontalScrollRef = useRef(false);
1059
+ const syncHorizontalScroll = useCallback((source: "header" | "body") => {
974
1060
  const body = scrollContainerRef.current;
975
1061
  const header = headerScrollRef.current;
976
- if (body && header) header.scrollLeft = body.scrollLeft;
1062
+ if (body == null || header == null || isSyncingHorizontalScrollRef.current) return;
1063
+ const from = source === "header" ? header : body;
1064
+ const to = source === "header" ? body : header;
1065
+ if (to.scrollLeft === from.scrollLeft) return;
1066
+ isSyncingHorizontalScrollRef.current = true;
1067
+ to.scrollLeft = from.scrollLeft;
1068
+ isSyncingHorizontalScrollRef.current = false;
977
1069
  }, []);
978
1070
 
1071
+ const handleBodyScroll = useCallback(() => {
1072
+ syncHorizontalScroll("body");
1073
+ }, [syncHorizontalScroll]);
1074
+
1075
+ const handleHeaderScroll = useCallback(() => {
1076
+ syncHorizontalScroll("header");
1077
+ }, [syncHorizontalScroll]);
1078
+
1079
+ const horizontalScrollbarAtTop = horizontalScrollbarPosition === "top";
1080
+ // Shared thin thumb styling. When the bar is on top, the header scrollport shows
1081
+ // it; the body keeps overflow-x hidden so a second thumb doesn't appear at the bottom.
1082
+ // Trackpad / shift-wheel horizontal gestures land on the body; with the top
1083
+ // scrollbar the body has overflow-x:hidden, so forward deltaX to the header.
1084
+ const handleBodyWheel = useCallback((event: React.WheelEvent<HTMLDivElement>) => {
1085
+ if (!horizontalScrollbarAtTop || event.deltaX === 0) return;
1086
+ const header = headerScrollRef.current;
1087
+ if (header == null) return;
1088
+ header.scrollLeft += event.deltaX;
1089
+ }, [horizontalScrollbarAtTop]);
1090
+
979
1091
  // ── Toolbar / Footer context ─────────────────────────────────
980
1092
  const toolbarCtx: DataGridToolbarContext<TRow> = useMemo(
981
1093
  () => ({
@@ -1084,34 +1196,46 @@ export function DataGrid<TRow>(props: DataGridProps<TRow>) {
1084
1196
  )}
1085
1197
  <div
1086
1198
  ref={headerScrollRef}
1087
- className="w-full min-w-0 shrink-0 overflow-hidden border-b border-foreground/[0.06]"
1199
+ className={cn(
1200
+ "w-full min-w-0 shrink-0 border-b border-foreground/[0.06]",
1201
+ horizontalScrollbarAtTop
1202
+ ? cn("overflow-x-auto overflow-y-hidden", DATA_GRID_SCROLLBAR_CLASS_NAME)
1203
+ : "overflow-hidden",
1204
+ )}
1205
+ onScroll={horizontalScrollbarAtTop ? handleHeaderScroll : undefined}
1088
1206
  >
1089
1207
  <div
1090
1208
  className="flex"
1091
- style={{ height: headerHeight, minWidth: totalContentWidth }}
1209
+ style={{ height: headerHeight, minWidth: totalContentWidth || undefined, width: visibleColumns.length === 0 ? "100%" : undefined }}
1092
1210
  role="row"
1093
1211
  >
1094
- {selectionMode !== "none" && (
1095
- <div
1096
- className="flex items-center justify-center border-r border-foreground/[0.04]"
1097
- style={{ width: 44 }}
1098
- >
1099
- {selectionMode === "multiple" && (
1100
- <SelectionCheckbox
1101
- checked={allSelected}
1102
- indeterminate={someSelected}
1103
- onChange={handleSelectAll}
1104
- ariaLabel="Select all rows on this page"
1105
- title="Select all rows on this page"
1106
- />
1212
+ {isLoading && visibleColumns.length === 0 ? (
1213
+ <SchemaPendingHeaderCells showCheckbox={selectionMode !== "none"} />
1214
+ ) : (
1215
+ <>
1216
+ {selectionMode !== "none" && (
1217
+ <div
1218
+ className="flex items-center justify-center border-r border-foreground/[0.04]"
1219
+ style={{ width: 44 }}
1220
+ >
1221
+ {selectionMode === "multiple" && (
1222
+ <SelectionCheckbox
1223
+ checked={allSelected}
1224
+ indeterminate={someSelected}
1225
+ onChange={handleSelectAll}
1226
+ ariaLabel="Select all rows on this page"
1227
+ title="Select all rows on this page"
1228
+ />
1229
+ )}
1230
+ </div>
1107
1231
  )}
1108
- </div>
1232
+ {visibleColumns.map((col) => {
1233
+ const header = headerByColId.get(col.id);
1234
+ if (!header) return null;
1235
+ return <HeaderCell key={col.id} header={header} col={col} resizable={resizable} />;
1236
+ })}
1237
+ </>
1109
1238
  )}
1110
- {visibleColumns.map((col) => {
1111
- const header = headerByColId.get(col.id);
1112
- if (!header) return null;
1113
- return <HeaderCell key={col.id} header={header} col={col} resizable={resizable} />;
1114
- })}
1115
1239
  </div>
1116
1240
  </div>
1117
1241
  </div>
@@ -1120,14 +1244,15 @@ export function DataGrid<TRow>(props: DataGridProps<TRow>) {
1120
1244
  <div
1121
1245
  ref={scrollContainerRef}
1122
1246
  className={cn(
1123
- "relative z-0 w-full min-w-0 overflow-auto bg-transparent",
1247
+ "relative z-0 w-full min-w-0 bg-transparent",
1248
+ // Top scrollbar: body is y-only so the horizontal thumb isn't duplicated
1249
+ // under the rows. scrollLeft is still set programmatically from the header.
1250
+ horizontalScrollbarAtTop ? "overflow-y-auto overflow-x-hidden" : "overflow-auto",
1124
1251
  isBounded ? "min-h-0 flex-1" : "flex-none",
1125
- "[&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar]:h-1.5",
1126
- "[&::-webkit-scrollbar-track]:bg-transparent",
1127
- "[&::-webkit-scrollbar-thumb]:bg-foreground/[0.08] [&::-webkit-scrollbar-thumb]:rounded-full",
1128
- "[&::-webkit-scrollbar-thumb]:hover:bg-foreground/[0.15]",
1252
+ DATA_GRID_SCROLLBAR_CLASS_NAME,
1129
1253
  )}
1130
1254
  onScroll={handleBodyScroll}
1255
+ onWheel={horizontalScrollbarAtTop ? handleBodyWheel : undefined}
1131
1256
  >
1132
1257
  <div
1133
1258
  ref={rowsClipRef}
@@ -1139,15 +1264,27 @@ export function DataGrid<TRow>(props: DataGridProps<TRow>) {
1139
1264
  }}
1140
1265
  >
1141
1266
  {isLoading && (
1142
- <div style={{ minWidth: totalContentWidth }}>
1143
- {loadingState ?? Array.from({ length: 8 }).map((_, i) => (
1144
- <SkeletonRow
1145
- key={i}
1146
- columns={visibleColumns}
1147
- height={estimatedRowHeight}
1148
- showCheckbox={selectionMode !== "none"}
1149
- />
1150
- ))}
1267
+ <div style={{ minWidth: totalContentWidth || undefined, width: visibleColumns.length === 0 ? "100%" : undefined }}>
1268
+ {loadingState ?? (
1269
+ visibleColumns.length === 0 ? (
1270
+ // Schema not known yet (common for query grids that discover
1271
+ // columns from the first page) — fall back to a full-width
1272
+ // shimmer so loading never looks like an empty black pane.
1273
+ <SchemaPendingSkeleton
1274
+ rowHeight={estimatedRowHeight}
1275
+ showCheckbox={selectionMode !== "none"}
1276
+ />
1277
+ ) : (
1278
+ Array.from({ length: 8 }).map((_, i) => (
1279
+ <SkeletonRow
1280
+ key={i}
1281
+ columns={visibleColumns}
1282
+ height={estimatedRowHeight}
1283
+ showCheckbox={selectionMode !== "none"}
1284
+ />
1285
+ ))
1286
+ )
1287
+ )}
1151
1288
  </div>
1152
1289
  )}
1153
1290
 
@@ -345,6 +345,13 @@ export type DataGridProps<TRow> = {
345
345
  fillHeight?: boolean;
346
346
  /** Top offset for the sticky toolbar + header (px or CSS string). */
347
347
  stickyTop?: number | string;
348
+ /**
349
+ * Where the horizontal scrollbar is shown when columns overflow.
350
+ * - `"bottom"` (default): on the row scrollport, under the last visible rows.
351
+ * - `"top"`: under the column headers (top of the table body), so wide
352
+ * tables stay reachable without scrolling to the bottom first.
353
+ */
354
+ horizontalScrollbarPosition?: "top" | "bottom";
348
355
 
349
356
  // ── Callbacks ──────────────────────────────────────────────────
350
357
  } & DataGridCallbacks<TRow> & {
@@ -1 +0,0 @@
1
- {"version":3,"file":"data-grid-AJi1TNDa.d.ts","names":[],"sources":["../src/components/data-grid/data-grid.tsx"],"mappings":";;;iBAyEgB,mCAAA,CAAoC,MAA0B,EAAlB,WAAW;;AAAvE;;;;AAA8E;AAkiB9E;;;;;;;;;;;AAAyD;;;;;;;;;;;;;;;;;;;;;iBAAzC,QAAA,OAAe,KAAA,EAAO,aAAa,CAAC,IAAA,gCAAK,GAAA,CAAA,OAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"data-grid-toolbar-BGSNGDpT.d.ts","names":[],"sources":["../src/components/data-grid/data-grid-toolbar.tsx"],"mappings":";;;;iBA6QgB,eAAA;EACd,GAAA;EACA,KAAA;EACA,YAAA;EACA;AAAA;EAEA,GAAA,EAAK,sBAAA,CAAuB,IAAA;EANd;;;;EAWd,KAAA,GAAQ,KAAA,CAAM,SAAA;EARd;;;;;EAcA,YAAA,GAAe,KAAA,CAAM,SAAA;EAAS;;;EAI9B,eAAA;AAAA,gCACD,GAAA,CAAA,OAAA"}