playbook_ui 17.2.0.pre.rc.1 → 17.2.0.pre.rc.2

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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_form/docs/_description.md +2 -5
  3. data/app/pb_kits/playbook/pb_form/docs/_footer.md +2 -1
  4. data/app/pb_kits/playbook/pb_form/docs/_form_default_values.html.erb +183 -0
  5. data/app/pb_kits/playbook/pb_form/docs/_form_default_values.md +3 -0
  6. data/app/pb_kits/playbook/pb_form/docs/_form_defaults.html.erb +180 -0
  7. data/app/pb_kits/playbook/pb_form/docs/_form_defaults.md +1 -0
  8. data/app/pb_kits/playbook/pb_form/docs/_form_docs_demo.js +226 -0
  9. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_vs.html.erb +36 -0
  10. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_vs.md +1 -0
  11. data/app/pb_kits/playbook/pb_form/docs/_form_loading.html.erb +39 -0
  12. data/app/pb_kits/playbook/pb_form/docs/_form_loading.md +1 -0
  13. data/app/pb_kits/playbook/pb_form/docs/_form_multi_column.html.erb +60 -0
  14. data/app/pb_kits/playbook/pb_form/docs/_form_multi_column.md +3 -0
  15. data/app/pb_kits/playbook/pb_form/docs/_form_validation.html.erb +224 -0
  16. data/app/pb_kits/playbook/pb_form/docs/_form_validation.md +27 -0
  17. data/app/pb_kits/playbook/pb_form/docs/_sample_data.ruby +148 -0
  18. data/app/pb_kits/playbook/pb_form/docs/example.yml +6 -6
  19. data/app/pb_kits/playbook/pb_form/docs/snippets/comparison_playbook.txt +7 -0
  20. data/app/pb_kits/playbook/pb_form/docs/snippets/comparison_rails.txt +5 -0
  21. data/app/pb_kits/playbook/pb_form/docs/snippets/default_values_checkbox.txt +5 -0
  22. data/app/pb_kits/playbook/pb_form/docs/snippets/default_values_collection_select.txt +8 -0
  23. data/app/pb_kits/playbook/pb_form/docs/snippets/default_values_date_picker.txt +5 -0
  24. data/app/pb_kits/playbook/pb_form/docs/snippets/default_values_dropdown.txt +11 -0
  25. data/app/pb_kits/playbook/pb_form/docs/snippets/default_values_email_field.txt +1 -0
  26. data/app/pb_kits/playbook/pb_form/docs/snippets/default_values_multi_level_select.txt +25 -0
  27. data/app/pb_kits/playbook/pb_form/docs/snippets/default_values_multi_select.txt +11 -0
  28. data/app/pb_kits/playbook/pb_form/docs/snippets/default_values_number_field.txt +1 -0
  29. data/app/pb_kits/playbook/pb_form/docs/snippets/default_values_password_field.txt +1 -0
  30. data/app/pb_kits/playbook/pb_form/docs/snippets/default_values_phone_number.txt +4 -0
  31. data/app/pb_kits/playbook/pb_form/docs/snippets/default_values_search_field.txt +1 -0
  32. data/app/pb_kits/playbook/pb_form/docs/snippets/default_values_select.txt +3 -0
  33. data/app/pb_kits/playbook/pb_form/docs/snippets/default_values_star_rating.txt +2 -0
  34. data/app/pb_kits/playbook/pb_form/docs/snippets/default_values_text_field.txt +1 -0
  35. data/app/pb_kits/playbook/pb_form/docs/snippets/default_values_textarea.txt +1 -0
  36. data/app/pb_kits/playbook/pb_form/docs/snippets/default_values_time_picker.txt +1 -0
  37. data/app/pb_kits/playbook/pb_form/docs/snippets/default_values_time_zone_select.txt +3 -0
  38. data/app/pb_kits/playbook/pb_form/docs/snippets/default_values_typeahead.txt +13 -0
  39. data/app/pb_kits/playbook/pb_form/docs/snippets/default_values_typeahead_default.txt +13 -0
  40. data/app/pb_kits/playbook/pb_form/docs/snippets/default_values_url_field.txt +1 -0
  41. data/app/pb_kits/playbook/pb_form/docs/snippets/defaults_checkbox.txt +4 -0
  42. data/app/pb_kits/playbook/pb_form/docs/snippets/defaults_collection_select.txt +7 -0
  43. data/app/pb_kits/playbook/pb_form/docs/snippets/defaults_date_picker.txt +1 -0
  44. data/app/pb_kits/playbook/pb_form/docs/snippets/defaults_dropdown.txt +7 -0
  45. data/app/pb_kits/playbook/pb_form/docs/snippets/defaults_email_field.txt +1 -0
  46. data/app/pb_kits/playbook/pb_form/docs/snippets/defaults_multi_level_select.txt +19 -0
  47. data/app/pb_kits/playbook/pb_form/docs/snippets/defaults_multi_select.txt +7 -0
  48. data/app/pb_kits/playbook/pb_form/docs/snippets/defaults_number_field.txt +1 -0
  49. data/app/pb_kits/playbook/pb_form/docs/snippets/defaults_password_field.txt +1 -0
  50. data/app/pb_kits/playbook/pb_form/docs/snippets/defaults_phone_number.txt +1 -0
  51. data/app/pb_kits/playbook/pb_form/docs/snippets/defaults_scoped_fields.txt +5 -0
  52. data/app/pb_kits/playbook/pb_form/docs/snippets/defaults_search_field.txt +1 -0
  53. data/app/pb_kits/playbook/pb_form/docs/snippets/defaults_select.txt +2 -0
  54. data/app/pb_kits/playbook/pb_form/docs/snippets/defaults_star_rating.txt +2 -0
  55. data/app/pb_kits/playbook/pb_form/docs/snippets/defaults_text_field.txt +1 -0
  56. data/app/pb_kits/playbook/pb_form/docs/snippets/defaults_textarea.txt +1 -0
  57. data/app/pb_kits/playbook/pb_form/docs/snippets/defaults_time_picker.txt +1 -0
  58. data/app/pb_kits/playbook/pb_form/docs/snippets/defaults_time_zone_select.txt +3 -0
  59. data/app/pb_kits/playbook/pb_form/docs/snippets/defaults_typeahead.txt +8 -0
  60. data/app/pb_kits/playbook/pb_form/docs/snippets/defaults_typeahead_default.txt +13 -0
  61. data/app/pb_kits/playbook/pb_form/docs/snippets/defaults_url_field.txt +1 -0
  62. data/app/pb_kits/playbook/pb_form/docs/snippets/multi_column.txt +14 -0
  63. data/app/pb_kits/playbook/pb_form/docs/snippets/validation_checkbox.txt +4 -0
  64. data/app/pb_kits/playbook/pb_form/docs/snippets/validation_collection_select.txt +12 -0
  65. data/app/pb_kits/playbook/pb_form/docs/snippets/validation_date_picker.txt +6 -0
  66. data/app/pb_kits/playbook/pb_form/docs/snippets/validation_dropdown.txt +7 -0
  67. data/app/pb_kits/playbook/pb_form/docs/snippets/validation_email_field.txt +5 -0
  68. data/app/pb_kits/playbook/pb_form/docs/snippets/validation_multi_level_select.txt +25 -0
  69. data/app/pb_kits/playbook/pb_form/docs/snippets/validation_multi_select.txt +11 -0
  70. data/app/pb_kits/playbook/pb_form/docs/snippets/validation_number_field.txt +5 -0
  71. data/app/pb_kits/playbook/pb_form/docs/snippets/validation_password_field.txt +5 -0
  72. data/app/pb_kits/playbook/pb_form/docs/snippets/validation_phone_number.txt +4 -0
  73. data/app/pb_kits/playbook/pb_form/docs/snippets/validation_search_field.txt +8 -0
  74. data/app/pb_kits/playbook/pb_form/docs/snippets/validation_select.txt +7 -0
  75. data/app/pb_kits/playbook/pb_form/docs/snippets/validation_star_rating.txt +2 -0
  76. data/app/pb_kits/playbook/pb_form/docs/snippets/validation_text_field.txt +5 -0
  77. data/app/pb_kits/playbook/pb_form/docs/snippets/validation_textarea.txt +1 -0
  78. data/app/pb_kits/playbook/pb_form/docs/snippets/validation_time_picker.txt +5 -0
  79. data/app/pb_kits/playbook/pb_form/docs/snippets/validation_time_zone_select.txt +7 -0
  80. data/app/pb_kits/playbook/pb_form/docs/snippets/validation_typeahead.txt +14 -0
  81. data/app/pb_kits/playbook/pb_form/docs/snippets/validation_typeahead_default.txt +14 -0
  82. data/app/pb_kits/playbook/pb_form/docs/snippets/validation_url_field.txt +5 -0
  83. data/lib/playbook/forms/builder/checkbox_field.rb +1 -1
  84. data/lib/playbook/forms/builder/dropdown_field.rb +1 -1
  85. data/lib/playbook/forms/builder/star_rating_field.rb +1 -1
  86. data/lib/playbook/version.rb +1 -1
  87. metadata +80 -11
  88. data/app/pb_kits/playbook/pb_form/docs/_form_form_with.html.erb +0 -175
  89. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_loading.html.erb +0 -39
  90. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_loading.md +0 -1
  91. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate.html.erb +0 -176
  92. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate.md +0 -1
  93. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_validation_msg.html.erb +0 -90
  94. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_validation_msg.md +0 -13
  95. data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.html.erb +0 -173
  96. data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.md +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0cfdd29dff7aa690ffa80f529b880a467df847de2897b20978f15aee42493f9
