primer_view_components 0.0.121 → 0.0.122

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/app/assets/styles/primer_view_components.css +2 -2
  4. data/app/assets/styles/primer_view_components.css.map +1 -1
  5. data/app/components/primer/alpha/action_list.css.json +123 -1
  6. data/app/components/primer/alpha/auto_complete.css.json +23 -1
  7. data/app/components/primer/alpha/banner.css.json +24 -1
  8. data/app/components/primer/alpha/button_marketing.css.json +33 -1
  9. data/app/components/primer/alpha/check_box.rb +74 -0
  10. data/app/components/primer/alpha/check_box_group.rb +36 -0
  11. data/app/components/primer/alpha/dialog.css.json +82 -1
  12. data/app/components/primer/alpha/dialog.rb +1 -1
  13. data/app/components/primer/alpha/dropdown.css.json +40 -1
  14. data/app/components/primer/alpha/form_button.rb +32 -0
  15. data/app/components/primer/alpha/form_control.html.erb +26 -0
  16. data/app/components/primer/alpha/form_control.rb +105 -0
  17. data/app/components/primer/alpha/layout.css.json +80 -1
  18. data/app/components/primer/alpha/menu.css.json +28 -1
  19. data/app/components/primer/alpha/multi_input.rb +81 -0
  20. data/app/components/primer/alpha/radio_button.rb +25 -0
  21. data/app/components/primer/alpha/radio_button_group.rb +36 -0
  22. data/app/components/primer/alpha/segmented_control.css +1 -1
  23. data/app/components/primer/alpha/segmented_control.css.json +31 -1
  24. data/app/components/primer/alpha/segmented_control.css.map +1 -1
  25. data/app/components/primer/alpha/segmented_control.pcss +43 -12
  26. data/app/components/primer/alpha/select.rb +37 -0
  27. data/app/components/primer/alpha/submit_button.rb +32 -0
  28. data/app/components/primer/alpha/tab_nav.css.json +24 -1
  29. data/app/components/primer/alpha/tab_panels.rb +7 -0
  30. data/app/components/primer/alpha/text_area.rb +24 -0
  31. data/app/components/primer/alpha/text_field.css +2 -2
  32. data/app/components/primer/alpha/text_field.css.json +134 -1
  33. data/app/components/primer/alpha/text_field.css.map +1 -1
  34. data/app/components/primer/alpha/text_field.pcss +27 -0
  35. data/app/components/primer/alpha/text_field.rb +15 -20
  36. data/app/components/primer/alpha/toggle_switch.css +1 -1
  37. data/app/components/primer/alpha/toggle_switch.css.json +40 -1
  38. data/app/components/primer/alpha/toggle_switch.css.map +1 -1
  39. data/app/components/primer/alpha/toggle_switch.pcss +31 -61
  40. data/app/components/primer/alpha/underline_nav.css.json +28 -1
  41. data/app/components/primer/beta/avatar.css.json +17 -1
  42. data/app/components/primer/beta/avatar_stack.css.json +28 -1
  43. data/app/components/primer/beta/blankslate.css.json +22 -1
  44. data/app/components/primer/beta/border_box.css.json +54 -1
  45. data/app/components/primer/beta/breadcrumbs.css.json +11 -1
  46. data/app/components/primer/beta/button.css.json +71 -1
  47. data/app/components/primer/beta/counter.css.json +10 -1
  48. data/app/components/primer/beta/flash.css.json +27 -1
  49. data/app/components/primer/beta/label.css.json +25 -1
  50. data/app/components/primer/beta/link.css.json +19 -1
  51. data/app/components/primer/beta/popover.css.json +39 -1
  52. data/app/components/primer/beta/progress_bar.css.json +10 -1
  53. data/app/components/primer/beta/state.css.json +13 -1
  54. data/app/components/primer/beta/subhead.css.json +12 -1
  55. data/app/components/primer/beta/timeline_item.css.json +16 -1
  56. data/app/components/primer/beta/truncate.css.json +12 -1
  57. data/app/components/primer/component.rb +10 -2
  58. data/app/components/primer/truncate.css.json +13 -1
  59. data/app/forms/{select_list_form.rb → select_form.rb} +1 -1
  60. data/app/lib/primer/css/layout.css.json +316 -1
  61. data/app/lib/primer/css/utilities.css.json +1659 -1
  62. data/lib/primer/form_components.rb +26 -6
  63. data/lib/primer/forms/builder.rb +1 -17
  64. data/lib/primer/forms/button.rb +4 -1
  65. data/lib/primer/forms/check_box_group.html.erb +14 -9
  66. data/lib/primer/forms/check_box_group.rb +5 -0
  67. data/lib/primer/forms/dsl/check_box_group_input.rb +3 -4
  68. data/lib/primer/forms/dsl/input.rb +33 -2
  69. data/lib/primer/forms/dsl/input_methods.rb +49 -1
  70. data/lib/primer/forms/dsl/radio_button_group_input.rb +2 -3
  71. data/lib/primer/forms/dsl/{select_list_input.rb → select_input.rb} +2 -2
  72. data/lib/primer/forms/dsl/text_field_input.rb +7 -5
  73. data/lib/primer/forms/form_control.rb +0 -1
  74. data/lib/primer/forms/group.html.erb +1 -1
  75. data/lib/primer/forms/multi.html.erb +8 -6
  76. data/lib/primer/forms/multi.rb +2 -0
  77. data/lib/primer/forms/radio_button_group.html.erb +14 -9
  78. data/lib/primer/forms/radio_button_group.rb +5 -0
  79. data/lib/primer/forms/{select_list.html.erb → select.html.erb} +0 -0
  80. data/lib/primer/forms/{select_list.rb → select.rb} +2 -2
  81. data/lib/primer/forms/spacing_wrapper.html.erb +1 -1
  82. data/lib/primer/forms/text_area.rb +1 -1
  83. data/lib/primer/forms/text_field.rb +5 -1
  84. data/lib/primer/forms/utils.rb +20 -0
  85. data/lib/primer/view_components/engine.rb +1 -1
  86. data/lib/primer/view_components/version.rb +1 -1
  87. data/lib/primer/yard/backend.rb +1 -15
  88. data/lib/primer/yard/component_manifest.rb +44 -25
  89. data/lib/primer/yard/component_ref.rb +40 -0
  90. data/lib/primer/yard/docs_helper.rb +16 -2
  91. data/lib/primer/yard/legacy_gatsby_backend.rb +9 -15
  92. data/lib/primer/yard/lookbook_docs_helper.rb +32 -0
  93. data/lib/primer/yard/lookbook_pages_backend.rb +194 -0
  94. data/lib/primer/yard/registry.rb +6 -21
  95. data/lib/primer/yard/renders_many_handler.rb +1 -1
  96. data/lib/primer/yard/renders_one_handler.rb +1 -1
  97. data/lib/primer/yard.rb +14 -0
  98. data/lib/tasks/docs.rake +26 -13
  99. data/previews/pages/forms/01_introduction.md.erb +44 -0
  100. data/previews/pages/forms/02_getting_started.md.erb +125 -0
  101. data/previews/pages/forms/03_caption_templates.md.erb +30 -0
  102. data/previews/pages/forms/04_after_content.md.erb +39 -0
  103. data/previews/pages/forms/05_groups_layouts.md.erb +22 -0
  104. data/previews/pages/forms/06_miscellaneous_inputs.md.erb +43 -0
  105. data/previews/pages/forms/07_toggle_switch_forms.md.erb +58 -0
  106. data/previews/pages/forms/08_validations.md.erb +28 -0
  107. data/previews/pages/forms/09_compound_forms.md.erb +97 -0
  108. data/previews/primer/alpha/check_box_group_preview.rb +89 -0
  109. data/previews/primer/alpha/check_box_preview.rb +62 -0
  110. data/previews/primer/alpha/form_control_preview/playground.html.erb +9 -0
  111. data/previews/primer/alpha/form_control_preview.rb +106 -0
  112. data/previews/primer/alpha/multi_input_preview/playground.html.erb +41 -0
  113. data/previews/primer/alpha/multi_input_preview.rb +80 -0
  114. data/previews/primer/alpha/radio_button_group_preview.rb +83 -0
  115. data/previews/primer/alpha/radio_button_preview.rb +62 -0
  116. data/previews/primer/alpha/select_preview.rb +130 -0
  117. data/previews/primer/alpha/text_area_preview.rb +87 -0
  118. data/previews/primer/alpha/text_field_preview.rb +10 -1
  119. data/previews/primer/forms/forms_preview/example_toggle_switch_form.html.erb +2 -2
  120. data/previews/primer/forms/forms_preview/{select_list_form.html.erb → select_form.html.erb} +1 -1
  121. data/previews/primer/forms/forms_preview.rb +3 -1
  122. data/static/arguments.json +1358 -1328
  123. data/static/audited_at.json +10 -0
  124. data/static/constants.json +20 -0
  125. data/static/previews.json +218 -40
  126. data/static/statuses.json +10 -0
  127. metadata +41 -7
