primer_view_components 0.0.39 → 0.0.44

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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +269 -3
  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/button_marketing.rb +70 -0
  6. data/app/components/primer/auto_complete.rb +99 -41
  7. data/app/components/primer/auto_complete/auto_complete.html.erb +1 -0
  8. data/app/components/primer/avatar_stack_component.rb +7 -4
  9. data/app/components/primer/base_component.rb +17 -7
  10. data/app/components/primer/beta/text.rb +27 -0
  11. data/app/components/primer/blankslate_component.html.erb +1 -0
  12. data/app/components/primer/blankslate_component.rb +68 -49
  13. data/app/components/primer/button_component.rb +3 -2
  14. data/app/components/primer/button_group.rb +2 -2
  15. data/app/components/primer/clipboard_copy_component.js +13 -2
  16. data/app/components/primer/clipboard_copy_component.ts +15 -2
  17. data/app/components/primer/component.rb +6 -1
  18. data/app/components/primer/counter_component.rb +6 -1
  19. data/app/components/primer/details_component.rb +12 -1
  20. data/app/components/primer/dropdown.d.ts +1 -0
  21. data/app/components/primer/{dropdown_component.html.erb → dropdown.html.erb} +2 -1
  22. data/app/components/primer/dropdown.js +1 -0
  23. data/app/components/primer/dropdown.rb +149 -0
  24. data/app/components/primer/dropdown.ts +1 -0
  25. data/app/components/primer/dropdown/menu.d.ts +1 -0
  26. data/app/components/primer/dropdown/menu.html.erb +25 -0
  27. data/app/components/primer/dropdown/menu.js +1 -0
  28. data/app/components/primer/dropdown/menu.rb +99 -0
  29. data/app/components/primer/dropdown/menu.ts +1 -0
  30. data/app/components/primer/flash_component.rb +2 -2
  31. data/app/components/primer/flex_component.rb +27 -0
  32. data/app/components/primer/flex_item_component.rb +1 -1
  33. data/app/components/primer/heading_component.rb +11 -18
  34. data/app/components/primer/hidden_text_expander.rb +3 -3
  35. data/app/components/primer/icon_button.rb +20 -3
  36. data/app/components/primer/image.rb +46 -0
  37. data/app/components/primer/image_crop.d.ts +1 -0
  38. data/app/components/primer/image_crop.html.erb +12 -0
  39. data/app/components/primer/image_crop.js +1 -0
  40. data/app/components/primer/image_crop.rb +36 -0
  41. data/app/components/primer/image_crop.ts +1 -0
  42. data/app/components/primer/label_component.rb +6 -2
  43. data/app/components/primer/local_time.d.ts +1 -0
  44. data/app/components/primer/local_time.js +1 -0
  45. data/app/components/primer/local_time.rb +59 -0
  46. data/app/components/primer/local_time.ts +1 -0
  47. data/app/components/primer/{markdown_component.rb → markdown.rb} +11 -6
  48. data/app/components/primer/navigation/tab_component.rb +10 -3
  49. data/app/components/primer/octicon_component.html.erb +7 -0
  50. data/app/components/primer/octicon_component.rb +25 -15
  51. data/app/components/primer/octicon_symbols_component.html.erb +3 -0
  52. data/app/components/primer/octicon_symbols_component.rb +61 -0
  53. data/app/components/primer/primer.d.ts +3 -0
  54. data/app/components/primer/primer.js +3 -0
  55. data/app/components/primer/primer.ts +3 -0
  56. data/app/components/primer/spinner_component.rb +4 -2
  57. data/app/components/primer/subhead_component.rb +34 -4
  58. data/app/components/primer/tab_nav_component.html.erb +5 -1
  59. data/app/components/primer/tab_nav_component.rb +62 -9
  60. data/app/components/primer/{tooltip_component.rb → tooltip.rb} +10 -8
  61. data/app/components/primer/truncate.rb +6 -2
  62. data/app/components/primer/underline_nav_component.html.erb +1 -1
  63. data/app/components/primer/underline_nav_component.rb +17 -1
  64. data/app/lib/primer/classify.rb +21 -8
  65. data/app/lib/primer/classify/cache.rb +16 -1
  66. data/app/lib/primer/classify/grid.rb +45 -0
  67. data/app/lib/primer/octicon/cache.rb +4 -0
  68. data/app/lib/primer/tabbed_component_helper.rb +2 -2
  69. data/app/lib/primer/view_helper.rb +2 -1
  70. data/lib/primer/view_components.rb +1 -1
  71. data/lib/primer/view_components/engine.rb +2 -0
  72. data/lib/primer/view_components/linters.rb +3 -0
  73. data/lib/primer/view_components/linters/argument_mappers/button.rb +82 -0
  74. data/lib/primer/view_components/linters/argument_mappers/conversion_error.rb +10 -0
  75. data/lib/primer/view_components/linters/argument_mappers/system_arguments.rb +46 -0
  76. data/lib/primer/view_components/linters/button_component_migration_counter.rb +35 -0
  77. data/lib/primer/view_components/linters/flash_component_migration_counter.rb +16 -0
  78. data/lib/primer/view_components/linters/helpers.rb +93 -0
  79. data/lib/primer/view_components/version.rb +1 -1
  80. data/lib/tasks/coverage.rake +14 -0
  81. data/lib/tasks/docs.rake +387 -0
  82. data/lib/tasks/statuses.rake +12 -0
  83. data/lib/yard/docs_helper.rb +67 -0
  84. data/static/statuses.json +56 -1
  85. metadata +72 -13
  86. data/app/components/primer/button_marketing_component.rb +0 -68
  87. data/app/components/primer/dropdown/menu_component.html.erb +0 -12
  88. data/app/components/primer/dropdown/menu_component.rb +0 -46
  89. data/app/components/primer/dropdown_component.rb +0 -73
  90. data/app/components/primer/text_component.rb +0 -22
