primer_view_components 0.0.39 → 0.0.40

Sign up to get free protection for your applications and to get access to all the features.
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