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
@@ -79,7 +79,7 @@ export default class PbAdvancedTable extends PbEnhancedElement {
79
79
  }
80
80
  if (!allChildrenChecked) {
81
81
  parentRow.classList.remove("bg-row-selection");
82
-
82
+
83
83
  if (this.isRowExpanded(parentRow)) {
84
84
  parentRow.classList.remove("bg-silver");
85
85
  parentRow.classList.add("bg-white");
@@ -201,15 +201,15 @@ export default class PbAdvancedTable extends PbEnhancedElement {
201
201
  this.toggleElement(this.target);
202
202
  }
203
203
  });
204
-
204
+
205
205
  this.hideCloseIcon();
206
-
206
+
207
207
  const table = this.element.closest("table");
208
-
208
+
209
209
  // Prevent duplicate initialization
210
210
  if (table.dataset.pbAdvancedTableInitialized) return;
211
211
  table.dataset.pbAdvancedTableInitialized = "true";
212
-
212
+
213
213
  // Bind checkbox change handlers for all row checkboxes
214
214
  const checkboxLabels = table.querySelectorAll("label[data-row-id]");
215
215
  checkboxLabels.forEach((label) => {
@@ -219,7 +219,7 @@ export default class PbAdvancedTable extends PbEnhancedElement {
219
219
  this.handleCheckboxClick(event);
220
220
  });
221
221
  });
222
-
222
+
223
223
  // Bind nested row expansion logic
224
224
  const nestedButtons = table.querySelectorAll("[data-advanced-table]");
225
225
  nestedButtons.forEach((button) => {
@@ -233,18 +233,18 @@ export default class PbAdvancedTable extends PbEnhancedElement {
233
233
  }
234
234
  });
235
235
  });
236
-
236
+
237
237
  // Bind select-all logic for this table
238
238
  const selectAllCheckbox = table.querySelector("#select-all-rows");
239
239
  if (selectAllCheckbox) {
240
240
  selectAllCheckbox.addEventListener("change", () => {
241
241
  const checkboxInput = selectAllCheckbox.querySelector('input[type="checkbox"]');
242
242
  const checkAll = checkboxInput.checked;
243
-
243
+
244
244
  const checkboxes = Array.from(
245
245
  table.querySelectorAll("label[data-row-id] input[type='checkbox']")
246
246
  );
247
-
247
+
248
248
  checkboxes.forEach((cb) => {
249
249
  cb.checked = checkAll;
250
250
  const rowId = cb.id;
@@ -260,14 +260,14 @@ export default class PbAdvancedTable extends PbEnhancedElement {
260
260
  rowEl?.classList.add("bg-white");
261
261
  }
262
262
  });
263
-
263
+
264
264
  checkboxes.forEach((cb) => this.updateParentCheckboxes(cb));
265
-
265
+
266
266
  this.updateTableSelectedRowsAttribute();
267
267
  });
268
268
  }
269
269
  }
270
-
270
+
271
271
 
272
272
  hideCloseIcon() {
273
273
  const closeIcon = this.element.querySelector(UP_ARROW_SELECTOR);
@@ -449,6 +449,149 @@ export default class PbAdvancedTable extends PbEnhancedElement {
449
449
  }
450
450
  }
451
451
 