@@ -0,0 +1,44 @@
1
+ ---
2
+ title: "Introduction"
3
+ id: introduction
4
+ ---
5
+
6
+ Primer contains a framework for declaratively building Rails forms. It was designed to meet the following goals:
7
+
8
+ 1. Be accessible by default.
9
+ 1. Integrate deeply with Ruby on Rails and its existing form capabilities.
10
+ 1. Follow web standards.
11
+ 1. Adhere to Primer's [form interface guidelines](/design/ui-patterns/forms).
12
+
13
+ ### Accessible by default
14
+
15
+ Creating accessible forms can be challenging. Forms are a common source of accessibility issues, and can hinder someone from successfully completing and submitting a form. This can be true even for people who do not use assistive technologies.
16
+
17
+ Primer forms are designed to be accessible by default - i.e. accessible without developer effort - borrowing from Rails' sane defaults philosophy.
18
+
19
+ For example, Primer forms:
20
+
21
+ 1. Automatically associate labels, captions, and validation messages with their inputs.
22
+ 1. Use `<fieldset>`s where appropriate.
23
+ 1. Focus the first invalid input after submission.
24
+
25
+ And more!
26
+
27
+ ### Integrates with Rails
28
+
29
+ Primer forms augment Rails' battle-tested form capabilties, allowing developers to leverage their existing Rails knowledge. Primer forms delegate HTML generation to Rails, meaning inputs accept the same arguments as their vanilla Rails counterparts. For example, Primer's `#text_field` method accepts all the arguments Rails' `#text_field_tag` method accepts.
30
+
31
+ Primer forms are emitted using the familiar `#render` method, and wrapped using `#primer_form_with`, analogous to Rails' `#form_with`.
32
+
33
+ ### Follows web standards
34
+
35
+ Under the hood, Primer forms are just HTML with some JavaScript sprinkles. Nothing too fancy, meaning they should work pretty much everywhere.
36
+
37
+ ### What to read next
38
+
39
+ 1. Read the getting started guide.
40
+ 1. Take a look at the various form inputs.
41
+ 1. Explore form layout options.
42
+ 1. Compose forms together.
43
+ 1. Include custom validation messages.
44
+ 1. Submit boolean values immediately with toggle switch forms.
@@ -0,0 +1,125 @@
1
+ ---
2
+ title: "Getting started"
3
+ id: getting_started
4
+ ---
5
+
6
+ A number of the concepts in Primer Forms are borrowed from the [view_component](https://viewcomponent.org) framework. For example, forms are defined inside Ruby classes and rendered using the `#render` method.
7
+
8
+ Let's create a sign up form and render it on the page.
9
+
10
+ ## Example for the impatient
11
+
12
+ <%= code :ruby do %>
13
+ class SignUpForm < ApplicationForm
14
+ form do |sign_up_form|
15
+ sign_up_form.group(layout: :horizontal) do |name_group|
16
+ name_group.text_field(
17
+ name: :first_name,
18
+ label: "First name",
19
+ required: true,
20
+ caption: "What your friends call you."
21
+ )
22
+
23
+ name_group.text_field(
24
+ name: :last_name,
25
+ label: "Last name",
26
+ required: true,
27
+ caption: "What the principal calls you."
28
+ )
29
+ end
30
+
31
+ sign_up_form.text_field(
32
+ name: :dietary_restrictions,
33
+ label: "Dietary restrictions",
34
+ caption: "Any allergies?"
35
+ )
36
+
37
+ if @show_notifications_checkbox
38
+ sign_up_form.check_box(
39
+ name: :email_notifications,
40
+ label: "Send me gobs of email!",
41
+ caption: "Check this if you enjoy getting spam."
42
+ )
43
+ end
44
+
45
+ sign_up_form.submit(label: "Submit")
46
+ end
47
+
48
+ def initialize(show_notifications_checkbox: true)
49
+ @show_notifications_checkbox = show_notifications_checkbox
50
+ end
51
+ end
52
+ <% end %>
53
+
54
+ ## Directory structure
55
+
56
+ Autoloading is automatically enabled for any sub-directory inside a Rails' app's app/ directory. Accordingly, we recommend placing all form classes inside app/forms/.
57
+
58
+ The directory structure within app/forms/ is arbitrary, but we have seen success using the same directory structure as app/controllers/. For example:
59
+
60
+ ```
61
+ app
62
+ ╵─ controllers
63
+ ╵─ settings
64
+ ╵─ email_notifications_controller.rb
65
+ ╵─ forms
66
+ ╵─ settings
67
+ ╵─ email_notifications
68
+ ╵─ config_form.rb
69
+ ```
70
+
71
+ ## `ApplicationForm`
72
+
73
+ Our example class above inherits from `ApplicationForm`. This follows the Rails convention of a base class that houses application-specific logic common to all forms. It's similar in purpose to `ApplicationRecord` and `ApplicationController`.
74
+
75
+ Ultimately, forms must inherit from `Primer::Forms::Base`, meaning `ApplicationForm` can be as simple as:
76
+
77
+ <%= code :ruby do %>
78
+ class ApplicationForm < Primer::Forms::Base
79
+ # empty body, add common methods, includes, etc here
80
+ end
81
+ <% end %>
82
+
83
+ Place `ApplicationForm` in app/forms/application_form.rb
84
+
85
+ ## Anatomy of a form
86
+
87
+ Forms are just Ruby classes that inherit from `Primer::Forms::Base`. Define a form's inputs using the `form` class method, which accepts a block. The block is called with an instance of `Primer::Forms::Dsl::FormObject`, which responds to the following form input methods:
88
+
89
+ * [`text_field`](<%= page_path(:text_field_input) %>): Text input field.
90
+ * [`text_area`](<%= page_path(:text_area_input) %>): Text area field (multi-line).
91
+ * [`select_list`](<%= page_path(:select_list_input) %>): Drop-down select list.
92
+ * [`check_box`](<%= page_path(:check_box_input) %>): Single check box.
93
+ * [`check_box_group`](<%= page_path(:check_box_group_input) %>): Group of related check boxes.
94
+ * [`radio_button_group`](<%= page_path(:radio_button_group_input) %>): Group of related radio buttons.
95
+ * [`hidden`](<%= page_path(:miscellaneous_inputs) %>): Hidden input.
96
+ * [`multi`](<%= page_path(:multi_input) %>): Multi input (multiple fields that look like a single field).
97
+ * [`submit`](<%= page_path(:submit_button_input) %>): Submit button.
98
+ * [`button`](<%= page_path(:form_button_input) %>): Regular, non-submit button.
99
+ * [`separator`](<%= page_path(:miscellaneous_inputs) %>): Horizontal separator.
100
+ * [`fields_for`](<%= page_path(:compound_forms) %>): Used for composing forms together.
101
+
102
+ ## Rendering
103
+
104
+ Forms are rendered using the familiar Rails `#render` method:
105
+
106
+ <%= code :erb do %>
107
+ <%%= primer_form_with(model: @user) do |f| %>
108
+ <%%= render SignUpForm.new(f, show_notifications_checkbox: false) %>
109
+ <%% end %>
110
+ <% end %>
111
+
112
+ ## The `primer_form_with` helper
113
+
114
+ You may have noticed the use of the `primer_form_with` helper above. This helper is analogous to Rails' `form_with` helper, but layers on some additional functionality. It automatically:
115
+
116
+ 1. configures the form to use Primer's form builder
117
+ 1. disables Rails' default error markup functionality, which breaks Primer styling
118
+ 1. forces labels to always identify their inputs via the `for` attribute
119
+ 1. enables the special handling of `system_arguments` that allows for utility arguments like `ml: 2` and friends
120
+
121
+ It is recommended to always use `primer_form_with` instead of `form_with`.
122
+
123
+ ## The `primer_form_for` helper
124
+
125
+ Before the introduction of `#form_with` in Rails 5.1, forms were created using the `#form_for` helper. Although `#form_for` has since been soft-deprecated, Primer includes the analogous `#primer_form_for` for completeness. Prefer `#primer_form_with` for new forms.
@@ -0,0 +1,30 @@
1
+ ---
2
+ title: "Caption templates"
3
+ id: caption_templates
4
+ ---
5
+
6
+ All inputs support the `caption:` argument as a string, which renders explainer text underneath the input and validation message. Caption strings can be anything that responds to `#to_s`, meaning HTML-safe strings can be used in cases that need a little bit of markup:
7
+
8
+ <%= code :ruby do %>
9
+ class ExampleForm < ApplicationForm
10
+ form do |example_form|
11
+ example_form.text_field(
12
+ name: :disco_name,
13
+ label: "Disco name",
14
+ caption: "Enter your <strong>grooviest</strong> name".html_safe
15
+ )
16
+ end
17
+ end
18
+ <% end %>
19
+
20
+ ## Defining caption templates
21
+
22
+ In cases where a lot of logic or markup is necessary, or where the caption text is awkward to express in Ruby code, forms may define caption content in separate template files. Caption templates are located in a directory named after the form. For example, if `ExampleForm` lives in app/forms/example\_form.rb, its caption templates should be created in the `app/forms/example_form/` directory.
23
+
24
+ Caption template files must be named after the field they describe. For example, a caption template for the `disco_name` field above would live in app/forms/example\_form/disco\_name_caption.html.erb.
25
+
26
+ In cases where the `caption:` argument _and_ a caption template are provided, the `caption:` argument takes precedence.
27
+
28
+ ## Example
29
+
30
+ <%= embed Primer::Forms::FormsPreview, :caption_template_form %>
@@ -0,0 +1,39 @@
1
+ ---
2
+ title: '"After" content'
3
+ id: after_content
4
+ ---
5
+
6
+ Primer forms is designed to be accessible by default. In service of this goal, the framework intentionally limits the types of inputs, layout styles, etc that may be used to build a form. For example, consider rendering a hidden component like a dialog or tooltip that appears when a form input is hovered or clicked. Normally these hidden elements can only be rendered _outside_ the form. The hidden elements don't "travel" with the form and can be easily forgotten.
7
+
8
+ For such cases, Primer forms feature "after" content. Like [caption templates](<%= page_path(:caption_templates) %>), after content lives in a directory named after the form, specifically in a file called after\_content.html.erb. If present, after\_content.html.erb is rendered after the last form input.
9
+
10
+ ## Usage
11
+
12
+ <%= code :ruby do %>
13
+ # app/forms/example_form.rb
14
+ class ExampleForm < ApplicationForm
15
+ form do |example_form|
16
+ example_form.text_field(
17
+ name: :name,
18
+ label: "Name",
19
+ id: "my-text-field"
20
+ )
21
+ end
22
+ end
23
+ <% end %>
24
+
25
+ <%= code :erb do %>
26
+ <%%# app/forms/example_form/after_content.html.erb %>
27
+ <%%= render(
28
+ Primer::Alpha::Tooltip.new(
29
+ for_id: "my-text-field",
30
+ type: :description,
31
+ text: "Some tooltip text",
32
+ direction: :ne
33
+ )
34
+ ) %>
35
+ <% end %>
36
+
37
+ ## Example
38
+
39
+ <%= embed Primer::Forms::FormsPreview, :after_content_form %>
@@ -0,0 +1,22 @@
1
+ ---
2
+ title: "Groups and layouts"
3
+ label: "Groups and Layouts"
4
+ id: groups_layouts
5
+ ---
6
+
7
+ By default, forms are oriented vertically. In other words, inputs are rendered from top to bottom in the order they are defined. Rendering inputs this way is generally more accessible than rendering inputs side-by-side, and should be preferred for most use-cases.
8
+
9
+ However in certain circumstances it may be appropriate to render groups of inputs horizontally, and Primer forms provides a grouping mechanism for doing so:
10
+
11
+ <%= code :ruby do %>
12
+ class ExampleForm < ApplicationForm
13
+ form do |example_form|
14
+ example_form.group(layout: :horizontal) do |name_group|
15
+ name_group.text_field(name: :first_name, label: "First name")
16
+ name_group.text_field(name: :last_name, label: "Last name")
17
+ end
18
+ end
19
+ end
20
+ <% end %>
21
+
22
+ The `layout:` argument also accepts a `:vertical` option, which is the default.
@@ -0,0 +1,43 @@
1
+ ---
2
+ title: "Miscellaneous inputs"
3
+ label: "Miscellaneous Inputs"
4
+ id: miscellaneous_inputs
5
+ ---
6
+
7
+ Aside from the set of component-based form inputs, Primer forms also features several additional inputs.
8
+
9
+ ## Hidden inputs
10
+
11
+ Primer supports standard hidden HTML inputs, eg. `<input type="hidden">`, via the `#hidden` method.
12
+
13
+ ### Usage
14
+
15
+ <%= code :ruby do %>
16
+ class ExampleForm < ApplicationForm
17
+ form do |example_form|
18
+ example_form.hidden(arguments)
19
+ end
20
+ end
21
+ <% end %>
22
+
23
+ `#hidden` is a thin wrapper around Rails' [`hidden_field` method](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-hidden_field) and accepts the same arguments.
24
+
25
+ ## Separators
26
+
27
+ Occasionally it can be appealing to visually distinguish parts of a form from each other. The `#separator` method adds a horizontal rule between inputs.
28
+
29
+ ### Usage
30
+
31
+ <%= code :ruby do %>
32
+ class ExampleForm < ApplicationForm
33
+ form do |example_form|
34
+ example_form.separator
35
+ end
36
+ end
37
+ <% end %>
38
+
39
+ `#separator` does not accept any arguments.
40
+
41
+ ### Example
42
+
43
+ <%= embed Primer::Forms::FormsPreview, :multi_text_field_form %>
@@ -0,0 +1,58 @@
1
+ ---
2
+ title: "Toggle switch forms"
3
+ label: "Toggle Switch Forms"
4
+ id: toggle_switch_forms
5
+ ---
6
+
7
+ The Primer toggle switch component is designed to immediately submit a true/false value to the server when toggled. The forms framework adds a label, caption, and validation message to the stock toggle switch in keeping with the other inputs in the framework. However, toggle switches cannot be added to forms directly. Instead, toggle switch forms are their own complete, standalone forms.
8
+
9
+ Toggle switches make POST requests to the configured URL (the `src` argument).
10
+
11
+ ## Creating toggle switch forms
12
+
13
+ Toggle switch forms can be defined in one of two ways: either by creating an instance of the `Primer::Forms::ToggleSwitchForm` class or by inheriting from it.
14
+
15
+ ### Instance
16
+
17
+ <%= code :erb do %>
18
+ <%% render(Primer::Forms::ToggleSwitchForm.new(arguments)) %>
19
+ <% end %>
20
+
21
+ ### Inheritance
22
+
23
+ Define the form:
24
+
25
+ <%= code :ruby do %>
26
+ # app/forms/my_toggle_form.rb
27
+ class MyToggleForm < Primer::Forms::ToggleSwitchForm
28
+ def initialize(**system_arguments)
29
+ super(arguments, **system_arguments)
30
+ end
31
+ end
32
+ <% end %>
33
+
34
+ Render the form:
35
+
36
+ <%= code :erb do %>
37
+ <%%# app/views/some_controller/some_view.html.erb %>
38
+ <%% render(MyToggleForm.new) %>
39
+ <% end %>
40
+
41
+ ## Arguments
42
+
43
+ | Name | Type | Default | Description |
44
+ | :- | :- | :- | :- |
45
+ |`name`|String||The name of the input, used to associate the toggle switch with its label.|
46
+ |`label`|String||Label text displayed adjacent to the input.|
47
+ |`src`|String||The URL to submit toggle POST requests to.|
48
+ |`csrf`|String||A CSRF token to submit along with the toggle value. If no value is provided, a CSRF token is automatically generated via Rails' [`form_authenticity_token` method](https://api.rubyonrails.org/v7.0/classes/ActionController/RequestForgeryProtection.html#method-i-form_authenticity_token)|
49
+ |`hidden`|Boolean||When set to `true`, visually hides the form.|
50
+ |`label_arguments`|Hash||Attributes that will be passed to Rails' `builder.label` method. These can be HTML attributes or any of the other label options Rails supports. They will appear as HTML attributes on the `<label>` tag.|
51
+
52
+ ## Error messages
53
+
54
+ The server can indicate that an error occurred by responding with a non-2xx status code to toggle switch POST requests. In such a scenario, the response body will be used to populate the toggle switch form's validation message, and will cause a red warning icon to appear next to the switch.
55
+
56
+ ## Example
57
+
58
+ <%= embed Primer::Forms::FormsPreview, :example_toggle_switch_form %>
@@ -0,0 +1,28 @@
1
+ ---
2
+ title: "Validations"
3
+ id: validations
4
+ ---
5
+
6
+ In Rails, forms usually have an associated model object. Models define validations which include validation messages that describe which validations failed and why. Primer forms automatically display validation messages when a model object is provided.
7
+
8
+ ## Custom validation messages
9
+
10
+ All form inputs allow overriding the default validation message via the `:validation_message` argument:
11
+
12
+ <%= code :ruby do %>
13
+ class ExampleForm < ApplicationForm
14
+ form do |example_form|
15
+ example_form.text_field(
16
+ name: :foo,
17
+ label: "Foo",
18
+ validation_message: "Custom validation message"
19
+ )
20
+ end
21
+ end
22
+ <% end %>
23
+
24
+ A custom validation message is also useful if no associated model object is available, i.e. for forms that are not tied to a database table.
25
+
26
+ ## Input focus
27
+
28
+ Primer forms will automatically focus the first invalid input in accordance with accessibility best-practices. Inputs are considered invalid if the associated model's field is invalid _or_ if a custom validation message has been provided.
@@ -0,0 +1,97 @@
1
+ ---
2
+ title: "Compound forms"
3
+ label: "Compound Forms"
4
+ id: compound_forms
5
+ ---
6
+
7
+ In addition to rendering individual forms, Primer supports combining forms together.
8
+
9
+ ## Form lists
10
+
11
+ Form lists are arrays of one or more forms. They can be rendered anywhere individual forms can be rendered. Consider this example where the user wants to render two individual forms:
12
+
13
+ <%= code :erb do %>
14
+ <%%= primer_form_with(model: Foo.new) do |f| %>
15
+ <%%= render(FirstForm.new(f)) %>
16
+ <%%= render(SecondForm.new(f)) %>
17
+ <%% end %>
18
+ <% end %>
19
+
20
+ The example above can be re-written using `FormList`:
21
+
22
+ <%= code :erb do %>
23
+ <%%= primer_form_with(model: Foo.new) do |f| %>
24
+ <%%= render(Primer::Forms::FormList.new(FirstForm.new(f), SecondForm.new(f))) %>
25
+ <%% end %>
26
+ <% end %>
27
+
28
+ Form lists can come in handy in cases where the framework expects a single form object, such as the nested form option for check boxes and radio buttons:
29
+
30
+ <%= code :ruby do %>
31
+ class ExampleForm < ApplicationForm
32
+ form do |example_form|
33
+ example_form.check_box(name: :foo, label: "Foo") do |check_box|
34
+ check_box.nested_form do |builder|
35
+ Primer::Forms::FormList.new(
36
+ FirstForm.new(builder),
37
+ SecondForm.new(builder)
38
+ )
39
+ end
40
+ end
41
+ end
42
+ end
43
+ <% end %>
44
+
45
+ ## Associated records
46
+
47
+ In all the examples above, Primer assumes `FirstForm` and `SecondForm` define inputs that have corresponding fields in the provided model object (an instance of `Foo`). However it's common in Rails to accept nested attributes for an associated model object. For example, a `User` may have a `Profile`. Both are created on signup, so the form should accept both sets of attributes. Rails provides the `fields_for` helper for just this scenario:
48
+
49
+ <%= code :erb do %>
50
+ <%%= form_with(model: User.new) do |f| %>
51
+ <%%= f.text_field :username %>
52
+ <%%= f.fields_for(:profile) do |f| %>
53
+ <%%= f.text_field :first_name %>
54
+ <%%= f.text_field :last_name %>
55
+ <%% end %>
56
+ <%% end %>
57
+ <% end %>
58
+
59
+ Primer forms also support a `#fields_for` method for accomplishing the same goal with form objects. The example above can be rewritten as follows:
60
+
61
+ <%= code :ruby do %>
62
+ # app/forms/signup_form.rb
63
+ class SignupForm < ApplicationForm
64
+ form do |signup_form|
65
+ signup_form.text_field(name: :username, label: "Username")
66
+ signup_form.fields_for(:profile) do |builder|
67
+ ProfileForm.new(builder)
68
+ end
69
+ end
70
+ end
71
+ <% end %>
72
+
73
+ <%= code :ruby do %>
74
+ # app/forms/profile_form.rb
75
+ class ProfileForm < ApplicationForm
76
+ form do |profile_form|
77
+ profile_form.text_field(name: :first_name, label: "First name")
78
+ profile_form.text_field(name: :last_name, label: "Last name")
79
+ end
80
+ end
81
+ <% end %>
82
+
83
+ ## Avoiding attribute nesting
84
+
85
+ For situations where an association exists between two models, the approach described above works well. However sometimes all you want is to compose two forms together that aren't connected by an association. In such cases the fields will still include the name of the parent model when submitted to the server, eg. `user[profile][first_name]` instead of `profile[first_name]`. To render the form independent of the parent, pass `nested: false` to `fields_for`:
86
+
87
+ <%= code :ruby do %>
88
+ # app/forms/signup_form.rb
89
+ class SignupForm < ApplicationForm
90
+ form do |signup_form|
91
+ signup_form.text_field(name: :username, label: "Username")
92
+ signup_form.fields_for(:profile, nested: false) do |builder|
93
+ ProfileForm.new(builder)
94
+ end
95
+ end
96
+ end
97
+ <% end %>
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Alpha
5
+ # @label CheckBoxGroup
6
+ class CheckBoxGroupPreview < ViewComponent::Preview
7
+ # @label Playground
8
+ #
9
+ # @param name text
10
+ # @param label text
11
+ # @param caption text
12
+ # @param disabled toggle
13
+ def playground(
14
+ name: "my-check-group",
15
+ label: "I would go into battle with:",
16
+ caption: "Qa'pla!",
17
+ disabled: false
18
+ )
19
+ system_arguments = {
20
+ name: name,
21
+ label: label,
22
+ caption: caption,
23
+ disabled: disabled
24
+ }
25
+
26
+ render(Primer::Alpha::CheckBoxGroup.new(**system_arguments)) do |component|
27
+ component.check_box(label: "Jean-Luc Picard", value: "picard")
28
+ component.check_box(label: "Hikaru Sulu", value: "sulu")
29
+ component.check_box(label: "Kathryn Janeway", value: "janeway")
30
+ component.check_box(label: "Benjamin Sisko", value: "sisko")
31
+ end
32
+ end
33
+
34
+ # @label Default
35
+ def default
36
+ render(Primer::Alpha::CheckBoxGroup.new(name: "my-check-group", label: "I would go into battle with:")) do |component|
37
+ component.check_box(label: "Jean-Luc Picard", value: "picard")
38
+ component.check_box(label: "Hikaru Sulu", value: "sulu")
39
+ component.check_box(label: "Kathryn Janeway", value: "janeway")
40
+ component.check_box(label: "Benjamin Sisko", value: "sisko")
41
+ end
42
+ end
43
+
44
+ # @!group Options
45
+ #
46
+ # @label With caption
47
+ def with_caption
48
+ render(Primer::Alpha::CheckBoxGroup.new(caption: "With a caption", name: "my-check-group", label: "I would go into battle with:")) do |component|
49
+ component.check_box(label: "Jean-Luc Picard", value: "picard1")
50
+ component.check_box(label: "Hikaru Sulu", value: "sulu1")
51
+ component.check_box(label: "Kathryn Janeway", value: "janeway1")
52
+ component.check_box(label: "Benjamin Sisko", value: "sisko1")
53
+ end
54
+ end
55
+
56
+ # @label Visually hidden label
57
+ def visually_hide_label
58
+ render(Primer::Alpha::CheckBoxGroup.new(visually_hide_label: true, name: "my-check-group", label: "I would go into battle with:")) do |component|
59
+ component.check_box(label: "Jean-Luc Picard", value: "picard2")
60
+ component.check_box(label: "Hikaru Sulu", value: "sulu2")
61
+ component.check_box(label: "Kathryn Janeway", value: "janeway2")
62
+ component.check_box(label: "Benjamin Sisko", value: "sisko2")
63
+ end
64
+ end
65
+
66
+ # @label Full width
67
+ def full_width
68
+ render(Primer::Alpha::CheckBoxGroup.new(full_width: true, name: "my-check-group", label: "I would go into battle with:")) do |component|
69
+ component.check_box(label: "Jean-Luc Picard", value: "picard3")
70
+ component.check_box(label: "Hikaru Sulu", value: "sulu3")
71
+ component.check_box(label: "Kathryn Janeway", value: "janeway3")
72
+ component.check_box(label: "Benjamin Sisko", value: "sisko3")
73
+ end
74
+ end
75
+
76
+ # @label Disabled
77
+ def disabled
78
+ render(Primer::Alpha::CheckBoxGroup.new(disabled: true, name: "my-check-group", label: "I would go into battle with:")) do |component|
79
+ component.check_box(label: "Jean-Luc Picard", value: "picard4")
80
+ component.check_box(label: "Hikaru Sulu", value: "sulu4")
81
+ component.check_box(label: "Kathryn Janeway", value: "janeway4")
82
+ component.check_box(label: "Benjamin Sisko", value: "sisko4")
83
+ end
84
+ end
85
+ #
86
+ # @!endgroup
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Alpha
5
+ # @label CheckBox
6
+ class CheckBoxPreview < ViewComponent::Preview
7
+ # @label Playground
8
+ #
9
+ # @param name text
10
+ # @param id text
11
+ # @param value text
12
+ # @param label text
13
+ # @param caption text
14
+ # @param visually_hide_label toggle
15
+ # @param disabled toggle
16
+ def playground(
17
+ name: "my-check-box",
18
+ id: nil,
19
+ value: "picard",
20
+ label: "Jean-Luc Picard",
21
+ caption: "Make it so",
22
+ visually_hide_label: false,
23
+ disabled: false
24
+ )
25
+ system_arguments = {
26
+ name: name,
27
+ value: value,
28
+ label: label,
29
+ caption: caption,
30
+ visually_hide_label: visually_hide_label,
31
+ disabled: disabled
32
+ }
33
+
34
+ render(Primer::Alpha::CheckBox.new(**system_arguments))
35
+ end
36
+
37
+ # @label Default
38
+ def default
39
+ render(Primer::Alpha::CheckBox.new(name: "my-check-box", label: "Jean-Luc Picard"))
40
+ end
41
+
42
+ # @!group Options
43
+ #
44
+ # @label With caption
45
+ def with_caption
46
+ render(Primer::Alpha::CheckBox.new(caption: "With a caption", name: "my-check-box1", label: "Jean-Luc Picard"))
47
+ end
48
+
49
+ # @label Visually hidden label
50
+ def visually_hide_label
51
+ render(Primer::Alpha::CheckBox.new(visually_hide_label: true, name: "my-check-box2", label: "Jean-Luc Picard"))
52
+ end
53
+
54
+ # @label Disabled
55
+ def disabled
56
+ render(Primer::Alpha::CheckBox.new(disabled: true, name: "my-check-box4", label: "Jean-Luc Picard"))
57
+ end
58
+ #
59
+ # @!endgroup
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,9 @@
1
+ <%= render(Primer::Alpha::FormControl.new(**system_arguments)) do |component| %>
2
+ <% component.with_input do |input_arguments| %>
3
+ <%= render(Primer::Alpha::SegmentedControl.new("aria-label": "Best character", **input_arguments)) do |seg| %>
4
+ <% seg.with_item(label: "Han Solo", selected: true) %>
5
+ <% seg.with_item(label: "Luke Skywalker") %>
6
+ <% seg.with_item(label: "Leia Organa") %>
7
+ <% end %>
8
+ <% end %>
9
+ <% end %>