playbook_ui 14.15.0.pre.rc.4 → 14.15.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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/Components/RegularTableView.tsx +127 -0
  3. data/app/pb_kits/playbook/pb_advanced_table/Components/TableActionBar.tsx +55 -0
  4. data/app/pb_kits/playbook/pb_advanced_table/Components/TablePagination.tsx +33 -0
  5. data/app/pb_kits/playbook/pb_advanced_table/Components/VirtualizedTableView.tsx +275 -0
  6. data/app/pb_kits/playbook/pb_advanced_table/Context/AdvancedTableContext.tsx +143 -3
  7. data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableActions.ts +66 -0
  8. data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableState.ts +195 -0
  9. data/app/pb_kits/playbook/pb_advanced_table/SubKits/TableBody.tsx +45 -99
  10. data/app/pb_kits/playbook/pb_advanced_table/Utilities/CellRendererUtils.tsx +73 -0
  11. data/app/pb_kits/playbook/pb_advanced_table/Utilities/RowUtils.ts +52 -0
  12. data/app/pb_kits/playbook/pb_advanced_table/Utilities/TableContainerStyles.ts +80 -0
  13. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +123 -7
  14. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +153 -299
  15. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_infinite_scroll.jsx +50 -0
  16. data/app/pb_kits/playbook/pb_advanced_table/docs/advanced_table_mock_data_infinite_scroll.json +152002 -0
  17. data/app/pb_kits/playbook/pb_card/_card.tsx +2 -1
  18. data/app/pb_kits/playbook/pb_date_picker/date_picker.html.erb +4 -1
  19. data/app/pb_kits/playbook/pb_date_picker/date_picker.rb +2 -0
  20. data/app/pb_kits/playbook/pb_date_picker/index.ts +38 -0
  21. data/app/pb_kits/playbook/pb_drawer/_drawer.scss +19 -3
  22. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_borders.jsx +3 -3
  23. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_breakpoints.jsx +20 -37
  24. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_menu.jsx +6 -6
  25. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_overlay.jsx +1 -0
  26. data/app/pb_kits/playbook/pb_drawer/docs/example.yml +1 -0
  27. data/app/pb_kits/playbook/pb_filter/Filter/CurrentFilters.tsx +5 -4
  28. data/app/pb_kits/playbook/pb_filter/Filter/FilterSingle.tsx +2 -2
  29. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate.html.erb +1 -1
  30. data/app/pb_kits/playbook/pb_form_pill/_form_pill.scss +9 -2
  31. data/app/pb_kits/playbook/pb_form_pill/_form_pill.tsx +4 -0
  32. data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_wrapped.html.erb +40 -0
  33. data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_wrapped.jsx +50 -0
  34. data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_wrapped.md +3 -0
  35. data/app/pb_kits/playbook/pb_form_pill/docs/example.yml +2 -0
  36. data/app/pb_kits/playbook/pb_form_pill/docs/index.js +1 -0
  37. data/app/pb_kits/playbook/pb_form_pill/form_pill.rb +7 -1
  38. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.scss +7 -0
  39. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +13 -3
  40. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled.html.erb +72 -0
  41. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled.jsx +91 -0
  42. data/app/pb_kits/playbook/pb_multi_level_select/docs/example.yml +2 -1
  43. data/app/pb_kits/playbook/pb_multi_level_select/docs/index.js +1 -0
  44. data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.rb +6 -0
  45. data/app/pb_kits/playbook/pb_popover/_popover.tsx +1 -1
  46. data/app/pb_kits/playbook/pb_radio/_radio.tsx +85 -74
  47. data/app/pb_kits/playbook/pb_radio/docs/_radio_react_hook.jsx +60 -0
  48. data/app/pb_kits/playbook/pb_radio/docs/_radio_react_hook.md +1 -0
  49. data/app/pb_kits/playbook/pb_radio/docs/example.yml +2 -1
  50. data/app/pb_kits/playbook/pb_radio/docs/index.js +1 -0
  51. data/app/pb_kits/playbook/pb_radio/radio.test.js +16 -0
  52. data/app/pb_kits/playbook/pb_select/docs/_select_react_hook.jsx +58 -0
  53. data/app/pb_kits/playbook/pb_select/docs/_select_react_hook.md +1 -0
  54. data/app/pb_kits/playbook/pb_select/docs/example.yml +1 -0
  55. data/app/pb_kits/playbook/pb_select/docs/index.js +1 -0
  56. data/app/pb_kits/playbook/pb_select/select.html.erb +3 -5
  57. data/app/pb_kits/playbook/pb_selectable_card/selectable_card.html.erb +1 -5
  58. data/app/pb_kits/playbook/pb_selectable_card_icon/selectable_card_icon.html.erb +1 -4
  59. data/app/pb_kits/playbook/pb_selectable_icon/selectable_icon.html.erb +1 -5
  60. data/app/pb_kits/playbook/pb_timeline/_timeline.scss +2 -2
  61. data/app/pb_kits/playbook/pb_title/_title.scss +32 -0
  62. data/app/pb_kits/playbook/pb_title/_title.tsx +10 -1
  63. data/app/pb_kits/playbook/pb_title/docs/_title_default.html.erb +1 -2
  64. data/app/pb_kits/playbook/pb_title/docs/_title_default.jsx +1 -1
  65. data/app/pb_kits/playbook/pb_title/docs/_title_display_size.html.erb +7 -0
  66. data/app/pb_kits/playbook/pb_title/docs/_title_display_size.jsx +54 -0
  67. data/app/pb_kits/playbook/pb_title/docs/_title_display_size.md +1 -0
  68. data/app/pb_kits/playbook/pb_title/docs/example.yml +2 -0
  69. data/app/pb_kits/playbook/pb_title/docs/index.js +1 -0
  70. data/app/pb_kits/playbook/pb_title/title.rb +10 -1
  71. data/app/pb_kits/playbook/pb_tooltip/_tooltip.tsx +25 -0
  72. data/app/pb_kits/playbook/pb_tooltip/docs/_tooltip_sizing.jsx +69 -0
  73. data/app/pb_kits/playbook/pb_tooltip/docs/_tooltip_sizing.md +3 -0
  74. data/app/pb_kits/playbook/pb_tooltip/docs/example.yml +1 -1
  75. data/app/pb_kits/playbook/pb_tooltip/docs/index.js +1 -0
  76. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +2 -1
  77. data/app/pb_kits/playbook/pb_typeahead/components/MultiValue.tsx +5 -1
  78. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +4 -1
  79. data/app/pb_kits/playbook/utilities/object.test.js +99 -0
  80. data/app/pb_kits/playbook/utilities/object.ts +29 -1
  81. data/dist/chunks/_typeahead-BhfaW1J9.js +36 -0
  82. data/dist/chunks/_weekday_stacked-CKRIELiF.js +45 -0
  83. data/dist/chunks/lazysizes-DHz07jlL.js +1 -0
  84. data/dist/chunks/{lib-Dmay5Z6U.js → lib-5OzNgeeu.js} +2 -2
  85. data/dist/chunks/{pb_form_validation-DdP7BnVX.js → pb_form_validation-DGhKbZtO.js} +1 -1
  86. data/dist/chunks/vendor.js +1 -1
  87. data/dist/playbook-doc.js +1 -1
  88. data/dist/playbook-rails-react-bindings.js +1 -1
  89. data/dist/playbook-rails.js +1 -1
  90. data/dist/playbook.css +1 -1
  91. data/lib/playbook/version.rb +1 -1
  92. metadata +34 -7
  93. data/dist/chunks/_typeahead-NXKDTf__.js +0 -36
  94. data/dist/chunks/_weekday_stacked-DtCYkCXM.js +0 -45
  95. data/dist/chunks/lazysizes-B7xYodB-.js +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a671aef2ddd39a78ec98fcb4073566d8d8c07c887e206ef6e83c6bea4bbeae07
