playbook_ui 14.11.1.pre.alpha.PBNTR768stickyrightcolumn5431 → 14.11.1.pre.alpha.PBNTR798datepickerturbo5537

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_sort_control.md +2 -2
  3. data/app/pb_kits/playbook/pb_button/button.html.erb +2 -3
  4. data/app/pb_kits/playbook/pb_checkbox/checkbox.html.erb +1 -6
  5. data/app/pb_kits/playbook/pb_date_picker/date_picker.html.erb +24 -16
  6. data/app/pb_kits/playbook/pb_date_picker/date_picker.rb +2 -0
  7. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_turbo_frames.html.erb +13 -0
  8. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_turbo_frames_rails.md +3 -0
  9. data/app/pb_kits/playbook/pb_date_picker/docs/example.yml +1 -0
  10. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_with_list_rails.html.erb +3 -9
  11. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_with_list_rails.md +5 -0
  12. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_subtle_variant.md +1 -1
  13. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +7 -12
  14. data/app/pb_kits/playbook/pb_dropdown/dropdown_container.html.erb +9 -14
  15. data/app/pb_kits/playbook/pb_dropdown/dropdown_option.html.erb +6 -11
  16. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.html.erb +8 -14
  17. data/app/pb_kits/playbook/pb_list/item.html.erb +30 -8
  18. data/app/pb_kits/playbook/pb_list/item.rb +7 -0
  19. data/app/pb_kits/playbook/pb_list/list.html.erb +31 -11
  20. data/app/pb_kits/playbook/pb_list/list.rb +4 -0
  21. data/app/pb_kits/playbook/pb_loading_inline/_loading_inline.tsx +6 -1
  22. data/app/pb_kits/playbook/pb_phone_number_input/_phone_number_input.tsx +30 -12
  23. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_format.html.erb +15 -0
  24. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_format.jsx +24 -0
  25. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_format.md +1 -0
  26. data/app/pb_kits/playbook/pb_phone_number_input/docs/example.yml +3 -1
  27. data/app/pb_kits/playbook/pb_phone_number_input/docs/index.js +1 -0
  28. data/app/pb_kits/playbook/pb_phone_number_input/phone_number_input.rb +3 -0
  29. data/app/pb_kits/playbook/pb_phone_number_input/phone_number_input.test.js +20 -1
  30. data/app/pb_kits/playbook/pb_radio/_radio.scss +12 -8
  31. data/app/pb_kits/playbook/pb_radio/docs/_radio_custom_children.jsx +8 -3
  32. data/app/pb_kits/playbook/pb_timeline/_timeline.scss +30 -30
  33. data/dist/chunks/{_typeahead-DCD3NrAk.js → _typeahead-TN5aDUj9.js} +1 -1
  34. data/dist/chunks/_weekday_stacked-BcnpLG66.js +45 -0
  35. data/dist/chunks/vendor.js +1 -1
  36. data/dist/playbook-doc.js +1 -1
  37. data/dist/playbook-rails-react-bindings.js +1 -1
  38. data/dist/playbook-rails.js +1 -1
  39. data/dist/playbook.css +1 -1
  40. data/lib/playbook/version.rb +1 -1
  41. metadata +10 -4
  42. data/dist/chunks/_weekday_stacked-aUbMqVRj.js +0 -45
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4a05fe2789773e3d82f2ff7b8db22a31c699e6cc1c306daf4d2478a38d949f5b
4
- data.tar.gz: c008e59419462f277e0faf29b8592cf61c786a16b84e55795925f2efedf8c82e
3
+ metadata.gz: d9db2c07365905ebc29fe49d03e07ebffa52ad662459427fe22a5dfadf4b9131
4
+ data.tar.gz: 4397ab9e0058cebebc804b899f98eeeb84486b502380360135adbbc12e264559
5
5
  SHA512:
