govuk_design_system_formbuilder 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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: []