4
- data.tar.gz: 79a8b005d92dfb2a80c0908349254e76a931f8e366abccbdd079bcf3b57725bd
3
+ metadata.gz: 988b1ee65101fb13b487e7fb3f8589b7b06a4510503b9a1d41dd84d1887ade9b
4
+ data.tar.gz: 2ebbaf39afdeca0386f701dc867be16170b39f8f8cbcba101e92ca8df1d9bd35
5
5
  SHA512:
6
- metadata.gz: 9e1497589d4831e05816145ea26dd5f174b94d035cdfd46ec112f973ad6ffa860821535adced274c664a81dcd356b14876a11f5162a6bb6d1a0a2fda12038799
7
- data.tar.gz: 985f3701953d47a71657c909d63303cfe60413b0a374bafc96f611fbb477b4262ab4b5011c16c31e124ca05581a66af587c7d6d15b5e99997f2efd2cd43d5ad0
6
+ metadata.gz: e500e0257cd7da6adb7a707ea08e797d8322204341eaff40613dee843ee6c79d47db18c76192204a2b29576b3b3eb9ca13d8cb5ab5c2b401a7d4e775dec39450
7
+ data.tar.gz: dbf426e01b04c62e96af16a1fc4769659c7111a0a276c6a5a2f05ae1fa2948be612447dfea2c39d5115e068ee71cc90f16c24de34a4f2791aa719d03cdb3cda2
@@ -0,0 +1,127 @@
1
+ import React, { useContext } from "react"
2
+ import classnames from "classnames"
3
+ import { flexRender, Row, Cell } from "@tanstack/react-table"
4
+
5
+ import { GenericObject } from "../../types"
6
+ import { isChrome } from "../Utilities/BrowserCheck"
7
+
8
+ import LoadingInline from "../../pb_loading_inline/_loading_inline"
9
+ import Checkbox from "../../pb_checkbox/_checkbox"
10
+
11
+ import { SubRowHeaderRow } from "../Components/SubRowHeaderRow"
12
+ import { LoadingCell } from "../Components/LoadingCell"
13
+ import { renderCollapsibleTrail } from "../Components/CollapsibleTrail"
14
+
15
+ import AdvancedTableContext from "../Context/AdvancedTableContext"
16
+
17
+ type RegularTableViewProps = {
18
+ collapsibleTrail?: boolean
19
+ subRowHeaders?: string[]
20
+ }
21
+
22
+ export const RegularTableView = ({
23
+ collapsibleTrail = true,
24
+ subRowHeaders,
25
+ }: RegularTableViewProps) => {
26
+ const {
27
+ enableToggleExpansion,
28
+ handleExpandOrCollapse,
29
+ inlineRowLoading,
30
+ loading,
31
+ responsive,
32
+ table,
33
+ selectableRows,
34
+ hasAnySubRows,
35
+ isPinnedLeft = false,
36
+ } = useContext(AdvancedTableContext)
37
+
38
+ const columnPinning = table.getState().columnPinning || { left: [] };
39
+ const columnDefinitions = table.options.meta?.columnDefinitions || [];
40
+
41
+ return (
42
+ <>
43
+ {table.getRowModel().rows.map((row: Row<GenericObject>) => {
44
+ const isExpandable = row.getIsExpanded();
45
+ const isFirstChildofSubrow = row.depth > 0 && row.index === 0;
46
+ const rowHasNoChildren = row.original?.children && !row.original.children.length ? true : false;
47
+ const numberOfColumns = table.getAllFlatColumns().length;
48
+ const isDataLoading = isExpandable && (inlineRowLoading && rowHasNoChildren) && (row.depth < columnDefinitions[0]?.cellAccessors?.length);
49
+ const rowBackground = isExpandable && ((!inlineRowLoading && row.getCanExpand()) || (inlineRowLoading && rowHasNoChildren));
50
+ const rowColor = row.getIsSelected() ? "bg-row-selection" : rowBackground ? "bg-silver" : "bg-white";
51
+
52
+ return (
53
+ <React.Fragment key={`${row.index}-${row.id}-${row.depth}-row`}>
54
+ {isFirstChildofSubrow && subRowHeaders && (
55
+ <SubRowHeaderRow
56
+ collapsibleTrail={collapsibleTrail}
57
+ enableToggleExpansion={enableToggleExpansion}
58
+ onClick={handleExpandOrCollapse}
59
+ row={row}
60
+ subRowHeaders={subRowHeaders}
61
+ table={table}
62
+ />
63
+ )}
64
+
65
+ <tr
66
+ className={`${rowColor} ${row.depth > 0 ? `depth-sub-row-${row.depth}` : ""}`}
67
+ id={`${row.index}-${row.id}-${row.depth}-row`}
68
+ >
69
+ {/* Render custom checkbox column when we want selectableRows for non-expanding tables */}
70
+ {selectableRows && !hasAnySubRows && (
71
+ <td className="checkbox-cell">
72
+ <Checkbox
73
+ checked={row.getIsSelected()}
74
+ disabled={!row.getCanSelect()}
75
+ indeterminate={row.getIsSomeSelected()}
76
+ name={row.id}
77
+ onChange={row.getToggleSelectedHandler()}
78
+ />
79
+ </td>
80
+ )}
81
+
82
+ {row.getVisibleCells().map((cell: Cell<GenericObject, unknown>, i: number) => {
83
+ const isPinnedLeft = columnPinning.left.includes(cell.column.id);
84
+ const isLastCell = cell.column.parent?.columns?.at(-1)?.id === cell.column.id;
85
+
86
+ return (
87
+ <td
88
+ align="right"
89
+ className={classnames(
90
+ `${cell.id}-cell position_relative`,
91
+ isChrome() ? "chrome-styles" : "",
92
+ isPinnedLeft && 'pinned-left',
93
+ isLastCell && 'last-cell',
94
+ )}
95
+ key={`${cell.id}-data`}
96
+ >
97
+ {collapsibleTrail && i === 0 && row.depth > 0 && renderCollapsibleTrail(row.depth)}
98
+ <span id={`${cell.id}-span`}>
99
+ {loading ? (
100
+ <LoadingCell />
101
+ ) : (
102
+ flexRender(cell.column.columnDef.cell, cell.getContext())
103
+ )}
104
+ </span>
105
+ </td>
106
+ );
107
+ })}
108
+ </tr>
109
+
110
+ {/* Display LoadingInline if Row Data is querying and there are no children already */}
111
+ {isDataLoading && (
112
+ <tr key={`${row.id}-row`}>
113
+ <td colSpan={numberOfColumns}
114
+ style={{ paddingLeft: `${row.depth === 0 ? 0.5 : (row.depth * 2)}em` }}
115
+ >
116
+ <LoadingInline />
117
+ </td>
118
+ </tr>
119
+ )}
120
+ </React.Fragment>
121
+ );
122
+ })}
123
+ </>
124
+ );
125
+ }
126
+
127
+ export default RegularTableView;
@@ -0,0 +1,55 @@
1
+ import React, { useEffect, useRef } from "react";
2
+ import Card from "../../pb_card/_card";
3
+ import Caption from "../../pb_caption/_caption";
4
+ import Flex from "../../pb_flex/_flex";
5
+ import FlexItem from "../../pb_flex/_flex_item";
6
+ import { showActionBar, hideActionBar } from "../Utilities/ActionBarAnimationHelper";
7
+
8
+ interface TableActionBarProps {
9
+ isVisible: boolean;
10
+ selectedCount: number;
11
+ actions?: React.ReactNode[] | React.ReactNode;
12
+ }
13
+
14
+ const TableActionBar: React.FC<TableActionBarProps> = ({
15
+ isVisible,
16
+ selectedCount,
17
+ actions
18
+ }) => {
19
+ const cardRef = useRef(null);
20
+
21
+ useEffect(() => {
22
+ if (cardRef.current) {
23
+ if (isVisible) {
24
+ showActionBar(cardRef.current);
25
+ } else {
26
+ hideActionBar(cardRef.current);
27
+ }
28
+ }
29
+ }, [isVisible]);
30
+
31
+ return (
32
+ <Card
33
+ borderNone={!isVisible}
34
+ className={`${isVisible && "show-action-card row-selection-actions-card"}`}
35
+ htmlOptions={{ ref: cardRef as any }}
36
+ padding={`${isVisible ? "xs" : "none"}`}
37
+ >
38
+ <Flex
39
+ alignItems="center"
40
+ justify="between"
41
+ >
42
+ <Caption
43
+ color="light"
44
+ paddingLeft="xs"
45
+ size="xs"
46
+ >
47
+ {selectedCount} Selected
48
+ </Caption>
49
+ <FlexItem>{actions}</FlexItem>
50
+ </Flex>
51
+ </Card>
52
+ );
53
+ };
54
+
55
+ export default TableActionBar;
@@ -0,0 +1,33 @@
1
+ import React from "react";
2
+ import Pagination from "../../pb_pagination/_pagination";
3
+
4
+ interface TablePaginationProps {
5
+ table: any;
6
+ onChange: (page: number) => void;
7
+ position: "top" | "bottom";
8
+ range?: number;
9
+ }
10
+
11
+ const TablePagination: React.FC<TablePaginationProps> = ({
12
+ table,
13
+ onChange,
14
+ position,
15
+ range = 5
16
+ }) => {
17
+ const current = table.getState().pagination.pageIndex + 1;
18
+ const total = table.getPageCount();
19
+
20
+ return (
21
+ <Pagination
22
+ current={current}
23
+ key={`pagination-${position}-${current}`}
24
+ marginBottom={position === "top" ? "xs" : undefined}
25
+ marginTop={position === "bottom" ? "xs" : undefined}
26
+ onChange={onChange}
27
+ range={range}
28
+ total={total}
29
+ />
30
+ );
31
+ };
32
+
33
+ export default TablePagination;
@@ -0,0 +1,275 @@
1
+ import React, { useContext, useLayoutEffect, useState, useEffect } from "react"
2
+ import classnames from "classnames"
3
+ import { flexRender, Cell } from "@tanstack/react-table"
4
+ import { VirtualItem } from "@tanstack/react-virtual"
5
+
6
+ import { GenericObject } from "../../types"
7
+
8
+ import { isChrome } from "../Utilities/BrowserCheck"
9
+ import { getVirtualizedRowStyle } from "../Utilities/TableContainerStyles"
10
+
11
+ import LoadingInline from "../../pb_loading_inline/_loading_inline"
12
+ import Checkbox from "../../pb_checkbox/_checkbox"
13
+
14
+ import { SubRowHeaderRow } from "../Components/SubRowHeaderRow"
15
+ import { LoadingCell } from "../Components/LoadingCell"
16
+ import { renderCollapsibleTrail } from "../Components/CollapsibleTrail"
17
+
18
+ import AdvancedTableContext from "../Context/AdvancedTableContext"
19
+
20
+ type VirtualizedTableViewProps = {
21
+ collapsibleTrail?: boolean
22
+ subRowHeaders?: string[]
23
+ }
24
+
25
+ export const VirtualizedTableView = ({
26
+ collapsibleTrail = true,
27
+ subRowHeaders,
28
+ }: VirtualizedTableViewProps) => {
29
+ const {
30
+ enableToggleExpansion,
31
+ handleExpandOrCollapse,
32
+ inlineRowLoading,
33
+ loading,
34
+ table,
35
+ selectableRows,
36
+ hasAnySubRows,
37
+ virtualizer,
38
+ flattenedItems,
39
+ } = useContext(AdvancedTableContext)
40
+
41
+ const columnPinning = table.getState().columnPinning || { left: [] };
42
+ const sortingState = JSON.stringify(table.getState().sorting || []);
43
+
44
+ // Store column widths extracted from header
45
+ const [columnWidths, setColumnWidths] = useState<{[key: string]: string}>({});
46
+
47
+ // Function to get header cell widths
48
+ const getHeaderCellWidths = () => {
49
+ const widths: {[key: string]: string} = {};
50
+
51
+ // Get all header cells
52
+ const headerCells = document.querySelectorAll('.table-header-cells, .table-header-cells-custom');
53
+
54
+ // If checkbox is present in header
55
+ if (selectableRows && !hasAnySubRows && headerCells.length > 0) {
56
+ widths['checkbox'] = `${headerCells[0].getBoundingClientRect().width}px`;
57
+ }
58
+
59
+ // Process regular header cells
60
+ table.getFlatHeaders().forEach((header: any, index: number) => {
61
+ // Adjust index if checkbox column exists
62
+ const headerIndex = (selectableRows && !hasAnySubRows) ? index + 1 : index;
63
+
64
+ if (headerCells[headerIndex]) {
65
+ const width = headerCells[headerIndex].getBoundingClientRect().width;
66
+ widths[header.id] = `${width}px`;
67
+ }
68
+ });
69
+
70
+ return widths;
71
+ };
72
+
73
+ // Debounce function to prevent too many updates during resize
74
+ const debounce = <T extends (...args: any[]) => any>(func: T, wait: number): ((...args: Parameters<T>) => void) => {
75
+ let timeout: ReturnType<typeof setTimeout>;
76
+ return function executedFunction(...args: Parameters<T>) {
77
+ const later = () => {
78
+ clearTimeout(timeout);
79
+ func(...args);
80
+ };
81
+ clearTimeout(timeout);
82
+ timeout = setTimeout(later, wait);
83
+ };
84
+ };
85
+
86
+ // Update column widths when component mounts and when sorting changes
87
+ useLayoutEffect(() => {
88
+ // Apply widths after a small delay to ensure header is rendered
89
+ const timer = setTimeout(() => {
90
+ setColumnWidths(getHeaderCellWidths());
91
+ }, 0);
92
+
93
+ return () => clearTimeout(timer);
94
+ }, [table, selectableRows, hasAnySubRows, sortingState]);
95
+
96
+ // Add window resize listener to update widths on window resize
97
+ useEffect(() => {
98
+ // Create debounced version of the width measurement function
99
+ const handleResize = debounce(() => {
100
+ setColumnWidths(getHeaderCellWidths());
101
+ }, 0);
102
+
103
+ // Add the event listener
104
+ window.addEventListener('resize', handleResize);
105
+
106
+ // Cleanup
107
+ return () => {
108
+ window.removeEventListener('resize', handleResize);
109
+ };
110
+ }, [table, selectableRows, hasAnySubRows]);
111
+
112
+ // Safety check
113
+ if (!virtualizer || !flattenedItems) {
114
+ return (
115
+ <tr>
116
+ <td colSpan={table.getAllFlatColumns().length || 1}>
117
+ No data to display.
118
+ </td>
119
+ </tr>
120
+ );
121
+ }
122
+
123
+ // 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
+
137
+ if (!virtualItems.length) {
138
+ return (
139
+ <tr>
140
+ <td colSpan={table.getAllFlatColumns().length || 1}>
141
+ No items to display.
142
+ </td>
143
+ </tr>
144
+ );
145
+ }
146
+
147
+ return (
148
+ <>
149
+ {virtualItems.map((virtualRow: VirtualItem) => {
150
+ const item = flattenedItems[virtualRow.index];
151
+ if (!item) return null;
152
+
153
+ // Use consistent row styling
154
+ const virtualItemStyle = getVirtualizedRowStyle(virtualRow.start);
155
+
156
+ if (item.type === 'header') {
157
+ return (
158
+ <tr
159
+ className="virtualized-table-row virtualized-header-row"
160
+ key={`header-${item.id}-sort-${sortingState}`}
161
+ style={virtualItemStyle}
162
+ >
163
+ <td colSpan={table.getAllFlatColumns().length}>
164
+ <SubRowHeaderRow
165
+ collapsibleTrail={collapsibleTrail}
166
+ enableToggleExpansion={enableToggleExpansion}
167
+ onClick={handleExpandOrCollapse}
168
+ row={item.row}
169
+ subRowHeaders={subRowHeaders}
170
+ table={table}
171
+ />
172
+ </td>
173
+ </tr>
174
+ );
175
+ }
176
+
177
+ if (item.type === 'row') {
178
+ const row = item.row;
179
+ const isExpandable = row.getIsExpanded();
180
+ const rowHasNoChildren = row.original?.children && !row.original.children.length ? true : false;
181
+ const rowBackground = isExpandable && ((!inlineRowLoading && row.getCanExpand()) || (inlineRowLoading && rowHasNoChildren));
182
+ const rowColor = row.getIsSelected() ? "bg-row-selection" : rowBackground ? "bg-silver" : "bg-white";
183
+
184
+ return (
185
+ <tr
186
+ className={`virtualized-table-row ${rowColor} ${row.depth > 0 ? `depth-sub-row-${row.depth}` : ""}`}
187
+ data-index={virtualRow.index}
188
+ key={`row-${item.id}-sort-${sortingState}`}
189
+ ref={(node) => {
190
+ if (node) {
191
+ try {
192
+ virtualizer.measureElement(node);
193
+ } catch (err) {
194
+ // Silent error handling
195
+ }
196
+ }
197
+ }}
198
+ style={virtualItemStyle}
199
+ >
200
+ {/* Render custom checkbox column when we want selectableRows for non-expanding tables */}
201
+ {selectableRows && !hasAnySubRows && (
202
+ <td
203
+ className="checkbox-cell"
204
+ style={{ width: columnWidths['checkbox'] || 'auto' }}
205
+ >
206
+ <Checkbox
207
+ checked={row.getIsSelected()}
208
+ disabled={!row.getCanSelect()}
209
+ indeterminate={row.getIsSomeSelected()}
210
+ name={row.id}
211
+ onChange={row.getToggleSelectedHandler()}
212
+ />
213
+ </td>
214
+ )}
215
+
216
+ {row.getVisibleCells().map((cell: Cell<GenericObject, unknown>, i: number) => {
217
+ const isPinnedLeft = columnPinning.left.includes(cell.column.id);
218
+ const isLastCell = cell.column.parent?.columns?.at(-1)?.id === cell.column.id;
219
+ const cellWidth = columnWidths[cell.column.id] || 'auto';
220
+
221
+ return (
222
+ <td
223
+ align="right"
224
+ className={classnames(
225
+ `${cell.id}-cell position_relative`,
226
+ isChrome() ? "chrome-styles" : "",
227
+ isPinnedLeft && 'pinned-left',
228
+ isLastCell && 'last-cell',
229
+ )}
230
+ key={`${cell.id}-data`}
231
+ style={{ width: cellWidth }}
232
+ >
233
+ {collapsibleTrail && i === 0 && row.depth > 0 && renderCollapsibleTrail(row.depth)}
234
+ <span id={`${cell.id}-span`}>
235
+ {loading ? (
236
+ <LoadingCell />
237
+ ) : (
238
+ flexRender(cell.column.columnDef.cell, cell.getContext())
239
+ )}
240
+ </span>
241
+ </td>
242
+ );
243
+ })}
244
+ </tr>
245
+ );
246
+ }
247
+
248
+ if (item.type === 'loading') {
249
+ // Render loading indicator
250
+ const row = item.row;
251
+ const numberOfColumns = table.getAllFlatColumns().length;
252
+
253
+ return (
254
+ <tr
255
+ className="virtualized-table-row virtualized-loading-row"
256
+ key={`loading-${item.id}-sort-${sortingState}`}
257
+ style={virtualItemStyle}
258
+ >
259
+ <td
260
+ colSpan={numberOfColumns}
261
+ style={{ paddingLeft: `${row.depth === 0 ? 0.5 : (row.depth * 2)}em` }}
262
+ >
263
+ <LoadingInline />
264
+ </td>
265
+ </tr>
266
+ );
267
+ }
268
+
269
+ return null;
270
+ })}
271
+ </>
272
+ );
273
+ }
274
+
275
+ export default VirtualizedTableView;
@@ -1,5 +1,145 @@
1
- import { createContext } from "react"
1
+ import React, { createContext, useRef, useMemo, useEffect } from 'react';
2
+ import { useVirtualizer } from '@tanstack/react-virtual';
2
3
 