6
- metadata.gz: 3b3268e3ba86c6f6f292aade7f2401f9e187995ac3ec5854ac81307bb24745ed1721aa6447d9f755c6f724519ac30de7c203762ee92b17aeebfe0440aea5d83c
7
- data.tar.gz: 850c027e756601d977e7b919a2d19b5705a433878b7c2248a3b5fae924cf24311f1781e3d3ae41ef158f0fce893d751040ef292d21e9d8a5f74284961357ef3c
6
+ metadata.gz: ec13a85caeecebf850170e19c3eba654ad56ea06d6b186d8acfa9586dd4ed1ba0a97f4172cbc9fb60d675401acad7903f1f90f04a374613df457762bf1e51f9c
7
+ data.tar.gz: 9efa5d87d16f447ffe8011ebbcfbfa9133eb276758db161fc5097e698f53f55f4bf68a289b546902719f8adff35cf5f62ab211067027f72101b47df6dadabcb9
@@ -1,3 +1,3 @@
1
- `sortControl` is an optional prop that can be used to gain greater control over the sort state of the Advanced Table. Tanstack handles sort itself, however it does provide for a way to handle the state manually if needed. Usecases for this include needing to store the sort state so it persists on page reload, set an initial sort state, etc.
1
+ `sortControl` is an optional prop that can be used to gain greater control over the sort state of the Advanced Table. Tanstack handles sort itself, however it does provide for a way to handle the state manually if needed. Usecases for this include needing to store the sort state so it persists on page reload, set an initial sort state, etc.
2
2
 
3
- The sort state must be an object with a single key/value pair, with the key being "desc" and the value being a boolean. The default for sort directino is `desc: true`.
3
+ The sort state must be an object with a single key/value pair, with the key being "desc" and the value being a boolean. The default for sort direction is `desc: true`.
@@ -1,6 +1,5 @@
1
- <%= content_tag(object.tag,
2
- object.tag == "button" ? object.options : object.link_options,
3
- **combined_html_options) do %>
1
+ <%= pb_content_tag(object.tag,
2
+ object.tag == "button" ? object.options : object.link_options) do %>
4
3
  <% if object.variant === "reaction" %>
5
4
  <% if icon && object.valid_emoji(object.icon) %>
6
5
  <%= pb_rails("flex", props:{ align: "center" }) do %>
@@ -1,9 +1,4 @@
1
- <%= content_tag(:label, aria: object.aria,
2
- id: object.id,
3
- data: object.data,
4
- class: object.classname,
5
- **combined_html_options
6
- ) do %>
1
+ <%= pb_content_tag(:label) do %>
7
2
  <%= content.presence || object.input %>
8
3
  <% if object.indeterminate %>
9
4
  <span data-pb-checkbox-icon-span="true" class="pb_checkbox_indeterminate">
@@ -1,9 +1,5 @@
1
- <%= content_tag(:div,
2
- aria: object.aria,
3
- class: object.classname + object.error_class,
4
- data: object.data,
5
- id: object.id,
6
- **combined_html_options) do %>
1
+ <%= pb_content_tag(:div,
2
+ class: object.classname + object.error_class) do %>
7
3
  <div class="input_wrapper">
8
4
  <% if content.present? %>
9
5
  <%= content %>
@@ -68,18 +64,30 @@
68
64
  </div>
69
65
 
70
66
  <%= javascript_tag do %>
