playbook_ui 18.2.0 → 18.3.0.pre.alpha.PLAY3342advancedtablereactmulticolumnselectablerow21082

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 (466) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/Components/CollapsibleTrail.tsx +8 -1
  3. data/app/pb_kits/playbook/pb_advanced_table/Components/RegularTableView.tsx +39 -8
  4. data/app/pb_kits/playbook/pb_advanced_table/Components/TableActionBar.tsx +62 -2
  5. data/app/pb_kits/playbook/pb_advanced_table/Components/VirtualizedTableView.tsx +24 -4
  6. data/app/pb_kits/playbook/pb_advanced_table/SubKits/TableHeader.tsx +105 -66
  7. data/app/pb_kits/playbook/pb_advanced_table/Utilities/RowUtils.ts +29 -0
  8. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +83 -5
  9. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +3 -0
  10. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.html.erb +1 -1
  11. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.rb +8 -0
  12. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +1100 -1
  13. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility.jsx +1 -1
  14. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility.md +5 -2
  15. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility_custom.md +3 -1
  16. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility_multi_select_all.jsx +82 -0
  17. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility_multi_select_all.md +3 -0
  18. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility_with_state.md +3 -1
  19. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_group_separators.jsx +67 -0
  20. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_group_separators.md +7 -0
  21. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_group_separators_rails.html.erb +78 -0
  22. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_group_separators_rails.md +3 -0
  23. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_selectable_rows.jsx +177 -58
  24. data/app/pb_kits/playbook/pb_advanced_table/docs/_playground.json +17 -2
  25. data/app/pb_kits/playbook/pb_advanced_table/docs/_playground.overrides.json +8 -1
  26. data/app/pb_kits/playbook/pb_advanced_table/docs/advanced_table_mock_data_group_separators.json +328 -0
  27. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +3 -0
  28. data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +2 -0
  29. data/app/pb_kits/playbook/pb_advanced_table/index.js +39 -2
  30. data/app/pb_kits/playbook/pb_advanced_table/kit.schema.json +44 -16
  31. data/app/pb_kits/playbook/pb_advanced_table/table_body.html.erb +2 -2
  32. data/app/pb_kits/playbook/pb_advanced_table/table_body.rb +53 -23
  33. data/app/pb_kits/playbook/pb_advanced_table/table_header.rb +1 -1
  34. data/app/pb_kits/playbook/pb_advanced_table/table_row.html.erb +1 -1
  35. data/app/pb_kits/playbook/pb_advanced_table/table_row.rb +2 -0
  36. data/app/pb_kits/playbook/pb_avatar/_avatar.tsx +3 -1
  37. data/app/pb_kits/playbook/pb_avatar/kit.schema.json +23 -10
  38. data/app/pb_kits/playbook/pb_background/docs/_playground.json +1 -0
  39. data/app/pb_kits/playbook/pb_background/kit.schema.json +10 -3
  40. data/app/pb_kits/playbook/pb_badge/docs/_playground.json +2 -0
  41. data/app/pb_kits/playbook/pb_badge/kit.schema.json +27 -13
  42. data/app/pb_kits/playbook/pb_body/docs/_playground.json +3 -3
  43. data/app/pb_kits/playbook/pb_body/kit.schema.json +14 -5
  44. data/app/pb_kits/playbook/pb_button/_button.tsx +3 -1
  45. data/app/pb_kits/playbook/pb_button/button.rb +3 -0
  46. data/app/pb_kits/playbook/pb_button/button.test.js +154 -0
  47. data/app/pb_kits/playbook/pb_button/docs/_button_options_rails.md +5 -0
  48. data/app/pb_kits/playbook/pb_button/docs/_button_options_react.md +9 -0
  49. data/app/pb_kits/playbook/pb_button/docs/_playground.json +4 -2
  50. data/app/pb_kits/playbook/pb_button/docs/_playground.overrides.json +1 -1
  51. data/app/pb_kits/playbook/pb_button/docs/example.yml +1 -1
  52. data/app/pb_kits/playbook/pb_button/kit.schema.json +31 -27
  53. data/app/pb_kits/playbook/pb_caption/kit.schema.json +17 -7
  54. data/app/pb_kits/playbook/pb_card/docs/_playground.json +1 -0
  55. data/app/pb_kits/playbook/pb_card/kit.schema.json +2 -1
  56. data/app/pb_kits/playbook/pb_checkbox/docs/_playground.json +6 -1
  57. data/app/pb_kits/playbook/pb_checkbox/kit.schema.json +10 -5
  58. data/app/pb_kits/playbook/pb_collapsible/child_kits/CollapsibleMain.tsx +4 -4
  59. data/app/pb_kits/playbook/pb_collapsible/collapsible.test.js +81 -2
  60. data/app/pb_kits/playbook/pb_contact/_contact.tsx +4 -1
  61. data/app/pb_kits/playbook/pb_contact/docs/_playground.json +1 -0
  62. data/app/pb_kits/playbook/pb_contact/kit.schema.json +3 -1
  63. data/app/pb_kits/playbook/pb_copy_button/docs/_playground.json +5 -1
  64. data/app/pb_kits/playbook/pb_copy_button/kit.schema.json +10 -5
  65. data/app/pb_kits/playbook/pb_currency/docs/_playground.json +1 -1
  66. data/app/pb_kits/playbook/pb_currency/kit.schema.json +6 -2
  67. data/app/pb_kits/playbook/pb_dashboard_value/docs/_playground.json +2 -2
  68. data/app/pb_kits/playbook/pb_dashboard_value/kit.schema.json +4 -2
  69. data/app/pb_kits/playbook/pb_date/_date.tsx +3 -1
  70. data/app/pb_kits/playbook/pb_date/date.test.js +1 -1
  71. data/app/pb_kits/playbook/pb_date/docs/_playground.json +1 -0
  72. data/app/pb_kits/playbook/pb_date/kit.schema.json +17 -7
  73. data/app/pb_kits/playbook/pb_date_picker/date_picker.test.js +4 -0
  74. data/app/pb_kits/playbook/pb_date_picker/docs/_playground.json +4 -1
  75. data/app/pb_kits/playbook/pb_date_picker/kit.schema.json +32 -14
  76. data/app/pb_kits/playbook/pb_date_range_inline/_date_range_inline.tsx +3 -1
  77. data/app/pb_kits/playbook/pb_date_range_inline/docs/_playground.json +1 -0
  78. data/app/pb_kits/playbook/pb_date_range_inline/kit.schema.json +3 -1
  79. data/app/pb_kits/playbook/pb_date_range_stacked/_date_range_stacked.tsx +3 -1
  80. data/app/pb_kits/playbook/pb_date_range_stacked/kit.schema.json +1 -0
  81. data/app/pb_kits/playbook/pb_date_stacked/_date_stacked.tsx +4 -1
  82. data/app/pb_kits/playbook/pb_date_stacked/date_stacked.test.js +1 -0
  83. data/app/pb_kits/playbook/pb_date_stacked/kit.schema.json +1 -0
  84. data/app/pb_kits/playbook/pb_date_time/_date_time.tsx +3 -1
  85. data/app/pb_kits/playbook/pb_date_time/dateTime.test.js +1 -0
  86. data/app/pb_kits/playbook/pb_date_time/docs/_playground.json +2 -1
  87. data/app/pb_kits/playbook/pb_date_time/kit.schema.json +3 -1
  88. data/app/pb_kits/playbook/pb_date_time_stacked/_date_time_stacked.tsx +2 -1
  89. data/app/pb_kits/playbook/pb_date_time_stacked/docs/_playground.json +1 -0
  90. data/app/pb_kits/playbook/pb_date_time_stacked/kit.schema.json +3 -1
  91. data/app/pb_kits/playbook/pb_date_year_stacked/_date_year_stacked.tsx +3 -1
  92. data/app/pb_kits/playbook/pb_date_year_stacked/date_year_stacked.test.js +1 -0
  93. data/app/pb_kits/playbook/pb_date_year_stacked/kit.schema.json +1 -0
  94. data/app/pb_kits/playbook/pb_detail/docs/_playground.json +2 -1
  95. data/app/pb_kits/playbook/pb_detail/kit.schema.json +2 -1
  96. data/app/pb_kits/playbook/pb_dialog/docs/_playground.json +1 -1
  97. data/app/pb_kits/playbook/pb_dialog/kit.schema.json +22 -9
  98. data/app/pb_kits/playbook/pb_distribution_bar/docs/_playground.json +2 -2
  99. data/app/pb_kits/playbook/pb_distribution_bar/kit.schema.json +2 -1
  100. data/app/pb_kits/playbook/pb_draggable/utilities/touchDrag.ts +10 -0
  101. data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +112 -0
  102. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +121 -15
  103. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_draggable_pills.html.erb +51 -0
  104. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_draggable_pills.jsx +62 -0
  105. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_draggable_pills_rails.md +9 -0
  106. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_draggable_pills_react.md +9 -0
  107. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_form.jsx +50 -0
  108. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_form_react.md +9 -0
  109. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_option_creation.html.erb +16 -0
  110. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_option_creation.jsx +27 -0
  111. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_option_creation_rails.md +9 -0
  112. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_option_creation_react.md +9 -0
  113. data/app/pb_kits/playbook/pb_dropdown/docs/_playground.json +39 -3
  114. data/app/pb_kits/playbook/pb_dropdown/docs/_playground.overrides.json +13 -2
  115. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +5 -0
  116. data/app/pb_kits/playbook/pb_dropdown/docs/index.js +4 -1
  117. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +2 -2
  118. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +24 -1
  119. data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +235 -1
  120. data/app/pb_kits/playbook/pb_dropdown/dropdown_creation.test.jsx +414 -0
  121. data/app/pb_kits/playbook/pb_dropdown/dropdown_form.test.jsx +173 -0
  122. data/app/pb_kits/playbook/pb_dropdown/dropdown_index.test.js +545 -0
  123. data/app/pb_kits/playbook/pb_dropdown/hooks/useHandleOnKeydown.tsx +22 -5
  124. data/app/pb_kits/playbook/pb_dropdown/index.js +260 -37
  125. data/app/pb_kits/playbook/pb_dropdown/keyboard_accessibility.js +3 -1
  126. data/app/pb_kits/playbook/pb_dropdown/kit.schema.json +59 -9
  127. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownContainer.tsx +43 -25
  128. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownFormFields.tsx +83 -0
  129. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownOption.tsx +13 -5
  130. data/app/pb_kits/playbook/pb_dropdown/subcomponents/MultiSelectTriggerDisplay.tsx +336 -31
  131. data/app/pb_kits/playbook/pb_dropdown/utilities/optionCreation.ts +21 -0
  132. data/app/pb_kits/playbook/pb_dropdown/utilities/pillReorder.js +281 -0
  133. data/app/pb_kits/playbook/pb_dropdown/utilities/quickpickDateSync.test.ts +24 -0
  134. data/app/pb_kits/playbook/pb_dropdown/utilities/usePillDropIndicator.ts +144 -0
  135. data/app/pb_kits/playbook/pb_empty_state/docs/_playground.json +1 -1
  136. data/app/pb_kits/playbook/pb_empty_state/kit.schema.json +2 -1
  137. data/app/pb_kits/playbook/pb_file_upload/docs/_playground.json +3 -2
  138. data/app/pb_kits/playbook/pb_file_upload/kit.schema.json +8 -3
  139. data/app/pb_kits/playbook/pb_filter/Filter/SortMenu.tsx +8 -1
  140. data/app/pb_kits/playbook/pb_filter/docs/_filter_one_click.html.erb +38 -0
  141. data/app/pb_kits/playbook/pb_filter/docs/_filter_one_click.jsx +69 -0
  142. data/app/pb_kits/playbook/pb_filter/docs/_filter_one_click.md +1 -0
  143. data/app/pb_kits/playbook/pb_filter/docs/example.yml +2 -0
  144. data/app/pb_kits/playbook/pb_filter/docs/index.js +2 -1
  145. data/app/pb_kits/playbook/pb_filter/filter.html.erb +4 -4
  146. data/app/pb_kits/playbook/pb_filter/filter.rb +28 -0
  147. data/app/pb_kits/playbook/pb_filter/filter.test.js +41 -0
  148. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_playground.json +4 -4
  149. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/fixed_confirmation_toast.test.js +1 -2
  150. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/kit.schema.json +6 -3
  151. data/app/pb_kits/playbook/pb_flex/docs/_playground.json +3 -0
  152. data/app/pb_kits/playbook/pb_flex/kit.schema.json +27 -12
  153. data/app/pb_kits/playbook/pb_form/docs/_description_react.md +3 -0
  154. data/app/pb_kits/playbook/pb_form/docs/_form_default_values.jsx +898 -0
  155. data/app/pb_kits/playbook/pb_form/docs/_form_default_values_react.md +3 -0
  156. data/app/pb_kits/playbook/pb_form/docs/_form_defaults.jsx +879 -0
  157. data/app/pb_kits/playbook/pb_form/docs/_form_defaults_react.md +29 -0
  158. data/app/pb_kits/playbook/pb_form/docs/_form_loading.jsx +75 -0
  159. data/app/pb_kits/playbook/pb_form/docs/{_form_loading.md → _form_loading_rails.md} +1 -0
  160. data/app/pb_kits/playbook/pb_form/docs/_form_loading_react.md +1 -0
  161. data/app/pb_kits/playbook/pb_form/docs/_form_model_values.jsx +147 -0
  162. data/app/pb_kits/playbook/pb_form/docs/_form_model_values_react.md +16 -0
  163. data/app/pb_kits/playbook/pb_form/docs/_form_multi_column.jsx +164 -0
  164. data/app/pb_kits/playbook/pb_form/docs/_form_multi_column_react.md +3 -0
  165. data/app/pb_kits/playbook/pb_form/docs/_form_validation.jsx +1057 -0
  166. data/app/pb_kits/playbook/pb_form/docs/_form_validation_react.md +26 -0
  167. data/app/pb_kits/playbook/pb_form/docs/example.yml +7 -0
  168. data/app/pb_kits/playbook/pb_form/form.rb +1 -1
  169. data/app/pb_kits/playbook/pb_form/kit.schema.json +1 -6
  170. data/app/pb_kits/playbook/pb_form/pb_form_validation.js +1 -0
  171. data/app/pb_kits/playbook/pb_form_group/_form_group.scss +1 -4
  172. data/app/pb_kits/playbook/pb_form_pill/docs/_playground.json +3 -0
  173. data/app/pb_kits/playbook/pb_form_pill/kit.schema.json +6 -3
  174. data/app/pb_kits/playbook/pb_full_screen/_full_screen.scss +6 -0
  175. data/app/pb_kits/playbook/pb_full_screen/_full_screen.tsx +11 -2
  176. data/app/pb_kits/playbook/pb_full_screen/docs/_full_screen_advanced_table_sticky_header.jsx +73 -0
  177. data/app/pb_kits/playbook/pb_full_screen/docs/_full_screen_advanced_table_sticky_header.md +1 -0
  178. data/app/pb_kits/playbook/pb_full_screen/docs/_playground.json +3 -3
  179. data/app/pb_kits/playbook/pb_full_screen/docs/example.yml +1 -0
  180. data/app/pb_kits/playbook/pb_full_screen/docs/index.js +1 -0
  181. data/app/pb_kits/playbook/pb_full_screen/full_screen.test.jsx +4 -2
  182. data/app/pb_kits/playbook/pb_full_screen/kit.schema.json +8 -4
  183. data/app/pb_kits/playbook/pb_hashtag/docs/_playground.json +2 -2
  184. data/app/pb_kits/playbook/pb_hashtag/kit.schema.json +2 -1
  185. data/app/pb_kits/playbook/pb_highlight/kit.schema.json +2 -1
  186. data/app/pb_kits/playbook/pb_icon/_icon.tsx +6 -1
  187. data/app/pb_kits/playbook/pb_icon/docs/_playground.json +2 -2
  188. data/app/pb_kits/playbook/pb_icon/kit.schema.json +31 -13
  189. data/app/pb_kits/playbook/pb_icon_circle/_icon_circle.tsx +3 -1
  190. data/app/pb_kits/playbook/pb_icon_circle/docs/_playground.json +2 -2
  191. data/app/pb_kits/playbook/pb_icon_circle/kit.schema.json +3 -1
  192. data/app/pb_kits/playbook/pb_icon_stat_value/docs/_playground.json +3 -2
  193. data/app/pb_kits/playbook/pb_icon_stat_value/kit.schema.json +4 -2
  194. data/app/pb_kits/playbook/pb_image/_image.tsx +5 -3
  195. data/app/pb_kits/playbook/pb_image/docs/_playground.json +27 -27
  196. data/app/pb_kits/playbook/pb_image/kit.schema.json +57 -24
  197. data/app/pb_kits/playbook/pb_label_pill/_label_pill.tsx +3 -1
  198. data/app/pb_kits/playbook/pb_label_pill/kit.schema.json +1 -0
  199. data/app/pb_kits/playbook/pb_label_value/docs/_playground.json +2 -2
  200. data/app/pb_kits/playbook/pb_label_value/kit.schema.json +2 -1
  201. data/app/pb_kits/playbook/pb_layout/docs/_playground.json +2 -2
  202. data/app/pb_kits/playbook/pb_layout/kit.schema.json +43 -20
  203. data/app/pb_kits/playbook/pb_legend/_legend.tsx +3 -1
  204. data/app/pb_kits/playbook/pb_legend/docs/_playground.json +2 -2
  205. data/app/pb_kits/playbook/pb_legend/kit.schema.json +3 -1
  206. data/app/pb_kits/playbook/pb_lightbox/_lightbox.tsx +2 -2
  207. data/app/pb_kits/playbook/pb_lightbox/kit.schema.json +84 -0
  208. data/app/pb_kits/playbook/pb_link/docs/_playground.json +6 -3
  209. data/app/pb_kits/playbook/pb_link/kit.schema.json +14 -6
  210. data/app/pb_kits/playbook/pb_list/_list_item.tsx +6 -3
  211. data/app/pb_kits/playbook/pb_list/docs/_playground.json +2 -2
  212. data/app/pb_kits/playbook/pb_list/kit.schema.json +2 -1
  213. data/app/pb_kits/playbook/pb_loading_inline/docs/_playground.json +0 -1
  214. data/app/pb_kits/playbook/pb_loading_inline/kit.schema.json +4 -1
  215. data/app/pb_kits/playbook/pb_map/docs/_map_default.md +22 -3
  216. data/app/pb_kits/playbook/pb_map/kit.schema.json +4 -6
  217. data/app/pb_kits/playbook/pb_message/kit.schema.json +2 -1
  218. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +4 -1
  219. data/app/pb_kits/playbook/pb_multiple_users/docs/_playground.json +2 -1
  220. data/app/pb_kits/playbook/pb_multiple_users/kit.schema.json +19 -11
  221. data/app/pb_kits/playbook/pb_multiple_users_stacked/docs/_playground.json +1 -0
  222. data/app/pb_kits/playbook/pb_multiple_users_stacked/kit.schema.json +2 -1
  223. data/app/pb_kits/playbook/pb_nav/_nav_item.test.js +17 -0
  224. data/app/pb_kits/playbook/pb_nav/docs/_playground.json +2 -0
  225. data/app/pb_kits/playbook/pb_nav/kit.schema.json +4 -2
  226. data/app/pb_kits/playbook/pb_online_status/_online_status.tsx +3 -1
  227. data/app/pb_kits/playbook/pb_online_status/kit.schema.json +1 -0
  228. data/app/pb_kits/playbook/pb_overlay/docs/_playground.json +3 -3
  229. data/app/pb_kits/playbook/pb_overlay/kit.schema.json +22 -12
  230. data/app/pb_kits/playbook/pb_pagination/kit.schema.json +6 -3
  231. data/app/pb_kits/playbook/pb_passphrase/docs/_playground.json +6 -4
  232. data/app/pb_kits/playbook/pb_passphrase/kit.schema.json +10 -5
  233. data/app/pb_kits/playbook/pb_pb_bar_graph/pbbargraph.test.jsx +1 -2
  234. data/app/pb_kits/playbook/pb_pb_circle_chart/pb_circle_chart.test.jsx +1 -2
  235. data/app/pb_kits/playbook/pb_pb_gauge_chart/kit.schema.json +2 -1
  236. data/app/pb_kits/playbook/pb_pb_gauge_chart/pb_gauge_chart.test.jsx +1 -2
  237. data/app/pb_kits/playbook/pb_pb_line_graph/pb_line_graph.test.jsx +1 -2
  238. data/app/pb_kits/playbook/pb_person/_person.tsx +3 -1
  239. data/app/pb_kits/playbook/pb_person/kit.schema.json +1 -0
  240. data/app/pb_kits/playbook/pb_person_contact/docs/_playground.json +3 -3
  241. data/app/pb_kits/playbook/pb_person_contact/kit.schema.json +2 -1
  242. data/app/pb_kits/playbook/pb_phone_number_input/_phone_number_input.scss +3 -7
  243. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_validation.html.erb +1 -1
  244. data/app/pb_kits/playbook/pb_phone_number_input/docs/_playground.json +3 -0
  245. data/app/pb_kits/playbook/pb_phone_number_input/img/flags.webp +0 -0
  246. data/app/pb_kits/playbook/pb_phone_number_input/img/flags@2x.webp +0 -0
  247. data/app/pb_kits/playbook/pb_phone_number_input/img/globe.webp +0 -0
  248. data/app/pb_kits/playbook/pb_phone_number_input/img/globe@2x.webp +0 -0
  249. data/app/pb_kits/playbook/pb_phone_number_input/intlTelInput.scss +13 -6
  250. data/app/pb_kits/playbook/pb_phone_number_input/kit.schema.json +6 -3
  251. data/app/pb_kits/playbook/pb_phone_number_input/phone_number_input_assets.test.js +61 -0
  252. data/app/pb_kits/playbook/pb_pill/_pill.tsx +3 -1
  253. data/app/pb_kits/playbook/pb_pill/kit.schema.json +1 -0
  254. data/app/pb_kits/playbook/pb_popover/docs/_playground.json +1 -2
  255. data/app/pb_kits/playbook/pb_popover/index.ts +47 -8
  256. data/app/pb_kits/playbook/pb_popover/kit.schema.json +18 -9
  257. data/app/pb_kits/playbook/pb_popover/popover_index.test.js +456 -0
  258. data/app/pb_kits/playbook/pb_progress_pills/docs/_playground.json +1 -0
  259. data/app/pb_kits/playbook/pb_progress_pills/kit.schema.json +2 -1
  260. data/app/pb_kits/playbook/pb_progress_simple/docs/_playground.json +3 -2
  261. data/app/pb_kits/playbook/pb_progress_simple/kit.schema.json +4 -2
  262. data/app/pb_kits/playbook/pb_progress_step/_progress_step_item.tsx +3 -1
  263. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_playground.json +10 -3
  264. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_playground.overrides.json +1 -1
  265. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_required_indicator.md +2 -2
  266. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_default.md +3 -4
  267. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_extensions.md +1 -1
  268. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_markdown_support.md +1 -1
  269. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_required_indicator.html.erb +9 -0
  270. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_required_indicator.md +3 -0
  271. data/app/pb_kits/playbook/pb_rich_text_editor/docs/example.yml +1 -0
  272. data/app/pb_kits/playbook/pb_rich_text_editor/kit.schema.json +18 -9
  273. data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor.html.erb +3 -7
  274. data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor_rails.js +26 -21
  275. data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor_rails.test.js +85 -1
  276. data/app/pb_kits/playbook/pb_selectable_card/docs/_playground.json +1 -0
  277. data/app/pb_kits/playbook/pb_selectable_card/kit.schema.json +4 -2
  278. data/app/pb_kits/playbook/pb_selectable_card_icon/_selectable_card_icon.scss +0 -9
  279. data/app/pb_kits/playbook/pb_selectable_card_icon/docs/_playground.json +1 -0
  280. data/app/pb_kits/playbook/pb_selectable_card_icon/docs/_selectable_card_icon_custom.jsx +1 -1
  281. data/app/pb_kits/playbook/pb_selectable_card_icon/kit.schema.json +2 -1
  282. data/app/pb_kits/playbook/pb_selectable_icon/_selectable_icon.scss +0 -7
  283. data/app/pb_kits/playbook/pb_source/_source.tsx +3 -1
  284. data/app/pb_kits/playbook/pb_source/docs/_playground.json +1 -0
  285. data/app/pb_kits/playbook/pb_source/kit.schema.json +3 -1
  286. data/app/pb_kits/playbook/pb_source/source.rb +1 -1
  287. data/app/pb_kits/playbook/pb_star_rating/docs/_playground.json +1 -0
  288. data/app/pb_kits/playbook/pb_star_rating/kit.schema.json +15 -7
  289. data/app/pb_kits/playbook/pb_stat_change/_stat_change.tsx +3 -1
  290. data/app/pb_kits/playbook/pb_stat_change/docs/_playground.json +1 -0
  291. data/app/pb_kits/playbook/pb_stat_change/kit.schema.json +3 -1
  292. data/app/pb_kits/playbook/pb_stat_value/docs/_playground.json +3 -1
  293. data/app/pb_kits/playbook/pb_stat_value/kit.schema.json +2 -1
  294. data/app/pb_kits/playbook/pb_table/docs/_playground.json +5 -1
  295. data/app/pb_kits/playbook/pb_table/kit.schema.json +28 -11
  296. data/app/pb_kits/playbook/pb_text_input/docs/_playground.json +3 -0
  297. data/app/pb_kits/playbook/pb_text_input/kit.schema.json +22 -9
  298. data/app/pb_kits/playbook/pb_textarea/_textarea.tsx +4 -1
  299. data/app/pb_kits/playbook/pb_time/kit.schema.json +13 -5
  300. data/app/pb_kits/playbook/pb_time_picker/docs/_playground.json +1 -0
  301. data/app/pb_kits/playbook/pb_time_picker/kit.schema.json +2 -1
  302. data/app/pb_kits/playbook/pb_time_range_inline/_time_range_inline.tsx +3 -1
  303. data/app/pb_kits/playbook/pb_time_range_inline/kit.schema.json +13 -5
  304. data/app/pb_kits/playbook/pb_time_stacked/_time_stacked.tsx +3 -1
  305. data/app/pb_kits/playbook/pb_time_stacked/kit.schema.json +1 -0
  306. data/app/pb_kits/playbook/pb_timeline/_item.tsx +3 -1
  307. data/app/pb_kits/playbook/pb_timestamp/_timestamp.tsx +3 -1
  308. data/app/pb_kits/playbook/pb_timestamp/kit.schema.json +1 -0
  309. data/app/pb_kits/playbook/pb_timestamp/timestamp.test.js +1 -0
  310. data/app/pb_kits/playbook/pb_title/kit.schema.json +23 -10
  311. data/app/pb_kits/playbook/pb_title_count/docs/_playground.json +1 -0
  312. data/app/pb_kits/playbook/pb_title_count/kit.schema.json +2 -1
  313. data/app/pb_kits/playbook/pb_tooltip/docs/_playground.json +5 -1
  314. data/app/pb_kits/playbook/pb_tooltip/kit.schema.json +10 -5
  315. data/app/pb_kits/playbook/pb_typeahead/_typeahead.scss +73 -0
  316. data/app/pb_kits/playbook/pb_typeahead/_typeahead.test.jsx +285 -12
  317. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +22 -14
  318. data/app/pb_kits/playbook/pb_typeahead/components/PillRenderer.tsx +15 -11
  319. data/app/pb_kits/playbook/pb_typeahead/components/ValueContainer.tsx +29 -30
  320. data/app/pb_kits/playbook/pb_typeahead/components/usePillDropIndicator.ts +155 -0
  321. data/app/pb_kits/playbook/pb_typeahead/docs/_playground.json +5 -5
  322. data/app/pb_kits/playbook/pb_typeahead/docs/_playground.overrides.json +2 -2
  323. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills.html.erb +0 -1
  324. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills.jsx +0 -1
  325. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills_rails.md +3 -3
  326. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills_react.md +7 -0
  327. data/app/pb_kits/playbook/pb_typeahead/kit.schema.json +3 -3
  328. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +3 -3
  329. data/app/pb_kits/playbook/pb_user/_user.tsx +3 -1
  330. data/app/pb_kits/playbook/pb_user/docs/_playground.json +3 -1
  331. data/app/pb_kits/playbook/pb_user/kit.schema.json +20 -9
  332. data/app/pb_kits/playbook/pb_user_badge/_user_badge.tsx +3 -1
  333. data/app/pb_kits/playbook/pb_user_badge/kit.schema.json +1 -0
  334. data/app/pb_kits/playbook/pb_weekday_stacked/_weekday_stacked.tsx +3 -1
  335. data/app/pb_kits/playbook/pb_weekday_stacked/docs/_playground.json +1 -0
  336. data/app/pb_kits/playbook/pb_weekday_stacked/kit.schema.json +3 -1
  337. data/app/pb_kits/playbook/pb_weekday_stacked/weekday_stacked.test.jsx +1 -0
  338. data/app/pb_kits/playbook/utilities/test/globalProps/onClick.test.js +471 -0
  339. data/app/pb_kits/playbook/utilities/test-utils.js +0 -1
  340. data/dist/ai/all-schemas.json +986 -388
  341. data/dist/ai/external-dependencies.json +1 -1
  342. data/dist/ai/forms.json +2 -2
  343. data/dist/ai/index.json +11 -3
  344. data/dist/ai/kits/advanced_table.schema.json +44 -16
  345. data/dist/ai/kits/avatar.schema.json +22 -9
  346. data/dist/ai/kits/background.schema.json +10 -3
  347. data/dist/ai/kits/badge.schema.json +26 -12
  348. data/dist/ai/kits/body.schema.json +14 -5
  349. data/dist/ai/kits/button.schema.json +29 -25
  350. data/dist/ai/kits/caption.schema.json +17 -7
  351. data/dist/ai/kits/card.schema.json +2 -1
  352. data/dist/ai/kits/checkbox.schema.json +10 -5
  353. data/dist/ai/kits/contact.schema.json +3 -1
  354. data/dist/ai/kits/copy_button.schema.json +10 -5
  355. data/dist/ai/kits/currency.schema.json +6 -2
  356. data/dist/ai/kits/dashboard_value.schema.json +4 -2
  357. data/dist/ai/kits/date.schema.json +16 -6
  358. data/dist/ai/kits/date_picker.schema.json +31 -13
  359. data/dist/ai/kits/date_range_inline.schema.json +3 -1
  360. data/dist/ai/kits/date_range_stacked.schema.json +1 -0
  361. data/dist/ai/kits/date_stacked.schema.json +1 -0
  362. data/dist/ai/kits/date_time.schema.json +3 -1
  363. data/dist/ai/kits/date_time_stacked.schema.json +3 -1
  364. data/dist/ai/kits/date_year_stacked.schema.json +1 -0
  365. data/dist/ai/kits/detail.schema.json +2 -1
  366. data/dist/ai/kits/dialog.schema.json +22 -9
  367. data/dist/ai/kits/distribution_bar.schema.json +2 -1
  368. data/dist/ai/kits/dropdown.schema.json +98 -18
  369. data/dist/ai/kits/empty_state.schema.json +2 -1
  370. data/dist/ai/kits/file_upload.schema.json +8 -3
  371. data/dist/ai/kits/fixed_confirmation_toast.schema.json +6 -3
  372. data/dist/ai/kits/flex.schema.json +27 -12
  373. data/dist/ai/kits/form.schema.json +1 -6
  374. data/dist/ai/kits/form_pill.schema.json +6 -3
  375. data/dist/ai/kits/full_screen.schema.json +8 -4
  376. data/dist/ai/kits/hashtag.schema.json +2 -1
  377. data/dist/ai/kits/highlight.schema.json +2 -1
  378. data/dist/ai/kits/icon.schema.json +31 -13
  379. data/dist/ai/kits/icon_circle.schema.json +3 -1
  380. data/dist/ai/kits/icon_stat_value.schema.json +4 -2
  381. data/dist/ai/kits/image.schema.json +57 -24
  382. data/dist/ai/kits/label_pill.schema.json +1 -0
  383. data/dist/ai/kits/label_value.schema.json +2 -1
  384. data/dist/ai/kits/layout.schema.json +43 -20
  385. data/dist/ai/kits/legend.schema.json +3 -1
  386. data/dist/ai/kits/lightbox.schema.json +86 -0
  387. data/dist/ai/kits/link.schema.json +14 -6
  388. data/dist/ai/kits/list.schema.json +2 -1
  389. data/dist/ai/kits/loading_inline.schema.json +4 -1
  390. data/dist/ai/kits/map.schema.json +4 -7
  391. data/dist/ai/kits/message.schema.json +2 -1
  392. data/dist/ai/kits/multiple_users.schema.json +19 -11
  393. data/dist/ai/kits/multiple_users_stacked.schema.json +2 -1
  394. data/dist/ai/kits/nav.schema.json +4 -2
  395. data/dist/ai/kits/online_status.schema.json +1 -0
  396. data/dist/ai/kits/overlay.schema.json +22 -12
  397. data/dist/ai/kits/pagination.schema.json +6 -3
  398. data/dist/ai/kits/passphrase.schema.json +10 -5
  399. data/dist/ai/kits/pb_gauge_chart.schema.json +2 -1
  400. data/dist/ai/kits/person.schema.json +1 -0
  401. data/dist/ai/kits/person_contact.schema.json +2 -1
  402. data/dist/ai/kits/phone_number_input.schema.json +6 -3
  403. data/dist/ai/kits/pill.schema.json +1 -0
  404. data/dist/ai/kits/popover.schema.json +17 -8
  405. data/dist/ai/kits/progress_pills.schema.json +2 -1
  406. data/dist/ai/kits/progress_simple.schema.json +4 -2
  407. data/dist/ai/kits/rich_text_editor.schema.json +19 -10
  408. data/dist/ai/kits/selectable_card.schema.json +4 -2
  409. data/dist/ai/kits/selectable_card_icon.schema.json +2 -1
  410. data/dist/ai/kits/source.schema.json +3 -1
  411. data/dist/ai/kits/star_rating.schema.json +15 -7
  412. data/dist/ai/kits/stat_change.schema.json +3 -1
  413. data/dist/ai/kits/stat_value.schema.json +2 -1
  414. data/dist/ai/kits/table.schema.json +28 -11
  415. data/dist/ai/kits/text_input.schema.json +22 -9
  416. data/dist/ai/kits/time.schema.json +12 -4
  417. data/dist/ai/kits/time_picker.schema.json +2 -1
  418. data/dist/ai/kits/time_range_inline.schema.json +13 -5
  419. data/dist/ai/kits/time_stacked.schema.json +1 -0
  420. data/dist/ai/kits/timestamp.schema.json +1 -0
  421. data/dist/ai/kits/title.schema.json +23 -10
  422. data/dist/ai/kits/title_count.schema.json +2 -1
  423. data/dist/ai/kits/tooltip.schema.json +10 -5
  424. data/dist/ai/kits/typeahead.schema.json +3 -3
  425. data/dist/ai/kits/user.schema.json +20 -9
  426. data/dist/ai/kits/user_badge.schema.json +1 -0
  427. data/dist/ai/kits/weekday_stacked.schema.json +3 -1
  428. data/dist/ai/playgrounds/advanced_table.json +1 -1
  429. data/dist/ai/playgrounds/dropdown.json +1 -1
  430. data/dist/ai/playgrounds/index.json +1 -1
  431. data/dist/ai/playgrounds/rich_text_editor.json +1 -1
  432. data/dist/ai/playgrounds/typeahead.json +1 -1
  433. data/dist/ai/visual-index.json +1 -1
  434. data/dist/chunks/_pagination-DZ0eQuH2.js +1 -0
  435. data/dist/chunks/{_pb_line_graph-Du7xE9vl.js → _pb_line_graph-DyvH5Zj1.js} +1 -1
  436. data/dist/chunks/{_detail-Bsvl694i.js → _tooltip-CFbtqu-E.js} +2 -2
  437. data/dist/chunks/_typeahead-CkiAf8zx.js +1 -0
  438. data/dist/chunks/{globalProps-B120NbSJ.js → globalProps-xfGTpdLP.js} +1 -1
  439. data/dist/chunks/index-4jrZjexD.js +1 -0
  440. data/dist/chunks/index-BAy61y8S.js +1 -0
  441. data/dist/chunks/index-Cb_SDipk.js +1 -0
  442. data/dist/chunks/index-D3RdsDp7.js +1 -0
  443. data/dist/chunks/index-D8PZIesy.js +1 -0
  444. data/dist/chunks/index-DIoc-aua.js +1 -0
  445. data/dist/chunks/index-qex65UMn.js +1 -0
  446. data/dist/chunks/lib-Cu_i0y0k.js +29 -0
  447. data/dist/chunks/{time_picker_helper-CYGg0UQ0.js → time_picker_helper-B82MORju.js} +1 -1
  448. data/dist/chunks/vendor.js +2 -2
  449. data/dist/menu.yml +3 -3
  450. data/dist/playbook-rails-react-bindings.js +1 -1
  451. data/dist/playbook-rails.js +1 -1
  452. data/dist/playbook.css +1 -1
  453. data/lib/playbook/changelog_generator.rb +47 -21
  454. data/lib/playbook/connect_message_generator.rb +15 -2
  455. data/lib/playbook/version.rb +2 -2
  456. metadata +99 -23
  457. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills.md +0 -7
  458. data/dist/chunks/_table-Bf6LGyL9.js +0 -1
  459. data/dist/chunks/_typeahead-w6S5Vx4b.js +0 -1
  460. data/dist/chunks/lib-Buo6zHEw.js +0 -29
  461. /data/app/pb_kits/playbook/pb_form/docs/{_description.md → _description_rails.md} +0 -0
  462. /data/app/pb_kits/playbook/pb_form/docs/{_form_default_values.md → _form_default_values_rails.md} +0 -0
  463. /data/app/pb_kits/playbook/pb_form/docs/{_form_defaults.md → _form_defaults_rails.md} +0 -0
  464. /data/app/pb_kits/playbook/pb_form/docs/{_form_model_values.md → _form_model_values_rails.md} +0 -0
  465. /data/app/pb_kits/playbook/pb_form/docs/{_form_multi_column.md → _form_multi_column_rails.md} +0 -0
  466. /data/app/pb_kits/playbook/pb_form/docs/{_form_validation.md → _form_validation_rails.md} +0 -0
