primer_view_components 0.0.43 → 0.0.44

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +52 -4
  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/button_marketing.rb +70 -0
  6. data/app/components/primer/auto_complete.rb +97 -41
  7. data/app/components/primer/auto_complete/auto_complete.html.erb +1 -0
  8. data/app/components/primer/beta/text.rb +27 -0
  9. data/app/components/primer/blankslate_component.rb +2 -1
  10. data/app/components/primer/button_component.rb +3 -2
  11. data/app/components/primer/details_component.rb +12 -1
  12. data/app/components/primer/dropdown.d.ts +1 -0
  13. data/app/components/primer/{dropdown_component.html.erb → dropdown.html.erb} +2 -1
  14. data/app/components/primer/dropdown.js +1 -0
  15. data/app/components/primer/dropdown.rb +149 -0
  16. data/app/components/primer/dropdown.ts +1 -0
  17. data/app/components/primer/dropdown/menu.d.ts +1 -0
  18. data/app/components/primer/dropdown/menu.html.erb +25 -0
  19. data/app/components/primer/dropdown/menu.js +1 -0
  20. data/app/components/primer/dropdown/menu.rb +99 -0
  21. data/app/components/primer/dropdown/menu.ts +1 -0
  22. data/app/components/primer/heading_component.rb +1 -1
  23. data/app/components/primer/icon_button.rb +1 -1
  24. data/app/components/primer/navigation/tab_component.rb +2 -2
  25. data/app/components/primer/octicon_component.rb +3 -2
  26. data/app/components/primer/primer.d.ts +1 -0
  27. data/app/components/primer/primer.js +1 -0
  28. data/app/components/primer/primer.ts +1 -0
  29. data/app/components/primer/spinner_component.rb +2 -0
  30. data/lib/primer/view_components/linters/argument_mappers/button.rb +82 -0
  31. data/lib/primer/view_components/linters/argument_mappers/conversion_error.rb +10 -0
  32. data/lib/primer/view_components/linters/argument_mappers/system_arguments.rb +46 -0
  33. data/lib/primer/view_components/linters/button_component_migration_counter.rb +20 -1
  34. data/lib/primer/view_components/linters/flash_component_migration_counter.rb +1 -1
  35. data/lib/primer/view_components/linters/helpers.rb +7 -3
  36. data/lib/primer/view_components/version.rb +1 -1
  37. data/lib/tasks/docs.rake +111 -96
  38. data/lib/yard/docs_helper.rb +12 -2
  39. data/static/statuses.json +6 -4
  40. metadata +17 -8
  41. data/app/components/primer/button_marketing_component.rb +0 -68
  42. data/app/components/primer/dropdown/menu_component.html.erb +0 -12
  43. data/app/components/primer/dropdown/menu_component.rb +0 -46
  44. data/app/components/primer/dropdown_component.rb +0 -73
  45. data/app/components/primer/text_component.rb +0 -25
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ERBLint
4
+ module Linters
5
+ module ArgumentMappers
6
+ # Error when converting arguments.
7
+ class ConversionError < StandardError; end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "conversion_error"
4
+
5
+ module ERBLint
6
+ module Linters
7
+ module ArgumentMappers
8
+ # Maps element attributes to system arguments.
9
+ class SystemArguments
10
+ STRING_PARAETERS = %w[aria- data-].freeze
11
+ TEST_SELECTOR_REGEX = /test_selector\((?<selector>.+)\)$/.freeze
12
+
13
+ attr_reader :attribute
14
+ def initialize(attribute)
15
+ @attribute = attribute
16
+ end
17
+
18
+ def to_args
19
+ if attribute.erb?
20
+ _, _, code_node = *attribute.node
21
+
22
+ raise ConversionError, "Cannot convert erb block" if code_node.nil?
23
+
24
+ code = code_node.loc.source.strip
25
+ m = code.match(TEST_SELECTOR_REGEX)
26
+
27
+ raise ConversionError, "Cannot convert erb block" if m.blank?
28
+
29
+ { test_selector: m[:selector].tr("'", '"') }
30
+ elsif attr_name == "data-test-selector"
31
+ { test_selector: attribute.value.to_json }
32
+ elsif attr_name.start_with?(*STRING_PARAETERS)
33
+ # if attribute has no value_node, it means it is a boolean attribute.
34
+ { "\"#{attr_name}\"" => attribute.value_node ? attribute.value.to_json : true }
35
+ else
36
+ raise ConversionError, "Cannot convert attribute \"#{attr_name}\""
37
+ end
38
+ end
39
+
40
+ def attr_name
41
+ attribute.name
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "helpers"
4
+ require_relative "argument_mappers/button"
4
5
 
