primer_view_components 0.0.46 → 0.0.50
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 +144 -0
- data/app/components/primer/base_component.rb +2 -2
- data/app/components/primer/beta/auto_complete.rb +159 -0
- data/app/components/primer/beta/auto_complete/auto_complete.d.ts +1 -0
- data/app/components/primer/{auto_complete → beta/auto_complete}/auto_complete.html.erb +0 -0
- data/app/components/primer/beta/auto_complete/auto_complete.js +1 -0
- data/app/components/primer/{auto_complete → beta/auto_complete}/auto_complete.ts +0 -0
- data/app/components/primer/beta/auto_complete/item.rb +44 -0
- data/app/components/primer/beta/avatar.rb +77 -0
- data/app/components/primer/{avatar_stack_component.html.erb → beta/avatar_stack.html.erb} +0 -0
- data/app/components/primer/beta/avatar_stack.rb +92 -0
- data/app/components/primer/clipboard_copy.html.erb +2 -2
- data/app/components/primer/component.rb +5 -1
- data/app/components/primer/details_component.rb +7 -7
- data/app/components/primer/image_crop.html.erb +4 -4
- data/app/components/primer/label_component.rb +13 -12
- data/app/components/primer/markdown.rb +9 -9
- data/app/components/primer/navigation/tab_component.rb +30 -2
- data/app/components/primer/popover_component.rb +6 -3
- data/app/components/primer/primer.d.ts +1 -1
- data/app/components/primer/primer.js +1 -1
- data/app/components/primer/primer.ts +1 -1
- data/app/components/primer/tab_nav_component.rb +4 -3
- data/app/components/primer/timeline_item_component.rb +2 -2
- data/app/components/primer/truncate.rb +6 -1
- data/app/components/primer/underline_nav_component.rb +4 -3
- data/app/lib/primer/octicon/cache.rb +1 -1
- data/lib/primer/classify.rb +4 -18
- data/lib/primer/classify/cache.rb +0 -5
- data/lib/primer/classify/utilities.rb +54 -22
- data/lib/primer/classify/utilities.yml +16 -0
- data/lib/primer/view_components.rb +34 -6
- data/lib/primer/view_components/constants.rb +55 -0
- data/lib/primer/view_components/linters/argument_mappers/base.rb +74 -0
- data/lib/primer/view_components/linters/argument_mappers/button.rb +32 -44
- data/lib/primer/view_components/linters/argument_mappers/clipboard_copy.rb +20 -0
- data/lib/primer/view_components/linters/argument_mappers/helpers/erb_block.rb +24 -0
- data/lib/primer/view_components/linters/argument_mappers/label.rb +50 -0
- data/lib/primer/view_components/linters/argument_mappers/system_arguments.rb +4 -1
- data/lib/primer/view_components/linters/autocorrectable.rb +30 -0
- data/lib/primer/view_components/linters/button_component_migration_counter.rb +9 -23
- data/lib/primer/view_components/linters/clipboard_copy_component_migration_counter.rb +21 -0
- data/lib/primer/view_components/linters/close_button_component_migration_counter.rb +16 -0
- data/lib/primer/view_components/linters/helpers.rb +56 -38
- data/lib/primer/view_components/linters/label_component_migration_counter.rb +25 -0
- data/lib/primer/view_components/statuses.rb +14 -0
- data/lib/primer/view_components/version.rb +1 -1
- data/lib/rubocop/config/default.yml +12 -0
- data/lib/rubocop/cop/primer.rb +4 -0
- data/lib/rubocop/cop/primer/no_tag_memoize.rb +42 -0
- data/lib/rubocop/cop/primer/system_argument_instead_of_class.rb +75 -0
- data/lib/tasks/constants.rake +12 -0
- data/lib/tasks/docs.rake +87 -32
- data/lib/tasks/utilities.rake +4 -10
- data/lib/yard/docs_helper.rb +12 -3
- data/static/arguments.yml +973 -0
- data/static/assets/view-components.svg +18 -0
- data/static/classes.yml +174 -0
- data/static/constants.json +628 -0
- data/static/statuses.json +5 -5
- metadata +34 -13
- data/app/components/primer/auto_complete.rb +0 -157
- data/app/components/primer/auto_complete/item.rb +0 -42
- data/app/components/primer/avatar_component.rb +0 -75
- data/app/components/primer/avatar_stack_component.rb +0 -90
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "helpers"
|
4
|
+
require_relative "autocorrectable"
|
5
|
+
require_relative "argument_mappers/label"
|
6
|
+
|
7
|
+
module ERBLint
|
8
|
+
module Linters
|
9
|
+
# Counts the number of times a HTML label is used instead of the component.
|
10
|
+
class LabelComponentMigrationCounter < Linter
|
11
|
+
include Helpers
|
12
|
+
include Autocorrectable
|
13
|
+
|
14
|
+
TAGS = Primer::ViewComponents::Constants.get(
|
15
|
+
component: "Primer::LabelComponent",
|
16
|
+
constant: "TAG_OPTIONS"
|
17
|
+
).freeze
|
18
|
+
|
19
|
+
CLASSES = %w[Label].freeze
|
20
|
+
MESSAGE = "We are migrating labels to use [Primer::LabelComponent](https://primer.style/view-components/components/label), please try to use that instead of raw HTML."
|
21
|
+
ARGUMENT_MAPPER = ArgumentMappers::Label
|
22
|
+
COMPONENT = "Primer::LabelComponent"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rubocop"
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Cop
|
7
|
+
module Primer
|
8
|
+
# This cop ensures that tags are not set with ||=
|
9
|
+
#
|
10
|
+
# bad
|
11
|
+
# @system_arguments[:tag] ||= :h1
|
12
|
+
#
|
13
|
+
# good
|
14
|
+
# @system_arguments[:tag] = fetch_or_fallback(TAG_OPTIONS, tag, DEFAULT_TAG)
|
15
|
+
#
|
16
|
+
# good
|
17
|
+
# @system_arguments[:tag] = :h2
|
18
|
+
class NoTagMemoize < RuboCop::Cop::Cop
|
19
|
+
INVALID_MESSAGE = <<~STR
|
20
|
+
Avoid `[:tag] ||=`. Instead, try one of the following:
|
21
|
+
- Don't allow consumers to update the tag by having a fixed tag (e.g. `system_arguments[:tag] = :div`)
|
22
|
+
- Use the `fetch_or_fallback` helper to only allow a tag from a restricted list.
|
23
|
+
STR
|
24
|
+
|
25
|
+
def_node_search :tag_memoized?, <<~PATTERN
|
26
|
+
(or-asgn
|
27
|
+
(send
|
28
|
+
_
|
29
|
+
_
|
30
|
+
(sym :tag)
|
31
|
+
)
|
32
|
+
_
|
33
|
+
)
|
34
|
+
PATTERN
|
35
|
+
|
36
|
+
def on_or_asgn(node)
|
37
|
+
add_offense(node, message: INVALID_MESSAGE) if tag_memoized?(node)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rubocop"
|
4
|
+
require "primer/classify/utilities"
|
5
|
+
require "primer/view_components/statuses"
|
6
|
+
|
7
|
+
module RuboCop
|
8
|
+
module Cop
|
9
|
+
module Primer
|
10
|
+
# This cop ensures that components use System Arguments instead of CSS classes.
|
11
|
+
#
|
12
|
+
# bad
|
13
|
+
# Component.new(classes: "mr-1")
|
14
|
+
#
|
15
|
+
# good
|
16
|
+
# Component.new(mr: 1)
|
17
|
+
class SystemArgumentInsteadOfClass < RuboCop::Cop::Cop
|
18
|
+
INVALID_MESSAGE = <<~STR
|
19
|
+
Avoid using CSS classes when you can use System Arguments: https://primer.style/view-components/system-arguments.
|
20
|
+
STR
|
21
|
+
|
22
|
+
def on_send(node)
|
23
|
+
return unless node.method_name == :new
|
24
|
+
return unless ::Primer::ViewComponents::STATUSES.key?(node.receiver.const_name)
|
25
|
+
return unless node.arguments?
|
26
|
+
|
27
|
+
# we are looking for hash arguments and they are always last
|
28
|
+
kwargs = node.arguments.last
|
29
|
+
|
30
|
+
return unless kwargs.type == :hash
|
31
|
+
|
32
|
+
# find classes pair
|
33
|
+
classes_arg = kwargs.pairs.find { |kwarg| kwarg.key.value == :classes }
|
34
|
+
|
35
|
+
return if classes_arg.nil?
|
36
|
+
return unless classes_arg.value.type == :str
|
37
|
+
|
38
|
+
# get actual classes
|
39
|
+
classes = classes_arg.value.value
|
40
|
+
|
41
|
+
system_arguments = ::Primer::Classify::Utilities.classes_to_hash(classes)
|
42
|
+
|
43
|
+
# no classes are fixable
|
44
|
+
return if system_arguments[:classes] == classes
|
45
|
+
|
46
|
+
add_offense(classes_arg, message: INVALID_MESSAGE)
|
47
|
+
end
|
48
|
+
|
49
|
+
def autocorrect(node)
|
50
|
+
lambda do |corrector|
|
51
|
+
system_arguments = ::Primer::Classify::Utilities.classes_to_hash(node.value.value)
|
52
|
+
corrector.replace(node.loc.expression, arguments_as_string(system_arguments))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def arguments_as_string(system_arguments)
|
59
|
+
system_arguments.map do |key, value|
|
60
|
+
val = case value
|
61
|
+
when Symbol
|
62
|
+
":#{value}"
|
63
|
+
when String
|
64
|
+
value.to_json
|
65
|
+
else
|
66
|
+
value
|
67
|
+
end
|
68
|
+
|
69
|
+
"#{key}: #{val}"
|
70
|
+
end.join(", ")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :constants do
|
4
|
+
task :dump do
|
5
|
+
require File.expand_path("./../../demo/config/environment.rb", __dir__)
|
6
|
+
require "primer/view_components"
|
7
|
+
# Loads all components for `.descendants` to work properly
|
8
|
+
Dir["./app/components/primer/**/*.rb"].sort.each { |file| require file }
|
9
|
+
|
10
|
+
Primer::ViewComponents.dump_constants
|
11
|
+
end
|
12
|
+
end
|
data/lib/tasks/docs.rake
CHANGED
@@ -32,10 +32,10 @@ namespace :docs do
|
|
32
32
|
Primer::OcticonSymbolsComponent,
|
33
33
|
Primer::ImageCrop,
|
34
34
|
Primer::IconButton,
|
35
|
-
Primer::AutoComplete,
|
36
|
-
Primer::AutoComplete::Item,
|
37
|
-
Primer::
|
38
|
-
Primer::
|
35
|
+
Primer::Beta::AutoComplete,
|
36
|
+
Primer::Beta::AutoComplete::Item,
|
37
|
+
Primer::Beta::Avatar,
|
38
|
+
Primer::Beta::AvatarStack,
|
39
39
|
Primer::BaseButton,
|
40
40
|
Primer::BlankslateComponent,
|
41
41
|
Primer::BorderBoxComponent,
|
@@ -81,7 +81,7 @@ namespace :docs do
|
|
81
81
|
Primer::Dropdown,
|
82
82
|
Primer::LocalTime,
|
83
83
|
Primer::ImageCrop,
|
84
|
-
Primer::AutoComplete,
|
84
|
+
Primer::Beta::AutoComplete,
|
85
85
|
Primer::ClipboardCopy,
|
86
86
|
Primer::TabContainerComponent,
|
87
87
|
Primer::TabNavComponent,
|
@@ -97,28 +97,31 @@ namespace :docs do
|
|
97
97
|
|
98
98
|
errors = []
|
99
99
|
|
100
|
-
|
100
|
+
# Deletes docs before regenerating them, guaranteeing that we don't keep stale docs.
|
101
|
+
FileUtils.rm_rf(Dir.glob("docs/content/components/**/*.md"))
|
102
|
+
|
103
|
+
components.sort_by(&:name).each do |component|
|
101
104
|
documentation = registry.get(component.name)
|
102
105
|
|
103
|
-
|
104
|
-
short_name = component.name.gsub(/Primer|::|Component/, "")
|
106
|
+
data = docs_metadata(component)
|
105
107
|
|
106
|
-
path = Pathname.new(
|
108
|
+
path = Pathname.new(data[:path])
|
107
109
|
path.dirname.mkdir unless path.dirname.exist?
|
108
110
|
File.open(path, "w") do |f|
|
109
111
|
f.puts("---")
|
110
|
-
f.puts("title: #{
|
111
|
-
f.puts("
|
112
|
-
f.puts("
|
113
|
-
f.puts("
|
112
|
+
f.puts("title: #{data[:title]}")
|
113
|
+
f.puts("componentId: #{data[:component_id]}")
|
114
|
+
f.puts("status: #{data[:status]}")
|
115
|
+
f.puts("source: #{data[:source]}")
|
116
|
+
f.puts("storybook: #{data[:storybook]}")
|
114
117
|
f.puts("---")
|
115
118
|
f.puts
|
116
|
-
f.puts("import Example from '
|
119
|
+
f.puts("import Example from '#{data[:example_path]}'")
|
117
120
|
|
118
121
|
initialize_method = documentation.meths.find(&:constructor?)
|
119
122
|
|
120
123
|
if js_components.include?(component)
|
121
|
-
f.puts("import RequiresJSFlash from '
|
124
|
+
f.puts("import RequiresJSFlash from '#{data[:require_js_path]}'")
|
122
125
|
f.puts
|
123
126
|
f.puts("<RequiresJSFlash />")
|
124
127
|
end
|
@@ -183,8 +186,8 @@ namespace :docs do
|
|
183
186
|
end
|
184
187
|
|
185
188
|
component_args = {
|
186
|
-
"component" =>
|
187
|
-
"source" =>
|
189
|
+
"component" => data[:title],
|
190
|
+
"source" => data[:source],
|
188
191
|
"parameters" => args
|
189
192
|
}
|
190
193
|
|
@@ -225,23 +228,12 @@ namespace :docs do
|
|
225
228
|
f.puts("## Examples")
|
226
229
|
|
227
230
|
initialize_method.tags(:example).each do |tag|
|
228
|
-
name = tag
|
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
|
-
|
231
|
+
name, description, code = parse_example_tag(tag)
|
240
232
|
f.puts
|
241
233
|
f.puts("### #{name}")
|
242
234
|
if description
|
243
235
|
f.puts
|
244
|
-
f.puts(description)
|
236
|
+
f.puts(view_context.render(inline: description.squish))
|
245
237
|
end
|
246
238
|
f.puts
|
247
239
|
html = view_context.render(inline: code)
|
@@ -328,13 +320,14 @@ namespace :docs do
|
|
328
320
|
f.puts(" class #{short_name}Preview < ViewComponent::Preview")
|
329
321
|
|
330
322
|
yard_example_tags.each_with_index do |tag, index|
|
331
|
-
|
323
|
+
name, _, code = parse_example_tag(tag)
|
324
|
+
method_name = name.split("|").first.downcase.parameterize.underscore
|
332
325
|
f.puts(" def #{method_name}; end")
|
333
326
|
f.puts unless index == yard_example_tags.size - 1
|
334
327
|
path = Pathname.new("demo/test/components/previews/primer/docs/#{short_name.underscore}_preview/#{method_name}.html.erb")
|
335
328
|
path.dirname.mkdir unless path.dirname.exist?
|
336
329
|
File.open(path, "w") do |view_file|
|
337
|
-
view_file.puts(
|
330
|
+
view_file.puts(code.to_s)
|
338
331
|
end
|
339
332
|
end
|
340
333
|
|
@@ -346,6 +339,7 @@ namespace :docs do
|
|
346
339
|
end
|
347
340
|
|
348
341
|
def generate_yard_registry
|
342
|
+
ENV["SKIP_STORYBOOK_PRELOAD"] = "1"
|
349
343
|
require File.expand_path("./../../demo/config/environment.rb", __dir__)
|
350
344
|
require "primer/view_components"
|
351
345
|
require "yard/docs_helper"
|
@@ -371,6 +365,22 @@ namespace :docs do
|
|
371
365
|
registry
|
372
366
|
end
|
373
367
|
|
368
|
+
def parse_example_tag(tag)
|
369
|
+
name = tag.name
|
370
|
+
description = nil
|
371
|
+
code = nil
|
372
|
+
|
373
|
+
if tag.text.include?("@description")
|
374
|
+
splitted = tag.text.split(/@description|@code/)
|
375
|
+
description = splitted.second.gsub(/^[ \t]{2}/, "").strip
|
376
|
+
code = splitted.last.gsub(/^[ \t]{2}/, "").strip
|
377
|
+
else
|
378
|
+
code = tag.text
|
379
|
+
end
|
380
|
+
|
381
|
+
[name, description, code]
|
382
|
+
end
|
383
|
+
|
374
384
|
def pretty_default_value(tag, component)
|
375
385
|
params = tag.object.parameters.find { |param| [tag.name.to_s, tag.name.to_s + ":"].include?(param[0]) }
|
376
386
|
default = tag.defaults&.first || params&.second
|
@@ -384,4 +394,49 @@ namespace :docs do
|
|
384
394
|
|
385
395
|
pretty_value(constant_value)
|
386
396
|
end
|
397
|
+
|
398
|
+
def docs_metadata(component)
|
399
|
+
(status_module, short_name) = status_module_and_short_name(component)
|
400
|
+
status_path = status_module.nil? ? "" : "#{status_module}/"
|
401
|
+
status = component.status.to_s
|
402
|
+
|
403
|
+
{
|
404
|
+
title: short_name,
|
405
|
+
component_id: short_name.underscore,
|
406
|
+
status: status.capitalize,
|
407
|
+
source: source_url(component),
|
408
|
+
storybook: storybook_url(component),
|
409
|
+
path: "docs/content/components/#{status_path}#{short_name.downcase}.md",
|
410
|
+
example_path: example_path(component),
|
411
|
+
require_js_path: require_js_path(component)
|
412
|
+
}
|
413
|
+
end
|
414
|
+
|
415
|
+
def source_url(component)
|
416
|
+
path = component.name.split("::").map(&:underscore).join("/")
|
417
|
+
|
418
|
+
"https://github.com/primer/view_components/tree/main/app/components/#{path}.rb"
|
419
|
+
end
|
420
|
+
|
421
|
+
def storybook_url(component)
|
422
|
+
path = component.name.split("::").map { |n| n.underscore.dasherize }.join("-")
|
423
|
+
|
424
|
+
"https://primer.style/view-components/stories/?path=/story/#{path}"
|
425
|
+
end
|
426
|
+
|
427
|
+
def example_path(component)
|
428
|
+
example_path = "../../src/@primer/gatsby-theme-doctocat/components/example"
|
429
|
+
example_path = "../#{example_path}" if status_module?(component)
|
430
|
+
example_path
|
431
|
+
end
|
432
|
+
|
433
|
+
def require_js_path(component)
|
434
|
+
require_js_path = "../../src/@primer/gatsby-theme-doctocat/components/requires-js-flash"
|
435
|
+
require_js_path = "../#{require_js_path}" if status_module?(component)
|
436
|
+
require_js_path
|
437
|
+
end
|
438
|
+
|
439
|
+
def status_module?(component)
|
440
|
+
(%w[Alpha Beta] & component.name.split("::")).any?
|
441
|
+
end
|
387
442
|
end
|
data/lib/tasks/utilities.rake
CHANGED
@@ -5,29 +5,23 @@ namespace :utilities do
|
|
5
5
|
require "yaml"
|
6
6
|
require "json"
|
7
7
|
require File.expand_path("./../../demo/config/environment.rb", __dir__)
|
8
|
+
require "primer/classify/utilities"
|
8
9
|
|
9
10
|
# Keys that are looked for to be included in the utilities.yml file
|
10
11
|
SUPPORTED_KEYS = %i[
|
11
12
|
anim
|
12
13
|
d
|
13
14
|
float
|
15
|
+
height
|
14
16
|
hide
|
15
17
|
m mt mr mb ml mx my
|
16
18
|
p pt pr pb pl px py
|
17
19
|
position
|
18
20
|
wb
|
21
|
+
width
|
19
22
|
v
|
20
23
|
].freeze
|
21
24
|
|
22
|
-
# Replacements for some classnames that end up being a different argument key
|
23
|
-
REPLACEMENT_KEYS = {
|
24
|
-
"^anim" => "animation",
|
25
|
-
"^v-align" => "vertical_align",
|
26
|
-
"^d" => "display",
|
27
|
-
"^wb" => "word_break",
|
28
|
-
"^v" => "visibility"
|
29
|
-
}.freeze
|
30
|
-
|
31
25
|
BREAKPOINTS = [nil, "sm", "md", "lg", "xl"].freeze
|
32
26
|
|
33
27
|
css_data =
|
@@ -52,7 +46,7 @@ namespace :utilities do
|
|
52
46
|
key = ""
|
53
47
|
|
54
48
|
# Look for a replacement key
|
55
|
-
REPLACEMENT_KEYS.each do |k, v|
|
49
|
+
Primer::Classify::Utilities::REPLACEMENT_KEYS.each do |k, v|
|
56
50
|
next unless classname.match?(Regexp.new(k))
|
57
51
|
|
58
52
|
key = v
|
data/lib/yard/docs_helper.rb
CHANGED
@@ -29,7 +29,7 @@ module YARD
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def link_to_accessibility
|
32
|
-
"[Accessibility](#
|
32
|
+
"[Accessibility](#accessibility)"
|
33
33
|
end
|
34
34
|
|
35
35
|
def link_to_system_arguments_docs
|
@@ -41,8 +41,10 @@ module YARD
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def link_to_component(component)
|
44
|
-
short_name = component
|
45
|
-
"
|
44
|
+
(status_module, short_name) = status_module_and_short_name(component)
|
45
|
+
status_path = status_module.nil? ? "" : "#{status_module}/"
|
46
|
+
|
47
|
+
"[#{short_name}](/components/#{status_path}#{short_name.downcase})"
|
46
48
|
end
|
47
49
|
|
48
50
|
def link_to_octicons
|
@@ -53,6 +55,13 @@ module YARD
|
|
53
55
|
"[Learn more about best heading practices (WAI Headings)](https://www.w3.org/WAI/tutorials/page-structure/headings/)"
|
54
56
|
end
|
55
57
|
|
58
|
+
def status_module_and_short_name(component)
|
59
|
+
name_with_status = component.name.gsub(/Primer::|Component/, "")
|
60
|
+
|
61
|
+
m = name_with_status.match(/(?<status>Beta|Alpha|Deprecated)?(::)?(?<name>.*)/)
|
62
|
+
[m[:status]&.downcase, m[:name].gsub("::", "")]
|
63
|
+
end
|
64
|
+
|
56
65
|
def pretty_value(val)
|
57
66
|
case val
|
58
67
|
when nil
|