playbook_ui 14.19.0 → 14.20.0.pre.alpha.play2168firstcolumnborderbug7950

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/RegularTableView.tsx +11 -1
  3. data/app/pb_kits/playbook/pb_advanced_table/Components/TableActionBar.tsx +175 -16
  4. data/app/pb_kits/playbook/pb_advanced_table/Components/TableHeaderCell.tsx +56 -25
  5. data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableState.ts +23 -13
  6. data/app/pb_kits/playbook/pb_advanced_table/Utilities/VisibilityTree.ts +47 -0
  7. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +14 -10
  8. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +7 -2
  9. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.html.erb +16 -8
  10. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.rb +9 -0
  11. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_beta.md +6 -2
  12. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility.jsx +57 -0
  13. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility.md +4 -0
  14. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility_custom.jsx +62 -0
  15. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility_custom.md +1 -0
  16. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility_multi.jsx +82 -0
  17. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility_multi.md +1 -0
  18. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility_with_state.jsx +66 -0
  19. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility_with_state.md +3 -0
  20. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_default.md +1 -1
  21. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_selectable_rows_actions_rails.html.erb +137 -0
  22. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_selectable_rows_actions_rails.md +3 -0
  23. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_selectable_rows_header_rails.html.erb +40 -0
  24. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_selectable_rows_header_rails.md +1 -0
  25. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_table_props.html.erb +1 -1
  26. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +6 -0
  27. data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +5 -1
  28. data/app/pb_kits/playbook/pb_advanced_table/index.js +155 -12
  29. data/app/pb_kits/playbook/pb_advanced_table/scss_partials/advanced_table_sticky_mixin.scss +1 -0
  30. data/app/pb_kits/playbook/pb_advanced_table/table_action_bar.html.erb +23 -0
  31. data/app/pb_kits/playbook/pb_advanced_table/table_action_bar.rb +19 -0
  32. data/app/pb_kits/playbook/pb_advanced_table/table_header.rb +4 -0
  33. data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +1 -1
  34. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +77 -19
  35. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default_rails.html.erb +31 -0
  36. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default_rails.md +5 -0
  37. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select.jsx +56 -0
  38. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select.md +3 -0
  39. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_display.jsx +58 -0
  40. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_display.md +3 -0
  41. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_display_rails.html.erb +20 -0
  42. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_display_rails.md +1 -0
  43. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_rails.html.erb +19 -0
  44. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_rails.md +3 -0
  45. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_with_autocomplete.html.erb +20 -0
  46. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_with_autocomplete.jsx +57 -0
  47. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_with_autocomplete.md +1 -0
  48. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_with_custom_options.html.erb +50 -0
  49. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_with_custom_options.jsx +105 -0
  50. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_with_default.html.erb +22 -0
  51. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_with_default.jsx +67 -0
  52. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display.jsx +11 -0
  53. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display.md +1 -1
  54. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display_rails.html.erb +33 -2
  55. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display_rails.md +3 -1
  56. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +11 -1
  57. data/app/pb_kits/playbook/pb_dropdown/docs/index.js +5 -0
  58. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +3 -3
  59. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +16 -2
  60. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.html.erb +34 -13
  61. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.rb +3 -1
  62. data/app/pb_kits/playbook/pb_dropdown/hooks/useHandleOnKeydown.tsx +0 -6
  63. data/app/pb_kits/playbook/pb_dropdown/index.js +336 -30
  64. data/app/pb_kits/playbook/pb_dropdown/keyboard_accessibility.js +39 -12
  65. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownContainer.tsx +2 -2
  66. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownOption.tsx +16 -12
  67. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +79 -13
  68. data/app/pb_kits/playbook/pb_dropdown/subcomponents/MultiSelectTriggerDisplay.tsx +58 -0
  69. data/app/pb_kits/playbook/pb_file_upload/_file_upload.scss +13 -0
  70. data/app/pb_kits/playbook/pb_file_upload/_file_upload.tsx +11 -1
  71. data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_error.html.erb +1 -0
  72. data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_error.jsx +41 -0
  73. data/app/pb_kits/playbook/pb_file_upload/docs/example.yml +2 -0
  74. data/app/pb_kits/playbook/pb_file_upload/docs/index.js +1 -0
  75. data/app/pb_kits/playbook/pb_file_upload/file_upload.html.erb +1 -0
  76. data/app/pb_kits/playbook/pb_file_upload/file_upload.rb +7 -1
  77. data/app/pb_kits/playbook/pb_file_upload/fileupload.test.js +18 -0
  78. data/app/pb_kits/playbook/pb_form/docs/_form_form_with.html.erb +1 -0
  79. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate.html.erb +1 -0
  80. data/app/pb_kits/playbook/pb_form_group/_error_state_mixin.scss +2 -2
  81. data/app/pb_kits/playbook/pb_form_pill/_form_pill.scss +19 -12
  82. data/app/pb_kits/playbook/pb_home_address_street/_home_address_street.tsx +13 -7
  83. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +2 -2
  84. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_color.html.erb +11 -11
  85. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_color.jsx +11 -11
  86. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_default.html.erb +11 -11
  87. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_default.jsx +11 -11
  88. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled.html.erb +11 -11
  89. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled.jsx +11 -11
  90. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled_options.html.erb +11 -11
  91. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled_options.jsx +11 -11
  92. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled_options_default.html.erb +11 -11
  93. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled_options_default.jsx +11 -11
  94. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled_options_parent.html.erb +11 -11
  95. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled_options_parent.jsx +11 -11
  96. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled_options_parent_default.html.erb +11 -11
  97. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled_options_parent_default.jsx +11 -11
  98. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_error.html.erb +11 -11
  99. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_error.jsx +11 -11
  100. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.html.erb +11 -11
  101. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.jsx +11 -11
  102. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_react_hook.jsx +11 -11
  103. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_reset.html.erb +11 -11
  104. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_return_all_selected.html.erb +11 -11
  105. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_return_all_selected.jsx +11 -11
  106. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_selected_ids.html.erb +11 -11
  107. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_selected_ids.md +2 -0
  108. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_selected_ids_react.jsx +11 -11
  109. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_selected_ids_react.md +3 -1
  110. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_single.html.erb +22 -22
  111. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_single.jsx +22 -22
  112. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_single_children_only.html.erb +22 -22
  113. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_single_children_only.jsx +22 -22
  114. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children.jsx +11 -11
  115. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_children_with_radios.jsx +11 -11
  116. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_with_form.html.erb +11 -11
  117. data/app/pb_kits/playbook/pb_person/_person.tsx +12 -2
  118. data/app/pb_kits/playbook/pb_phone_number_input/_phone_number_input.scss +9 -9
  119. data/app/pb_kits/playbook/pb_section_separator/_section_separator.tsx +2 -2
  120. data/app/pb_kits/playbook/pb_select/docs/_select_custom_select_subheaders.html.erb +12 -0
  121. data/app/pb_kits/playbook/pb_select/docs/_select_custom_select_subheaders.jsx +31 -0
  122. data/app/pb_kits/playbook/pb_select/docs/_select_custom_select_subheaders.md +1 -0
  123. data/app/pb_kits/playbook/pb_select/docs/example.yml +2 -0
  124. data/app/pb_kits/playbook/pb_select/docs/index.js +1 -0
  125. data/app/pb_kits/playbook/pb_text_input/_text_input.scss +4 -2
  126. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +73 -3
  127. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_preserve_input.jsx +23 -0
  128. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_preserve_input.md +1 -0
  129. data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +1 -0
  130. data/app/pb_kits/playbook/pb_typeahead/docs/index.js +1 -0
  131. data/dist/chunks/_typeahead-BmOWdDtp.js +22 -0
  132. data/dist/chunks/_weekday_stacked-CvcuQyr9.js +45 -0
  133. data/dist/chunks/lazysizes-B7xYodB-.js +1 -0
  134. data/dist/chunks/lib-D5R1BjUn.js +29 -0
  135. data/dist/chunks/{pb_form_validation-BioH7DWv.js → pb_form_validation-BZ2AVAi_.js} +1 -1
  136. data/dist/chunks/vendor.js +1 -1
  137. data/dist/playbook-doc.js +2 -2
  138. data/dist/playbook-rails-react-bindings.js +1 -1
  139. data/dist/playbook-rails.js +1 -1
  140. data/dist/playbook.css +1 -1
  141. data/lib/playbook/kit_base.rb +3 -3
  142. data/lib/playbook/version.rb +2 -2
  143. metadata +47 -8
  144. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default.html.erb +0 -10
  145. data/dist/chunks/_typeahead-D62OcwsT.js +0 -22
  146. data/dist/chunks/_weekday_stacked-Ceh9N0ow.js +0 -45
  147. data/dist/chunks/lazysizes-DHz07jlL.js +0 -1
  148. data/dist/chunks/lib-CeKZrPmu.js +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 242dea3f908e8ab1063890e221e4a5a52ab48694d7cba5ce7d20223b05ebece5
