playbook_ui 14.23.0.pre.alpha.PLAY2243customindeterminatemaincheckboxlabels9046 → 14.23.0.pre.alpha.PLAY2283multiheaderverticalbordersdoc9335

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 (117) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/_playbook.scss +0 -1
  3. data/app/pb_kits/playbook/pb_advanced_table/Components/CustomCell.tsx +7 -6
  4. data/app/pb_kits/playbook/pb_advanced_table/Components/SortIconButton.tsx +24 -25
  5. data/app/pb_kits/playbook/pb_advanced_table/Components/TableActionBar.tsx +10 -10
  6. data/app/pb_kits/playbook/pb_advanced_table/Components/TableHeaderCell.tsx +11 -12
  7. data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableState.ts +7 -4
  8. data/app/pb_kits/playbook/pb_advanced_table/SubKits/TableHeader.tsx +1 -1
  9. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +78 -2
  10. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +13 -7
  11. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.html.erb +2 -2
  12. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.rb +14 -2
  13. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +34 -0
  14. data/app/pb_kits/playbook/pb_advanced_table/advanced_table_action_bar.js +16 -0
  15. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_headers_vertical_border.html.erb +43 -0
  16. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_headers_vertical_border.jsx +64 -0
  17. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_row_styling.html.erb +46 -0
  18. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_row_styling_rails.md +7 -0
  19. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_selectable_rows_header_rails.html.erb +1 -1
  20. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_selectable_rows_rails.html.erb +1 -1
  21. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_sort_per_column.jsx +55 -0
  22. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_sort_per_column.md +6 -0
  23. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_sort_per_column_for_multi_column.jsx +80 -0
  24. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_sort_per_column_for_multi_column.md +1 -0
  25. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_with_custom_header.md +1 -1
  26. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_with_custom_header_multi_header.jsx +107 -0
  27. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_with_custom_header_multi_header.md +1 -0
  28. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_with_custom_header_rails.html.erb +51 -0
  29. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_with_custom_header_rails.md +1 -0
  30. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +7 -0
  31. data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +4 -0
  32. data/app/pb_kits/playbook/pb_advanced_table/flat_advanced_table.js +4 -11
  33. data/app/pb_kits/playbook/pb_advanced_table/index.js +108 -125
  34. data/app/pb_kits/playbook/pb_advanced_table/scss_partials/advanced_table_sticky_mixin.scss +7 -1
  35. data/app/pb_kits/playbook/pb_advanced_table/table_body.rb +7 -4
  36. data/app/pb_kits/playbook/pb_advanced_table/table_header.html.erb +5 -1
  37. data/app/pb_kits/playbook/pb_advanced_table/table_header.rb +46 -4
  38. data/app/pb_kits/playbook/pb_advanced_table/table_row.html.erb +13 -4
  39. data/app/pb_kits/playbook/pb_advanced_table/table_row.rb +24 -5
  40. data/app/pb_kits/playbook/pb_advanced_table/table_subrow_header.rb +1 -1
  41. data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_legend_position.md +1 -1
  42. data/app/pb_kits/playbook/pb_checkbox/index.js +220 -30
  43. data/app/pb_kits/playbook/pb_circle_chart/docs/_circle_chart_legend_position.md +1 -1
  44. data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +17 -1
  45. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +6 -0
  46. data/app/pb_kits/playbook/pb_dropdown/_dropdown_mixin.scss +36 -0
  47. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_active_style_options.jsx +90 -0
  48. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_active_style_options_react.md +4 -0
  49. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_radio_options.jsx +1 -0
  50. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_radio_options_react.md +1 -1
  51. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +3 -2
  52. data/app/pb_kits/playbook/pb_dropdown/docs/index.js +2 -1
  53. data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +24 -0
  54. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownOption.tsx +11 -1
  55. data/app/pb_kits/playbook/pb_line_graph/docs/_description.md +1 -3
  56. data/app/pb_kits/playbook/pb_line_graph/docs/_line_graph_colors.jsx +36 -17
  57. data/app/pb_kits/playbook/pb_line_graph/docs/_line_graph_colors_react.md +3 -0
  58. data/app/pb_kits/playbook/pb_line_graph/docs/_line_graph_default.jsx +31 -16
  59. data/app/pb_kits/playbook/pb_line_graph/docs/_line_graph_height.jsx +63 -31
  60. data/app/pb_kits/playbook/pb_line_graph/docs/_line_graph_height.md +3 -0
  61. data/app/pb_kits/playbook/pb_line_graph/docs/_line_graph_legend.jsx +35 -16
  62. data/app/pb_kits/playbook/pb_line_graph/docs/_line_graph_legend_nonclickable.jsx +41 -16
  63. data/app/pb_kits/playbook/pb_line_graph/docs/_line_graph_legend_position.jsx +107 -62
  64. data/app/pb_kits/playbook/pb_line_graph/docs/_line_graph_legend_position.md +4 -7
  65. data/app/pb_kits/playbook/pb_line_graph/docs/example.yml +0 -1
  66. data/app/pb_kits/playbook/pb_line_graph/docs/index.js +0 -1
  67. data/app/pb_kits/playbook/pb_line_graph/lineGraphTheme.ts +16 -1
  68. data/app/pb_kits/playbook/pb_multi_level_select/_helper_functions.tsx +18 -9
  69. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +3 -1
  70. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_show_checked_children.html.erb +75 -0
  71. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_show_checked_children.jsx +94 -0
  72. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_show_checked_children.md +3 -0
  73. data/app/pb_kits/playbook/pb_multi_level_select/docs/index.js +1 -1
  74. data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.rb +3 -0
  75. data/app/pb_kits/playbook/pb_pagination/_pagination.tsx +4 -0
  76. data/app/pb_kits/playbook/pb_pagination/docs/_pagination_default_rails.md +3 -1
  77. data/app/pb_kits/playbook/pb_pagination/docs/_pagination_default_react.md +3 -1
  78. data/app/pb_kits/playbook/pb_phone_number_input/_phone_number_input.tsx +3 -0
  79. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_strict_mode.html.erb +10 -0
  80. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_strict_mode.jsx +26 -0
  81. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_strict_mode.md +3 -0
  82. data/app/pb_kits/playbook/pb_phone_number_input/docs/example.yml +2 -0
  83. data/app/pb_kits/playbook/pb_phone_number_input/docs/index.js +1 -0
  84. data/app/pb_kits/playbook/pb_phone_number_input/phone_number_input.rb +3 -0
  85. data/app/pb_kits/playbook/pb_select/select.rb +4 -2
  86. data/dist/chunks/_line_graph-D7DgMqnT.js +1 -0
  87. data/dist/chunks/_typeahead-BzYZCpJO.js +6 -0
  88. data/dist/chunks/_weekday_stacked-CCn-qLh_.js +37 -0
  89. data/dist/chunks/lib-CY5ZPzic.js +29 -0
  90. data/dist/chunks/{pb_form_validation-DF742j1h.js → pb_form_validation-D3b0JKHH.js} +1 -1
  91. data/dist/chunks/vendor.js +1 -1
  92. data/dist/menu.yml +3 -10
  93. data/dist/playbook-doc.js +2 -2
  94. data/dist/playbook-rails-react-bindings.js +1 -1
  95. data/dist/playbook-rails.js +1 -1
  96. data/dist/playbook.css +1 -1
  97. data/lib/playbook/version.rb +1 -1
  98. metadata +32 -22
  99. data/app/pb_kits/playbook/pb_line_graph/docs/_line_graph_pb_styles.jsx +0 -52
  100. data/app/pb_kits/playbook/pb_line_graph/docs/_line_graph_pb_styles.md +0 -1
  101. data/app/pb_kits/playbook/pb_walkthrough/_walkthrough.scss +0 -0
  102. data/app/pb_kits/playbook/pb_walkthrough/_walkthrough.tsx +0 -202
  103. data/app/pb_kits/playbook/pb_walkthrough/docs/_walkthrough_continuous.jsx +0 -69
  104. data/app/pb_kits/playbook/pb_walkthrough/docs/_walkthrough_default.jsx +0 -71
  105. data/app/pb_kits/playbook/pb_walkthrough/docs/_walkthrough_multi_beacon.jsx +0 -110
  106. data/app/pb_kits/playbook/pb_walkthrough/docs/_walkthrough_no_beacon.jsx +0 -76
  107. data/app/pb_kits/playbook/pb_walkthrough/docs/_walkthrough_no_overlay.jsx +0 -76
  108. data/app/pb_kits/playbook/pb_walkthrough/docs/_walkthrough_styled.jsx +0 -76
  109. data/app/pb_kits/playbook/pb_walkthrough/docs/example.yml +0 -10
  110. data/app/pb_kits/playbook/pb_walkthrough/docs/index.js +0 -6
  111. data/app/pb_kits/playbook/pb_walkthrough/walkthrough.test.jsx +0 -34
  112. data/dist/chunks/_gauge-BUpiCaK5.js +0 -1
  113. data/dist/chunks/_typeahead-ITbXBlyi.js +0 -6
  114. data/dist/chunks/_weekday_stacked-BIEMUAn8.js +0 -61
  115. data/dist/chunks/lib-AStGp3dD.js +0 -29
  116. /data/app/pb_kits/playbook/pb_advanced_table/docs/{_advanced_table_row_styling.md → _advanced_table_row_styling_react.md} +0 -0
  117. /data/app/pb_kits/playbook/pb_line_graph/docs/{_line_graph_colors.md → _line_graph_colors_rails.md} +0 -0