5
6
  module ERBLint
6
7
  module Linters
@@ -9,8 +10,26 @@ module ERBLint
9
10
  include Helpers
10
11
 
11
12
  TAGS = %w[button summary a].freeze
12
- CLASS = "btn"
13
+ CLASSES = %w[btn btn-link].freeze
13
14
  MESSAGE = "We are migrating buttons to use [Primer::ButtonComponent](https://primer.style/view-components/components/button), please try to use that instead of raw HTML."
15
+
16
+ private
17
+
18
+ def map_arguments(tag)
19
+ ArgumentMappers::Button.new(tag).to_s
20
+ rescue ArgumentMappers::ConversionError
21
+ nil
22
+ end
23
+
24
+ def message(tag)
25
+ args = map_arguments(tag)
26
+
27
+ return MESSAGE if args.nil?
28
+
29
+ msg = "#{MESSAGE}\n\nTry using:\n\n<%= render Primer::ButtonComponent.new"
30
+ msg += "(#{args})" if args.present?
31
+ "#{msg} %>\n\nInstead of:\n"
32
+ end
14
33
  end
15
34
  end
16
35
  end
@@ -9,7 +9,7 @@ module ERBLint
9
9
  include Helpers
10
10
 
11
11
  TAGS = %w[div].freeze
12
- CLASS = "flash"
12
+ CLASSES = %w[flash].freeze
13
13
  MESSAGE = "We are migrating flashes to use [Primer::FlashComponent](https://primer.style/view-components/components/flash), please try to use that instead of raw HTML."
14
14
  end
15
15
  end
@@ -8,7 +8,7 @@ module ERBLint
8
8
  # Helper methods for linting ERB.
9
9
  module Helpers
10
10
  def self.included(base)
11
- base.include(LinterRegistry)
11
+ base.include(ERBLint::LinterRegistry)
12
12
 
13
13
  define_method "run" do |processed_source|
14
14
  tags(processed_source).each do |tag|
@@ -17,9 +17,9 @@ module ERBLint
17
17
 
18
18
  classes = tag.attributes["class"]&.value&.split(" ")
19
19
 
20
- next unless !self.class::CLASS || classes&.include?(self.class::CLASS)
20
+ next if self.class::CLASSES.any? && (classes & self.class::CLASSES).blank?
21
21
 
22
- generate_offense(self.class, processed_source, tag, self.class::MESSAGE)
22
+ generate_offense(self.class, processed_source, tag, message(tag))
23
23
  end
24
24
 
25
25
  counter_correct?(processed_source)
@@ -42,6 +42,10 @@ module ERBLint
42
42
 
43
43
  private
44
44
 
45
+ def message(_tag)
46
+ self.class::MESSAGE
47
+ end
48
+
45
49
  def tags(processed_source)
46
50
  processed_source.parser.nodes_with_type(:tag).map { |tag_node| BetterHtml::Tree::Tag.from_node(tag_node) }
47
51
  end
@@ -5,7 +5,7 @@ module Primer
5
5
  module VERSION
6
6
  MAJOR = 0
7
7
  MINOR = 0
8
- PATCH = 43
8
+ PATCH = 44
9
9
 
10
10
  STRING = [MAJOR, MINOR, PATCH].join(".")
11
11
  end
data/lib/tasks/docs.rake CHANGED
@@ -43,12 +43,12 @@ namespace :docs do
43
43
  Primer::BreadcrumbComponent,
44
44
  Primer::ButtonComponent,
45
45
  Primer::ButtonGroup,
46
- Primer::ButtonMarketingComponent,
46
+ Primer::Alpha::ButtonMarketing,
47
47
  Primer::ClipboardCopy,
