playbook_ui 14.22.0.pre.rc.2 → 14.22.0.pre.rc.3

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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/Components/VirtualizedTableView.tsx +36 -16
  3. data/app/pb_kits/playbook/pb_advanced_table/Context/AdvancedTableContext.tsx +18 -5
  4. data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableActions.ts +37 -17
  5. data/app/pb_kits/playbook/pb_advanced_table/SubKits/TableBody.tsx +3 -0
  6. data/app/pb_kits/playbook/pb_advanced_table/SubKits/TableHeader.tsx +91 -40
  7. data/app/pb_kits/playbook/pb_advanced_table/Utilities/TableContainerStyles.ts +3 -2
  8. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +32 -4
  9. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +32 -18
  10. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +20 -1
  11. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_infinite_scroll.md +3 -0
  12. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +1 -0
  13. data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +2 -1
  14. data/app/pb_kits/playbook/pb_filter/Filter/FilterSection.tsx +49 -0
  15. data/app/pb_kits/playbook/pb_filter/Filter/FilterSidebar.tsx +69 -0
  16. data/app/pb_kits/playbook/pb_filter/Filter/index.tsx +13 -0
  17. data/app/pb_kits/playbook/pb_filter/_filter.scss +4 -0
  18. data/app/pb_kits/playbook/pb_filter/docs/_filter_sidebar.jsx +224 -0
  19. data/app/pb_kits/playbook/pb_filter/docs/example.yml +1 -0
  20. data/app/pb_kits/playbook/pb_filter/docs/index.js +1 -0
  21. data/dist/chunks/{_typeahead-CoOpeYom.js → _typeahead-CVIBi3oA.js} +2 -2
  22. data/dist/chunks/_weekday_stacked-BbF2JNq2.js +45 -0
  23. data/dist/chunks/vendor.js +1 -1
  24. data/dist/playbook-doc.js +1 -1
  25. data/dist/playbook-rails-react-bindings.js +1 -1
  26. data/dist/playbook-rails.js +1 -1
  27. data/dist/playbook.css +1 -1
  28. data/lib/playbook/version.rb +1 -1
  29. metadata +8 -4
  30. data/dist/chunks/_weekday_stacked-JnoR3mIy.js +0 -45
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 60de2699b3213e11373d3894f23082a1bd0f1975413dd47e56a9ac1a05cc5fe0
4
- data.tar.gz: 2e486251afd8b0ea3866755887d78e31a7cc6a96d28e78f76a1ebcce67072e6c
3
+ metadata.gz: 758567d131c4ffac31b6b9dfaf532518031be4ee6923c3078111d582eb282dea
4
+ data.tar.gz: df96ff270bdbbd7596b9c465baa69ffd9ae394133347f67a130284ea3ba5c366
5
5
  SHA512:
6
- metadata.gz: f924a62c189edfd0462efa80f3f3c6960044b67194ff47212a261deea88e5dc81939cee8d816436bff37b322e4fd41ca0baf4f7aa768d61c71fa7d8c3c6f8652
7
- data.tar.gz: 1498242e9f85a89aadc89e1a1f78528b4a2638e7544ad1907910e1b301dbc61f23e95041a2a961d47660277efc9ea08aacee30af13e1a811f872b45c812c9f80
6
+ metadata.gz: 91a15df0481aeb874fe3d03c750fbb172eab5b852e7279be6579399b11ea70cd736954fd1351f8d2e83416605a6e712b891e895a32544db347581b231b0f2f72
7
+ data.tar.gz: 98e02a517893e29d1903a37a5d2472f8bb2896482d2e03a14106d8bb7a423104d53efc90a4b96c658de9a550d26683c410604e885c572c11b0f19ed079e4e84c
@@ -1,6 +1,6 @@
1
- import React, { useContext, useLayoutEffect, useState, useEffect } from "react"
1
+ import React, { useContext, useLayoutEffect, useState, useEffect, useRef } from "react"
2
2
  import classnames from "classnames"
