primer_view_components 0.0.39 → 0.0.40

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -2
  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/avatar_stack_component.rb +5 -2
  6. data/app/components/primer/base_component.rb +2 -2
  7. data/app/components/primer/blankslate_component.rb +3 -3
  8. data/app/components/primer/button_group.rb +1 -1
  9. data/app/components/primer/component.rb +5 -0
  10. data/app/components/primer/counter_component.rb +6 -1
  11. data/app/components/primer/flex_component.rb +27 -0
  12. data/app/components/primer/flex_item_component.rb +1 -1
  13. data/app/components/primer/heading_component.rb +11 -18
  14. data/app/components/primer/hidden_text_expander.rb +1 -1
  15. data/app/components/primer/icon_button.rb +20 -3
  16. data/app/components/primer/image_crop.d.ts +1 -0
  17. data/app/components/primer/image_crop.html.erb +12 -0
  18. data/app/components/primer/image_crop.js +1 -0
  19. data/app/components/primer/image_crop.rb +36 -0
  20. data/app/components/primer/image_crop.ts +1 -0
  21. data/app/components/primer/{markdown_component.rb → markdown.rb} +5 -4
  22. data/app/components/primer/octicon_component.html.erb +7 -0
  23. data/app/components/primer/octicon_component.rb +15 -8
  24. data/app/components/primer/octicon_symbols_component.html.erb +3 -0
  25. data/app/components/primer/octicon_symbols_component.rb +61 -0
  26. data/app/components/primer/primer.d.ts +1 -0
  27. data/app/components/primer/primer.js +1 -0
  28. data/app/components/primer/primer.ts +1 -0
  29. data/app/components/primer/spinner_component.rb +2 -2
  30. data/app/components/primer/subhead_component.rb +34 -4
  31. data/app/components/primer/text_component.rb +5 -2
  32. data/app/lib/primer/classify.rb +1 -1
  33. data/app/lib/primer/classify/cache.rb +1 -1
  34. data/app/lib/primer/octicon/cache.rb +4 -0
  35. data/lib/primer/view_components.rb +1 -1
  36. data/lib/primer/view_components/version.rb +1 -1
  37. data/lib/tasks/coverage.rake +14 -0
  38. data/lib/tasks/docs.rake +312 -0
  39. data/lib/tasks/statuses.rake +12 -0
  40. data/lib/yard/docs_helper.rb +57 -0
  41. data/static/statuses.json +52 -1
  42. metadata +15 -3
@@ -0,0 +1,7 @@
1
+ <%= render(Primer::BaseComponent.new(**@system_arguments)) do %>
2
+ <% if @use_symbol %>
3
+ <use href="#octicon_<%= [@icon.symbol, @icon.height].join("_") %>"></use>
4
+ <% else %>
5
+ <%= @icon.path.html_safe %>
6
+ <% end %>
7
+ <% end %>
@@ -4,14 +4,18 @@ require "octicons"
4
4
 
5
5
  module Primer
6
6
  # `Octicon` renders an <%= link_to_octicons %> with <%= link_to_system_arguments_docs %>.
7
+ # `Octicon` can also be rendered with the `primer_octicon` helper, which accepts the same arguments.
7
8
  class OcticonComponent < Primer::Component
8
9
  status :beta
9
10
 
10
11
  SIZE_DEFAULT = :small
12
+ SIZE_MEDIUM = :medium
13
+ SIZE_LARGE = :large
14
+
11
15
  SIZE_MAPPINGS = {
12
16
  SIZE_DEFAULT => 16,
13
- :medium => 32,
14
- :large => 64
17
+ SIZE_MEDIUM => 32,
18
+ SIZE_LARGE => 64
15
19
  }.freeze
16
20
  SIZE_OPTIONS = SIZE_MAPPINGS.keys
17
21
 
@@ -25,16 +29,21 @@ module Primer
25
29
  # @example Large
26
30
  # <%= render(Primer::OcticonComponent.new("x", size: :large)) %>
27
31
  #
32
+ # @example Helper
33
+ # <%= primer_octicon("check") %>
34
+ #
28
35
  # @param icon [String] Name of <%= link_to_octicons %> to use.