@@ -10,49 +10,239 @@ export default class PbCheckbox extends PbEnhancedElement {
10
10
  connect() {
11
11
  const mainCheckboxWrapper = this.element;
12
12
  const mainCheckbox = mainCheckboxWrapper.querySelector('input')
13
- const childCheckboxes = document.querySelectorAll(`[data-pb-checkbox-indeterminate-parent="${this.element.id}"] input[type="checkbox"]`);
13
+ const directChildCheckboxes = document.querySelectorAll(`[data-pb-checkbox-indeterminate-parent="${this.element.id}"] input[type="checkbox"]`);
14
14
 
15
- const updateMainCheckbox = () => {
16
- // Count the number of checked child checkboxes
17
- const checkedCount = Array.from(childCheckboxes).filter(cb => cb.checked).length;
18
- // Determine if the main checkbox should be in an indeterminate state
19
- const indeterminate = checkedCount > 0 && checkedCount < childCheckboxes.length;
20
-
21
- // Set the main checkbox states
22
- mainCheckbox.indeterminate = indeterminate;
23
- mainCheckbox.checked = checkedCount > 0;
24
-
25
- // Determine the main checkbox label based on the number of checked checkboxes
26
- const checkAllLabel = mainCheckboxWrapper.dataset.pbCheckboxIndeterminateMainLabelCheck ?? 'Check All'
27
- const uncheckAllLabel = mainCheckboxWrapper.dataset.pbCheckboxIndeterminateMainLabelUncheck ?? 'Uncheck All'
28
- const text = checkedCount === 0 ? checkAllLabel : uncheckAllLabel;
15
+ // Helper function to get all descendant checkboxes
16
+ const getAllDescendantCheckboxes = () => {
17
+ const descendants = [];
18
+ const queue = [...directChildCheckboxes];
19
+
20
+ // Breadth-first search to find all nested descendants
21
+ while (queue.length > 0) {
22
+ const checkbox = queue.shift();
23
+ descendants.push(checkbox);
24
+
25
+ // Find children of this checkbox
26
+ const checkboxWrapper = checkbox.closest('[data-pb-checkbox-indeterminate-main="true"]');
27
+ if (checkboxWrapper) {
28
+ const childCheckboxes = document.querySelectorAll(`[data-pb-checkbox-indeterminate-parent="${checkboxWrapper.id}"] input[type="checkbox"]`);
29
+ queue.push(...childCheckboxes);
30
+ }
31
+ }
32
+
33
+ // Also include any non-"main" checkboxes that have this as a parent
34
+ const nonMainChildCheckboxes = document.querySelectorAll(`[data-pb-checkbox-indeterminate-parent="${this.element.id}"] input[type="checkbox"]`);
35
+ nonMainChildCheckboxes.forEach(cb => {
36
+ if (!descendants.includes(cb)) {
37
+ descendants.push(cb);
38
+ }
39
+ });
40
+
41
+ return descendants;
42
+ };
29
43
 
30
- // Determine the icon class to add and remove based on the number of checked checkboxes
31
- const iconClassToAdd = checkedCount === 0 ? 'pb_checkbox_checkmark' : 'pb_checkbox_indeterminate';
32
- const iconClassToRemove = checkedCount === 0 ? 'pb_checkbox_indeterminate' : 'pb_checkbox_checkmark';
44
+ // Helper function to determine checkbox state
45
+ const getCheckboxState = (checkboxes) => {
46
+ const checkedCount = checkboxes.filter(cb => cb.checked).length;
47
+ const totalCount = checkboxes.length;
48
+
49
+ return {
50
+ allChecked: checkedCount === totalCount,
51
+ noneChecked: checkedCount === 0,
52
+ indeterminate: !(checkedCount === totalCount || checkedCount === 0),
53
+ checkedCount,
54
+ totalCount
55
+ };
56
+ };
33
57
 
34
- // Update main checkbox label
35
- mainCheckboxWrapper.getElementsByClassName('pb_body_kit')[0].textContent = text;
58
+ // Helper function to update checkbox visual state
59
+ const updateCheckboxVisualState = (checkbox, isIndeterminate, isChecked) => {
60
+ checkbox.indeterminate = isIndeterminate;
61
+ checkbox.checked = isChecked;
62
+ };
63
+
64
+ // Helper function to update checkbox label and icons
65
+ const updateCheckboxLabelAndIcons = (wrapper, isIndeterminate, checkedCount) => {
66
+ const checkAllLabel = wrapper.dataset.pbCheckboxIndeterminateMainLabelCheck ?? 'Check All';
67
+ const uncheckAllLabel = wrapper.dataset.pbCheckboxIndeterminateMainLabelUncheck ?? 'Uncheck All';
68
+ const text = checkedCount === 0 ? checkAllLabel : uncheckAllLabel;
69
+
70
+ // Update label
71
+ const bodyKitElement = wrapper.getElementsByClassName('pb_body_kit')[0];
72
+ if (bodyKitElement) {
73
+ bodyKitElement.textContent = text;
74
+ }
75
+
76
+ // Update icons
77
+ const iconSpan = wrapper.querySelector('[data-pb-checkbox-icon-span]');
78
+ if (iconSpan) {
79
+ const iconClassToAdd = isIndeterminate ? 'pb_checkbox_indeterminate' : 'pb_checkbox_checkmark';
80
+ const iconClassToRemove = isIndeterminate ? 'pb_checkbox_checkmark' : 'pb_checkbox_indeterminate';
81
+ iconSpan.classList.add(iconClassToAdd);
82
+ iconSpan.classList.remove(iconClassToRemove);
83
+ }
36
84
 
37
- // Add and remove the icon class to the main checkbox wrapper
38
- mainCheckboxWrapper.querySelector('[data-pb-checkbox-icon-span]').classList.add(iconClassToAdd);
39
- mainCheckboxWrapper.querySelector('[data-pb-checkbox-icon-span]').classList.remove(iconClassToRemove);
85
+ // Toggle icon visibility
86
+ const indeterminateIcon = wrapper.getElementsByClassName("indeterminate_icon")[0];
87
+ const checkIcon = wrapper.getElementsByClassName("check_icon")[0];
88
+
89
+ if (indeterminateIcon) {
90
+ indeterminateIcon.classList.toggle('hidden', !isIndeterminate);
91
+ }
92
+ if (checkIcon) {
93
+ checkIcon.classList.toggle('hidden', isIndeterminate);
94
+ }
95
+ };
96
+
97
+ // Main function to update this checkbox's state
98
+ const updateMainCheckbox = () => {
99
+ const allDescendantCheckboxes = getAllDescendantCheckboxes();
100
+ const state = getCheckboxState(allDescendantCheckboxes);
40
101
 
41
- // Toggle the visibility of the checkbox icon based on the indeterminate state
42
- mainCheckboxWrapper.getElementsByClassName("indeterminate_icon")[0].classList.toggle('hidden', !indeterminate);
43
- mainCheckboxWrapper.getElementsByClassName("check_icon")[0].classList.toggle('hidden', indeterminate);
102
+ updateCheckboxVisualState(mainCheckbox, state.indeterminate, state.allChecked);
103
+ updateCheckboxLabelAndIcons(mainCheckboxWrapper, state.indeterminate, state.checkedCount);
44
104
  };
45
105
 
46
- // Set indeterminate icon on main checkbox if initial children checkboxes are checked
106
+ // Function to update parent checkboxes recursively
107
+ const updateParentCheckboxes = () => {
108
+ const parentId = mainCheckboxWrapper.dataset.pbCheckboxIndeterminateParent;
109
+ if (parentId) {
110
+ const parentCheckbox = document.getElementById(parentId);
111
+ if (parentCheckbox) {
112
+ const parentWrapper = parentCheckbox.closest('[data-pb-checkbox-indeterminate-main="true"]');
113
+ if (parentWrapper) {
114
+ const parentInstance = parentWrapper.pbCheckboxInstance;
115
+ if (parentInstance && parentInstance.updateMainCheckbox) {
116
+ parentInstance.updateMainCheckbox();
117
+ parentInstance.updateParentCheckboxes();
118
+ }
119
+ }
120
+ }
121
+ }
122
+ };
123
+
124
+ // Function to update non-main checkboxes when their children change
125
+ const setupNonMainCheckboxUpdates = () => {
126
+ const allCheckboxesWithChildren = document.querySelectorAll('input[type="checkbox"]');
127
+ allCheckboxesWithChildren.forEach(cb => {
128
+ const checkboxWrapper = cb.closest('[data-pb-checkbox-indeterminate-main="true"]');
129
+ if (checkboxWrapper && checkboxWrapper !== mainCheckboxWrapper) {
130
+ return; // Skip different "main" checkboxes
131
+ }
132
+
133
+ const childCheckboxes = document.querySelectorAll(`[data-pb-checkbox-indeterminate-parent="${cb.id}"] input[type="checkbox"]`);
134
+ if (childCheckboxes.length > 0) {
135
+ childCheckboxes.forEach(childCb => {
136
+ childCb.addEventListener('change', () => {
137
+ const state = getCheckboxState(Array.from(childCheckboxes));
138
+ updateCheckboxVisualState(cb, state.indeterminate, state.allChecked);
139
+
140
+ // Trigger updates on all main checkboxes that might be affected
141
+ const mainCheckboxes = document.querySelectorAll('[data-pb-checkbox-indeterminate-main="true"]');
142
+ mainCheckboxes.forEach(mainCb => {
143
+ const mainInstance = mainCb.pbCheckboxInstance;
144
+ if (mainInstance && mainInstance.updateMainCheckbox) {
145
+ setTimeout(() => {
146
+ mainInstance.updateMainCheckbox();
147
+ }, 0);
148
+ }
149
+ });
150
+ });
151
+ });
152
+ }
153
+ });
154
+ };
155
+
156
+
157
+
158
+ // Initialize checkbox state
47
159
  updateMainCheckbox();
48
160
 
49
- this.element.querySelector('input').addEventListener('change', function() {
50
- childCheckboxes.forEach(cb => cb.checked = this.checked);
161
+ // Handle main checkbox change - propagate to all descendants
162
+ mainCheckbox.addEventListener('change', function() {
163
+ const allDescendantCheckboxes = getAllDescendantCheckboxes();
164
+ const state = getCheckboxState(allDescendantCheckboxes);
165
+
166
+ if (state.indeterminate) {
167
+ // If indeterminate, uncheck all descendants and the parent
168
+ allDescendantCheckboxes.forEach(cb => {
169
+ cb.checked = false;
170
+ // Dispatch custom event for programmatic changes- change styles in advanced table
171
+ cb.dispatchEvent(new Event('checkbox-programmatic-change', { bubbles: true }));
172
+ });
173
+ this.checked = false;
174
+ } else {
175
+ // Otherwise, set all descendants to the same state as this checkbox
176
+ allDescendantCheckboxes.forEach(cb => {
177
+ cb.checked = this.checked;
178
+ // Dispatch custom event for programmatic changes- change styles in advanced table
179
+ cb.dispatchEvent(new Event('checkbox-programmatic-change', { bubbles: true }));
180
+ });
181
+ }
182
+
183
+ // Update this checkbox first, then parents after a delay
51
184
  updateMainCheckbox();
185
+ setTimeout(() => {
186
+ updateParentCheckboxes();
187
+ }, 0);
188
+
189
+ // Also trigger updates on all main checkboxes to ensure proper state propagation
190
+ triggerAllMainCheckboxUpdates();
52
191
  });
53
192
 
54
- childCheckboxes.forEach(cb => {
193
+ // Handle child checkbox changes
194
+ directChildCheckboxes.forEach(cb => {
55
195
  cb.addEventListener('change', updateMainCheckbox);
56
196
  });
197
+
198
+ // Handle deeper descendant changes
199
+ const allDescendantCheckboxes = getAllDescendantCheckboxes();
200
+ allDescendantCheckboxes.forEach(cb => {
201
+ if (!Array.from(directChildCheckboxes).includes(cb)) {
202
+ cb.addEventListener('change', updateMainCheckbox);
203
+ }
204
+ });
205
+
206
+ // Handle non-main child checkboxes
207
+ const allChildCheckboxes = document.querySelectorAll(`[data-pb-checkbox-indeterminate-parent="${this.element.id}"] input[type="checkbox"]`);
208
+ allChildCheckboxes.forEach(cb => {
209
+ if (!allDescendantCheckboxes.includes(cb)) {
210
+ cb.addEventListener('change', updateMainCheckbox);
211
+ }
212
+ });
213
+
214
+ // Also trigger updates on all main checkboxes when any checkbox changes
215
+ let updateTimeout = null;
216
+ const triggerAllMainCheckboxUpdates = () => {
217
+ // Debounce the updates to prevent excessive calls
218
+ if (updateTimeout) {
219
+ clearTimeout(updateTimeout);
220
+ }
221
+ updateTimeout = setTimeout(() => {
222
+ const mainCheckboxes = document.querySelectorAll('[data-pb-checkbox-indeterminate-main="true"]');
223
+ mainCheckboxes.forEach(mainCb => {
224
+ const mainInstance = mainCb.pbCheckboxInstance;
225
+ if (mainInstance && mainInstance.updateMainCheckbox) {
226
+ mainInstance.updateMainCheckbox();
227
+ }
228
+ });
229
+ }, 10); // Small delay to batch updates
230
+ };
231
+
232
+ // Store the original updateMainCheckbox function and create a new one that also triggers updates
233
+ const originalUpdateMainCheckbox = updateMainCheckbox;
234
+ const enhancedUpdateMainCheckbox = () => {
235
+ originalUpdateMainCheckbox();
236
+ triggerAllMainCheckboxUpdates();
237
+ };
238
+
239
+ // Replace the updateMainCheckbox function
240
+ mainCheckboxWrapper.pbCheckboxInstance = {
241
+ updateMainCheckbox: enhancedUpdateMainCheckbox,
242
+ updateParentCheckboxes
243
+ };
244
+
245
+ // Setup updates for non-main checkboxes with children
246
+ setupNonMainCheckboxUpdates();
57
247
  }
58
248
  }
@@ -1,7 +1,7 @@
1
1
  ##### Prop
2
2
 
3
3
  `align` **Type**: String | **Values**: left | center | right (defaults to center)
4
- `verticalAlign` **Type**: String | **Values**: top | middle | bottom (defaults middle)
4
+ `verticalAlign` **Type**: String | **Values**: top | middle | bottom (defaults to bottom)
5
5
  `layout` **Type**: String | **Values**: horizontal | vertical | proximate (defaults to horizontal)
6
6
  `x` **Type**: Number (defaults to 0)
7
7
  `y` **Type**: Number (defaults to 0)
@@ -8,6 +8,7 @@
8
8
  @import "../pb_textarea/textarea_mixin";
9
9
 
10
10
  @import "./scss_partials/dropdown_animation";
11
+ @import "dropdown_mixin";
11
12
 
12
13
  [class*="pb_dropdown"] {
13
14
  .dropdown_wrapper {
@@ -98,9 +99,23 @@
98
99
  [class^="pb_title_kit"], a {
99
100
  color: $white !important;
100
101
  }
102
+ border-bottom: 1px solid $border_light;
101
103
  &:hover {
102
- background-color: $product_1_background !important;
104
+ background-color: $product_1_background;
105
+ }
106
+
107
+ // activeStyle font color map
108
+ @each $name, $color in $font-colors {
109
+ &.font-#{$name} {
110
+ @include apply-font-color($color);
111
+ }
103
112
  }
113
+ // activeStyle background color map (no difference between selected and selected+hover custom colors)
114
+ @each $name, $bg in $background-colors {
115
+ &.bg-#{$name} {
116
+ background-color: $bg;
117
+ }
118
+ }
104
119
  }
105
120
  }
106
121
 
@@ -267,6 +282,7 @@
267
282
  }
268
283
  &[class*="selected"] {
269
284
  background-color: $primary;
285
+ border-bottom: rgba($white, 0.15);
270
286
  }
271
287
  }
272
288
  }