71
- window.addEventListener("DOMContentLoaded", () => {
72
- datePickerHelper(<%= object.date_picker_config %>, "<%= object.scroll_container %>")
67
+ (function() {
68
+ const loadDatePicker = () => {
69
+ datePickerHelper(<%= object.date_picker_config %>, "<%= object.scroll_container %>")
73
70
 
74
- if (<%= object.selection_type == "quickpick" %>) {
75
- document.getElementById("<%= object.picker_id %>").addEventListener("change", ({ target }) => {
76
- const startDate = document.getElementById("<%= object.start_date_id %>")
77
- const endDate = document.getElementById("<%= object.end_date_id %>")
78
- const splittedValue = target.value.split(" to ")
79
- startDate.value = splittedValue[0]
80
- endDate.value = splittedValue[1] ? splittedValue[1] : splittedValue[0]
71
+ if (<%= object.selection_type == "quickpick" %>) {
72
+ document.getElementById("<%= object.picker_id %>").addEventListener("change", ({ target }) => {
73
+ const startDate = document.getElementById("<%= object.start_date_id %>")
74
+ const endDate = document.getElementById("<%= object.end_date_id %>")
75
+ const splittedValue = target.value.split(" to ")
76
+ startDate.value = splittedValue[0]
77
+ endDate.value = splittedValue[1] ? splittedValue[1] : splittedValue[0]
78
+ })
79
+ }
80
+ }
81
+
82
+ window.addEventListener("DOMContentLoaded", () => {
83
+ loadDatePicker()
84
+ })
85
+
86
+ if (<%= !object.custom_event_type.empty? %>) {
87
+ window.addEventListener("<%= object.custom_event_type %>", () => {
88
+ loadDatePicker()
81
89
  })
82
90
  }
83
- })
91
+ })()
84
92
  <% end %>
85
93
  <% end %>
@@ -73,6 +73,8 @@ module Playbook
73
73
  default: false
74
74
  prop :year_range, type: Playbook::Props::Array,
75
75
  default: [1900, 2100]
76
+ prop :custom_event_type, type: Playbook::Props::String,
77
+ default: ""
76
78
 
77
79
  def classname
78
80
  default_margin_bottom = margin_bottom.present? ? "" : " mb_sm"
@@ -0,0 +1,13 @@
1
+ <%= pb_rails("date_picker", props: { picker_id: "date-picker-turbo-frames", custom_event_type: "datePickerLoader" }) %>
2
+ <%= pb_rails("button", props: { id: "date-picker-loader", text: "Click Event Simulation" }) %>
3
+
4
+ <script>
5
+ document.getElementById("date-picker-loader").addEventListener("click", () => {
6
+ window.document.dispatchEvent(
7
+ new CustomEvent("datePickerLoader", {
8
+ bubbles: true,
9
+ })
10
+ )
11
+ console.log("Event 'datePickerLoader' dispatched - in a real implementation, this event would trigger the date picker's reinitialization through the custom_event_type prop connection")
12
+ })
13
+ </script>
@@ -0,0 +1,3 @@
1
+ The `custom_event_type` prop allows you to specify a custom event that will trigger the date picker's initialization, in addition to the default initialization on `DOMContentLoaded`. This is particularly useful in dynamic contexts like Turbo Frame updates where you need to reinitialize the date picker after new content loads. For Turbo integration, use `custom_event_type: "turbo:before-render"` to ensure the date picker properly reinitializes after Turbo navigation events.
2
+
3
+ In this example, we demonstrate the setup pattern by connecting a button to dispatch a "datePickerLoader" event - while the date picker's event listener is properly configured through the `custom_event_type` prop, this demo only logs the event dispatch to the console to illustrate the connection structure.
@@ -28,6 +28,7 @@ examples:
28
28
  - date_picker_time: Time Selection
29
29
  - date_picker_positions: Custom Positions
30
30
  - date_picker_positions_element: Custom Position (based on element)
31
+ - date_picker_turbo_frames: Within Turbo Frames
31
32
 
32
33
  react:
33
34
  - date_picker_default: Default
@@ -6,14 +6,8 @@
6
6
 
7
7
  ] %>
8
8
 
9
- <%= pb_rails("draggable", props: {initial_items: initial_items}) do %>
10
- <%= pb_rails("draggable/draggable_container") do %>
11
- <%= pb_rails("list", props: {ordered: false}) do %>
12
- <% initial_items.each do |item| %>
13
- <%= pb_rails("draggable/draggable_item", props:{drag_id: item[:id]}) do %>
14
- <%= pb_rails("list/item") do %><%= item[:name] %><% end %>
15
- <% end %>
16
- <% end %>
17
- <% end %>
9
+ <%= pb_rails("list", props: { enable_drag: true, items: initial_items }) do %>
10
+ <% initial_items.each do |item| %>
11
+ <%= pb_rails("list/item", props:{drag_id: item[:id]}) do %><%= item[:name] %><% end %>
18
12
  <% end %>
19
13
  <% end %>
@@ -0,0 +1,5 @@
1
+ For a simplified version of the Draggable API for the List kit, you can do the following:
2
+
3
+ The List kit is optimized to work with the draggable kit. To enable drag, use the `enable_drag` prop on List kit with an array of the included items AND `drag_id` prop on ListItems. You will also need to include the `items` prop containing your array of listed items for the Draggable API.
4
+
5
+ An additional optional boolean prop (set to true by default) of `drag_handle` is also available on ListItem kit to show the drag handle icon.
@@ -1 +1 @@
1
- For the `subtle` variant, it is recommended that you set the `Separators` prop to `false` to remove the separator lines between the options for a more cleaner look.
1
+ For the `subtle` variant, it is recommended that you set the `Separators` prop to `false` to remove the separator lines between the options for a cleaner look.
@@ -1,19 +1,14 @@
1
- <%= content_tag(:div,
2
- aria: object.aria,
3
- class: object.classname,
4
- data: object.data,
5
- id: object.id,
6
- **combined_html_options) do %>
1
+ <%= pb_content_tag do %>
7
2
  <% if object.label.present? %>
8
3
  <%= pb_rails("caption", props: {text: object.label, margin_bottom:"xs"}) %>
9
4
  <% end %>
10
5
  <div class="dropdown_wrapper<%= error_class %>" style="position: relative">
11
- <input
12
- data-default-value="<%= input_default_value %>"
13
- id="dropdown-selected-option"
14
- name="<%= object.name %>"
15
- style="display: none"
16
- <%= object.required ? "required" : ""%>
6
+ <input
7
+ data-default-value="<%= input_default_value %>"
8
+ id="dropdown-selected-option"
9
+ name="<%= object.name %>"
10
+ style="display: none"
11
+ <%= object.required ? "required" : ""%>
17
12
  />
18
13
  <% if content.present? %>
19
14
  <%= content.presence %>
@@ -1,20 +1,15 @@
1
- <%= content_tag(:div,
2
- aria: object.aria,
3
- class: object.classname,
4
- data: object.data,
5
- id: object.id,
6
- **combined_html_options) do %>
1
+ <%= pb_content_tag do %>
7
2
  <%= pb_rails("list", props: {ordered: false, borderless: false}) do %>
8
- <% if content.present? %>
3
+ <% if content.present? %>
9
4
  <%= content.presence %>
10
- <% else %>
5
+ <% else %>
11
6
  <%= pb_rails("list/item", props: {
12
- display: "flex",
13
- justify_content: "center",
14
- padding:"xs",
15
- }) do %>
7
+ display: "flex",
8
+ justify_content: "center",
9
+ padding:"xs",
10
+ }) do %>
16
11
  <%= pb_rails("body", props: {text: "No option"}) %>