29
36
  # @param size [Symbol] <%= one_of(Primer::OcticonComponent::SIZE_MAPPINGS) %>
37
+ # @param use_symbol [Boolean] EXPERIMENTAL (May change or be removed) - Set to true when using with <%= link_to_component(Primer::OcticonSymbolsComponent) %>.
30
38
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
31
- def initialize(icon_name = nil, icon: nil, size: SIZE_DEFAULT, **system_arguments)
39
+ def initialize(icon_name = nil, icon: nil, size: SIZE_DEFAULT, use_symbol: false, **system_arguments)
32
40
  icon_key = icon_name || icon
33
- cache_key = [icon_key, size, system_arguments.slice(:height, :width)].join("_")
41
+ cache_key = Primer::Octicon::Cache.get_key(symbol: icon_key, size: size, **system_arguments.slice(:height, :width))
34
42
 
35
43
  @system_arguments = system_arguments
36
44
  @system_arguments[:tag] = :svg
37
45
  @system_arguments[:aria] ||= {}
46
+ @use_symbol = use_symbol
38
47
 
39
48
  if @system_arguments[:aria][:label] || @system_arguments[:"aria-label"]
40
49
  @system_arguments[:role] = "img"
@@ -62,10 +71,8 @@ module Primer
62
71
  @system_arguments.merge!(@icon.options.except(:class, :'aria-hidden'))
63
72
  end
64
73
 
65
- def call
66
- render(Primer::BaseComponent.new(**@system_arguments)) { @icon.path.html_safe } # rubocop:disable Rails/OutputSafety
74
+ def self._after_compile
75
+ Primer::Octicon::Cache.preload!
67
76
  end
68
-
69
- Primer::Octicon::Cache.preload!
70
77
  end
71
78
  end
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" hidden>
2
+ <%= symbol_tags %>
3
+ </svg>
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "octicons"
4
+
5
+ module Primer
6
+ # OcticonSymbols renders a symbol dictionary using a list of <%= link_to_octicons %>.
7
+ class OcticonSymbolsComponent < Primer::Component
8
+ # @example Symbol dictionary
9
+ # <%= render(Primer::OcticonComponent.new(icon: :check, use_symbol: true, color: :icon_success)) %>
10
+ # <%= render(Primer::OcticonComponent.new(icon: :check, use_symbol: true, color: :text_danger)) %>
11
+ # <%= render(Primer::OcticonComponent.new(icon: :check, use_symbol: true, size: :medium)) %>
12
+ # <%= render(Primer::OcticonSymbolsComponent.new(icons: [{ symbol: :check }, { symbol: :check, size: :medium }])) %>
13
+ #
14
+ # @param icons [Array<Hash>] List of icons to render, in the format { symbol: :icon_name, size: :small }
15
+ def initialize(icons: [])
16
+ @icons = {}
17
+ icons.each do |icon|
18
+ symbol = icon[:symbol]
19
+ size = Primer::OcticonComponent::SIZE_MAPPINGS[
20
+ fetch_or_fallback(Primer::OcticonComponent::SIZE_OPTIONS, icon[:size] || Primer::OcticonComponent::SIZE_DEFAULT, Primer::OcticonComponent::SIZE_DEFAULT)
21
+ ]
22
+
23
+ cache_key = Primer::Octicon::Cache.get_key(symbol: symbol, size: size)
24
+
25
+ if (cache_icon = Primer::Octicon::Cache.read(cache_key))
26
+ icon_instance = cache_icon
27
+ else
28
+ icon_instance = Octicons::Octicon.new(symbol, height: size)
29
+
30
+ Primer::Octicon::Cache.set(cache_key, icon_instance)
31
+ end
32
+
33
+ # Don't put the same icon twice
34
+ @icons[[symbol, icon_instance.height]] = icon_instance if @icons[[symbol, icon_instance.height]].nil?
35
+ end
36
+ end
37
+
38
+ def render?
39
+ @icons.any?
40
+ end
41
+
42
+ def self._after_compile
43
+ Primer::Octicon::Cache.preload!
44
+ end
45
+
46
+ def symbol_tags
47
+ safe_join(
48
+ @icons.values.map do |icon|
49
+ content_tag(
50
+ :symbol,
51
+ icon.path.html_safe, # rubocop:disable Rails/OutputSafety
52
+ id: "octicon_#{icon.symbol}_#{icon.height}",
53
+ viewBox: icon.options[:viewBox],
54
+ width: icon.width,
55
+ height: icon.height
56
+ )
57
+ end
58
+ )
59
+ end
60
+ end
61
+ end
@@ -2,3 +2,4 @@ import './auto_complete/auto_complete';
2
2
  import './clipboard_copy_component';