@@ -39,6 +39,10 @@ type DropdownProps = {
39
39
  options: GenericObject;
40
40
  separators?: boolean;
41
41
  variant?: "default" | "subtle";
42
+ activeStyle?: {
43
+ backgroundColor?: string;
44
+ fontColor?: string;
45
+ };
42
46
  };
43
47
 
44
48
  interface DropdownComponent
@@ -69,6 +73,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
69
73
  options,
70
74
  separators = true,
71
75
  variant = "default",
76
+ activeStyle,
72
77
  } = props;
73
78
 
74
79
  const ariaProps = buildAriaProps(aria);
@@ -251,6 +256,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
251
256
  >
252
257
  <DropdownContext.Provider
253
258
  value={{
259
+ activeStyle,
254
260
  autocomplete,
255
261
  dropdownContainerRef,
256
262
  filteredOptions,
@@ -0,0 +1,36 @@
1
+ @import "../tokens/colors";
2
+
3
+ // activeStyle fontColor sass map to go through text colors + set of custom colors
4
+ $custom-font-colors: (
5
+ primary: $primary
6
+ );
7
+
8
+ $merged-font-colors: map-merge($text_colors, $custom-font-colors);
9
+
10
+ $font-colors: ();
11
+
12
+ @each $key, $val in $merged-font-colors {
13
+ $font-colors: map-merge($font-colors, ($key: $val));
14
+ }
15
+
16
+ @mixin apply-font-color($color) {
17
+ color: $color;
18
+
19
+ [class^="pb_body"],
20
+ [class^="pb_title_kit"],
21
+ a {
22
+ color: $color !important;
23
+ }
24
+ }
25
+
26
+ // activeStyle backgroundColor map (set of custom colors)
27
+ $custom-background-colors: (
28
+ "bg_light": $bg_light,
29
+ "white": $white,
30
+ );
31
+
32
+ $background-colors: ();
33
+
34
+ @each $key, $val in $custom-background-colors {
35
+ $background-colors: map-merge($background-colors, ($key: $val));
36
+ }
@@ -0,0 +1,90 @@
1
+ import React from 'react'
2
+ import Dropdown from '../_dropdown'
3
+
4
+ const DropdownCustomActiveStyleOptions = (props) => {
5
+
6
+
7
+ const options = [
8
+ {
9
+ label: "United States",
10
+ value: "unitedStates",
11
+ id: "us"
12
+ },
13
+ {
14
+ label: "Canada",
15
+ value: "canada",
16
+ id: "ca"
17
+ },
18
+ {
19
+ label: "Pakistan",
20
+ value: "pakistan",
21
+ id: "pk"
22
+ }
23
+ ];
24
+
25
+
26
+ return (
27
+ <div>
28
+ <Dropdown
29
+ activeStyle={{
30
+ backgroundColor: "bg_light",
31
+ fontColor: "primary",
32
+ }}
33
+ label="Background Color: bg_light; Font Color: primary"
34
+ marginBottom="sm"
35
+ options={options}
36
+ {...props}
37
+ >
38
+ <Dropdown.Trigger/>
39
+ <Dropdown.Container>
40
+ {options.map((option) => (
41
+ <Dropdown.Option key={option.id}
42
+ option={option}
43
+ />
44
+ ))}
45
+ </Dropdown.Container>
46
+ </Dropdown>
47
+ <Dropdown
48
+ activeStyle={{
49
+ backgroundColor: "white",
50
+ fontColor: "primary",
51
+ }}
52
+ label="Background Color: white; Font Color: primary"
53
+ marginBottom="sm"
54
+ options={options}
55
+ {...props}
56
+ />
57
+ <Dropdown
58
+ activeStyle={{
59
+ backgroundColor: "bg_light",
60
+ fontColor: "text_lt_default",
61
+ }}
62
+ autocomplete
63
+ label="Background Color: bg_light; Font Color: text_lt_default"
64
+ marginBottom="sm"
65
+ options={options}
66
+ {...props}
67
+ />
68
+ <Dropdown
69
+ activeStyle={{
70
+ fontColor: "text_lt_lighter",
71
+ }}
72
+ label="Font Color: text_lt_lighter"
73
+ marginBottom="sm"
74
+ options={options}
75
+ {...props}
76
+ >
77
+ <Dropdown.Trigger/>
78
+ <Dropdown.Container>
79
+ {options.map((option) => (
80
+ <Dropdown.Option key={option.id}
81
+ option={option}
82
+ />
83
+ ))}
84
+ </Dropdown.Container>
85
+ </Dropdown>
86
+ </div>
87
+ )
88
+ }
89
+
90
+ export default DropdownCustomActiveStyleOptions
@@ -0,0 +1,4 @@
1
+ The `activeStyle` prop can be used to customize the appearance of the dropdown selection indicator. It accepts an object with the following keys: `backgroundColor` sets the background color of the selected item (and its hover state); `fontColor` sets the font color of the selected item.
2
+
3
+ `backgroundColor` **Type**: String | **Values**: bg_light | white | **Default**: (no selection) is primary
4
+ `fontColor` **Type**: String | **Values**: primary | all [Playbook Text Colors](https://playbook.powerapp.cloud/visual_guidelines/colors) | **Default**: (no selection) is white
@@ -18,6 +18,7 @@ const DropdownCustomRadioOptions = (props) => {
18
18
  return (
19
19
  <div>
20
20
  <Dropdown
21
+ activeStyle={{ backgroundColor: "bg_light", fontColor: "text_lt_default" }}
21
22
  label="Select Item"
22
23
  onSelect={(selectedItem) => setSelectedValue(selectedItem?.value)}
23
24
  options={options}
@@ -1 +1 @@
1
- Radio inputs can be used inside `Dropdown.Option` for a custom layout that mimics form-like selection within a dropdown.
1
+ Radio inputs can be used inside `Dropdown.Option` for a custom layout that mimics form-like selection within a dropdown. Use the [activeStyle](https://playbook.powerapp.cloud/kits/dropdown/react#custom-active-style-options) `backgroundColor` and `fontColor` props to create contrast between the Radio selection indicator and the Dropdown selection background indicator.
@@ -16,7 +16,7 @@ examples:
16
16
  - dropdown_with_search_rails: Custom Trigger Dropdown with Search
17
17
  - dropdown_with_custom_padding: Custom Option Padding
18
18
  - dropdown_with_custom_icon_options: Custom Icon Options
19
- # - dropdown_with_custom_radio_options: Custom Radio Options # TODO: Update and publish doc ex in [PLAY-2146](https://runway.powerhrg.com/backlog_items/PLAY-2146) (remove this comment afterwards)
19
+ # - dropdown_with_custom_radio_options: Custom Radio Options # TODO: Update and publish doc ex in the Rails follow up to [PLAY-2146](https://runway.powerhrg.com/backlog_items/PLAY-2146) (remove this comment afterwards)
20
20
  - dropdown_error: Dropdown with Error
21
21
  - dropdown_default_value: Default Value
22
22
  - dropdown_multi_select_with_default: Multi Select Default Value
@@ -39,8 +39,9 @@ examples:
39
39
  - dropdown_with_custom_trigger: Custom Trigger
40
40
  - dropdown_with_search: Custom Trigger Dropdown with Search
41
41
  - dropdown_with_custom_padding: Custom Option Padding
42
+ - dropdown_with_custom_active_style_options: Custom Active Style Options
42
43
  - dropdown_with_custom_icon_options: Custom Icon Options
43
- # - dropdown_with_custom_radio_options: Custom Radio Options # TODO: Update and publish doc ex in [PLAY-2146](https://runway.powerhrg.com/backlog_items/PLAY-2146) (remove this comment afterwards)
44
+ - dropdown_with_custom_radio_options: Custom Radio Options
44
45
  - dropdown_error: Dropdown with Error
45
46
  - dropdown_default_value: Default Value
46
47
  - dropdown_multi_select_with_default: Multi Select Default Value
@@ -21,4 +21,5 @@ export { default as DropdownMultiSelectWithAutocomplete } from './_dropdown_mult
21
21
  export { default as DropdownMultiSelectWithDefault } from './_dropdown_multi_select_with_default.jsx'
22
22
  export { default as DropdownMultiSelectWithCustomOptions } from './_dropdown_multi_select_with_custom_options.jsx'
23
23
  export {default as DropdownWithCustomIconOptions} from './_dropdown_with_custom_icon_options.jsx'
24
- export {default as DropdownWithCustomRadioOptions} from './_dropdown_with_custom_radio_options.jsx'
24
+ export {default as DropdownWithCustomRadioOptions} from './_dropdown_with_custom_radio_options.jsx'
25
+ export {default as DropdownWithCustomActiveStyleOptions} from './_dropdown_with_custom_active_style_options.jsx'
@@ -369,4 +369,28 @@ test("defaultValue works with multiSelect", () => {
369
369
  const option2 = Array.from(kit.querySelectorAll(".pb_dropdown_option_list"));
370
370
  const firstOpt = options[0].label
371
371
  expect(option2[0]).not.toHaveTextContent(firstOpt)
372
+ })
373
+
374
+ test("applies activeStyle backgroundColor and fontColor when selected", () => {
375
+ render(
376
+ <Dropdown
377
+ activeStyle={{
378
+ backgroundColor: "bg_light",
379
+ fontColor: "primary",
380
+ }}
381
+ data={{ testid: testId }}
382
+ options={options}
383
+ />
384
+ )
385
+
386
+ const kit = screen.getByTestId(testId)
387
+ const option = kit.querySelectorAll(".pb_dropdown_option_list")[1]
388
+
389
+ fireEvent.click(option)
390
+
391
+ const selected = kit.querySelector(".pb_dropdown_option_selected")
392
+
393
+ expect(selected).toBeInTheDocument()
394
+ expect(selected).toHaveClass("bg-bg_light")
395
+ expect(selected).toHaveClass("font-primary")
372
396
  })
@@ -41,6 +41,7 @@ const DropdownOption = (props: DropdownOptionProps) => {
41
41
  } = props;
42
42
 
43
43
  const {
44
+ activeStyle,
44
45
  filteredOptions,
45
46
  filterItem,
46
47
  focusedOptionIndex,
@@ -59,7 +60,6 @@ const DropdownOption = (props: DropdownOptionProps) => {
59
60
  ? selected.some((item) => item.label === option?.label)
60
61
  : (selected as GenericObject)?.label === option?.label;
61
62
 
62
-
63
63
  if (!isItemMatchingFilter(option) || (multiSelect && isSelected)) {
64
64
  return null;
65
65
  }
@@ -70,6 +70,14 @@ const DropdownOption = (props: DropdownOptionProps) => {
70
70
 
71
71
  const selectedClass = isSelected ? "selected" : "list";
72
72
 
73
+
74
+ const bgTokenClass = activeStyle?.backgroundColor
75
+ ? `bg-${activeStyle.backgroundColor}`
76
+ : "";
77
+ const fontTokenClass = activeStyle?.fontColor
78
+ ? `font-${activeStyle.fontColor}`
79
+ : "";
80
+
73
81
  const ariaProps = buildAriaProps(aria);
74
82
  const dataProps = buildDataProps(data);
75
83
  const htmlProps = buildHtmlProps(htmlOptions);
@@ -79,6 +87,8 @@ const DropdownOption = (props: DropdownOptionProps) => {
79
87
  selectedClass,
80
88
  focusedClass,
81
89
  ),
90
+ bgTokenClass,
91
+ fontTokenClass,
82
92
  globalProps(props),
83
93
  className
84
94
  );
@@ -1,3 +1 @@
1
- Line graphs are used to show changes in data over time. The default height of line graph is 400px and can be changed. The default height is in pixel units, but can also use percentage string (percentage would be that of the width. For example, `height:"50%"` would mean that the height is 50% of the width). This allows for preserving the aspect ratio across responsive sizes.
2
-
3
- For more information, see: <a href="https://api.highcharts.com/highcharts/chart.height" target="_blank"> highcharts/chart.height</a>.
1
+ **Important Note for the React Kit**: In order to leverage this kit, you must install `highcharts` and `highcharts-react-official` into your project as shown below. To then apply Playbook styles to your Highchart, import lineGraphTheme.ts from playbook-ui and merge it with your Highchart options. Then, pass the merged value to the options prop. Playbook’s styling will be applied automatically. See the examples in the documentation below.
@@ -1,6 +1,8 @@
1
1
  import React from 'react'
2
-
3
- import LineGraph from '../_line_graph'
2
+ import lineGraphTheme from '../lineGraphTheme'
3
+ import Highcharts from "highcharts"
4
+ import HighchartsReact from "highcharts-react-official"
5
+ import colors from '../../tokens/exports/_colors.module.scss'
4
6
 
5
7
  const data = [{
6
8
  name: 'Installation',
@@ -19,19 +21,36 @@ const data = [{
19
21
  data: [null, null, null, 3112, 4989, 5816, 15274, 18111],
20
22
  }]
21
23
 
22
- const LineGraphColors = (props) => (
23
- <div>
24
- <LineGraph
25
- axisTitle="Number of Employees"
26
- chartData={data}
27
- colors={['data-4', 'data-5', 'data-6', 'data-7', 'data-8']}
28
- id="line-colors"
29
- title="Line Graph with Custom Data Colors"
30
- xAxisCategories={['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']}
31
- yAxisMin={0}
32
- {...props}
33
- />
34
- </div>
35
- )
24
+ const categories = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
25
+
26
+ const LineGraphColors = () => {
27
+ const chartOptions = {
28
+ title: {
29
+ text: 'Line Graph with Custom Data Colors',
30
+ },
31
+ xAxis: {
32
+ categories: categories,
33
+ },
34
+ yAxis: {
35
+ min: 0,
36
+ title: {
37
+ text: 'Number of Employees',
38
+ },
39
+ },
40
+ series: data,
41
+ colors: [colors.data_4, colors.data_5, "#144075", colors.data_7, colors.data_8]
42
+ }
43
+
44
+ const options = Highcharts.merge({}, lineGraphTheme, chartOptions)
45
+
46
+ return (
47
+ <div>
48
+ <HighchartsReact
49
+ highcharts={Highcharts}
50
+ options={options}
51
+ />
52
+ </div>
53
+ )
54
+ }
36
55
 
37
- export default LineGraphColors
56
+ export default LineGraphColors