452
+ // Isolate action bar functionality so it doesn't mix with existing functionality
453
+ class PbAdvancedTableActionBar {
454
+ constructor() {
455
+ this.init();
456
+ }
457
+
458
+ init() {
459
+ // Initialize action bars for all advanced tables with action bars
460
+ document.addEventListener('DOMContentLoaded', () => {
461
+ this.setupActionBars();
462
+ });
463
+
464
+ // Also run immediately in case DOM is already loaded
465
+ if (document.readyState === 'loading') {
466
+ // DOM is still loading
467
+ } else {
468
+ // DOM is already loaded
469
+ this.setupActionBars();
470
+ }
471
+ }
472
+
473
+ setupActionBars() {
474
+ const advancedTables = document.querySelectorAll('.pb_advanced_table');
475
+
476
+ advancedTables.forEach(table => {
477
+ // Only proceed if this table has both selectable rows AND an action bar
478
+ if (!this.shouldEnableActionBar(table)) return;
479
+
480
+ const actionBar = table.querySelector('.row-selection-actions-card');
481
+ if (!actionBar) return; // Skip tables without action bars
482
+
483
+ // Initialize action bar styles
484
+ this.initializeActionBar(actionBar);
485
+
486
+ // Set up checkbox listeners for this table
487
+ this.setupCheckboxListeners(table, actionBar);
488
+ });
489
+ }
490
+
491
+ shouldEnableActionBar(table) {
492
+ // Check if the table has selectable rows
493
+ const hasSelectableRows = table.querySelector('input[type="checkbox"]') !== null;
494
+
495
+ // Check if the table has a row selection action bar (not other types of action bars)
496
+ const hasRowSelectionActionBar = table.querySelector('.row-selection-actions-card') !== null;
497
+
498
+ // Additional check: look for the presence of row checkboxes with data-row-id
499
+ const hasRowCheckboxes = table.querySelector('label[data-row-id] input[type="checkbox"]') !== null;
500
+
501
+ // Only enable if ALL conditions are met:
502
+ // 1. Has selectable checkboxes
503
+ // 2. Has the specific row selection action bar
504
+ // 3. Has row checkboxes (not just other types of checkboxes)
505
+ return hasSelectableRows && hasRowSelectionActionBar && hasRowCheckboxes;
506
+ }
507
+
508
+ initializeActionBar(actionBar) {
509
+ // Set initial hidden state
510
+ Object.assign(actionBar.style, {
511
+ height: '0px',
512
+ overflow: 'hidden',
513
+ display: 'block',
514
+ opacity: '0'
515
+ });
516
+
517
+ // Remove any visibility classes
518
+ actionBar.classList.remove("p_xs", "is-visible", "show-action-card");
519
+ actionBar.classList.add("p_none");
520
+ }
521
+
522
+ setupCheckboxListeners(table, actionBar) {
523
+ // Only listen to row checkboxes (those with data-row-id), not all checkboxes
524
+ const rowCheckboxes = table.querySelectorAll('label[data-row-id] input[type="checkbox"]');
525
+
526
+ rowCheckboxes.forEach(checkbox => {
527
+ checkbox.addEventListener('change', () => {
528
+ // Use setTimeout to ensure this runs after the main checkbox logic
529
+ setTimeout(() => {
530
+ this.updateActionBarVisibility(table, actionBar);
531
+ }, 0);
532
+ });
533
+ });
534
+
535
+ // Special handling for select-all checkbox (only if it exists)
536
+ const selectAllCheckbox = table.querySelector("#select-all-rows");
537
+ if (selectAllCheckbox) {
538
+ const selectAllInput = selectAllCheckbox.querySelector('input[type="checkbox"]');
539
+ if (selectAllInput) {
540
+ selectAllInput.addEventListener('change', () => {
541
+ // Use setTimeout to ensure this runs after the main select-all logic
542
+ setTimeout(() => {
543
+ this.updateActionBarVisibility(table, actionBar);
544
+ }, 10); // Slightly longer delay for select-all to ensure all row checkboxes are updated
545
+ });
546
+ }
547
+ }
548
+ }
549
+
550
+ updateActionBarVisibility(table, actionBar) {
551
+ // Only count row checkboxes (those with data-row-id), not all checkboxes
552
+ const rowCheckboxes = table.querySelectorAll('label[data-row-id] input[type="checkbox"]');
553
+
554
+ // Get all checked row checkboxes
555
+ const selectedRowCheckboxes = Array.from(rowCheckboxes).filter(cb => cb.checked);
556
+
557
+ // Get the selected count
558
+ const selectedCount = selectedRowCheckboxes.length;
559
+
560
+ if (selectedCount > 0) {
561
+ this.showActionBar(actionBar, selectedCount);
562
+ } else {
563
+ this.hideActionBar(actionBar);
564
+ }
565
+ }
566
+
567
+ showActionBar(actionBar, selectedCount) {
568
+ // Show action bar directly
569
+ actionBar.style.height = 'auto';
570
+ actionBar.style.overflow = 'visible';
571
+ actionBar.style.opacity = '1';
572
+ actionBar.classList.remove("p_none");
573
+ actionBar.classList.add("p_xs", "is-visible", "show-action-card");
574
+
575
+ // Update the count
576
+ const countElement = actionBar.querySelector(".selected-count");
577
+ if (countElement) {
578
+ countElement.textContent = `${selectedCount} Selected`;
579
+ }
580
+ }
581
+
582
+ hideActionBar(actionBar) {
583
+ // Hide action bar directly
584
+ actionBar.style.height = '0px';
585
+ actionBar.style.overflow = 'hidden';
586
+ actionBar.style.opacity = '0';
587
+ actionBar.classList.add("p_none");
588
+ actionBar.classList.remove("p_xs", "is-visible", "show-action-card");
589
+ }
590
+ }
591
+
592
+ // Initialize the isolated action bar functionality
593
+ new PbAdvancedTableActionBar();
594
+
452
595
  window.expandAllRows = (element) => {
453
596
  PbAdvancedTable.handleToggleAllHeaders(element);
454
597
  };
