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
@@ -12,8 +12,10 @@ import DropdownContainer from "./subcomponents/DropdownContainer";
12
12
  import DropdownContext from "./context";
13
13
  import DropdownOption from "./subcomponents/DropdownOption";
14
14
  import DropdownTrigger from "./subcomponents/DropdownTrigger";
15
+ import DropdownFormFields from "./subcomponents/DropdownFormFields";
15
16
  import useDropdown from "./hooks/useDropdown";
16
17
  import useAsyncOptions, { LoadOptions } from "./hooks/useAsyncOptions";
18
+ import { getCreationOption, matchesOptionExactly } from "./utilities/optionCreation";
17
19
  import getQuickPickOptions from "./quickpick";
18
20
 
19
21
  import {
@@ -99,6 +101,7 @@ type CustomQuickPickDates = {
99
101
  type InputChangeReason = "input" | "clear";
100
102
 
101
103
  type DropdownProps = {
104
+ allowOptionCreation?: boolean;
102
105
  aria?: { [key: string]: string };
103
106
  async?: boolean;
104
107
  loadOptions?: LoadOptions;
@@ -122,6 +125,9 @@ type DropdownProps = {
122
125
  data?: { [key: string]: string };
123
126
  defaultValue?: GenericObject;
124
127
  error?: string;
128
+ enableFormSupport?: boolean;
129
+ enablePillReorder?: boolean;
130
+ externalDragHandle?: boolean;
125
131
  htmlOptions?: { [key: string]: string | number | boolean | (() => void) },
126
132
  id?: string;
127
133
  isClosed?: boolean;
@@ -134,6 +140,7 @@ type DropdownProps = {
134
140
  options?: GenericObject;
135
141
  placeholder?: string;
136
142
  separators?: boolean;
143
+ showPillIndex?: boolean;
137
144
  variant?: "default" | "subtle" | "quickpick";
138
145
  rangeEndsToday?: boolean;
139
146
  controlsStartId?: string;
@@ -143,6 +150,8 @@ type DropdownProps = {
143
150
  fontColor?: string;
144
151
  };
145
152
  requiredIndicator?: boolean;
153
+ required?: boolean;
154
+ validationMessage?: string;
146
155
  };
147
156
 
148
157
  interface DropdownComponent
@@ -154,6 +163,7 @@ interface DropdownComponent
154
163
 
155
164
  let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
156
165
  const {
166
+ allowOptionCreation = false,
157
167
  aria = {},
158
168
  async = false,
159
169
  loadOptions,
@@ -163,7 +173,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
163
173
  onInputChange,
164
174
  renderOption,
165
175
  value,
166
- autocomplete = false,
176
+ autocomplete: autocompleteProp = false,
167
177
  blankSelection = '',
168
178
  children,
169
179
  className,
@@ -175,7 +185,10 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
175
185
  data = {},
176
186
  defaultValue = {},
177
187
  disabled = false,
178
- error,
188
+ error: errorProp,
189
+ enableFormSupport = false,
190
+ enablePillReorder = false,
191
+ externalDragHandle = false,
179
192
  htmlOptions = {},
180
193
  id,
181
194
  isClosed = true,
@@ -188,15 +201,27 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
188
201
  onSelect,
189
202
  options,
190
203
  placeholder,
204
+ showPillIndex = false,
191
205
  rangeEndsToday = false,
192
206
  controlsStartId,
193
207
  controlsEndId,
194
208
  separators = true,
195
209
  variant = "default",
196
210
  activeStyle,
197
- requiredIndicator = false
211
+ requiredIndicator = false,
212
+ required = false,
213
+ validationMessage = "Please fill out this field."
198
214
  } = props;
199
215
 
216
+ const isPillReorderEnabled = Boolean(enablePillReorder && multiSelect);
217
+
218
+ const creationEnabled = allowOptionCreation && variant !== "quickpick";
219
+ const autocomplete = autocompleteProp || creationEnabled;
220
+
221
+ const [formInvalid, setFormInvalid] = useState(false);
222
+ const formError = formInvalid ? validationMessage : "";
223
+ const error = enableFormSupport ? errorProp || formError : errorProp;
224
+
200
225
  const ariaProps = buildAriaProps(aria);
201
226
  const dataProps = buildDataProps(data);
202
227
  const htmlProps = buildHtmlProps(htmlOptions);
@@ -365,6 +390,11 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
365
390
  ? [selected]
366
391
  : [];
367
392
 
393
+ const hasFormSelection = enableFormSupport && selectedArray.some((option) => String(option.id ?? option.value ?? "") !== "");
394
+ useEffect(() => {
395
+ if (!enableFormSupport || hasFormSelection || !required) setFormInvalid(false);
396
+ }, [enableFormSupport, hasFormSelection, required]);
397
+
368
398
  const selectedOptionIds = useMemo(() => (
369
399
  multiSelect && getOptionValue
370
400
  ? new Set(selectedArray.map((option) => String(getOptionValue(option))))
@@ -415,7 +445,16 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
415
445
  return optionsWithBlankSelection.filter((option: GenericObject) => !selectedArray.some((sel) => isSameOption(sel, option)));
416
446
  }, [optionsWithBlankSelection, selectedArray, multiSelect, isSameOption, selectedOptionIds, getOptionValue]);
417
447
 
418
- const filteredOptions = useMemo(() => {
448
+ const searchIsPending = loading || (asyncEnabled && asyncStatus === "loading");
449
+ const searchFailed = asyncEnabled && asyncStatus === "error";
450
+ const canCreateOption = creationEnabled && !disabled && !searchIsPending && !searchFailed;
451
+ let creationOption: GenericObject | null = null;
452
+ if (canCreateOption) {
453
+ const existingOptions = Array.isArray(dropdownOptions) ? dropdownOptions : [];
454
+ creationOption = getCreationOption(filterItem, [...existingOptions, ...selectedArray]);
455
+ }
456
+
457
+ const matchingOptions = useMemo(() => {
419
458
  const selectedLabel =
420
459
  !multiSelect &&
421
460
  !Array.isArray(selected) &&
@@ -431,9 +470,29 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
431
470
  const filterText =
432
471
  selectedLabel && filterItem === selectedLabel ? "" : filterItem;
433
472
  return availableOptions.filter((opt: GenericObject) =>
434
- String(opt.label).toLowerCase().includes(filterText.toLowerCase())
473
+ String(opt.label).toLowerCase().includes(filterText.toLowerCase()) ||
474
+ (creationEnabled && matchesOptionExactly(opt, filterText))
435
475
  );
436
- }, [availableOptions, filterItem, multiSelect, selected, asyncEnabled, autocomplete]);
476
+ }, [availableOptions, filterItem, multiSelect, selected, asyncEnabled, autocomplete, creationEnabled]);
477
+
478
+ const filteredOptions = creationOption ? [...matchingOptions, creationOption] : matchingOptions;
479
+ const inputShowsSelection = !multiSelect && !Array.isArray(selected) && filterItem === selected?.label;
480
+ let creationFocusIndex = -1;
481
+ if (creationEnabled && creationOption) {
482
+ creationFocusIndex = matchingOptions.length;
483
+ } else if (creationEnabled) {
484
+ creationFocusIndex = matchingOptions.findIndex((option: GenericObject) => !option.disabled);
485
+ }
486
+
487
+ useEffect(() => {
488
+ if (!creationEnabled) return;
489
+ if (isDropDownClosed || !filterItem.trim() || inputShowsSelection) {
490
+ setFocusedOptionIndex(-1);
491
+ return;
492
+ }
493
+
494
+ setFocusedOptionIndex(creationFocusIndex);
495
+ }, [creationEnabled, isDropDownClosed, filterItem, inputShowsSelection, creationFocusIndex]);
437
496
 
438
497
  // Empty copy follows the settled search. Hiding already-selected rows must not turn a
439
498
  // successful result list into "No results found", including the last pick while the menu stays open.
@@ -441,18 +500,25 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
441
500
  ? noOptionsText || "No results found"
442
501
  : "";
443
502
 
503
+ let asyncStatusText = "";
504
+ if (asyncEnabled) {
505
+ if (searchIsPending) asyncStatusText = "Loading…";
506
+ else if (searchFailed) asyncStatusText = "Unable to load options";
507
+ else if (!creationOption) asyncStatusText = asyncEmptyCopy;
508
+ }
509
+
444
510
  // A keystroke clears the highlight. Focus the first result once the next list arrives,
445
511
  // including options the application loads itself.
446
512
  const asyncResultOptions = loadOptions ? loadedOptions : options;
447
513
  useEffect(() => {
448
- if (asyncEnabled && !isDropDownClosed) {
514
+ if (!creationEnabled && asyncEnabled && !isDropDownClosed) {
449
515
  setFocusedOptionIndex(filteredOptions.length ? 0 : -1);
450
516
  }
451
- }, [asyncResultOptions]);
517
+ }, [asyncResultOptions, creationEnabled]);
452
518
 
453
519
  // For keyboard accessibility: Set focus within dropdown to selected item if it exists
454
520
  useEffect(() => {
455
- if (!isDropDownClosed) {
521
+ if (!creationEnabled && !isDropDownClosed) {
456
522
  let newIndex = 0;
457
523
  if (selected && !Array.isArray(selected) && selected.label) {
458
524
  const selectedIndex = filteredOptions.findIndex((option: GenericObject) => isSameOption(option, selected));
@@ -462,7 +528,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
462
528
  }
463
529
  setFocusedOptionIndex(newIndex);
464
530
  }
465
- }, [isDropDownClosed]);
531
+ }, [isDropDownClosed, creationEnabled]);
466
532
 
467
533
  // Auto-position dropdown above/below based on available space or fixed/absolute when portaled to body / floating root
468
534
  useLayoutEffect(() => {
@@ -580,10 +646,12 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
580
646
  }
581
647
  } else {
582
648
  setSelected(clickedItem);
583
- // Async autocomplete shows the selection in the input, as controlled mode and Rails do
584
- setFilterItem(isControlled && autocomplete
585
- ? (selected as GenericObject)?.label || ""
586
- : asyncEnabled && autocomplete ? clickedItem.label || "" : "");
649
+ // Plain uncontrolled autocomplete clears its query after selection.
650
+ const keepSelectedLabel = autocomplete && (creationEnabled || asyncEnabled || isControlled);
651
+ const nextQuery = keepSelectedLabel
652
+ ? (isControlled ? (selected as GenericObject)?.label : clickedItem.label) || ""
653
+ : "";
654
+ setFilterItem(nextQuery);
587
655
  if (shouldCloseOnClick) {
588
656
  setIsDropDownClosed(true);
589
657
  }
@@ -724,6 +792,27 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
724
792
  }
725
793
  }, [variant, initialSelected, controlsStartId, controlsEndId]);
