playbook_ui 14.14.0 → 14.15.0.pre.alpha.PBNTR900phonenumberinputhidden6620

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 (173) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/Components/RegularTableView.tsx +127 -0
  3. data/app/pb_kits/playbook/pb_advanced_table/Components/TableActionBar.tsx +55 -0
  4. data/app/pb_kits/playbook/pb_advanced_table/Components/TablePagination.tsx +33 -0
  5. data/app/pb_kits/playbook/pb_advanced_table/Components/VirtualizedTableView.tsx +275 -0
  6. data/app/pb_kits/playbook/pb_advanced_table/Context/AdvancedTableContext.tsx +143 -3
  7. data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableActions.ts +66 -0
  8. data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableState.ts +195 -0
  9. data/app/pb_kits/playbook/pb_advanced_table/SubKits/TableBody.tsx +45 -99
  10. data/app/pb_kits/playbook/pb_advanced_table/Utilities/CellRendererUtils.tsx +73 -0
  11. data/app/pb_kits/playbook/pb_advanced_table/Utilities/RowUtils.ts +52 -0
  12. data/app/pb_kits/playbook/pb_advanced_table/Utilities/TableContainerStyles.ts +80 -0
  13. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +123 -7
  14. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +153 -299
  15. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_infinite_scroll.jsx +50 -0
  16. data/app/pb_kits/playbook/pb_advanced_table/docs/advanced_table_mock_data_infinite_scroll.json +152002 -0
  17. data/app/pb_kits/playbook/pb_card/_card.tsx +2 -1
  18. data/app/pb_kits/playbook/pb_currency/_currency.tsx +46 -31
  19. data/app/pb_kits/playbook/pb_currency/currency.html.erb +15 -8
  20. data/app/pb_kits/playbook/pb_currency/currency.rb +17 -2
  21. data/app/pb_kits/playbook/pb_currency/docs/_currency_null_display.html.erb +22 -0
  22. data/app/pb_kits/playbook/pb_currency/docs/_currency_null_display.jsx +34 -0
  23. data/app/pb_kits/playbook/pb_currency/docs/_currency_null_display_rails.md +1 -0
  24. data/app/pb_kits/playbook/pb_currency/docs/_currency_null_display_react.md +1 -0
  25. data/app/pb_kits/playbook/pb_currency/docs/example.yml +2 -0
  26. data/app/pb_kits/playbook/pb_currency/docs/index.js +2 -1
  27. data/app/pb_kits/playbook/pb_date_picker/date_picker.html.erb +4 -1
  28. data/app/pb_kits/playbook/pb_date_picker/date_picker.rb +2 -0
  29. data/app/pb_kits/playbook/pb_date_picker/index.ts +38 -0
  30. data/app/pb_kits/playbook/pb_dialog/dialog.html.erb +5 -6
  31. data/app/pb_kits/playbook/pb_dialog/dialogHelper.js +2 -0
  32. data/app/pb_kits/playbook/pb_dialog/index.js +75 -0
  33. data/app/pb_kits/playbook/pb_drawer/_drawer.scss +43 -3
  34. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_behavior.html.erb +8 -0
  35. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_borders.html.erb +33 -0
  36. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_borders.jsx +3 -3
  37. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_breakpoints.html.erb +0 -0
  38. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_breakpoints.jsx +20 -37
  39. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_default.html.erb +20 -1
  40. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_menu.html.erb +24 -0
  41. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_menu.jsx +6 -6
  42. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_overlay.html.erb +21 -0
  43. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_overlay.jsx +1 -0
  44. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_overlay.md +1 -0
  45. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_sizes.html.erb +49 -0
  46. data/app/pb_kits/playbook/pb_drawer/docs/example.yml +6 -0
  47. data/app/pb_kits/playbook/pb_drawer/drawer.html.erb +20 -12
  48. data/app/pb_kits/playbook/pb_drawer/drawer.rb +49 -1
  49. data/app/pb_kits/playbook/pb_drawer/index.js +257 -0
  50. data/app/pb_kits/playbook/pb_filter/Filter/CurrentFilters.tsx +5 -4
  51. data/app/pb_kits/playbook/pb_filter/Filter/FilterSingle.tsx +2 -2
  52. data/app/pb_kits/playbook/pb_form/docs/_form_form_with.html.erb +2 -1
  53. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate.html.erb +4 -3
  54. data/app/pb_kits/playbook/pb_form_pill/_form_pill.scss +9 -2
  55. data/app/pb_kits/playbook/pb_form_pill/_form_pill.tsx +4 -0
  56. data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_wrapped.html.erb +40 -0
  57. data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_wrapped.jsx +50 -0
  58. data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_wrapped.md +3 -0
  59. data/app/pb_kits/playbook/pb_form_pill/docs/example.yml +2 -0
  60. data/app/pb_kits/playbook/pb_form_pill/docs/index.js +1 -0
  61. data/app/pb_kits/playbook/pb_form_pill/form_pill.rb +7 -1
  62. data/app/pb_kits/playbook/pb_icon_button/_icon_button.tsx +71 -0
  63. data/app/pb_kits/playbook/pb_icon_button/docs/_icon_button_default.jsx +17 -0
  64. data/app/pb_kits/playbook/pb_icon_button/docs/_icon_button_sizes.jsx +61 -0
  65. data/app/pb_kits/playbook/pb_icon_button/docs/example.yml +4 -0
  66. data/app/pb_kits/playbook/pb_icon_button/docs/index.js +2 -0
  67. data/app/pb_kits/playbook/pb_icon_button/icon_button.test.jsx +39 -0
  68. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.scss +7 -0
  69. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +13 -3
  70. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled.html.erb +72 -0
  71. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled.jsx +91 -0
  72. data/app/pb_kits/playbook/pb_multi_level_select/docs/example.yml +2 -1
  73. data/app/pb_kits/playbook/pb_multi_level_select/docs/index.js +1 -0
  74. data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.rb +6 -0
  75. data/app/pb_kits/playbook/pb_overlay/_overlay.scss +13 -0
  76. data/app/pb_kits/playbook/pb_overlay/_overlay.tsx +7 -1
  77. data/app/pb_kits/playbook/pb_overlay/docs/_overlay_hide_scroll_bar.html.erb +11 -0
  78. data/app/pb_kits/playbook/pb_overlay/docs/_overlay_hide_scroll_bar.jsx +37 -0
  79. data/app/pb_kits/playbook/pb_overlay/docs/_overlay_hide_scroll_bar_rails.md +1 -0
  80. data/app/pb_kits/playbook/pb_overlay/docs/_overlay_hide_scroll_bar_react.md +1 -0
  81. data/app/pb_kits/playbook/pb_overlay/docs/_overlay_vertical_dynamic_multi_directional.html.erb +11 -0
  82. data/app/pb_kits/playbook/pb_overlay/docs/_overlay_vertical_dynamic_multi_directional.md +1 -0
  83. data/app/pb_kits/playbook/pb_overlay/docs/example.yml +3 -0
  84. data/app/pb_kits/playbook/pb_overlay/docs/index.js +1 -0
  85. data/app/pb_kits/playbook/pb_overlay/index.js +61 -0
  86. data/app/pb_kits/playbook/pb_overlay/overlay.html.erb +5 -3
  87. data/app/pb_kits/playbook/pb_overlay/overlay.rb +16 -1
  88. data/app/pb_kits/playbook/pb_overlay/overlay.test.jsx +12 -0
  89. data/app/pb_kits/playbook/pb_phone_number_input/_phone_number_input.tsx +6 -0
  90. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_default.html.erb +1 -1
  91. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_hidden_inputs.html.erb +5 -0
  92. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_hidden_inputs.md +1 -0
  93. data/app/pb_kits/playbook/pb_phone_number_input/docs/example.yml +3 -1
  94. data/app/pb_kits/playbook/pb_phone_number_input/phone_number_input.rb +3 -0
  95. data/app/pb_kits/playbook/pb_popover/_popover.tsx +2 -2
  96. data/app/pb_kits/playbook/pb_popover/popover.test.js +1 -1
  97. data/app/pb_kits/playbook/pb_progress_step/progress_step_item.html.erb +2 -2
  98. data/app/pb_kits/playbook/pb_radio/_radio.tsx +85 -74
  99. data/app/pb_kits/playbook/pb_radio/docs/_radio_react_hook.jsx +60 -0
  100. data/app/pb_kits/playbook/pb_radio/docs/_radio_react_hook.md +1 -0
  101. data/app/pb_kits/playbook/pb_radio/docs/example.yml +2 -1
  102. data/app/pb_kits/playbook/pb_radio/docs/index.js +1 -0
  103. data/app/pb_kits/playbook/pb_radio/radio.test.js +16 -0
  104. data/app/pb_kits/playbook/pb_select/docs/_select_react_hook.jsx +58 -0
  105. data/app/pb_kits/playbook/pb_select/docs/_select_react_hook.md +1 -0
  106. data/app/pb_kits/playbook/pb_select/docs/example.yml +1 -0
  107. data/app/pb_kits/playbook/pb_select/docs/index.js +1 -0
  108. data/app/pb_kits/playbook/pb_select/index.js +38 -0
  109. data/app/pb_kits/playbook/pb_select/select.html.erb +3 -5
  110. data/app/pb_kits/playbook/pb_select/select.rb +8 -0
  111. data/app/pb_kits/playbook/pb_selectable_card/selectable_card.html.erb +1 -5
  112. data/app/pb_kits/playbook/pb_selectable_card_icon/selectable_card_icon.html.erb +1 -4
  113. data/app/pb_kits/playbook/pb_selectable_icon/selectable_icon.html.erb +1 -5
  114. data/app/pb_kits/playbook/pb_text_input/inputMask.ts +2 -3
  115. data/app/pb_kits/playbook/pb_timeline/_timeline.scss +2 -2
  116. data/app/pb_kits/playbook/pb_title/_title.scss +32 -0
  117. data/app/pb_kits/playbook/pb_title/_title.tsx +10 -1
  118. data/app/pb_kits/playbook/pb_title/docs/_title_default.html.erb +1 -2
  119. data/app/pb_kits/playbook/pb_title/docs/_title_default.jsx +1 -1
  120. data/app/pb_kits/playbook/pb_title/docs/_title_display_size.html.erb +7 -0
  121. data/app/pb_kits/playbook/pb_title/docs/_title_display_size.jsx +54 -0
  122. data/app/pb_kits/playbook/pb_title/docs/_title_display_size.md +1 -0
  123. data/app/pb_kits/playbook/pb_title/docs/example.yml +2 -0
  124. data/app/pb_kits/playbook/pb_title/docs/index.js +1 -0
  125. data/app/pb_kits/playbook/pb_title/title.rb +10 -1
  126. data/app/pb_kits/playbook/pb_tooltip/_tooltip.scss +0 -3
  127. data/app/pb_kits/playbook/pb_tooltip/_tooltip.tsx +25 -0
  128. data/app/pb_kits/playbook/pb_tooltip/docs/_tooltip_delay_rails.html.erb +39 -0
  129. data/app/pb_kits/playbook/pb_tooltip/docs/_tooltip_delay_rails.md +3 -0
  130. data/app/pb_kits/playbook/pb_tooltip/docs/_tooltip_icon_rails.html.erb +22 -0
  131. data/app/pb_kits/playbook/pb_tooltip/docs/_tooltip_interaction.html.erb +26 -0
  132. data/app/pb_kits/playbook/pb_tooltip/docs/_tooltip_sizing.jsx +69 -0
  133. data/app/pb_kits/playbook/pb_tooltip/docs/_tooltip_sizing.md +3 -0
  134. data/app/pb_kits/playbook/pb_tooltip/docs/example.yml +4 -1
  135. data/app/pb_kits/playbook/pb_tooltip/docs/index.js +1 -0
  136. data/app/pb_kits/playbook/pb_tooltip/floating_ui.js +282 -0
  137. data/app/pb_kits/playbook/pb_tooltip/index.js +2 -2
  138. data/app/pb_kits/playbook/pb_tooltip/tooltip.rb +10 -2
  139. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +51 -5
  140. data/app/pb_kits/playbook/pb_typeahead/components/MultiValue.tsx +5 -1
  141. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_dynamic_options.html.erb +45 -0
  142. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_dynamic_options.md +5 -0
  143. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_dynamic_options_pure_rails.html.erb +33 -0
  144. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_dynamic_options_pure_rails.md +3 -0
  145. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_react_hook.jsx +66 -0
  146. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_react_hook.md +1 -0
  147. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_with_pills_async_custom_options.jsx +1 -1
  148. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_with_pills_async_users.jsx +1 -1
  149. data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +3 -0
  150. data/app/pb_kits/playbook/pb_typeahead/docs/index.js +1 -0
  151. data/app/pb_kits/playbook/pb_typeahead/index.ts +61 -8
  152. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +17 -2
  153. data/app/pb_kits/playbook/utilities/object.test.js +99 -0
  154. data/app/pb_kits/playbook/utilities/object.ts +29 -1
  155. data/dist/chunks/_typeahead-ClZGb4WZ.js +36 -0
  156. data/dist/chunks/_weekday_stacked-BAbPkPFp.js +45 -0
  157. data/dist/chunks/lazysizes-B7xYodB-.js +1 -0
  158. data/dist/chunks/{lib-D3us1bGD.js → lib-5OzNgeeu.js} +2 -2
  159. data/dist/chunks/{pb_form_validation-BpihMSOQ.js → pb_form_validation-DGhKbZtO.js} +1 -1
  160. data/dist/chunks/vendor.js +1 -1
  161. data/dist/menu.yml +3 -3
  162. data/dist/playbook-doc.js +1 -1
  163. data/dist/playbook-rails-react-bindings.js +1 -1
  164. data/dist/playbook-rails.js +1 -1
  165. data/dist/playbook.css +1 -1
  166. data/lib/playbook/forms/builder/time_zone_select_field.rb +19 -0
  167. data/lib/playbook/forms/builder.rb +1 -0
  168. data/lib/playbook/version.rb +2 -2
  169. metadata +74 -8
  170. data/app/pb_kits/playbook/pb_tooltip/docs/_tooltip_icon.erb +0 -10
  171. data/dist/chunks/_typeahead-PqkcDf1H.js +0 -36
  172. data/dist/chunks/_weekday_stacked-B_pw5Znc.js +0 -45
  173. data/dist/chunks/lazysizes-DHz07jlL.js +0 -1