@@ -77,6 +77,7 @@
77
77
  top: 0;
78
78
  left: 0;
79
79
  border-radius: unset;
80
+ z-index: 5;
80
81
  }
81
82
 
82
83
  .checkbox-cell {
@@ -0,0 +1,23 @@
1
+ <%= pb_rails("card", props: {
2
+ border_none: object.is_visible,
3
+ classname: object.classname,
4
+ padding: object.is_visible ? "xs" : "none",
5
+ data: {
6
+ action_bar: true
7
+ }
8
+ }) do %>
9
+ <%= pb_rails("flex", props: { align_items: "center", justify: "between" }) do %>
10
+ <%= pb_rails("caption", props: { color: "light", padding_left: "xs", size: "xs" }) do %>
11
+ <span class="selected-count"><%= object.selected_count %> Selected</span>
12
+ <% end %>
13
+ <%= pb_rails("flex/flex_item") do %>
14
+ <%= pb_rails("flex") do %>
15
+ <% if object.actions.present? %>
16
+ <% object.actions.each do |action| %>
17
+ <%= action %>
18
+ <% end %>
19
+ <% end %>
20
+ <% end %>
21
+ <% end %>
22
+ <% end %>
23
+ <% end %>
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Playbook
4
+ module PbAdvancedTable
5
+ class TableActionBar < Playbook::KitBase
6
+ prop :actions, type: Playbook::Props::Array,
7
+ default: []
8
+ prop :is_visible, type: Playbook::Props::Boolean,
9
+ default: false
10
+ prop :selected_count, type: Playbook::Props::Number,
11
+ default: 0
12
+
13
+ def classname
14
+ # Just use row-selection-actions-card as the base class
15
+ generate_classname("row-selection-actions-card", separator: " ")
16
+ end
17
+ end
18
+ end
19
+ end
@@ -15,6 +15,8 @@ module Playbook
15
15
  default: "scroll"
16
16
  prop :selectable_rows, type: Playbook::Props::Boolean,
17
17
  default: false
18
+ prop :show_actions_bar, type: Playbook::Props::Boolean,
19
+ default: true
18
20
 
19
21
  def classname
20
22
  additional_classes = []
@@ -27,6 +29,7 @@ module Playbook
27
29
  def th_classname(is_first_column: false)
28
30
  additional_classes = []
29
31
  additional_classes << "pinned-left" if is_first_column && responsive == "scroll" && !selectable_rows
32
+ additional_classes << "header-cells-with-actions" if selectable_rows && show_actions_bar
30
33
 
31
34
  generate_classname("table-header-cells", *additional_classes, separator: " ")
32
35
  end
@@ -46,6 +49,7 @@ module Playbook
46
49
  if selectable_rows
47
50
  additional_classes = []
48
51
  additional_classes << "table-header-cells-custom"
52
+ additional_classes << "header-cells-with-actions" if show_actions_bar
49
53
  additional_classes << "checkbox-cell-header"
50
54
  additional_classes << "pinned-left" if responsive == "scroll"
51
55
  pb_rails("table/table_header", props: {
@@ -15,7 +15,7 @@
15
15
  @include pb_body;
16
16
  border: 1px solid $border_light;
17
17
  background-color: $white;
18
- height: 45px;
18
+ min-height: 45px;
19
19
  @media (hover: hover) {
20
20
  &:hover,
21
21
  &:active,
@@ -1,4 +1,4 @@
1
- import React, { useState, useRef, useEffect, forwardRef, useImperativeHandle } from "react";
1
+ import React, { useState, useRef, useEffect, forwardRef, useImperativeHandle, useMemo } from "react";
2
2
  import classnames from "classnames";
3
3
  import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from "../utilities/props";
4
4
  import { globalProps } from "../utilities/globalProps";
@@ -25,6 +25,7 @@ type DropdownProps = {
25
25
  blankSelection?: string;
26
26
  children?: React.ReactChild[] | React.ReactChild | React.ReactElement[];
27
27
  className?: string;
28
+ formPillProps?: GenericObject;
28
29
  dark?: boolean;
29
30
  data?: { [key: string]: string };
30
31
  defaultValue?: GenericObject;
@@ -33,6 +34,7 @@ type DropdownProps = {
33
34
  id?: string;
34
35
  isClosed?: boolean;
35
36
  label?: string;
37
+ multiSelect?: boolean;
36
38
  onSelect?: (arg: GenericObject) => null;
37
39
  options: GenericObject;
38
40
  separators?: boolean;
@@ -61,6 +63,8 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
61
63
  id,
62
64
  isClosed = true,
63
65
  label,
66
+ multiSelect = false,
67
+ formPillProps,
64
68
  onSelect,
65
69
  options,
66
70
  separators = true,
@@ -80,7 +84,20 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
80
84
  const [isDropDownClosed, setIsDropDownClosed, toggleDropdown] = useDropdown(isClosed);
81
85
 
82
86
  const [filterItem, setFilterItem] = useState("");
83
- const [selected, setSelected] = useState<GenericObject>(defaultValue);
87
+ const initialSelected = useMemo(() => {
88
+ if (multiSelect) {
89
+ if (Array.isArray(defaultValue)) return defaultValue;
90
+ return defaultValue && Object.keys(defaultValue).length
91
+ ? [defaultValue]
92
+ : [];
93
+ }
94
+ return defaultValue || {};
95
+ }, [multiSelect, defaultValue]);
96
+
97
+ const [selected, setSelected] = useState<GenericObject | GenericObject[]>(
98
+ initialSelected
99
+ );
100
+
84
101
  const [isInputFocused, setIsInputFocused] = useState(false);
85
102
  const [hasTriggerSubcomponent, setHasTriggerSubcomponent] = useState(true);
86
103
  const [hasContainerSubcomponent, setHasContainerSubcomponent] =
@@ -93,6 +110,12 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
93
110
  const inputWrapperRef = useRef(null);
94
111
  const dropdownContainerRef = useRef(null);
95
112
 
113
+ const selectedArray = Array.isArray(selected)
114
+ ? selected
115
+ : selected && Object.keys(selected).length
116
+ ? [selected]
117
+ : [];
118
+
96
119
  const { trigger, container, otherChildren } =
97
120
  separateChildComponents(children);
98
121
 
@@ -124,16 +147,23 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
124
147
 
125
148
  const blankSelectionOption: GenericObject = blankSelection ? [{ label: blankSelection, value: "" }] : [];
126
149
  const optionsWithBlankSelection = blankSelectionOption.concat(options);
127
- const filteredOptions = optionsWithBlankSelection?.filter((option: GenericObject) => {
128
- const label = typeof option.label === 'string' ? option.label.toLowerCase() : option.label;
129
- return String(label).toLowerCase().includes(filterItem.toLowerCase());
130
- });
150
+
151
+ const availableOptions = useMemo(()=> {
152
+ if (!multiSelect) return optionsWithBlankSelection;
153
+ return optionsWithBlankSelection.filter((option: GenericObject) => !selectedArray.some((sel) => sel.label === option.label));
154
+ }, [optionsWithBlankSelection, selectedArray, multiSelect]);
155
+
156
+ const filteredOptions = useMemo(() => {
157
+ return availableOptions.filter((opt: GenericObject) =>
158
+ String(opt.label).toLowerCase().includes(filterItem.toLowerCase())
159
+ );
160
+ }, [availableOptions, filterItem]);
131
161
 
132
162
  // For keyboard accessibility: Set focus within dropdown to selected item if it exists
133
163
  useEffect(() => {
134
164
  if (!isDropDownClosed) {
135
165
  let newIndex = 0;
136
- if (selected && selected?.label) {
166
+ if (selected && !Array.isArray(selected) && selected.label) {
137
167
  const selectedIndex = filteredOptions.findIndex((option: GenericObject) => option.label === selected.label);
138
168
  if (selectedIndex >= 0) {
139
169
  newIndex = selectedIndex;
@@ -149,12 +179,27 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
149
179
  setIsDropDownClosed(false);
150
180
  };
151
181
 
152
- const handleOptionClick = (selectedItem: GenericObject) => {
153
- setSelected(selectedItem);
154
- setFilterItem("");
155
- setIsDropDownClosed(true);
156
- onSelect && onSelect(selectedItem);
157
- };
182
+
183
+ const handleOptionClick = (clickedItem: GenericObject) => {
184
+ if (multiSelect) {
185
+ setSelected((prev) => {
186
+ const list = prev as GenericObject[];
187
+ const exists = list.find((option) => option.value === clickedItem.value);
188
+ const next = exists
189
+ ? list.filter((option) => option.value !== clickedItem.value)
190
+ : [...list, clickedItem];
191
+ onSelect && onSelect(next);
192
+ return next;
193
+ });
194
+ setFilterItem("");
195
+ setIsDropDownClosed(true);
196
+ } else {
197
+ setSelected(clickedItem);
198
+ setFilterItem("");
199
+ setIsDropDownClosed(true);
200
+ onSelect && onSelect(clickedItem);
201
+ }
202
+ };
158
203
 
159
204
  const handleWrapperClick = () => {
160
205
  autocomplete && inputRef?.current?.focus();
@@ -162,9 +207,14 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
162
207
  };
163
208
 
164
209
  const handleBackspace = () => {
210
+ if (multiSelect) {
211
+ setSelected([]);
212
+ onSelect && onSelect([]);
213
+ } else {
165
214
  setSelected({});
166
215
  onSelect && onSelect(null);
167
216
  setFocusedOptionIndex(-1);
217
+ }
168
218
  };
169
219
 
170
220
  const componentsToRender = prepareSubcomponents({
@@ -178,12 +228,17 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
178
228
  });
179
229
 
180
230
  useImperativeHandle(ref, () => ({
181
- clearSelected: () => {
182
- setSelected({});
183
- setFilterItem("");
184
- setIsDropDownClosed(true);
185
- onSelect && onSelect(null);
186
- },
231
+ clearSelected: () => {
232
+ if (multiSelect) {
233
+ setSelected([]);
234
+ onSelect && onSelect([]);
235
+ } else {
236
+ setSelected({});
237
+ onSelect && onSelect(null);
238
+ }
239
+ setFilterItem("");
240
+ setIsDropDownClosed(true);
241
+ },
187
242
  }));
188
243
 
189
244
  return (
@@ -201,6 +256,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
201
256
  filteredOptions,
202
257
  filterItem,
203
258
  focusedOptionIndex,
259
+ formPillProps,
204
260
  handleBackspace,
205
261
  handleChange,
206
262
  handleOptionClick,
@@ -209,6 +265,8 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
209
265
  inputWrapperRef,
210
266
  isDropDownClosed,
211
267
  isInputFocused,
268
+ multiSelect,
269
+ onSelect,
212
270
  optionsWithBlankSelection,
213
271
  selected,
214
272
  setFocusedOptionIndex,
@@ -0,0 +1,31 @@
1
+ <%
2
+ options = [
3
+ { label: 'United States', value: 'United States', id: 'us' },
4
+ { label: 'Canada', value: 'Canada', id: 'ca' },
5
+ { label: 'Pakistan', value: 'Pakistan', id: 'pk' },
6
+ ]
7
+
8
+ %>
9
+
10
+ <%
11
+ options2 = [
12
+ { label: 'India', value: 'India', id: 'in' },
13
+ { label: 'Mexico', value: 'Mexico', id: 'mx' },
14
+ { label: 'Brazil', value: 'Brazil', id: 'br' },
15
+ { label: 'Argentina', value: 'Argentina', id: 'ar' },
16
+ { label: 'Colombia', value: 'Colombia', id: 'co' },
17
+ { label: 'Chile', value: 'Chile', id: 'cl' },
18
+ { label: 'Peru', value: 'Peru', id: 'pe' },
19
+ ]
20
+
21
+ %>
22
+
23
+ <%= pb_rails("dropdown", props: {options: options}) %>
24
+
25
+ <script>
26
+ document.addEventListener("pb:dropdown:selected", (e) => {
27
+ const option = e.detail;
28
+ const dropdown = e.target;
29
+ console.log("Selected option:", option);
30
+ })
31
+ </script>
@@ -0,0 +1,5 @@
1
+ This kit's `options` prop requires an array of objects, each of which will be used as the selectable options within the dropdown. Each option object can support any number of key-value pairs, but each MUST contain `label`, `value` and `id`.
2
+
3
+ The kit also comes with a custom event called "pb:dropdown:selected" which updates dynamically with the selection as it changes. See code snippet to see this in action.
4
+
5
+ In addition, a data attribute called `data-option-selected` with the selection is also rendered on the parent dropdown div.
@@ -0,0 +1,56 @@
1
+ import React from 'react'
2
+ import Dropdown from '../../pb_dropdown/_dropdown'
3
+
4
+ const DropdownMultiSelect = (props) => {
5
+
6
+ const options = [
7
+ {
8
+ label: "United States",
9
+ value: "United States",
10
+ },
11
+ {
12
+ label: "United Kingdom",
13
+ value: "United Kingdom",
14
+ },
15
+ {
16
+ label: "Canada",
17
+ value: "Canada",
18
+ },
19
+ {
20
+ label: "Pakistan",
21
+ value: "Pakistan",
22
+ },
23
+ {
24
+ label: "India",
25
+ value: "India",
26
+ },
27
+ {
28
+ label: "Australia",
29
+ value: "Australia",
30
+ },
31
+ {
32
+ label: "New Zealand",
33
+ value: "New Zealand",
34
+ },
35
+ {
36
+ label: "Italy",
37
+ value: "Italy",
38
+ },
39
+ {
40
+ label: "Spain",
41
+ value: "Spain",
42
+ }
43
+ ];
44
+
45
+ return (
46
+ <div>
47
+ <Dropdown
48
+ multiSelect
49
+ options={options}
50
+ {...props}
51
+ />
52
+ </div>
53
+ )
54
+ }
55
+
56
+ export default DropdownMultiSelect
@@ -0,0 +1,3 @@
1
+ `multiSelect` is a boolean prop that if set to true will allow for multiple options to be selected from the Dropdown.
2
+
3
+ `multiSelect` is set to false by default.
@@ -0,0 +1,58 @@
1
+ import React from 'react'
2
+ import Dropdown from '../../pb_dropdown/_dropdown'
3
+
4
+ const DropdownMultiSelectDisplay = (props) => {
5
+
6
+ const options = [
7
+ {
8
+ label: "United States",
9
+ value: "United States",
10
+ },
11
+ {
12
+ label: "United Kingdom",
13
+ value: "United Kingdom",
14
+ },
15
+ {
16
+ label: "Canada",
17
+ value: "Canada",
18
+ },
19
+ {
20
+ label: "Pakistan",
21
+ value: "Pakistan",
22
+ },
23
+ {
24
+ label: "India",
25
+ value: "India",
26
+ },
27
+ {
28
+ label: "Australia",
29
+ value: "Australia",
30
+ },
31
+ {
32
+ label: "New Zealand",
33
+ value: "New Zealand",
34
+ },
35
+ {
36
+ label: "Italy",
37
+ value: "Italy",
38
+ },
39
+ {
40
+ label: "Spain",
41
+ value: "Spain",
42
+ }
43
+ ];
44
+
45
+
46
+ return (
47
+ <div>
48
+ <Dropdown
49
+ formPillProps={{size:"small", color:"neutral"}}
50
+ multiSelect
51
+ options={options}
52
+ {...props}
53
+ />
54
+ </div>
55
+ )
56
+ }
57
+
58
+ export default DropdownMultiSelectDisplay
@@ -0,0 +1,3 @@
1
+ By default, the `multiSelect` prop will render selected options as the default FormPill. `FormPillProps` however can be used to customize these Pills with any props that exist for the FormPill.
2
+
3
+ This prop must be an object that contains valid FormPill props. For a full list of FormPill props, see [here](https://playbook.powerapp.cloud/kits/form_pill/react).
@@ -0,0 +1,20 @@
1
+ <%
2
+ options = [
3
+ { label: 'United States', value: 'United States', id: 'us' },
4
+ { label: 'Canada', value: 'Canada', id: 'ca' },
5
+ { label: 'Pakistan', value: 'Pakistan', id: 'pk' },
6
+ { label: 'India', value: 'India', id: 'in' },
7
+ { label: 'United Kingdom', value: 'United Kingdom', id: 'uk' },
8
+ { label: 'Australia', value: 'Australia', id: 'au' },
9
+ { label: 'New Zealand', value: 'New Zealand', id: 'nz' },
10
+ { label: 'Germany', value: 'Germany', id: 'de' },
11
+ { label: 'France', value: 'France', id: 'fr' },
12
+ { label: 'Italy', value: 'Italy', id: 'it' },
13
+ ]
14
+ %>
15
+
16
+ <%= pb_rails("dropdown", props: {
17
+ options: options,
18
+ multi_select: true,
19
+ form_pill_props: { size:"small", color:"neutral" },
20
+ }) %>