playbook_ui 16.3.0 → 16.4.0.pre.alpha.PLAY2846reactadvancedtablecalcheaderpinnedrows15356

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 (148) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/Components/CustomCell.tsx +17 -4
  3. data/app/pb_kits/playbook/pb_advanced_table/Components/TableHeaderCell.tsx +3 -1
  4. data/app/pb_kits/playbook/pb_advanced_table/Context/AdvancedTableContext.tsx +5 -2
  5. data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableActions.ts +21 -9
  6. data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableState.ts +5 -2
  7. data/app/pb_kits/playbook/pb_advanced_table/SubKits/TableHeader.tsx +9 -11
  8. data/app/pb_kits/playbook/pb_advanced_table/Utilities/ExpansionControlHelpers.tsx +25 -1
  9. data/app/pb_kits/playbook/pb_advanced_table/Utilities/RowModelUtils.ts +100 -0
  10. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +65 -1
  11. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +12 -2
  12. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.html.erb +2 -2
  13. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.rb +9 -0
  14. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +109 -2
  15. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_cascade_collapse.jsx +50 -0
  16. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_cascade_collapse.md +1 -0
  17. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_pinned_rows_rails.html.erb +57 -0
  18. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_pinned_rows_rails.md +7 -0
  19. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_sort_parent_only.jsx +175 -0
  20. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_sort_parent_only.md +5 -0
  21. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +3 -0
  22. data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +3 -1
  23. data/app/pb_kits/playbook/pb_advanced_table/index.js +130 -29
  24. data/app/pb_kits/playbook/pb_advanced_table/scss_partials/advanced_table_sticky_mixin.scss +6 -2
  25. data/app/pb_kits/playbook/pb_advanced_table/table_body.html.erb +21 -4
  26. data/app/pb_kits/playbook/pb_advanced_table/table_body.rb +115 -9
  27. data/app/pb_kits/playbook/pb_advanced_table/table_row.html.erb +3 -1
  28. data/app/pb_kits/playbook/pb_advanced_table/table_row.rb +12 -1
  29. data/app/pb_kits/playbook/pb_advanced_table/table_subrow_header.html.erb +4 -1
  30. data/app/pb_kits/playbook/pb_advanced_table/table_subrow_header.rb +9 -1
  31. data/app/pb_kits/playbook/pb_button/_button_mixins.scss +6 -1
  32. data/app/pb_kits/playbook/pb_button/docs/_button_full_width_rails.md +19 -0
  33. data/app/pb_kits/playbook/pb_button/docs/_button_full_width_react.md +23 -0
  34. data/app/pb_kits/playbook/pb_circle_icon_button/_circle_icon_button.scss +5 -0
  35. data/app/pb_kits/playbook/pb_collapsible/index.js +15 -26
  36. data/app/pb_kits/playbook/pb_date_picker/date_picker_helper.ts +3 -1
  37. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_compound_components.html.erb +1 -1
  38. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_compound_components.jsx +6 -3
  39. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_full_height.html.erb +3 -3
  40. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_full_height.jsx +6 -3
  41. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_full_height_placement.html.erb +3 -3
  42. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_full_height_placement.jsx +6 -3
  43. data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +3 -0
  44. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +1 -0
  45. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_closing_options_rails.html.erb +16 -0
  46. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_closing_options_rails.md +1 -0
  47. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_custom_event_type.html.erb +224 -0
  48. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_custom_event_type.md +7 -0
  49. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +2 -0
  50. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +8 -1
  51. data/app/pb_kits/playbook/pb_dropdown/index.js +255 -46
  52. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +19 -14
  53. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/_fixed_confirmation_toast.scss +4 -0
  54. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/_fixed_confirmation_toast.tsx +3 -0
  55. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_nav_margin.html.erb +46 -0
  56. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_nav_margin.jsx +42 -0
  57. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_nav_margin_rails.md +1 -0
  58. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_nav_margin_react.md +1 -0
  59. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/example.yml +2 -0
  60. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/index.js +2 -1
  61. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/fixed_confirmation_toast.rb +7 -1
  62. data/app/pb_kits/playbook/pb_icon/icon.rb +7 -1
  63. data/app/pb_kits/playbook/pb_list/_list_mixin.scss +4 -4
  64. data/app/pb_kits/playbook/pb_multi_level_select/_helper_functions.tsx +1 -1
  65. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +27 -16
  66. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_placeholder.html.erb +109 -0
  67. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_placeholder.jsx +127 -0
  68. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_placeholder.md +1 -0
  69. data/app/pb_kits/playbook/pb_multi_level_select/docs/example.yml +2 -0
  70. data/app/pb_kits/playbook/pb_multi_level_select/docs/index.js +1 -0
  71. data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.rb +3 -0
  72. data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.test.jsx +27 -0
  73. data/app/pb_kits/playbook/pb_popover/docs/_popover_placement.jsx +81 -0
  74. data/app/pb_kits/playbook/pb_popover/docs/_popover_placement_react.md +1 -0
  75. data/app/pb_kits/playbook/pb_popover/docs/_popover_position.html.erb +128 -0
  76. data/app/pb_kits/playbook/pb_popover/docs/_popover_position_rails.md +1 -0
  77. data/app/pb_kits/playbook/pb_popover/docs/example.yml +2 -0
  78. data/app/pb_kits/playbook/pb_popover/docs/index.js +2 -1
  79. data/app/pb_kits/playbook/pb_rich_text_editor/_rich_text_editor.tsx +35 -134
  80. data/app/pb_kits/playbook/pb_rich_text_editor/_tiptap_editor.tsx +51 -0
  81. data/app/pb_kits/playbook/pb_rich_text_editor/_trix_editor.tsx +206 -0
  82. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_default.jsx +56 -0
  83. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_default.md +1 -0
  84. data/app/pb_kits/playbook/pb_rich_text_editor/docs/example.yml +13 -21
  85. data/app/pb_kits/playbook/pb_rich_text_editor/docs/index.js +0 -10
  86. data/app/pb_kits/playbook/pb_rich_text_editor/inlineFocus.ts +5 -4
  87. data/app/pb_kits/playbook/pb_selectable_list/_selectable_list.scss +19 -1
  88. data/app/pb_kits/playbook/pb_table/_table.tsx +24 -21
  89. data/app/pb_kits/playbook/pb_table/docs/_sections.yml +1 -0
  90. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_external_filter_rails.html.erb +45 -0
  91. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_external_filter_rails.md +39 -0
  92. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_variant_rails.md +2 -1
  93. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_with_card_title_props.jsx +152 -0
  94. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_with_card_title_props.md +17 -0
  95. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_with_card_title_props_rails.html.erb +121 -0
  96. data/app/pb_kits/playbook/pb_table/docs/_table_with_filter_with_card_title_props_rails.md +17 -0
  97. data/app/pb_kits/playbook/pb_table/docs/example.yml +3 -0
  98. data/app/pb_kits/playbook/pb_table/docs/index.js +1 -0
  99. data/app/pb_kits/playbook/pb_table/table.html.erb +17 -13
  100. data/app/pb_kits/playbook/pb_table/table.rb +8 -0
  101. data/app/pb_kits/playbook/pb_table/table.test.js +33 -0
  102. data/app/pb_kits/playbook/pb_textarea/_textarea.scss +4 -1
  103. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +105 -3
  104. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_with_highlight.jsx +20 -8
  105. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_with_highlight.md +3 -0
  106. data/app/pb_kits/playbook/utilities/_hover.scss +6 -3
  107. data/app/pb_kits/playbook/utilities/domHelpers.ts +50 -0
  108. data/dist/chunks/{_pb_line_graph-CKBPxTmM.js → _pb_line_graph-D6s5rymw.js} +1 -1
  109. data/dist/chunks/_typeahead-BNp_YiTh.js +1 -0
  110. data/dist/chunks/componentRegistry-DRSp5D_e.js +1 -0
  111. data/dist/chunks/{globalProps-DLCfJwiU.js → globalProps-Ds_6HBhX.js} +1 -1
  112. data/dist/chunks/lib-BaO72ugL.js +29 -0
  113. data/dist/chunks/vendor.js +5 -5
  114. data/dist/menu.yml +3 -2
  115. data/dist/playbook-rails-react-bindings.js +1 -1
  116. data/dist/playbook-rails.js +1 -1
  117. data/dist/playbook.css +1 -1
  118. data/lib/playbook/pb_forms_helper.rb +3 -0
  119. data/lib/playbook/version.rb +2 -2
  120. metadata +42 -32
  121. data/app/pb_kits/playbook/pb_button/docs/_button_full_width.md +0 -1
  122. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_attributes.html.erb +0 -5
  123. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_attributes.jsx +0 -15
  124. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_default.html.erb +0 -1
  125. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_focus.html.erb +0 -3
  126. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_focus.jsx +0 -17
  127. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_inline.html.erb +0 -6
  128. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_inline.jsx +0 -16
  129. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_label.jsx +0 -28
  130. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_label.md +0 -1
  131. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_preview.html.erb +0 -35
  132. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_preview.jsx +0 -45
  133. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_required_indicator.html.erb +0 -10
  134. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_required_indicator.jsx +0 -22
  135. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_required_indicator.md +0 -3
  136. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_simple.html.erb +0 -1
  137. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_simple.jsx +0 -13
  138. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_sticky.html.erb +0 -1
  139. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_sticky.jsx +0 -15
  140. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_templates.html.erb +0 -115
  141. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_templates.jsx +0 -42
  142. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_toolbar_bottom.html.erb +0 -4
  143. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_toolbar_bottom.jsx +0 -14
  144. data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor.html.erb +0 -5
  145. data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor.rb +0 -63
  146. data/dist/chunks/_typeahead-B7bktFm6.js +0 -1
  147. data/dist/chunks/componentRegistry-DzmmLR2x.js +0 -1
  148. data/dist/chunks/lib-QT_7rPYf.js +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6ab403f777f646b3d7d608303239044ad0a7740e582035beccb22ddff5a24ce1