726
794
 
795
+ const handleFormInvalid = () => {
796
+ setFormInvalid(true);
797
+ const focusTarget = inputRef.current || inputWrapperRef.current;
798
+ focusTarget?.focus();
799
+ };
800
+
801
+ const handleFormReset = () => {
802
+ const hasDefaultSelection = Object.keys(initialSelected).length > 0;
803
+ const resetValue = multiSelect || hasDefaultSelection ? initialSelected : null;
804
+ const resetQuery = autocomplete && !multiSelect
805
+ ? (initialSelected as GenericObject).label || ""
806
+ : "";
807
+
808
+ setSelected(initialSelected);
809
+ handleSelectionChange(resetValue);
810
+ setFilterItem(resetQuery);
811
+ setFormInvalid(false);
812
+ setIsDropDownClosed(true);
813
+ if (asyncEnabled) clearAsync();
814
+ };
815
+
727
816
  return (
728
817
  <div {...ariaProps}
729
818
  {...dataProps}
@@ -738,7 +827,9 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
738
827
  <DropdownContext.Provider
739
828
  value={{
740
829
  asyncEnabled,
741
- asyncStatus: asyncEnabled ? (loading || asyncStatus === "loading" ? "Loading…" : asyncStatus === "error" ? "Unable to load options" : asyncEmptyCopy) : "",
830
+ asyncStatus: asyncStatusText,
831
+ creationEnabled,
832
+ creationOption,
742
833
  renderOption,
743
834
  optionKey,
744
835
  isControlled,
@@ -754,6 +845,8 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
754
845
  disabled,
755
846
  error,
756
847
  errorId,
848
+ enablePillReorder: isPillReorderEnabled,
849
+ externalDragHandle,
757
850
  filterItem,
758
851
  filteredOptions,
759
852
  focusedOptionIndex,
@@ -782,9 +875,22 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
782
875
  setIsDropDownClosed,
783
876
  setIsInputFocused,
784
877
  setSelected,
878
+ showPillIndex,
785
879
  toggleDropdown
786
880
  }}
787
881
  >
882
+ {enableFormSupport && (
883
+ <DropdownFormFields
884
+ disabled={disabled}
885
+ multiSelect={multiSelect}
886
+ name={name}
887
+ onInvalid={handleFormInvalid}
888
+ onReset={handleFormReset}
889
+ required={required}
890
+ selected={selectedArray}
891
+ validationMessage={validationMessage}
892
+ />
893
+ )}
788
894
  {label && (
789
895
  <label
790
896
  data-dropdown="pb-dropdown-label"
@@ -0,0 +1,51 @@
1
+ <%
2
+ options = [
3
+ { label: 'Year', value: 'year', id: 'year' },
4
+ { label: 'Territory', value: 'territory', id: 'territory' },
5
+ { label: 'Branch', value: 'branch', id: 'branch' },
6
+ { label: 'Product', value: 'product', id: 'product' },
7
+ ]
8
+ %>
9
+
10
+ <%= pb_rails("body", props: { text: "Select services, then drag the pills to reorder them.", margin_bottom: "xs" }) %>
11
+
12
+ <%= pb_rails("dropdown", props: {
13
+ id: "dropdown-draggable-pills-example",
14
+ default_value: [options[0], options[1], options[2]],
15
+ enable_pill_reorder: true,
16
+ show_pill_index: true,
17
+ multi_select: true,
18
+ label: "Services",
19
+ name: :services,
20
+ options: options,
21
+ placeholder: "Add services...",
22
+ }) %>
23
+
24
+ <%= pb_rails("body", props: {
25
+ id: "dropdown-draggable-order",
26
+ color: "lighter",
27
+ margin_top: "xs",
28
+ text: "Current order: Year -> Territory -> Branch",
29
+ }) %>
30
+
31
+ <script>
32
+ (() => {
33
+ const dropdownId = "dropdown-draggable-pills-example";
34
+ const orderElement = document.getElementById("dropdown-draggable-order");
35
+
36
+ const updateOrderText = (detail) => {
37
+ if (!orderElement || !Array.isArray(detail)) return;
38
+ orderElement.textContent = `Current order: ${detail.map((item) => item.label).join(" -> ")}`;
39
+ };
40
+
41
+ document.addEventListener("pb:dropdown:reordered", (event) => {
42
+ if (event.target.id !== dropdownId) return;
43
+ updateOrderText(event.detail);
44
+ });
45
+
46
+ document.addEventListener("pb:dropdown:selected", (event) => {
47
+ if (event.target.id !== dropdownId) return;
48
+ updateOrderText(event.detail);
49
+ });
50
+ })();
51
+ </script>
@@ -0,0 +1,62 @@
1
+ import React, { useState } from 'react'
2
+ import { Body, Button, Dropdown, Flex } from 'playbook-ui'
3
+
4
+ const options = [
5
+ { label: 'Year', value: 'year', id: 'year' },
6
+ { label: 'Territory', value: 'territory', id: 'territory' },
7
+ { label: 'Branch', value: 'branch', id: 'branch' },
8
+ { label: 'Product', value: 'product', id: 'product' },
9
+ ]
10
+
11
+ const DropdownDraggablePills = (props) => {
12
+ const [value, setValue] = useState([
13
+ options[0],
14
+ options[1],
15
+ options[2],
16
+ ])
17
+
18
+ const currentOrder = value.map((item) => item.label).join(' -> ')
19
+
20
+ const handleReset = () => {
21
+ setValue([options[0], options[1], options[2]])
22
+ }
23
+
24
+ return (
25
+ <Flex
26
+ align="stretch"
27
+ gap="sm"
28
+ orientation="column"
29
+ >
30
+ <Flex
31
+ align="center"
32
+ justify="between"
33
+ >
34
+ <Body text="Select services, then drag the pills to reorder them." />
35
+ <Button
36
+ onClick={handleReset}
37
+ text="Reset"
38
+ variant="secondary"
39
+ />
40
+ </Flex>
41
+
42
+ <Dropdown
43
+ enablePillReorder
44
+ label="Services"
45
+ multiSelect
46
+ onChange={({ target }) => setValue(target.value || [])}
47
+ options={options}
48
+ placeholder="Add services..."
49
+ showPillIndex
50
+ value={value}
51
+ {...props}
52
+ />
53
+
54
+ <Body
55
+ color="lighter"
56
+ text={`Current order: ${currentOrder}`}
57
+ />
58
+ </Flex>
59
+ )
60
+ }
61
+
62
+ export default DropdownDraggablePills
@@ -0,0 +1,9 @@
1
+ Use `enable_pill_reorder: true` with `show_pill_index: true` to let users drag selected multi-select pills into a new order. By default, each pill shows a grip icon and can be dragged from anywhere on the pill. Set `external_drag_handle: true` to use an external grip handle instead (drag only from the handle).
2
+
3
+ While dragging, pills stay in place, a ghost follows the pointer, and a blue line marks where the pill will land. Order is committed on drop.
4
+
5
+ The `pb:dropdown:reordered` custom event receives the reordered value array. Submitted form values (hidden inputs) preserve the new order. `pb:dropdown:selected` also fires with the updated order.
6
+
7
+ Keyboard reorder: focus a pill, then press ArrowLeft or ArrowRight to move it one position.
8
+
9
+ Works with Multi-Select and Multi-Select + Autocomplete Dropdown variants only, and is disabled by default.
@@ -0,0 +1,9 @@
1
+ Use `enablePillReorder` to let users drag selected multi-select pills into a new order. Pair with `showPillIndex` to display numbered prefixes that update automatically after reorder or removal.
2
+
3
+ By default, each pill shows a grip icon and can be dragged from anywhere on the pill. Set `externalDragHandle` to `true` to use an external grip handle instead (drag only from the handle).
4
+
5
+ While dragging, pills stay in place, a ghost follows the pointer, and a blue line marks where the pill will land. Order is committed on drop. The `onChange` callback receives the reordered value array.
6
+
7
+ Keyboard reorder: focus a pill, then press ArrowLeft or ArrowRight to move it one position.
8
+
9
+ Works with Multi-Select and Multi-Select + Autocomplete. Dropdown variants only, and is disabled by default.
@@ -0,0 +1,50 @@
1
+ import React, { useState } from 'react'
2
+ import Dropdown from '../_dropdown'
3
+ import Button from '../../pb_button/_button'
4
+ import Body from '../../pb_body/_body'
5
+
6
+ const DropdownForm = () => {
7
+ const [submittedTags, setSubmittedTags] = useState(null)
8
+ const options = [
9
+ { id: 'product', label: 'Product', value: 'product' },
10
+ { id: 'support', label: 'Support', value: 'support' },
11
+ ]
12
+
13
+ const handleSubmit = (event) => {
14
+ event.preventDefault()
15
+ setSubmittedTags(new FormData(event.currentTarget).getAll('tags[]'))
16
+ }
17
+
18
+ return (
19
+ <form onReset={() => setSubmittedTags(null)}
20
+ onSubmit={handleSubmit}
21
+ >
22
+ <Dropdown
23
+ enableFormSupport
24
+ label="Tags"
25
+ marginBottom="md"
26
+ multiSelect
27
+ name="tags[]"
28
+ options={options}
29
+ required
30
+ requiredIndicator
31
+ validationMessage="Select at least one tag."
32
+ />
33
+ <Button htmlType="submit"
34
+ text="Submit"
35
+ />
36
+ <Button htmlType="reset"
37
+ marginLeft="xs"
38
+ text="Reset"
39
+ variant="secondary"
40
+ />
41
+ {submittedTags && (
42
+ <Body marginTop="sm"
43
+ text={`Submitted tags: ${submittedTags.join(', ')}`}
44
+ />
45
+ )}
46
+ </form>
47
+ )
48
+ }
49
+
50
+ export default DropdownForm
@@ -0,0 +1,9 @@
1
+ Set `enableFormSupport` to opt into React native form submission, required validation, and reset handling. The prop defaults to `false`. Passing `name` or `required` alone does not enable native form behavior. Option creation is a separate feature controlled by `allowOptionCreation`.
2
+
3
+ With `enableFormSupport` enabled and `name` provided, Dropdown renders named hidden inputs. Each selected option submits its `id`, falling back to `value`. For `multiSelect`, each selection gets a separate input and the name ends in `[]`; an existing `[]` suffix is preserved. Disabled Dropdowns do not submit values. An empty selection submits an empty string.
4
+
5
+ The example below requires at least one tag. Submit an empty selection to see validation, select tags and submit to see the values from `FormData`, or use Reset to restore the default selection.
6
+
7
+ Use `required` for native form constraint validation, `validationMessage` for the inline required message, and `requiredIndicator` for the visual asterisk. Invalid submissions focus the trigger or autocomplete input. Typing a query does not satisfy required validation; select an option first. The unnamed validation input never submits query text.
8
+
9
+ Native form reset restores `defaultValue`, clears the query and validation message, and closes the menu. A controlled Dropdown reports that reset through its normal selection callbacks; the parent must update `value`. Canceling the reset event preserves the selection. In a controlled form library, pass `value`, `name`, and `onChange`; Dropdown sends `{ target: { name, value } }`, with an option object or array as `value`. The forwarded ref exposes `clearSelected`, rather than a native input ref.
@@ -0,0 +1,16 @@
1
+ <%= pb_rails("dropdown", props: {
2
+ allow_option_creation: true,
3
+ label: "Grid Alignment Group",
4
+ margin_bottom: "md",
5
+ name: "grid_alignment_group",
6
+ options: [{ id: "existing", label: "Existing group", value: "existing" }],
7
+ }) %>
8
+
9
+ <%= pb_rails("dropdown", props: {
10
+ allow_option_creation: true,
11
+ label: "Home IDs",
12
+ multi_select: true,
13
+ name: "home_ids[]",
14
+ options: [{ id: "existing", label: "Existing group", value: "existing" }],
15
+ placeholder: "Type a home ID and press Enter",
16
+ }) %>
@@ -0,0 +1,27 @@
1
+ import React from 'react'
2
+ import Dropdown from '../_dropdown'
3
+
4
+ const DropdownOptionCreation = () => {
5
+ const options = [{ id: 'existing', label: 'Existing tag', value: 'existing' }]
6
+
7
+ return (
8
+ <>
9
+ <Dropdown
10
+ allowOptionCreation
11
+ label="Grid Alignment Group"
12
+ marginBottom="md"
13
+ name="gridAlignmentGroup"
14
+ options={options}
15
+ />
16
+ <Dropdown
17
+ allowOptionCreation
18
+ label="Tags"
19
+ multiSelect
20
+ name="tags[]"
21
+ options={options}
22
+ />
23
+ </>
24
+ )
25
+ }
26
+
27
+ export default DropdownOptionCreation
@@ -0,0 +1,9 @@
1
+ Enable `allow_option_creation: true` to select text that is not already an option. This also enables autocomplete on the default trigger. With custom subcomponents, provide an autocomplete trigger or a searchable container. Creation supports single and multiple selections, including async search, and is ignored for the quickpick variant.
2
+
3
+ Type a new value and click the `Create "…"` row, or use the arrow keys and Enter. A non-empty search focuses the Create row when it is available; arrow keys can choose another option. Creation trims surrounding whitespace, rejects blank text, and suppresses case-insensitive matches against existing or selected labels, values, and IDs. An exact ID or value match keeps the existing option visible and selectable even when its label does not contain the query. The selected object is `{ id: text, label: text, value: text, __isNew__: true }`. The Create label is presentation only.
4
+
5
+ Created selections emit the normal `pb:dropdown:selected` event with the option object, or an array for `multi_select: true`. Rails retains its existing native form support. Multi-select fields submit individual selected values in selection order, and the field name receives a `[]` suffix only if it does not already have one. While selections exist, the empty base input is disabled so it does not add a blank array entry. Clearing the selection restores the base input for required validation; disabled Dropdowns stay excluded from submission.
6
+
7
+ For async usage, combine `allow_option_creation: true` with `async: true` and handle the existing `pb:dropdown:search` event using its `setResults` and `setError` callbacks. Creation is hidden during pending searches and errors, then offered when settled results have no exact match. Creation can be offered below `search_term_minimum_length`, when no remote search runs.
8
+
9
+ Creation does not persist a server record or append to application-owned options; persist selections through your normal form submission or selection handler.
@@ -0,0 +1,9 @@
1
+ Enable `allowOptionCreation` to select text that is not already an option. This also enables autocomplete on the default trigger. Creation supports single and multiple selections, including async search, and is ignored for the quickpick variant.
2
+
3
+ Type a new value and click the `Create "…"` row, or use the arrow keys and Enter. Creation trims surrounding whitespace, rejects blank text, and suppresses case-insensitive matches against existing or selected labels, values, and IDs. An exact ID or value match keeps the existing option visible and selectable even when its label does not contain the query. The selected object is `{ id: text, label: text, value: text, __isNew__: true }`. The Create label is presentation only.
4
+
5
+ Created selections use the normal `onSelect(optionOrArray)` and `onChange({ target: { name, value } })` callbacks. Multiple selections produce an array of option objects. Native form submission additionally requires `enableFormSupport`; option creation alone does not enable it. Creation does not persist a server record or append to application-owned options; persist selections through your normal form submission or selection handler.
6
+
7
+ For async usage, combine `allowOptionCreation` with `async` and your existing `loadOptions`; both Promise and callback loaders are supported. Creation is hidden during pending searches and errors, then offered when settled results have no exact match. Applications that manage fetching themselves must pass `loading` while the request is pending.
8
+
9
+ A non-empty search focuses the Create row when it is available; arrow keys can choose another option. Closing the menu or clearing the query removes automatic option focus. Enter selects a highlighted option, including when the query is blank. Enter with a closed menu, or a blank query and no highlighted option, remains available for normal form submission and does not change the selection.
@@ -5,21 +5,32 @@
5
5
  "options": "Options.data"
6
6
  },
7
7
  "defaults": {
8
+ "allowOptionCreation": false,
8
9
  "async": false,
10
+ "defaultOptions": false,
11
+ "cacheOptions": false,
12
+ "loading": false,
9
13
  "autocomplete": false,
10
14
  "blankSelection": "",
11
15
  "clearable": true,
12
16
  "closeOnClick": "any",
13
17
  "constrainHeight": false,
14
18
  "disabled": false,
19
+ "dark": false,
20
+ "defaultValue": {},
21
+ "enableFormSupport": false,
22
+ "enablePillReorder": false,
23
+ "externalDragHandle": false,
24
+ "isClosed": true,
15
25
  "multiSelect": false,
16
26
  "separators": true,
27
+ "showPillIndex": false,
17
28
  "variant": "default",
18
29
  "rangeEndsToday": false,
19
30
  "controlsStartId": "",
20
31
  "controlsEndId": "",
21
32
  "requiredIndicator": false,
22
- "dark": false,
33
+ "required": false,
23
34
  "label": "Select a Country",
24
35
  "options": [
25
36
  {
@@ -124,7 +135,11 @@
124
135
  "closeOnClick",
125
136
  "constrainHeight",
126
137
  "isClosed",
127
- "requiredIndicator"
138
+ "enableFormSupport",
139
+ "requiredIndicator",
140
+ "required",
141
+ "validationMessage",
142
+ "allowOptionCreation"
128
143
  ]
129
144
  },
130
145
  {
@@ -141,6 +156,9 @@
141
156
  "props": [
142
157
  "separators",
143
158
  "formPillProps",
159
+ "enablePillReorder",
160
+ "externalDragHandle",
161
+ "showPillIndex",
144
162
  "activeStyle",
145
163
  "dark",
146
164
  "disabled"
@@ -155,6 +173,15 @@
155
173
  }
156
174
  ],
157
175
  "presets": [
176
+ {
177
+ "name": "Create tags",
178
+ "props": {
179
+ "allowOptionCreation": true,
180
+ "multiSelect": true,
181
+ "name": "tags[]",
182
+ "label": "Tags"
183
+ }
184
+ },
158
185
  {
159
186
  "name": "Async search",
160
187
  "structureMode": "async",
@@ -339,6 +366,15 @@
339
366
  "formPillProps": {
340
367
  "requires": "multiSelect"
341
368
  },
369
+ "enablePillReorder": {
370
+ "requires": "multiSelect"
371
+ },
372
+ "externalDragHandle": {
373
+ "requires": "enablePillReorder"
374
+ },
375
+ "showPillIndex": {
376
+ "requires": "multiSelect"
377
+ },
342
378
  "rangeEndsToday": {
343
379
  "requires": {
344
380
  "variant": "quickpick"
@@ -389,7 +425,7 @@
389
425
  "when": {
390
426
  "multiSelect": true
391
427
  },
392
- "message": "Multi-select returns arrays of selected options. Use formPillProps to adjust the selected-pill appearance and clearable to let users reset the selection.",
428
+ "message": "Multi-select returns arrays of selected options. Use formPillProps to adjust the selected-pill appearance and clearable to let users reset the selection. Opt into enablePillReorder to drag pills into a new priority order.",
393
429
  "type": "info"
394
430
  },
395
431
  "autocomplete_info": {