aplance-ui 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/package.json +1 -1
  2. package/registry/components/primitives/modal/modal.tsx +52 -13
  3. package/registry/components/primitives/popover/popover-context.ts +1 -0
  4. package/registry/components/primitives/popover/popover.tsx +13 -2
  5. package/registry/components/primitives/slider/slider.module.css +62 -5
  6. package/registry/components/primitives/slider/slider.tsx +192 -34
  7. package/registry/components/primitives/slider/types.ts +56 -3
  8. package/registry/components/primitives/slider/utils.ts +9 -0
  9. package/registry/components/ui/data-table/data-table.module.css +124 -6
  10. package/registry/components/ui/data-table/data-table.tsx +176 -88
  11. package/registry/components/ui/data-table/pagination-bar.tsx +174 -71
  12. package/registry/components/ui/data-table/types.ts +12 -3
  13. package/registry/components/ui/input-fields/common/input-field-native.module.css +52 -42
  14. package/registry/components/ui/input-fields/custom-select/custom-select.module.css +21 -1
  15. package/registry/components/ui/input-fields/custom-select/custom-select.tsx +90 -9
  16. package/registry/components/ui/input-fields/input/input.tsx +58 -29
  17. package/registry/components/ui/input-fields/media-input/media-input.module.css +29 -0
  18. package/registry/components/ui/input-fields/media-input/media-preview.tsx +25 -7
  19. package/registry/components/ui/tabs/tabs.module.css +22 -8
  20. package/registry/components/ui/tabs/tabs.tsx +5 -1
  21. package/registry/hooks/useFocusOutside.tsx +21 -6
  22. package/registry/hooks/useHasMounted.ts +22 -0
  23. package/registry/registry.json +21 -3
@@ -29,6 +29,18 @@
29
29
  }
30
30
  }
31
31
 
32
+ /* On tablet/phone the configured multi-column row can't fit. Stack every field
33
+ full width instead of overflowing the page or scrolling (a scroll container
34
+ would clip the inputs' focus rings). `!important` beats the inline
35
+ grid-template-columns the FilterBar sets per consumer; `stretch` makes each
36
+ field fill the row. */
37
+ @media (--md) {
38
+ .filter_bar {
39
+ grid-template-columns: 1fr !important;
40
+ justify-items: stretch;
41
+ }
42
+ }
43
+
32
44
  /* ─────────────────────────────────────────────
33
45
  Content wrap
34
46
  Grows to fill all space below the filter bar.
@@ -113,17 +125,32 @@
113
125
  min-height: 0;
114
126
  overflow-x: auto;
115
127
  overflow-y: auto;
128
+ /* Reserve the scrollbar gutter by DEFAULT so clientWidth is stable: the
129
+ vertical scrollbar (when rows overflow) sits in the reserved gutter instead
130
+ of stealing ~15px and triggering an unexpected horizontal scrollbar. This
131
+ default also covers the first paint before JS measures. */
132
+ scrollbar-gutter: stable;
116
133
  border: var(--border-1) solid var(--color-border);
117
- border-radius: var(--radius-md);
118
- background: var(--color-background);
119
- position: relative;
120
- }
134
+ border-radius: var(--radius-md);
135
+ background: var(--color-background);
136
+ position: relative;
137
+ }
138
+
139
+ /* Once JS confirms the body does NOT overflow vertically (no scrollbar to make
140
+ room for), release the gutter so the content fills to the border - otherwise
141
+ a few-row table shows a blank ~15px strip on the right. `data-has-scroll` is
142
+ set from `scrollHeight > clientHeight` in syncHeaderMetrics. */
143
+ .table_body_wrap[data-has-scroll="false"] {
144
+ scrollbar-gutter: auto;
145
+ }
121
146
 
122
147
  /* ─────────────────────────────────────────────
123
148
  Shared <table> - header wrap + body wrap.
124
149
  table-layout:fixed + identical widths = perfect column alignment.
125
150
  ───────────────────────────────────────────── */
126
151
  .table {
152
+ --data-table-selection-control-size: var(--spacing-9);
153
+
127
154
  border-collapse: separate;
128
155
  border-spacing: 0;
129
156
  table-layout: fixed;
@@ -295,6 +322,8 @@
295
322
  }
296
323
 
