govuk_design_system_formbuilder 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (27) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +1 -0
  3. data/Rakefile +0 -0
  4. data/lib/govuk_design_system_formbuilder/base.rb +45 -0
  5. data/lib/govuk_design_system_formbuilder/builder.rb +163 -0
  6. data/lib/govuk_design_system_formbuilder/containers/character_count.rb +38 -0
  7. data/lib/govuk_design_system_formbuilder/containers/check_boxes.rb +15 -0
  8. data/lib/govuk_design_system_formbuilder/containers/fieldset.rb +47 -0
  9. data/lib/govuk_design_system_formbuilder/containers/form_group.rb +27 -0
  10. data/lib/govuk_design_system_formbuilder/containers/radios.rb +23 -0
  11. data/lib/govuk_design_system_formbuilder/elements/check_box.rb +84 -0
  12. data/lib/govuk_design_system_formbuilder/elements/check_boxes/collection_check_box.rb +28 -0
  13. data/lib/govuk_design_system_formbuilder/elements/check_boxes/hint.rb +38 -0
  14. data/lib/govuk_design_system_formbuilder/elements/check_boxes/label.rb +21 -0
  15. data/lib/govuk_design_system_formbuilder/elements/date.rb +89 -0
  16. data/lib/govuk_design_system_formbuilder/elements/error_message.rb +31 -0
  17. data/lib/govuk_design_system_formbuilder/elements/hint.rb +24 -0
  18. data/lib/govuk_design_system_formbuilder/elements/input.rb +77 -0
  19. data/lib/govuk_design_system_formbuilder/elements/label.rb +51 -0
  20. data/lib/govuk_design_system_formbuilder/elements/radio.rb +15 -0
  21. data/lib/govuk_design_system_formbuilder/elements/radios/collection_radio.rb +32 -0
  22. data/lib/govuk_design_system_formbuilder/elements/radios/fieldset_radio.rb +57 -0
  23. data/lib/govuk_design_system_formbuilder/elements/submit.rb +46 -0
  24. data/lib/govuk_design_system_formbuilder/elements/text_area.rb +70 -0
  25. data/lib/govuk_design_system_formbuilder/version.rb +3 -0
  26. data/lib/govuk_design_system_formbuilder.rb +32 -0
  27. metadata +174 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fb762e8a359cd08175a12d4589052ea02fe5d30cdd74b0d970ba8b52c8ac09c2