3
3
  import './tab_container_component';
4
4
  import './time_ago_component';
5
+ import './image_crop';
@@ -2,3 +2,4 @@ import './auto_complete/auto_complete';
2
2
  import './clipboard_copy_component';
3
3
  import './tab_container_component';
4
4
  import './time_ago_component';
5
+ import './image_crop';
@@ -2,3 +2,4 @@ import './auto_complete/auto_complete'
2
2
  import './clipboard_copy_component'
3
3
  import './tab_container_component'
4
4
  import './time_ago_component'
5
+ import './image_crop'
@@ -27,10 +27,10 @@ module Primer
27
27
  # <%= render(Primer::SpinnerComponent.new(size: :large)) %>
28
28
  #
29
29
  # @param size [Symbol] <%= one_of(Primer::SpinnerComponent::SIZE_MAPPINGS) %>
30
- def initialize(size: DEFAULT_SIZE, **system_arguments)
30
+ def initialize(size: DEFAULT_SIZE, style: DEFAULT_STYLE, **system_arguments)
31
31
  @system_arguments = system_arguments
32
32
  @system_arguments[:tag] = :svg
33
- @system_arguments[:style] ||= DEFAULT_STYLE
33
+ @system_arguments[:style] ||= style
34
34
  @system_arguments[:animation] = :rotate
35
35
  @system_arguments[:width] = SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size, DEFAULT_SIZE)]
36
36
  @system_arguments[:height] = SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size, DEFAULT_SIZE)]
@@ -1,16 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Primer
4
- # Use `Subhead` for page headings.
4
+ # Use `Subhead` as the start of a section. The `:heading` slot will render an `<h2>` font-sized text.
5
+ #
6
+ # - Optionally set the `:description` slot to render a short description and the `:actions` slot for a related action.
7
+ # - Use a succint, one-line description for the `:description` slot. For longer descriptions, omit the description slot and render a paragraph below the `Subhead`.
8
+ # - Use the actions slot to render a related action to the right of the heading. Use <%= link_to_component(Primer::ButtonComponent) %> or <%= link_to_component(Primer::LinkComponent) %>.
9
+ #
10
+ # @accessibility
11
+ # The `:heading` slot defaults to rendering a `<div>`. Update the tag to a heading element with the appropriate level to improve page navigation for assistive technologies.
12
+ # <%= link_to_heading_practices %>
5
13
  class SubheadComponent < Primer::Component
6
14
  status :beta
7
15
 
16
+ DEFAULT_HEADING_TAG = :div
17
+ HEADING_TAG_OPTIONS = [DEFAULT_HEADING_TAG, :h1, :h2, :h3, :h4, :h5, :h6].freeze
18
+
8
19
  # The heading
9
20
  #
21
+ # @param tag [Symbol] <%= one_of(Primer::SubheadComponent::HEADING_TAG_OPTIONS)%>
10
22
  # @param danger [Boolean] Whether to style the heading as dangerous.