297
324
  .selection_checkbox {
325
+ --native-control-size: var(--data-table-selection-control-size);
326
+
298
327
  width: fit-content;
299
328
  min-width: fit-content;
300
329
  display: inline-flex;
@@ -532,6 +561,36 @@
532
561
  justify-content: space-between;
533
562
  gap: var(--spacing-8);
534
563
  min-width: 0;
564
+ /* When the count + controls can't fit, the WHOLE bar scrolls horizontally as
565
+ one strip rather than wrapping or clipping. The outlet hides X overflow, so
566
+ letting a cluster shrink would just clip the nav buttons - scroll the bar
567
+ instead. `space-between` still spreads them when there IS room. The vertical
568
+ padding gives the buttons' focus rings room so `overflow-x: auto` (which also
569
+ clips the Y axis) doesn't crop them. */
570
+ overflow-x: auto;
571
+ scrollbar-width: thin;
572
+ scrollbar-color: var(--color-border) transparent;
573
+ padding-block: var(--spacing-2);
574
+
575
+ > * {
576
+ flex-shrink: 0;
577
+ }
578
+ }
579
+
580
+ @media (--sm) {
581
+ .pagination_bar {
582
+ gap: var(--spacing-5);
583
+ }
584
+ }
585
+
586
+ .pagination_bar_loading {
587
+ flex-wrap: wrap;
588
+ row-gap: var(--spacing-4);
589
+ }
590
+
591
+ .loading_select_skeleton {
592
+ border-radius: var(--radius-sm);
593
+ flex-shrink: 0;
535
594
  }
536
595
 
537
596
  .pagination_left {
@@ -539,7 +598,10 @@
539
598
  align-items: center;
540
599
  gap: var(--spacing-8);
541
600
  min-width: 0;
542
- flex-shrink: 1;
601
+ }
602
+
603
+ .pagination_left_loading {
604
+ overflow: hidden;
543
605
  }
544
606
 
545
607
  .pagination_count {
@@ -557,13 +619,59 @@
557
619
  display: flex;
558
620
  align-items: center;
559
621
  gap: var(--spacing-8);
560
- flex-shrink: 0;
622
+ min-width: 0;
623
+ }
624
+
625
+ @media (--sm) {
626
+ .pagination_controls {
627
+ gap: var(--spacing-5);
628
+ }
629
+ }
630
+
631
+ .pagination_controls_loading {
632
+ flex-direction: row;
633
+ flex-wrap: wrap;
634
+ align-items: center;
635
+ justify-content: flex-end;
636
+ max-width: 100%;
637
+ row-gap: var(--spacing-4);
638
+ }
639
+
640
+ .pagination_controls_loading > * {
641
+ flex: 0 1 auto;
642
+ min-width: 0;
643
+ max-width: 100%;
644
+ }
645
+
646
+ .pagination_text_skeleton {
647
+ height: 1em;
648
+ min-width: 0;
649
+ max-width: 100%;
650
+ flex: 0 1 auto;
651
+ }
652
+
653
+ .pagination_select_skeleton,
654
+ .pagination_button_skeleton {
655
+ width: var(--spacing-11);
656
+ height: var(--spacing-11);
657
+ min-width: 0;
658
+ }
659
+
660
+ .pagination_select_skeleton {
661
+ flex: 0 1 auto;
662
+ }
663
+
664
+ .pagination_button_skeleton {
665
+ flex: 0 0 auto;
561
666
  }
562
667
 
563
668
  .page_size_wrap {
564
669
  display: flex;
565
670
  align-items: center;
566
671
  gap: var(--spacing-5);
672
+ min-width: 0;
673
+ flex-shrink: 1;
674
+ max-width: 100%;
567
675
  }
568
676
 
569
677
  .page_size_label {
@@ -589,4 +697,14 @@
589
697
  display: flex;
590
698
  align-items: center;
591
699
  gap: var(--spacing-2);
700
+ flex-shrink: 0;
701
+ }
702
+
703
+ .page_nav_loading {
704
+ flex-wrap: wrap;
705
+ justify-content: flex-end;
706
+ row-gap: var(--spacing-2);
707
+ min-width: 0;
708
+ max-width: 100%;
709
+ flex-shrink: 1;
592
710
  }
@@ -12,12 +12,12 @@ import {
12
12
  type Updater,
13
13
  } from "@tanstack/react-table";
