primer_view_components 0.0.39 → 0.0.44

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