@@ -0,0 +1,45 @@
1
+ <%= pb_rails("select", props: {
2
+ id:"color_context_2",
3
+ label: "Choose a Color",
4
+ name: "color_name",
5
+ options: [
6
+ { value: "red", value_text: "Red" },
7
+ { value: "blue", value_text: "Blue" },
8
+ { value: "green", value_text: "Green" }
9
+ ],
10
+ }) %>
11
+
12
+ <%= pb_rails("typeahead", props: {
13
+ label: "Pick a Shade",
14
+ is_multi: false,
15
+ search_context_selector: "color_context_2",
16
+ options_by_context: {
17
+ "red": [
18
+ { label: "Scarlet", value: "scarlet" },
19
+ { label: "Mahogany", value: "mahogany" },
20
+ { label: "Crimson", value: "crimson" }
21
+ ],
22
+ "blue": [
23
+ { label: "Sky Blue", value: "sky" },
24
+ { label: "Cerulean", value: "cerulean" },
25
+ { label: "Navy", value: "navy" }
26
+ ],
27
+ "green": [
28
+ { label: "Emerald", value: "emerald" },
29
+ { label: "Mint", value: "mint" },
30
+ { label: "Olive", value: "olive" }
31
+ ]
32
+ },
33
+ id: "typeahead-dynamic-options",
34
+ }) %>
35
+
36
+
37
+ <%= javascript_tag defer: "defer" do %>
38
+ document.addEventListener("pb-typeahead-kit-typeahead-dynamic-options-result-option-select", function(event) {
39
+ console.log('Single Option selected')
40
+ console.dir(event.detail)
41
+ })
42
+ document.addEventListener("pb-typeahead-kit-typeahead-dynamic-options-result-clear", function() {
43
+ console.log('All options cleared')
44
+ })
45
+ <% end %>
@@ -0,0 +1,5 @@
1
+ You can also set up a typeahead to render options dynamically based on input from a select. To achieve this:
2
+ - The typeahead must have a unique `id`
3
+ - Use the `search_context_selector` prop on the typeahead. The value here must match the id of the select so the Typeahead knows where to read the current "context" from.
4
+ - Use `options_by_context` to pass in a hash whose keys match the possible values of your “context” select. Each key maps to an array of { label, value } objects. The typeahead automatically displays only the subset of options matching the current context.
5
+ - Additionally, the optional `clear_on_context_change` prop controls whether the typeahead clears or not when a change happens in the linked select. This prop is set to true by default so that whenever a selection is made in the select, the Typeahead automatically clears its current input/selection.
@@ -0,0 +1,33 @@
1
+ <%= pb_rails("select", props: {
2
+ id:"color_context",
3
+ label: "Choose a Color",
4
+ name: "color_name_2",
5
+ options: [
6
+ { value: "red", value_text: "Red" },
7
+ { value: "blue", value_text: "Blue" },
8
+ { value: "green", value_text: "Green" }
9
+ ],
10
+ }) %>
11
+
12
+ <%= pb_rails("typeahead", props: {
13
+ label: "Pick a Shade",
14
+ search_context_selector: "color_context",
15
+ options_by_context: {
16
+ "red": [
17
+ { label: "Scarlet", value: "scarlet" },
18
+ { label: "Mahogany", value: "mahogany" },
19
+ { label: "Crimson", value: "crimson" }
20
+ ],
21
+ "blue": [
22
+ { label: "Sky Blue", value: "sky" },
23
+ { label: "Cerulean", value: "cerulean" },
24
+ { label: "Navy", value: "navy" }
25
+ ],
26
+ "green": [
27
+ { label: "Emerald", value: "emerald" },
28
+ { label: "Mint", value: "mint" },
29
+ { label: "Olive", value: "olive" }
30
+ ]
31
+ },
32
+ search_term_minimum_length: 0,
33
+ }) %>
@@ -0,0 +1,3 @@
1
+ The dynamic rendering of options for the typeahead can also be achieved with a pure Rails implementation (not react rendered). For this implementation, use all the props as above with the following additions:
2
+
3
+ - `search_term_minimum_length`: this sets the minimum input in the typeahead needed to display the dropdown. This is set to 3 by default. Set it to 0 for the dropdown to always display when the typeahead is interacted with.
@@ -0,0 +1,66 @@
1
+ import React from 'react'
2
+
3
+ import { Typeahead, Title } from 'playbook-ui'
4
+ import { useForm } from 'react-hook-form'
5
+
6
+ const languages = [
7
+ { label: 'JavaScript', value: '1995', category: 'Web Development' },
8
+ { label: 'Python', value: '1991', category: 'General Purpose' },
9
+ { label: 'Java', value: '1995', category: 'Enterprise' },
10
+ { label: 'C++', value: '1985', category: 'Systems Programming' },
11
+ { label: 'Go', value: '2009', category: 'Systems Programming' },
12
+ { label: 'Rust', value: '2010', category: 'Systems Programming' },
13
+ { label: 'Swift', value: '2014', category: 'Mobile Development' },
14
+ { label: 'Kotlin', value: '2011', category: 'Mobile Development' },
15
+ { label: 'Ruby', value: '1995', category: 'General Purpose' },
16
+ { label: 'PHP', value: '1995', category: 'Web Development' },
17
+ ]
18
+
19
+ const colors = [
20
+ { label: 'Orange', value: '#FFA500' },
21
+ { label: 'Red', value: '#FF0000' },
22
+ { label: 'Green', value: '#00FF00' },
23
+ { label: 'Blue', value: '#0000FF' },
24
+ ]
25
+
26
+ const TypeaheadReactHook = (props) => {
27
+ const { register, watch } = useForm()
28
+
29
+ const selectedLanguages = watch('languages')
30
+ const selectedColor = watch('color')
31
+
32
+ return (
33
+ <>
34
+ <Typeahead
35
+ isMulti
36
+ label="Multi Select Languages"
37
+ multiKit="language"
38
+ options={languages}
39
+ {...props}
40
+ {...register('languages')}
41
+ />
42
+ <Title
43
+ size={4}
44
+ text='Selected Languages'
45
+ />
46
+ {selectedLanguages && selectedLanguages.map(language => (
47
+ <p key={language.label}>{`${language.label} - ${language.value} - ${language.category}`}</p>
48
+ ))}
49
+
50
+ <Typeahead
51
+ label="Colors"
52
+ marginTop="lg"
53
+ options={colors}
54
+ {...props}
55
+ {...register('color')}
56
+ />
57
+ <Title
58
+ size={4}
59
+ text='Selected Color'
60
+ />
61
+ <p>{ selectedColor && `${selectedColor.label} - ${selectedColor.value}`}</p>
62
+ </>
63
+ )
64
+ }
65
+
66
+ export default TypeaheadReactHook
@@ -0,0 +1 @@
1
+ You can pass `react-hook-form` props to the Typeahead kit.
@@ -49,7 +49,7 @@ const TypeaheadWithPillsAsyncCustomOptions = (props) => {
49
49
  * @summary - for doc example purposes only
50
50
  */
51
51
  const handleOnMultiValueClick = (value) => {
52
- alert(`You removed the user: "${value.label}"`)
52
+ alert(`You added the user: "${value.label}"`)
53
53
  }
54
54
 
55
55
  return (
@@ -56,7 +56,7 @@ const TypeaheadWithPillsAsyncUsers = (props) => {
56
56
  * @summary - for doc example purposes only
57
57
  */
58
58
  const handleOnMultiValueClick = (value) => {
59
- alert(`You removed the user: "${value.label}"`)
59
+ alert(`You added the user: "${value.label}"`)
60
60
  }
61
61
 
62
62
  return (
@@ -12,9 +12,12 @@ examples:
12
12
  - typeahead_margin_bottom: Margin Bottom
13
13
  - typeahead_with_pills_color: With Pills (Custom Color)
14
14
  - typeahead_truncated_text: Truncated Text
15
+ - typeahead_dynamic_options: Dynamic Options
16
+ - typeahead_dynamic_options_pure_rails: Dynamic Options (Pure Rails)
15
17
 
16
18
  react:
17
19
  - typeahead_default: Default
20
+ - typeahead_react_hook: React Hook
18
21
  - typeahead_with_highlight: With Highlight
19
22
  - typeahead_with_pills: With Pills
20
23
  - typeahead_with_pills_async: With Pills (Async Data)
@@ -13,3 +13,4 @@ export { default as TypeaheadCustomMenuList } from './_typeahead_custom_menu_lis
13
13
  export { default as TypeaheadMarginBottom } from './_typeahead_margin_bottom.jsx'
14
14
  export { default as TypeaheadWithPillsColor } from './_typeahead_with_pills_color.jsx'
15
15
  export { default as TypeaheadTruncatedText } from './_typeahead_truncated_text.jsx'
16
+ export { default as TypeaheadReactHook } from './_typeahead_react_hook.jsx'
@@ -20,6 +20,34 @@ export default class PbTypeahead extends PbEnhancedElement {
20
20
  this.searchInput.addEventListener('focus', () => this.debouncedSearch())
21
21
  this.searchInput.addEventListener('input', () => this.debouncedSearch())
22
22
  this.resultsElement.addEventListener('click', (event: MouseEvent) => this.optionSelected(event))
23
+
24
+ if (this.clearOnContextChange && this.searchContextElement) {
25
+ this.searchContextElement.addEventListener('change', () => {
26
+ this.searchInputClear()
27
+ this.resultsCacheClear()
28
+ this.clearResults()
29
+ })
30
+ }
31
+ }
32
+
33
+ get optionsByContext() {
34
+ return (this.element as HTMLElement).dataset.pbTypeaheadKitOptionsByContext
35
+ ? JSON.parse((this.element as HTMLElement).dataset.pbTypeaheadKitOptionsByContext)
36
+ : null
37
+ }
38
+
39
+ get searchContextElement() {
40
+ const selector = (this.element as HTMLElement).dataset.pbTypeaheadKitSearchContextSelector
41
+ if (!selector) return null
42
+
43
+ const found = this.element.parentNode?.querySelector(`#${selector}`)
44
+ || this.element.closest(selector)
45
+
46
+ return found || null
47
+ }
48
+
49
+ get clearOnContextChange() {
50
+ return (this.element as HTMLElement).dataset.pbTypeaheadKitClearOnContextChange === 'true'
23
51
  }
24
52
 
25
53
  handleKeydown(event: KeyboardEvent) {
@@ -40,14 +68,32 @@ export default class PbTypeahead extends PbEnhancedElement {
40
68
 
41
69
  const searchTerm = this.searchTerm
42
70
  const searchContext = this.searchContext
43
- const search = {
44
- searchingFor: searchTerm,
45
- searchingContext: searchContext,
46
- setResults: (results: Array<DocumentFragment>) => {
47
- this.resultsCacheUpdate(searchTerm, searchContext, results)
48
- },
71
+
72
+ if (this.optionsByContext && Object.keys(this.optionsByContext).length > 0) {
73
+ const contextArray = this.optionsByContext[searchContext] || []
74
+
75
+ const filteredResults = contextArray.filter((obj: { label: string, value: string }) => {
76
+ return obj.label
77
+ && obj.label.toLowerCase().includes(searchTerm.toLowerCase())
78
+ })
79
+
80
+ const optionFragments = filteredResults.map((obj: { label: string, value: string }) => {
81
+ const singleOption = document.createDocumentFragment()
82
+ singleOption.appendChild(document.createTextNode(obj.label))
83
+ return singleOption
84
+ })
85
+
86
+ this.resultsCacheUpdate(searchTerm, searchContext, optionFragments)
87
+ } else {
88
+ const search = {
89
+ searchingFor: searchTerm,
90
+ searchingContext: searchContext,
91
+ setResults: (results: Array<DocumentFragment>) => {
92
+ this.resultsCacheUpdate(searchTerm, searchContext, results)
93
+ },
94
+ }
95
+ this.element.dispatchEvent(new CustomEvent('pb-typeahead-kit-search', { bubbles: true, detail: search }))
49
96
  }
50
- this.element.dispatchEvent(new CustomEvent('pb-typeahead-kit-search', { bubbles: true, detail: search }))
51
97
  }
52
98
 
53
99
  resultsCacheUpdate(searchTerm: string, searchContext: string, results: Array<DocumentFragment>) {
@@ -87,11 +133,15 @@ export default class PbTypeahead extends PbEnhancedElement {
87
133
  const resultOption = (event.target as Element).closest('[data-result-option-item]')
88
134
  if (!resultOption) return
89
135
 
136
+ const selectedText = resultOption.textContent.trim()
137
+
90
138
  this._validSelection = true
91
139
  this.removeValidationError()
92
140
 
141
+ if (this.searchContextElement) this.searchInput.value = selectedText
142
+
93
143
  this.resultsCacheClear()
94
- this.searchInputClear()
144
+ if (!this.searchContextElement) this.searchInputClear()
95
145
  this.clearResults()
96
146
 
97
147
  this.element.dispatchEvent(new CustomEvent('pb-typeahead-kit-result-option-selected', { bubbles: true, detail: { selected: resultOption, typeahead: this } }))
@@ -173,6 +223,9 @@ export default class PbTypeahead extends PbEnhancedElement {
173
223
  this.element.parentNode.querySelector(selector) ||
174
224
  this.element.closest(selector)
175
225
  ) as HTMLInputElement).value
226
+ else if (this.searchContextElement) {
227
+ return (this.searchContextElement as HTMLInputElement).value
228
+ }
176
229
 
177
230
  return null
178
231
  }
@@ -44,6 +44,14 @@ module Playbook
44
44
  default: false
45
45
  prop :validation, type: Playbook::Props::HashProp,
46
46
  default: {}
47
+ prop :wrapped, type: Playbook::Props::Boolean,
48
+ default: false
49
+ prop :clear_on_context_change, type: Playbook::Props::Boolean,
50
+ default: true
51
+ prop :options_by_context, type: Playbook::Props::HashProp,
52
+ default: {}
53
+ prop :search_context_selector, type: Playbook::Props::String,
54
+ default: nil
47
55
 
48
56
  def classname
49
57
  default_margin_bottom = margin_bottom.present? ? "" : " mb_sm"
@@ -58,12 +66,15 @@ module Playbook
58
66
  Hash(values[:data]).merge(
59
67
  pb_typeahead_kit: true,
60
68
  pb_typeahead_kit_search_term_minimum_length: search_term_minimum_length,
61
- pb_typeahead_kit_search_debounce_timeout: search_debounce_timeout
69
+ pb_typeahead_kit_search_debounce_timeout: search_debounce_timeout,
70
+ pb_typeahead_kit_clear_on_context_change: clear_on_context_change,
71
+ pb_typeahead_kit_search_context_selector: search_context_selector,
72
+ pb_typeahead_kit_options_by_context: options_by_context.to_json
62
73
  )
63
74
  end
64
75
 
65
76
  def is_react?
66
- pills || !is_multi
77
+ pills || !is_multi || wrapped
67
78
  end
68
79
 
69
80
  def typeahead_react_options
@@ -85,6 +96,10 @@ module Playbook
85
96
  placeholder: placeholder,
86
97
  plusIcon: plus_icon,
87
98
  truncate: truncate,
99
+ wrapped: wrapped,
100
+ searchContextSelector: search_context_selector,
101
+ optionsByContext: options_by_context,
102
+ clearOnContextChange: clear_on_context_change,
88
103
  }
89
104
 
90
105
  base_options[:getOptionLabel] = get_option_label if get_option_label.present?
@@ -0,0 +1,99 @@
1
+ import { isEmpty, get, isString, uniqueId, omitBy } from './object';
2
+
3
+ describe('Lodash functions', () => {
4
+ describe('isEmpty', () => {
5
+ test('returns true for empty objects', () => {
6
+ expect(isEmpty({})).toBe(true);
7
+ });
8
+ test('returns true for empty arrays', () => {
9
+ expect(isEmpty([])).toBe(true);
10
+ });
11
+ test('returns false for non-empty objects', () => {
12
+ expect(isEmpty({ a: 1 })).toBe(false);
13
+ });
14
+ test('returns false for non-empty arrays', () => {
15
+ expect(isEmpty([1])).toBe(false);
16
+ });
17
+ test('returns true for null and undefined', () => {
18
+ expect(isEmpty(null)).toBe(true);
19
+ expect(isEmpty(undefined)).toBe(true);
20
+ });
21
+ test('returns false for non-object, non-array values', () => {
22
+ expect(isEmpty("hello")).toBe(false);
23
+ expect(isEmpty(123)).toBe(false);
24
+ });
25
+ });
26
+
27
+ describe('get', () => {
28
+ const obj = { a: { b: { c: 42 } }, arr: [{ x: 1 }, { y: 2 }] };
29
+
30
+ test('retrieves nested properties by dot path', () => {
31
+ expect(get(obj, 'a.b.c')).toBe(42);
32
+ });
33
+ test('returns default value if path does not exist', () => {
34
+ expect(get(obj, 'a.b.d', 'default')).toBe('default');
35
+ });
36
+ test('retrieves array values using bracket notation', () => {
37
+ expect(get(obj, 'arr[1].y')).toBe(2);
38
+ });
39
+ test('returns default for non-existent paths', () => {
40
+ expect(get(obj, 'non.existent.path', null)).toBe(null);
41
+ });
42
+ });
43
+
44
+ describe('isString', () => {
45
+ test('returns true for string literal', () => {
46
+ expect(isString('test')).toBe(true);
47
+ });
48
+ test('returns true for String object', () => {
49
+ expect(isString(new String('test'))).toBe(true);
50
+ });
51
+ test('returns false for non-string values', () => {
52
+ expect(isString(123)).toBe(false);
53
+ expect(isString(null)).toBe(false);
54
+ expect(isString({ a: 1 })).toBe(false);
55
+ expect(isString([ 'a','b','c' ])).toBe(false);
56
+ });
57
+ });
58
+
59
+ describe('uniqueId', () => {
60
+ test('generates unique ids without prefix', () => {
61
+ const id1 = uniqueId();
62
+ const id2 = uniqueId();
63
+ expect(id1).not.toBe(id2);
64
+ expect(Number(id1)).toBeLessThan(Number(id2));
65
+ });
66
+ test('generates unique ids with prefix', () => {
67
+ const id1 = uniqueId('id_');
68
+ const id2 = uniqueId('id_');
69
+ const id3 = uniqueId('id_');
70
+ expect(id1).not.toBe(id2);
71
+ expect(id1).not.toBe(id3);
72
+ expect(id2).not.toBe(id3);
73
+ expect(id1.startsWith('id_')).toBe(true);
74
+ expect(id2.startsWith('id_')).toBe(true);
75
+ expect(id3.startsWith('id_')).toBe(true);
76
+ });
77
+ });
78
+
79
+ describe('omitBy', () => {
80
+ test('omits keys for which predicate returns true', () => {
81
+ const obj = { a: 1, b: 2, c: 3 };
82
+ const isEven = value => value % 2 === 0;
83
+ const noEvenValues = omitBy(obj, isEven);
84
+ expect(noEvenValues).toEqual({ a: 1, c: 3 });
85
+ });
86
+ test('returns empty object for null input', () => {
87
+ expect(omitBy(null, () => true)).toEqual({});
88
+ });
89
+ test('returns empty object for non-object input', () => {
90
+ expect(omitBy("string", () => true)).toEqual({});
91
+ });
92
+ test('returns original object if predicate returns false for all keys', () => {
93
+ const obj = { a: 1, b: 2 };
94
+ const isBiggerThanFive = value => value >= 4;
95
+ const objWithSmallValues = omitBy(obj, isBiggerThanFive);
96
+ expect(objWithSmallValues).toEqual(obj);
97
+ });
98
+ });
99
+ });
@@ -1,3 +1,31 @@
1
1
  /* 🛠️ Any commonly used lodash functions can be added here. 🤙 */
2
2
 
3
- export const isEmpty = (obj: Record<string, unknown>): boolean => Object.keys(obj).length < 1
3
+ export const isEmpty = (obj: any) => [Object, Array].includes((obj || {}).constructor) && !Object.entries((obj || {})).length;
4
+
5
+ export const get = <T, R = any>(obj: T, path: string, defaultValue?: R): R | any => {
6
+ const travel = (regexp: RegExp): any =>
7
+ String.prototype.split
8
+ .call(path, regexp)
9
+ .filter(Boolean)
10
+ .reduce((res: any, key: string) => (res !== null && res !== undefined ? res[key] : res), obj)
11
+ const result = travel(/[,[\]]+?/) || travel(/[,[\].]+?/)
12
+ return result === undefined || result === obj ? defaultValue : result
13
+ }
14
+
15
+ export const isString = (str: unknown): str is string =>
16
+ str != null && typeof (str as any).valueOf() === "string"
17
+
18
+ export const uniqueId: (prefix?: string) => string = (() => {
19
+ let counter = 0
20
+ return (prefix = '') => `${prefix}${++counter}`
21
+ })()
22
+
23
+ export const omitBy = (obj: Record<string, any>, predicate: (value: any, key: string) => boolean) => {
24
+ if (obj === null || typeof obj !== 'object') return {}
25
+ return Object.keys(obj).reduce((result: Record<string, any>, key: string) => {
26
+ if (!predicate(obj[key], key)) {
27
+ result[key] = obj[key];
28
+ }
29
+ return result;
30
+ }, {})
31
+ }