17
12
  <% end %>
18
13
  <% end %>
19
- <% end %>
20
- <% end %>
14
+ <% end %>
15
+ <% end %>
@@ -1,15 +1,10 @@
1
- <%= content_tag(:div,
2
- aria: object.aria,
3
- class: object.classname,
4
- data: object.data,
5
- id: object.option[:id],
6
- **combined_html_options) do %>
1
+ <%= pb_content_tag(:div, id: object.option[:id]) do %>
7
2
  <%= pb_rails("list/item", props: {
8
- display: "flex",
9
- justify_content: "center",
10
- padding:"none",
3
+ display: "flex",
4
+ justify_content: "center",
5
+ padding:"none",
11
6
  cursor: "pointer"
12
- }) do %>
7
+ }) do %>
13
8
  <div class="dropdown_option_wrapper">
14
9
  <% if content.present? %>
15
10
  <%= content.presence %>
@@ -18,4 +13,4 @@
18
13
  <% end %>
19
14
  </div>
20
15
  <% end %>
21
- <% end %>
16
+ <% end %>
@@ -1,21 +1,16 @@
1
- <%= content_tag(:div,
2
- aria: object.aria,
3
- class: object.classname,
4
- data: object.data,
5
- id: object.id,
6
- **combined_html_options) do %>
1
+ <%= pb_content_tag do %>
7
2
  <% if content.present? %>
8
3
  <div style="display: inline-block" tabindex="0" data-dropdown-custom-trigger>
9
4
  <%= content.presence %>
10
5
  </div>
11
6
  <% else %>