4
+ data.tar.gz: a190f9073ebcd4d59abb9c62485be024f729343544fbc278c2dbbb3d5949b14a
5
+ SHA512:
6
+ metadata.gz: 20a8147da81aa6add15ec3500ffd849e18ef5f29add89a4d10511d90be78eca08a278315ae17036c5c5ae1e7861408d27133c8f2dcb84faf30e032b7219b142c
7
+ data.tar.gz: 5f1e669b7248b062727a7b960aa4448c216a9ad4d30d5ead7314d8989430925b31fd700ed7cd34d4613e0c81465a358d61d59ca4ff47abc8a161b145338e8879
data/README.md ADDED
@@ -0,0 +1 @@
1
+ # GOV.UK Form Builder
data/Rakefile ADDED
File without changes
@@ -0,0 +1,45 @@
1
+ module GOVUKDesignSystemFormBuilder
2
+ class Base
3
+ def initialize(builder, object_name, attribute_name)
4
+ @builder = builder
5
+ @object_name = object_name
6
+ @attribute_name = attribute_name
7
+ end
8
+
9
+ def html
10
+ fail 'should be overridden'
11
+ end
12
+
13
+ def hint_id
14
+ return nil unless @text.present?
15
+
16
+ @hint_id || [@object_name, @attribute_name, 'hint'].join('-').parameterize
17
+ end
18
+
19
+ def error_id
20
+ return nil unless has_errors?
21
+
22
+ [@object_name, @attribute_name, 'error'].join('-').parameterize
23
+ end
24
+
25
+ def conditional_id
26
+ [@object_name, @attribute_name, @value, 'conditional'].join('-').parameterize
27
+ end
28
+
29
+ def has_errors?
30
+ @builder.object.invalid? &&
31
+ @builder.object.errors.messages.keys.include?(@attribute_name)
32
+ end
33
+
34
+ def attribute_descriptor
35
+ [@object_name, @attribute_name, @value].compact.join('_').parameterize
36
+ end
37
+
38
+ def attribute_identifier
39
+ "%<object_name>s[%<attribute_name>s]" % {
40
+ object_name: @object_name,
41
+ attribute_name: @attribute_name
42
+ }
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,163 @@
1
+ module GOVUKDesignSystemFormBuilder
2
+ module Builder
3
+ def govuk_text_field(attribute_name, hint: {}, label: {}, **args)
4
+ Elements::Input.new(self, object_name, attribute_name, attribute_type: :text, hint: hint, label: label, **args).html
5
+ end
6
+
7
+ def govuk_phone_field(attribute_name, hint: {}, label: {}, **args)
8
+ Elements::Input.new(self, object_name, attribute_name, attribute_type: :phone, hint: hint, label: label, **args).html
9
+ end
10
+
11
+ def govuk_email_field(attribute_name, hint: {}, label: {}, **args)
12
+ Elements::Input.new(self, object_name, attribute_name, attribute_type: :email, hint: hint, label: label, **args).html
13
+ end
14
+
15
+ def govuk_url_field(attribute_name, hint: {}, label: {}, **args)
16
+ Elements::Input.new(self, object_name, attribute_name, attribute_type: :url, hint: hint, label: label, **args).html
17
+ end
18
+
19
+ def govuk_number_field(attribute_name, hint: {}, label: {}, **args)
20
+ Elements::Input.new(self, object_name, attribute_name, attribute_type: :number, hint: hint, label: label, **args).html
21
+ end
22
+
23
+ def govuk_text_area(attribute_name, hint: {}, label: {}, max_words: nil, max_chars: nil, rows: 5, **args)
24
+ Elements::TextArea.new(self, object_name, attribute_name, hint: hint, label: label, max_words: max_words, max_chars: max_chars, rows: rows, **args).html
25
+ end
26
+
27
+ # FIXME #govuk_collection_select args differ from Rails' #collection_select args in that
28
+ # options: and :html_options are keyword arguments. Leaving them as regular args with
29
+ # defaults and following them with keyword args (hint and label) doesn't seem to work
30
+ def govuk_collection_select(attribute_name, collection, value_method, text_method, options: {}, html_options: {}, hint: {}, label: {})
31
+ label_element = Elements::Label.new(self, object_name, attribute_name, label)
32
+ hint_element = Elements::Hint.new(self, object_name, attribute_name, hint)
33
+ error_element = Elements::ErrorMessage.new(self, object_name, attribute_name)
34
+
35
+ Containers::FormGroup.new(self, object_name, attribute_name).html do
36
+ safe_join([
37
+ label_element.html,
38
+ hint_element.html,
39
+ error_element.html,
40
+
41
+ (yield if block_given?),
42
+
43
+ collection_select(
44
+ attribute_name,
45
+ collection,
46
+ value_method,
47
+ text_method,
48
+ options,
49
+ html_options.merge(
50
+ aria: {
51
+ describedby: [
52
+ hint_element.hint_id,
53
+ error_element.error_id
54
+ ].compact.join(' ').presence
55
+ }
56
+ )
57
+ )
58
+ ])
59
+ end
60
+ end
61
+
62
+ def govuk_collection_radio_buttons(attribute_name, collection, value_method, text_method, hint_method = nil, options: { inline: false }, html_options: {}, hint: {}, legend: {})
63
+ hint_element = Elements::Hint.new(self, object_name, attribute_name, hint)
64
+ Containers::FormGroup.new(self, object_name, attribute_name).html do
65
+ safe_join(
66
+ [
67
+ hint_element.html,
68
+ Containers::Fieldset.new(self, object_name, attribute_name, legend: legend, described_by: hint_element.hint_id).html do
69
+ safe_join(
70
+ [
71
+ (yield if block_given?),
72
+ Containers::Radios.new(self, inline: options.dig(:inline)).html do
73
+ safe_join(
74
+ collection.map do |item|
75
+ Elements::Radios::CollectionRadio.new(self, object_name, attribute_name, item, value_method, text_method, hint_method).html
76
+ end
77
+ )
78
+ end
79
+ ].compact
80
+ )
81
+ end
82
+ ]
83
+ )
84
+ end
85
+ end
86
+
87
+ def govuk_radio_buttons_fieldset(attribute_name, options: { inline: false }, hint: {}, legend: {})
88
+ hint_element = Elements::Hint.new(self, object_name, attribute_name, hint)
89
+
90
+ Containers::FormGroup.new(self, object_name, attribute_name).html do
91
+ safe_join([
92
+ hint_element.html,
93
+ Containers::Fieldset.new(self, object_name, attribute_name, legend: legend, described_by: hint_element.hint_id).html do
94
+ Containers::Radios.new(self, inline: options.dig(:inline)).html do
95
+ yield
96
+ end
97
+ end
98
+ ])
99
+ end
100
+ end
101
+
102
+ # only intended for use inside a #govuk_radio_buttons_fieldset
103
+ def govuk_radio_button(attribute_name, value, hint: {}, label: {})
104
+ Elements::Radios::FieldsetRadio.new(self, object_name, attribute_name, value, hint: hint, label: label).html do
105
+ (yield if block_given?)
106
+ end
107
+ end
108
+
109
+ def govuk_radio_divider(text = 'or')
110
+ tag.div(text, class: %w(govuk-radios__divider))
111
+ end
112
+
113
+ def govuk_collection_check_boxes(attribute_name, collection, value_method, text_method, hint_method = nil, html_options: {}, hint: {}, legend: {})
114
+ hint_element = Elements::Hint.new(self, object_name, attribute_name, hint)
115
+ Containers::FormGroup.new(self, object_name, attribute_name).html do
116
+ safe_join(
117
+ [
118
+ hint_element.html,
119
+ (yield if block_given?),
120
+ Containers::Fieldset.new(self, object_name, attribute_name, legend: legend, described_by: hint_element.hint_id).html do
121
+ collection_check_boxes(attribute_name, collection, value_method, text_method) do |check_box|
122
+ Elements::CheckBoxes::CollectionCheckBox.new(self, attribute_name, check_box, hint_method).html
123
+ end
124
+ end
125
+ ]
126
+ )
127
+ end
128
+ end
129
+
130
+ def govuk_check_boxes_fieldset(attribute_name, html_options: {}, hint: {}, legend: {})
131
+ hint_element = Elements::Hint.new(self, object_name, attribute_name, hint)
132
+
133
+ Containers::FormGroup.new(self, object_name, attribute_name).html do
134
+ safe_join([
135
+ hint_element.html,
136
+ Containers::Fieldset.new(self, object_name, attribute_name, legend: legend, described_by: hint_element.hint_id).html do
137
+ Containers::CheckBoxes.new(self).html do
138
+ yield
139
+ end
140
+ end
141
+ ])
142
+ end
143
+ end
144
+
145
+ def govuk_check_box(attribute_name, value, hint: {}, label: {})
146
+ Elements::CheckBox.new(self, object_name, attribute_name, value, hint: hint, label: label).html do
147
+ (yield if block_given?)
148
+ end
149
+ end
150
+
151
+ def govuk_submit(text = 'Continue', warning: false, secondary: false, prevent_double_click: true)
152
+ Elements::Submit.new(self, text, warning: warning, secondary: secondary, prevent_double_click: prevent_double_click).html do
153
+ (yield if block_given?)
154
+ end
155
+ end
156
+
157
+ def govuk_date_field(attribute_name, hint: {}, legend: {})
158
+ Elements::Date.new(self, object_name, attribute_name, hint: hint, legend: legend).html do
159
+ (yield if block_given?)
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,38 @@
1
+ module GOVUKDesignSystemFormBuilder
2
+ module Containers
3
+ class CharacterCount < Base
4
+ def initialize(builder, max_words:, max_chars:)
5
+ @builder = builder
6
+ fail ArgumentError, 'limit can be words or chars' if max_words && max_chars
7
+ @max_words = max_words
8
+ @max_chars = max_chars
9
+ end
10
+
11
+ def html
12
+ return yield unless limit?
13
+
14
+ @builder.content_tag(
15
+ 'div',
16
+ class: 'govuk-character-count',
17
+ data: { module: 'character-count' }.merge(limit)
18
+ ) do
19
+ yield
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def limit
26
+ if @max_words
27
+ { maxwords: @max_words }
28
+ elsif @max_chars
29
+ { maxlength: @max_chars }
30
+ end
31
+ end
32
+
33
+ def limit?
34
+ @max_words || @max_chars
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,15 @@
1
+ module GOVUKDesignSystemFormBuilder
2
+ module Containers
3
+ class CheckBoxes < Base
4
+ def initialize(builder)
5
+ @builder = builder
6
+ end
7
+
8
+ def html
9
+ @builder.content_tag('div', class: 'govuk-checkboxes', data: { module: 'checkboxes' }) do
10
+ yield
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,47 @@
1
+ module GOVUKDesignSystemFormBuilder
2
+ module Containers
3
+ class Fieldset < GOVUKDesignSystemFormBuilder::Base
4
+ LEGEND_DEFAULTS = { text: nil, tag: 'h1', size: 'xl' }.freeze
5
+ LEGEND_SIZES = %w(xl l m s)
6
+
7
+ def initialize(builder, object_name, attribute_name, legend: {}, described_by: nil)
8
+ super(builder, object_name, attribute_name)
9
+ @legend = LEGEND_DEFAULTS.merge(legend)
10
+ @described_by = described_by
11
+ end
12
+
13
+ def html
14
+ @builder.content_tag('div', class: fieldset_classes, aria: { describedby: @described_by }) do
15
+ @builder.safe_join([
16
+ build_legend,
17
+ yield
18
+ ])
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def build_legend
25
+ if @legend.dig(:text).present?
26
+ @builder.content_tag('legend', class: legend_classes) do
27
+ @builder.tag.send(@legend.dig(:tag), @legend.dig(:text), class: legend_heading_classes)
28
+ end
29
+ end
30
+ end
31
+
32
+ def fieldset_classes
33
+ %w(govuk-fieldset)
34
+ end
35
+
36
+ def legend_classes
37
+ size = @legend.dig(:size)
38
+ fail "invalid size #{size}, must be #{LEGEND_SIZES.join(', ')}" unless size.in?(LEGEND_SIZES)
39
+ "govuk-fieldset__legend govuk-fieldset__legend--#{size}"
40
+ end
41
+
42
+ def legend_heading_classes
43
+ %(govuk-fieldset__heading)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,27 @@
1
+ module GOVUKDesignSystemFormBuilder
2
+ module Containers
3
+ class FormGroup < GOVUKDesignSystemFormBuilder::Base
4
+ def initialize(builder, object_name, attribute_name)
5
+ super(builder, object_name, attribute_name)
6
+ end
7
+
8
+ def html
9
+ @builder.content_tag('div', class: form_group_classes) do
10
+ yield
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def form_group_classes
17
+ %w(govuk-form-group).push(form_group_error_classes).compact
18
+ end
19
+
20
+ def form_group_error_classes
21
+ return nil unless has_errors?
22
+
23
+ 'govuk-form-group--error'
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ module GOVUKDesignSystemFormBuilder
2
+ module Containers
3
+ class Radios < GOVUKDesignSystemFormBuilder::Base
4
+ def initialize(builder, inline:)
5
+ @builder = builder
6
+ @inline = inline
7
+ end
8
+
9
+ def html
10
+ @builder.content_tag('div', class: radios_classes, data: { module: 'radios' }) do
11
+ yield
12
+ end
13
+ end
14
+ private
15
+
16
+ def radios_classes
17
+ %w(govuk-radios).tap do |c|
18
+ c.push('govuk-radios--inline') if @inline
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,84 @@
1
+ module GOVUKDesignSystemFormBuilder
2
+ module Elements
3
+ class CheckBox < GOVUKDesignSystemFormBuilder::Base
4
+ def initialize(builder, object_name, attribute_name, value, label:, hint:)
5
+ super(builder, object_name, attribute_name)
6
+ @value = value
7
+ @label = label
8
+ @hint = hint
9
+ end
10
+
11
+ def html(&block)
12
+ @conditional_content, @conditional_id = process(block)
13
+
14
+ @builder.content_tag('div', class: 'govuk-checkboxes__item') do
15
+ @builder.safe_join(
16
+ [
17
+ input,
18
+
19
+ Elements::Label.new(
20
+ @builder,
21
+ @object_name,
22
+ @attribute_name,
23
+ { value: @value.parameterize, text: @value }.merge(@label)
24
+ ).html,
25
+
26
+ Elements::Hint.new(
27
+ @builder,
28
+ @object_name,
29
+ @attribute_name,
30
+ id: hint_id,
31
+ class: check_box_hint_classes,
32
+ **@hint
33
+ ).html,
34
+
35
+ conditional_content
36
+ ]
37
+ )
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def input
44
+ @builder.check_box(
45
+ @attribute_name,
46
+ id: attribute_descriptor,
47
+ class: check_box_classes,
48
+ aria: { describedby: hint_id },
49
+ data: { 'aria-controls' => @conditional_id }
50
+ )
51
+ end
52
+
53
+ def conditional_content
54
+ return nil unless @conditional_content.present?
55
+
56
+ @builder.content_tag('div', class: conditional_classes, id: @conditional_id) do
57
+ @conditional_content
58
+ end
59
+ end
60
+
61
+ def process(block)
62
+ return content = block.call, (content && conditional_id)
63
+ end
64
+
65
+ def conditional_classes
66
+ %w(govuk-checkboxes__conditional govuk-checkboxes__conditional--hidden)
67
+ end
68
+
69
+ def hint_id
70
+ return nil unless @hint['text'].present?
71
+
72
+ [@object_name, @attribute_name, @value, 'hint'].join('-').parameterize
73
+ end
74
+
75
+ def check_box_classes
76
+ %w(govuk-checkbox)
77
+ end
78
+
79
+ def check_box_hint_classes
80
+ %w(govuk-hint govuk-checkboxes__hint)
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,28 @@
1
+ module GOVUKDesignSystemFormBuilder
2
+ module Elements
3
+ module CheckBoxes
4
+ class CollectionCheckBox < GOVUKDesignSystemFormBuilder::Base
5
+ def initialize(builder, attribute, checkbox, hint_method = nil)
6
+ @builder = builder
7
+ @attribute = attribute
8
+ @checkbox = checkbox
9
+ @item = checkbox.object
10
+ @hint = @item.send(hint_method) if hint_method.present?
11
+ end
12
+
13
+ def html
14
+ hint = Hint.new(@builder, @attribute, @checkbox, @hint)
15
+ @builder.content_tag('div', class: 'govuk-checkboxes__item') do
16
+ @builder.safe_join(
17
+ [
18
+ @checkbox.check_box(class: "govuk-checkboxes_input", aria: { describedby: hint.id }),
19
+ Label.new(@checkbox).html,
20
+ hint.html
21
+ ]
22
+ )
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,38 @@
1
+ module GOVUKDesignSystemFormBuilder
2
+ module Elements
3
+ module CheckBoxes
4
+ class Hint
5
+ def initialize(builder, attribute, checkbox, text)
6
+ @builder = builder
7
+ @attribute = attribute
8
+ @checkbox = checkbox
9
+ @text = text
10
+ end
11
+
12
+ def html
13
+ return nil if @text.blank?
14
+
15
+ @builder.tag.span(@text, class: hint_classes, id: id)
16
+ end
17
+
18
+ def id
19
+ return nil if @text.blank?
20
+
21
+ [
22
+ @attribute,
23
+ @checkbox.object.id,
24
+ 'hint'
25
+ ]
26
+ .join('-')
27
+ .parameterize
28
+ end
29
+
30
+ private
31
+
32
+ def hint_classes
33
+ %w(govuk-hint govuk-checkboxes__hint)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,21 @@
1
+ module GOVUKDesignSystemFormBuilder
2
+ module Elements
3
+ module CheckBoxes
4
+ class Label
5
+ def initialize(builder)
6
+ @builder = builder
7
+ end
8
+
9
+ def html
10
+ @builder.label(class: label_classes)
11
+ end
12
+
13
+ private
14
+
15
+ def label_classes
16
+ %w(govuk-label govuk-checkboxes__label)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,89 @@
1
+ module GOVUKDesignSystemFormBuilder
2
+ module Elements
3
+ class Date < GOVUKDesignSystemFormBuilder::Base
4
+ SEGMENTS = { day: '3i', month: '2i', year: '1i' }
5
+
6
+ def initialize(builder, object_name, attribute_name, legend:, hint:)
7
+ super(builder, object_name, attribute_name)
8
+ @legend = legend
9
+ @hint = hint
10
+ end
11
+
12
+ def html
13
+ hint_element = Elements::Hint.new(@builder, @object_name, @attribute_name, @hint)
14
+
15
+ Containers::FormGroup.new(@builder, @object_name, @attribute_name).html do
16
+ Containers::Fieldset.new(@builder, @object_name, @attribute_name, legend: @legend, described_by: hint_element.hint_id).html do
17
+ @builder.safe_join(
18
+ [
19
+ hint_element.html,
20
+ (yield if block_given?),
21
+ @builder.content_tag('div', class: 'govuk-date-input') do
22
+ @builder.safe_join(
23
+ [
24
+ date_input_group(:day, min: 1, max: 31),
25
+ date_input_group(:month, min: 1, max: 12),
26
+ # FIXME there must be more sensible defaults than this!
27
+ date_input_group(:year, min: 1900, max: 2100, width: 4)
28
+ ]
29
+ )
30
+ end
31
+ ]
32
+ )
33
+ end
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def date_input_group(segment, min:, max:, width: 2)
40
+ value = @builder.object.try(@attribute_name).try(segment)
41
+
42
+ @builder.content_tag('div', class: %w(govuk-date-input__item)) do
43
+ @builder.content_tag('div', class: %w(govuk-form-group)) do
44
+ @builder.safe_join(
45
+ [
46
+ @builder.tag.label(
47
+ segment.capitalize,
48
+ class: date_input_label_classes,
49
+ for: date_attribute_descriptor(segment)
50
+ ),
51
+
52
+ @builder.tag.input(
53
+ id: date_attribute_descriptor(segment),
54
+ class: date_input_classes(width),
55
+ for: date_attribute_identifier(segment),
56
+ type: 'number',
57
+ min: min,
58
+ max: max,
59
+ step: 1,
60
+ value: value
61
+ )
62
+ ]
63
+ )
64
+ end
65
+ end
66
+ end
67
+
68
+ def date_input_classes(width)
69
+ %w(govuk-input govuk-date-input__input).push("govuk-input--width-#{width}")
70
+ end
71
+
72
+ def date_input_label_classes
73
+ %w(govuk-label govuk-date-input__label)
74
+ end
75
+
76
+ def date_attribute_descriptor(segment)
77
+ [@object_name, @attribute_name, SEGMENTS.fetch(segment)].join("_")
78
+ end
79
+
80
+ def date_attribute_identifier(segment)
81
+ "%<object_name>s[%<attribute_name>s(%<segment>s)]" % {
82
+ object_name: @object_name,
83
+ attribute_name: @attribute_name,
84
+ segment: SEGMENTS.fetch(segment)
85
+ }
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,31 @@
1
+ module GOVUKDesignSystemFormBuilder
2
+ module Elements
3
+ class ErrorMessage < GOVUKDesignSystemFormBuilder::Base
4
+ def initialize(builder, object_name, attribute_name, options = {})
5
+ super(builder, object_name, attribute_name)
6
+ @options = default_options.merge(options)
7
+ end
8
+
9
+ def html
10
+ return nil unless has_errors?
11
+
12
+ @builder.content_tag('span', class: 'govuk-error-message', id: error_id) do
13
+ @builder.safe_join([
14
+ @builder.tag.span('Error: ', class: 'govuk-visually-hidden'),
15
+ message
16
+ ])
17
+ end
18
+ end
19
+
20
+ def message
21
+ @builder.object.errors.messages[@attribute_name]
22
+ end
23
+
24
+ private
25
+
26
+ def default_options
27
+ {}
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,24 @@
1
+ module GOVUKDesignSystemFormBuilder
2
+ module Elements
3
+ class Hint < GOVUKDesignSystemFormBuilder::Base
4
+ def initialize(builder, object_name, attribute_name, options = {})
5
+ super(builder, object_name, attribute_name)
6
+ @text = options&.dig(:text)
7
+ @extra_classes = options&.dig(:class)
8
+ @hint_id = options&.dig(:id)
9
+ end
10
+
11
+ def html
12
+ return nil unless @text.present?
13
+
14
+ @builder.tag.span(@text, class: hint_classes, id: hint_id)
15
+ end
16
+
17
+ private
18
+
19
+ def hint_classes
20
+ %w(govuk-hint).push(@extra_classes).compact
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,77 @@
1
+ module GOVUKDesignSystemFormBuilder
2
+ module Elements
3
+ class Input < GOVUKDesignSystemFormBuilder::Base
4
+ def initialize(builder, object_name, attribute_name, attribute_type:, hint:, label:, **extra_args)
5
+ super(builder, object_name, attribute_name)
6
+
7
+ @extra_args = extra_args.dup
8
+ @width = @extra_args.delete(:width)
9
+ @builder_method = [attribute_type, 'field'].join('_')
10
+ @label = label
11
+ @hint = hint
12
+ end
13
+
14
+ def html
15
+ hint_element = Elements::Hint.new(@builder, @object_name, @attribute_name, @hint)
16
+ label_element = Elements::Label.new(@builder, @object_name, @attribute_name, @label)
17
+ error_element = Elements::ErrorMessage.new(@builder, @object_name, @attribute_name)
18
+
19
+ Containers::FormGroup.new(@builder, @object_name, @attribute_name).html do
20
+ @builder.safe_join(
21
+ [
22
+ label_element.html,
23
+ hint_element.html,
24
+ error_element.html,
25
+ @builder.send(
26
+ @builder_method,
27
+ @attribute_name,
28
+ class: input_classes,
29
+ name: attribute_descriptor,
30
+ aria: {
31
+ describedby: [
32
+ hint_element.hint_id,
33
+ error_element.error_id
34
+ ].compact.join(' ').presence
35
+ },
36
+ **@extra_args
37
+ )
38
+ ]
39
+ )
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def input_classes
46
+ %w(govuk-input).tap do |classes|
47
+ classes.push(width_classes)
48
+ classes.push('govuk-input--error') if has_errors?
49
+ end
50
+ end
51
+
52
+ def width_classes
53
+ return unless @width.present?
54
+
55
+ case @width
56
+ # fixed (character) widths
57
+ when 20 then 'govuk-input--width-20'
58
+ when 10 then 'govuk-input--width-10'
59
+ when 5 then 'govuk-input--width-5'
60
+ when 4 then 'govuk-input--width-4'
61
+ when 3 then 'govuk-input--width-3'
62
+ when 2 then 'govuk-input--width-2'
63
+
64
+ # fluid widths
65
+ when 'full' then 'govuk-!-width-full'
66
+ when 'three-quarters' then 'govuk-!-width-three-quarters'
67
+ when 'two-thirds' then 'govuk-!-width-two-thirds'
68
+ when 'one-half' then 'govuk-!-width-one-half'
69
+ when 'one-third' then 'govuk-!-width-one-third'
70
+ when 'one-quarter' then 'govuk-!-width-one-quarter'
71
+
72
+ else fail "invalid width #{@width}"
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,51 @@
1
+ module GOVUKDesignSystemFormBuilder
2
+ module Elements
3
+ class Label < GOVUKDesignSystemFormBuilder::Base
4
+ def initialize(builder, object_name, attribute_name, text: nil, value: nil, size: 'regular', weight: 'regular')
5
+ super(builder, object_name, attribute_name)
6
+
7
+ @text = label_text(text)
8
+ @value = value # used by attribute_descriptor
9
+ @size_class = label_size_class(size)
10
+ @weight_class = label_weight_class(weight)
11
+ end
12
+
13
+ def html
14
+ return nil unless @text.present?
15
+
16
+ @builder.label(
17
+ @attribute_name,
18
+ @text,
19
+ value: @value,
20
+ class: %w(govuk-label).push(@size_class, @weight_class).compact
21
+ )
22
+ end
23
+
24
+ private
25
+
26
+ def label_text(option_text)
27
+ [option_text, @value, @attribute_name.capitalize].compact.first
28
+ end
29
+
30
+ def label_size_class(size)
31
+ case size
32
+ when 'large' then "govuk-!-font-size-48"
33
+ when 'medium' then "govuk-!-font-size-36"
34
+ when 'small' then "govuk-!-font-size-27"
35
+ when 'regular' then nil
36
+ else
37
+ fail 'size must be either large, medium, small or regular'
38
+ end
39
+ end
40
+
41
+ def label_weight_class(weight)
42
+ case weight
43
+ when 'bold' then "govuk-!-font-weight-bold"
44
+ when 'regular' then nil
45
+ else
46
+ fail 'weight must be bold or regular'
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,15 @@
1
+ module GOVUKDesignSystemFormBuilder
2
+ module Elements
3
+ class Radio < GOVUKDesignSystemFormBuilder::Base
4
+ def hint_id
5
+ return nil unless @hint.present?
6
+
7
+ [@object_name, @attribute_name, @value, 'hint'].join('-').parameterize
8
+ end
9
+
10
+ def radio_hint_classes
11
+ %w(govuk-hint govuk-radios__hint)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,32 @@
1
+ module GOVUKDesignSystemFormBuilder
2
+ module Elements
3
+ module Radios
4
+ class CollectionRadio < Radio
5
+ def initialize(builder, object_name, attribute_name, item, value_method, text_method, hint_method)
6
+ super(builder, object_name, attribute_name)
7
+ @item = item
8
+ @value = item.send(value_method)
9
+ @text = item.send(text_method)
10
+ @hint = item.send(hint_method) if hint_method.present?
11
+ end
12
+
13
+ def html
14
+ @builder.content_tag('div', class: 'govuk-radios__item') do
15
+ @builder.safe_join(
16
+ [
17
+ @builder.radio_button(
18
+ @attribute_name,
19
+ @value,
20
+ id: attribute_descriptor,
21
+ aria: { describedby: hint_id }
22
+ ),
23
+ Elements::Label.new(@builder, @object_name, @attribute_name, text: @text, value: @value).html,
24
+ Elements::Hint.new(@builder, @object_name, @attribute_name, id: hint_id, class: radio_hint_classes, text: @hint).html,
25
+ ].compact
26
+ )
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,57 @@
1
+ module GOVUKDesignSystemFormBuilder
2
+ module Elements
3
+ module Radios
4
+ class FieldsetRadio < Radio
5
+ def initialize(builder, object_name, attribute_name, value, label:, hint:)
6
+ super(builder, object_name, attribute_name)
7
+ @value = value
8
+ @label = label
9
+ @hint = hint
10
+ end
11
+
12
+ def html(&block)
13
+ @conditional_content, @conditional_id = process(block)
14
+
15
+ @builder.content_tag('div', class: 'govuk-radios__item') do
16
+ @builder.safe_join(
17
+ [
18
+ input,
19
+ Elements::Label.new(@builder, @object_name, @attribute_name, @label).html,
20
+ Elements::Hint.new(@builder, @object_name, @attribute_name, id: hint_id, class: radio_hint_classes, text: @hint).html,
21
+ conditional_content
22
+ ]
23
+ )
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def input
30
+ @builder.radio_button(
31
+ @attribute_name,
32
+ @value,
33
+ id: attribute_descriptor,
34
+ aria: { describedby: hint_id },
35
+ data: { 'aria-controls' => @conditional_id }
36
+ )
37
+ end
38
+
39
+ def conditional_content
40
+ return nil unless @conditional_content.present?
41
+
42
+ @builder.content_tag('div', class: conditional_classes, id: @conditional_id) do
43
+ @conditional_content
44
+ end
45
+ end
46
+
47
+ def process(block)
48
+ return content = block.call, (content && conditional_id)
49
+ end
50
+
51
+ def conditional_classes
52
+ %w(govuk-radios__conditional govuk-radios__conditional--hidden)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,46 @@
1
+ module GOVUKDesignSystemFormBuilder
2
+ module Elements
3
+ class Submit < GOVUKDesignSystemFormBuilder::Base
4
+ def initialize(builder, text, warning:, secondary:, prevent_double_click:)
5
+ @builder = builder
6
+ @text = text
7
+ @prevent_double_click = prevent_double_click
8
+
9
+ fail ArgumentError, 'buttons can be warning or secondary' if (warning && secondary)
10
+ @warning = warning
11
+ @secondary = secondary
12
+ end
13
+
14
+ def html(&block)
15
+ content = process(block)
16
+
17
+ @builder.content_tag('div', class: %w(govuk-form-group)) do
18
+ @builder.safe_join([
19
+ @builder.submit(@text, class: submit_button_classes(content.present?), **extra_args),
20
+ content
21
+ ])
22
+ end
23
+ end
24
+ private
25
+
26
+ def submit_button_classes(content_present)
27
+ %w(govuk-button).tap do |classes|
28
+ classes.push('govuk-button--warning') if @warning
29
+ classes.push('govuk-button--secondary') if @secondary
30
+
31
+ # NOTE only this input will receive a right margin, block
32
+ # contents must be addressed individually
33
+ classes.push('govuk-!-margin-right-1') if content_present
34
+ end
35
+ end
36
+
37
+ def extra_args
38
+ { data: { 'prevent-double-click' => (@prevent_double_click || nil) }.compact }
39
+ end
40
+
41
+ def process(block)
42
+ block.call
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,70 @@
1
+ module GOVUKDesignSystemFormBuilder
2
+ module Elements
3
+ class TextArea < Base
4
+ def initialize(builder, object_name, attribute_name, hint:, label:, rows:, max_words:, max_chars:, **extra_args)
5
+ super(builder, object_name, attribute_name)
6
+ @label = label
7
+ @hint = hint
8
+ @extra_args = extra_args
9
+ @max_words = max_words
10
+ @max_chars = max_chars
11
+ @rows = rows
12
+ end
13
+
14
+ def html
15
+ hint_element = Elements::Hint.new(@builder, @object_name, @attribute_name, @hint)
16
+ label_element = Elements::Label.new(@builder, @object_name, @attribute_name, @label)
17
+ error_element = Elements::ErrorMessage.new(@builder, @object_name, @attribute_name)
18
+
19
+ Containers::CharacterCount.new(@builder, max_words: @max_words, max_chars: @max_chars).html do
20
+ Containers::FormGroup.new(@builder, @object_name, @attribute_name).html do
21
+ @builder.safe_join(
22
+ [
23
+ label_element.html,
24
+ hint_element.html,
25
+ error_element.html,
26
+ @builder.text_area(
27
+ @attribute_name,
28
+ class: govuk_textarea_classes,
29
+ **@extra_args.merge(rows: @rows)
30
+ ),
31
+ character_count_info
32
+ ]
33
+ )
34
+ end
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def govuk_textarea_classes
41
+ %w(govuk-textarea).tap do |classes|
42
+ classes.push('govuk-textarea--error') if has_errors?
43
+ classes.push('js-character-count') if limit?
44
+ end
45
+ end
46
+
47
+ def character_count_info
48
+ return nil unless limit?
49
+
50
+ @builder.tag.span(
51
+ "You can enter up to #{character_count_description}",
52
+ class: %w(govuk-hint govuk-character-count__message),
53
+ aria: { live: 'polite' }
54
+ )
55
+ end
56
+
57
+ def character_count_description
58
+ if @max_words
59
+ "#{@max_words} words"
60
+ elsif @max_chars
61
+ "#{@max_chars} characters"
62
+ end
63
+ end
64
+
65
+ def limit?
66
+ @max_words || @max_chars
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,3 @@
1
+ module GOVUKDesignSystemFormBuilder
2
+ VERSION = '0.7.0'.freeze
3
+ end
@@ -0,0 +1,32 @@
1
+ require 'govuk_design_system_formbuilder/version'
2
+ require 'govuk_design_system_formbuilder/builder'
3
+ require 'govuk_design_system_formbuilder/base'
4
+
5
+ require 'govuk_design_system_formbuilder/elements/hint'
6
+ require 'govuk_design_system_formbuilder/elements/label'
7
+ require 'govuk_design_system_formbuilder/elements/input'
8
+ require 'govuk_design_system_formbuilder/elements/date'
9
+ require 'govuk_design_system_formbuilder/elements/radio'
10
+ require 'govuk_design_system_formbuilder/elements/radios/collection_radio'
11
+ require 'govuk_design_system_formbuilder/elements/radios/fieldset_radio'
12
+ require 'govuk_design_system_formbuilder/elements/check_box'
13
+ require 'govuk_design_system_formbuilder/elements/check_boxes/collection_check_box'
14
+ require 'govuk_design_system_formbuilder/elements/check_boxes/label'
15
+ require 'govuk_design_system_formbuilder/elements/check_boxes/hint'
16
+ require 'govuk_design_system_formbuilder/elements/error_message'
17
+ require 'govuk_design_system_formbuilder/elements/submit'
18
+ require 'govuk_design_system_formbuilder/elements/text_area'
19
+
20
+ require 'govuk_design_system_formbuilder/containers/form_group'
21
+ require 'govuk_design_system_formbuilder/containers/fieldset'
22
+ require 'govuk_design_system_formbuilder/containers/radios'
23
+ require 'govuk_design_system_formbuilder/containers/check_boxes'
24
+ require 'govuk_design_system_formbuilder/containers/character_count'
25
+
26
+ module GOVUKDesignSystemFormBuilder
27
+ class FormBuilder < ActionView::Helpers::FormBuilder
28
+ delegate :content_tag, :tag, :safe_join, :safe_concat, :capture, :link_to, to: :@template
29
+
30
+ include GOVUKDesignSystemFormBuilder::Builder
31
+ end
32
+ end
metadata ADDED
@@ -0,0 +1,174 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: govuk_design_system_formbuilder
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.7.0
5
+ platform: ruby
6
+ authors:
7
+ - Peter Yates
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-06-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.2'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 5.2.3
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '5.2'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 5.2.3
33
+ - !ruby/object:Gem::Dependency
34
+ name: sqlite3
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec-rails
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rspec-html-matchers
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: govuk-lint
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: pry
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: pry-byebug
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ description: A Rails form builder that generates form inputs adhering to the GOV.UK
118
+ Design System
119
+ email:
120
+ - peter.yates@graphia.co.uk
121
+ executables: []
122
+ extensions: []
123
+ extra_rdoc_files: []
124
+ files:
125
+ - README.md
126
+ - Rakefile
127
+ - lib/govuk_design_system_formbuilder.rb
128
+ - lib/govuk_design_system_formbuilder/base.rb
129
+ - lib/govuk_design_system_formbuilder/builder.rb
130
+ - lib/govuk_design_system_formbuilder/containers/character_count.rb
131
+ - lib/govuk_design_system_formbuilder/containers/check_boxes.rb
132
+ - lib/govuk_design_system_formbuilder/containers/fieldset.rb
133
+ - lib/govuk_design_system_formbuilder/containers/form_group.rb
134
+ - lib/govuk_design_system_formbuilder/containers/radios.rb
135
+ - lib/govuk_design_system_formbuilder/elements/check_box.rb
136
+ - lib/govuk_design_system_formbuilder/elements/check_boxes/collection_check_box.rb
137
+ - lib/govuk_design_system_formbuilder/elements/check_boxes/hint.rb
138
+ - lib/govuk_design_system_formbuilder/elements/check_boxes/label.rb
139
+ - lib/govuk_design_system_formbuilder/elements/date.rb
140
+ - lib/govuk_design_system_formbuilder/elements/error_message.rb
141
+ - lib/govuk_design_system_formbuilder/elements/hint.rb
142
+ - lib/govuk_design_system_formbuilder/elements/input.rb
143
+ - lib/govuk_design_system_formbuilder/elements/label.rb
144
+ - lib/govuk_design_system_formbuilder/elements/radio.rb
145
+ - lib/govuk_design_system_formbuilder/elements/radios/collection_radio.rb
146
+ - lib/govuk_design_system_formbuilder/elements/radios/fieldset_radio.rb
147
+ - lib/govuk_design_system_formbuilder/elements/submit.rb
148
+ - lib/govuk_design_system_formbuilder/elements/text_area.rb
149
+ - lib/govuk_design_system_formbuilder/version.rb
150
+ homepage: https://github.com/DFE-Digital
151
+ licenses:
152
+ - MIT
153
+ metadata: {}
154
+ post_install_message:
155
+ rdoc_options: []
156
+ require_paths:
157
+ - lib
158
+ required_ruby_version: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ required_rubygems_version: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ requirements: []
169
+ rubyforge_project:
170
+ rubygems_version: 2.7.6
171
+ signing_key:
172
+ specification_version: 4
173
+ summary: GOV.UK-compliant Rails form builder
174
+ test_files: []