primer_view_components 0.0.44 → 0.0.48
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 +187 -0
- data/app/components/primer/avatar_stack_component.rb +9 -3
- data/app/components/primer/base_component.rb +52 -23
- 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/border_box_component.rb +3 -0
- data/app/components/primer/clipboard_copy.rb +25 -7
- data/app/components/primer/component.rb +9 -1
- data/app/components/primer/details_component.rb +12 -8
- data/app/components/primer/image_crop.rb +1 -1
- data/app/components/primer/markdown.rb +9 -9
- data/app/components/primer/menu_component.rb +7 -3
- data/app/components/primer/navigation/tab_component.rb +19 -5
- 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 +8 -6
- data/app/components/primer/timeline_item_component.rb +2 -2
- data/app/components/primer/tooltip.rb +1 -1
- data/app/components/primer/truncate.rb +5 -0
- data/app/components/primer/underline_nav_component.rb +12 -6
- data/{app/lib → lib}/primer/classify.rb +16 -33
- data/{app/lib → lib}/primer/classify/cache.rb +6 -40
- data/{app/lib → lib}/primer/classify/flex.rb +0 -0
- data/{app/lib → lib}/primer/classify/functional_background_colors.rb +2 -0
- data/{app/lib → lib}/primer/classify/functional_border_colors.rb +2 -0
- data/{app/lib → lib}/primer/classify/functional_colors.rb +0 -0
- data/{app/lib → lib}/primer/classify/functional_text_colors.rb +2 -0
- data/{app/lib → lib}/primer/classify/grid.rb +0 -0
- data/lib/primer/classify/utilities.rb +148 -0
- data/lib/primer/classify/utilities.yml +1271 -0
- data/lib/primer/view_components.rb +1 -0
- data/lib/primer/view_components/linters/argument_mappers/system_arguments.rb +5 -4
- data/lib/primer/view_components/linters/button_component_migration_counter.rb +9 -5
- data/lib/primer/view_components/linters/helpers.rb +132 -17
- 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/docs.rake +72 -18
- data/lib/tasks/utilities.rake +105 -0
- data/lib/yard/docs_helper.rb +1 -1
- data/static/statuses.json +4 -4
- metadata +30 -21
- data/app/components/primer/auto_complete.rb +0 -156
- data/app/components/primer/auto_complete/item.rb +0 -42
- data/app/components/primer/avatar_component.rb +0 -75
- data/app/lib/primer/classify/spacing.rb +0 -63
@@ -7,7 +7,7 @@ module ERBLint
|
|
7
7
|
module ArgumentMappers
|
8
8
|
# Maps element attributes to system arguments.
|
9
9
|
class SystemArguments
|
10
|
-
|
10
|
+
STRING_PARAMETERS = %w[aria- data-].freeze
|
11
11
|
TEST_SELECTOR_REGEX = /test_selector\((?<selector>.+)\)$/.freeze
|
12
12
|
|
13
13
|
attr_reader :attribute
|
@@ -29,9 +29,10 @@ module ERBLint
|
|
29
29
|
{ test_selector: m[:selector].tr("'", '"') }
|
30
30
|
elsif attr_name == "data-test-selector"
|
31
31
|
{ test_selector: attribute.value.to_json }
|
32
|
-
elsif attr_name.start_with?(*
|
33
|
-
|
34
|
-
|
32
|
+
elsif attr_name.start_with?(*STRING_PARAMETERS)
|
33
|
+
raise ConversionError, "Cannot convert attribute \"#{attr_name}\" because its value contains an erb block" if attribute.value_node&.children&.any? { |n| n.try(:type) == :erb }
|
34
|
+
|
35
|
+
{ "\"#{attr_name}\"" => attribute.value.to_json }
|
35
36
|
else
|
36
37
|
raise ConversionError, "Cannot convert attribute \"#{attr_name}\""
|
37
38
|
end
|
@@ -21,14 +21,18 @@ module ERBLint
|
|
21
21
|
nil
|
22
22
|
end
|
23
23
|
|
24
|
-
def
|
25
|
-
|
24
|
+
def correction(args)
|
25
|
+
return nil if args.nil?
|
26
26
|
|
27
|
+
correction = "<%= render Primer::ButtonComponent.new"
|
28
|
+
correction += "(#{args})" if args.present?
|
29
|
+
"#{correction} do %>"
|
30
|
+
end
|
31
|
+
|
32
|
+
def message(args)
|
27
33
|
return MESSAGE if args.nil?
|
28
34
|
|
29
|
-
|
30
|
-
msg += "(#{args})" if args.present?
|
31
|
-
"#{msg} %>\n\nInstead of:\n"
|
35
|
+
"#{MESSAGE}\n\nTry using:\n\n#{correction(args)}\n\nInstead of:\n"
|
32
36
|
end
|
33
37
|
end
|
34
38
|
end
|
@@ -7,34 +7,69 @@ module ERBLint
|
|
7
7
|
module Linters
|
8
8
|
# Helper methods for linting ERB.
|
9
9
|
module Helpers
|
10
|
+
# from https://github.com/Shopify/erb-lint/blob/6179ee2d9d681a6ec4dd02351a1e30eefa748d3d/lib/erb_lint/linters/self_closing_tag.rb
|
11
|
+
SELF_CLOSING_TAGS = %w[
|
12
|
+
area base br col command embed hr input keygen
|
13
|
+
link menuitem meta param source track wbr img
|
14
|
+
].freeze
|
15
|
+
|
16
|
+
DUMP_FILE = ".erblint-counter-ignore.json"
|
17
|
+
|
10
18
|
def self.included(base)
|
11
19
|
base.include(ERBLint::LinterRegistry)
|
12
20
|
|
13
21
|
define_method "run" do |processed_source|
|
14
|
-
|
22
|
+
@total_offenses = 0
|
23
|
+
@offenses_not_corrected = 0
|
24
|
+
tags = tags(processed_source)
|
25
|
+
tag_tree = build_tag_tree(tags)
|
26
|
+
|
27
|
+
tags.each do |tag|
|
15
28
|
next if tag.closing?
|
16
29
|
next unless self.class::TAGS&.include?(tag.name)
|
17
30
|
|
18
|
-
classes = tag.attributes["class"]&.value&.split(" ")
|
31
|
+
classes = tag.attributes["class"]&.value&.split(" ") || []
|
19
32
|
|
20
|
-
|
33
|
+
tag_tree[tag][:offense] = false
|
21
34
|
|
22
|
-
|
35
|
+
next unless self.class::CLASSES.blank? || (classes & self.class::CLASSES).any?
|
36
|
+
|
37
|
+
args = map_arguments(tag)
|
38
|
+
correction = correction(args)
|
39
|
+
|
40
|
+
tag_tree[tag][:offense] = true
|
41
|
+
tag_tree[tag][:correctable] = !correction.nil?
|
42
|
+
tag_tree[tag][:message] = message(args)
|
43
|
+
tag_tree[tag][:correction] = correction
|
44
|
+
end
|
45
|
+
|
46
|
+
tag_tree.each do |tag, h|
|
47
|
+
next unless h[:offense]
|
48
|
+
|
49
|
+
@total_offenses += 1
|
50
|
+
# We always fix the offenses using blocks. The closing tag corresponds to `<% end %>`.
|
51
|
+
if h[:correctable]
|
52
|
+
add_offense(tag.loc, h[:message], h[:correction])
|
53
|
+
add_offense(h[:closing].loc, h[:message], "<% end %>")
|
54
|
+
else
|
55
|
+
@offenses_not_corrected += 1
|
56
|
+
generate_offense(self.class, processed_source, tag, h[:message])
|
57
|
+
end
|
23
58
|
end
|
24
59
|
|
25
60
|
counter_correct?(processed_source)
|
61
|
+
|
62
|
+
dump_data(processed_source) if ENV["DUMP_LINT_DATA"] == "1"
|
26
63
|
end
|
27
64
|
|
28
65
|
define_method "autocorrect" do |processed_source, offense|
|
29
66
|
return unless offense.context
|
30
67
|
|
31
68
|
lambda do |corrector|
|
32
|
-
if
|
33
|
-
|
34
|
-
corrector.replace(offense.source_range, offense.context)
|
69
|
+
if offense.context.include?(counter_disable)
|
70
|
+
correct_counter(corrector, processed_source, offense)
|
35
71
|
else
|
36
|
-
|
37
|
-
corrector.insert_before(processed_source.source_buffer.source_range, "#{offense.context}\n")
|
72
|
+
corrector.replace(offense.source_range, offense.context)
|
38
73
|
end
|
39
74
|
end
|
40
75
|
end
|
@@ -42,10 +77,77 @@ module ERBLint
|
|
42
77
|
|
43
78
|
private
|
44
79
|
|
80
|
+
# Override this function to convert the HTML element attributes to argument for a component.
|
81
|
+
#
|
82
|
+
# @return [Hash] if possible to map all attributes to arguments.
|
83
|
+
# @return [Nil] if cannot map to arguments.
|
84
|
+
def map_arguments(_tag)
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
|
88
|
+
# Override this function to define how to autocorrect an element to a component.
|
89
|
+
#
|
90
|
+
# @return [String] with the text to replace the HTML element if possible to correct.
|
91
|
+
# @return [Nil] if cannot correct element.
|
92
|
+
def correction(_tag)
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
|
96
|
+
# Override this function to customize the linter message.
|
97
|
+
#
|
98
|
+
# @return [String] message to show on linter error.
|
45
99
|
def message(_tag)
|
46
100
|
self.class::MESSAGE
|
47
101
|
end
|
48
102
|
|
103
|
+
def counter_disable
|
104
|
+
"erblint:counter #{self.class.name.demodulize}"
|
105
|
+
end
|
106
|
+
|
107
|
+
def correct_counter(corrector, processed_source, offense)
|
108
|
+
if processed_source.file_content.include?(counter_disable)
|
109
|
+
# update the counter if exists
|
110
|
+
corrector.replace(offense.source_range, offense.context)
|
111
|
+
else
|
112
|
+
# add comment with counter if none
|
113
|
+
corrector.insert_before(processed_source.source_buffer.source_range, "#{offense.context}\n")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# This assumes that the AST provided represents valid HTML, where each tag has a corresponding closing tag.
|
118
|
+
# From the tags, we build a structured tree which represents the tag hierarchy.
|
119
|
+
# With this, we are able to know where the tags start and end.
|
120
|
+
def build_tag_tree(tags)
|
121
|
+
tag_tree = {}
|
122
|
+
current_opened_tag = nil
|
123
|
+
|
124
|
+
tags.each do |tag|
|
125
|
+
if tag.closing?
|
126
|
+
if current_opened_tag && tag.name == current_opened_tag.name
|
127
|
+
tag_tree[current_opened_tag][:closing] = tag
|
128
|
+
current_opened_tag = tag_tree[current_opened_tag][:parent]
|
129
|
+
end
|
130
|
+
|
131
|
+
next
|
132
|
+
end
|
133
|
+
|
134
|
+
self_closing = self_closing?(tag)
|
135
|
+
|
136
|
+
tag_tree[tag] = {
|
137
|
+
closing: self_closing ? tag : nil,
|
138
|
+
parent: current_opened_tag
|
139
|
+
}
|
140
|
+
|
141
|
+
current_opened_tag = tag unless self_closing
|
142
|
+
end
|
143
|
+
|
144
|
+
tag_tree
|
145
|
+
end
|
146
|
+
|
147
|
+
def self_closing?(tag)
|
148
|
+
tag.self_closing? || SELF_CLOSING_TAGS.include?(tag.name)
|
149
|
+
end
|
150
|
+
|
49
151
|
def tags(processed_source)
|
50
152
|
processed_source.parser.nodes_with_type(:tag).map { |tag_node| BetterHtml::Tree::Tag.from_node(tag_node) }
|
51
153
|
end
|
@@ -54,7 +156,6 @@ module ERBLint
|
|
54
156
|
comment_node = nil
|
55
157
|
expected_count = 0
|
56
158
|
rule_name = self.class.name.match(/:?:?(\w+)\Z/)[1]
|
57
|
-
offenses_count = @offenses.length
|
58
159
|
|
59
160
|
processed_source.parser.ast.descendants(:erb).each do |node|
|
60
161
|
indicator_node, _, code_node, = *node
|
@@ -62,32 +163,46 @@ module ERBLint
|
|
62
163
|
comment = code_node&.loc&.source&.strip
|
63
164
|
|
64
165
|
if indicator == "#" && comment.start_with?("erblint:count") && comment.match(rule_name)
|
65
|
-
comment_node =
|
166
|
+
comment_node = node
|
66
167
|
expected_count = comment.match(/\s(\d+)\s?$/)[1].to_i
|
67
168
|
end
|
68
169
|
end
|
69
170
|
|
70
|
-
if
|
71
|
-
|
171
|
+
if @offenses_not_corrected.zero?
|
172
|
+
# have to adjust to get `\n` so we delete the whole line
|
173
|
+
add_offense(processed_source.to_source_range(comment_node.loc.adjust(end_pos: 1)), "Unused erblint:count comment for #{rule_name}", "") if comment_node
|
72
174
|
return
|
73
175
|
end
|
74
176
|
|
75
177
|
first_offense = @offenses[0]
|
76
178
|
|
77
179
|
if comment_node.nil?
|
78
|
-
add_offense(processed_source.to_source_range(first_offense.source_range), "#{rule_name}: If you must, add <%# erblint:counter #{rule_name} #{
|
79
|
-
|
180
|
+
add_offense(processed_source.to_source_range(first_offense.source_range), "#{rule_name}: If you must, add <%# erblint:counter #{rule_name} #{@offenses_not_corrected} %> to bypass this check.", "<%# erblint:counter #{rule_name} #{@offenses_not_corrected} %>")
|
181
|
+
elsif expected_count != @offenses_not_corrected
|
182
|
+
add_offense(processed_source.to_source_range(comment_node.loc), "Incorrect erblint:counter number for #{rule_name}. Expected: #{expected_count}, actual: #{@offenses_not_corrected}.", "<%# erblint:counter #{rule_name} #{@offenses_not_corrected} %>")
|
183
|
+
# the only offenses remaining are not autocorrectable, so we can ignore them
|
184
|
+
elsif expected_count == @offenses_not_corrected && @offenses.size == @offenses_not_corrected
|
80
185
|
clear_offenses
|
81
|
-
add_offense(processed_source.to_source_range(comment_node.loc), "Incorrect erblint:counter number for #{rule_name}. Expected: #{expected_count}, actual: #{offenses_count}.", " erblint:counter #{rule_name} #{offenses_count} ") if expected_count != offenses_count
|
82
186
|
end
|
83
187
|
end
|
84
188
|
|
85
189
|
def generate_offense(klass, processed_source, tag, message = nil, replacement = nil)
|
86
190
|
message ||= klass::MESSAGE
|
87
|
-
klass_name = klass.name.
|
191
|
+
klass_name = klass.name.demodulize
|
88
192
|
offense = ["#{klass_name}:#{message}", tag.node.loc.source].join("\n")
|
89
193
|
add_offense(processed_source.to_source_range(tag.loc), offense, replacement)
|
90
194
|
end
|
195
|
+
|
196
|
+
def dump_data(processed_source)
|
197
|
+
return if @total_offenses.zero?
|
198
|
+
|
199
|
+
data = File.exist?(DUMP_FILE) ? JSON.parse(File.read(DUMP_FILE)) : {}
|
200
|
+
|
201
|
+
data[processed_source.filename] ||= {}
|
202
|
+
data[processed_source.filename][self.class.name.demodulize] = @total_offenses
|
203
|
+
|
204
|
+
File.write(DUMP_FILE, JSON.pretty_generate(data))
|
205
|
+
end
|
91
206
|
end
|
92
207
|
end
|
93
208
|
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
|
data/lib/tasks/docs.rake
CHANGED
@@ -32,9 +32,9 @@ namespace :docs do
|
|
32
32
|
Primer::OcticonSymbolsComponent,
|
33
33
|
Primer::ImageCrop,
|
34
34
|
Primer::IconButton,
|
35
|
-
Primer::AutoComplete,
|
36
|
-
Primer::AutoComplete::Item,
|
37
|
-
Primer::
|
35
|
+
Primer::Beta::AutoComplete,
|
36
|
+
Primer::Beta::AutoComplete::Item,
|
37
|
+
Primer::Beta::Avatar,
|
38
38
|
Primer::AvatarStackComponent,
|
39
39
|
Primer::BaseButton,
|
40
40
|
Primer::BlankslateComponent,
|
@@ -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,30 @@ 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("status: #{
|
112
|
-
f.puts("source:
|
113
|
-
f.puts("storybook:
|
112
|
+
f.puts("title: #{data[:title]}")
|
113
|
+
f.puts("status: #{data[:status]}")
|
114
|
+
f.puts("source: #{data[:source]}")
|
115
|
+
f.puts("storybook: #{data[:storybook]}")
|
114
116
|
f.puts("---")
|
115
117
|
f.puts
|
116
|
-
f.puts("import Example from '
|
118
|
+
f.puts("import Example from '#{data[:example_path]}'")
|
117
119
|
|
118
120
|
initialize_method = documentation.meths.find(&:constructor?)
|
119
121
|
|
120
122
|
if js_components.include?(component)
|
121
|
-
f.puts("import RequiresJSFlash from '
|
123
|
+
f.puts("import RequiresJSFlash from '#{data[:require_js_path]}'")
|
122
124
|
f.puts
|
123
125
|
f.puts("<RequiresJSFlash />")
|
124
126
|
end
|
@@ -176,15 +178,15 @@ namespace :docs do
|
|
176
178
|
"name" => tag.name,
|
177
179
|
"type" => tag.types.join(", "),
|
178
180
|
"default" => default_value,
|
179
|
-
"description" => view_context.render(inline: tag.text)
|
181
|
+
"description" => view_context.render(inline: tag.text.squish)
|
180
182
|
}
|
181
183
|
|
182
|
-
f.puts("| `#{tag.name}` | `#{tag.types.join(', ')}` | #{default_value} | #{view_context.render(inline: tag.text)} |")
|
184
|
+
f.puts("| `#{tag.name}` | `#{tag.types.join(', ')}` | #{default_value} | #{view_context.render(inline: tag.text.squish)} |")
|
183
185
|
end
|
184
186
|
|
185
187
|
component_args = {
|
186
|
-
"component" =>
|
187
|
-
"source" =>
|
188
|
+
"component" => data[:title],
|
189
|
+
"source" => data[:source],
|
188
190
|
"parameters" => args
|
189
191
|
}
|
190
192
|
|
@@ -346,6 +348,7 @@ namespace :docs do
|
|
346
348
|
end
|
347
349
|
|
348
350
|
def generate_yard_registry
|
351
|
+
ENV["SKIP_STORYBOOK_PRELOAD"] = "1"
|
349
352
|
require File.expand_path("./../../demo/config/environment.rb", __dir__)
|
350
353
|
require "primer/view_components"
|
351
354
|
require "yard/docs_helper"
|
@@ -384,4 +387,55 @@ namespace :docs do
|
|
384
387
|
|
385
388
|
pretty_value(constant_value)
|
386
389
|
end
|
390
|
+
|
391
|
+
def status_module_and_short_name(component)
|
392
|
+
name_with_status = component.name.gsub(/Primer::|Component/, "")
|
393
|
+
|
394
|
+
m = name_with_status.match(/(?<status>Beta|Alpha|Deprecated)?(::)?(?<name>.*)/)
|
395
|
+
[m[:status]&.downcase, m[:name].gsub("::", "")]
|
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
|
+
status: status.capitalize,
|
406
|
+
source: source_url(component),
|
407
|
+
storybook: storybook_url(component),
|
408
|
+
path: "docs/content/components/#{status_path}#{short_name.downcase}.md",
|
409
|
+
example_path: example_path(component),
|
410
|
+
require_js_path: require_js_path(component)
|
411
|
+
}
|
412
|
+
end
|
413
|
+
|
414
|
+
def source_url(component)
|
415
|
+
path = component.name.split("::").map(&:underscore).join("/")
|
416
|
+
|
417
|
+
"https://github.com/primer/view_components/tree/main/app/components/#{path}.rb"
|
418
|
+
end
|
419
|
+
|
420
|
+
def storybook_url(component)
|
421
|
+
path = component.name.split("::").map { |n| n.underscore.dasherize }.join("-")
|
422
|
+
|
423
|
+
"https://primer.style/view-components/stories/?path=/story/#{path}"
|
424
|
+
end
|
425
|
+
|
426
|
+
def example_path(component)
|
427
|
+
example_path = "../../src/@primer/gatsby-theme-doctocat/components/example"
|
428
|
+
example_path = "../#{example_path}" if status_module?(component)
|
429
|
+
example_path
|
430
|
+
end
|
431
|
+
|
432
|
+
def require_js_path(component)
|
433
|
+
require_js_path = "../../src/@primer/gatsby-theme-doctocat/components/requires-js-flash"
|
434
|
+
require_js_path = "../#{require_js_path}" if status_module?(component)
|
435
|
+
require_js_path
|
436
|
+
end
|
437
|
+
|
438
|
+
def status_module?(component)
|
439
|
+
(%w[Alpha Beta] & component.name.split("::")).any?
|
440
|
+
end
|
387
441
|
end
|