primer_view_components 0.0.119 → 0.0.120

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 (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