primer_view_components 0.0.119 → 0.0.120

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -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/modal_dialog.js +2 -0
  6. data/app/components/primer/alpha/modal_dialog.ts +2 -0
  7. data/lib/primer/deprecations.yml +0 -13
  8. data/lib/primer/view_components/linters/button_component_migration_counter.rb +2 -2
  9. data/lib/primer/view_components/version.rb +1 -1
  10. data/lib/primer/view_components.rb +5 -0
  11. data/lib/primer/yard/backend.rb +38 -0
  12. data/lib/primer/yard/component_manifest.rb +123 -0
  13. data/lib/primer/yard/docs_helper.rb +81 -0
  14. data/lib/primer/yard/legacy_gatsby_backend.rb +271 -0
  15. data/lib/primer/yard/registry.rb +146 -0
  16. data/lib/primer/yard/renders_many_handler.rb +23 -0
  17. data/lib/primer/yard/renders_one_handler.rb +23 -0
  18. data/lib/rubocop/config/default.yml +3 -0
  19. data/lib/rubocop/cop/primer/test_selector.rb +48 -0
  20. data/lib/tasks/docs.rake +37 -405
  21. data/previews/primer/alpha/dialog_preview/body_has_scrollbar_overflow.html.erb +9 -0
  22. data/previews/primer/alpha/dialog_preview.rb +15 -0
  23. data/static/arguments.json +0 -32
  24. data/static/audited_at.json +0 -3
  25. data/static/constants.json +0 -20
  26. data/static/statuses.json +0 -3
  27. metadata +17 -15
  28. data/app/components/primer/box_component.rb +0 -7
  29. data/app/components/primer/clipboard_copy.rb +0 -7
  30. data/app/components/primer/dropdown_menu_component.html.erb +0 -8
  31. data/app/components/primer/dropdown_menu_component.rb +0 -58
  32. data/lib/yard/docs_helper.rb +0 -79
  33. data/lib/yard/renders_many_handler.rb +0 -19
  34. data/lib/yard/renders_one_handler.rb +0 -19
