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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +52 -4
- data/app/assets/javascripts/primer_view_components.js +1 -1
- data/app/assets/javascripts/primer_view_components.js.map +1 -1
- data/app/components/primer/alpha/button_marketing.rb +70 -0
- data/app/components/primer/auto_complete.rb +97 -41
- data/app/components/primer/auto_complete/auto_complete.html.erb +1 -0
- data/app/components/primer/beta/text.rb +27 -0
- data/app/components/primer/blankslate_component.rb +2 -1
- data/app/components/primer/button_component.rb +3 -2
- data/app/components/primer/details_component.rb +12 -1
- data/app/components/primer/dropdown.d.ts +1 -0
- data/app/components/primer/{dropdown_component.html.erb → dropdown.html.erb} +2 -1
- data/app/components/primer/dropdown.js +1 -0
- data/app/components/primer/dropdown.rb +149 -0
- data/app/components/primer/dropdown.ts +1 -0
- data/app/components/primer/dropdown/menu.d.ts +1 -0
- data/app/components/primer/dropdown/menu.html.erb +25 -0
- data/app/components/primer/dropdown/menu.js +1 -0
- data/app/components/primer/dropdown/menu.rb +99 -0
- data/app/components/primer/dropdown/menu.ts +1 -0
- data/app/components/primer/heading_component.rb +1 -1
- data/app/components/primer/icon_button.rb +1 -1
- data/app/components/primer/navigation/tab_component.rb +2 -2
- data/app/components/primer/octicon_component.rb +3 -2
- data/app/components/primer/primer.d.ts +1 -0
- data/app/components/primer/primer.js +1 -0
- data/app/components/primer/primer.ts +1 -0
- data/app/components/primer/spinner_component.rb +2 -0
- data/lib/primer/view_components/linters/argument_mappers/button.rb +82 -0
- data/lib/primer/view_components/linters/argument_mappers/conversion_error.rb +10 -0
- data/lib/primer/view_components/linters/argument_mappers/system_arguments.rb +46 -0
- data/lib/primer/view_components/linters/button_component_migration_counter.rb +20 -1
- data/lib/primer/view_components/linters/flash_component_migration_counter.rb +1 -1
- data/lib/primer/view_components/linters/helpers.rb +7 -3
- data/lib/primer/view_components/version.rb +1 -1
- data/lib/tasks/docs.rake +111 -96
- data/lib/yard/docs_helper.rb +12 -2
- data/static/statuses.json +6 -4
- metadata +17 -8
- data/app/components/primer/button_marketing_component.rb +0 -68
- data/app/components/primer/dropdown/menu_component.html.erb +0 -12
- data/app/components/primer/dropdown/menu_component.rb +0 -46
- data/app/components/primer/dropdown_component.rb +0 -73
- data/app/components/primer/text_component.rb +0 -25
@@ -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
|
-
|
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
|
-
|
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
|
20
|
+
next if self.class::CLASSES.any? && (classes & self.class::CLASSES).blank?
|
21
21
|
|
22
|
-
generate_offense(self.class, processed_source, tag,
|
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
|
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::
|
46
|
+
Primer::Alpha::ButtonMarketing,
|
47
47
|
Primer::ClipboardCopy,
|
48
48
|
Primer::CloseButton,
|
49
49
|
Primer::CounterComponent,
|
50
50
|
Primer::DetailsComponent,
|
51
|
-
Primer::
|
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::
|
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(:
|
131
|
+
if documentation.tags(:deprecated).any?
|
128
132
|
f.puts
|
129
|
-
f.puts("##
|
130
|
-
documentation.tags(:
|
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(:
|
140
|
+
if documentation.tags(:accessibility).any?
|
137
141
|
f.puts
|
138
|
-
f.puts("##
|
139
|
-
documentation.tags(:
|
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
|
-
|
149
|
+
params = initialize_method.tags(:param)
|
146
150
|
|
147
|
-
|
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
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
153
|
+
f.puts
|
154
|
+
f.puts("## Arguments")
|
155
|
+
f.puts
|
156
|
+
f.puts("| Name | Type | Default | Description |")
|
157
|
+
f.puts("| :- | :- | :- | :- |")
|
158
158
|
|
159
|
-
|
160
|
-
|
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
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
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
|
-
|
174
|
-
|
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
|
-
|
186
|
-
|
187
|
-
|
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
|
-
|
221
|
-
"
|
222
|
-
"
|
223
|
-
"
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
263
|
-
|
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
|