4
- data.tar.gz: d4dc694b6f85c099e9b6f53d512e8fa0adc96a428a9c0d5699745594c6cd2d93
3
+ metadata.gz: aec00cc0f01b4b8a03cdeb6e7ec2f557bc24f0faf27bc53e40eefaec41ce9f11
4
+ data.tar.gz: 2f66281990b349330568f3e150163ab5351326fa367c231b4e6055a5fa5d53fb
5
5
  SHA512:
6
- metadata.gz: ccb47fdb993c29446e42924ae80737f6f33d20ee3b9f6fb5ffe35fbfd97431edbb1743e958b418021030ec02f116eaf7de26675bf3d792e08ec4c7a25ee4ba0e
7
- data.tar.gz: a85463fbd073388ae9218fa64b56fa72b857883a1bf635154a5ba168cccbaea2d45e8f626b463d53042fded52f26556f87da8429f9b3e8ad742dac50324b7b9f
6
+ metadata.gz: c28bc30a8a258ce3e8deb15673e26cf34ecc4d87c0cec6a30a811d3ecfa6a9ae06b4e171be3cfb979123275238203738260172f6eb6f95cf66e5f82af3a61a50
7
+ data.tar.gz: 4c1dd728c52fde23dcabc4979216465ad7e167ce628a90f2eb0af92e744078c2109d54a3cf341022cbd3296e15fe88cf71b486413e524696af5dc0161dfbbf11
@@ -11,6 +11,7 @@ import Icon from "../../pb_icon/_icon"
11
11
  import Checkbox from "../../pb_checkbox/_checkbox"