14
14
  import {
15
- useCallback,
16
- useEffect,
17
- useLayoutEffect,
18
- useMemo,
19
- useRef,
20
- useState,
15
+ useCallback,
16
+ useEffect,
17
+ useLayoutEffect,
18
+ useMemo,
19
+ useRef,
20
+ useState,
21
21
  type CSSProperties,
22
22
  type DragEvent as ReactDragEvent,
23
23
  type ReactNode,
@@ -74,13 +74,15 @@ const EMPTY_FILTER_VALUES: Record<string, string> = {};
74
74
  const ICON_SIZE = 15;
75
75
  const SORT_ICON_SIZE = 13;
76
76
  const ICON_STROKE = 1.6;
77
- const EMPTY_COLUMN_EXTRA_WIDTHS: Record<string, number> = {};
78
- // Grace period after `isLoading` flips false before committing to the empty
79
- // state. Eliminates the flash when consumers set isLoading=false a render
80
- // before populating rows, without making genuinely-empty results feel sluggish.
81
- const EMPTY_STATE_GRACE_MS = 150;
82
- const useIsomorphicLayoutEffect =
83
- typeof window === "undefined" ? useEffect : useLayoutEffect;
77
+ const EMPTY_COLUMN_EXTRA_WIDTHS: Record<string, number> = {};
78
+ // Grace period after `isLoading` flips false before committing to the empty
79
+ // state. Eliminates the flash when consumers set isLoading=false a render
80
+ // before populating rows, without making genuinely-empty results feel sluggish.
81
+ const EMPTY_STATE_GRACE_MS = 150;
82
+
83
+ const MIN_SKELETON_ROWS = 1;
84
+ const useIsomorphicLayoutEffect =
85
+ typeof window === "undefined" ? useEffect : useLayoutEffect;
84
86
 
85
87
  export const DataTable = <TData extends { id: string }>({
86
88
  data,
@@ -106,6 +108,7 @@ export const DataTable = <TData extends { id: string }>({
106
108
  rowLabel = "row",
107
109
 
108
110
  rowsSelectable = false,
111
+ getRowCanSelect,
109
112
  rowSelection = {},
110
113
  onRowSelectionChange,
111
114
 
@@ -220,7 +223,7 @@ export const DataTable = <TData extends { id: string }>({
220
223
  const table = useReactTable<TData>({
221
224
  data,
222
225
  columns,
223
- enableRowSelection: rowsSelectable,
226
+ enableRowSelection: rowsSelectable ? (getRowCanSelect ?? true) : false,
224
227
  enableColumnResizing,
225
228
  enableColumnPinning,
226
229
  enableGrouping,
@@ -345,6 +348,8 @@ export const DataTable = <TData extends { id: string }>({
345
348
  // columns stay perfectly aligned with their body counterparts.
346
349
  const headerRef = useRef<HTMLDivElement>(null);
347
350
  const bodyRef = useRef<HTMLDivElement>(null);
351
+ const bodyTableRef = useRef<HTMLTableElement>(null);
352
+ const skeletonRowRef = useRef<HTMLTableRowElement>(null);
348
353
  const rows = table.getRowModel().rows;
349
354
  const headerCount = table.getFlatHeaders().length;
350
355
  const totalWidth = table.getTotalSize();
@@ -352,19 +357,38 @@ export const DataTable = <TData extends { id: string }>({
352
357
  const tableColumnPinning = tableState.columnPinning;
353
358
  const tableColumnSizing = tableState.columnSizing;
354
359
  const tableColumnOrder = tableState.columnOrder;
355
- const tableColumnVisibility = tableState.columnVisibility;
356
- const tableGrouping = tableState.grouping;
357
- const [bodyViewportWidth, setBodyViewportWidth] = useState(0);
358
- const bodyViewportWidthRef = useRef(0);
359
-
360
- const commitBodyViewportWidth = useCallback((nextWidth: number) => {
361
- if (bodyViewportWidthRef.current === nextWidth) return;
362
- bodyViewportWidthRef.current = nextWidth;
363
- setBodyViewportWidth(nextWidth);
364
- }, []);
365
-
366
- const syncHeaderMetrics = useCallback(() => {
367
- const body = bodyRef.current;
360
+ const tableColumnVisibility = tableState.columnVisibility;
361
+ const tableGrouping = tableState.grouping;
362
+ const [bodyViewportWidth, setBodyViewportWidth] = useState(0);
363
+ const bodyViewportWidthRef = useRef(0);
364
+ // Body wrap height tracked separately so the loading skeleton can render
365
+ // exactly the count of rows that fits the visible body. Avoids the
366
+ // "skeleton overflows + body wrap scrolls" feel during cold load.
367
+ const [bodyViewportHeight, setBodyViewportHeight] = useState(0);
368
+ const bodyViewportHeightRef = useRef(0);
369
+ const [skeletonRowHeight, setSkeletonRowHeight] = useState(0);
370
+ const skeletonRowHeightRef = useRef(0);
371
+
372
+ const commitBodyViewportWidth = useCallback((nextWidth: number) => {
373
+ if (bodyViewportWidthRef.current === nextWidth) return;
374
+ bodyViewportWidthRef.current = nextWidth;
375
+ setBodyViewportWidth(nextWidth);
376
+ }, []);
377
+
378
+ const commitBodyViewportHeight = useCallback((nextHeight: number) => {
379
+ if (bodyViewportHeightRef.current === nextHeight) return;
380
+ bodyViewportHeightRef.current = nextHeight;
381
+ setBodyViewportHeight(nextHeight);
382
+ }, []);
383
+
384
+ const commitSkeletonRowHeight = useCallback((nextHeight: number) => {
385
+ if (nextHeight <= 0 || skeletonRowHeightRef.current === nextHeight) return;
386
+ skeletonRowHeightRef.current = nextHeight;
387
+ setSkeletonRowHeight(nextHeight);
388
+ }, []);
389
+
390
+ const syncHeaderMetrics = useCallback(() => {
391
+ const body = bodyRef.current;
368
392
  const header = headerRef.current;
369
393
  if (!body || !header) return;
370
394
 
@@ -378,39 +402,44 @@ export const DataTable = <TData extends { id: string }>({
378
402
  paddingRight,
379
403
  );
380
404
  header.parentElement?.setAttribute("data-has-scroll", hasScroll.toString());
381
-
382
- // Re-sync horizontal scroll position after width changes so pinned
383
- // columns stay perfectly aligned with their body counterparts.
384
- header.scrollLeft = body.scrollLeft;
385
- commitBodyViewportWidth(body.clientWidth);
386
- }, [commitBodyViewportWidth]);
387
-
388
- useIsomorphicLayoutEffect(() => {
389
- syncHeaderMetrics();
390
- }, [
391
- rows.length,
405
+ // Mirror onto the body wrap so CSS can release the reserved scrollbar gutter
406
+ // when there's no vertical overflow (no blank strip on a few-row table).
407
+ body.setAttribute("data-has-scroll", hasScroll.toString());
408
+
409
+ // Re-sync horizontal scroll position after width changes so pinned
410
+ // columns stay perfectly aligned with their body counterparts.
411
+ header.scrollLeft = body.scrollLeft;
412
+ commitBodyViewportWidth(body.clientWidth);
413
+ commitBodyViewportHeight(body.clientHeight);
414
+ }, [commitBodyViewportWidth, commitBodyViewportHeight]);
415
+
416
+ useIsomorphicLayoutEffect(() => {
417
+ syncHeaderMetrics();
418
+ }, [
419
+ rows.length,
392
420
  headerCount,
393
421
  totalWidth,
394
422
  tableColumnPinning,
395
423
  tableColumnSizing,
396
424
  tableColumnOrder,
397
425
  tableColumnVisibility,
398
- tableGrouping,
399
- syncHeaderMetrics,
400
- ]);
401
-
402
- useIsomorphicLayoutEffect(() => {
403
- const body = bodyRef.current;
404
- if (!body) return;
405
- if (typeof ResizeObserver === "undefined") return;
406
-
407
- const observer = new ResizeObserver(syncHeaderMetrics);
408
- observer.observe(body);
426
+ tableGrouping,
427
+ syncHeaderMetrics,
428
+ ]);
429
+
430
+ useIsomorphicLayoutEffect(() => {
431
+ const body = bodyRef.current;
432
+ if (!body) return;
433
+ if (typeof ResizeObserver === "undefined") return;
434
+
435
+ const observer = new ResizeObserver(syncHeaderMetrics);
436
+ observer.observe(body);
437
+ if (bodyTableRef.current) observer.observe(bodyTableRef.current);
409
438
 
410
439
  return () => {
411
440
  observer.disconnect();
412
441
  };
413
- }, [syncHeaderMetrics]);
442
+ }, [syncHeaderMetrics, rows.length, isLoading]);
414
443
 
415
444
  useEffect(() => {
416
445
  const body = bodyRef.current;
@@ -439,7 +468,7 @@ export const DataTable = <TData extends { id: string }>({
439
468
  }, []);
440
469
 
441
470
  // ── Derived UI state ────────────────────────────────────────────────────────
442
- // Defer the empty state by a tick after a loading→idle transition so the
471
+ // Defer the empty state by a tick after a loading-to-idle transition so the
443
472
  // loader doesn't flicker into "no results" for one paint before rows land.
444
473
  // Genuinely empty results (no in-flight load) commit immediately on mount.
445
474
  const isRowsEmpty = rows.length === 0;
@@ -448,6 +477,9 @@ export const DataTable = <TData extends { id: string }>({
448
477
  );
449
478
  const wasLoadingRef = useRef(isLoading);
450
479
 
480
+ // wasLoadingRef is intentionally omitted from the dep array: it's a ref used
481
+ // to track the previous value of isLoading across renders without triggering
482
+ // re-runs. React guarantees ref objects are stable across the component lifetime.
451
483
  useEffect(() => {
452
484
  const wasLoading = wasLoadingRef.current;
453
485
  wasLoadingRef.current = isLoading;
@@ -456,18 +488,19 @@ export const DataTable = <TData extends { id: string }>({
456
488
  setShowEmptyState(false);
457
489
  return;
458
490
  }
459
- if (wasLoading) {
460
- const id = window.setTimeout(
461
- () => setShowEmptyState(true),
462
- EMPTY_STATE_GRACE_MS,
463
- );
464
- return () => window.clearTimeout(id);
491
+ if (!wasLoading) {
492
+ setShowEmptyState(true);
493
+ return;
465
494
  }
466
- setShowEmptyState(true);
495
+ const id = window.setTimeout(
496
+ () => setShowEmptyState(true),
497
+ EMPTY_STATE_GRACE_MS,
498
+ );
499
+ return () => window.clearTimeout(id);
467
500
  }, [isLoading, isRowsEmpty]);
468
501
 
469
502
  // Single tab-like discriminator drives the body - loader, empty, or content.
470
- // While loading OR while bridging the loading→empty grace window, the
503
+ // While loading OR while bridging the loading-to-empty grace window, the
471
504
  // loader stays visible to avoid the brief "no results" flash.
472
505
  const view: "loader" | "empty" | "content" =
473
506
  isLoading || (isRowsEmpty && !showEmptyState)
@@ -476,12 +509,40 @@ export const DataTable = <TData extends { id: string }>({
476
509
  ? "empty"
477
510
  : "content";
478
511
 
512
+ useIsomorphicLayoutEffect(() => {
513
+ if (view !== "loader") return;
514
+ const row = skeletonRowRef.current;
515
+ if (!row) return;
516
+
517
+ const measureRow = () => {
518
+ commitSkeletonRowHeight(Math.ceil(row.getBoundingClientRect().height));
519
+ };
520
+
521
+ measureRow();
522
+ if (typeof ResizeObserver === "undefined") return;
523
+
524
+ const observer = new ResizeObserver(measureRow);
525
+ observer.observe(row);
526
+
527
+ return () => {
528
+ observer.disconnect();
529
+ };
530
+ }, [commitSkeletonRowHeight, view]);
531
+
479
532
  const selectedCount = rowsSelectable
480
533
  ? table.getSelectedRowModel().flatRows.length
481
534
  : 0;
482
535
  const headerGroups = table.getHeaderGroups();
483
536
  const visibleLeafColumns = table.getVisibleLeafColumns();
484
537
 
538
+ const skeletonRowCount =
539
+ bodyViewportHeight > 0 && skeletonRowHeight > 0
540
+ ? Math.max(
541
+ MIN_SKELETON_ROWS,
542
+ Math.floor(bodyViewportHeight / skeletonRowHeight),
543
+ )
544
+ : MIN_SKELETON_ROWS;
545
+
485
546
  const columnExtraWidths = useMemo<Record<string, number>>(() => {
486
547
  const extraSpace = Math.max(0, bodyViewportWidth - totalWidth);
487
548
  if (extraSpace <= 0) return EMPTY_COLUMN_EXTRA_WIDTHS;
@@ -511,14 +572,24 @@ export const DataTable = <TData extends { id: string }>({
511
572
  return next;
512
573
  }, [bodyViewportWidth, totalWidth, visibleLeafColumns]);
513
574
 
514
- // The table always fills its container while preserving configured widths as
515
- // a minimum. Any extra viewport space is distributed across visible center
516
- // columns, keeping pinned offsets stable and avoiding an empty right gutter.
575
+ const tableWidth = Math.max(totalWidth, bodyViewportWidth);
576
+
577
+ // Match the table element width to the measured column widths. This keeps
578
+ // header, body, and skeleton rows on the same sizing contract during resize.
517
579
  const tableSizingStyle: CSSProperties = useMemo(
518
- () => ({ width: "100%", minWidth: `${totalWidth}px` }),
519
- [totalWidth],
580
+ () => ({ width: `${tableWidth}px`, minWidth: `${totalWidth}px` }),
581
+ [tableWidth, totalWidth],
520
582
  );
521
583
 
584
+ const bodySkeletonStyle: CSSProperties = {
585
+ height: "var(--spacing-9)",
586
+ };
587
+
588
+ const selectSkeletonStyle: CSSProperties = {
589
+ width: "var(--data-table-selection-control-size, var(--spacing-9))",
590
+ height: "var(--data-table-selection-control-size, var(--spacing-9))",
591
+ };
592
+
522
593
  const tableClass = clsx(
523
594
  s.table,
524
595
  showVerticalBorders && s.table_vertical_borders,
@@ -588,38 +659,50 @@ export const DataTable = <TData extends { id: string }>({
588
659
  {view === "loader" && (
589
660
  <table className={tableClass} style={tableSizingStyle}>
590
661
  <tbody>
591
- {Array.from({ length: pagination?.pageSize || 10 }).map(
592
- (_, i) => (
593
- <tr key={`skeleton-${i}`}>
594
- {visibleLeafColumns.map((col) => {
595
- const isPinned = col.getIsPinned();
596
- const isLastPinnedCol = isPinned && isLastPinned(col);
597
- const isSelectColumn = col.id === SELECT_COLUMN_ID;
598
- const baseStyle = getColumnStyle(
599
- col,
600
- col.getSize(),
601
- columnExtraWidths[col.id] ?? 0,
602
- );
662
+ {Array.from({ length: skeletonRowCount }).map(
663
+ (_, rowIndex) => (
664
+ <tr
665
+ key={`skeleton-${rowIndex}`}
666
+ ref={rowIndex === 0 ? skeletonRowRef : undefined}
667
+ >
668
+ {visibleLeafColumns.map((column) => {
669
+ const isPinned = column.getIsPinned();
670
+ const isLastPinnedCol =
671
+ isPinned && isLastPinned(column);
672
+ const isSelectColumn = column.id === SELECT_COLUMN_ID;
603
673
 
604
674
  return (
605
675
  <td
606
- key={col.id}
676
+ key={column.id}
607
677
  className={clsx(
608
678
  isSelectColumn && s.col_select,
609
679
  isPinned === "left" && s.col_pinned_left,
610
680
  isPinned === "right" && s.col_pinned_right,
611
681
  isLastPinnedCol && s.col_pinned_last,
612
682
  )}
613
- style={baseStyle}
614
- >
615
- <div className={s.td_inner}>
616
- <Loader
617
- variant="skeleton"
618
- style={{ height: "var(--spacing-9)" }}
619
- />
620
- </div>
621
- </td>
622
- );
683
+ style={getColumnStyle(
684
+ column,
685
+ column.getSize(),
686
+ columnExtraWidths[column.id] ?? 0,
687
+ )}
688
+ >
689
+ <div className={s.td_inner}>
690
+ <Loader
691
+ variant="skeleton"
692
+ className={
693
+ isSelectColumn
694
+ ? s.loading_select_skeleton
695
+ : undefined
696
+ }
697
+ style={
698
+ isSelectColumn
699
+ ? selectSkeletonStyle
700
+ : bodySkeletonStyle
701
+ }
702
+ />
703
+ </div>
704
+ </td>
705
+ );
623
706
  })}
624
707
  </tr>
625
708
  ),
@@ -633,7 +716,11 @@ export const DataTable = <TData extends { id: string }>({
633
716
  </div>
634
717
  )}
635
718
  {view === "content" && (
636
- <table className={tableClass} style={tableSizingStyle}>
719
+ <table
720
+ className={tableClass}
721
+ style={tableSizingStyle}
722
+ ref={bodyTableRef}
723
+ >
637
724
  <tbody>
638
725
  {rows.map((row) => (
639
726
  <BodyRow<TData>
@@ -666,6 +753,7 @@ export const DataTable = <TData extends { id: string }>({
666
753
  onPaginationChange={onPaginationChange}
667
754
  pageSizeOptions={pageSizeOptions}
668
755
  showPageSizeSelect={showPageSizeSelect}
756
+ isLoading={view === "loader"}
669
757
  totalCount={totalCount}
670
758
  rowLabel={rowLabel}
671
759
  rowsSelectable={rowsSelectable}