@@ -5,7 +5,13 @@ module Primer
5
5
  #
6
6
  # @accessibility
7
7
  # `IconButton` requires an `aria-label`, which will provide assistive technologies with an accessible label.
8
+ # The `aria-label` should describe the action to be invoked rather than the icon itself. For instance,
9
+ # if your `IconButton` renders a magnifying glass icon and invokves a search action, the `aria-label` should be
10
+ # `"Search"` instead of `"Magnifying glass"`.
11
+ # [Learn more about best functional image practices (WAI Images)](https://www.w3.org/WAI/tutorials/images/functional)
8
12
  class IconButton < Primer::Component
13
+ status :beta
14
+
9
15
  DEFAULT_SCHEME = :default
10
16
  SCHEME_MAPPINGS = {
11
17
  DEFAULT_SCHEME => "",
@@ -21,22 +27,33 @@ module Primer
21
27
  # <%= render(Primer::IconButton.new(icon: :search, "aria-label": "Search")) %>
22
28
  # <%= render(Primer::IconButton.new(icon: :trash, "aria-label": "Delete", scheme: :danger)) %>
23
29
  #
30
+ # @example In a BorderBox
31
+ #
32
+ # <%= render(Primer::BorderBoxComponent.new) do |component| %>
33
+ # <% component.body do %>
34
+ # <%= render(Primer::Beta::Text.new(pr: 2)) { "Body" } %>
35
+ # <%= render(Primer::IconButton.new(icon: :pencil, box: true, "aria-label": "Edit")) %>
36
+ # <% end %>
37
+ # <% end %>
38
+ #
24
39
  # @param scheme [Symbol] <%= one_of(Primer::IconButton::SCHEME_OPTIONS) %>
25
40
  # @param icon [String] Name of <%= link_to_octicons %> to use.
26
41
  # @param tag [Symbol] <%= one_of(Primer::BaseButton::TAG_OPTIONS) %>
27
42
  # @param type [Symbol] <%= one_of(Primer::BaseButton::TYPE_OPTIONS) %>
43
+ # @param box [Boolean] Whether the button is in a <%= link_to_component(Primer::BorderBoxComponent) %>. If `true`, the button will have the `Box-btn-octicon` class.
28
44
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
29
- def initialize(scheme: DEFAULT_SCHEME, icon:, **system_arguments)
45
+ def initialize(scheme: DEFAULT_SCHEME, icon:, box: false, **system_arguments)
30
46
  @icon = icon
31
47
 
32
48
  @system_arguments = system_arguments
33
49
  @system_arguments[:classes] = class_names(
34
50
  "btn-octicon",
35
51
  SCHEME_MAPPINGS[fetch_or_fallback(SCHEME_OPTIONS, scheme, DEFAULT_SCHEME)],
36
- system_arguments[:classes]
52
+ system_arguments[:classes],
53
+ "Box-btn-octicon" => box
37
54
  )
38
55
 
39
- raise ArgumentError, "`aria-label` is required." if @system_arguments[:"aria-label"].nil? && !Rails.env.production?
56
+ validate_aria_label
40
57
  end
41
58
 
42
59
  def call
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ # Use `Image` to render images.
5
+ #
6
+ # @accessibility
7
+ # Always provide a meaningful `alt`.
8
+ class Image < Primer::Component
9
+ # @example Default
10
+ #
11
+ # <%= render(Primer::Image.new(src: "https://github.com/github.png", alt: "GitHub")) %>
12
+ #
13
+ # @example Helper
14
+ #
15
+ # <%= primer_image(src: "https://github.com/github.png", alt: "GitHub") %>
16
+ #
17
+ # @example Lazy loading
18
+ #
19
+ # <%= render(Primer::Image.new(src: "https://github.com/github.png", alt: "GitHub", lazy: true)) %>
20
+ #
21
+ # @example Custom size
22
+ #
23
+ # <%= render(Primer::Image.new(src: "https://github.com/github.png", alt: "GitHub", height: 100, width: 100)) %>
24
+ #
25
+ # @param src [String] The source url of the image.
26
+ # @param alt [String] Specifies an alternate text for the image.
27
+ # @param lazy [Boolean] Whether or not to lazily load the image.
28
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
29
+ def initialize(src:, alt:, lazy: false, **system_arguments)
30
+ @system_arguments = system_arguments
31
+
32
+ @system_arguments[:tag] = :img
33
+ @system_arguments[:src] = src
34
+ @system_arguments[:alt] = alt
35
+
36
+ return unless lazy
37
+
38
+ @system_arguments[:loading] = :lazy
39
+ @system_arguments[:decoding] = :async
40
+ end
41
+
42
+ def call
43
+ render(Primer::BaseComponent.new(**@system_arguments))
44
+ end
45
+ end
46
+ end
@@ -0,0 +1 @@
1
+ import '@github/image-crop-element';
@@ -0,0 +1,12 @@
1
+ <%= render Primer::BaseComponent.new(**@system_arguments) do %>
2
+ <% if loading.present? %>
3
+ <%= loading %>
4
+ <% else %>
5
+ <%= render(Primer::SpinnerComponent.new(size: :large, flex: 1, "data-loading-slot": true)) %>
6
+ <% end %>
7
+
8
+ <input type="hidden" data-image-crop-input="x" name="cropped_x">
9
+ <input type="hidden" data-image-crop-input="y" name="cropped_y">
10
+ <input type="hidden" data-image-crop-input="width" name="cropped_width">
11
+ <input type="hidden" data-image-crop-input="height" name="cropped_height">
12
+ <% end %>
@@ -0,0 +1 @@
1
+ import '@github/image-crop-element';
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ # A client-side mechanism to crop images.
5
+ class ImageCrop < Primer::Component
6
+ # A loading indicator that is shown while the image is loading.
7
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
8
+ renders_one :loading, lambda { |**system_arguments|
9
+ system_arguments[:tag] ||= :div
10
+ system_arguments[:"data-loading-slot"] = true
11
+
12
+ Primer::BaseComponent.new(**system_arguments)
13
+ }
14
+
15
+ # @example Simple cropper
16
+ # <%= render(Primer::ImageCrop.new(src: "https://github.com/koddsson.png")) %>
17
+ #
18
+ # @example Square cropper
19
+ # <%= render(Primer::ImageCrop.new(src: "https://github.com/koddsson.png", rounded: false)) %>
20
+ #
21
+ # @example Cropper with a custom loader
22
+ # <%= render(Primer::ImageCrop.new(src: "https://github.com/koddsson.png", rounded: false)) do |cropper| %>
23
+ # <% cropper.loading(style: "width: 120px").with_content("Loading...") %>
24
+ # <% end %>
25
+ #
26
+ # @param src [String] The path of the image to crop.
27
+ # @param rounded [Boolean] If the crop mask should be a circle. Defaults to true.
28
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
29
+ def initialize(src:, rounded: true, **system_arguments)
30
+ @system_arguments = system_arguments
31
+ @system_arguments[:tag] = "image-crop"
32
+ @system_arguments[:src] = src
33
+ @system_arguments[:rounded] = true if rounded
34
+ end
35
+ end
36
+ end
@@ -0,0 +1 @@
1
+ import '@github/image-crop-element'
@@ -5,6 +5,9 @@ module Primer
5
5
  class LabelComponent < Primer::Component
6
6
  status :beta
7
7
 
8
+ DEFAULT_TAG = :span
9
+ TAG_OPTIONS = [DEFAULT_TAG, :summary, :a, :div].freeze
10
+
8
11
  SCHEME_MAPPINGS = {
9
12
  primary: "Label--primary",
10
13
  secondary: "Label--secondary",
@@ -38,13 +41,14 @@ module Primer
38
41
  # <%= render(Primer::LabelComponent.new(title: "Label: Label")) { "Default" } %>
39
42
  # <%= render(Primer::LabelComponent.new(title: "Label: Label", variant: :large)) { "Large" } %>
40
43
  #
44
+ # @param tag [Symbol] <%= one_of(Primer::LabelComponent::TAG_OPTIONS) %>
41
45
  # @param title [String] `title` attribute for the component element.
42
46
  # @param scheme [Symbol] <%= one_of(Primer::LabelComponent::SCHEME_MAPPINGS.keys) %>
43
47
  # @param variant [Symbol] <%= one_of(Primer::LabelComponent::VARIANT_OPTIONS) %>
44
48
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
45
- def initialize(title:, scheme: nil, variant: nil, **system_arguments)
49
+ def initialize(tag: DEFAULT_TAG, title:, scheme: nil, variant: nil, **system_arguments)
46
50
  @system_arguments = system_arguments
47
- @system_arguments[:tag] ||= :span
51
+ @system_arguments[:tag] = fetch_or_fallback(TAG_OPTIONS, tag, DEFAULT_TAG)
48
52
  @system_arguments[:title] = title
49
53
  @system_arguments[:classes] = class_names(
50
54
  "Label",
@@ -0,0 +1 @@
1
+ import '@github/time-elements';
@@ -0,0 +1 @@
1
+ import '@github/time-elements';
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ # Use `LocalTime` to format a date and time in the user's preferred locale format. This component requires JavaScript.
5
+ class LocalTime < Primer::Component
6
+ DEFAULT_DIGIT_TYPE = :numeric
7
+ DIGIT_TYPE_OPTIONS = [DEFAULT_DIGIT_TYPE, :"2-digit"].freeze
8
+
9
+ DEFAULT_TEXT_TYPE = :short
10
+ TEXT_TYPE_OPTIONS = [DEFAULT_TEXT_TYPE, :long].freeze
11
+
12
+ # @example Default
13
+ # <%= render(Primer::LocalTime.new(datetime: DateTime.parse("2014-06-01T13:05:07Z"))) %>
14
+ #
15
+ # @example All the options
16
+ # <%= render(Primer::LocalTime.new(datetime: DateTime.parse("2014-06-01T13:05:07Z"), weekday: :long, year: :"2-digit", month: :long, day: :"2-digit", hour: :"2-digit", minute: :"2-digit", second: :"2-digit", time_zone_name: :long)) %>
17
+ #
18
+ # @example With initial content
19
+ # <%= render(Primer::LocalTime.new(datetime: DateTime.parse("2014-06-01T13:05:07Z"))) do %>
20
+ # <!-- This content will be replaced once the component connects -->
21
+ # 2014/06/01 13:05
22
+ # <% end %>
23
+ #
24
+ # @param datetime [DateTime] The date to parse
25
+ # @param initial_text [String] Text to render before component is initialized
26
+ # @param weekday [Symbol] <%= one_of(Primer::LocalTime::TEXT_TYPE_OPTIONS) %>
27
+ # @param year [Symbol] <%= one_of(Primer::LocalTime::DIGIT_TYPE_OPTIONS) %>
28
+ # @param month [Symbol] <%= one_of(Primer::LocalTime::TEXT_TYPE_OPTIONS) %>
29
+ # @param day [Symbol] <%= one_of(Primer::LocalTime::DIGIT_TYPE_OPTIONS) %>
30
+ # @param hour [Symbol] <%= one_of(Primer::LocalTime::DIGIT_TYPE_OPTIONS) %>
31
+ # @param minute [Symbol] <%= one_of(Primer::LocalTime::DIGIT_TYPE_OPTIONS) %>
32
+ # @param second [Symbol] <%= one_of(Primer::LocalTime::DIGIT_TYPE_OPTIONS) %>
33
+ # @param time_zone_name [Symbol] <%= one_of(Primer::LocalTime::TEXT_TYPE_OPTIONS) %>
34
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
35
+ def initialize(datetime:, initial_text: nil, weekday: DEFAULT_TEXT_TYPE, year: DEFAULT_DIGIT_TYPE, month: DEFAULT_TEXT_TYPE, day: DEFAULT_DIGIT_TYPE, hour: DEFAULT_DIGIT_TYPE, minute: DEFAULT_DIGIT_TYPE, second: DEFAULT_DIGIT_TYPE, time_zone_name: DEFAULT_TEXT_TYPE, **system_arguments)
36
+ @system_arguments = system_arguments
37
+
38
+ @datetime = datetime
39
+
40
+ @system_arguments[:tag] = "local-time"
41
+ @system_arguments[:datetime] = datetime
42
+
43
+ @initial_text = initial_text
44
+
45
+ @system_arguments[:weekday] = fetch_or_fallback(TEXT_TYPE_OPTIONS, weekday, DEFAULT_TEXT_TYPE)
46
+ @system_arguments[:year] = fetch_or_fallback(DIGIT_TYPE_OPTIONS, year, DEFAULT_DIGIT_TYPE)
47
+ @system_arguments[:month] = fetch_or_fallback(TEXT_TYPE_OPTIONS, month, DEFAULT_TEXT_TYPE)
48
+ @system_arguments[:day] = fetch_or_fallback(DIGIT_TYPE_OPTIONS, day, DEFAULT_DIGIT_TYPE)
49
+ @system_arguments[:hour] = fetch_or_fallback(DIGIT_TYPE_OPTIONS, hour, DEFAULT_DIGIT_TYPE)
50
+ @system_arguments[:minute] = fetch_or_fallback(DIGIT_TYPE_OPTIONS, minute, DEFAULT_DIGIT_TYPE)
51
+ @system_arguments[:second] = fetch_or_fallback(DIGIT_TYPE_OPTIONS, second, DEFAULT_DIGIT_TYPE)
52
+ @system_arguments[:"time-zone-name"] = fetch_or_fallback(TEXT_TYPE_OPTIONS, time_zone_name, DEFAULT_TEXT_TYPE)
53
+ end
54
+
55
+ def call
56
+ render(Primer::BaseComponent.new(**@system_arguments).with_content(@initial_text || @datetime.strftime("%B %-d, %Y %H:%M %Z")))
57
+ end
58
+ end
59
+ end
@@ -0,0 +1 @@
1
+ import '@github/time-elements'
@@ -2,9 +2,13 @@
2
2
 
3
3
  module Primer
4
4
  # Use `Markdown` to wrap markdown content
5
- class MarkdownComponent < Primer::Component
5
+ class Markdown < Primer::Component
6
+ status :beta
7
+
8
+ DEFAULT_TAG = :div
9
+ TAG_OPTIONS = [DEFAULT_TAG, :article, :td].freeze
6
10
  # @example Default
7
- # <%= render(Primer::MarkdownComponent.new) do %>
11
+ # <%= render(Primer::Markdown.new) do %>
8
12
  # <p>Text can be <b>bold</b>, <i>italic</i>, or <s>strikethrough</s>. <a href="https://github.com">Links </a> should be blue with no underlines (unless hovered over).</p>
9
13
  #
10
14
  # <p>There should be whitespace between paragraphs. There should be whitespace between paragraphs. There should be whitespace between paragraphs. There should be whitespace between paragraphs.</p>
@@ -235,11 +239,11 @@ module Primer
235
239
  #
236
240
  # <p>Code can also use syntax highlighting.</p>
237
241
  #
238
- # <pre><code class="prism-code language-javascript">var foo = "bar";</code></pre>
242
+ # <pre><code>var foo = "bar";</code></pre>
239
243
  #
240
244
  # <pre><code>Long, single-line code blocks should not wrap. They should horizontally scroll if they are too long. This line should be long enough to demonstrate this.</code></pre>
241
245
  #
242
- # <pre><code class="prism-code language-javascript">var foo = "The same thing is true for code with syntax highlighting. A single line of code should horizontally scroll if it is really long.";</code></pre>
246
+ # <pre><code>var foo = "The same thing is true for code with syntax highlighting. A single line of code should horizontally scroll if it is really long.";</code></pre>
243
247
  #
244
248
  # <p>Inline code inside table cells should still be distinguishable.</p>
245
249
  #
@@ -275,10 +279,11 @@ module Primer
275
279
  # <pre><code>This is the final element on the page and there should be no margin below this.</code></pre>
276
280
  # <% end %>
277
281
  #
282
+ # @param tag [Symbol] <%= one_of(Primer::Markdown::TAG_OPTIONS) %>
278
283
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
279
- def initialize(**system_arguments)
284
+ def initialize(tag: DEFAULT_TAG, **system_arguments)
280
285
  @system_arguments = system_arguments
281
- @system_arguments[:tag] ||= :div
286
+ @system_arguments[:tag] = fetch_or_fallback(TAG_OPTIONS, tag, DEFAULT_TAG)
282
287
 
283
288
  @system_arguments[:classes] = class_names(
284
289
  "markdown-body",
@@ -4,7 +4,13 @@ module Primer
4
4
  module Navigation
5
5
  # This component is part of navigation components such as `Primer::TabNavComponent`
6
6
  # and `Primer::UnderlineNavComponent` and should not be used by itself.
7
+ #
8
+ # @accessibility
9
+ # `TabComponent` renders the selected anchor tab with `aria-current="page"` by default.
10
+ # When the selected tab does not correspond to the current page, such as in a nested inner tab, make sure to use aria-current="true"
7
11
  class TabComponent < Primer::Component
12
+ DEFAULT_ARIA_CURRENT_FOR_ANCHOR = :page
13
+ ARIA_CURRENT_OPTIONS_FOR_ANCHOR = [true, DEFAULT_ARIA_CURRENT_FOR_ANCHOR].freeze
8
14
  # Panel controlled by the Tab. This will not render anything in the tab itself.
9
15
  # It will provide a accessor for the Tab's parent to call and render the panel
10
16
  # content in the appropriate place.
@@ -32,8 +38,8 @@ module Primer
32
38
 
33
39
  # The Tab's text.
34
40
  #
35
- # @param kwargs [Hash] The same arguments as <%= link_to_component(Primer::TextComponent) %>.
36
- renders_one :text, Primer::TextComponent
41
+ # @param kwargs [Hash] The same arguments as <%= link_to_component(Primer::Beta::Text) %>.
42
+ renders_one :text, Primer::Beta::Text
37
43
 
38
44
  # Counter to be rendered in the Tab right.
39
45
  #
@@ -105,7 +111,8 @@ module Primer
105
111
  return unless @selected
106
112
 
107
113
  if @system_arguments[:tag] == :a
108
- @system_arguments[:"aria-current"] = :page
114
+ aria_current = @system_arguments[:"aria-current"] || @system_arguments.dig(:aria, :current) || DEFAULT_ARIA_CURRENT_FOR_ANCHOR
115
+ @system_arguments[:"aria-current"] = fetch_or_fallback(ARIA_CURRENT_OPTIONS_FOR_ANCHOR, aria_current, DEFAULT_ARIA_CURRENT_FOR_ANCHOR)
109
116
  else
110
117
  @system_arguments[:"aria-selected"] = true
111
118
  end
@@ -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,37 +4,49 @@ 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
+
11
14
  SIZE_MAPPINGS = {
12
15
  SIZE_DEFAULT => 16,
13
- :medium => 32,
14
- :large => 64
16
+ SIZE_MEDIUM => 24
15
17
  }.freeze
16
18
  SIZE_OPTIONS = SIZE_MAPPINGS.keys
17
19
 
18
20
  # @example Default
19
- # <%= render(Primer::OcticonComponent.new("check")) %>
20
- # <%= render(Primer::OcticonComponent.new(icon: "check")) %>
21
+ # <%= render(Primer::OcticonComponent.new(:check)) %>
22
+ # <%= render(Primer::OcticonComponent.new(icon: :check)) %>
21
23
  #
22
24
  # @example Medium
23
- # <%= render(Primer::OcticonComponent.new("people", size: :medium)) %>
25
+ # <%= render(Primer::OcticonComponent.new(:people, size: :medium)) %>
24
26
  #
25
- # @example Large
26
- # <%= render(Primer::OcticonComponent.new("x", size: :large)) %>
27
+ # @example Helper
28
+ # <%= primer_octicon(:check) %>
27
29
  #
28
- # @param icon [String] Name of <%= link_to_octicons %> to use.
29
- # @param size [Symbol] <%= one_of(Primer::OcticonComponent::SIZE_MAPPINGS) %>
30
+ # @param icon_name [Symbol, String] Name of <%= link_to_octicons %> to use.
31
+ # @param icon [Symbol, String] Name of <%= link_to_octicons %> to use.
32
+ # @param size [Symbol] <%= one_of(Primer::OcticonComponent::SIZE_MAPPINGS, sort: false) %>
33
+ # @param use_symbol [Boolean] EXPERIMENTAL (May change or be removed) - Set to true when using with <%= link_to_component(Primer::OcticonSymbolsComponent) %>.
30
34
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
31
- def initialize(icon_name = nil, icon: nil, size: SIZE_DEFAULT, **system_arguments)
35
+ def initialize(icon_name = nil, icon: nil, size: SIZE_DEFAULT, use_symbol: false, **system_arguments)
32
36
  icon_key = icon_name || icon
33
- cache_key = [icon_key, size, system_arguments.slice(:height, :width)].join("_")
37
+
38
+ # Don't allow sizes under 16px
39
+ if system_arguments[:height].present? && system_arguments[:height].to_i < 16 || system_arguments[:width].present? && system_arguments[:width].to_i < 16
40
+ system_arguments.delete(:height)
41
+ system_arguments.delete(:width)
42
+ end
43
+
44
+ cache_key = Primer::Octicon::Cache.get_key(symbol: icon_key, size: size, **system_arguments.slice(:height, :width))
34
45
 
35
46
  @system_arguments = system_arguments
36
47
  @system_arguments[:tag] = :svg
37
48
  @system_arguments[:aria] ||= {}
49
+ @use_symbol = use_symbol
38
50
 
39
51
  if @system_arguments[:aria][:label] || @system_arguments[:"aria-label"]
40
52
  @system_arguments[:role] = "img"
@@ -62,10 +74,8 @@ module Primer
62
74
  @system_arguments.merge!(@icon.options.except(:class, :'aria-hidden'))
63
75
  end
64
76
 
65
- def call
66
- render(Primer::BaseComponent.new(**@system_arguments)) { @icon.path.html_safe } # rubocop:disable Rails/OutputSafety
77
+ def self._after_compile
78
+ Primer::Octicon::Cache.preload!
67
79
  end
68
-
69
- Primer::Octicon::Cache.preload!
70
80
  end
71
81
  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