12
12
 
13
13
  import AdvancedTableContext from "../Context/AdvancedTableContext"
14
+ import { getDescendantRowIds } from "../Utilities/ExpansionControlHelpers"
14
15
 
15
16
  interface CustomCellProps {
16
17
  getValue?: Getter<string>
@@ -31,13 +32,25 @@ export const CustomCell = ({
31
32
  selectableRows,
32
33
  customStyle = {},
33
34
  }: CustomCellProps & GlobalProps) => {
34
- const { setExpanded, expanded, expandedControl, inlineRowLoading, hasAnySubRows } = useContext(AdvancedTableContext);
35
+ const { setExpanded, expanded, expandedControl, inlineRowLoading, hasAnySubRows, cascadeCollapse } = useContext(AdvancedTableContext);
35
36
 
36
37
  const handleOnExpand = (row: Row<GenericObject>) => {
37
38
  onRowToggleClick && onRowToggleClick(row);
38
-
39
- if (!expandedControl) {
40
- setExpanded({ ...expanded, [row.id]: !row.getIsExpanded() });
39
+
40
+ const willBeExpanded = !row.getIsExpanded();
41
+ if (willBeExpanded) {
42
+ if (!expandedControl) {
43
+ setExpanded({ ...expanded, [row.id]: true });
44
+ }
45
+ } else {
46
+ if (cascadeCollapse) {
47
+ const idsToRemove = new Set([row.id, ...getDescendantRowIds(row)]);
48
+ const nextExpanded = { ...expanded };
49
+ idsToRemove.forEach((id) => delete nextExpanded[id]);
50
+ setExpanded(nextExpanded);
51
+ } else if (!expandedControl) {
52
+ setExpanded({ ...expanded, [row.id]: false });
53
+ }
41
54
  }
42
55
  };
43
56
 
@@ -63,6 +63,7 @@ export const TableHeaderCell = ({
63
63
  stickyLeftColumn,
64
64
  inlineRowLoading,
65
65
  isActionBarVisible,
66
+ cascadeCollapse,
66
67
  } = useContext(AdvancedTableContext);
67
68
 
68
69
  type justifyTypes = "none" | "center" | "start" | "end" | "between" | "around" | "evenly"
@@ -182,7 +183,8 @@ const isToggleExpansionEnabled =
182
183
  table.getRowModel(),
183
184
  expanded,
184
185
  undefined,
185
- depth
186
+ depth,
187
+ cascadeCollapse
186
188
  )
187
189
  setExpanded(updated)
188
190
  }
@@ -33,7 +33,8 @@ export const AdvancedTableProvider = ({ children, ...props }: {
33
33
 
34
34
  const measureHeights = useCallback(() => {
35
35
  if (headerRef.current) {
36
- const headerRect = headerRef.current.getBoundingClientRect();
36
+ const headerElement = headerRef.current as HTMLElement;
37
+ const headerRect = headerElement.getBoundingClientRect();
37
38
  if (headerRect.height > 0) {
38
39
  setHeaderHeight(headerRect.height);
39
40
  }
@@ -67,9 +68,11 @@ export const AdvancedTableProvider = ({ children, ...props }: {
67
68
  };
68
69
  }, [measureHeights]);
69
70
 
71
+ const headerGroupCount = table?.getHeaderGroups()?.length ?? 0;
72
+
70
73
  useEffect(() => {
71
74
  measureHeights();
72
- }, [table?.getRowModel().rows.length, measureHeights]);
75
+ }, [table?.getRowModel().rows.length, headerGroupCount, measureHeights]);
73
76
 
74
77
 
75
78
  // Create a flattened data array that includes ALL components for virtualization
@@ -12,6 +12,7 @@ interface UseTableActionsProps {
12
12
  inlineRowLoading?: boolean;
13
13
  localPagination?: { pageIndex: number; pageSize: number };
14
14
  setLocalPagination?: (pagination: { pageIndex: number; pageSize: number }) => void;
15
+ cascadeCollapse?: boolean;
15
16
  }
16
17
 
17
18
  export function useTableActions({
@@ -22,7 +23,8 @@ export function useTableActions({
22
23
  onRowSelectionChange,
23
24
  inlineRowLoading = false,
24
25
  localPagination,
25
- setLocalPagination
26
+ setLocalPagination,
27
+ cascadeCollapse = false
26
28
  }: UseTableActionsProps) {
27
29
 
28
30
  // State to achieve 1 second delay before fetching more rows
@@ -32,15 +34,25 @@ export function useTableActions({
32
34
  // Handle expand/collapse
33
35
  const handleExpandOrCollapse = useCallback(async (row: Row<GenericObject>) => {
34
36
  if (onToggleExpansionClick) onToggleExpansionClick(row)
35
- const updatedExpandedState = await updateExpandAndCollapseState(
36
- table.getRowModel(),
37
- expanded,
38
- row?.parentId,
39
- undefined
40
- )
41
37
 
42
- setExpanded(updatedExpandedState)
43
- }, [expanded, setExpanded, onToggleExpansionClick, table]);
38
+ const anyTopLevelExpanded = table.getRowModel().rows.some((r: Row<GenericObject>) => r.getIsExpanded())
39
+ const isHeaderCollapseAll = row == null && anyTopLevelExpanded
40
+
41
+ if (cascadeCollapse && isHeaderCollapseAll) {
42
+ setExpanded({})
43
+ return
44
+ }
45
+
46
+ const updatedExpandedState = await updateExpandAndCollapseState(
47
+ table.getRowModel(),
48
+ expanded,
49
+ row?.parentId,
50
+ undefined,
51
+ cascadeCollapse
52
+ )
53
+
54
+ setExpanded(updatedExpandedState)
55
+ }, [expanded, setExpanded, onToggleExpansionClick, table, cascadeCollapse]);
44
56
 
45
57
  // Handle pagination
46
58
  const onPageChange = useCallback((page: number) => {
@@ -12,6 +12,7 @@ import {
12
12
  import { GenericObject } from "../../types";
13
13
  import { createColumnHelper } from "@tanstack/react-table";
14
14
  import { createCellFunction } from "../Utilities/CellRendererUtils";
15
+ import { getParentOnlySortedRowModel } from "../Utilities/RowModelUtils";
15
16
 
16
17
  interface UseTableStateProps {
17
18
  tableData: GenericObject[];
@@ -36,6 +37,7 @@ interface UseTableStateProps {
36
37
  columnVisibilityControl?: GenericObject;
37
38
  rowStyling?: GenericObject;
38
39
  inlineRowLoading?: boolean;
40
+ sortParentOnly?: boolean;
39
41
  }
40
42
 
41
43
  export function useTableState({
@@ -55,7 +57,8 @@ export function useTableState({
55
57
  columnVisibilityControl,
56
58
  pinnedRows,
57
59
  rowStyling,
58
- inlineRowLoading = false
60
+ inlineRowLoading = false,
61
+ sortParentOnly = false
59
62
  }: UseTableStateProps) {
60
63
 
61
64
  // Create a local state for expanded and setExpanded if expandedControl not used
@@ -190,7 +193,7 @@ export function useTableState({
190
193
  getSubRows: (row: GenericObject) => row.children,
191
194
  getCoreRowModel: getCoreRowModel(),
192
195
  getExpandedRowModel: getExpandedRowModel(),
193
- getSortedRowModel: getSortedRowModel(),
196
+ getSortedRowModel: sortParentOnly ? getParentOnlySortedRowModel() : getSortedRowModel(),
194
197
  enableSortingRemoval: enableSortingRemoval,
195
198
  sortDescFirst: true,
196
199
  onRowSelectionChange: setRowSelection,
@@ -64,14 +64,12 @@ export const TableHeader = ({
64
64
  );
65
65
 
66
66
  const renderRegularTableHeader = () => (
67
- <thead className={classes}
67
+ <thead className={classes}
68
68
  id={id}
69
+ ref={headerRef}
69
70
  >
70
- {table.getHeaderGroups().map((headerGroup: HeaderGroup<GenericObject>, index: number) => (
71
- <tr
72
- key={`${headerGroup.id}-headerGroup`}
73
- ref={index === 0 ? headerRef : null}
74
- >
71
+ {table.getHeaderGroups().map((headerGroup: HeaderGroup<GenericObject>) => (
72
+ <tr key={`${headerGroup.id}-headerGroup`}>
75
73
  {!hasAnySubRows && selectableRows && (
76
74
  <th className={customCellClassnames}>
77
75
  <Checkbox
@@ -105,16 +103,16 @@ export const TableHeader = ({
105
103
  );
106
104
 
107
105
  const renderVirtualizedTableHeader = () => (
108
- <thead
109
- className={classes}
106
+ <thead
107
+ className={classes}
110
108
  data-virtualized="true"
111
109
  id={id}
110
+ ref={headerRef}
112
111
  >
113
- {table.getHeaderGroups().map((headerGroup: HeaderGroup<GenericObject>, index: number) => (
114
- <tr
112
+ {table.getHeaderGroups().map((headerGroup: HeaderGroup<GenericObject>) => (
113
+ <tr
115
114
  className="virtualized-header-row-header"
116
115
  key={`${headerGroup.id}-headerGroup-virtualized`}
117
- ref={index === 0 ? headerRef : null}
118
116
  >
119
117
  {!hasAnySubRows && selectableRows && (
120
118
  <th className={classnames(customCellClassnames, "virtualized-header-cell")}>
@@ -11,11 +11,21 @@ const filterExpandableRows = (expandedState: Record<string, boolean>) => {
11
11
  return expandedState
12
12
  }
13
13
 
14
+ export const getDescendantRowIds = (row: Row<GenericObject>): string[] => {
15
+ const ids: string[] = []
16
+ for (const sub of row.subRows || []) {
17
+ ids.push(sub.id)
18
+ ids.push(...getDescendantRowIds(sub))
19
+ }
20
+ return ids
21
+ }
22
+
14
23
  export const updateExpandAndCollapseState = (
15
24
  tableRows: RowModel<GenericObject>,
16
25
  expanded: Record<string, boolean>,
17
26
  targetParent?: string,
18
27
  targetDepth?: number,
28
+ cascadeCollapse?: boolean,
19
29
  ) => {
20
30
  const updateExpandedRows: Record<string, boolean> = {};
21
31
  const rows = targetDepth !== undefined ? tableRows.flatRows : tableRows.rows;
@@ -51,8 +61,22 @@ export const updateExpandAndCollapseState = (
51
61
  }
52
62
  }
53
63
 
54
- return filterExpandableRows({
64
+ const updatedExpandedState = filterExpandableRows({
55
65
  ...(expanded as ExpandedStateObject),
56
66
  ...updateExpandedRows,
57
67
  });
68
+
69
+ if (cascadeCollapse && !isExpandAction) {
70
+ const idsToRemove = new Set<string>();
71
+ for (const row of rowsToToggle) {
72
+ const shouldUpdate =
73
+ targetDepth === undefined ? true : row.depth === targetDepth;
74
+ if (shouldUpdate) {
75
+ getDescendantRowIds(row).forEach((id) => idsToRemove.add(id));
76
+ }
77
+ }
78
+ idsToRemove.forEach((id) => delete updatedExpandedState[id]);
79
+ }
80
+
81
+ return updatedExpandedState;
58
82
  };
@@ -0,0 +1,100 @@
1
+ // Returns a row model getter that sorts only depth-0 (parent) rows so children and grandchild rows keep their original order under each parent.
2
+
3
+ import type { Table, Row, RowModel, RowData } from "@tanstack/react-table";
4
+
5
+ export function getParentOnlySortedRowModel<TData extends RowData>(): (
6
+ table: Table<TData>
7
+ ) => () => RowModel<TData> {
8
+ return (table) => () => {
9
+ const sortingState = table.getState().sorting;
10
+ const rowModel = table.getPreSortedRowModel();
11
+
12
+ if (!rowModel.rows.length || !sortingState?.length) {
13
+ return rowModel;
14
+ }
15
+
16
+ const sortedFlatRows: Row<TData>[] = [];
17
+ const availableSorting = sortingState.filter((sort) =>
18
+ table.getColumn(sort.id)?.getCanSort()
19
+ );
20
+
21
+ const columnInfoById: Record<
22
+ string,
23
+ {
24
+ sortUndefined?: false | -1 | 1 | "first" | "last";
25
+ invertSorting?: boolean;
26
+ sortingFn: (rowA: Row<TData>, rowB: Row<TData>, columnId: string) => number;
27
+ }
28
+ > = {};
29
+
30
+ availableSorting.forEach((sortEntry) => {
31
+ const column = table.getColumn(sortEntry.id);
32
+ if (!column) return;
33
+ columnInfoById[sortEntry.id] = {
34
+ sortUndefined: column.columnDef.sortUndefined,
35
+ invertSorting: column.columnDef.invertSorting,
36
+ sortingFn: column.getSortingFn(),
37
+ };
38
+ });
39
+
40
+ const parentRows = rowModel.rows.map((row) => ({ ...row }));
41
+ parentRows.sort((rowA, rowB) => {
42
+ for (let i = 0; i < availableSorting.length; i += 1) {
43
+ const sortEntry = availableSorting[i]!;
44
+ const columnInfo = columnInfoById[sortEntry.id]!;
45
+ const sortUndefined = columnInfo.sortUndefined;
46
+ const isDesc = sortEntry?.desc ?? false;
47
+ let sortInt = 0;
48
+
49
+ if (sortUndefined) {
50
+ const aValue = rowA.getValue(sortEntry.id);
51
+ const bValue = rowB.getValue(sortEntry.id);
52
+ const aUndefined = aValue === undefined;
53
+ const bUndefined = bValue === undefined;
54
+ if (aUndefined || bUndefined) {
55
+ if (sortUndefined === "first") return aUndefined ? -1 : 1;
56
+ if (sortUndefined === "last") return aUndefined ? 1 : -1;
57
+ sortInt =
58
+ aUndefined && bUndefined
59
+ ? 0
60
+ : aUndefined
61
+ ? sortUndefined
62
+ : -sortUndefined;
63
+ }
64
+ }
65
+
66
+ if (sortInt === 0) {
67
+ sortInt = columnInfo.sortingFn(rowA, rowB, sortEntry.id);
68
+ }
69
+
70
+ if (sortInt !== 0) {
71
+ if (isDesc) sortInt *= -1;
72
+ if (columnInfo.invertSorting) sortInt *= -1;
73
+ return sortInt;
74
+ }
75
+ }
76
+ return rowA.index - rowB.index;
77
+ });
78
+
79
+ function flattenRowsInOrder(rows: Row<TData>[]): void {
80
+ rows.forEach((row) => {
81
+ sortedFlatRows.push(row);
82
+ if (row.subRows?.length) {
83
+ flattenRowsInOrder(row.subRows);
84
+ }
85
+ });
86
+ }
87
+ flattenRowsInOrder(parentRows);
88
+
89
+ const rowsById: Record<string, Row<TData>> = {};
90
+ sortedFlatRows.forEach((row) => {
91
+ rowsById[row.id] = row;
92
+ });
93
+
94
+ return {
95
+ rows: parentRows,
96
+ flatRows: sortedFlatRows,
97
+ rowsById,
98
+ };
99
+ };
100
+ }
@@ -63,6 +63,51 @@
63
63
  width: 100%;
64
64
  }
65
65
 
66
+ // Override pb_table endcap: first and last column use same padding as rest of row
67
+ // First column
68
+ .pb_table.table-sm tbody tr td:first-child,
69
+ .pb_table.table-sm tbody tr .pb_table_td:first-child,
70
+ .pb_table.table-sm tbody .pb_table_tr td:first-child,
71
+ .pb_table.table-sm tbody .pb_table_tr .pb_table_td:first-child,
72
+ .pb_table.table-sm .pb_table_tbody tr td:first-child,
73
+ .pb_table.table-sm .pb_table_tbody tr .pb_table_td:first-child,
74
+ .pb_table.table-sm .pb_table_tbody .pb_table_tr td:first-child,
75
+ .pb_table.table-sm .pb_table_tbody .pb_table_tr .pb_table_td:first-child {
76
+ padding-left: $space-xs;
77
+ }
78
+ .pb_table.table-md tbody tr td:first-child,
79
+ .pb_table.table-md tbody tr .pb_table_td:first-child,
80
+ .pb_table.table-md tbody .pb_table_tr td:first-child,
81
+ .pb_table.table-md tbody .pb_table_tr .pb_table_td:first-child,
82
+ .pb_table.table-md .pb_table_tbody tr td:first-child,
83
+ .pb_table.table-md .pb_table_tbody tr .pb_table_td:first-child,
84
+ .pb_table.table-md .pb_table_tbody .pb_table_tr td:first-child,
85
+ .pb_table.table-md .pb_table_tbody .pb_table_tr .pb_table_td:first-child {
86
+ padding-left: $space-sm;
87
+ }
88
+
89
+ // Last column
90
+ .pb_table.table-sm tbody tr td:last-child,
91
+ .pb_table.table-sm tbody tr .pb_table_td:last-child,
92
+ .pb_table.table-sm tbody .pb_table_tr td:last-child,
93
+ .pb_table.table-sm tbody .pb_table_tr .pb_table_td:last-child,
94
+ .pb_table.table-sm .pb_table_tbody tr td:last-child,
95
+ .pb_table.table-sm .pb_table_tbody tr .pb_table_td:last-child,
96
+ .pb_table.table-sm .pb_table_tbody .pb_table_tr td:last-child,
97
+ .pb_table.table-sm .pb_table_tbody .pb_table_tr .pb_table_td:last-child {
98
+ padding-right: $space-xs;
99
+ }
100
+ .pb_table.table-md tbody tr td:last-child,
101
+ .pb_table.table-md tbody tr .pb_table_td:last-child,
102
+ .pb_table.table-md tbody .pb_table_tr td:last-child,
103
+ .pb_table.table-md tbody .pb_table_tr .pb_table_td:last-child,
104
+ .pb_table.table-md .pb_table_tbody tr td:last-child,
105
+ .pb_table.table-md .pb_table_tbody tr .pb_table_td:last-child,
106
+ .pb_table.table-md .pb_table_tbody .pb_table_tr td:last-child,
107
+ .pb_table.table-md .pb_table_tbody .pb_table_tr .pb_table_td:last-child {
108
+ padding-right: $space-sm;
109
+ }
110
+
66
111
  // Virtualized Table and Rows for Infinite Scroll
67
112
  scrollbar-gutter: stable right-edges;
68
113
  .virtualized-header-row-header {
@@ -629,12 +674,18 @@
629
674
  @each $color_name, $color_value in $border_color_options {
630
675
  &.column-group-border-#{$color_name} {
631
676
  @if $theme == "light" {
677
+ &:not(.advanced-table-no-table-container) {
678
+ @include advanced-table-sticky-wrapper-frame($color_value);
679
+ }
632
680
  @include advanced-table-sticky-mixin(
633
681
  $color_value,
634
682
  $white,
635
683
  lighten($silver, $opacity_7)
636
684
  );
637
685
  } @else if $theme == "dark" {
686
+ &:not(.advanced-table-no-table-container) {
687
+ @include advanced-table-sticky-wrapper-frame($color_value);
688
+ }
638
689
  @include advanced-table-sticky-mixin(
639
690
  $color_value,
640
691
  $bg_dark_card,
@@ -670,6 +721,10 @@
670
721
  width: 100%;
671
722
  @include scrollbar-styling;
672
723
 
724
+ &:not(.advanced-table-no-table-container) {
725
+ @include advanced-table-sticky-wrapper-frame($border_light);
726
+ }
727
+
673
728
  // These are the responsive borders that should NOT inherit the custom color
674
729
  @include advanced-table-sticky-mixin(
675
730
  $border_light,
@@ -720,7 +775,7 @@
720
775
  }
721
776
  }
722
777
 
723
- // Row Pinning - additional inline styles in RegularTableView.tsx
778
+ // Row Pinning - React uses inline style; Rails passes same style via html_options from table_body
724
779
  .pinned-row {
725
780
  box-shadow: 0 4px 10px 0 rgba($shadow, 0.16) !important;
726
781
  }
@@ -963,6 +1018,11 @@
963
1018
  .sticky-left {
964
1019
  background-color: $bg_dark;
965
1020
  }
1021
+
1022
+ &:not(.advanced-table-no-table-container) {
1023
+ @include advanced-table-sticky-wrapper-frame($border_dark);
1024
+ }
1025
+
966
1026
  @include advanced-table-sticky-mixin(
967
1027
  $border_dark,
968
1028
  $bg_dark_card,
@@ -981,6 +1041,10 @@
981
1041
  }
982
1042
  }
983
1043
 
1044
+ &:not(.advanced-table-no-table-container) {
1045
+ @include advanced-table-sticky-wrapper-frame($border_dark);
1046
+ }
1047
+
984
1048
  // These are the responsive borders that should NOT inherit the custom color
985
1049
  @include advanced-table-sticky-mixin(
986
1050
  $border_dark,
@@ -31,6 +31,7 @@ type FullscreenControls = {
31
31
  type AdvancedTableProps = {
32
32
  aria?: { [key: string]: string }
33
33
  actions?: React.ReactNode[] | React.ReactNode
34
+ cascadeCollapse?: boolean
34
35
  children?: React.ReactNode | React.ReactNode[]
35
36
  className?: string
36
37
  columnDefinitions: GenericObject[]
@@ -65,6 +66,7 @@ type AdvancedTableProps = {
65
66
  showActionsBar?: boolean,
66
67
  persistToggleExpansionButton?: boolean,
67
68
  sortControl?: GenericObject
69
+ sortParentOnly?: boolean
68
70
  tableData: GenericObject[]
69
71
  tableOptions?: GenericObject
70
72
  tableProps?: GenericObject
@@ -80,6 +82,7 @@ const AdvancedTable = (props: AdvancedTableProps) => {
80
82
  const {
81
83
  aria = {},
82
84
  actions,
85
+ cascadeCollapse = false,
83
86
  children,
84
87
  className,
85
88
  columnDefinitions,
@@ -112,6 +115,7 @@ const AdvancedTable = (props: AdvancedTableProps) => {
112
115
  selectableRows,
113
116
  persistToggleExpansionButton = false,
114
117
  sortControl,
118
+ sortParentOnly = false,
115
119
  stickyLeftColumn,
116
120
  tableData,
117
121
  tableOptions,
@@ -123,6 +127,8 @@ const AdvancedTable = (props: AdvancedTableProps) => {
123
127
  fullScreenControl,
124
128
  } = props;
125
129
 
130
+ const noTableCardContainer = tableProps?.container === false;
131
+
126
132
  // Component refs
127
133
  const tableWrapperRef = useRef<HTMLDivElement>(null);
128
134
 
@@ -157,7 +163,8 @@ const AdvancedTable = (props: AdvancedTableProps) => {
157
163
  columnVisibilityControl,
158
164
  pinnedRows,
159
165
  rowStyling,
160
- inlineRowLoading
166
+ inlineRowLoading,
167
+ sortParentOnly
161
168
  });
162
169
 
163
170
  // Initialize table actions
@@ -173,7 +180,8 @@ const AdvancedTable = (props: AdvancedTableProps) => {
173
180
  onRowSelectionChange,
174
181
  inlineRowLoading,
175
182
  localPagination,
176
- setLocalPagination
183
+ setLocalPagination,
184
+ cascadeCollapse
177
185
  });
178
186
 
179
187
  // Set table row count for loading state
@@ -275,6 +283,7 @@ const AdvancedTable = (props: AdvancedTableProps) => {
275
283
  'hidden-action-bar': (selectableRows || columnVisibilityControl) && !isActionBarVisible,
276
284
  },
277
285
  {'advanced-table-sticky-left-columns': stickyLeftColumn && stickyLeftColumn.length > 0},
286
+ { 'advanced-table-no-table-container': noTableCardContainer },
278
287
  columnGroupBorderColor ? `column-group-border-${columnGroupBorderColor}` : '',
279
288
  scrollBarNone ? 'advanced-table-hide-scrollbar' : '',
280
289
  globalProps(props),
@@ -339,6 +348,7 @@ const AdvancedTable = (props: AdvancedTableProps) => {
339
348
  >
340
349
  {renderFullscreenHeader()}
341
350
  <AdvancedTableProvider
351
+ cascadeCollapse={cascadeCollapse}
342
352
  columnDefinitions={columnDefinitions}
343
353
  columnGroupBorderColor={columnGroupBorderColor}
344
354
  columnVisibilityControl={columnVisibilityControl}
@@ -7,12 +7,12 @@
7
7
  }) %>
8
8
  <% end %>
9
9
 
10
- <%= pb_rails("table", props: { size: "sm", data_table: true, number_spacing:"tabular", responsive:"none", dark: dark, classname: object.loading ? "content-loading" : "" }.merge(object.table_props)) do %>
10
+ <%= pb_rails("table", props: { size: "sm", data_table: true, number_spacing:"tabular", responsive:"none", dark: dark, classname: object.loading ? "content-loading" : "" }.merge(object.table_props || {})) do %>
11
11
  <% if content.present? %>
12
12
  <% content.presence %>
13
13
  <% else %>
14
14
  <%= pb_rails("advanced_table/table_header", props: { table_id: object.id, column_definitions: object.column_definitions, enable_toggle_expansion: object.enable_toggle_expansion, responsive: object.responsive, loading: object.loading, selectable_rows: object.selectable_rows, show_actions_bar: object.show_actions_bar, inline_row_loading: object.inline_row_loading, persist_toggle_expansion_button: object.persist_toggle_expansion_button, table_data: object.table_data }) %>
15
- <%= pb_rails("advanced_table/table_body", props: { table_id: object.id, table_data: object.table_data, column_definitions: object.column_definitions, responsive: object.responsive, loading: object.loading, selectable_rows: object.selectable_rows, enable_toggle_expansion: object.enable_toggle_expansion, row_styling: object.row_styling, inline_row_loading: object.inline_row_loading }) %>
15
+ <%= pb_rails("advanced_table/table_body", props: { table_id: object.id, table_data: object.table_data, column_definitions: object.column_definitions, responsive: object.responsive, loading: object.loading, selectable_rows: object.selectable_rows, enable_toggle_expansion: object.enable_toggle_expansion, row_styling: object.row_styling, inline_row_loading: object.inline_row_loading, pinned_rows: object.pinned_rows }) %>
16
16
  <% end %>
17
17
  <% end %>
18
18
  <% end %>
@@ -37,6 +37,8 @@ module Playbook
37
37
  default: false
38
38
  prop :persist_toggle_expansion_button, type: Playbook::Props::Boolean,
39
39
  default: false
40
+ prop :pinned_rows, type: Playbook::Props::HashProp,
41
+ default: {}
40
42
 
41
43
  def classname
42
44
  additional_classes = [
@@ -46,9 +48,16 @@ module Playbook
46
48
  hidden_action_bar_class,
47
49
  ]
48
50
  additional_classes << "column-group-border-#{column_group_border_color}" if column_group_border_color != "none"
51
+ additional_classes << "advanced-table-no-table-container" if no_table_card_container?
49
52
  generate_classname("pb_advanced_table", *additional_classes, separator: " ")
50
53
  end
51
54
 
55
+ def no_table_card_container?
56
+ return false unless table_props.is_a?(Hash)
57
+
58
+ table_props[:container] == false || table_props["container"] == false
59
+ end
60
+
52
61
  def responsive_classname
53
62
  responsive == "scroll" ? "advanced-table-responsive-scroll" : "advanced-table-responsive-none"
54
63
  end