11
23
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
12
- renders_one :heading, lambda { |danger: false, **system_arguments|
13
- system_arguments[:tag] ||= :div
24
+ renders_one :heading, lambda { |tag: DEFAULT_HEADING_TAG, danger: false, **system_arguments|
25
+ system_arguments[:tag] = fetch_or_fallback(HEADING_TAG_OPTIONS, tag, DEFAULT_HEADING_TAG)
14
26
  system_arguments[:classes] = class_names(
15
27
  system_arguments[:classes],
16
28
  "Subhead-heading",
@@ -42,7 +54,7 @@ module Primer
42
54
 
43
55
  # @example Default
44
56
  # <%= render(Primer::SubheadComponent.new) do |component| %>
45
- # <% component.heading do %>
57
+ # <% component.heading(tag: :h3) do %>
46
58
  # My Heading
47
59
  # <% end %>
48
60
  # <% component.description do %>
@@ -50,6 +62,24 @@ module Primer
50
62
  # <% end %>
51
63
  # <% end %>
52
64
  #
65
+ # @example With dangerous heading
66
+ # <%= render(Primer::SubheadComponent.new) do |component| %>
67
+ # <% component.heading(tag: :h3, danger: true) do %>
68
+ # My Heading
69
+ # <% end %>
70
+ # <% component.description do %>
71
+ # My Description
72
+ # <% end %>
73
+ # <% end %>
74
+ #
75
+ # @example With long description
76
+ # <%= render(Primer::SubheadComponent.new) do |component| %>
77
+ # <% component.heading(tag: :h3) do %>
78
+ # My Heading
79
+ # <% end %>
80
+ # <% end %>
81
+ # <p> This is a longer description that is sitting below the Subhead. It's much longer than a description that could sit comfortably in the Subhead. </p>
82
+ #
53
83
  # @example Without border
54
84
  # <%= render(Primer::SubheadComponent.new(hide_border: true)) do |component| %>
55
85
  # <% component.heading do %>
@@ -5,14 +5,17 @@ module Primer
5
5
  class TextComponent < Primer::Component
6
6
  status :beta
7
7
 
8
+ DEFAULT_TAG = :span
9
+
8
10
  # @example Default
9
11
  # <%= render(Primer::TextComponent.new(tag: :p, font_weight: :bold)) { "Bold Text" } %>
10
12
  # <%= render(Primer::TextComponent.new(tag: :p, color: :text_danger)) { "Danger Text" } %>
11
13
  #
14
+ # @param tag [Symbol]
12
15
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
13
- def initialize(**system_arguments)
16
+ def initialize(tag: DEFAULT_TAG, **system_arguments)
14
17
  @system_arguments = system_arguments
15
- @system_arguments[:tag] ||= :span
18
+ @system_arguments[:tag] = tag
16
19
  end
17
20
 
18
21
  def call
@@ -207,7 +207,7 @@ module Primer
207
207
  elsif Primer::Classify::Flex::KEYS.include?(key)
208
208
  memo[:classes] << Primer::Classify::Flex.classes(key, val, breakpoint)
209
209
  elsif key == WIDTH_KEY || key == HEIGHT_KEY
210
- if val == :fit || val == :fill
210
+ if val == :fit
211
211
  memo[:classes] << "#{key}-#{val}"
212
212
  else
213
213
  memo[key] = val
@@ -111,7 +111,7 @@ module Primer
111
111
 
112
112
  preload(
113
113
  keys: [Primer::Classify::WIDTH_KEY, Primer::Classify::HEIGHT_KEY],
114
- values: [:fit, :fill]
114
+ values: [:fit]
115
115
  )
116
116
 
117
117
  preload(
@@ -9,6 +9,10 @@ module Primer
9
9
  PRELOADED_ICONS = [:alert, :check, :"chevron-down", :clippy, :clock, :"dot-fill", :info, :"kebab-horizontal", :link, :lock, :mail, :pencil, :plus, :question, :repo, :search, :"shield-lock", :star, :trash, :x].freeze
10
10
 
11
11
  class << self
12
+ def get_key(symbol:, size:, width: nil, height: nil)
13
+ [symbol, size, width, height].join("_")
14
+ end
15
+
12
16
  def read(key)
13
17
  LOOKUP[key]
14
18
  end
@@ -27,7 +27,7 @@ module Primer
27
27
  statuses = generate_statuses
28
28
 
29
29
  File.open(File.join(path, DEFAULT_STATUS_FILE_NAME), "w") do |f|
30
- f.write(statuses.to_json)
30
+ f.write(JSON.pretty_generate(statuses))
31
31
  f.write($INPUT_RECORD_SEPARATOR)
32
32
  end
33
33
  end
@@ -5,7 +5,7 @@ module Primer
5
5
  module VERSION
6
6
  MAJOR = 0
7
7
  MINOR = 0
8
- PATCH = 39
8
+ PATCH = 40
9
9
 
10
10
  STRING = [MAJOR, MINOR, PATCH].join(".")
11
11
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :coverage do
4
+ task :report do
5
+ require "simplecov"
6
+ require "simplecov-console"
7
+
8
+ SimpleCov.minimum_coverage 100
9
+
10
+ SimpleCov.collate Dir["simplecov-resultset-*/.resultset.json"], "rails" do
11
+ formatter SimpleCov::Formatter::Console
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,312 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :docs do
4
+ task :livereload do
5
+ require "listen"
6
+
7
+ Rake::Task["docs:build"].execute
8
+
9
+ puts "Listening for changes to documentation..."
10
+
11
+ listener = Listen.to("app") do |modified, added, removed|
12
+ puts "modified absolute path: #{modified}"
13
+ puts "added absolute path: #{added}"
14
+ puts "removed absolute path: #{removed}"
15
+
16
+ Rake::Task["docs:build"].execute
17
+ end
18
+ listener.start # not blocking
19
+ sleep
20
+ end
21
+
22
+ task :build do
23
+ require File.expand_path("./../../demo/config/environment.rb", __dir__)
24
+ require "primer/view_components"
25
+ require "yard/docs_helper"
26
+ require "view_component/test_helpers"
27
+ include ViewComponent::TestHelpers
28
+ include Primer::ViewHelper
29
+ include YARD::DocsHelper
30
+
31
+ Dir["./app/components/primer/**/*.rb"].sort.each { |file| require file }
32
+
33
+ YARD::Rake::YardocTask.new
34
+
35
+ # Custom tags for yard
36
+ YARD::Tags::Library.define_tag("Accessibility", :accessibility)
37
+ YARD::Tags::Library.define_tag("Deprecation", :deprecation)
38
+
39
+ puts "Building YARD documentation."
40
+ Rake::Task["yard"].execute
41
+
42
+ puts "Converting YARD documentation to Markdown files."
43
+
44
+ # Rails controller for rendering arbitrary ERB
45
+ view_context = ApplicationController.new.tap { |c| c.request = ActionDispatch::TestRequest.create }.view_context
46
+
47
+ registry = YARD::RegistryStore.new
48
+ registry.load!(".yardoc")
49
+ components = [
50
+ Primer::OcticonSymbolsComponent,
51
+ Primer::ImageCrop,
52
+ Primer::IconButton,
53
+ Primer::AutoComplete,
54
+ Primer::AutoComplete::Item,
55
+ Primer::AvatarComponent,
56
+ Primer::AvatarStackComponent,
57
+ Primer::BaseButton,
58
+ Primer::BlankslateComponent,
59
+ Primer::BorderBoxComponent,
60
+ Primer::BoxComponent,
61
+ Primer::BreadcrumbComponent,
62
+ Primer::ButtonComponent,
63
+ Primer::ButtonGroup,
64
+ Primer::ButtonMarketingComponent,
65
+ Primer::ClipboardCopy,
66
+ Primer::CloseButton,
67
+ Primer::CounterComponent,
68
+ Primer::DetailsComponent,
69
+ Primer::DropdownComponent,
70
+ Primer::DropdownMenuComponent,
71
+ Primer::FlashComponent,
72
+ Primer::FlexComponent,
73
+ Primer::FlexItemComponent,
74
+ Primer::HeadingComponent,
75
+ Primer::HiddenTextExpander,
76
+ Primer::LabelComponent,
77
+ Primer::LayoutComponent,
78
+ Primer::LinkComponent,
79
+ Primer::Markdown,
80
+ Primer::MenuComponent,
81
+ Primer::Navigation::TabComponent,
82
+ Primer::OcticonComponent,
83
+ Primer::PopoverComponent,
84
+ Primer::ProgressBarComponent,
85
+ Primer::StateComponent,
86
+ Primer::SpinnerComponent,
87
+ Primer::SubheadComponent,
88
+ Primer::TabContainerComponent,
89
+ Primer::TabNavComponent,
90
+ Primer::TextComponent,
91
+ Primer::TimeAgoComponent,
92
+ Primer::TimelineItemComponent,
93
+ Primer::TooltipComponent,
94
+ Primer::Truncate,
95
+ Primer::UnderlineNavComponent
96
+ ]
97
+
98
+ js_components = [
99
+ Primer::ImageCrop,
100
+ Primer::AutoComplete,
101
+ Primer::ClipboardCopy,
102
+ Primer::TabContainerComponent,
103
+ Primer::TabNavComponent,
104
+ Primer::TimeAgoComponent,
105
+ Primer::UnderlineNavComponent
106
+ ]
107
+
108
+ all_components = Primer::Component.descendants - [Primer::BaseComponent]
109
+ components_needing_docs = all_components - components
110
+
111
+ components_without_examples = []
112
+ args_for_components = []
113
+ classes_found_in_examples = []
114
+
115
+ components.each do |component|
116
+ documentation = registry.get(component.name)
117
+
118
+ # Primer::AvatarComponent => Avatar
119
+ short_name = component.name.gsub(/Primer|::|Component/, "")
120
+
121
+ path = Pathname.new("docs/content/components/#{short_name.downcase}.md")
122
+ path.dirname.mkdir unless path.dirname.exist?
123
+ File.open(path, "w") do |f|
124
+ f.puts("---")
125
+ f.puts("title: #{short_name}")
126
+ f.puts("status: #{component.status.to_s.capitalize}")
127
+ f.puts("source: https://github.com/primer/view_components/tree/main/app/components/primer/#{component.to_s.demodulize.underscore}.rb")
128
+ f.puts("storybook: https://primer.style/view-components/stories/?path=/story/primer-#{short_name.underscore.dasherize}-component")
129
+ f.puts("---")
130
+ f.puts
131
+ f.puts("import Example from '../../src/@primer/gatsby-theme-doctocat/components/example'")
132
+
133
+ if js_components.include?(component)
134
+ f.puts("import RequiresJSFlash from '../../src/@primer/gatsby-theme-doctocat/components/requires-js-flash'")
135
+ f.puts
136
+ f.puts("<RequiresJSFlash />")
137
+ end
138
+
139
+ f.puts
140
+ f.puts("<!-- Warning: AUTO-GENERATED file, do not edit. Add code comments to your Ruby instead <3 -->")
141
+ f.puts
142
+ f.puts(view_context.render(inline: documentation.base_docstring))
143
+
144
+ if documentation.tags(:accessibility).any?
145
+ f.puts
146
+ f.puts("## Accessibility")
147
+ documentation.tags(:accessibility).each do |tag|
148
+ f.puts
149
+ f.puts view_context.render(inline: tag.text)
150
+ end
151
+ end
152
+
153
+ if documentation.tags(:deprecated).any?
154
+ f.puts
155
+ f.puts("## Deprecation")
156
+ documentation.tags(:deprecated).each do |tag|
157
+ f.puts
158
+ f.puts view_context.render(inline: tag.text)
159
+ end
160
+ end
161
+
162
+ initialize_method = documentation.meths.find(&:constructor?)
163
+
164
+ if initialize_method.tags(:example).any?
165
+ f.puts
166
+ f.puts("## Examples")
167
+ else
168
+ components_without_examples << component
169
+ end
170
+
171
+ initialize_method.tags(:example).each do |tag|
172
+ (name, description) = tag.name.split("|")
173
+ f.puts
174
+ f.puts("### #{name}")
175
+ if description
176
+ f.puts
177
+ f.puts(description)
178
+ end
179
+ f.puts
180
+ html = view_context.render(inline: tag.text)
181
+ html.scan(/class="([^"]*)"/) do |classnames|
182
+ classes_found_in_examples.concat(classnames[0].split(" ").reject { |c| c.starts_with?("octicon", "js", "my-") }.map { ".#{_1}"})
183
+ end
184
+ f.puts("<Example src=\"#{html.tr('"', "\'").delete("\n")}\" />")
185
+ f.puts
186
+ f.puts("```erb")
187
+ f.puts(tag.text.to_s)
188
+ f.puts("```")
189
+ end
190
+
191
+ params = initialize_method.tags(:param)
192
+ if params.any?
193
+ f.puts
194
+ f.puts("## Arguments")
195
+ f.puts
196
+ f.puts("| Name | Type | Default | Description |")
197
+ f.puts("| :- | :- | :- | :- |")
198
+
199
+ args = []
200
+ params.each do |tag|
201
+ params = tag.object.parameters.find { |param| [tag.name.to_s, tag.name.to_s + ":"].include?(param[0]) }
202
+
203
+ default =
204
+ if params && params[1]
205
+ constant_name = "#{component.name}::#{params[1]}"
206
+ constant_value = constant_name.safe_constantize
207
+ if constant_value.nil?
208
+ pretty_value(params[1])
209
+ else
210
+ pretty_value(constant_value)
211
+ end
212
+ else
213
+ "N/A"
214
+ end
215
+
216
+ args << {
217
+ "name" => tag.name,
218
+ "type" => tag.types.join(", "),
219
+ "default" => default,
220
+ "description" => view_context.render(inline: tag.text)
221
+ }
222
+
223
+ f.puts("| `#{tag.name}` | `#{tag.types.join(', ')}` | #{default} | #{view_context.render(inline: tag.text)} |")
224
+ end
225
+
226
+ component_args = {
227
+ "component" => short_name,
228
+ "source" => "https://github.com/primer/view_components/tree/main/app/components/primer/#{component.to_s.demodulize.underscore}.rb",
229
+ "parameters" => args
230
+ }
231
+
232
+ args_for_components << component_args
233
+ end
234
+
235
+ # Slots V2 docs
236
+ slot_v2_methods = documentation.meths.select { |x| x[:renders_one] || x[:renders_many] }
237
+
238
+ if slot_v2_methods.any?
239
+ f.puts
240
+ f.puts("## Slots")
241
+
242
+ slot_v2_methods.each do |slot_documentation|
243
+ f.puts
244
+ f.puts("### `#{slot_documentation.name.to_s.capitalize}`")
245
+
246
+ if slot_documentation.base_docstring.present?
247
+ f.puts
248
+ f.puts(view_context.render(inline: slot_documentation.base_docstring))
249
+ end
250
+
251
+ param_tags = slot_documentation.tags(:param)
252
+ if param_tags.any?
253
+ f.puts
254
+ f.puts("| Name | Type | Default | Description |")
255
+ f.puts("| :- | :- | :- | :- |")
256
+ end
257
+
258
+ param_tags.each do |tag|
259
+ params = tag.object.parameters.find { |param| [tag.name.to_s, tag.name.to_s + ":"].include?(param[0]) }
260
+
261
+ default =
262
+ if params && params[1]
263
+ "`#{params[1]}`"
264
+ else
265
+ "N/A"
266
+ end
267
+
268
+ f.puts("| `#{tag.name}` | `#{tag.types.join(', ')}` | #{default} | #{view_context.render(inline: tag.text)} |")
269
+ end
270
+ end
271
+ end
272
+ end
273
+ end
274
+
275
+ File.open("static/classes.yml", "w") do |f|
276
+ f.puts YAML.dump(classes_found_in_examples.sort.uniq)
277
+ end
278
+
279
+ File.open("static/arguments.yml", "w") do |f|
280
+ f.puts YAML.dump(args_for_components)
281
+ end
282
+
283
+ # Build system arguments docs from BaseComponent
284
+ documentation = registry.get(Primer::BaseComponent.name)
285
+ File.open("docs/content/system-arguments.md", "w") do |f|
286
+ f.puts("---")
287
+ f.puts("title: System arguments")
288
+ f.puts("---")
289
+ f.puts
290
+ f.puts("<!-- Warning: AUTO-GENERATED file, do not edit. Add code comments to your Ruby instead <3 -->")
291
+ f.puts
292
+ f.puts(documentation.base_docstring)
293
+ f.puts
294
+
295
+ initialize_method = documentation.meths.find(&:constructor?)
296
+
297
+ f.puts(view_context.render(inline: initialize_method.base_docstring))
298
+ end
299
+
300
+ puts "Markdown compiled."
301
+
302
+ if components_without_examples.any?
303
+ puts
304
+ puts "The following components have no examples defined: #{components_without_examples.map(&:name).join(', ')}. Consider adding an example?"
305
+ end
306
+
307
+ if components_needing_docs.any?
308
+ puts
309
+ puts "The following components needs docs. Care to contribute them? #{components_needing_docs.map(&:name).join(', ')}"
310
+ end
311
+ end
312
+ end