12
7
  <%= pb_rails("flex", props: {
13
- align: "center",
14
- border_radius:"lg",
15
- classname: object.trigger_wrapper_classes,
16
- cursor: "pointer",
17
- justify: "between",
18
- padding_x:"sm",
8
+ align: "center",
9
+ border_radius:"lg",
10
+ classname: object.trigger_wrapper_classes,
11
+ cursor: "pointer",
12
+ justify: "between",
13
+ padding_x:"sm",
19
14
  padding_y:"xs",
20
15
  html_options: {tabindex:"0"}
21
16
  }) do %>
@@ -23,7 +18,7 @@
23
18
  <%= pb_rails("flex", props: {align: "center"}) do %>
24
19
  <% if object.custom_display.present? %>
25
20
  <%= pb_rails("flex", props: {align: "center"}) do %>
26
- <div id="dropdown_trigger_custom_display" style="display: none;">
21
+ <div id="dropdown_trigger_custom_display" style="display: none;">
27
22
  <%= object.custom_display %>
28
23
  </div>
29
24
  <%= pb_rails("body", props: {text: object.default_display_placeholder, id: "dropdown_trigger_display"}) %>
@@ -40,4 +35,3 @@
40
35
  <% end %>
41
36
  <% end %>
42
37
  <% end %>
43
-
@@ -1,10 +1,32 @@
1
- <%= content_tag(:li,
2
- aria: object.aria,
3
- class: object.classname,
4
- data: object.data,
5
- id: object.id,
6
- tabindex: object.tabindex,
7
- **combined_html_options
8
- ) do %>
1
+ <% if object.draggable? %>
2
+ <%= pb_rails("draggable/draggable_item", props:{drag_id: object.drag_id}) do %>
3
+ <%= content_tag(:li,
4
+ aria: object.aria,
5
+ class: object.classname,
6
+ data: object.data,
7
+ id: object.id,
8
+ tabindex: object.tabindex,
9
+ **combined_html_options
10
+ ) do %>
11
+ <% if object.drag_handle %>
12
+ <span style="vertical-align: middle;">
13
+ <%= pb_rails("body") do %>
14
+ <svg width="auto" height="auto" viewBox="0 0 31 25" fill="none" xmlns="http://www.w3.org/2000/svg" color="currentColor" class="pb_custom_icon svg-inline--fa vertical_align_middle svg_fw"><path d="M12.904 6.355a1.48 1.48 0 01-1.5-1.5c0-.796.656-1.5 1.5-1.5.797 0 1.5.704 1.5 1.5 0 .844-.703 1.5-1.5 1.5zm0 7.5a1.48 1.48 0 01-1.5-1.5c0-.796.656-1.5 1.5-1.5.797 0 1.5.704 1.5 1.5 0 .844-.703 1.5-1.5 1.5zm1.5 6c0 .844-.703 1.5-1.5 1.5a1.48 1.48 0 01-1.5-1.5c0-.796.656-1.5 1.5-1.5.797 0 1.5.704 1.5 1.5zm4.5-13.5a1.48 1.48 0 01-1.5-1.5c0-.796.657-1.5 1.5-1.5.797 0 1.5.704 1.5 1.5 0 .844-.703 1.5-1.5 1.5zm1.5 6c0 .844-.703 1.5-1.5 1.5a1.48 1.48 0 01-1.5-1.5c0-.796.657-1.5 1.5-1.5.797 0 1.5.704 1.5 1.5zm-1.5 9a1.48 1.48 0 01-1.5-1.5c0-.796.657-1.5 1.5-1.5.797 0 1.5.704 1.5 1.5 0 .844-.703 1.5-1.5 1.5z" fill="#242B42"></path></svg>
15
+ <% end %>
16
+ </span>
17
+ <% end %>
18
+ <%= content.presence %>
19
+ <% end %>
20
+ <% end %>
21
+ <% else %>
22
+ <%= content_tag(:li,
23
+ aria: object.aria,
24
+ class: object.classname,
25
+ data: object.data,
26
+ id: object.id,
27
+ tabindex: object.tabindex,
28
+ **combined_html_options
29
+ ) do %>
9
30
  <%= content.presence %>
31
+ <% end %>
10
32
  <% end %>