48
48
  Primer::CloseButton,
49
49
  Primer::CounterComponent,
50
50
  Primer::DetailsComponent,
51
- Primer::DropdownComponent,
51
+ Primer::Dropdown,
52
52
  Primer::DropdownMenuComponent,
53
53
  Primer::FlashComponent,
54
54
  Primer::FlexComponent,
@@ -69,7 +69,7 @@ namespace :docs do
69
69
  Primer::SubheadComponent,
70
70
  Primer::TabContainerComponent,
71
71
  Primer::TabNavComponent,
72
- Primer::TextComponent,
72
+ Primer::Beta::Text,
73
73
  Primer::TimeAgoComponent,
74
74
  Primer::TimelineItemComponent,
75
75
  Primer::Tooltip,
@@ -78,6 +78,7 @@ namespace :docs do
78
78
  ]
79
79
 
80
80
  js_components = [
81
+ Primer::Dropdown,
81
82
  Primer::LocalTime,
82
83
  Primer::ImageCrop,
83
84
  Primer::AutoComplete,
@@ -91,10 +92,11 @@ namespace :docs do
91
92
  all_components = Primer::Component.descendants - [Primer::BaseComponent]
92
93
  components_needing_docs = all_components - components
93
94
 
94
- components_without_examples = []
95
95
  args_for_components = []
96
96
  classes_found_in_examples = []
97
97
 
98
+ errors = []
99
+
98
100
  components.each do |component|
99
101
  documentation = registry.get(component.name)
100
102
 
@@ -113,6 +115,8 @@ namespace :docs do
113
115
  f.puts
114
116
  f.puts("import Example from '../../src/@primer/gatsby-theme-doctocat/components/example'")
115
117
 
118
+ initialize_method = documentation.meths.find(&:constructor?)
119
+
116
120
  if js_components.include?(component)
117
121
  f.puts("import RequiresJSFlash from '../../src/@primer/gatsby-theme-doctocat/components/requires-js-flash'")
118
122
  f.puts
@@ -124,108 +128,68 @@ namespace :docs do
124
128
  f.puts
125
129
  f.puts(view_context.render(inline: documentation.base_docstring))
126
130
 
127
- if documentation.tags(:accessibility).any?
131
+ if documentation.tags(:deprecated).any?
128
132
  f.puts
129
- f.puts("## Accessibility")
130
- documentation.tags(:accessibility).each do |tag|
133
+ f.puts("## Deprecation")
134
+ documentation.tags(:deprecated).each do |tag|
131
135
  f.puts
132
136
  f.puts view_context.render(inline: tag.text)
133
137
  end
134
138
  end
135
139
 
136
- if documentation.tags(:deprecated).any?
140
+ if documentation.tags(:accessibility).any?
137
141
  f.puts
138
- f.puts("## Deprecation")
139
- documentation.tags(:deprecated).each do |tag|
142
+ f.puts("## Accessibility")
143
+ documentation.tags(:accessibility).each do |tag|
140
144
  f.puts
141
145
  f.puts view_context.render(inline: tag.text)
142
146
  end
143
147
  end
144
148
 
145
- initialize_method = documentation.meths.find(&:constructor?)
149
+ params = initialize_method.tags(:param)
146
150
 
147
- if initialize_method.tags(:example).any?
148
- f.puts
149
- f.puts("## Examples")
150
- else
151
- components_without_examples << component
152
- end
151
+ errors << { component.name => { arguments: "No argument documentation found" } } unless params.any?
153
152
 
154
- initialize_method.tags(:example).each do |tag|
155
- name = tag.name
156
- description = nil
157
- code = nil
153
+ f.puts
154
+ f.puts("## Arguments")
155
+ f.puts
156
+ f.puts("| Name | Type | Default | Description |")
157
+ f.puts("| :- | :- | :- | :- |")
158
158
 
159
- if tag.text.include?("@description")
160
- splitted = tag.text.split(/@description|@code/)
161
- description = splitted.second.gsub(/^[ \t]{2}/, "").strip
162
- code = splitted.last.gsub(/^[ \t]{2}/, "").strip
163
- else
164
- code = tag.text
165
- end
159
+ docummented_params = params.map(&:name)
160
+ component_params = component.instance_method(:initialize).parameters.map { |p| p.last.to_s }
166
161
 
167
- f.puts
168
- f.puts("### #{name}")
169
- if description
170
- f.puts
171
- f.puts(description)
162
+ if (docummented_params & component_params).size != component_params.size
163
+ err = { arguments: {} }
164
+ (component_params - docummented_params).each do |arg|
165
+ err[:arguments][arg] = "Not documented"
172
166
  end
173
- f.puts
174
- html = view_context.render(inline: code)
175
- html.scan(/class="([^"]*)"/) do |classnames|
176
- classes_found_in_examples.concat(classnames[0].split(" ").reject { |c| c.starts_with?("octicon", "js", "my-") }.map { ".#{_1}"})
177
- end
178
- f.puts("<Example src=\"#{html.tr('"', "\'").delete("\n")}\" />")
179
- f.puts
180
- f.puts("```erb")
181
- f.puts(code.to_s)
182
- f.puts("```")
167
+
168
+ errors << { component.name => err }
183
169
  end
184
170
 
185
- params = initialize_method.tags(:param)
186
- if params.any?
187
- f.puts
188
- f.puts("## Arguments")
189
- f.puts
190
- f.puts("| Name | Type | Default | Description |")
191
- f.puts("| :- | :- | :- | :- |")
192
-
193
- args = []
194
- params.each do |tag|
195
- params = tag.object.parameters.find { |param| [tag.name.to_s, tag.name.to_s + ":"].include?(param[0]) }
196
-
197
- default =
198
- if params && params[1]
199
- constant_name = "#{component.name}::#{params[1]}"
200
- constant_value = constant_name.safe_constantize
201
- if constant_value.nil?
202
- pretty_value(params[1])
203
- else
204
- pretty_value(constant_value)
205
- end
206
- else
207
- "N/A"
208
- end
209
-
210
- args << {
211
- "name" => tag.name,
212
- "type" => tag.types.join(", "),
213
- "default" => default,
214
- "description" => view_context.render(inline: tag.text)
215
- }
216
-
217
- f.puts("| `#{tag.name}` | `#{tag.types.join(', ')}` | #{default} | #{view_context.render(inline: tag.text)} |")
218
- end
171
+ args = []
172
+ params.each do |tag|
173
+ default_value = pretty_default_value(tag, component)
219
174
 
220
- component_args = {
221
- "component" => short_name,
222
- "source" => "https://github.com/primer/view_components/tree/main/app/components/primer/#{component.to_s.demodulize.underscore}.rb",
223
- "parameters" => args
175
+ args << {
176
+ "name" => tag.name,
177
+ "type" => tag.types.join(", "),
178
+ "default" => default_value,
179
+ "description" => view_context.render(inline: tag.text)
224
180
  }
225
181
 
226
- args_for_components << component_args
182
+ f.puts("| `#{tag.name}` | `#{tag.types.join(', ')}` | #{default_value} | #{view_context.render(inline: tag.text)} |")
227
183
  end
228
184
 
185
+ component_args = {
186
+ "component" => short_name,
187
+ "source" => "https://github.com/primer/view_components/tree/main/app/components/primer/#{component.to_s.demodulize.underscore}.rb",
188
+ "parameters" => args
189
+ }
190
+
191
+ args_for_components << component_args
192
+
229
193
  # Slots V2 docs
230
194
  slot_v2_methods = documentation.meths.select { |x| x[:renders_one] || x[:renders_many] }
231
195
 
@@ -250,22 +214,61 @@ namespace :docs do
250
214
  end
251
215
 
252
216
  param_tags.each do |tag|
253
- params = tag.object.parameters.find { |param| [tag.name.to_s, tag.name.to_s + ":"].include?(param[0]) }
217
+ f.puts("| `#{tag.name}` | `#{tag.types.join(', ')}` | #{pretty_default_value(tag, component)} | #{view_context.render(inline: tag.text)} |")
218
+ end
219
+ end
220
+ end
254
221
 
255
- default =
256
- if params && params[1]
257
- "`#{params[1]}`"
258
- else
259
- "N/A"
260
- end
222
+ errors << { component.name => { example: "No examples found" } } unless initialize_method.tags(:example).any?
261
223
 
262
- f.puts("| `#{tag.name}` | `#{tag.types.join(', ')}` | #{default} | #{view_context.render(inline: tag.text)} |")
263
- end
224
+ f.puts
225
+ f.puts("## Examples")
226
+
227
+ initialize_method.tags(:example).each do |tag|
228
+ name = tag.name
229
+ description = nil
230
+ code = nil
231
+
232
+ if tag.text.include?("@description")
233
+ splitted = tag.text.split(/@description|@code/)
234
+ description = splitted.second.gsub(/^[ \t]{2}/, "").strip
235
+ code = splitted.last.gsub(/^[ \t]{2}/, "").strip
236
+ else
237
+ code = tag.text
238
+ end
239
+
240
+ f.puts
241
+ f.puts("### #{name}")
242
+ if description
243
+ f.puts
244
+ f.puts(description)
245
+ end
246
+ f.puts
247
+ html = view_context.render(inline: code)
248
+ html.scan(/class="([^"]*)"/) do |classnames|
249
+ classes_found_in_examples.concat(classnames[0].split(" ").reject { |c| c.starts_with?("octicon", "js", "my-") }.map { ".#{_1}"})
264
250
  end
251
+ f.puts("<Example src=\"#{html.tr('"', "\'").delete("\n")}\" />")
252
+ f.puts
253
+ f.puts("```erb")
254
+ f.puts(code.to_s)
255
+ f.puts("```")
265
256
  end
266
257
  end
267
258
  end
268
259
 
260
+ unless errors.empty?
261
+ puts "==============================================="
262
+ puts "===================== ERRORS =================="
263
+ puts "===============================================\n\n"
264
+ puts JSON.pretty_generate(errors)
265
+ puts "\n\n==============================================="
266
+ puts "==============================================="
267
+ puts "==============================================="
268
+
269
+ raise
270
+ end
271
+
269
272
  File.open("static/classes.yml", "w") do |f|
270
273
  f.puts YAML.dump(classes_found_in_examples.sort.uniq)
271
274
  end
@@ -293,11 +296,6 @@ namespace :docs do
293
296
 
294
297
  puts "Markdown compiled."
295
298
 
296
- if components_without_examples.any?
297
- puts
298
- puts "The following components have no examples defined: #{components_without_examples.map(&:name).join(', ')}. Consider adding an example?"
299
- end
300
-
301
299
  if components_needing_docs.any?
302
300
  puts
303
301
  puts "The following components needs docs. Care to contribute them? #{components_needing_docs.map(&:name).join(', ')}"
@@ -307,6 +305,8 @@ namespace :docs do
307
305
  task :preview do
308
306
  registry = generate_yard_registry
309
307
 
308
+ FileUtils.rm_rf("demo/test/components/previews/primer/docs/")
309
+
310
310
  components = Primer::Component.descendants
311
311
 
312
312
  # Generate previews from documentation examples
@@ -361,6 +361,7 @@ namespace :docs do
361
361
  # Custom tags for yard
362
362
  YARD::Tags::Library.define_tag("Accessibility", :accessibility)
363
363
  YARD::Tags::Library.define_tag("Deprecation", :deprecation)
364
+ YARD::Tags::Library.define_tag("Parameter", :param, :with_types_name_and_default)
364
365
 
365
366
  puts "Building YARD documentation."
366
367
  Rake::Task["yard"].execute
@@ -369,4 +370,18 @@ namespace :docs do
369
370
  registry.load!(".yardoc")
370
371
  registry
371
372
  end
373
+
374
+ def pretty_default_value(tag, component)
375
+ params = tag.object.parameters.find { |param| [tag.name.to_s, tag.name.to_s + ":"].include?(param[0]) }
376
+ default = tag.defaults&.first || params&.second
377
+
378
+ return "N/A" unless default
379
+
380
+ constant_name = "#{component.name}::#{default}"
381
+ constant_value = default.safe_constantize || constant_name.safe_constantize
382
+
383
+ return pretty_value(default) if constant_value.nil?
384
+
385
+ pretty_value(constant_value)
386
+ end
372
387
  end