4
- data.tar.gz: efe2a6be9fa5b594a9ec8cf3dba4085b7bbc3b6e0ce181a60710b0e760165640
3
+ metadata.gz: 672b9baf425e982f8d59fe03e71b4211cd715c2975de0f0c3a30895201d6b780
4
+ data.tar.gz: ed5012fd921338fdcc088c046e07e0a44f54b11f61e9921309958307b324fdfa
5
5
  SHA512:
6
- metadata.gz: d1000f4e35e8953cbeff95dba2a55f2373c594320e00fc58ac8436eded89a5eb9f5383c85600bf3d747d71148182841ab2a1c5f4124581b5b03596813ea0b353
7
- data.tar.gz: '068ba2587e7d290077532454b72d6b6cddefa2d919834bc41329259a010b70535ab6ab0636a846973f3c436d6f6cc259a1fb455edade7989f352b15e76c3543d'
6
+ metadata.gz: 8291cbace8ba3b7abebdf73b25d577d4a464fc49b1c8de677c1e990b42409a90e3b59a1b76f11312a7443c45ddb18bae51e481e501e602bfc3c735aa8ff98422
7
+ data.tar.gz: e1277c1934e5ecd79e9740070ca25dee5966f39916501d5cd257055bb96aaade4fa9d5ed924badab1a53666e725caaaef2644a6ec55393d1f06032cf5b250b6f
@@ -1,6 +1,3 @@
1
- The `form` kit provides consumers with a convenient, consistently styled `<form>` wrapper.
2
-
3
- ### Form Helpers
4
-
5
- This kit uses rails `form_with` with our custom builder to render forms using other kits such as text_input, select, and typeahead to name a few. Doing so provides UI consistency within forms and makes adding a form to your page easier.
1
+ Use `pb_form_with` to build Rails forms with Playbook kits. It wraps Rails [`form_with`](https://guides.rubyonrails.org/form_helpers.html) and supplies a custom form builder so fields like text inputs, selects, and typeaheads render with consistent Playbook styling and behavior.
6
2
 
3
+ Pick an input below to see Defaults, Validation, Default Values, and Multi-Column layout patterns one field at a time.
@@ -1,2 +1,3 @@
1
1
  ### Things to Avoid
2
- Avoid using a form without validation if fields are required and using with validation if not required.
2
+
3
+ Avoid using a form without validation when fields are required, and avoid enabling validation on fields that are not required.
@@ -0,0 +1,183 @@
1
+ <%
2
+ load Playbook.kit_path("form", "docs", "_sample_data.ruby").to_s
3
+ demo_id = "default_values"
4
+ input_types = PlaybookFormDocsSampleData.input_types
5
+ default_key = PlaybookFormDocsSampleData.default_input_key
6
+ select_choices = PlaybookFormDocsSampleData.select_choices
7
+ dropdown_options = PlaybookFormDocsSampleData.dropdown_options("-dv")
8
+ typeahead_options = PlaybookFormDocsSampleData.typeahead_options
9
+ collection = PlaybookFormDocsSampleData.collection
10
+ tree_data = PlaybookFormDocsSampleData.tree_data("default-values")
11
+ form_open_source = PlaybookFormDocsSampleData.form_open(scope: demo_id)
12
+ form_close_source = PlaybookFormDocsSampleData.form_close
13
+ %>
14
+
15
+ <div data-form-docs-demo="<%= demo_id %>">
16
+ <div hidden data-form-docs-open><%= raw render_code(form_open_source, "erb") %></div>
17
+ <div hidden data-form-docs-close><%= raw render_code(form_close_source, "erb") %></div>
18
+ <%= pb_rails("body", props: {
19
+ text: "Select one or more inputs to add them to the form.",
20
+ color: "light",
21
+ margin_bottom: "xs",
22
+ }) %>
23
+ <%= pb_rails("flex", props: { gap: "xs", wrap: true, margin_bottom: "md" }) do %>
24
+ <% input_types.each do |input_type| %>
25
+ <div data-form-docs-pill="<%= input_type[:key] %>"
26
+ role="button"
27
+ tabindex="0"
28
+ style="cursor: pointer">
29
+ <%= pb_rails("pill", props: {
30
+ text: input_type[:label],
31
+ text_transform: "none",
32
+ variant: input_type[:key] == default_key ? "primary" : "neutral",
33
+ }) %>
34
+ </div>
35
+ <% end %>
36
+ <% end %>
37
+
38
+ <%= pb_rails("flex", props: PlaybookFormDocsSampleData.demo_panel_flex_props) do %>
39
+ <div style="<%= PlaybookFormDocsSampleData.demo_form_column_style %>">
40
+ <%= pb_rails("card", props: PlaybookFormDocsSampleData.demo_form_card_props) do %>
41
+ <%= pb_form_with(scope: :default_values, url: "", method: :get, html: { onsubmit: "return false;" }) do |form| %>
42
+ <% input_types.each do |input_type| %>
43
+ <div data-form-docs-panel="<%= input_type[:key] %>"
44
+ style="<%= input_type[:key] == default_key ? "" : "display: none;" %>">
45
+ <div data-form-docs-snippet hidden><%= raw render_code(
46
+ PlaybookFormDocsSampleData.form_body_snippet(
47
+ File.read(Playbook.kit_path("form", "docs", "snippets", "default_values_#{input_type[:key]}.txt"))
48
+ ),
49
+ "erb"
50
+ ) %></div>
51
+ <% case input_type[:key]
52
+ when "text_field" %>
53
+ <%= form.text_field :full_name, props: { label: "Full Name", value: "John Doe" } %>
54
+ <% when "email_field" %>
55
+ <%= form.email_field :email, props: { label: "Email", value: "jane@example.com" } %>
56
+ <% when "number_field" %>
57
+ <%= form.number_field :quantity, props: { label: "Quantity", value: 3 } %>
58
+ <% when "search_field" %>
59
+ <%= form.search_field :project_number, props: { label: "Project Number", value: "33-12345" } %>
60
+ <% when "password_field" %>
61
+ <%= form.password_field :password, props: { label: "Password", value: "secret" } %>
62
+ <% when "url_field" %>
63
+ <%= form.url_field :website, props: { label: "Website", value: "https://example.com" } %>
64
+ <% when "textarea" %>
65
+ <%= form.text_area :bio, props: { label: "Bio", value: "Loves building with Playbook." } %>
66
+ <% when "select" %>
67
+ <%= form.select :favorite_color, select_choices,
68
+ { selected: "blue" },
69
+ props: { label: "Favorite Color", blank_selection: "Select one..." } %>
70
+ <% when "collection_select" %>
71
+ <%= form.collection_select :state, collection, :value, :name,
72
+ { selected: 2 },
73
+ props: { label: "State", blank_selection: "Select one..." } %>
74
+ <% when "dropdown" %>
75
+ <%= form.dropdown_field "default_values[country]",
76
+ props: {
77
+ label: "Country",
78
+ options: dropdown_options,
79
+ default_value: { label: "Canada", value: "Canada", id: "ca-dv" },
80
+ } %>
81
+ <% when "multi_select" %>
82
+ <%= form.dropdown_field "default_values[countries]",
83
+ props: {
84
+ label: "Countries",
85
+ options: dropdown_options,
86
+ multi_select: true,
87
+ default_value: [{ label: "Canada", value: "Canada", id: "ca-dv" }],
88
+ } %>
89
+ <% when "checkbox" %>
90
+ <%= form.check_box :newsletter,
91
+ props: { text: "Subscribe to newsletter", label: "Checkbox" },
92
+ checked: true,
93
+ checked_value: "yes",
94
+ unchecked_value: "no" %>
95
+ <% when "date_picker" %>
96
+ <%= form.date_picker :start_date, props: {
97
+ label: "Start Date",
98
+ allow_input: true,
99
+ default_date: "06/15/2026",
100
+ } %>
101
+ <% when "time_picker" %>
102
+ <%= form.time_picker :start_time, props: { label: "Start Time", default_time: "09:30" } %>
103
+ <% when "time_zone_select" %>
104
+ <%= form.time_zone_select_field :time_zone, ActiveSupport::TimeZone.us_zones,
105
+ { default: "Pacific Time (US & Canada)" },
106
+ props: { label: "Time Zone" } %>
107
+ <% when "typeahead_default" %>
108
+ <%= form.typeahead "default_values[color_search_single]",
109
+ props: {
110
+ options: typeahead_options,
111
+ is_multi: false,
112
+ label: "Favorite Color",
113
+ placeholder: "Search for a color",
114
+ default_options: [{ label: "Blue", value: "#0000FF" }],
115
+ } %>
116
+ <% when "typeahead" %>
117
+ <%= form.typeahead "default_values[color_search]",
118
+ props: {
119
+ options: typeahead_options,
120
+ pills: true,
121
+ label: "Favorite Color",
122
+ placeholder: "Search for a color",
123
+ default_options: [{ label: "Blue", value: "#0000FF" }],
124
+ } %>
125
+ <% when "phone_number" %>
126
+ <%= form.phone_number_field "default_values[phone]", props: {
127
+ label: "Phone Number",
128
+ value: "5551234567",
129
+ } %>
130
+ <% when "star_rating" %>
131
+ <%= form.star_rating_field "default_values[rating]",
132
+ props: { variant: "interactive", label: "Rating", rating: 4 } %>
133
+ <% when "multi_level_select" %>
134
+ <%= form.multi_level_select "default_values[department]",
135
+ props: {
136
+ id: "mls-default-values",
137
+ tree_data: tree_data,
138
+ margin_bottom: "sm",
139
+ label: "Department",
140
+ selected_ids: ["default-values-102"],
141
+ } %>
142
+ <% end %>
143
+ </div>
144
+ <% end %>
145
+
146
+ <%= form.actions do |action| %>
147
+ <%= action.submit %>
148
+ <%= action.button props: { type: "reset", text: "Cancel", variant: "secondary" } %>
149
+ <% end %>
150
+ <% end %>
151
+ <% end %>
152
+ </div>
153
+
154
+ <div style="<%= PlaybookFormDocsSampleData.demo_data_column_style %>">
155
+ <%= pb_rails("caption", props: { text: "Submitted Form Data", margin_bottom: "xs" }) %>
156
+ <%= pb_rails("card", props: { background: "light", padding: "md" }) do %>
157
+ <div data-form-docs-empty>
158
+ <%= pb_rails("body", props: { text: "No data submitted yet.", color: "light" }) %>
159
+ </div>
160
+ <pre data-form-docs-output
161
+ style="display: none; margin: 0; overflow: auto; font-size: 12px; line-height: 1.5; white-space: pre-wrap;"></pre>
162
+ <% end %>
163
+ </div>
164
+ <% end %>
165
+
166
+ <%= pb_rails("body", props: {
167
+ text: "Submit the form to see the data on the right.",
168
+ color: "light",
169
+ margin_top: "sm",
170
+ margin_bottom: "sm",
171
+ }) %>
172
+
173
+ <%= pb_rails("card", props: { background: "dark", dark: true, padding: "none" }) do %>
174
+ <div class="pb--codeCopy pb--codeCopy_compact">
175
+ <pre class="highlight" data-form-docs-code style="margin: 0; overflow: auto; font-size: 12px; line-height: 1.5; white-space: pre;"></pre>
176
+ </div>
177
+ <% end %>
178
+ </div>
179
+
180
+ <script>
181
+ <%= raw File.read(Playbook.kit_path("form", "docs", "_form_docs_demo.js")) %>
182
+ PbFormDocsDemo("<%= demo_id %>")
183
+ </script>
@@ -0,0 +1,3 @@
1
+ Toggle pills to add pre-filled inputs. Initial values come from builder props (for example `value` on text inputs). Submit to see the selected fields' data on the right.
2
+
3
+ For `typeahead`, `phone_number_field`, and `dropdown_field`, pass a scoped string name that matches the form scope (for example `"default_values[phone]"` when `scope: :default_values`) — see Defaults for details.
@@ -0,0 +1,180 @@
1
+ <%
2
+ load Playbook.kit_path("form", "docs", "_sample_data.ruby").to_s
3
+ demo_id = "defaults"
4
+ input_types = PlaybookFormDocsSampleData.input_types
5
+ default_key = PlaybookFormDocsSampleData.default_input_key
6
+ select_choices = PlaybookFormDocsSampleData.select_choices
7
+ dropdown_options = PlaybookFormDocsSampleData.dropdown_options
8
+ typeahead_options = PlaybookFormDocsSampleData.typeahead_options
9
+ collection = PlaybookFormDocsSampleData.collection
10
+ tree_data = PlaybookFormDocsSampleData.tree_data("defaults")
11
+ form_open_source = PlaybookFormDocsSampleData.form_open(scope: demo_id)
12
+ form_close_source = PlaybookFormDocsSampleData.form_close
13
+ %>
14
+
15
+ <div data-form-docs-demo="<%= demo_id %>">
16
+ <div hidden data-form-docs-open><%= raw render_code(form_open_source, "erb") %></div>
17
+ <div hidden data-form-docs-close><%= raw render_code(form_close_source, "erb") %></div>
18
+ <%= pb_rails("body", props: {
19
+ text: "Select one or more inputs to add them to the form.",
20
+ color: "light",
21
+ margin_bottom: "xs",
22
+ }) %>
23
+ <%= pb_rails("flex", props: { gap: "xs", wrap: true, margin_bottom: "md" }) do %>
24
+ <% input_types.each do |input_type| %>
25
+ <div data-form-docs-pill="<%= input_type[:key] %>"
26
+ role="button"
27
+ tabindex="0"
28
+ style="cursor: pointer">
29
+ <%= pb_rails("pill", props: {
30
+ text: input_type[:label],
31
+ text_transform: "none",
32
+ variant: input_type[:key] == default_key ? "primary" : "neutral",
33
+ }) %>
34
+ </div>
35
+ <% end %>
36
+ <% end %>
37
+
38
+ <%= pb_rails("flex", props: PlaybookFormDocsSampleData.demo_panel_flex_props) do %>
39
+ <div style="<%= PlaybookFormDocsSampleData.demo_form_column_style %>">
40
+ <%= pb_rails("card", props: PlaybookFormDocsSampleData.demo_form_card_props) do %>
41
+ <%= pb_form_with(scope: :defaults, url: "", method: :get, html: { onsubmit: "return false;" }) do |form| %>
42
+ <% input_types.each do |input_type| %>
43
+ <div data-form-docs-panel="<%= input_type[:key] %>"
44
+ style="<%= input_type[:key] == default_key ? "" : "display: none;" %>">
45
+ <div data-form-docs-snippet hidden><%= raw render_code(
46
+ PlaybookFormDocsSampleData.form_body_snippet(
47
+ File.read(Playbook.kit_path("form", "docs", "snippets", "defaults_#{input_type[:key]}.txt"))
48
+ ),
49
+ "erb"
50
+ ) %></div>
51
+ <% case input_type[:key]
52
+ when "text_field" %>
53
+ <%= form.text_field :full_name, props: { label: "Full Name" } %>
54
+ <% when "email_field" %>
55
+ <%= form.email_field :email, props: { label: "Email" } %>
56
+ <% when "number_field" %>
57
+ <%= form.number_field :quantity, props: { label: "Quantity" } %>
58
+ <% when "search_field" %>
59
+ <%= form.search_field :project_number, props: { label: "Project Number" } %>
60
+ <% when "password_field" %>
61
+ <%= form.password_field :password, props: { label: "Password" } %>
62
+ <% when "url_field" %>
63
+ <%= form.url_field :website, props: { label: "Website" } %>
64
+ <% when "textarea" %>
65
+ <%= form.text_area :bio, props: { label: "Bio" } %>
66
+ <% when "select" %>
67
+ <%= form.select :favorite_color, select_choices,
68
+ props: { label: "Favorite Color", blank_selection: "Select one..." } %>
69
+ <% when "collection_select" %>
70
+ <%= form.collection_select :state, collection, :value, :name,
71
+ props: { label: "State", blank_selection: "Select one..." } %>
72
+ <% when "dropdown" %>
73
+ <%= form.dropdown_field "defaults[country]",
74
+ props: { label: "Country", options: dropdown_options } %>
75
+ <% when "multi_select" %>
76
+ <%= form.dropdown_field "defaults[countries]",
77
+ props: {
78
+ label: "Countries",
79
+ options: dropdown_options,
80
+ multi_select: true,
81
+ } %>
82
+ <% when "checkbox" %>
83
+ <%= form.check_box :newsletter,
84
+ props: { text: "Subscribe to newsletter", label: "Checkbox" },
85
+ checked_value: "yes",
86
+ unchecked_value: "no" %>
87
+ <% when "date_picker" %>
88
+ <%= form.date_picker :start_date, props: { label: "Start Date", allow_input: true } %>
89
+ <% when "time_picker" %>
90
+ <%= form.time_picker :start_time, props: { label: "Start Time" } %>
91
+ <% when "time_zone_select" %>
92
+ <%= form.time_zone_select_field :time_zone, ActiveSupport::TimeZone.us_zones,
93
+ { default: "Eastern Time (US & Canada)" },
94
+ props: { label: "Time Zone" } %>
95
+ <% when "typeahead_default" %>
96
+ <%= form.typeahead "defaults[color_search_single]",
97
+ props: {
98
+ options: typeahead_options,
99
+ is_multi: false,
100
+ label: "Favorite Color",
101
+ placeholder: "Search for a color",
102
+ } %>
103
+ <% when "typeahead" %>
104
+ <%= form.typeahead "defaults[color_search]",
105
+ props: {
106
+ options: typeahead_options,
107
+ pills: true,
108
+ label: "Favorite Color",
109
+ placeholder: "Search for a color",
110
+ } %>
111
+ <% when "phone_number" %>
112
+ <%= form.phone_number_field "defaults[phone]", props: { label: "Phone Number" } %>
113
+ <% when "star_rating" %>
114
+ <%= form.star_rating_field "defaults[rating]",
115
+ props: { variant: "interactive", label: "Rating" } %>
116
+ <% when "multi_level_select" %>
117
+ <%= form.multi_level_select "defaults[department]",
118
+ props: {
119
+ id: "mls-defaults",
120
+ tree_data: tree_data,
121
+ margin_bottom: "sm",
122
+ label: "Department",
123
+ } %>
124
+ <% end %>
125
+ </div>
126
+ <% end %>
127
+
128
+ <%= form.actions do |action| %>
129
+ <%= action.submit %>
130
+ <%= action.button props: { type: "reset", text: "Cancel", variant: "secondary" } %>
131
+ <% end %>
132
+ <% end %>
133
+ <% end %>
134
+ </div>
135
+
136
+ <div style="<%= PlaybookFormDocsSampleData.demo_data_column_style %>">
137
+ <%= pb_rails("caption", props: { text: "Submitted Form Data", margin_bottom: "xs" }) %>
138
+ <%= pb_rails("card", props: { background: "light", padding: "md" }) do %>
139
+ <div data-form-docs-empty>
140
+ <%= pb_rails("body", props: { text: "No data submitted yet.", color: "light" }) %>
141
+ </div>
142
+ <pre data-form-docs-output
143
+ style="display: none; margin: 0; overflow: auto; font-size: 12px; line-height: 1.5; white-space: pre-wrap;"></pre>
144
+ <% end %>
145
+ </div>
146
+ <% end %>
147
+
148
+ <%= pb_rails("body", props: {
149
+ text: "Submit the form to see the data on the right.",
150
+ color: "light",
151
+ margin_top: "sm",
152
+ margin_bottom: "sm",
153
+ }) %>
154
+
155
+ <%= pb_rails("card", props: { background: "dark", dark: true, padding: "none" }) do %>
156
+ <div class="pb--codeCopy pb--codeCopy_compact">
157
+ <pre class="highlight" data-form-docs-code style="margin: 0; overflow: auto; font-size: 12px; line-height: 1.5; white-space: pre;"></pre>
158
+ </div>
159
+ <% end %>
160
+ </div>
161
+
162
+ <%= pb_rails("title", props: { size: 4, text: "Naming kit fields under a scope", margin_top: "md", margin_bottom: "sm" }) %>
163
+ <%= pb_rails("body", props: { tag: "div", margin_bottom: "sm" }) do %>
164
+ <p><code>text_field</code>, <code>select</code>, <code>check_box</code>, and similar Rails-backed helpers nest under the form <code>scope</code> automatically (<code>defaults[full_name]</code> when <code>scope: :defaults</code>).</p>
165
+ <p>Playbook-only helpers — <code>typeahead</code>, <code>phone_number_field</code>, and <code>dropdown_field</code> — do not. Pass an explicit string that matches the form scope so params stay nested:</p>
166
+ <% end %>
167
+ <%= pb_rails("card", props: { background: "dark", dark: true, padding: "none", margin_bottom: "sm" }) do %>
168
+ <div class="pb--codeCopy pb--codeCopy_compact">
169
+ <pre class="highlight" style="margin: 0; overflow: auto; font-size: 12px; line-height: 1.5; white-space: pre;"><%= raw render_code(
170
+ File.read(Playbook.kit_path("form", "docs", "snippets", "defaults_scoped_fields.txt")),
171
+ "erb"
172
+ ) %></pre>
173
+ </div>
174
+ <% end %>
175
+ <%= pb_rails("body", props: { text: "Submitted params then land under params[:defaults], matching the code you copy.", color: "light" }) %>
176
+
177
+ <script>
178
+ <%= raw File.read(Playbook.kit_path("form", "docs", "_form_docs_demo.js")) %>
179
+ PbFormDocsDemo("<%= demo_id %>")
180
+ </script>
@@ -0,0 +1 @@
1
+ Toggle pills to add or remove inputs from the form. Submit to see values for the selected fields on the right. The code snippet updates to match the current selection.
@@ -0,0 +1,226 @@
1
+ window.PbFormDocsDemo = window.PbFormDocsDemo || function (demoId) {
2
+ var root = document.querySelector('[data-form-docs-demo="' + demoId + '"]')
3
+ if (!root || root.dataset.pbFormDocsReady === "true") return
4
+ root.dataset.pbFormDocsReady = "true"
5
+
6
+ var dataOutput = root.querySelector("[data-form-docs-output]")
7
+ var emptyState = root.querySelector("[data-form-docs-empty]")
8
+ var codeOutput = root.querySelector("[data-form-docs-code]")
9
+ var formOpenEl = root.querySelector("[data-form-docs-open]")
10
+ var formCloseEl = root.querySelector("[data-form-docs-close]")
11
+ var formOpenHtml = formOpenEl ? formOpenEl.innerHTML.trim() : ""
12
+ var formCloseHtml = formCloseEl ? trimHighlightHtml(formCloseEl.innerHTML) : ""
13
+
14
+ function trimHighlightHtml(html) {
15
+ // Rouge keeps leading indent as literal spaces before the first span.
16
+ // String trim() would strip those; only drop trailing newlines.
17
+ return html.replace(/\n+$/g, "")
18
+ }
19
+
20
+ function clearSubmittedData() {
21
+ if (dataOutput) {
22
+ dataOutput.textContent = ""
23
+ dataOutput.style.display = "none"
24
+ }
25
+ if (emptyState) emptyState.style.display = ""
26
+ clearQueryParams()
27
+ }
28
+
29
+ function clearQueryParams() {
30
+ var url = window.location.pathname + window.location.hash
31
+ if (window.location.search) history.replaceState(null, "", url)
32
+ }
33
+
34
+ function formDataToQueryString(form) {
35
+ var params = new URLSearchParams()
36
+ var formData = new FormData(form)
37
+
38
+ formData.forEach(function (value, key) {
39
+ if (key === "authenticity_token" || key === "utf8" || key === "_method") return
40
+ params.append(key, value)
41
+ })
42
+
43
+ return params.toString()
44
+ }
45
+
46
+ function updateQueryParams(form) {
47
+ var query = formDataToQueryString(form)
48
+ var url = window.location.pathname + window.location.hash
49
+ if (query) url += "?" + query
50
+ history.replaceState(null, "", url)
51
+ }
52
+
53
+ function setPillActive(pillEl, active) {
54
+ var kit = pillEl.querySelector("[class*='pb_pill_kit_']")
55
+ if (!kit) return
56
+ kit.className = kit.className
57
+ .replace(/pb_pill_kit_primary_/g, "pb_pill_kit_TEMP_")
58
+ .replace(/pb_pill_kit_neutral_/g, active ? "pb_pill_kit_primary_" : "pb_pill_kit_neutral_")
59
+ .replace(/pb_pill_kit_TEMP_/g, active ? "pb_pill_kit_primary_" : "pb_pill_kit_neutral_")
60
+ }
61
+
62
+ function setPanelEnabled(panel, enabled) {
63
+ panel.style.display = enabled ? "" : "none"
64
+ panel.querySelectorAll("input, select, textarea").forEach(function (el) {
65
+ el.disabled = !enabled
66
+ })
67
+ }
68
+
69
+ function selectedKeys() {
70
+ var keys = []
71
+ root.querySelectorAll("[data-form-docs-pill]").forEach(function (pillEl) {
72
+ var kit = pillEl.querySelector("[class*='pb_pill_kit_primary_']")
73
+ if (kit) keys.push(pillEl.getAttribute("data-form-docs-pill"))
74
+ })
75
+ return keys
76
+ }
77
+
78
+ function updateCodeSnippet(keys) {
79
+ if (!codeOutput) return
80
+
81
+ // Match visible panel order (fixed kit order), not click/selection order.
82
+ var selected = {}
83
+ keys.forEach(function (key) { selected[key] = true })
84
+
85
+ var fieldSnippets = []
86
+ root.querySelectorAll("[data-form-docs-panel]").forEach(function (panel) {
87
+ var key = panel.getAttribute("data-form-docs-panel")
88
+ if (!selected[key]) return
89
+ var snippetEl = panel.querySelector("[data-form-docs-snippet]")
90
+ if (snippetEl) fieldSnippets.push(trimHighlightHtml(snippetEl.innerHTML))
91
+ })
92
+
93
+ codeOutput.innerHTML = [formOpenHtml].concat(fieldSnippets).concat([formCloseHtml]).join("\n")
94
+ }
95
+
96
+ function syncSelection(keys) {
97
+ root.querySelectorAll("[data-form-docs-pill]").forEach(function (pillEl) {
98
+ var key = pillEl.getAttribute("data-form-docs-pill")
99
+ setPillActive(pillEl, keys.indexOf(key) !== -1)
100
+ })
101
+ root.querySelectorAll("[data-form-docs-panel]").forEach(function (panel) {
102
+ var key = panel.getAttribute("data-form-docs-panel")
103
+ setPanelEnabled(panel, keys.indexOf(key) !== -1)
104
+ })
105
+ updateCodeSnippet(keys)
106
+ clearSubmittedData()
107
+ }
108
+
109
+ function toggleKey(key) {
110
+ var keys = selectedKeys()
111
+ var index = keys.indexOf(key)
112
+ if (index === -1) {
113
+ keys.push(key)
114
+ } else if (keys.length > 1) {
115
+ keys.splice(index, 1)
116
+ }
117
+ syncSelection(keys)
118
+ }
119
+
120
+ function assignFormValue(result, key, value) {
121
+ var path = []
122
+ var asArray = false
123
+ var rest = key
124
+ var bareMatch = rest.match(/^([^[\]]+)/)
125
+
126
+ if (!bareMatch) {
127
+ result[key] = value
128
+ return
129
+ }
130
+
131
+ path.push(bareMatch[1])
132
+ rest = rest.slice(bareMatch[1].length)
133
+
134
+ while (rest.length > 0) {
135
+ if (rest.indexOf("[]") === 0) {
136
+ asArray = true
137
+ rest = rest.slice(2)
138
+ break
139
+ }
140
+
141
+ var bracketMatch = rest.match(/^\[([^\]]+)\]/)
142
+ if (!bracketMatch) {
143
+ result[key] = value
144
+ return
145
+ }
146
+
147
+ path.push(bracketMatch[1])
148
+ rest = rest.slice(bracketMatch[0].length)
149
+ }
150
+
151
+ if (rest.length > 0) {
152
+ result[key] = value
153
+ return
154
+ }
155
+
156
+ var cursor = result
157
+ for (var i = 0; i < path.length - 1; i++) {
158
+ var segment = path[i]
159
+ if (!cursor[segment] || typeof cursor[segment] !== "object" || Array.isArray(cursor[segment])) {
160
+ cursor[segment] = {}
161
+ }
162
+ cursor = cursor[segment]
163
+ }
164
+
165
+ var last = path[path.length - 1]
166
+ if (asArray) {
167
+ if (!Array.isArray(cursor[last])) cursor[last] = []
168
+ cursor[last].push(value)
169
+ } else if (Object.prototype.hasOwnProperty.call(cursor, last)) {
170
+ if (!Array.isArray(cursor[last])) cursor[last] = [cursor[last]]
171
+ cursor[last].push(value)
172
+ } else {
173
+ cursor[last] = value
174
+ }
175
+ }
176
+
177
+ function formDataToObject(form) {
178
+ var result = {}
179
+ var formData = new FormData(form)
180
+
181
+ formData.forEach(function (value, key) {
182
+ if (key === "authenticity_token" || key === "utf8" || key === "_method") return
183
+ assignFormValue(result, key, value)
184
+ })
185
+
186
+ return result
187
+ }
188
+
189
+ function showSubmittedData(data) {
190
+ if (emptyState) emptyState.style.display = "none"
191
+ if (dataOutput) {
192
+ dataOutput.style.display = ""
193
+ dataOutput.textContent = JSON.stringify(data, null, 2)
194
+ }
195
+ }
196
+
197
+ root.querySelectorAll("[data-form-docs-pill]").forEach(function (pillEl) {
198
+ pillEl.addEventListener("click", function () {
199
+ toggleKey(pillEl.getAttribute("data-form-docs-pill"))
200
+ })
201
+ pillEl.addEventListener("keydown", function (event) {
202
+ if (event.key !== "Enter" && event.key !== " ") return
203
+ event.preventDefault()
204
+ toggleKey(pillEl.getAttribute("data-form-docs-pill"))
205
+ })
206
+ })
207
+
208
+ root.querySelectorAll("form").forEach(function (form) {
209
+ // Docs demos never navigate. Capture + return false cover the window before
210
+ // this script runs (live examples delay inline scripts ~100ms) and Turbo.
211
+ form.setAttribute("onsubmit", "return false;")
212
+ form.addEventListener("submit", function (event) {
213
+ event.preventDefault()
214
+ event.stopPropagation()
215
+ if (typeof form.reportValidity === "function" && !form.reportValidity()) return
216
+ showSubmittedData(formDataToObject(form))
217
+ updateQueryParams(form)
218
+ }, true)
219
+
220
+ form.addEventListener("reset", function () {
221
+ setTimeout(clearSubmittedData, 0)
222
+ })
223
+ })
224
+
225
+ syncSelection(selectedKeys())
226
+ }
@@ -0,0 +1,36 @@
1
+ <%= pb_rails("flex", props: { align: "stretch", gap: "md", margin_bottom: "md", orientation: "column" }) do %>
2
+ <%= pb_rails("flex/flex_item") do %>
3
+ <%= pb_rails("caption", props: { text: "Rails form_with", margin_bottom: "xs" }) %>
4
+ <%= pb_rails("card", props: { background: "dark", dark: true, padding: "none" }) do %>
5
+ <div class="pb--codeCopy pb--codeCopy_compact">
6
+ <pre class="highlight" style="margin: 0; overflow: auto; font-size: 12px; line-height: 1.5; white-space: pre;"><%= raw render_code(
7
+ File.read(Playbook.kit_path("form", "docs", "snippets", "comparison_rails.txt")),
8
+ "erb"
9
+ ) %></pre>
10
+ </div>
11
+ <% end %>
12
+ <% end %>
13
+
14
+ <%= pb_rails("flex/flex_item") do %>
15
+ <%= pb_rails("caption", props: { text: "Playbook pb_form_with", margin_bottom: "xs" }) %>
16
+ <%= pb_rails("card", props: { background: "dark", dark: true, padding: "none" }) do %>
17
+ <div class="pb--codeCopy pb--codeCopy_compact">
18
+ <pre class="highlight" style="margin: 0; overflow: auto; font-size: 12px; line-height: 1.5; white-space: pre;"><%= raw render_code(
19
+ File.read(Playbook.kit_path("form", "docs", "snippets", "comparison_playbook.txt")),
20
+ "erb"
21
+ ) %></pre>
22
+ </div>
23
+ <% end %>
24
+ <% end %>
25
+ <% end %>
26
+
27
+ <%= pb_rails("title", props: { size: 4, text: "What pb_form_with adds", margin_bottom: "sm" }) %>
28
+ <%= pb_rails("body", props: { tag: "div" }) do %>
29
+ <ul style="margin: 0; padding-left: 1.25rem;">
30
+ <li>Automatic Playbook styling via the <code>pb-form</code> class and kit-backed fields</li>
31
+ <li>A custom form builder for inputs such as <code>text_field</code>, <code>select</code>, <code>typeahead</code>, and more</li>
32
+ <li>Consistent spacing and action buttons through <code>form.actions</code></li>
33
+ <li>Optional client-side validation with <code>validate: true</code> and field-level messages</li>
34
+ <li>For <code>typeahead</code>, <code>phone_number_field</code>, and <code>dropdown_field</code>, pass a scoped string name that matches the form scope (e.g. <code>"user[phone]"</code> when <code>scope: :user</code>) so params nest together</li>
35
+ </ul>
36
+ <% end %>
@@ -0,0 +1 @@
1
+ Standard Rails `form_with` still works, but it does not apply Playbook kit styling or the form builder helpers. Prefer `pb_form_with` when building forms in Playbook apps.