@@ -3,11 +3,18 @@
3
3
  module Playbook
4
4
  module PbList
5
5
  class Item < Playbook::KitBase
6
+ prop :drag_handle, type: Playbook::Props::Boolean,
7
+ default: true
8
+ prop :drag_id, type: Playbook::Props::String
6
9
  prop :tabindex
7
10
 
8
11
  def classname
9
12
  generate_classname("pb_item_kit")
10
13
  end
14
+
15
+ def draggable?
16
+ drag_id.present?
17
+ end
11
18
  end
12
19
  end
13
20
  end
@@ -1,13 +1,33 @@
1
- <%= content_tag(:div, class: object.list_classname) do %>
2
- <%= content_tag(:"#{object.ordered_class}",
3
- aria: object.aria,
4
- class: object.classname,
5
- data: object.data,
6
- id: object.id,
7
- role: object.role,
8
- tabindex: object.tabindex,
9
- **combined_html_options
10
- ) do %>
11
- <%= content.presence %>
1
+ <% if object.enable_drag %>
2
+ <%= pb_rails("draggable", props: {initial_items: object.items}) do %>
3
+ <%= pb_rails("draggable/draggable_container") do %>
4
+ <%= content_tag(:div, class: object.list_classname) do %>
5
+ <%= content_tag(:"#{object.ordered_class}",
6
+ aria: object.aria,
7
+ class: object.classname,
8
+ data: object.data,
9
+ id: object.id,
10
+ role: object.role,
11
+ tabindex: object.tabindex,
12
+ **combined_html_options
13
+ ) do %>
14
+ <%= content.presence %>
15
+ <% end %>
16
+ <% end %>
17
+ <% end %>
18
+ <% end %>
19
+ <% else %>
20
+ <%= content_tag(:div, class: object.list_classname) do %>
21
+ <%= content_tag(:"#{object.ordered_class}",
22
+ aria: object.aria,
23
+ class: object.classname,
24
+ data: object.data,
25
+ id: object.id,
26
+ role: object.role,
27
+ tabindex: object.tabindex,
28
+ **combined_html_options
29
+ ) do %>
30
+ <%= content.presence %>
31
+ <% end %>
12
32
  <% end %>
13
33
  <% end %>
@@ -7,6 +7,10 @@ module Playbook
7
7
  default: false
8
8
  prop :dark, type: Playbook::Props::Boolean,
9
9
  default: false
10
+ prop :enable_drag, type: Playbook::Props::Boolean,
11
+ default: false
12
+ prop :items, type: Playbook::Props::Array,
13
+ default: []
10
14
  prop :layout, type: Playbook::Props::Enum,
11
15
  values: ["left", "right", ""],
12
16
  default: ""
@@ -12,6 +12,7 @@ type LoadingInlineProps = {
12
12
  aria?: { [key: string]: string },
13
13
  className?: string,
14
14
  data?: { [key: string]: string },
15
+ dark?: boolean,
15
16
  htmlOptions?: {[key: string]: string | number | boolean | (() => void)},
16
17
  id?: string,
17
18
  text?: string,
@@ -23,6 +24,7 @@ const LoadingInline = (props: LoadingInlineProps) => {
23
24
  aria = {},
24
25
  className,
25
26
  data = {},
27
+ dark = false,
26
28
  htmlOptions = {},
27
29
  id,
28
30
  text = ' Loading',
@@ -45,7 +47,10 @@ const LoadingInline = (props: LoadingInlineProps) => {
45
47
  className={classes}
46
48
  id={id}
47
49
  >
48
- <Body color="light">
50
+ <Body
51
+ color="light"
52
+ dark={dark}
53
+ >
49
54
  <Icon
50
55
  aria={{ label: 'loading icon' }}
51
56
  fixedWidth
@@ -35,6 +35,7 @@ type PhoneNumberInputProps = {
35
35
  preferredCountries?: string[],
36
36
  required?: boolean,
37
37
  value?: string,
38
+ formatAsYouType?: boolean,
38
39
  }
39
40
 
40
41
  enum ValidationError {
@@ -87,6 +88,7 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.MutableRefOb
87
88
  required = false,
88
89
  preferredCountries = [],
89
90
  value = "",
91
+ formatAsYouType = false,
90
92
  } = props
91
93
 
92
94
  const ariaProps = buildAriaProps(aria)
@@ -99,8 +101,8 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.MutableRefOb
99
101
  )
100
102
 
101
103
  const inputRef = useRef<HTMLInputElement>()
104
+ const itiRef = useRef<any>(null);
102
105
  const [inputValue, setInputValue] = useState(value)
103
- const [itiInit, setItiInit] = useState<any>()
104
106
  const [error, setError] = useState(props.error)
105
107
  const [dropDownIsOpen, setDropDownIsOpen] = useState(false)
106
108
  const [selectedData, setSelectedData] = useState()
@@ -130,8 +132,12 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.MutableRefOb
130
132
  }
131
133
  })