@@ -86,6 +86,7 @@ export class ModalDialogElement extends HTMLElement {
86
86
  return;
87
87
  this.setAttribute('open', '');
88
88
  (_a = __classPrivateFieldGet(this, _ModalDialogElement_instances, "a", _ModalDialogElement_overlayBackdrop_get)) === null || _a === void 0 ? void 0 : _a.classList.remove('Overlay--hidden');
89
+ document.body.style.paddingRight = `${window.innerWidth - document.body.clientWidth}px`;
89
90
  document.body.style.overflow = 'hidden';
90
91
  if (__classPrivateFieldGet(this, _ModalDialogElement_focusAbortController, "f").signal.aborted) {
91
92
  __classPrivateFieldSet(this, _ModalDialogElement_focusAbortController, new AbortController(), "f");
@@ -98,6 +99,7 @@ export class ModalDialogElement extends HTMLElement {
98
99
  return;
99
100
  this.removeAttribute('open');
100
101
  (_b = __classPrivateFieldGet(this, _ModalDialogElement_instances, "a", _ModalDialogElement_overlayBackdrop_get)) === null || _b === void 0 ? void 0 : _b.classList.add('Overlay--hidden');
102
+ document.body.style.paddingRight = '0';
101
103
  document.body.style.overflow = 'initial';
102
104
  __classPrivateFieldGet(this, _ModalDialogElement_focusAbortController, "f").abort();
103
105
  // if #openButton is a child of a menu, we need to focus a suitable child of the menu
@@ -81,6 +81,7 @@ export class ModalDialogElement extends HTMLElement {
81
81
  if (this.open) return
82
82
  this.setAttribute('open', '')
83
83
  this.#overlayBackdrop?.classList.remove('Overlay--hidden')
84
+ document.body.style.paddingRight = `${window.innerWidth - document.body.clientWidth}px`
84
85
  document.body.style.overflow = 'hidden'
85
86
  if (this.#focusAbortController.signal.aborted) {
86
87
  this.#focusAbortController = new AbortController()
@@ -91,6 +92,7 @@ export class ModalDialogElement extends HTMLElement {
91
92
  if (!this.open) return
92
93
  this.removeAttribute('open')
93
94
  this.#overlayBackdrop?.classList.add('Overlay--hidden')
95
+ document.body.style.paddingRight = '0'
94
96
  document.body.style.overflow = 'initial'
95
97
  this.#focusAbortController.abort()
96
98
  // if #openButton is a child of a menu, we need to focus a suitable child of the menu
@@ -17,24 +17,11 @@ deprecations:
17
17
  autocorrect: true
18
18
  replacement: "Primer::Beta::Blankslate"
19
19
 
20
- - component: "Primer::BoxComponent"
21
- autocorrect: true
22
- replacement: "Primer::Box"
23
-
24
20
  - component: "Primer::ButtonComponent"
25
21
  autocorrect: false
26
22
  replacement: "Primer::Beta::Button"
27
23
  guide: "https://primer.style/view-components/guides/primer_button_component"
28
24
 
29
- - component: "Primer::ClipboardCopy"
30
- autocorrect: true
31
- replacement: "Primer::Beta::ClipboardCopy"
32
-
33
- - component: "Primer::DropdownMenuComponent"
34
- autocorrect: false
35
- replacement: "Primer::Beta::Dropdown"
36
- guide: "https://primer.style/view-components/guides/primer_dropdown_menu_component"
37
-
38
25
  - component: "Primer::Dropdown"
39
26
  autocorrect: true
40
27
  replacement: "Primer::Alpha::Dropdown"
@@ -18,9 +18,9 @@ module ERBLint
18
18
  # CloseButton component has preference when this class is seen in conjunction with `btn`.
19
19
  DISALLOWED_CLASSES = %w[close-button].freeze
20
20
  CLASSES = %w[btn btn-link].freeze
21
- 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."
21
+ MESSAGE = "We are migrating buttons to use [Primer::Beta::Button](https://primer.style/view-components/components/beta/button), please try to use that instead of raw HTML."
22
22
  ARGUMENT_MAPPER = ArgumentMappers::Button
23
- COMPONENT = "Primer::ButtonComponent"
23
+ COMPONENT = "Primer::Beta::Button"
24
24
  end
25
25
  end
26
26
  end
@@ -6,7 +6,7 @@ module Primer
6
6
  module VERSION
7
7
  MAJOR = 0
8
8
  MINOR = 0
9
- PATCH = 119
9
+ PATCH = 120
10
10
 
11
11
  STRING = [MAJOR, MINOR, PATCH].join(".")
12
12
  end
@@ -54,5 +54,10 @@ module Primer
54
54
  def self.read(stats)
55
55
  File.read(File.join(DEFAULT_STATIC_PATH, FILE_NAMES[stats]))
56
56
  end
57
+
58
+ # primer/view_components root directory.
59
+ def self.root
60
+ Pathname(File.expand_path(File.join("..", ".."), __dir__))
61
+ end
57
62
  end
58
63
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :nocov:
4
+ module Primer
5
+ module YARD
6
+ # Shared functionality for generating documentation from YARD comments.
7
+ class Backend
8
+ include DocsHelper
9
+
10
+ private
11
+
12
+ def pretty_default_value(tag, component)
13
+ params = tag.object.parameters.find { |param| [tag.name.to_s, "#{tag.name}:"].include?(param[0]) }
14
+ default = tag.defaults&.first || params&.second
15
+
16
+ return "N/A" unless default
17
+
18
+ constant_name = "#{component.name}::#{default}"
19
+ constant_value = default.safe_constantize || constant_name.safe_constantize
20
+
21
+ return pretty_value(default) if constant_value.nil?
22
+
23
+ pretty_value(constant_value)
24
+ end
25
+
26
+ def view_context
27
+ @view_context ||= begin
28
+ # Rails controller for rendering arbitrary ERB
29
+ vc = ApplicationController.new.tap { |c| c.request = ActionDispatch::TestRequest.create }.view_context
30
+ vc.singleton_class.include(DocsHelper)
31
+ vc.singleton_class.include(Primer::ViewHelper)
32
+ vc
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ # :nocov:
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :nocov:
4
+ module Primer
5
+ module YARD
6
+ # The set of documented components (and associated metadata).
7
+ class ComponentManifest
8
+ COMPONENTS = {
9
+ Primer::Beta::RelativeTime => {},
10
+ Primer::Beta::IconButton => {},
11
+ Primer::Beta::Button => {},
12
+ Primer::Alpha::SegmentedControl => {},
13
+ Primer::Alpha::Layout => {},
14
+ Primer::Alpha::HellipButton => {},
15
+ Primer::Alpha::Image => {},
16
+ Primer::LocalTime => { js: true },
17
+ Primer::Alpha::OcticonSymbols => {},
18
+ Primer::Alpha::ImageCrop => { js: true },
19
+ Primer::IconButton => { js: true },
20
+ Primer::Beta::AutoComplete => { js: true },
21
+ Primer::Beta::AutoComplete::Item => {},
22
+ Primer::Beta::Avatar => {},
23
+ Primer::Beta::AvatarStack => {},
24
+ Primer::Beta::BaseButton => {},
25
+ Primer::Alpha::Banner => { js: true },
26
+ Primer::Beta::Blankslate => {},
27
+ Primer::Beta::BorderBox => {},
28
+ Primer::Beta::BorderBox::Header => {},
29
+ Primer::Box => {},
30
+ Primer::Beta::Breadcrumbs => {},
31
+ Primer::ButtonComponent => { js: true },
32
+ Primer::Beta::ButtonGroup => {},
33
+ Primer::Alpha::ButtonMarketing => {},
34
+ Primer::Beta::ClipboardCopy => { js: true },
35
+ Primer::Beta::CloseButton => {},
36
+ Primer::Beta::Counter => {},
37
+ Primer::Beta::Details => {},
38
+ Primer::Alpha::Dialog => {},
39
+ Primer::Alpha::Dropdown => { js: true },
40
+ Primer::Beta::Flash => {},
41
+ Primer::Beta::Heading => {},
42
+ Primer::Alpha::HiddenTextExpander => {},
43
+ Primer::Beta::Label => {},
44
+ Primer::LayoutComponent => {},
45
+ Primer::Beta::Link => { js: true },
46
+ Primer::Beta::Markdown => {},
47
+ Primer::Alpha::Menu => {},
48
+ Primer::Navigation::TabComponent => {},
49
+ Primer::Beta::Octicon => {},
50
+ Primer::Beta::Popover => {},
51
+ Primer::Beta::ProgressBar => {},
52
+ Primer::Beta::State => {},
53
+ Primer::Beta::Spinner => {},
54
+ Primer::Beta::Subhead => {},
55
+ Primer::Alpha::TabContainer => { js: true },
56
+ Primer::Beta::Text => {},
57
+ Primer::TimeAgoComponent => { js: true },
58
+ Primer::Beta::TimelineItem => {},
59
+ Primer::Tooltip => {},
60
+ Primer::Truncate => {},
61
+ Primer::Beta::Truncate => {},
62
+ Primer::Alpha::UnderlineNav => {},
63
+ Primer::Alpha::UnderlinePanels => { js: true },
64
+ Primer::Alpha::TabNav => {},
65
+ Primer::Alpha::TabPanels => { js: true },
66
+ Primer::Alpha::Tooltip => { js: true },
67
+ Primer::Alpha::ToggleSwitch => { js: true },
68
+
69
+ # Examples can be seen in the NavList docs
70
+ Primer::Alpha::NavList => { js: true },
71
+ Primer::Alpha::NavList::Item => { js: true, examples: false },
72
+ Primer::Alpha::NavList::Section => { js: true, examples: false },
73
+
74
+ # ActionList is a base component that should not be used by itself, and thus
75
+ # does not have examples of its own
76
+ Primer::Alpha::ActionList => { js: true, examples: false },
77
+ Primer::Alpha::ActionList::Divider => { examples: false },
78
+ Primer::Alpha::ActionList::Heading => { examples: false },
79
+ Primer::Alpha::ActionList::Item => { examples: false },
80
+
81
+ # Forms
82
+ Primer::Alpha::TextField => { form_component: true }
83
+ }.freeze
84
+
85
+ class << self
86
+ def each(&block)
87
+ COMPONENTS.keys.each(&block)
88
+ end
89
+
90
+ def components_with_docs
91
+ @components_with_docs ||= COMPONENTS.keys
92
+ end
93
+
94
+ def all_components
95
+ @all_components ||= Primer::Component.descendants - [Primer::BaseComponent]
96
+ end
97
+
98
+ def components_without_docs
99
+ @components_without_docs ||= all_components - components_with_docs
100
+ end
101
+
102
+ def components_with_examples
103
+ @components_with_examples ||= COMPONENTS.keys.select do |c|
104
+ COMPONENTS[c].fetch(:examples, true)
105
+ end
106
+ end
107
+
108
+ def components_requiring_js
109
+ @components_requiring_js ||= COMPONENTS.keys.select do |c|
110
+ COMPONENTS[c].fetch(:js, false)
111
+ end
112
+ end
113
+
114
+ def form_components
115
+ @form_components ||= COMPONENTS.keys.select do |c|
116
+ COMPONENTS[c].fetch(:form_component, false)
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ # :nocov:
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :nocov:
4
+
5
+ module Primer
6
+ module YARD
7
+ # Helper methods to use for yard documentation
8
+ module DocsHelper
9
+ def one_of(enumerable, lower: false, sort: true)
10
+ # Sort the array if requested
11
+ if sort
12
+ enumerable = enumerable.sort do |a, b|
13
+ a.instance_of?(b.class) ? a <=> b : a.class.to_s <=> b.class.to_s
14
+ end
15
+ end
16
+
17
+ values =
18
+ case enumerable
19
+ when Hash
20
+ enumerable.map do |key, value|
21
+ "#{pretty_value(key)} (#{pretty_value(value)})"
22
+ end
23
+ else
24
+ enumerable.map do |key|
25
+ pretty_value(key)
26
+ end
27
+ end
28
+
29
+ prefix = "One of"
30
+ prefix = prefix.downcase if lower
31
+
32
+ "#{prefix} #{values.to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')}."
33
+ end
34
+
35
+ def link_to_accessibility
36
+ "[Accessibility](#accessibility)"
37
+ end
38
+
39
+ def link_to_system_arguments_docs
40
+ "[System arguments](/system-arguments)"
41
+ end
42
+
43
+ def link_to_typography_docs
44
+ "[Typography](/system-arguments#typography)"
45
+ end
46
+
47
+ def link_to_component(component)
48
+ status_module, short_name, class_name = status_module_and_short_name(component)
49
+ status_path = status_module.nil? ? "" : "#{status_module}/"
50
+
51
+ "[#{class_name}](/components/#{status_path}#{short_name.downcase})"
52
+ end
53
+
54
+ def link_to_octicons
55
+ "[Octicon](https://primer.style/octicons/)"
56
+ end
57
+
58
+ def link_to_heading_practices
59
+ "[Learn more about best heading practices (WAI Headings)](https://www.w3.org/WAI/tutorials/page-structure/headings/)"
60
+ end
61
+
62
+ def status_module_and_short_name(component)
63
+ name_with_status = component.name.gsub(/Primer::|Component/, "")
64
+
65
+ m = name_with_status.match(/(?<status>Beta|Alpha|Deprecated)?(?<_colons>::)?(?<name>.*)/)
66
+ [m[:status]&.downcase, m[:name].gsub("::", ""), m[:name]]
67
+ end
68
+
69
+ def pretty_value(val)
70
+ case val
71
+ when nil
72
+ "`nil`"
73
+ when Symbol
74
+ "`:#{val}`"
75
+ else
76
+ "`#{val}`"
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,271 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Naming/MethodParameterName
4
+
5
+ # :nocov:
6
+
7
+ require "primer/yard/component_manifest"
8
+ require "primer/yard/backend"
9
+
10
+ module Primer
11
+ module YARD
12
+ # Backend that generates documentation for the legacy, Gatsby-powered PVC docsite.
13
+ class LegacyGatsbyBackend < Backend
14
+ class << self
15
+ def parse_example_tag(tag)
16
+ name = tag.name
17
+ description = nil
18
+ code = nil
19
+
20
+ if tag.text.include?("@description")
21
+ splitted = tag.text.split(/@description|@code/)
22
+ description = splitted.second.gsub(/^[ \t]{2}/, "").strip
23
+ code = splitted.last.gsub(/^[ \t]{2}/, "").strip
24
+ else
25
+ code = tag.text
26
+ end
27
+
28
+ [name, description, code]
29
+ end
30
+ end
31
+
32
+ attr_reader :registry
33
+
34
+ def initialize(registry)
35
+ @registry = registry
36
+ end
37
+
38
+ def generate
39
+ args_for_components = []
40
+ errors = []
41
+
42
+ each_component do |component|
43
+ docs = registry.find(component)
44
+ status_path = docs.status_module.nil? ? "" : "#{docs.status_module}/"
45
+
46
+ metadata = docs.metadata.merge(
47
+ source: source_url(component),
48
+ lookbook: lookbook_url(component),
49
+ path: "docs/content/components/#{status_path}#{docs.short_name.downcase}.md",
50
+ example_path: example_path(component),
51
+ require_js_path: require_js_path(component)
52
+ )
53
+
54
+ path = Pathname.new(metadata[:path])
55
+ path.dirname.mkpath unless path.dirname.exist?
56
+
57
+ File.open(path, "w") do |f|
58
+ f.puts("---")
59
+ f.puts("title: #{metadata[:title]}")
60
+ f.puts("componentId: #{metadata[:component_id]}")
61
+ f.puts("status: #{metadata[:status]}")
62
+ f.puts("source: #{metadata[:source]}")
63
+ f.puts("a11yReviewed: #{metadata[:a11y_reviewed]}")
64
+ f.puts("lookbook: #{metadata[:lookbook]}") if preview_exists?(component)
65
+ f.puts("---")
66
+ f.puts
67
+ f.puts("import Example from '#{metadata[:example_path]}'")
68
+
69
+ if docs.requires_js?
70
+ f.puts("import RequiresJSFlash from '#{metadata[:require_js_path]}'")
71
+ f.puts
72
+ f.puts("<RequiresJSFlash />")
73
+ end
74
+
75
+ f.puts
76
+ f.puts("<!-- Warning: AUTO-GENERATED file, do not edit. Add code comments to your Ruby instead <3 -->")
77
+ f.puts
78
+ f.puts(view_context.render(inline: docs.base_docstring))
79
+
80
+ if docs.tags(:deprecated).any?
81
+ f.puts
82
+ f.puts("## Deprecation")
83
+ docs.tags(:deprecated).each do |tag|
84
+ f.puts
85
+ f.puts view_context.render(inline: tag.text)
86
+ end
87
+ end
88
+
89
+ if docs.tags(:accessibility).any?
90
+ f.puts
91
+ f.puts("## Accessibility")
92
+ docs.tags(:accessibility).each do |tag|
93
+ f.puts
94
+ f.puts view_context.render(inline: tag.text)
95
+ end
96
+ end
97
+
98
+ errors << { component.name => { arguments: "No argument documentation found" } } unless docs.params.any?
99
+
100
+ f.puts
101
+ f.puts("## Arguments")
102
+ f.puts
103
+ f.puts("| Name | Type | Default | Description |")
104
+ f.puts("| :- | :- | :- | :- |")
105
+
106
+ documented_params = docs.params.map(&:name)
107
+ component_params = component.instance_method(:initialize).parameters.map { |p| p.last.to_s }
108
+
109
+ if (documented_params & component_params).size != component_params.size
110
+ err = { arguments: {} }
111
+ (component_params - documented_params).each do |arg|
112
+ err[:arguments][arg] = "Not documented"
113
+ end
114
+
115
+ errors << { component.name => err }
116
+ end
117
+
118
+ args = []
119
+ docs.params.each do |tag|
120
+ default_value = pretty_default_value(tag, component)
121
+
122
+ args << {
123
+ "name" => tag.name,
124
+ "type" => tag.types.join(", "),
125
+ "default" => default_value,
126
+ "description" => view_context.render(inline: tag.text.squish)
127
+ }
128
+
129
+ f.puts("| `#{tag.name}` | `#{tag.types.join(', ')}` | #{default_value} | #{view_context.render(inline: tag.text.squish)} |")
130
+ end
131
+
132
+ component_args = {
133
+ "component" => metadata[:title],
134
+ "status" => component.status.to_s,
135
+ "source" => metadata[:source],
136
+ "lookbook" => metadata[:lookbook],
137
+ "parameters" => args
138
+ }
139
+
140
+ args_for_components << component_args
141
+
142
+ if docs.slot_methods.any?
143
+ f.puts
144
+ f.puts("## Slots")
145
+
146
+ docs.slot_methods.each do |slot_docs|
147
+ emit_method(slot_docs, component, f)
148
+ end
149
+ end
150
+
151
+ example_tags = docs.constructor.tags(:example)
152
+
153
+ if example_tags.any?
154
+ f.puts
155
+ f.puts("## Examples")
156
+
157
+ example_tags.each do |tag|
158
+ name, description, code = parse_example_tag(tag)
159
+ f.puts
160
+ f.puts("### #{name}")
161
+ if description
162
+ f.puts
163
+ f.puts(view_context.render(inline: description.squish))
164
+ end
165
+ f.puts
166
+ html = view_context.render(inline: code)
167
+ f.puts("<Example src=\"#{html.tr('"', "\'").delete("\n")}\" />")
168
+ f.puts
169
+ f.puts("```erb")
170
+ f.puts(code.to_s)
171
+ f.puts("```")
172
+ end
173
+ elsif manifest.components_with_examples.include?(component)
174
+ errors << { component.name => { example: "No examples found" } }
175
+ end
176
+ end
177
+ end
178
+
179
+ # Build system arguments docs from BaseComponent
180
+ system_args_docs = registry.find(Primer::BaseComponent)
181
+
182
+ File.open("docs/content/system-arguments.md", "w") do |f|
183
+ f.puts("---")
184
+ f.puts("title: System arguments")
185
+ f.puts("---")
186
+ f.puts
187
+ f.puts("<!-- Warning: AUTO-GENERATED file, do not edit. Add code comments to your Ruby instead <3 -->")
188
+ f.puts
189
+ f.puts(system_args_docs.base_docstring)
190
+ f.puts
191
+
192
+ f.puts(view_context.render(inline: system_args_docs.constructor.base_docstring))
193
+ end
194
+
195
+ [args_for_components, errors]
196
+ end
197
+
198
+ private
199
+
200
+ def emit_method(method_docs, component, f)
201
+ f.puts
202
+ f.puts("### `#{method_docs.name}`")
203
+
204
+ if method_docs.base_docstring.to_s.present?
205
+ f.puts
206
+ f.puts(view_context.render(inline: method_docs.base_docstring))
207
+ end
208
+
209
+ param_tags = method_docs.tags(:param)
210
+ if param_tags.any?
211
+ f.puts
212
+ f.puts("| Name | Type | Default | Description |")
213
+ f.puts("| :- | :- | :- | :- |")
214
+ end
215
+
216
+ param_tags.each do |tag|
217
+ f.puts("| `#{tag.name}` | `#{tag.types.join(', ')}` | #{pretty_default_value(tag, component)} | #{view_context.render(inline: tag.text)} |")
218
+ end
219
+ end
220
+
221
+ def each_component(&block)
222
+ manifest.components_with_docs.sort_by(&:name).each(&block)
223
+ end
224
+
225
+ def manifest
226
+ Primer::YARD::ComponentManifest
227
+ end
228
+
229
+ def source_url(component)
230
+ path = component.name.split("::").map(&:underscore).join("/")
231
+
232
+ "https://github.com/primer/view_components/tree/main/app/components/#{path}.rb"
233
+ end
234
+
235
+ def lookbook_url(component)
236
+ path = component.name.underscore.gsub("_component", "")
237
+
238
+ "https://primer.style/view-components/lookbook/inspect/#{path}/default/"
239
+ end
240
+
241
+ def example_path(component)
242
+ example_path = "../../src/@primer/gatsby-theme-doctocat/components/example"
243
+ example_path = "../#{example_path}" if status_module?(component)
244
+ example_path
245
+ end
246
+
247
+ def require_js_path(component)
248
+ require_js_path = "../../src/@primer/gatsby-theme-doctocat/components/requires-js-flash"
249
+ require_js_path = "../#{require_js_path}" if status_module?(component)
250
+ require_js_path
251
+ end
252
+
253
+ def preview_exists?(component)
254
+ path = component.name.underscore
255
+
256
+ File.exist?("previews/#{path}_preview.rb")
257
+ end
258
+
259
+ def status_module?(component)
260
+ (%w[Alpha Beta] & component.name.split("::")).any?
261
+ end
262
+
263
+ def parse_example_tag(tag)
264
+ self.class.parse_example_tag(tag)
265
+ end
266
+ end
267
+ end
268
+ end
269
+ # :nocov:
270
+
271
+ # rubocop:enable Naming/MethodParameterName