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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/app/assets/styles/primer_view_components.css +2 -2
- data/app/assets/styles/primer_view_components.css.map +1 -1
- data/app/components/primer/alpha/action_list.css.json +123 -1
- data/app/components/primer/alpha/auto_complete.css.json +23 -1
- data/app/components/primer/alpha/banner.css.json +24 -1
- data/app/components/primer/alpha/button_marketing.css.json +33 -1
- data/app/components/primer/alpha/check_box.rb +74 -0
- data/app/components/primer/alpha/check_box_group.rb +36 -0
- data/app/components/primer/alpha/dialog.css.json +82 -1
- data/app/components/primer/alpha/dialog.rb +1 -1
- data/app/components/primer/alpha/dropdown.css.json +40 -1
- data/app/components/primer/alpha/form_button.rb +32 -0
- data/app/components/primer/alpha/form_control.html.erb +26 -0
- data/app/components/primer/alpha/form_control.rb +105 -0
- data/app/components/primer/alpha/layout.css.json +80 -1
- data/app/components/primer/alpha/menu.css.json +28 -1
- data/app/components/primer/alpha/multi_input.rb +81 -0
- data/app/components/primer/alpha/radio_button.rb +25 -0
- data/app/components/primer/alpha/radio_button_group.rb +36 -0
- data/app/components/primer/alpha/segmented_control.css +1 -1
- data/app/components/primer/alpha/segmented_control.css.json +31 -1
- data/app/components/primer/alpha/segmented_control.css.map +1 -1
- data/app/components/primer/alpha/segmented_control.pcss +43 -12
- data/app/components/primer/alpha/select.rb +37 -0
- data/app/components/primer/alpha/submit_button.rb +32 -0
- data/app/components/primer/alpha/tab_nav.css.json +24 -1
- data/app/components/primer/alpha/tab_panels.rb +7 -0
- data/app/components/primer/alpha/text_area.rb +24 -0
- data/app/components/primer/alpha/text_field.css +2 -2
- data/app/components/primer/alpha/text_field.css.json +134 -1
- data/app/components/primer/alpha/text_field.css.map +1 -1
- data/app/components/primer/alpha/text_field.pcss +27 -0
- data/app/components/primer/alpha/text_field.rb +15 -20
- data/app/components/primer/alpha/toggle_switch.css +1 -1
- data/app/components/primer/alpha/toggle_switch.css.json +40 -1
- data/app/components/primer/alpha/toggle_switch.css.map +1 -1
- data/app/components/primer/alpha/toggle_switch.pcss +31 -61
- data/app/components/primer/alpha/underline_nav.css.json +28 -1
- data/app/components/primer/beta/avatar.css.json +17 -1
- data/app/components/primer/beta/avatar_stack.css.json +28 -1
- data/app/components/primer/beta/blankslate.css.json +22 -1
- data/app/components/primer/beta/border_box.css.json +54 -1
- data/app/components/primer/beta/breadcrumbs.css.json +11 -1
- data/app/components/primer/beta/button.css.json +71 -1
- data/app/components/primer/beta/counter.css.json +10 -1
- data/app/components/primer/beta/flash.css.json +27 -1
- data/app/components/primer/beta/label.css.json +25 -1
- data/app/components/primer/beta/link.css.json +19 -1
- data/app/components/primer/beta/popover.css.json +39 -1
- data/app/components/primer/beta/progress_bar.css.json +10 -1
- data/app/components/primer/beta/state.css.json +13 -1
- data/app/components/primer/beta/subhead.css.json +12 -1
- data/app/components/primer/beta/timeline_item.css.json +16 -1
- data/app/components/primer/beta/truncate.css.json +12 -1
- data/app/components/primer/component.rb +10 -2
- data/app/components/primer/truncate.css.json +13 -1
- data/app/forms/{select_list_form.rb → select_form.rb} +1 -1
- data/app/lib/primer/css/layout.css.json +316 -1
- data/app/lib/primer/css/utilities.css.json +1659 -1
- data/lib/primer/form_components.rb +26 -6
- data/lib/primer/forms/builder.rb +1 -17
- data/lib/primer/forms/button.rb +4 -1
- data/lib/primer/forms/check_box_group.html.erb +14 -9
- data/lib/primer/forms/check_box_group.rb +5 -0
- data/lib/primer/forms/dsl/check_box_group_input.rb +3 -4
- data/lib/primer/forms/dsl/input.rb +33 -2
- data/lib/primer/forms/dsl/input_methods.rb +49 -1
- data/lib/primer/forms/dsl/radio_button_group_input.rb +2 -3
- data/lib/primer/forms/dsl/{select_list_input.rb → select_input.rb} +2 -2
- data/lib/primer/forms/dsl/text_field_input.rb +7 -5
- data/lib/primer/forms/form_control.rb +0 -1
- data/lib/primer/forms/group.html.erb +1 -1
- data/lib/primer/forms/multi.html.erb +8 -6
- data/lib/primer/forms/multi.rb +2 -0
- data/lib/primer/forms/radio_button_group.html.erb +14 -9
- data/lib/primer/forms/radio_button_group.rb +5 -0
- data/lib/primer/forms/{select_list.html.erb → select.html.erb} +0 -0
- data/lib/primer/forms/{select_list.rb → select.rb} +2 -2
- data/lib/primer/forms/spacing_wrapper.html.erb +1 -1
- data/lib/primer/forms/text_area.rb +1 -1
- data/lib/primer/forms/text_field.rb +5 -1
- data/lib/primer/forms/utils.rb +20 -0
- data/lib/primer/view_components/engine.rb +1 -1
- data/lib/primer/view_components/version.rb +1 -1
- data/lib/primer/yard/backend.rb +1 -15
- data/lib/primer/yard/component_manifest.rb +44 -25
- data/lib/primer/yard/component_ref.rb +40 -0
- data/lib/primer/yard/docs_helper.rb +16 -2
- data/lib/primer/yard/legacy_gatsby_backend.rb +9 -15
- data/lib/primer/yard/lookbook_docs_helper.rb +32 -0
- data/lib/primer/yard/lookbook_pages_backend.rb +194 -0
- data/lib/primer/yard/registry.rb +6 -21
- data/lib/primer/yard/renders_many_handler.rb +1 -1
- data/lib/primer/yard/renders_one_handler.rb +1 -1
- data/lib/primer/yard.rb +14 -0
- data/lib/tasks/docs.rake +26 -13
- data/previews/pages/forms/01_introduction.md.erb +44 -0
- data/previews/pages/forms/02_getting_started.md.erb +125 -0
- data/previews/pages/forms/03_caption_templates.md.erb +30 -0
- data/previews/pages/forms/04_after_content.md.erb +39 -0
- data/previews/pages/forms/05_groups_layouts.md.erb +22 -0
- data/previews/pages/forms/06_miscellaneous_inputs.md.erb +43 -0
- data/previews/pages/forms/07_toggle_switch_forms.md.erb +58 -0
- data/previews/pages/forms/08_validations.md.erb +28 -0
- data/previews/pages/forms/09_compound_forms.md.erb +97 -0
- data/previews/primer/alpha/check_box_group_preview.rb +89 -0
- data/previews/primer/alpha/check_box_preview.rb +62 -0
- data/previews/primer/alpha/form_control_preview/playground.html.erb +9 -0
- data/previews/primer/alpha/form_control_preview.rb +106 -0
- data/previews/primer/alpha/multi_input_preview/playground.html.erb +41 -0
- data/previews/primer/alpha/multi_input_preview.rb +80 -0
- data/previews/primer/alpha/radio_button_group_preview.rb +83 -0
- data/previews/primer/alpha/radio_button_preview.rb +62 -0
- data/previews/primer/alpha/select_preview.rb +130 -0
- data/previews/primer/alpha/text_area_preview.rb +87 -0
- data/previews/primer/alpha/text_field_preview.rb +10 -1
- data/previews/primer/forms/forms_preview/example_toggle_switch_form.html.erb +2 -2
- data/previews/primer/forms/forms_preview/{select_list_form.html.erb → select_form.html.erb} +1 -1
- data/previews/primer/forms/forms_preview.rb +3 -1
- data/static/arguments.json +1358 -1328
- data/static/audited_at.json +10 -0
- data/static/constants.json +20 -0
- data/static/previews.json +218 -40
- data/static/statuses.json +10 -0
- 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 %>
|