primer_view_components 0.0.84 → 0.0.87

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/app/assets/javascripts/primer_view_components.js +1 -1
  4. data/app/assets/javascripts/primer_view_components.js.map +1 -1
  5. data/app/components/primer/alpha/auto_complete/item.rb +1 -1
  6. data/app/components/primer/alpha/auto_complete.rb +1 -1
  7. data/app/components/primer/alpha/button_marketing.rb +1 -1
  8. data/app/components/primer/alpha/text_field.rb +105 -0
  9. data/app/components/primer/alpha/tool-tip-element.d.ts +3 -1
  10. data/app/components/primer/alpha/tool-tip-element.js +20 -13
  11. data/app/components/primer/alpha/tool-tip-element.ts +23 -14
  12. data/app/components/primer/alpha/tooltip.rb +1 -1
  13. data/app/components/primer/beta/base_button.rb +47 -0
  14. data/app/components/primer/{alpha → beta}/border_box/header.html.erb +0 -0
  15. data/app/components/primer/{alpha → beta}/border_box/header.rb +6 -6
  16. data/app/components/primer/{border_box_component.html.erb → beta/border_box.html.erb} +0 -0
  17. data/app/components/primer/beta/border_box.rb +147 -0
  18. data/app/components/primer/blankslate_component.html.erb +0 -5
  19. data/app/components/primer/border_box_component.rb +2 -140
  20. data/app/components/primer/button_component.html.erb +12 -4
  21. data/app/components/primer/button_component.rb +2 -2
  22. data/app/components/primer/clipboard_copy.rb +6 -2
  23. data/app/components/primer/close_button.rb +1 -1
  24. data/app/components/primer/hellip_button.rb +1 -1
  25. data/app/components/primer/icon_button.html.erb +6 -0
  26. data/app/components/primer/icon_button.rb +46 -10
  27. data/app/components/primer/link_component.rb +12 -5
  28. data/app/helpers/primer/form_helper.rb +10 -0
  29. data/app/lib/primer/join_style_arguments_helper.rb +1 -1
  30. data/lib/primer/classify/utilities.rb +3 -6
  31. data/lib/primer/form_components.rb +36 -0
  32. data/lib/primer/forms/acts_as_component.rb +118 -0
  33. data/lib/primer/forms/base.html.erb +8 -0
  34. data/lib/primer/forms/base.rb +142 -0
  35. data/lib/primer/forms/base_component.rb +62 -0
  36. data/lib/primer/forms/buffer_rewriter.rb +51 -0
  37. data/lib/primer/forms/builder.rb +48 -0
  38. data/lib/primer/forms/caption.html.erb +10 -0
  39. data/lib/primer/forms/caption.rb +29 -0
  40. data/lib/primer/forms/check_box.html.erb +9 -0
  41. data/lib/primer/forms/check_box.rb +16 -0
  42. data/lib/primer/forms/check_box_group.html.erb +12 -0
  43. data/lib/primer/forms/check_box_group.rb +14 -0
  44. data/lib/primer/forms/dsl/check_box_group_input.rb +41 -0
  45. data/lib/primer/forms/dsl/check_box_input.rb +27 -0
  46. data/lib/primer/forms/dsl/form_object.rb +25 -0
  47. data/lib/primer/forms/dsl/form_reference_input.rb +36 -0
  48. data/lib/primer/forms/dsl/hidden_input.rb +29 -0
  49. data/lib/primer/forms/dsl/input.rb +237 -0
  50. data/lib/primer/forms/dsl/input_group.rb +34 -0
  51. data/lib/primer/forms/dsl/input_methods.rb +86 -0
  52. data/lib/primer/forms/dsl/multi_input.rb +58 -0
  53. data/lib/primer/forms/dsl/radio_button_group_input.rb +38 -0
  54. data/lib/primer/forms/dsl/radio_button_input.rb +37 -0
  55. data/lib/primer/forms/dsl/select_list_input.rb +53 -0
  56. data/lib/primer/forms/dsl/submit_button_input.rb +28 -0
  57. data/lib/primer/forms/dsl/text_area_input.rb +33 -0
  58. data/lib/primer/forms/dsl/text_field_input.rb +65 -0
  59. data/lib/primer/forms/form_control.html.erb +18 -0
  60. data/lib/primer/forms/form_control.rb +23 -0
  61. data/lib/primer/forms/form_list.html.erb +5 -0
  62. data/lib/primer/forms/form_list.rb +21 -0
  63. data/lib/primer/forms/form_reference.html.erb +3 -0
  64. data/lib/primer/forms/form_reference.rb +14 -0
  65. data/lib/primer/forms/group.html.erb +5 -0
  66. data/lib/primer/forms/group.rb +27 -0
  67. data/lib/primer/forms/hidden_field.html.erb +1 -0
  68. data/lib/primer/forms/hidden_field.rb +15 -0
  69. data/lib/primer/forms/multi.html.erb +3 -0
  70. data/lib/primer/forms/multi.rb +14 -0
  71. data/lib/primer/forms/radio_button.html.erb +14 -0
  72. data/lib/primer/forms/radio_button.rb +29 -0
  73. data/lib/primer/forms/radio_button_group.html.erb +12 -0
  74. data/lib/primer/forms/radio_button_group.rb +14 -0
  75. data/lib/primer/forms/select_list.html.erb +5 -0
  76. data/lib/primer/forms/select_list.rb +26 -0
  77. data/lib/primer/forms/separator.html.erb +1 -0
  78. data/lib/primer/forms/separator.rb +8 -0
  79. data/lib/primer/forms/spacing_wrapper.html.erb +3 -0
  80. data/lib/primer/forms/spacing_wrapper.rb +8 -0
  81. data/lib/primer/forms/submit_button.html.erb +4 -0
  82. data/lib/primer/forms/submit_button.rb +50 -0
  83. data/lib/primer/forms/text_area.html.erb +5 -0
  84. data/lib/primer/forms/text_area.rb +16 -0
  85. data/lib/primer/forms/text_field.html.erb +19 -0
  86. data/lib/primer/forms/text_field.rb +14 -0
  87. data/lib/primer/view_components/engine.rb +34 -0
  88. data/lib/primer/view_components/linters/argument_mappers/button.rb +2 -2
  89. data/lib/primer/view_components/linters/button_component_migration_counter.rb +1 -1
  90. data/lib/primer/view_components/linters/helpers/deprecated_components_helpers.rb +2 -8
  91. data/lib/primer/view_components/version.rb +1 -1
  92. data/lib/rubocop/cop/primer/component_name_migration.rb +3 -0
  93. data/lib/rubocop/cop/primer/deprecated_arguments.rb +0 -10
  94. data/lib/tasks/deprecated.rake +22 -0
  95. data/lib/tasks/docs.rake +5 -3
  96. data/static/arguments.yml +148 -39
  97. data/static/audited_at.json +4 -2
  98. data/static/classes.yml +2 -1
  99. data/static/constants.json +44 -39
  100. data/static/statuses.json +7 -5
  101. metadata +69 -8
  102. data/app/components/primer/base_button.rb +0 -43
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Forms
5
+ # :nodoc:
6
+ class Base
7
+ extend ActsAsComponent
8
+
9
+ renders_template File.join(__dir__, "base.html.erb"), :render_base_form
10
+
11
+ class << self
12
+ attr_reader :has_after_content, :__vcf_form_block, :__vcf_builder
13
+ alias after_content? has_after_content
14
+
15
+ def form(&block)
16
+ @__vcf_form_block = block
17
+ end
18
+
19
+ def new(builder, **options)
20
+ if builder && !builder.is_a?(Primer::Forms::Builder)
21
+ raise ArgumentError, "please pass an instance of Primer::Forms::Builder when "\
22
+ "constructing a form object (consider using the `primer_form_with` helper)"
23
+ end
24
+
25
+ allocate.tap do |form|
26
+ form.instance_variable_set(:@builder, builder)
27
+ form.send(:initialize, **options)
28
+ end
29
+ end
30
+
31
+ def inherited(base)
32
+ form_path = const_source_location(base.name)
33
+ return unless form_path
34
+
35
+ base.template_root_path = File.join(File.dirname(form_path), base.name.demodulize.underscore)
36
+
37
+ base.renders_template "after_content.html.erb" do
38
+ base.instance_variable_set(:@has_after_content, true)
39
+ end
40
+
41
+ base.renders_templates "*_caption.html.erb" do |path|
42
+ base.fields_with_caption_templates << File.basename(path).chomp("_caption.html.erb").to_sym
43
+ end
44
+ end
45
+
46
+ def caption_template?(field_name)
47
+ fields_with_caption_templates.include?(field_name)
48
+ end
49
+
50
+ def fields_with_caption_templates
51
+ @fields_with_caption_templates ||= []
52
+ end
53
+
54
+ private
55
+
56
+ # Unfortunately this bug (https://github.com/ruby/ruby/pull/5646) prevents us from using
57
+ # Ruby's native Module.const_source_location. Instead we have to fudge it by searching
58
+ # for the file in the configured autoload paths. Doing so relies on Rails' autoloading
59
+ # conventions, so it should work ok. Zeitwerk also has this information but lacks a
60
+ # public API to map constants to source files.
61
+ def const_source_location(class_name)
62
+ # NOTE: underscore respects namespacing, i.e. will convert Foo::Bar to foo/bar.
63
+ class_path = "#{class_name.underscore}.rb"
64
+
65
+ ActiveSupport::Dependencies.autoload_paths.each do |autoload_path|
66
+ absolute_path = File.join(autoload_path, class_path)
67
+ return absolute_path if File.exist?(absolute_path)
68
+ end
69
+
70
+ nil
71
+ end
72
+ end
73
+
74
+ def inputs
75
+ @inputs ||= form_object.inputs.map do |input|
76
+ next input unless input.input?
77
+
78
+ # wrap inputs in a group (unless they are already groups)
79
+ if input.type == :group
80
+ input
81
+ else
82
+ Primer::Forms::Dsl::InputGroup.new(builder: @builder, form: self) do |group|
83
+ group.send(:add_input, input)
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ def each_input_in(root_input, &block)
90
+ return enum_for(__method__, root_input) unless block
91
+
92
+ root_input.inputs.each do |input|
93
+ if input.respond_to?(:inputs)
94
+ each_input_in(input, &block)
95
+ else
96
+ yield input
97
+ end
98
+ end
99
+ end
100
+
101
+ def before_render
102
+ each_input_in(self) do |input|
103
+ if input.input? && input.invalid? && input.focusable?
104
+ input.autofocus!
105
+ break
106
+ end
107
+ end
108
+ end
109
+
110
+ def caption_template?(*args)
111
+ self.class.caption_template?(*args)
112
+ end
113
+
114
+ def after_content?(*args)
115
+ self.class.after_content?(*args)
116
+ end
117
+
118
+ def render_caption_template(name)
119
+ send(:"render_#{name}_caption")
120
+ end
121
+
122
+ def perform_render(&_block)
123
+ Base.compile!
124
+ self.class.compile!
125
+
126
+ render_base_form
127
+ end
128
+
129
+ private
130
+
131
+ def form_object
132
+ # rubocop:disable Naming/MemoizedInstanceVariableName
133
+ @__pf_form_object ||= Primer::Forms::Dsl::FormObject.new(builder: @builder, form: self).tap do |obj|
134
+ # compile before adding inputs so caption templates are identified
135
+ self.class.compile!
136
+ instance_exec(obj, &self.class.__vcf_form_block)
137
+ end
138
+ # rubocop:enable Naming/MemoizedInstanceVariableName
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "primer/class_name_helper"
4
+
5
+ module Primer
6
+ module Forms
7
+ # :nodoc:
8
+ class BaseComponent
9
+ include Primer::ClassNameHelper
10
+ extend ActsAsComponent
11
+
12
+ def self.inherited(base)
13
+ base.renders_template File.join(__dir__, "#{base.name.demodulize.underscore}.html.erb"), :render_template
14
+ end
15
+
16
+ delegate :required?, :disabled?, :hidden?, to: :@input
17
+
18
+ def perform_render(&block)
19
+ @__prf_content_block = block
20
+ compile_and_render_template
21
+ end
22
+
23
+ def content
24
+ return @__prf_content if defined?(@__prf_content_evaluated) && @__prf_content_evaluated
25
+
26
+ @__prf_content_evaluated = true
27
+ @__prf_content = capture do
28
+ @__prf_content_block.call
29
+ end
30
+ end
31
+
32
+ # :nocov:
33
+ def type
34
+ :component
35
+ end
36
+ # :nocov:
37
+
38
+ def input?
39
+ false
40
+ end
41
+
42
+ def to_component
43
+ self
44
+ end
45
+
46
+ private
47
+
48
+ def compile_and_render_template
49
+ self.class.compile! unless self.class.instance_methods(false).include?(:render_template)
50
+ render_template
51
+ end
52
+
53
+ def content_tag_if(condition, tag, **kwargs, &block)
54
+ if condition
55
+ content_tag(tag, **kwargs, &block)
56
+ else
57
+ capture(&block)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ripper"
4
+
5
+ module Primer
6
+ module Forms
7
+ # :nodoc:
8
+ class BufferRewriter < Ripper
9
+ class << self
10
+ def rewrite(code)
11
+ parser = new(code, "(code)", 0)
12
+ parser.parse
13
+
14
+ line_offsets = calc_line_offsets(code)
15
+
16
+ code.dup.tap do |result|
17
+ parser.var_refs.reverse_each do |lineno, stop|
18
+ line_offset = line_offsets[lineno]
19
+ stop += line_offset
20
+ stop -= 1 if stop < code.length
21
+ start = stop - "@output_buffer".length
22
+ result[start...stop] = "output_buffer"
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def calc_line_offsets(code)
30
+ idx = -1
31
+
32
+ [0].tap do |offsets|
33
+ while (idx = code.index(/\r?\n/, idx + 1))
34
+ offsets << Regexp.last_match.end(0)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ def on_var_ref(var)
41
+ return unless var == "@output_buffer"
42
+
43
+ var_refs << [lineno, column]
44
+ end
45
+
46
+ def var_refs
47
+ @var_refs ||= []
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "primer/classify"
4
+
5
+ module Primer
6
+ module Forms
7
+ # :nodoc:
8
+ class Builder < ActionView::Helpers::FormBuilder
9
+ include Primer::ClassNameHelper
10
+
11
+ UTILITY_KEYS = Primer::Classify::Utilities::UTILITIES.keys.freeze
12
+
13
+ def label(*args, **options, &block)
14
+ super(*args, classify(options), &block)
15
+ end
16
+
17
+ def check_box(*args, **options, &block)
18
+ super(*args, classify(options), &block)
19
+ end
20
+
21
+ def radio_button(*args, **options, &block)
22
+ super(*args, classify(options), &block)
23
+ end
24
+
25
+ def select(*args, **options, &block)
26
+ super(*args, classify(options), &block)
27
+ end
28
+
29
+ def text_field(*args, **options, &block)
30
+ super(*args, classify(options), &block)
31
+ end
32
+
33
+ def text_area(*args, **options, &block)
34
+ super(*args, classify(options), &block)
35
+ end
36
+
37
+ private
38
+
39
+ def classify(options)
40
+ options[:classes] = class_names(options.delete(:class), options[:classes])
41
+ options.merge!(Primer::Classify.call(options))
42
+ options.except!(*UTILITY_KEYS)
43
+ options[:class] = class_names(options[:class], options.delete(:classes))
44
+ options
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,10 @@
1
+ <% if @input.caption? && !@input.caption.blank? %>
2
+ <span class="FormControl-caption" id="<%= @input.caption_id %>"><%= @input.caption %></span>
3
+ <% elsif caption_template? %>
4
+ <% caption_template = render_caption_template %>
5
+ <% unless caption_template.blank? %>
6
+ <span class="FormControl-caption" id="<%= @input.caption_id %>">
7
+ <%= caption_template %>
8
+ </span>
9
+ <% end %>
10
+ <% end %>
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Forms
5
+ # :nodoc:
6
+ class Caption < BaseComponent
7
+ def initialize(input:)
8
+ @input = input
9
+ end
10
+
11
+ def caption_template?
12
+ @input.caption_template?
13
+ end
14
+
15
+ def render_caption_template
16
+ @input.render_caption_template
17
+ end
18
+
19
+ def before_render
20
+ return unless @input.caption? && caption_template?
21
+
22
+ raise <<~MESSAGE
23
+ Please provide either a caption: argument or caption template for the
24
+ '#{@input.name}' input; both were found.
25
+ MESSAGE
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,9 @@
1
+ <div class="FormControl-checkbox-wrap">
2
+ <%= builder.check_box(@input.name, **@input.input_arguments) %>
3
+ <span class="FormControl-checkbox-labelWrap">
4
+ <%= builder.label(@input.name, **@input.label_arguments) do %>
5
+ <%= @input.label %>
6
+ <% end %>
7
+ <%= render(Caption.new(input: @input)) %>
8
+ </span>
9
+ </div>
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Forms
5
+ # :nodoc:
6
+ class CheckBox < BaseComponent
7
+ delegate :builder, :form, to: :@input
8
+
9
+ def initialize(input:)
10
+ @input = input
11
+ @input.add_label_classes("FormControl-label")
12
+ @input.add_input_classes("FormControl-checkbox")
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ <fieldset>
2
+ <% if @input.label %>
3
+ <%= content_tag(:legend, **@input.label_arguments) do %>
4
+ <%= @input.label %>
5
+ <% end %>
6
+ <% end %>
7
+ <%= render(SpacingWrapper.new) do %>
8
+ <% @input.check_boxes.each do |check_box| %>
9
+ <%= render(check_box.to_component) %>
10
+ <% end %>
11
+ <% end %>
12
+ </fieldset>
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Forms
5
+ # :nodoc:
6
+ class CheckBoxGroup < BaseComponent
7
+ delegate :builder, :form, to: :@input
8
+
9
+ def initialize(input:)
10
+ @input = input
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Forms
5
+ module Dsl
6
+ # :nodoc:
7
+ class CheckBoxGroupInput < Input
8
+ attr_reader :label, :check_boxes
9
+
10
+ def initialize(label: nil, **system_arguments)
11
+ @label = label
12
+ @check_boxes = []
13
+
14
+ super(**system_arguments)
15
+
16
+ add_label_classes("FormControl-label", "mb-2")
17
+
18
+ yield(self) if block_given?
19
+ end
20
+
21
+ def to_component
22
+ CheckBoxGroup.new(input: self)
23
+ end
24
+
25
+ def name
26
+ nil
27
+ end
28
+
29
+ def type
30
+ :check_box_group
31
+ end
32
+
33
+ def check_box(**system_arguments)
34
+ @check_boxes << CheckBoxInput.new(
35
+ builder: @builder, form: @form, **system_arguments
36
+ )
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Forms
5
+ module Dsl
6
+ # :nodoc:
7
+ class CheckBoxInput < Input
8
+ attr_reader :name, :label
9
+
10
+ def initialize(name:, label:, **system_arguments)
11
+ @name = name
12
+ @label = label
13
+
14
+ super(**system_arguments)
15
+ end
16
+
17
+ def to_component
18
+ CheckBox.new(input: self)
19
+ end
20
+
21
+ def type
22
+ :check_box
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Forms
5
+ module Dsl
6
+ # :nodoc:
7
+ class FormObject
8
+ include InputMethods
9
+
10
+ attr_reader :builder, :form
11
+
12
+ def initialize(builder:, form:)
13
+ @builder = builder
14
+ @form = form
15
+
16
+ yield(self) if block_given?
17
+ end
18
+
19
+ def group(**options, &block)
20
+ add_input InputGroup.new(builder: @builder, form: @form, **options, &block)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Forms
5
+ module Dsl
6
+ # :nodoc:
7
+ class FormReferenceInput < Input
8
+ attr_reader :ref_block, :fields_for_args, :fields_for_kwargs
9
+
10
+ def initialize(*fields_for_args, builder:, form:, **fields_for_kwargs, &block)
11
+ @fields_for_args = fields_for_args
12
+ @fields_for_kwargs = fields_for_kwargs
13
+ @ref_block = block
14
+
15
+ super(builder: builder, form: form, **fields_for_kwargs)
16
+ end
17
+
18
+ def to_component
19
+ FormReference.new(input: self)
20
+ end
21
+
22
+ def name
23
+ nil
24
+ end
25
+
26
+ def label
27
+ nil
28
+ end
29
+
30
+ def type
31
+ :form
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Forms
5
+ module Dsl
6
+ # :nodoc:
7
+ class HiddenInput < Input
8
+ attr_reader :name
9
+
10
+ def initialize(name:, **system_arguments)
11
+ @name = name
12
+ super(**system_arguments)
13
+ end
14
+
15
+ def to_component
16
+ HiddenField.new(input: self)
17
+ end
18
+
19
+ def label
20
+ nil
21
+ end
22
+
23
+ def type
24
+ :hidden
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end