@@ -1,8 +1,9 @@
1
1
  import React, {useState} from "react"
2
- import { render, screen, waitFor } from "../utilities/test-utils"
2
+ import { fireEvent, render, screen, waitFor } from "../utilities/test-utils"
3
3
 
4
4
  import { AdvancedTable } from "playbook-ui/advanced-table"
5
5
  import { Pill, colors } from "playbook-ui"
6
+ import { buildGroupSeparatorInsets } from "./Utilities/RowUtils"
6
7
 
7
8
  global.ResizeObserver = class {
8
9
  observe() {}
@@ -1520,3 +1521,1101 @@ test("single header row does not mark every column as last-header-cell", () => {
1520
1521
  expect(kit.querySelector("tbody td.last-cell")).not.toBeInTheDocument()
1521
1522
  })
1522
1523
 
1524
+ const selectableMultiHeaderData = MOCK_DATA_NO_SUBROWS.map((row, index) => ({
1525
+ ...row,
1526
+ id: String(index + 1),
1527
+ }))
1528
+
1529
+ test("multi-column selectable headers keep one select-all checkbox on the lowest header row", () => {
1530
+ render(
1531
+ <AdvancedTable
1532
+ columnDefinitions={groupedColumnDefinitions}
1533
+ data={{ testid: testId }}
1534
+ enableToggleExpansion="none"
1535
+ selectableRows
1536
+ tableData={selectableMultiHeaderData}
1537
+ />
1538
+ )
1539
+
1540
+ const kit = screen.getByTestId(testId)
1541
+ const headerRows = kit.querySelectorAll("thead tr")
1542
+ const checkboxHeaderCells = kit.querySelectorAll("thead th.checkbox-cell-header")
1543
+ const headerCheckboxes = kit.querySelectorAll("thead th.checkbox-cell-header input[type='checkbox']")
1544
+
1545
+ expect(headerRows.length).toBeGreaterThan(1)
1546
+ expect(checkboxHeaderCells).toHaveLength(headerRows.length)
1547
+ expect(headerCheckboxes).toHaveLength(1)
1548
+
1549
+ const lowestHeaderRow = headerRows[headerRows.length - 1]
1550
+ expect(lowestHeaderRow.querySelector("th.checkbox-cell-header input[type='checkbox']")).toBeInTheDocument()
1551
+ expect(headerRows[0].querySelector("th.checkbox-cell-header input[type='checkbox']")).not.toBeInTheDocument()
1552
+ })
1553
+
1554
+ test("single-header selectable rows still render one select-all checkbox", () => {
1555
+ render(
1556
+ <AdvancedTable
1557
+ columnDefinitions={columnDefinitions}
1558
+ data={{ testid: testId }}
1559
+ enableToggleExpansion="none"
1560
+ selectableRows
1561
+ tableData={selectableMultiHeaderData}
1562
+ />
1563
+ )
1564
+
1565
+ const kit = screen.getByTestId(testId)
1566
+ const headerRows = kit.querySelectorAll("thead tr")
1567
+ const headerCheckboxes = kit.querySelectorAll("thead th.checkbox-cell-header input[type='checkbox']")
1568
+
1569
+ expect(headerRows).toHaveLength(1)
1570
+ expect(headerCheckboxes).toHaveLength(1)
1571
+ })
1572
+
1573
+ const visibilityColumnDefinitions = [
1574
+ {
1575
+ accessor: "year",
1576
+ label: "Year",
1577
+ cellAccessors: ["quarter", "month", "day"],
1578
+ id: "year",
1579
+ },
1580
+ {
1581
+ accessor: "newEnrollments",
1582
+ label: "New Enrollments",
1583
+ id: "newEnrollments",
1584
+ },
1585
+ {
1586
+ accessor: "scheduledMeetings",
1587
+ label: "Scheduled Meetings",
1588
+ id: "scheduledMeetings",
1589
+ },
1590
+ {
1591
+ accessor: "attendanceRate",
1592
+ label: "Attendance Rate",
1593
+ id: "attendanceRate",
1594
+ },
1595
+ ]
1596
+
1597
+ const groupedVisibilityColumnDefinitions = [
1598
+ {
1599
+ accessor: "year",
1600
+ label: "Year",
1601
+ cellAccessors: ["quarter", "month", "day"],
1602
+ id: "year",
1603
+ },
1604
+ {
1605
+ label: "Enrollment Data",
1606
+ id: "enrollmentData",
1607
+ columns: [
1608
+ {
1609
+ accessor: "newEnrollments",
1610
+ label: "New Enrollments",
1611
+ id: "newEnrollments",
1612
+ },
1613
+ {
1614
+ accessor: "scheduledMeetings",
1615
+ label: "Scheduled Meetings",
1616
+ id: "scheduledMeetings",
1617
+ },
1618
+ ],
1619
+ },
1620
+ ]
1621
+
1622
+ const openColumnVisibilityMenu = () => {
1623
+ fireEvent.click(screen.getByLabelText("sliders-h icon"))
1624
+ }
1625
+
1626
+ const headerTexts = (kit) =>
1627
+ [...kit.querySelectorAll("thead th")].map((cell) => cell.textContent || "")
1628
+
1629
+ const checkboxNamed = (name) =>
1630
+ screen.getByRole("checkbox", {
1631
+ name: (accessibleName) => {
1632
+ const normalized = accessibleName.replace(/\s+/g, " ").trim()
1633
+ return normalized === name || normalized.endsWith(` ${name}`)
1634
+ },
1635
+ })
1636
+
1637
+ const queryCheckboxNamed = (name) =>
1638
+ screen.queryByRole("checkbox", {
1639
+ name: (accessibleName) => {
1640
+ const normalized = accessibleName.replace(/\s+/g, " ").trim()
1641
+ return normalized === name || normalized.endsWith(` ${name}`)
1642
+ },
1643
+ })
1644
+
1645
+ test("column visibility select all is hidden unless showSelectAll is enabled", () => {
1646
+ render(
1647
+ <AdvancedTable
1648
+ columnDefinitions={visibilityColumnDefinitions}
1649
+ columnVisibilityControl={{ default: true }}
1650
+ data={{ testid: testId }}
1651
+ tableData={MOCK_DATA}
1652
+ />
1653
+ )
1654
+
1655
+ openColumnVisibilityMenu()
1656
+
1657
+ expect(queryCheckboxNamed("Select All")).not.toBeInTheDocument()
1658
+ expect(queryCheckboxNamed("Deselect All")).not.toBeInTheDocument()
1659
+ expect(checkboxNamed("New Enrollments")).toBeInTheDocument()
1660
+ })
1661
+
1662
+ test("column visibility select all toggles available columns and stays in sync", () => {
1663
+ render(
1664
+ <AdvancedTable
1665
+ columnDefinitions={visibilityColumnDefinitions}
1666
+ columnVisibilityControl={{ showSelectAll: true }}
1667
+ data={{ testid: testId }}
1668
+ tableData={MOCK_DATA}
1669
+ />
1670
+ )
1671
+
1672
+ const kit = screen.getByTestId(testId)
1673
+ openColumnVisibilityMenu()
1674
+
1675
+ const deselectAll = checkboxNamed("Deselect All")
1676
+ expect(deselectAll).toBeChecked()
1677
+ expect(deselectAll).not.toBePartiallyChecked()
1678
+ expect(deselectAll).toHaveAttribute("type", "checkbox")
1679
+ expect(deselectAll).not.toBeDisabled()
1680
+
1681
+ deselectAll.focus()
1682
+ expect(deselectAll).toHaveFocus()
1683
+ const spaceKey = new KeyboardEvent("keydown", {
1684
+ bubbles: true,
1685
+ cancelable: true,
1686
+ code: "Space",
1687
+ key: " ",
1688
+ })
1689
+ deselectAll.dispatchEvent(spaceKey)
1690
+ expect(spaceKey.defaultPrevented).toBe(false)
1691
+ fireEvent.click(deselectAll)
1692
+
1693
+ expect(checkboxNamed("Select All")).not.toBeChecked()
1694
+ expect(headerTexts(kit).some((text) => text.includes("Year"))).toBe(true)
1695
+ expect(headerTexts(kit).some((text) => text.includes("New Enrollments"))).toBe(false)
1696
+ expect(headerTexts(kit).some((text) => text.includes("Scheduled Meetings"))).toBe(false)
1697
+ expect(headerTexts(kit).some((text) => text.includes("Attendance Rate"))).toBe(false)
1698
+
1699
+ fireEvent.click(checkboxNamed("New Enrollments"))
1700
+
1701
+ expect(checkboxNamed("Select All")).toBePartiallyChecked()
1702
+ expect(checkboxNamed("New Enrollments")).toBeChecked()
1703
+ expect(checkboxNamed("Scheduled Meetings")).not.toBeChecked()
1704
+ expect(headerTexts(kit).some((text) => text.includes("New Enrollments"))).toBe(true)
1705
+ expect(headerTexts(kit).some((text) => text.includes("Scheduled Meetings"))).toBe(false)
1706
+
1707
+ fireEvent.click(checkboxNamed("Select All"))
1708
+
1709
+ expect(checkboxNamed("Deselect All")).toBeChecked()
1710
+ expect(headerTexts(kit).some((text) => text.includes("New Enrollments"))).toBe(true)
1711
+ expect(headerTexts(kit).some((text) => text.includes("Scheduled Meetings"))).toBe(true)
1712
+ expect(headerTexts(kit).some((text) => text.includes("Attendance Rate"))).toBe(true)
1713
+ expect(headerTexts(kit).some((text) => text.includes("Year"))).toBe(true)
1714
+ })
1715
+
1716
+ test("column visibility select all leaves excluded columns unchanged", () => {
1717
+ const onChange = jest.fn()
1718
+ const onColumnVisibilityChange = jest.fn()
1719
+
1720
+ const ControlledVisibility = () => {
1721
+ const [columnVisibility, setColumnVisibility] = useState({
1722
+ attendanceRate: false,
1723
+ scheduledMeetings: false,
1724
+ })
1725
+
1726
+ return (
1727
+ <AdvancedTable
1728
+ columnDefinitions={visibilityColumnDefinitions}
1729
+ columnVisibilityControl={{
1730
+ includeIds: ["newEnrollments", "scheduledMeetings"],
1731
+ onChange: (updater) => {
1732
+ const nextVisibility = typeof updater === "function" ? updater(columnVisibility) : updater
1733
+ onChange(nextVisibility)
1734
+ setColumnVisibility(nextVisibility)
1735
+ },
1736
+ onColumnVisibilityChange,
1737
+ showSelectAll: true,
1738
+ value: columnVisibility,
1739
+ }}
1740
+ data={{ testid: testId }}
1741
+ tableData={MOCK_DATA}
1742
+ />
1743
+ )
1744
+ }
1745
+
1746
+ render(<ControlledVisibility />)
1747
+
1748
+ const kit = screen.getByTestId(testId)
1749
+ openColumnVisibilityMenu()
1750
+
1751
+ expect(checkboxNamed("Select All")).toBePartiallyChecked()
1752
+ expect(queryCheckboxNamed("Attendance Rate")).not.toBeInTheDocument()
1753
+ expect(headerTexts(kit).some((text) => text.includes("Attendance Rate"))).toBe(false)
1754
+ expect(headerTexts(kit).some((text) => text.includes("Year"))).toBe(true)
1755
+ expect(headerTexts(kit).some((text) => text.includes("Scheduled Meetings"))).toBe(false)
1756
+
1757
+ fireEvent.click(checkboxNamed("Select All"))
1758
+
1759
+ expect(checkboxNamed("Deselect All")).toBeChecked()
1760
+ expect(headerTexts(kit).some((text) => text.includes("New Enrollments"))).toBe(true)
1761
+ expect(headerTexts(kit).some((text) => text.includes("Scheduled Meetings"))).toBe(true)
1762
+ expect(headerTexts(kit).some((text) => text.includes("Attendance Rate"))).toBe(false)
1763
+ expect(headerTexts(kit).some((text) => text.includes("Year"))).toBe(true)
1764
+ expect(onChange).toHaveBeenCalledWith(expect.objectContaining({
1765
+ attendanceRate: false,
1766
+ newEnrollments: true,
1767
+ scheduledMeetings: true,
1768
+ }))
1769
+ expect(onColumnVisibilityChange).toHaveBeenCalledWith(expect.objectContaining({
1770
+ attendanceRate: false,
1771
+ newEnrollments: true,
1772
+ scheduledMeetings: true,
1773
+ year: true,
1774
+ }))
1775
+
1776
+ fireEvent.click(checkboxNamed("Deselect All"))
1777
+
1778
+ expect(headerTexts(kit).some((text) => text.includes("New Enrollments"))).toBe(false)
1779
+ expect(headerTexts(kit).some((text) => text.includes("Scheduled Meetings"))).toBe(false)
1780
+ expect(headerTexts(kit).some((text) => text.includes("Attendance Rate"))).toBe(false)
1781
+ expect(headerTexts(kit).some((text) => text.includes("Year"))).toBe(true)
1782
+ })
1783
+
1784
+ test("column visibility select all stays in sync with grouped columns", () => {
1785
+ const onColumnVisibilityChange = jest.fn()
1786
+
1787
+ render(
1788
+ <AdvancedTable
1789
+ columnDefinitions={groupedVisibilityColumnDefinitions}
1790
+ columnVisibilityControl={{
1791
+ onColumnVisibilityChange,
1792
+ showSelectAll: true,
1793
+ }}
1794
+ data={{ testid: testId }}
1795
+ tableData={MOCK_DATA}
1796
+ />
1797
+ )
1798
+
1799
+ const kit = screen.getByTestId(testId)
1800
+ openColumnVisibilityMenu()
1801
+
1802
+ expect(checkboxNamed("Deselect All")).toBeChecked()
1803
+ expect(checkboxNamed("Enrollment Data")).toBeChecked()
1804
+
1805
+ const headerRowCount = kit.querySelectorAll("thead tr").length
1806
+ fireEvent.click(checkboxNamed("Deselect All"))
1807
+
1808
+ expect(onColumnVisibilityChange).toHaveBeenCalledWith(expect.objectContaining({
1809
+ enrollmentData: false,
1810
+ year: true,
1811
+ }))
1812
+ expect(kit.querySelectorAll("thead tr")).toHaveLength(headerRowCount)
1813
+ expect(checkboxNamed("Select All")).not.toBeChecked()
1814
+ expect(checkboxNamed("Enrollment Data")).not.toBeChecked()
1815
+ expect(checkboxNamed("New Enrollments")).not.toBeChecked()
1816
+ expect(headerTexts(kit).some((text) => text.includes("Enrollment Data"))).toBe(false)
1817
+ expect(headerTexts(kit).some((text) => text.includes("Year"))).toBe(true)
1818
+
1819
+ fireEvent.click(checkboxNamed("New Enrollments"))
1820
+
1821
+ expect(checkboxNamed("Select All")).toBePartiallyChecked()
1822
+ expect(checkboxNamed("Enrollment Data")).toBePartiallyChecked()
1823
+ expect(headerTexts(kit).some((text) => text.includes("New Enrollments"))).toBe(true)
1824
+ expect(headerTexts(kit).some((text) => text.includes("Scheduled Meetings"))).toBe(false)
1825
+
1826
+ fireEvent.click(checkboxNamed("Select All"))
1827
+
1828
+ expect(checkboxNamed("Deselect All")).toBeChecked()
1829
+ expect(checkboxNamed("Enrollment Data")).toBeChecked()
1830
+ expect(headerTexts(kit).some((text) => text.includes("New Enrollments"))).toBe(true)
1831
+ expect(headerTexts(kit).some((text) => text.includes("Scheduled Meetings"))).toBe(true)
1832
+ })
1833
+
1834
+ test("deselect all keeps nested multi-header rows", () => {
1835
+ const nestedColumnDefinitions = [
1836
+ {
1837
+ accessor: "year",
1838
+ label: "Year",
1839
+ id: "year",
1840
+ },
1841
+ {
1842
+ label: "Enrollment Data",
1843
+ id: "enrollmentData",
1844
+ columns: [
1845
+ {
1846
+ label: "Enrollment Stats",
1847
+ id: "enrollmentStats",
1848
+ columns: [
1849
+ {
1850
+ accessor: "newEnrollments",
1851
+ label: "New Enrollments",
1852
+ id: "newEnrollments",
1853
+ },
1854
+ {
1855
+ accessor: "scheduledMeetings",
1856
+ label: "Scheduled Meetings",
1857
+ id: "scheduledMeetings",
1858
+ },
1859
+ ],
1860
+ },
1861
+ ],
1862
+ },
1863
+ {
1864
+ label: "Performance Data",
1865
+ id: "performanceData",
1866
+ columns: [
1867
+ {
1868
+ label: "Attendance",
1869
+ id: "attendance",
1870
+ columns: [
1871
+ {
1872
+ accessor: "attendanceRate",
1873
+ label: "Attendance Rate",
1874
+ id: "attendanceRate",
1875
+ },
1876
+ ],
1877
+ },
1878
+ ],
1879
+ },
1880
+ ]
1881
+
1882
+ render(
1883
+ <AdvancedTable
1884
+ columnDefinitions={nestedColumnDefinitions}
1885
+ columnVisibilityControl={{ showSelectAll: true }}
1886
+ data={{ testid: testId }}
1887
+ tableData={MOCK_DATA}
1888
+ />
1889
+ )
1890
+
1891
+ const kit = screen.getByTestId(testId)
1892
+ expect(kit.querySelectorAll("thead tr")).toHaveLength(3)
1893
+
1894
+ openColumnVisibilityMenu()
1895
+ fireEvent.click(checkboxNamed("Deselect All"))
1896
+
1897
+ expect(kit.querySelectorAll("thead tr")).toHaveLength(3)
1898
+ const spacerCells = kit.querySelectorAll("thead tr.header-spacer-row[aria-hidden='true'] .header-spacer-cell")
1899
+ expect(spacerCells).toHaveLength(2)
1900
+ spacerCells.forEach((cell) => {
1901
+ expect(cell).not.toHaveClass("table-header-cells")
1902
+ expect(cell).not.toHaveClass("table-header-cells-custom")
1903
+ })
1904
+ expect(headerTexts(kit).some((text) => text.includes("Year"))).toBe(true)
1905
+ expect(headerTexts(kit).some((text) => text.includes("New Enrollments"))).toBe(false)
1906
+ expect(headerTexts(kit).some((text) => text.includes("Attendance Rate"))).toBe(false)
1907
+ })
1908
+
1909
+ test("grouped headers render no spacer rows or placeholder text while columns are visible", () => {
1910
+ render(
1911
+ <AdvancedTable
1912
+ columnDefinitions={groupedVisibilityColumnDefinitions}
1913
+ columnVisibilityControl={{ default: true }}
1914
+ data={{ testid: testId }}
1915
+ tableData={MOCK_DATA}
1916
+ />
1917
+ )
1918
+
1919
+ const kit = screen.getByTestId(testId)
1920
+ expect(kit.querySelectorAll("thead tr")).toHaveLength(2)
1921
+ expect(kit.querySelector("thead tr.header-spacer-row")).not.toBeInTheDocument()
1922
+ expect(headerTexts(kit)[0]).toBe("")
1923
+ })
1924
+
1925
+ test("select all sends the same callback shapes as single column toggles", () => {
1926
+ const onChange = jest.fn()
1927
+ const onColumnVisibilityChange = jest.fn()
1928
+
1929
+ const ControlledVisibility = () => {
1930
+ const [columnVisibility, setColumnVisibility] = useState({})
1931
+
1932
+ return (
1933
+ <AdvancedTable
1934
+ columnDefinitions={groupedVisibilityColumnDefinitions}
1935
+ columnVisibilityControl={{
1936
+ onChange: (updater) => {
1937
+ onChange(updater)
1938
+ setColumnVisibility(updater)
1939
+ },
1940
+ onColumnVisibilityChange,
1941
+ showSelectAll: true,
1942
+ value: columnVisibility,
1943
+ }}
1944
+ data={{ testid: testId }}
1945
+ tableData={MOCK_DATA}
1946
+ />
1947
+ )
1948
+ }
1949
+
1950
+ render(<ControlledVisibility />)
1951
+
1952
+ const kit = screen.getByTestId(testId)
1953
+ openColumnVisibilityMenu()
1954
+
1955
+ fireEvent.click(checkboxNamed("New Enrollments"))
1956
+ fireEvent.click(checkboxNamed("Select All"))
1957
+
1958
+ const [toggleUpdater, selectAllUpdater] = onChange.mock.calls.map(([updater]) => updater)
1959
+ expect(typeof toggleUpdater).toBe("function")
1960
+ expect(typeof selectAllUpdater).toBe("function")
1961
+ expect(selectAllUpdater({ newEnrollments: false })).toEqual({
1962
+ newEnrollments: true,
1963
+ scheduledMeetings: true,
1964
+ })
1965
+
1966
+ const [toggleState, selectAllState] = onColumnVisibilityChange.mock.calls.map(([state]) => state)
1967
+ expect(Object.keys(selectAllState).sort()).toEqual(Object.keys(toggleState).sort())
1968
+ expect(selectAllState).toEqual({ enrollmentData: true, year: true })
1969
+
1970
+ fireEvent.click(checkboxNamed("Deselect All"))
1971
+
1972
+ expect(onColumnVisibilityChange).toHaveBeenLastCalledWith({ enrollmentData: false, year: true })
1973
+ expect(checkboxNamed("New Enrollments")).not.toBeChecked()
1974
+ expect(checkboxNamed("Scheduled Meetings")).not.toBeChecked()
1975
+ expect(headerTexts(kit).some((text) => text.includes("Enrollment Data"))).toBe(false)
1976
+ })
1977
+
1978
+ test("hidden groups keep the existing header layout unless select all is enabled", () => {
1979
+ render(
1980
+ <AdvancedTable
1981
+ columnDefinitions={groupedVisibilityColumnDefinitions}
1982
+ columnVisibilityControl={{
1983
+ value: {
1984
+ newEnrollments: false,
1985
+ scheduledMeetings: false,
1986
+ },
1987
+ }}
1988
+ data={{ testid: testId }}
1989
+ tableData={MOCK_DATA}
1990
+ />
1991
+ )
1992
+
1993
+ const kit = screen.getByTestId(testId)
1994
+ expect(kit.querySelector("thead tr.header-spacer-row")).not.toBeInTheDocument()
1995
+ expect(kit.querySelectorAll("thead tr")).toHaveLength(1)
1996
+ expect(headerTexts(kit).some((text) => text.includes("Year"))).toBe(true)
1997
+ expect(headerTexts(kit).some((text) => text.includes("Enrollment Data"))).toBe(false)
1998
+ })
1999
+
2000
+ test("select all is omitted when no listed column can be hidden", () => {
2001
+ render(
2002
+ <AdvancedTable
2003
+ columnDefinitions={visibilityColumnDefinitions}
2004
+ columnVisibilityControl={{ showSelectAll: true }}
2005
+ data={{ testid: testId }}
2006
+ tableData={MOCK_DATA}
2007
+ tableOptions={{ enableHiding: false }}
2008
+ />
2009
+ )
2010
+
2011
+ const kit = screen.getByTestId(testId)
2012
+ openColumnVisibilityMenu()
2013
+
2014
+ expect(queryCheckboxNamed("Select All")).not.toBeInTheDocument()
2015
+ expect(queryCheckboxNamed("Deselect All")).not.toBeInTheDocument()
2016
+ expect(checkboxNamed("New Enrollments")).toBeChecked()
2017
+
2018
+ fireEvent.click(checkboxNamed("New Enrollments"))
2019
+
2020
+ expect(headerTexts(kit).some((text) => text.includes("New Enrollments"))).toBe(true)
2021
+ openColumnVisibilityMenu()
2022
+ openColumnVisibilityMenu()
2023
+ expect(checkboxNamed("New Enrollments")).toBeChecked()
2024
+ })
2025
+
2026
+ test("virtualized spacer cells stay out of column header measurement", () => {
2027
+ render(
2028
+ <AdvancedTable
2029
+ columnDefinitions={groupedVisibilityColumnDefinitions}
2030
+ columnVisibilityControl={{
2031
+ showSelectAll: true,
2032
+ value: {
2033
+ newEnrollments: false,
2034
+ scheduledMeetings: false,
2035
+ },
2036
+ }}
2037
+ data={{ testid: testId }}
2038
+ tableData={MOCK_DATA}
2039
+ virtualizedRows
2040
+ />
2041
+ )
2042
+
2043
+ const kit = screen.getByTestId(testId)
2044
+ const spacerCells = kit.querySelectorAll("thead .header-spacer-cell")
2045
+ const measuredCells = document.querySelectorAll(".table-header-cells, .table-header-cells-custom")
2046
+
2047
+ expect(spacerCells.length).toBeGreaterThan(0)
2048
+ spacerCells.forEach((cell) => {
2049
+ expect(cell).not.toHaveClass("table-header-cells")
2050
+ expect(cell).not.toHaveClass("table-header-cells-custom")
2051
+ })
2052
+ expect(Array.from(measuredCells).some((cell) => cell.classList.contains("header-spacer-cell"))).toBe(false)
2053
+ expect(headerTexts(kit).some((text) => text.includes("Year"))).toBe(true)
2054
+ })
2055
+
2056
+ test("grouped select all persisted from the onChange updater round-trips leaf visibility", () => {
2057
+ const onChange = jest.fn()
2058
+ const onColumnVisibilityChange = jest.fn()
2059
+
2060
+ const PersistedGroupedVisibility = () => {
2061
+ const [columnVisibility, setColumnVisibility] = useState({})
2062
+
2063
+ return (
2064
+ <AdvancedTable
2065
+ columnDefinitions={groupedVisibilityColumnDefinitions}
2066
+ columnVisibilityControl={{
2067
+ onChange: (updater) => {
2068
+ setColumnVisibility((currentVisibility) => {
2069
+ const nextVisibility = typeof updater === "function" ? updater(currentVisibility) : updater
2070
+ onChange(nextVisibility)
2071
+ return nextVisibility
2072
+ })
2073
+ },
2074
+ onColumnVisibilityChange,
2075
+ showSelectAll: true,
2076
+ value: columnVisibility,
2077
+ }}
2078
+ data={{ testid: testId }}
2079
+ tableData={MOCK_DATA}
2080
+ />
2081
+ )
2082
+ }
2083
+
2084
+ render(<PersistedGroupedVisibility />)
2085
+
2086
+ const kit = screen.getByTestId(testId)
2087
+ openColumnVisibilityMenu()
2088
+ fireEvent.click(checkboxNamed("Deselect All"))
2089
+
2090
+ expect(onChange).toHaveBeenCalledWith(expect.objectContaining({
2091
+ newEnrollments: false,
2092
+ scheduledMeetings: false,
2093
+ }))
2094
+ expect(onColumnVisibilityChange).toHaveBeenCalledWith({ enrollmentData: false, year: true })
2095
+ expect(checkboxNamed("Select All")).not.toBeChecked()
2096
+ expect(checkboxNamed("Enrollment Data")).not.toBeChecked()
2097
+ expect(checkboxNamed("New Enrollments")).not.toBeChecked()
2098
+ expect(checkboxNamed("Scheduled Meetings")).not.toBeChecked()
2099
+ expect(headerTexts(kit).some((text) => text.includes("Enrollment Data"))).toBe(false)
2100
+ expect(headerTexts(kit).some((text) => text.includes("New Enrollments"))).toBe(false)
2101
+ expect(headerTexts(kit).some((text) => text.includes("Scheduled Meetings"))).toBe(false)
2102
+ expect(headerTexts(kit).some((text) => text.includes("Year"))).toBe(true)
2103
+
2104
+ fireEvent.click(checkboxNamed("Select All"))
2105
+
2106
+ expect(onChange).toHaveBeenLastCalledWith(expect.objectContaining({
2107
+ newEnrollments: true,
2108
+ scheduledMeetings: true,
2109
+ }))
2110
+ expect(onColumnVisibilityChange).toHaveBeenLastCalledWith({ enrollmentData: true, year: true })
2111
+ expect(checkboxNamed("Deselect All")).toBeChecked()
2112
+ expect(checkboxNamed("Enrollment Data")).toBeChecked()
2113
+ expect(checkboxNamed("New Enrollments")).toBeChecked()
2114
+ expect(checkboxNamed("Scheduled Meetings")).toBeChecked()
2115
+ expect(headerTexts(kit).some((text) => text.includes("New Enrollments"))).toBe(true)
2116
+ expect(headerTexts(kit).some((text) => text.includes("Scheduled Meetings"))).toBe(true)
2117
+ })
2118
+
2119
+ test("select all state persisted from onColumnVisibilityChange round-trips through value", () => {
2120
+ const PersistedVisibility = () => {
2121
+ const [columnVisibility, setColumnVisibility] = useState({ attendanceRate: false })
2122
+
2123
+ return (
2124
+ <AdvancedTable
2125
+ columnDefinitions={visibilityColumnDefinitions}
2126
+ columnVisibilityControl={{
2127
+ onChange: () => {},
2128
+ onColumnVisibilityChange: setColumnVisibility,
2129
+ showSelectAll: true,
2130
+ value: columnVisibility,
2131
+ }}
2132
+ data={{ testid: testId }}
2133
+ tableData={MOCK_DATA}
2134
+ />
2135
+ )
2136
+ }
2137
+
2138
+ render(<PersistedVisibility />)
2139
+
2140
+ const kit = screen.getByTestId(testId)
2141
+ openColumnVisibilityMenu()
2142
+
2143
+ fireEvent.click(checkboxNamed("Select All"))
2144
+
2145
+ expect(checkboxNamed("Deselect All")).toBeChecked()
2146
+ expect(headerTexts(kit).some((text) => text.includes("Attendance Rate"))).toBe(true)
2147
+
2148
+ fireEvent.click(checkboxNamed("Deselect All"))
2149
+
2150
+ expect(checkboxNamed("Select All")).not.toBeChecked()
2151
+ expect(headerTexts(kit).some((text) => text.includes("New Enrollments"))).toBe(false)
2152
+ expect(headerTexts(kit).some((text) => text.includes("Attendance Rate"))).toBe(false)
2153
+ expect(headerTexts(kit).some((text) => text.includes("Year"))).toBe(true)
2154
+ })
2155
+
2156
+ const MOCK_DATA_GROUP_SEPARATORS = [
2157
+ {
2158
+ year: "2021",
2159
+ newEnrollments: "20",
2160
+ scheduledMeetings: "10",
2161
+ children: [
2162
+ {
2163
+ year: "2021",
2164
+ quarter: "Q1",
2165
+ newEnrollments: "5",
2166
+ scheduledMeetings: "2",
2167
+ children: [
2168
+ {
2169
+ year: "2021",
2170
+ quarter: "Q1",
2171
+ month: "January",
2172
+ newEnrollments: "2",
2173
+ scheduledMeetings: "1",
2174
+ },
2175
+ {
2176
+ year: "2021",
2177
+ quarter: "Q1",
2178
+ month: "February",
2179
+ newEnrollments: "3",
2180
+ scheduledMeetings: "1",
2181
+ },
2182
+ ],
2183
+ },
2184
+ {
2185
+ year: "2021",
2186
+ quarter: "Q2",
2187
+ newEnrollments: "6",
2188
+ scheduledMeetings: "3",
2189
+ children: [
2190
+ {
2191
+ year: "2021",
2192
+ quarter: "Q2",
2193
+ month: "April",
2194
+ newEnrollments: "3",
2195
+ scheduledMeetings: "1",
2196
+ },
2197
+ {
2198
+ year: "2021",
2199
+ quarter: "Q2",
2200
+ month: "May",
2201
+ newEnrollments: "3",
2202
+ scheduledMeetings: "2",
2203
+ },
2204
+ ],
2205
+ },
2206
+ ],
2207
+ },
2208
+ ]
2209
+
2210
+ test("groupSeparators=false (default) does not add group-separator-row", () => {
2211
+ render(
2212
+ <AdvancedTable
2213
+ columnDefinitions={columnDefinitions}
2214
+ data={{ testid: testId }}
2215
+ expandedControl={{ value: { "0": true, "0.0": true }, onChange: () => {} }}
2216
+ tableData={MOCK_DATA_GROUP_SEPARATORS}
2217
+ />
2218
+ )
2219
+
2220
+ const kit = screen.getByTestId(testId)
2221
+ expect(kit.querySelector(".group-separator-row")).not.toBeInTheDocument()
2222
+ })
2223
+
2224
+ test("groupSeparators marks last visible child of each nested group", () => {
2225
+ render(
2226
+ <AdvancedTable
2227
+ columnDefinitions={columnDefinitions}
2228
+ data={{ testid: testId }}
2229
+ expandedControl={{ value: { "0": true, "0.0": true, "0.1": true }, onChange: () => {} }}
2230
+ groupSeparators
2231
+ tableData={MOCK_DATA_GROUP_SEPARATORS}
2232
+ />
2233
+ )
2234
+
2235
+ const kit = screen.getByTestId(testId)
2236
+ const separatorRows = kit.querySelectorAll("tbody tr.group-separator-row")
2237
+ const separatorMonths = [...separatorRows].map((row) => row.textContent)
2238
+
2239
+ expect(separatorRows.length).toBe(2)
2240
+ expect(separatorMonths.some((text) => text.includes("February"))).toBe(true)
2241
+ expect(separatorMonths.some((text) => text.includes("May"))).toBe(true)
2242
+ expect(kit.querySelector("tbody tr.group-separator-row")?.textContent).not.toContain("January")
2243
+ expect(
2244
+ [...kit.querySelectorAll("tbody tr")].find((row) => row.textContent.includes("2021") && !row.classList.contains("depth-sub-row-1") && !row.classList.contains("depth-sub-row-2"))
2245
+ ).not.toHaveClass("group-separator-row")
2246
+ })
2247
+
2248
+ test("groupSeparators defers separator to last descendant when last sibling is expanded", () => {
2249
+ render(
2250
+ <AdvancedTable
2251
+ columnDefinitions={columnDefinitions}
2252
+ data={{ testid: testId }}
2253
+ expandedControl={{ value: { "0": true, "0.0": true }, onChange: () => {} }}
2254
+ groupSeparators
2255
+ tableData={MOCK_DATA_GROUP_SEPARATORS}
2256
+ />
2257
+ )
2258
+
2259
+ const kit = screen.getByTestId(testId)
2260
+ const q1Row = [...kit.querySelectorAll("tbody tr")].find((row) =>
2261
+ row.classList.contains("depth-sub-row-1") && row.textContent.includes("Q1")
2262
+ )
2263
+ const februaryRow = [...kit.querySelectorAll("tbody tr")].find((row) =>
2264
+ row.textContent.includes("February")
2265
+ )
2266
+
2267
+ expect(q1Row).not.toHaveClass("group-separator-row")
2268
+ expect(februaryRow).toHaveClass("group-separator-row")
2269
+ })
2270
+
2271
+ test("groupSeparators insets each closer to the outermost group it closes", () => {
2272
+ const data = [
2273
+ {
2274
+ year: "2022",
2275
+ newEnrollments: "1",
2276
+ scheduledMeetings: "1",
2277
+ children: [
2278
+ {
2279
+ year: "2022",
2280
+ quarter: "Q1",
2281
+ newEnrollments: "1",
2282
+ scheduledMeetings: "1",
2283
+ children: [
2284
+ {
2285
+ year: "2022",
2286
+ quarter: "Q1",
2287
+ month: "January",
2288
+ newEnrollments: "1",
2289
+ scheduledMeetings: "1",
2290
+ children: [
2291
+ { year: "2022", quarter: "Q1", month: "January", day: "15", newEnrollments: "1", scheduledMeetings: "1" },
2292
+ { year: "2022", quarter: "Q1", month: "January", day: "25", newEnrollments: "1", scheduledMeetings: "1" },
2293
+ ],
2294
+ },
2295
+ {
2296
+ year: "2022",
2297
+ quarter: "Q1",
2298
+ month: "May",
2299
+ newEnrollments: "1",
2300
+ scheduledMeetings: "1",
2301
+ children: [
2302
+ { year: "2022", quarter: "Q1", month: "May", day: "2", newEnrollments: "1", scheduledMeetings: "1" },
2303
+ ],
2304
+ },
2305
+ ],
2306
+ },
2307
+ {
2308
+ year: "2022",
2309
+ quarter: "Q2",
2310
+ newEnrollments: "1",
2311
+ scheduledMeetings: "1",
2312
+ children: [
2313
+ {
2314
+ year: "2022",
2315
+ quarter: "Q2",
2316
+ month: "June",
2317
+ newEnrollments: "1",
2318
+ scheduledMeetings: "1",
2319
+ children: [
2320
+ { year: "2022", quarter: "Q2", month: "June", day: "5", newEnrollments: "1", scheduledMeetings: "1" },
2321
+ ],
2322
+ },
2323
+ ],
2324
+ },
2325
+ ],
2326
+ },
2327
+ ]
2328
+
2329
+ render(
2330
+ <AdvancedTable
2331
+ columnDefinitions={columnDefinitions}
2332
+ data={{ testid: testId }}
2333
+ expandedControl={{
2334
+ value: { "0": true, "0.0": true, "0.0.0": true, "0.0.1": true, "0.1": true, "0.1.0": true },
2335
+ onChange: () => {},
2336
+ }}
2337
+ groupSeparators
2338
+ tableData={data}
2339
+ />
2340
+ )
2341
+
2342
+ const kit = screen.getByTestId(testId)
2343
+ const dayRow = (day) => [...kit.querySelectorAll("tbody tr.depth-sub-row-3")].find((row) =>
2344
+ row.querySelector("td")?.textContent.trim() === day
2345
+ )
2346
+
2347
+ // One stroke per row, inset to the outermost group it closes
2348
+ expect(dayRow("15")).not.toHaveClass("group-separator-row")
2349
+ // Closes January → January's trail
2350
+ expect(dayRow("25")).toHaveStyle({ "--group-separator-inset": "3.5em" })
2351
+ // Closes May + Q1 → Q1's trail
2352
+ expect(dayRow("2")).toHaveStyle({ "--group-separator-inset": "2.25em" })
2353
+ // Closes June + Q2 + 2022 → full-bleed
2354
+ expect(dayRow("5")).toHaveStyle({ "--group-separator-inset": "0em" })
2355
+ })
2356
+
2357
+ test("groupSeparators insets Q1 closer to the Q1 label, not the year gutter", () => {
2358
+ const data = [
2359
+ {
2360
+ year: "2022",
2361
+ newEnrollments: "25",
2362
+ scheduledMeetings: "1",
2363
+ children: [
2364
+ {
2365
+ year: "2022",
2366
+ quarter: "Q1",
2367
+ newEnrollments: "12",
2368
+ scheduledMeetings: "1",
2369
+ children: [
2370
+ { year: "2022", quarter: "Q1", month: "January", newEnrollments: "7", scheduledMeetings: "1" },
2371
+ { year: "2022", quarter: "Q1", month: "May", newEnrollments: "5", scheduledMeetings: "1" },
2372
+ ],
2373
+ },
2374
+ {
2375
+ year: "2022",
2376
+ quarter: "Q2",
2377
+ newEnrollments: "13",
2378
+ scheduledMeetings: "1",
2379
+ },
2380
+ ],
2381
+ },
2382
+ ]
2383
+
2384
+ render(
2385
+ <AdvancedTable
2386
+ columnDefinitions={columnDefinitions}
2387
+ data={{ testid: testId }}
2388
+ expandedControl={{ value: { "0": true, "0.0": true }, onChange: () => {} }}
2389
+ groupSeparators
2390
+ tableData={data}
2391
+ />
2392
+ )
2393
+
2394
+ const kit = screen.getByTestId(testId)
2395
+ const mayRow = [...kit.querySelectorAll("tbody tr")].find((row) =>
2396
+ row.classList.contains("group-separator-row") && row.textContent.includes("May")
2397
+ )
2398
+ const q2Row = [...kit.querySelectorAll("tbody tr")].find((row) =>
2399
+ row.classList.contains("group-separator-row") && row.textContent.includes("Q2")
2400
+ )
2401
+ // Q1 closer → deepest trail on May (depth 2) = 2.25em; year closer → 0 (full-bleed)
2402
+ expect(mayRow).toHaveStyle({ "--group-separator-inset": "2.25em" })
2403
+ expect(q2Row).toHaveStyle({ "--group-separator-inset": "0em" })
2404
+ })
2405
+
2406
+ test("groupSeparators marks last siblings in pinned nested rows", async () => {
2407
+ const data = [
2408
+ {
2409
+ id: "y1",
2410
+ year: "2021",
2411
+ newEnrollments: "20",
2412
+ scheduledMeetings: "10",
2413
+ children: [
2414
+ {
2415
+ id: "q1",
2416
+ year: "2021",
2417
+ quarter: "Q1",
2418
+ newEnrollments: "5",
2419
+ scheduledMeetings: "2",
2420
+ children: [
2421
+ {
2422
+ id: "jan",
2423
+ year: "2021",
2424
+ quarter: "Q1",
2425
+ month: "January",
2426
+ newEnrollments: "2",
2427
+ scheduledMeetings: "1",
2428
+ },
2429
+ {
2430
+ id: "feb",
2431
+ year: "2021",
2432
+ quarter: "Q1",
2433
+ month: "February",
2434
+ newEnrollments: "3",
2435
+ scheduledMeetings: "1",
2436
+ },
2437
+ ],
2438
+ },
2439
+ {
2440
+ id: "q2",
2441
+ year: "2021",
2442
+ quarter: "Q2",
2443
+ newEnrollments: "6",
2444
+ scheduledMeetings: "3",
2445
+ children: [
2446
+ {
2447
+ id: "apr",
2448
+ year: "2021",
2449
+ quarter: "Q2",
2450
+ month: "April",
2451
+ newEnrollments: "3",
2452
+ scheduledMeetings: "1",
2453
+ },
2454
+ {
2455
+ id: "may",
2456
+ year: "2021",
2457
+ quarter: "Q2",
2458
+ month: "May",
2459
+ newEnrollments: "3",
2460
+ scheduledMeetings: "2",
2461
+ },
2462
+ ],
2463
+ },
2464
+ ],
2465
+ },
2466
+ ]
2467
+
2468
+ const PinnedGroupSeparators = () => {
2469
+ const [pinned, setPinned] = useState({ top: ["y1"] })
2470
+ return (
2471
+ <AdvancedTable
2472
+ columnDefinitions={columnDefinitions}
2473
+ data={{ testid: testId }}
2474
+ expandedControl={{ value: { y1: true, q1: true, q2: true }, onChange: () => {} }}
2475
+ groupSeparators
2476
+ pinnedRows={{ value: pinned, onChange: setPinned }}
2477
+ tableData={data}
2478
+ />
2479
+ )
2480
+ }
2481
+
2482
+ render(<PinnedGroupSeparators />)
2483
+
2484
+ const kit = screen.getByTestId(testId)
2485
+ await waitFor(() => {
2486
+ const separatorRows = [...kit.querySelectorAll("tbody tr.pinned-row.group-separator-row")]
2487
+ const separatorText = separatorRows.map((row) => row.textContent)
2488
+
2489
+ expect(separatorRows.length).toBe(2)
2490
+ expect(separatorText.some((text) => text.includes("February"))).toBe(true)
2491
+ expect(separatorText.some((text) => text.includes("May"))).toBe(true)
2492
+ })
2493
+ })
2494
+
2495
+ test("groupSeparators keeps separator on expanded last sibling with empty children (inlineRowLoading)", () => {
2496
+ const data = [
2497
+ {
2498
+ year: "2021",
2499
+ newEnrollments: "10",
2500
+ scheduledMeetings: "1",
2501
+ children: [
2502
+ {
2503
+ year: "2021",
2504
+ quarter: "Q1",
2505
+ newEnrollments: "5",
2506
+ scheduledMeetings: "1",
2507
+ children: [],
2508
+ },
2509
+ {
2510
+ year: "2021",
2511
+ quarter: "Q2",
2512
+ newEnrollments: "5",
2513
+ scheduledMeetings: "1",
2514
+ children: [],
2515
+ },
2516
+ ],
2517
+ },
2518
+ ]
2519
+
2520
+ render(
2521
+ <AdvancedTable
2522
+ columnDefinitions={columnDefinitions}
2523
+ data={{ testid: testId }}
2524
+ expandedControl={{ value: { "0": true, "0.1": true }, onChange: () => {} }}
2525
+ groupSeparators
2526
+ inlineRowLoading
2527
+ tableData={data}
2528
+ />
2529
+ )
2530
+
2531
+ const kit = screen.getByTestId(testId)
2532
+ const q2Row = [...kit.querySelectorAll("tbody tr")].find((row) =>
2533
+ row.classList.contains("depth-sub-row-1") && row.textContent.includes("Q2")
2534
+ )
2535
+
2536
+ expect(q2Row).toHaveClass("group-separator-row")
2537
+ })
2538
+
2539
+ test("groupSeparators follows sorted visible sibling order", async () => {
2540
+ const data = [
2541
+ {
2542
+ year: "2021",
2543
+ newEnrollments: "10",
2544
+ scheduledMeetings: "1",
2545
+ children: [
2546
+ {
2547
+ year: "2021",
2548
+ quarter: "Q1",
2549
+ newEnrollments: "30",
2550
+ scheduledMeetings: "1",
2551
+ },
2552
+ {
2553
+ year: "2021",
2554
+ quarter: "Q2",
2555
+ newEnrollments: "10",
2556
+ scheduledMeetings: "1",
2557
+ },
2558
+ ],
2559
+ },
2560
+ ]
2561
+
2562
+ const sortColumnDefinitions = [
2563
+ {
2564
+ accessor: "year",
2565
+ label: "Year",
2566
+ cellAccessors: ["quarter", "month", "day"],
2567
+ },
2568
+ {
2569
+ accessor: "newEnrollments",
2570
+ label: "New Enrollments",
2571
+ enableSort: true,
2572
+ },
2573
+ {
2574
+ accessor: "scheduledMeetings",
2575
+ label: "Scheduled Meetings",
2576
+ },
2577
+ ]
2578
+
2579
+ render(
2580
+ <AdvancedTable
2581
+ columnDefinitions={sortColumnDefinitions}
2582
+ data={{ testid: testId }}
2583
+ expandedControl={{ value: { "0": true }, onChange: () => {} }}
2584
+ groupSeparators
2585
+ tableData={data}
2586
+ />
2587
+ )
2588
+
2589
+ const kit = screen.getByTestId(testId)
2590
+ // Original data order: Q2 is last sibling → separator on Q2
2591
+ expect(
2592
+ [...kit.querySelectorAll("tbody tr.group-separator-row")].some((row) =>
2593
+ row.textContent.includes("Q2")
2594
+ )
2595
+ ).toBe(true)
2596
+
2597
+ const sortButton = kit.querySelector(".header-sort-secondary-columns")
2598
+ expect(sortButton).toBeInTheDocument()
2599
+ // sortDescFirst: first click is desc (Q1=30, Q2=10) — Q2 still last.
2600
+ // Second click is asc (Q2=10, Q1=30) — separator should move to Q1.
2601
+ sortButton.click()
2602
+ sortButton.click()
2603
+
2604
+ await waitFor(() => {
2605
+ const separatorRows = [...kit.querySelectorAll("tbody tr.group-separator-row")]
2606
+ expect(separatorRows.some((row) => row.textContent.includes("Q1"))).toBe(true)
2607
+ expect(separatorRows.some((row) => row.textContent.includes("Q2"))).toBe(false)
2608
+ })
2609
+ })
2610
+
2611
+
2612
+ test("groupSeparators insets follow the trail formula past 3 levels", () => {
2613
+ const rowsAt = (depths) => depths.map((depth, index) => ({ id: `${index}`, depth }))
2614
+
2615
+ // Depth-5 leaf closing only its depth-4 parent → 5th trail (5 + 4 * 0.25)
2616
+ expect(buildGroupSeparatorInsets(rowsAt([0, 1, 2, 3, 4, 5, 4])).get("5")).toBe(6)
2617
+ // Depth-5 leaf closing up to the depth-1 group → 2nd trail
2618
+ expect(buildGroupSeparatorInsets(rowsAt([0, 1, 2, 3, 4, 5, 1])).get("5")).toBe(2.25)
2619
+ // Depth-5 leaf closing the year → full-bleed
2620
+ expect(buildGroupSeparatorInsets(rowsAt([0, 1, 2, 3, 4, 5, 0])).get("5")).toBe(0)
2621
+ })