4
- data.tar.gz: 2b8a758c373ac4796e6f7bfaf5b1df93f0a0289272df3dd3dbd5c758c84380f9
3
+ metadata.gz: fcf6c9b4cc1f09fe9f4ad71387108d0a83305a79498d68a455660c8a1d877d9a
4
+ data.tar.gz: c95645d5f485724ecc9af73bdfd15701be9aac3b11e3626dead28ce4767ec149
5
5
  SHA512:
6
- metadata.gz: f2aff5a100ae75592c9f06769d1773a5c053b9aaabf107b6861e36c7892d7a8239a7da9fbc82dfbaf1cc37dd58b1dd1ba580390d1e0dd21dcb6f496040ae3f33
7
- data.tar.gz: f144555056550a5abc69c7a9efd94ab207530c7d3263e18da5263cb26382b34f07b55b30a68834617f8e7bf57e5c8e724457e7f77225dbc31aa5a8b625515bcb
6
+ metadata.gz: a46969b38f920b5c13d33d8f2287a56942f8f20f833734a1c282c36cd8328f693ee134a4af557d03d60cd91be39c099c816c088a070f4041cad0ee4a95d16f15
7
+ data.tar.gz: 05fae68a0a62be4ec28dec7c2d46b8e16f8998309da8064566003b8b7bc9471cef8f36605dbd475627fcadd1c1988d41acd513f356278d0941ab5da354dfafe9
@@ -93,7 +93,17 @@ export const RegularTableView = ({
93
93
 
94
94
  {row.getVisibleCells().map((cell: Cell<GenericObject, unknown>, i: number) => {
95
95
  const isPinnedLeft = columnPinning.left.includes(cell.column.id);
96
- const isLastCell = cell.column.parent?.columns?.at(-1)?.id === cell.column.id;
96
+ const isLastCell = (() => {
97
+ const parent = cell.column.parent;
98
+ if (!parent) {
99
+ const last = row.getVisibleCells().at(-1);
100
+ return last?.column.id === cell.column.id;
101
+ }
102
+
103
+ const visibleSiblings = parent.columns.filter(col => col.getIsVisible());
104
+ return visibleSiblings.at(-1)?.id === cell.column.id;
105
+ })();
106
+
97
107
  const { column } = cell;
98
108
  return (
99
109
  <td
@@ -1,52 +1,211 @@
1
- import React, { useEffect, useRef } from "react";
1
+ import React, { useEffect, useRef, useContext, useState } from "react";
2
+
3
+ import AdvancedTableContext from "../Context/AdvancedTableContext";
4
+ import { buildVisibilityTree } from "../Utilities/VisibilityTree";
5
+
2
6
  import Card from "../../pb_card/_card";
3
7
  import Caption from "../../pb_caption/_caption";
4
8
  import Flex from "../../pb_flex/_flex";
5
9
  import FlexItem from "../../pb_flex/_flex_item";
6
- import { showActionBar, hideActionBar } from "../Utilities/ActionBarAnimationHelper";
10
+ import Icon from "../../pb_icon/_icon";
11
+ import Checkbox from "../../pb_checkbox/_checkbox";
12
+ import SectionSeparator from "../../pb_section_separator/_section_separator";
13
+ import Tooltip from "../../pb_tooltip/_tooltip";
14
+ import PbReactPopover from "../../pb_popover/_popover";
15
+
16
+ import {
17
+ showActionBar,
18
+ hideActionBar,
19
+ } from "../Utilities/ActionBarAnimationHelper";
20
+ import { GenericObject } from "../../types";
7
21
 
8
22
  interface TableActionBarProps {
9
- isVisible: boolean;
23
+ isVisible: boolean | GenericObject | undefined;
10
24
  selectedCount: number;
11
25
  actions?: React.ReactNode[] | React.ReactNode;
26
+ type?: string;
27
+ }
28
+
29
+ interface VisibilityNode {
30
+ id?: string | undefined;
31
+ label?: string | undefined;
32
+ children?: VisibilityNode[];
12
33
  }
13
34
 
14
35
  const TableActionBar: React.FC<TableActionBarProps> = ({
15
36
  isVisible,
16
37
  selectedCount,
17
- actions
38
+ actions,
39
+ type = "row-selection",
18
40
  }) => {
19
41
  const cardRef = useRef(null);
42
+ const { table, columnVisibilityControl, columnDefinitions } =
43
+ useContext(AdvancedTableContext);
44
+
45
+ // ----------- Column visibility logic -----------
46
+ const includeIds = columnVisibilityControl?.includeIds;
47
+ const firstLeafId = table.getAllLeafColumns()[0]?.id;
48
+ // Get the first leaf column ID to exclude it from the visibility tree
49
+ // This is to avoid showing the first column in the dropdown
50
+ // as toggling it's visibility breaks the expanded row functionality
51
+ const tree = buildVisibilityTree(columnDefinitions, includeIds).filter(node => node.id !== firstLeafId);
52
+
53
+ const renderLeaf = (id: string, label: string) => {
54
+ const col = table.getColumn(id);
55
+ const show = col.getIsVisible();
56
+
57
+ const handleVisibilityChange = () => {
58
+ col.toggleVisibility();
59
+ if (columnVisibilityControl?.onColumnVisibilityChange) {
60
+ const updatedVisibilityState = {
61
+ ...table.getAllColumns().reduce((acc: { [x: string]: any; }, col: { id: string | number; getIsVisible: () => any; }) => {
62
+ acc[col.id] = col.getIsVisible();
63
+ return acc;
64
+ }, {}),
65
+ };
66
+ columnVisibilityControl?.onColumnVisibilityChange(updatedVisibilityState);
67
+ }
68
+ };
69
+
70
+ return (
71
+ <Checkbox
72
+ checked={show}
73
+ key={id}
74
+ onChange={handleVisibilityChange}
75
+ paddingBottom="xs"
76
+ text={label}
77
+ />
78
+ );
79
+ };
80
+
81
+ const gatherLeafIds = (node: VisibilityNode): string[] =>
82
+ node.children && node.children.length
83
+ ? node.children.flatMap(gatherLeafIds)
84
+ : node.id
85
+ ? [node.id]
86
+ : [];
87
+
88
+ const renderGroup = (node: VisibilityNode ) => {
89
+ const leaves = gatherLeafIds(node);
90
+ const visibleArray = leaves.map((id) => table.getColumn(id).getIsVisible());
91
+ const allOn = visibleArray.every(Boolean);
92
+ const someOn = visibleArray.some(Boolean);
93
+
94
+ const handleGroupVisibilityChange = () => {
95
+ leaves.forEach((id) => table.getColumn(id).toggleVisibility(!allOn));
96
+ if (columnVisibilityControl?.onColumnVisibilityChange) {
97
+ const updatedVisibilityState = {
98
+ ...table.getAllColumns().reduce((acc: { [x: string]: any; }, col: { id: string | number; getIsVisible: () => any; }) => {
99
+ acc[col.id] = col.getIsVisible();
100
+ return acc;
101
+ }, {}),
102
+ };
103
+ columnVisibilityControl?.onColumnVisibilityChange(updatedVisibilityState);
104
+ }
105
+ };
106
+ return (
107
+ <>
108
+ <Checkbox
109
+ checked={allOn}
110
+ indeterminate={!allOn && someOn}
111
+ onChange={handleGroupVisibilityChange}
112
+ paddingBottom="xs"
113
+ text={node.label}
114
+ />
115
+ <Flex flexDirection="column"
116
+ paddingLeft="lg"
117
+ >
118
+ {node?.children?.map((child) =>
119
+ child.children ? renderGroup(child) : renderLeaf(child.id, child.label),
120
+ )}
121
+ </Flex>
122
+ </>
123
+ );
124
+ };
125
+ // ------------ End of column visibility logic --------
20
126
 
21
127
  useEffect(() => {
22
- if (cardRef.current) {
128
+ if (cardRef.current && type === "row-selection") {
23
129
  if (isVisible) {
24
130
  showActionBar(cardRef.current);
25
131
  } else {
26
132
  hideActionBar(cardRef.current);
27
133
  }
28
134
  }
29
- }, [isVisible]);
135
+ }, [isVisible, type]);
136
+
137
+ const [showPopover, setShowPopover] = useState(false)
138
+
139
+ const togglePopover = () => setShowPopover((prev) => !prev)
140
+ const handleShouldClose = (shouldClose: boolean) =>
141
+ setShowPopover(!shouldClose)
142
+
143
+ const popoverReference = (
144
+ <Tooltip
145
+ placement="top"
146
+ text="Column Configuration"
147
+ >
148
+ <div onClick={togglePopover}>
149
+ <Icon
150
+ color="primary"
151
+ cursor="pointer"
152
+ icon="sliders-h"
153
+ />
154
+ </div>
155
+ </Tooltip>
156
+ )
30
157
 
31
158
  return (
32
159
  <Card
33
160
  borderNone={!isVisible}
34
- className={`${isVisible && "show-action-card row-selection-actions-card"}`}
161
+ className={`${
162
+ isVisible && "show-action-card row-selection-actions-card"
163
+ }`}
35
164
  htmlOptions={{ ref: cardRef as any }}
36
165
  padding={`${isVisible ? "xs" : "none"}`}
37
166
  >
38
167
  <Flex
39
168
  alignItems="center"
40
- justify="between"
169
+ justify={type === "row-selection" ? "between" : "end"}
41
170
  >
42
- <Caption
43
- color="light"
44
- paddingLeft="xs"
45
- size="xs"
46
- >
47
- {selectedCount} Selected
48
- </Caption>
49
- <FlexItem>{actions}</FlexItem>
171
+ {type === "row-selection" ? (
172
+ <>
173
+ <Caption color="light"
174
+ paddingLeft="xs"
175
+ size="xs"
176
+ >
177
+ {selectedCount} Selected
178
+ </Caption>
179
+ <FlexItem>{actions}</FlexItem>
180
+ </>
181
+ ) : (
182
+ <PbReactPopover
183
+ closeOnClick="outside"
184
+ placement="bottom-end"
185
+ reference={popoverReference}
186
+ shouldClosePopover={handleShouldClose}
187
+ show={showPopover}
188
+ zIndex={3}
189
+ >
190
+ <>
191
+ <Caption
192
+ paddingY="sm"
193
+ text="Columns Config"
194
+ textAlign="center"
195
+ />
196
+ <SectionSeparator paddingBottom="xs" />
197
+ {tree.map((node: VisibilityNode) => (
198
+ <Flex cursor="pointer"
199
+ flexDirection="column"
200
+ key={node.id}
201
+ paddingX="xs"
202
+ >
203
+ {node.children ? renderGroup(node) : renderLeaf(node.id, node.label)}
204
+ </Flex>
205
+ ))}
206
+ </>
207
+ </PbReactPopover>
208
+ )}
50
209
  </Flex>
51
210
  </Card>
52
211
  );
@@ -1,4 +1,4 @@
1
- import React, { useContext } from "react"
1
+ import React, { useContext, useState } from "react"
2
2
  import classnames from "classnames"
3
3
  import { flexRender, Header, Table, RowModel } from "@tanstack/react-table"
4
4
 
@@ -8,10 +8,8 @@ import { GlobalProps } from "../../utilities/globalProps"
8
8
 
9
9
  import Flex from "../../pb_flex/_flex"
10
10
  import Checkbox from "../../pb_checkbox/_checkbox"
11
- import Dropdown from "../../pb_dropdown/_dropdown"
12
- import DropdownTrigger from "../../pb_dropdown/subcomponents/DropdownTrigger"
13
- import DropdownOption from "../../pb_dropdown/subcomponents/DropdownOption"
14
- import DropdownContainer from "../../pb_dropdown/subcomponents/DropdownContainer"
11
+ import SectionSeparator from "../../pb_section_separator/_section_separator"
12
+ import PbReactPopover from "../../pb_popover/_popover";
15
13
  import Icon from "../../pb_icon/_icon"
16
14
 
17
15
  import { SortIconButton } from "./SortIconButton"
@@ -79,9 +77,26 @@ export const TableHeaderCell = ({
79
77
  header?.column.getLeafColumns().length === 1 &&
80
78
  header?.column.getLeafColumns()[0].id === header.column.id
81
79
 
82
- const isLastHeaderCell =
83
- header?.column.parent?.columns.at(-1) === header?.column ||
84
- (header?.colSpan > 1 && header?.column.parent !== undefined);
80
+ const columnHasVisibleLeaf = (col: any): boolean =>
81
+ col.getIsVisible?.() ||
82
+ (Array.isArray(col.columns) &&
83
+ col.columns.some((child: any) => columnHasVisibleLeaf(child)));
84
+
85
+ // Check on column position in stack + visibility to add the vertical border
86
+ const isLastHeaderCell = (() => {
87
+ if (!header) return false;
88
+
89
+ if (header.colSpan > 1 && header.column.parent !== undefined) return true;
90
+
91
+ const parent = header.column.parent;
92
+
93
+ if (!parent) {
94
+ const topHeaders = table?.getHeaderGroups()[0].headers.filter((h: any) => columnHasVisibleLeaf(h.column));
95
+ return topHeaders?.at(-1)?.id === header.id;
96
+ }
97
+ const visibleSiblings = parent.columns.filter(columnHasVisibleLeaf);
98
+ return visibleSiblings.at(-1) === header.column;
99
+ })();
85
100
 
86
101
  const cellClassName = classnames(
87
102
  "table-header-cells",
@@ -119,6 +134,20 @@ const isToggleExpansionEnabled =
119
134
  justifyHeader = isLeafColumn ? "end" : "center";
120
135
  }
121
136
 
137
+ const [showPopover, setShowPopover] = useState(false)
138
+
139
+ const togglePopover = () => setShowPopover((prev) => !prev)
140
+ const handleShouldClose = (shouldClose: boolean) =>
141
+ setShowPopover(!shouldClose)
142
+
143
+ const popoverReference = (
144
+ <div className="gray-icon toggle-all-icon"
145
+ onClick={togglePopover}
146
+ >
147
+ <Icon icon={displayIcon(toggleExpansionIcon)[0]} />
148
+ </div>
149
+ )
150
+
122
151
  const handleExpandDepth = (depth: number) => {
123
152
  if (onExpandByDepthClick) {
124
153
  const flatRows = table?.getRowModel().flatRows
@@ -174,31 +203,33 @@ const isToggleExpansionEnabled =
174
203
  <ToggleIconButton onClick={handleExpandOrCollapse} />
175
204
  )}
176
205
  {isToggleExpansionEnabled && hasAnySubRows && expandByDepth && (
177
- <Dropdown className="expand-by-depth-dropdown-wrapper"
178
- options={expandByDepth}
179
- >
180
- <DropdownTrigger className="gray-icon toggle-all-icon">
181
- <Icon icon={displayIcon(toggleExpansionIcon)[0]} />
182
- </DropdownTrigger>
183
- <DropdownContainer className="expand-by-depth-dropdown">
184
- {expandByDepth.map((option:{ [key: string]: any }, index: number) => (
185
- <DropdownOption
186
- key={index}
187
- option={option}
188
- padding="none"
206
+
207
+ <PbReactPopover
208
+ closeOnClick="any"
209
+ placement="bottom-start"
210
+ reference={popoverReference}
211
+ shouldClosePopover={handleShouldClose}
212
+ show={showPopover}
213
+ zIndex={3}
189
214
  >
215
+ {expandByDepth.map((option:{ [key: string]: any }, index: number) => (
216
+ <>
190
217
  <Flex
191
218
  alignItems="center"
219
+ className="pb-advanced-table-popover-option"
220
+ cursor="pointer"
192
221
  htmlOptions={{onClick: () => {handleExpandDepth(option.depth)} }}
193
222
  paddingX="sm"
194
223
  paddingY="xs"
195
224
  >
196
225
  {option.label}
197
- </Flex>
198
- </DropdownOption>
199
- ))}
200
- </DropdownContainer>
201
- </Dropdown>
226
+ </Flex>
227
+ {index !== expandByDepth.length - 1 && <SectionSeparator/>}
228
+ </>
229
+ ))}
230
+ </PbReactPopover>
231
+
232
+
202
233
  )}
203
234
 
204
235
  {isToggleExpansionEnabledLoading &&(
@@ -26,6 +26,8 @@ interface UseTableStateProps {
26
26
  virtualizedRows?: boolean;
27
27
  tableOptions?: GenericObject;
28
28
  onRowSelectionChange?: (arg: RowSelectionState) => void;
29
+ columnVisibilityControl?: GenericObject;
30
+
29
31
  }
30
32
 
31
33
  export function useTableState({
@@ -40,16 +42,19 @@ export function useTableState({
40
42
  pagination = false,
41
43
  paginationProps,
42
44
  virtualizedRows = false,
43
- tableOptions
45
+ tableOptions,
46
+ columnVisibilityControl
44
47
  }: UseTableStateProps) {
45
48
  // Create a local state for expanded and setExpanded if expandedControl not used
46
49
  const [localExpanded, setLocalExpanded] = useState({});
47
50
  const [loadingStateRowCount, setLoadingStateRowCount] = useState(initialLoadingRowsCount);
48
51
  const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
49
-
52
+ const [localColumnVisibility, setLocalColumnVisibility] = useState({});
50
53
  // Determine whether to use the prop or the local state
51
54
  const expanded = expandedControl ? expandedControl.value : localExpanded;
52
55
  const setExpanded = expandedControl ? expandedControl.onChange : setLocalExpanded;
56
+ const columnVisibility = (columnVisibilityControl && columnVisibilityControl.value) ? columnVisibilityControl.value : localColumnVisibility;
57
+ const setColumnVisibility = (columnVisibilityControl && columnVisibilityControl.onChange) ? columnVisibilityControl.onChange : setLocalColumnVisibility;
53
58
 
54
59
  // Virtualized data handling (chunked loading)
55
60
  const fetchSize = 20; // Number of rows per "page"
@@ -104,17 +109,21 @@ export function useTableState({
104
109
  }]), [columnDefinitions, sortControl]);
105
110
 
106
111
  // Custom state based on features enabled
107
- const customState = useCallback(() => {
108
- if (sortControl && selectableRows) {
109
- return { state: { expanded, sorting, rowSelection } };
110
- } else if (sortControl) {
111
- return { state: { expanded, sorting } };
112
- } else if (selectableRows) {
113
- return { state: { expanded, rowSelection } };
114
- } else {
115
- return { state: { expanded } };
116
- }
117
- }, [expanded, rowSelection, sortControl, selectableRows, sorting]);
112
+ const customState = useCallback(() => ({
113
+ state: {
114
+ expanded,
115
+ ...(sortControl && { sorting }),
116
+ ...(selectableRows && { rowSelection }),
117
+ ...(columnVisibility && { columnVisibility }),
118
+ },
119
+ }), [
120
+ expanded,
121
+ sortControl,
122
+ sorting,
123
+ selectableRows,
124
+ rowSelection,
125
+ columnVisibility,
126
+ ]);
118
127
 
119
128
  // Pagination configuration
120
129
  const paginationInitializer = useMemo(() => {
@@ -145,6 +154,7 @@ export function useTableState({
145
154
  sortDescFirst: true,
146
155
  onRowSelectionChange: setRowSelection,
147
156
  getRowId: selectableRows ? row => row.id : undefined,
157
+ onColumnVisibilityChange: setColumnVisibility,
148
158
  meta: {
149
159
  columnDefinitions
150
160
  },
@@ -0,0 +1,47 @@
1
+ export interface VisibilityNode {
2
+ id: string;
3
+ label: string;
4
+ children?: VisibilityNode[];
5
+ }
6
+ export const buildVisibilityTree = (
7
+ defs: any[],
8
+ allowed?: string[] | null
9
+ ): VisibilityNode[] =>
10
+ defs
11
+ .map((def) => {
12
+ const isGroup = Array.isArray(def.columns) && def.columns.length > 0;
13
+
14
+ // No filter at all → keep it
15
+ if (!allowed?.length) {
16
+ return isGroup
17
+ ? {
18
+ id: def.id,
19
+ label: def.label,
20
+ children: buildVisibilityTree(def.columns, allowed),
21
+ }
22
+ : { id: def.id, label: def.label };
23
+ }
24
+
25
+ // 1️⃣ If *this* ID is explicitly allowed → keep it & all its children
26
+ if (allowed.includes(def.id)) {
27
+ return isGroup
28
+ ? {
29
+ id: def.id,
30
+ label: def.label,
31
+ children: buildVisibilityTree(def.columns, null),
32
+ }
33
+ : { id: def.id, label: def.label };
34
+ }
35
+
36
+ // Otherwise, if it’s a group, recurse & keep only if kids survive
37
+ if (isGroup) {
38
+ const kids = buildVisibilityTree(def.columns, allowed).filter(Boolean);
39
+ return kids.length
40
+ ? { id: def.id, label: def.label, children: kids }
41
+ : null;
42
+ }
43
+
44
+ // Leaf not allowed → drop it
45
+ return null;
46
+ })
47
+ .filter(Boolean);
@@ -150,13 +150,6 @@
150
150
  box-sizing: border-box !important;
151
151
  }
152
152
  }
153
- .expand-by-depth-dropdown-wrapper {
154
- position: unset !important;
155
- }
156
- .expand-by-depth-dropdown {
157
- width: unset !important;
158
- text-align: left;
159
- }
160
153
  }
161
154
 
162
155
  .pb_advanced_table_body {
@@ -375,6 +368,10 @@
375
368
  box-shadow: 1px 0px 0px 0px var(--column-border-color) !important;
376
369
  }
377
370
 
371
+ .pb_table_td:nth-child(2) {
372
+ box-shadow: inset 1px 0px 0px 0px var(--column-border-color) !important;
373
+ }
374
+
378
375
  // Color for collapsible trail
379
376
  .collapsible-trail {
380
377
  background-color: $border_light !important;
@@ -545,14 +542,12 @@
545
542
  background-color: $white;
546
543
  box-shadow: $shadow_deep;
547
544
  }
545
+
548
546
  @include advanced-table-sticky-mixin(
549
547
  $border_light,
550
548
  $white,
551
549
  lighten($silver, $opacity_7)
552
550
  );
553
-
554
- // Apply border colors for sticky columns
555
- @include apply-sticky-colors("light");
556
551
  }
557
552
 
558
553
  // Responsive Styles
@@ -573,6 +568,10 @@
573
568
  box-shadow: $shadow_deep !important;
574
569
  }
575
570
 
571
+ .pb_table_td:nth-child(2) {
572
+ box-shadow: 0 0 0 0 !important;
573
+ }
574
+
576
575
  .pb_advanced_table_header,
577
576
  .pb_advanced_table_body {
578
577
  th.sticky-left,
@@ -818,3 +817,8 @@
818
817
  }
819
818
  }
820
819
  }
820
+
821
+ // Outside of the pb_advanced_table class for popover
822
+ .pb-advanced-table-popover-option:hover {
823
+ background-color: $bg_light;
824
+ }
@@ -35,6 +35,7 @@ type AdvancedTableProps = {
35
35
  className?: string
36
36
  columnDefinitions: GenericObject[]
37
37
  columnGroupBorderColor?: "text_lt_default" | "text_lt_light" | "text_lt_lighter" | "text_dk_default" | "text_dk_light" | "text_dk_lighter"
38
+ columnVisibilityControl?: GenericObject
38
39
  dark?: boolean
39
40
  data?: { [key: string]: string }
40
41
  enableToggleExpansion?: "all" | "header" | "none"
@@ -73,6 +74,7 @@ const AdvancedTable = (props: AdvancedTableProps) => {
73
74
  className,
74
75
  columnDefinitions,
75
76
  columnGroupBorderColor,
77
+ columnVisibilityControl,
76
78
  dark = false,
77
79
  data = {},
78
80
  enableToggleExpansion = "header",
@@ -132,7 +134,8 @@ const AdvancedTable = (props: AdvancedTableProps) => {
132
134
  paginationProps,
133
135
  virtualizedRows,
134
136
  tableOptions,
135
- onRowSelectionChange
137
+ onRowSelectionChange,
138
+ columnVisibilityControl,
136
139
  });
137
140
 
138
141
  // Initialize table actions
@@ -252,7 +255,7 @@ const AdvancedTable = (props: AdvancedTableProps) => {
252
255
  : {};
253
256
 
254
257
  // Visibility flag for action bar
255
- const isActionBarVisible = selectableRows && showActionsBar && selectedRowsLength > 0;
258
+ const isActionBarVisible = (selectableRows && showActionsBar && selectedRowsLength > 0) || columnVisibilityControl;
256
259
 
257
260
  return (
258
261
  <>
@@ -286,6 +289,7 @@ const AdvancedTable = (props: AdvancedTableProps) => {
286
289
  <AdvancedTableProvider
287
290
  columnDefinitions={columnDefinitions}
288
291
  columnGroupBorderColor={columnGroupBorderColor}
292
+ columnVisibilityControl={columnVisibilityControl}
289
293
  enableToggleExpansion={enableToggleExpansion}
290
294
  enableVirtualization={virtualizedRows}
291
295
  expandByDepth={expandByDepth}
@@ -316,6 +320,7 @@ const AdvancedTable = (props: AdvancedTableProps) => {
316
320
  actions={actions}
317
321
  isVisible={isActionBarVisible}
318
322
  selectedCount={selectedRowsLength}
323
+ type={columnVisibilityControl ? "column-visibility" : "row-selection"}
319
324
  />
320
325
 
321
326
  {/* Main Table */}
@@ -1,10 +1,18 @@
1
1
  <%= pb_content_tag do %>
2
- <%= 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 %>
3
- <% if content.present? %>
4
- <% content.presence %>
5
- <% else %>
6
- <%= pb_rails("advanced_table/table_header", props: { column_definitions: object.column_definitions, enable_toggle_expansion: object.enable_toggle_expansion, responsive: object.responsive, loading: object.loading, selectable_rows: object.selectable_rows }) %>
7
- <%= pb_rails("advanced_table/table_body", props: { 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 }) %>
8
- <% end %>
9
- <% end %>
2
+ <% if object.id && object.selectable_rows && object.show_actions_bar %>
3
+ <%= pb_rails("advanced_table/table_action_bar", props: {
4
+ actions: object.actions,
5
+ is_visible: false,
6
+ selected_count: 0
7
+ }) %>
8
+ <% end %>
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 %>
11
+ <% if content.present? %>
12
+ <% content.presence %>
13
+ <% else %>
14
+ <%= pb_rails("advanced_table/table_header", props: { 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 }) %>
15
+ <%= pb_rails("advanced_table/table_body", props: { 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 }) %>
16
+ <% end %>
17
+ <% end %>
10
18
  <% end %>
@@ -25,6 +25,10 @@ module Playbook
25
25
  default: "auto"
26
26
  prop :selectable_rows, type: Playbook::Props::Boolean,
27
27
  default: false
28
+ prop :show_actions_bar, type: Playbook::Props::Boolean,
29
+ default: true
30
+ prop :actions, type: Playbook::Props::Array,
31
+ default: []
28
32
 
29
33
  def classname
30
34
  additional_classes = [responsive_classname, max_height_classname]
@@ -47,6 +51,11 @@ module Playbook
47
51
  def selected_rows_length
48
52
  selected_rows.length
49
53
  end
54
+
55
+ def is_action_bar_visible
56
+ # Action bar visibility is controlled by JS based on selection
57
+ false
58
+ end
50
59
  end
51
60
  end
52
61
  end