132
134
 
135
+ const unformatNumber = (formattedNumber: any) => {
136
+ return formattedNumber.replace(/\D/g, "")
137
+ }
138
+
133
139
  const showFormattedError = (reason = '') => {
134
- const countryName = itiInit.getSelectedCountryData().name
140
+ const countryName = itiRef.current.getSelectedCountryData().name
135
141
  const reasonText = reason.length > 0 ? ` (${reason})` : ''
136
142
  setError(`Invalid ${countryName} phone number${reasonText}`)
137
143
  return true
@@ -189,12 +195,12 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.MutableRefOb
189
195
  }
190
196
 
191
197
  const validateErrors = () => {
192
- if (itiInit) isValid(itiInit.isValidNumber())
193
- if (validateOnlyNumbers(itiInit)) return
194
- if (validateTooLongNumber(itiInit)) return
195
- if (validateTooShortNumber(itiInit)) return
196
- if (validateUnhandledError(itiInit)) return
197
- if (validateMissingAreaCode(itiInit)) return
198
+ if (itiRef.current) isValid(itiRef.current.isValidNumber())
199
+ if (validateOnlyNumbers(itiRef.current)) return
200
+ if (validateTooLongNumber(itiRef.current)) return
201
+ if (validateTooShortNumber(itiRef.current)) return
202
+ if (validateUnhandledError(itiRef.current)) return
203
+ if (validateMissingAreaCode(itiRef.current)) return
198
204
  }
199
205
 
200
206
  const getCurrentSelectedData = (itiInit: any, inputValue: string) => {
@@ -203,10 +209,16 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.MutableRefOb
203
209
 
204
210
  const handleOnChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
205
211
  setInputValue(evt.target.value)
206
- const phoneNumberData = getCurrentSelectedData(itiInit, evt.target.value)
212
+ let phoneNumberData
213
+ if (formatAsYouType) {
214
+ const formattedPhoneNumberData = getCurrentSelectedData(itiRef.current, evt.target.value)
215
+ phoneNumberData = {...formattedPhoneNumberData, number: unformatNumber(formattedPhoneNumberData.number)}
216
+ } else {
217
+ phoneNumberData = getCurrentSelectedData(itiRef.current, evt.target.value)
218
+ }
207
219
  setSelectedData(phoneNumberData)
208
220
  onChange(phoneNumberData)
209
- isValid(itiInit.isValidNumber())
221
+ isValid(itiRef.current.isValidNumber())
210
222
  }
211
223
 
212
224
  // Separating Concerns as React Docs Recommend
@@ -230,9 +242,11 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.MutableRefOb
230
242
  onlyCountries,
231
243
  countrySearch: false,
232
244
  fixDropdownWidth: false,
233
- formatAsYouType: false,
245
+ formatAsYouType: formatAsYouType,
234
246
  })
235
247
 
248
+ itiRef.current = telInputInit;
249
+
236
250
  inputRef.current.addEventListener("countrychange", (evt: Event) => {
237
251
  const phoneNumberData = getCurrentSelectedData(telInputInit, (evt.target as HTMLInputElement).value)
238
252
  setSelectedData(phoneNumberData)
@@ -243,7 +257,11 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.MutableRefOb
243
257
  inputRef.current.addEventListener("open:countrydropdown", () => setDropDownIsOpen(true))
244
258
  inputRef.current.addEventListener("close:countrydropdown", () => setDropDownIsOpen(false))
245
259
 
246
- setItiInit(telInputInit)
260
+ if (formatAsYouType) {
261
+ inputRef.current?.addEventListener("input", (evt) => {
262
+ handleOnChange(evt as unknown as React.ChangeEvent<HTMLInputElement>);
263
+ });
264
+ }
247
265
  }, [])