3
- import { flexRender, Cell } from "@tanstack/react-table"
3
+ import { flexRender, Cell, Row } from "@tanstack/react-table"
4
4
  import { VirtualItem } from "@tanstack/react-virtual"
5
5
 
6
6
  import { GenericObject } from "../../types"
@@ -10,6 +10,8 @@ import { getVirtualizedRowStyle } from "../Utilities/TableContainerStyles"
10
10
 
11
11
  import LoadingInline from "../../pb_loading_inline/_loading_inline"
12
12
  import Checkbox from "../../pb_checkbox/_checkbox"
13
+ import Detail from "../../pb_detail/_detail"
14
+ import Flex from "../../pb_flex/_flex"
13
15
 
14
16
  import { SubRowHeaderRow } from "../Components/SubRowHeaderRow"
15
17
  import { LoadingCell } from "../Components/LoadingCell"
@@ -20,11 +22,13 @@ import AdvancedTableContext from "../Context/AdvancedTableContext"
20
22
  type VirtualizedTableViewProps = {
21
23
  collapsibleTrail?: boolean
22
24
  subRowHeaders?: string[]
25
+ isFetching: boolean
23
26
  }
24
27
 
25
28
  export const VirtualizedTableView = ({
26
29
  collapsibleTrail = true,
27
30
  subRowHeaders,
31
+ isFetching,
28
32
  }: VirtualizedTableViewProps) => {
29
33
  const {
30
34
  enableToggleExpansion,
@@ -36,6 +40,7 @@ export const VirtualizedTableView = ({
36
40
  hasAnySubRows,
37
41
  virtualizer,
38
42
  flattenedItems,
43
+ totalAvailableCount,
39
44
  } = useContext(AdvancedTableContext)
40
45
 
41
46
  const columnPinning = table.getState().columnPinning || { left: [] };
@@ -121,19 +126,7 @@ export const VirtualizedTableView = ({
121
126
  }
122
127
 
123
128
  // Get virtual items
124
- let virtualItems: VirtualItem[] = [];
125
- try {
126
- virtualItems = virtualizer.getVirtualItems();
127
- } catch (err) {
128
- return (
129
- <tr>
130
- <td colSpan={table.getAllFlatColumns().length || 1}>
131
- Error loading virtualized data.
132
- </td>
133
- </tr>
134
- );
135
- }
136
-
129
+ const virtualItems: VirtualItem[] = virtualizer.getVirtualItems?.() || [];
137
130
  if (!virtualItems.length) {
138
131
  return (
139
132
  <tr>
@@ -143,6 +136,9 @@ export const VirtualizedTableView = ({
143
136
  </tr>
144
137
  );
145
138
  }
139
+
140
+ // Establish # of Parent Rows (so that Footer count does not include every single row)
141
+ const topLevelRowCount = table.getRowModel().flatRows.filter((row: Row<GenericObject>) => row.depth === 0).length;
146
142
 
147
143
  return (
148
144
  <>
@@ -177,7 +173,7 @@ export const VirtualizedTableView = ({
177
173
  if (item.type === 'row') {
178
174
  const row = item.row;
179
175
  const isExpandable = row.getIsExpanded();
180
- const rowHasNoChildren = row.original?.children && !row.original.children.length ? true : false;
176
+ const rowHasNoChildren = row.original?.children && !row.original.children.length;
181
177
  const rowBackground = isExpandable && ((!inlineRowLoading && row.getCanExpand()) || (inlineRowLoading && rowHasNoChildren));
182
178
  const rowColor = row.getIsSelected() ? "bg-row-selection" : rowBackground ? "bg-silver" : "bg-white";
183
179
 
@@ -266,6 +262,30 @@ export const VirtualizedTableView = ({
266
262
  );
267
263
  }
268
264
 
265
+ if (item.type === 'footer') {
266
+ // Render footer
267
+ return (
268
+ <tr
269
+ className="virtualized-table-row virtualized-footer"
270
+ key={`footer-row`}
271
+ style={virtualItemStyle}
272
+ >
273
+ <td colSpan={table.getAllFlatColumns().length}>
274
+ <Flex align="center"
275
+ justify="center"
276
+ >
277
+
278
+ {isFetching ? (
279
+ <LoadingInline />
280
+ ) : (
281
+ <Detail text={`Showing ${topLevelRowCount} of ${totalAvailableCount} rows`} />
282
+ )}
283
+ </Flex>
284
+ </td>
285
+ </tr>
286
+ )
287
+ }
288
+
269
289
  return null;
270
290
  })}
271
291
  </>
@@ -8,7 +8,7 @@ import { getRowHeightEstimate } from '../Utilities/TableContainerStyles';
8
8
  const AdvancedTableContext = createContext<any>({});
9
9
 
10
10
  interface FlattenedItem {
11
- type: 'header' | 'row' | 'loading';
11
+ type: 'header' | 'row' | 'loading' | 'footer';
12
12
  row: Row<GenericObject>;
13
13
  id: string;
14
14
  }
@@ -116,6 +116,17 @@ export const AdvancedTableProvider = ({ children, ...props }: {
116
116
  }
117
117
  });
118
118
 
119
+ const isFetching = props.isFetching || false;
120
+ const shouldAddFooter = table && !isFetching && tableRows.length > 0
121
+
122
+ if (shouldAddFooter) {
123
+ items.push({
124
+ type: 'footer',
125
+ row: {} as Row<GenericObject>,
126
+ id: `footer-row`,
127
+ });
128
+ }
129
+
119
130
  return items;
120
131
  }, [
121
132
  isVirtualized,
@@ -159,10 +170,11 @@ export const AdvancedTableProvider = ({ children, ...props }: {
159
170
  useEffect(() => {
160
171
  if (isVirtualized && virtualizer && table && containerRef.current) {
161
172
  // Force recalculation of all virtual items
173
+ virtualizer.setOptions({
174
+ ...virtualizer.options,
175
+ count: flattenedItems.length,
176
+ });
162
177
  virtualizer.measure();
163
-
164
- // Reset scroll position when sorting changes
165
- containerRef.current.scrollTop = 0;
166
178
  }
167
179
  }, [
168
180
  isVirtualized,
@@ -170,7 +182,8 @@ export const AdvancedTableProvider = ({ children, ...props }: {
170
182
  table,
171
183
  containerRef,
172
184
  JSON.stringify(table?.getState().sorting || []),
173
- JSON.stringify(table?.getState().expanded || {})
185
+ JSON.stringify(table?.getState().expanded || {}),
186
+ flattenedItems.length,
174
187
  ]);
175
188
 
176
189
  const contextValue = {
@@ -1,4 +1,4 @@
1
- import { useCallback, useEffect } from 'react';
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
2
  import { Row, RowPinningState } from "@tanstack/react-table";
3
3
  import { GenericObject } from "../../types";
4
4
  import { updateExpandAndCollapseState } from "../Utilities/ExpansionControlHelpers";
@@ -19,13 +19,21 @@ export function useTableActions({
19
19
  onRowSelectionChange
20
20
  }: UseTableActionsProps) {
21
21
 
22
+ // State to achieve 1 second delay before fetching more rows
23
+ const [bottomReached, setBottomReached] = useState(false)
24
+ const bottomTimeout = useRef<NodeJS.Timeout | null>(null)
25
+
22
26
  // Handle expand/collapse
23
27
  const handleExpandOrCollapse = useCallback(async (row: Row<GenericObject>) => {
24
- onToggleExpansionClick && onToggleExpansionClick(row);
25
- const expandedState = expanded;
26
- const targetParent = row?.parentId;
27
- const updatedRows = await updateExpandAndCollapseState(table.getRowModel(), expandedState, targetParent, undefined);
28
- setExpanded(updatedRows);
28
+ if (onToggleExpansionClick) onToggleExpansionClick(row)
29
+ const updatedExpandedState = await updateExpandAndCollapseState(
30
+ table.getRowModel(),
31
+ expanded,
32
+ row?.parentId,
33
+ undefined
34
+ )
35
+
36
+ setExpanded(updatedExpandedState)
29
37
  }, [expanded, setExpanded, onToggleExpansionClick, table]);
30
38
 
31
39
  // Handle pagination
@@ -35,20 +43,32 @@ export function useTableActions({
35
43
 
36
44
  // Handle scroll detection for infinite scroll/virtualization
37
45
  const fetchMoreOnBottomReached = useCallback((
38
- containerRefElement: HTMLDivElement | null,
39
- fetchNextPage: () => void,
40
- isFetching: boolean,
41
- totalFetched: number,
42
- totalDBRowCount: number
46
+ containerRef: HTMLDivElement | null,
47
+ fetchNextPage: () => void,
48
+ isFetching: boolean,
49
+ totalFetched: number,
50
+ totalDBRowCount: number
43
51
  ) => {
44
- if (containerRefElement) {
45
- const { scrollHeight, scrollTop, clientHeight } = containerRefElement;
46
- // If user scrolls near bottom, fetch more data
47
- if (scrollHeight - scrollTop - clientHeight < 500 && !isFetching && totalFetched < totalDBRowCount) {
48
- fetchNextPage();
52
+ if (!containerRef || isFetching || totalFetched >= totalDBRowCount) return
53
+ const { scrollTop, scrollHeight, clientHeight } = containerRef
54
+ const distanceFromBottom = scrollHeight - scrollTop - clientHeight
55
+ // If user scrolls near bottom, fetch more data after 1 second delay
56
+ if (distanceFromBottom < 50) {
57
+ if (!bottomReached) {
58
+ setBottomReached(true)
59
+ bottomTimeout.current = setTimeout(() => {
60
+ fetchNextPage()
61
+ setBottomReached(false)
62
+ }, 1000)
63
+ }
64
+ } else {
65
+ setBottomReached(false)
66
+ if (bottomTimeout.current) {
67
+ clearTimeout(bottomTimeout.current)
68
+ bottomTimeout.current = null
49
69
  }
50
70
  }
51
- }, []);
71
+ },[bottomReached]);
52
72
 
53
73
  // Update selection state
54
74
  useEffect(() => {
@@ -16,6 +16,7 @@ type TableBodyProps = {
16
16
  dark?: boolean
17
17
  id?: string
18
18
  subRowHeaders?: string[]
19
+ isFetching: boolean
19
20
  }
20
21
 
21
22
  export const TableBody = ({
@@ -24,6 +25,7 @@ export const TableBody = ({
24
25
  dark = false,
25
26
  id,
26
27
  subRowHeaders,
28
+ isFetching,
27
29
  ...props
28
30
  }: TableBodyProps) => {
29
31
 
@@ -63,6 +65,7 @@ export const TableBody = ({
63
65
  // Virtualized table view
64
66
  <VirtualizedTableView
65
67
  collapsibleTrail={collapsibleTrail}
68
+ isFetching={isFetching}
66
69
  subRowHeaders={subRowHeaders}
67
70
  />
68
71
  ) : (
@@ -40,9 +40,13 @@ export const TableHeader = ({
40
40
  showActionsBar,
41
41
  selectableRows,
42
42
  responsive,
43
- headerRef
43
+ headerRef,
44
+ virtualizedRows,
45
+ enableVirtualization,
44
46
  } = useContext(AdvancedTableContext)
45
47
 
48
+ const isVirtualized = virtualizedRows || enableVirtualization;
49
+
46
50
  const classes = classnames(
47
51
  buildCss("pb_advanced_table_header"),
48
52
  globalProps(props),
@@ -57,46 +61,93 @@ export const TableHeader = ({
57
61
  `${isChrome() ? "chrome-styles" : ""}`,
58
62
  `${responsive === "scroll" && "pinned-left"}`,
59
63
  );
64
+
65
+ const renderRegularTableHeader = () => (
66
+ <thead className={classes}
67
+ id={id}
68
+ >
69
+ {table.getHeaderGroups().map((headerGroup: HeaderGroup<GenericObject>, index: number) => (
70
+ <tr
71
+ key={`${headerGroup.id}-headerGroup`}
72
+ ref={index === 0 ? headerRef : null}
73
+ >
74
+ {!hasAnySubRows && selectableRows && (
75
+ <th className={customCellClassnames}>
76
+ <Checkbox
77
+ checked={table?.getIsAllRowsSelected()}
78
+ indeterminate={table?.getIsSomeRowsSelected()}
79
+ onChange={table?.getToggleAllRowsSelectedHandler()}
80
+ />
81
+ </th>
82
+ )}
83
+ {headerGroup.headers.map(header => {
84
+ const isPinnedLeft = columnPinning.left.includes(header.id)
85
+ return (
86
+ <TableHeaderCell
87
+ enableSorting={enableSorting}
88
+ enableToggleExpansion={enableToggleExpansion}
89
+ handleExpandOrCollapse={handleExpandOrCollapse}
90
+ header={header}
91
+ headerChildren={children}
92
+ isPinnedLeft={isPinnedLeft}
93
+ key={`${header.id}-header`}
94
+ loading={loading}
95
+ sortIcon={sortIcon}
96
+ table={table}
97
+ />
98
+ )
99
+ })}
100
+ </tr>
101
+ ))}
102
+ </thead>
103
+ );
104
+
105
+ const renderVirtualizedTableHeader = () => (
106
+ <thead
107
+ className={classes}
108
+ data-virtualized="true"
109
+ id={id}
110
+ >
111
+ {table.getHeaderGroups().map((headerGroup: HeaderGroup<GenericObject>, index: number) => (
112
+ <tr
113
+ className="virtualized-header-row-header"
114
+ key={`${headerGroup.id}-headerGroup-virtualized`}
115
+ ref={index === 0 ? headerRef : null}
116
+ >
117
+ {!hasAnySubRows && selectableRows && (
118
+ <th className={classnames(customCellClassnames, "virtualized-header-cell")}>
119
+ <Checkbox
120
+ checked={table?.getIsAllRowsSelected()}
121
+ indeterminate={table?.getIsSomeRowsSelected()}
122
+ onChange={table?.getToggleAllRowsSelectedHandler()}
123
+ />
124
+ </th>
125
+ )}
126
+ {headerGroup.headers.map(header => {
127
+ const isPinnedLeft = columnPinning.left.includes(header.id)
128
+ return (
129
+ <TableHeaderCell
130
+ enableSorting={enableSorting}
131
+ enableToggleExpansion={enableToggleExpansion}
132
+ handleExpandOrCollapse={handleExpandOrCollapse}
133
+ header={header}
134
+ headerChildren={children}
135
+ isPinnedLeft={isPinnedLeft}
136
+ isVirtualized
137
+ key={`${header.id}-header-virtualized`}
138
+ loading={loading}
139
+ sortIcon={sortIcon}
140
+ table={table}
141
+ />
142
+ )
143
+ })}
144
+ </tr>
145
+ ))}
146
+ </thead>
147
+ );
60
148
  return (
61
- <>
62
- <thead className={classes}
63
- id={id}
64
- >
65
- {/* Get the header groups (only one in this example) */}
66
- {table.getHeaderGroups().map((headerGroup: HeaderGroup<GenericObject>, index: number) => (
67
- <tr
68
- key={`${headerGroup.id}-headerGroup`}
69
- ref={index === 0 ? headerRef : null}
70
- >
71
- {!hasAnySubRows && selectableRows && (
72
- <th className={customCellClassnames}>
73
- <Checkbox
74
- checked={table?.getIsAllRowsSelected()}
75
- indeterminate={table?.getIsSomeRowsSelected()}
76
- onChange={table?.getToggleAllRowsSelectedHandler()}
77
- />
78
- </th>
79
- )}
80
- {headerGroup.headers.map(header => {
81
- const isPinnedLeft = columnPinning.left.includes(header.id)
82
- return (
83
- <TableHeaderCell
84
- enableSorting={enableSorting}
85
- enableToggleExpansion={enableToggleExpansion}
86
- handleExpandOrCollapse={handleExpandOrCollapse}
87
- header={header}
88
- headerChildren={children}
89
- isPinnedLeft={isPinnedLeft}
90
- key={`${header.id}-header`}
91
- loading={loading}
92
- sortIcon={sortIcon}
93
- table={table}
94
- />
95
- )
96
- })}
97
- </tr>
98
- ))}
99
- </thead>
149
+ <>
150
+ {isVirtualized ? renderVirtualizedTableHeader() : renderRegularTableHeader()}
100
151
  </>
101
152
  )
102
153
  }
@@ -57,7 +57,6 @@ export const getVirtualizedContainerStyles = (maxHeight?: string): React.CSSProp
57
57
  position: 'absolute',
58
58
  top: 0,
59
59
  left: 0,
60
- width: '100%',
61
60
  height: '40px', // Match standard table row height
62
61
  transform: `translateY(${startPosition}px)`,
63
62
  tableLayout: 'fixed',
@@ -67,12 +66,14 @@ export const getVirtualizedContainerStyles = (maxHeight?: string): React.CSSProp
67
66
  /**
68
67
  * Get height estimates for different row types
69
68
  */
70
- export const getRowHeightEstimate = (rowType: 'header' | 'row' | 'loading') => {
69
+ export const getRowHeightEstimate = (rowType: 'header' | 'row' | 'loading' | 'footer') => {
71
70
  switch (rowType) {
72
71
  case 'header':
73
72
  return 40; // Header height
74
73
  case 'loading':
75
74
  return 30; // Loading indicator height
75
+ case 'footer':
76
+ return 40
76
77
  case 'row':
77
78
  default:
78
79
  return 40; // Standard row height - match this to your design system
@@ -63,11 +63,30 @@
63
63
  width: 100%;
64
64
  }
65
65
 
66
- // Virtualized table styles
66
+ // Virtualized Table and Rows for Infinite Scroll
67
+ scrollbar-gutter: stable right-edges;
68
+ .virtualized-header-row-header {
69
+ width: 100% !important;
70
+ .table-header-cells:first-child {
71
+ min-width: 180px;
72
+ }
73
+ }
74
+
75
+ .virtualized-footer {
76
+ width: 100% !important;
77
+ td {
78
+ border-bottom-left-radius: 4px !important;
79
+ border-bottom-right-radius: 4px !important;
80
+ display: flex;
81
+ justify-content: center;
82
+ align-items: center;
83
+ }
84
+ }
85
+
67
86
  .virtualized-table-row {
68
87
  display: table !important;
69
88
  table-layout: fixed !important;
70
- width: 100% !important;
89
+ box-sizing: border-box !important;
71
90
 
72
91
  td {
73
92
  display: table-cell !important;
@@ -104,7 +123,7 @@
104
123
 
105
124
  -ms-overflow-style: none !important;
106
125
  scrollbar-width: none !important;
107
- }
126
+ }
108
127
 
109
128
 
110
129
  .row-selection-actions-card {
@@ -193,7 +212,6 @@
193
212
 
194
213
  // Fix virtualized row borders
195
214
  tr.virtualized-table-row {
196
- border-bottom: 1px solid $border_light;
197
215
 
198
216
  &.bg-silver {
199
217
  td:first-child {
@@ -606,6 +624,16 @@
606
624
  td.sticky-left {
607
625
  border-right: 1px solid $border_light !important;
608
626
  }
627
+ // Virtualized Table in Responsive Styles
628
+ .virtualized-header,
629
+ .virtualized-header-row-header,
630
+ .virtualized-table-row,
631
+ .virtualized-footer {
632
+ border-right: 1px solid $border_light !important;
633
+ .table-header-cells:first-child {
634
+ box-shadow: 0 0 10px 0 rgba($shadow, 0.16) !important;
635
+ }
636
+ }
609
637
  }
610
638
  }
611
639
  }
@@ -266,6 +266,29 @@ const AdvancedTable = (props: AdvancedTableProps) => {
266
266
  // Visibility flag for action bar
267
267
  const isActionBarVisible = (selectableRows && showActionsBar && selectedRowsLength > 0) || columnVisibilityControl;
268
268
 
269
+ // The actual Main <Table /> element
270
+ const tableElement = (
271
+ <Table
272
+ className={`${loading ? "content-loading" : ""}`}
273
+ dark={dark}
274
+ dataTable
275
+ numberSpacing="tabular"
276
+ responsive="none"
277
+ {...tableProps}
278
+ >
279
+ {children ? (
280
+ children
281
+ ) : (
282
+ <>
283
+ <TableHeader />
284
+ <TableBody
285
+ isFetching={isFetching}
286
+ />
287
+ </>
288
+ )}
289
+ </Table>
290
+ )
291
+
269
292
  return (
270
293
  <>
271
294
  {/* Top Pagination */}
@@ -322,6 +345,7 @@ const AdvancedTable = (props: AdvancedTableProps) => {
322
345
  table={table}
323
346
  tableContainerRef={tableWrapperRef}
324
347
  toggleExpansionIcon={toggleExpansionIcon}
348
+ totalAvailableCount={fullData.length}
325
349
  virtualizedRows={virtualizedRows}
326
350
  >
327
351
  <React.Fragment>
@@ -333,24 +357,14 @@ const AdvancedTable = (props: AdvancedTableProps) => {
333
357
  type={columnVisibilityControl ? "column-visibility" : "row-selection"}
334
358
  />
335
359
 
336
- {/* Main Table */}
337
- <Table
338
- className={`${loading ? "content-loading" : ""}`}
339
- dark={dark}
340
- dataTable
341
- numberSpacing="tabular"
342
- responsive="none"
343
- {...tableProps}
344
- >
345
- {children ? (
346
- children
347
- ) : (
348
- <>
349
- <TableHeader />
350
- <TableBody />
351
- </>
352
- )}
353
- </Table>
360
+ {/* Virtualized wrapper div only if virtualizedRows is true */}
361
+ {virtualizedRows ? (
362
+ <div style={{ overflow: 'auto', width: '100%' }}>
363
+ {tableElement}
364
+ </div>
365
+ ) : (
366
+ tableElement
367
+ )}
354
368
  </React.Fragment>
355
369
  </AdvancedTableProvider>
356
370
 
@@ -632,4 +632,23 @@ test("columnStyling.cellAlignment sets each <td> align attribute as expected", (
632
632
 
633
633
  const firstEnrollmentCell = screen.getAllByText("20")[0].closest("td");
634
634
  expect(firstEnrollmentCell).toHaveAttribute("align", "left");
635
- });
635
+ });
636
+
637
+ test("renders virtualized table rows and header", () => {
638
+ render(
639
+ <AdvancedTable
640
+ columnDefinitions={columnDefinitions}
641
+ data={{ testid: testId }}
642
+ tableData={MOCK_DATA_WITH_ID}
643
+ virtualizedRows
644
+ />
645
+ )
646
+
647
+ const kit = screen.getByTestId(testId)
648
+
649
+ const virtualizedHeader = kit.querySelector('.virtualized-header-row-header')
650
+ expect(virtualizedHeader).toBeInTheDocument()
651
+
652
+ const virtualizedRows = kit.querySelectorAll('.virtualized-table-row')
653
+ expect(virtualizedRows.length).toBeLessThan(MOCK_DATA_WITH_ID.length)
654
+ })
@@ -0,0 +1,3 @@
1
+ The `virtualizedRows` boolean prop enables the rendering of a virtualized table using [Tanstack's Virtualizer Library](https://tanstack.com/virtual/v3/docs/api/virtualizer) with infinite scroll capabilities for large data sets.
2
+
3
+ **Note:** Not all Advanced Table props work perfectly with the Virtualized Table - for complex table configurations with many added features or controls consider using the [Pagination](https://playbook.powerapp.cloud/kits/advanced_table/react#pagination) version instead. Additionally, it is a known issue that due to the use of absolute positioning for the virtualized header, it does not render in Safari.
@@ -61,3 +61,4 @@ examples:
61
61
  - advanced_table_scrollbar_none: Advanced Table Scrollbar None
62
62
  - advanced_table_column_styling: Column Styling
63
63
  - advanced_table_column_styling_column_headers: Column Styling with Multiple Headers
64
+ - advanced_table_infinite_scroll: Infinite Scroll
@@ -35,4 +35,5 @@ export { default as AdvancedTableColumnVisibilityWithState } from './_advanced_t
35
35
  export { default as AdvancedTablePinnedRows } from './_advanced_table_pinned_rows.jsx'
36
36
  export { default as AdvancedTableScrollbarNone} from './_advanced_table_scrollbar_none.jsx'
37
37
  export { default as AdvancedTableColumnStyling } from './_advanced_table_column_styling.jsx'
38
- export { default as AdvancedTableColumnStylingColumnHeaders } from './_advanced_table_column_styling_column_headers.jsx'
38
+ export { default as AdvancedTableColumnStylingColumnHeaders } from './_advanced_table_column_styling_column_headers.jsx'
39
+ export { default as AdvancedTableInfiniteScroll} from './_advanced_table_infinite_scroll.jsx'
@@ -0,0 +1,49 @@
1
+ import React from 'react'
2
+
3
+ import Collapsible from '../../pb_collapsible/_collapsible'
4
+ import Caption from '../../pb_caption/_caption'
5
+ import Body from '../../pb_body/_body'
6
+
7
+ type FilterSectionProps = {
8
+ children?: React.ReactChild[] | React.ReactChild,
9
+ collapsible?: boolean,
10
+ collapsed?: boolean,
11
+ headerText?: string,
12
+ }
13
+ const FilterSection = ({ children, collapsible = false, collapsed = true, headerText, }: FilterSectionProps): React.ReactElement => {
14
+ if (collapsible) {
15
+ return (
16
+ <Collapsible
17
+ collapsed={collapsed}
18
+ padding="none"
19
+ >
20
+ <Collapsible.Main
21
+ paddingX="sm"
22
+ paddingY="xs"
23
+ >
24
+ <Caption>{ headerText }</Caption>
25
+ </Collapsible.Main>
26
+ <Collapsible.Content
27
+ className="filter_section_collapsible"
28
+ paddingX="sm"
29
+ >
30
+ { children }
31
+ </Collapsible.Content>
32
+ </Collapsible>
33
+ )
34
+ }
35
+
36
+ return (
37
+ <Body
38
+ paddingTop="xs"
39
+ paddingX="sm"
40
+ >
41
+ <>
42
+ {headerText && <Caption marginBottom="sm">{ headerText }</Caption>}
43
+ { children }
44
+ </>
45
+ </Body>
46
+ )
47
+ }
48
+
49
+ export default FilterSection