3
- const AdvancedTableContext = createContext<any>({})
4
+ import { Row } from "@tanstack/react-table";
5
+ import { GenericObject } from "../../types";
6
+ import { getRowHeightEstimate } from '../Utilities/TableContainerStyles';
4
7
 
5
- export default AdvancedTableContext
8
+ const AdvancedTableContext = createContext<any>({});
9
+
10
+ interface FlattenedItem {
11
+ type: 'header' | 'row' | 'loading';
12
+ row: Row<GenericObject>;
13
+ id: string;
14
+ }
15
+
16
+ export const AdvancedTableProvider = ({ children, ...props }: {
17
+ children: React.ReactNode,
18
+ [key: string]: any
19
+ }) => {
20
+ // Always initialize refs and state unconditionally, even if not used
21
+ const tableContainerRef = useRef(null);
22
+ const containerRef = props.tableContainerRef || tableContainerRef;
23
+
24
+ const table = props.table;
25
+ const isVirtualized = props.virtualizedRows || props.enableVirtualization;
26
+
27
+ // Create a flattened data array that includes ALL components for virtualization
28
+ const flattenedItems = useMemo(() => {
29
+ if (!isVirtualized || !table) {
30
+ return [];
31
+ }
32
+
33
+ const tableRows = table.getRowModel().rows;
34
+ const items: FlattenedItem[] = [];
35
+ const subRowHeaders = props.subRowHeaders;
36
+ const inlineRowLoading = props.inlineRowLoading;
37
+ const columnDefinitions = props.columnDefinitions;
38
+
39
+ // Process each row and insert special components
40
+ tableRows.forEach((row: Row<GenericObject>, index: number) => {
41
+ const isFirstChildofSubrow = row.depth > 0 && row.index === 0;
42
+
43
+ if (isFirstChildofSubrow && subRowHeaders) {
44
+ items.push({
45
+ type: 'header',
46
+ row: row,
47
+ id: `header-${row.id}-${index}`,
48
+ });
49
+ }
50
+
51
+ items.push({
52
+ type: 'row',
53
+ row: row,
54
+ id: `row-${row.id}-${index}`
55
+ });
56
+
57
+ const isExpandable = row.getIsExpanded();
58
+ const rowHasNoChildren = row.original?.children && !row.original.children.length ? true : false;
59
+ const isDataLoading = isExpandable && (inlineRowLoading && rowHasNoChildren) &&
60
+ (row.depth < (columnDefinitions[0]?.cellAccessors?.length || 0));
61
+
62
+ if (isDataLoading) {
63
+ items.push({
64
+ type: 'loading',
65
+ row: row,
66
+ id: `loading-${row.id}-${index}`
67
+ });
68
+ }
69
+ });
70
+
71
+ return items;
72
+ }, [
73
+ isVirtualized,
74
+ table,
75
+ props.subRowHeaders,
76
+ props.inlineRowLoading,
77
+ props.columnDefinitions,
78
+ // Add dependency on row model hash to refresh when data changes
79
+ table?.getRowModel().rows.length,
80
+ // Important: Add sorting state as a dependency to refresh when sorting changes
81
+ JSON.stringify(table?.getState().sorting || []),
82
+ // Also add expanded state as a dependency
83
+ JSON.stringify(table?.getState().expanded || {}),
84
+ ]);
85
+
86
+ // Always initialize the virtualizer, even if we don't use it
87
+ // This satisfies the React hooks rules by ensuring hooks are called unconditionally
88
+ const virtualizer = useVirtualizer({
89
+ count: isVirtualized && flattenedItems.length > 0 ? flattenedItems.length : 0,
90
+ getScrollElement: () => containerRef.current,
91
+ estimateSize: (index) => {
92
+ // Skip if virtualization isn't enabled
93
+ if (!isVirtualized || flattenedItems.length === 0) return 0;
94
+
95
+ // Use consistent row height estimates
96
+ const item = flattenedItems[index];
97
+ if (!item) return getRowHeightEstimate('row'); // Default
98
+
99
+ return getRowHeightEstimate(item.type);
100
+ },
101
+ overscan: 10, // Load more items for smoother scrolling
102
+ getItemKey: (index) => {
103
+ if (!isVirtualized || flattenedItems.length === 0 || index >= flattenedItems.length) {
104
+ return `item-${index}`;
105
+ }
106
+ return flattenedItems[index]?.id || `item-${index}`;
107
+ },
108
+ });
109
+
110
+ // Reset virtualizer scroll position when important state changes
111
+ useEffect(() => {
112
+ if (isVirtualized && virtualizer && table && containerRef.current) {
113
+ // Force recalculation of all virtual items
114
+ virtualizer.measure();
115
+
116
+ // Reset scroll position when sorting changes
117
+ containerRef.current.scrollTop = 0;
118
+ }
119
+ }, [
120
+ isVirtualized,
121
+ virtualizer,
122
+ table,
123
+ containerRef,
124
+ JSON.stringify(table?.getState().sorting || []),
125
+ JSON.stringify(table?.getState().expanded || {})
126
+ ]);
127
+
128
+ const contextValue = {
129
+ ...props,
130
+ table,
131
+ tableContainerRef: containerRef,
132
+ virtualizer: isVirtualized ? virtualizer : null,
133
+ flattenedItems,
134
+ virtualizedRows: isVirtualized,
135
+ enableVirtualization: isVirtualized
136
+ };
137
+
138
+ return (
139
+ <AdvancedTableContext.Provider value={contextValue}>
140
+ {children}
141
+ </AdvancedTableContext.Provider>
142
+ );
143
+ };
144
+
145
+ export default AdvancedTableContext;