248
266
 
249
267
  let textInputProps: {[key: string]: any} = {
@@ -0,0 +1,15 @@
1
+ <%= pb_rails("phone_number_input", props: {
2
+ id: "phone_number_input",
3
+ format_as_you_type: true
4
+ }) %>
5
+
6
+ <%= pb_rails("button", props: {id: "clickable", text: "Save Phone Number"}) %>
7
+
8
+ <%= javascript_tag do %>
9
+ document.querySelector('#clickable').addEventListener('click', () => {
10
+ const formattedPhoneNumber = document.querySelector('#phone_number_input').value
11
+ const unformattedPhoneNumber = formattedPhoneNumber.replace(/\D/g, "")
12
+
13
+ alert(`Formatted: ${formattedPhoneNumber}. Unformatted: ${unformattedPhoneNumber}`)
14
+ })
15
+ <% end %>
@@ -0,0 +1,24 @@
1
+ import React, { useState } from "react";
2
+ import { PhoneNumberInput, Body } from "playbook-ui";
3
+
4
+ const PhoneNumberInputFormat = (props) => {
5
+ const [phoneNumber, setPhoneNumber] = useState("");
6
+
7
+ const handleOnChange = ({ number }) => {
8
+ setPhoneNumber(number);
9
+ };
10
+
11
+ return (
12
+ <>
13
+ <PhoneNumberInput
14
+ formatAsYouType
15
+ id="format"
16
+ onChange={handleOnChange}
17
+ {...props}
18
+ />
19
+ {phoneNumber && <Body>Unformatted number: {phoneNumber}</Body>}
20
+ </>
21
+ );
22
+ };
23
+
24
+ export default PhoneNumberInputFormat;
@@ -0,0 +1 @@
1
+ NOTE: the `number` in the React `onChange` event will not include formatting (no spaces, dashes, and parentheses). For Rails, the `value` will include formatting and its value must be sanitized manually.
@@ -8,10 +8,12 @@ examples:
8
8
  - phone_number_input_validation: Form Validation
9
9
  - phone_number_input_clear_field: Clearing the Input Field
10
10
  - phone_number_input_access_input_element: Accessing the Input Element
11
+ - phone_number_input_format: Format as You Type
11
12
 
12
13
  rails:
13
14
  - phone_number_input_default: Default
14
15
  - phone_number_input_preferred_countries: Preferred Countries
15
16
  - phone_number_input_initial_country: Initial Country
16
17
  - phone_number_input_only_countries: Limited Countries
17
- - phone_number_input_validation: Form Validation
18
+ - phone_number_input_validation: Form Validation
19
+ - phone_number_input_format: Format as You Type
@@ -5,3 +5,4 @@ export { default as PhoneNumberInputOnlyCountries } from './_phone_number_input_
5
5
  export { default as PhoneNumberInputValidation } from './_phone_number_input_validation'
6
6
  export { default as PhoneNumberInputClearField } from './_phone_number_input_clear_field'
7
7
  export { default as PhoneNumberInputAccessInputElement } from './_phone_number_input_access_input_element'
8
+ export { default as PhoneNumberInputFormat } from './_phone_number_input_format'
@@ -21,6 +21,8 @@ module Playbook
21
21
  default: ""
22
22
  prop :value, type: Playbook::Props::String,
23
23
  default: ""
24
+ prop :format_as_you_type, type: Playbook::Props::Boolean,
25
+ default: false
24
26
 
25
27
  def classname
26
28
  generate_classname("pb_phone_number_input")
@@ -32,6 +34,7 @@ module Playbook
32
34
  dark: dark,
33
35
  disabled: disabled,
34
36
  error: error,
37
+ formatAsYouType: format_as_you_type,
35
38
  initialCountry: initial_country,
36
39
  label: label,
37
40
  name: name,