primer_view_components 0.0.36 → 0.0.41

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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +220 -24
  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/auto_complete.rb +3 -1
  6. data/app/components/primer/auto_complete/item.rb +1 -1
  7. data/app/components/primer/avatar_component.rb +22 -3
  8. data/app/components/primer/avatar_stack_component.rb +8 -5
  9. data/app/components/primer/base_button.rb +47 -0
  10. data/app/components/primer/base_component.rb +14 -10
  11. data/app/components/primer/blankslate_component.rb +10 -7
  12. data/app/components/primer/border_box_component.rb +1 -1
  13. data/app/components/primer/box_component.rb +1 -1
  14. data/app/components/primer/breadcrumb_component.rb +1 -1
  15. data/app/components/primer/button_component.html.erb +9 -0
  16. data/app/components/primer/button_component.rb +39 -21
  17. data/app/components/primer/{button_group_component.html.erb → button_group.html.erb} +0 -0
  18. data/app/components/primer/button_group.rb +61 -0
  19. data/app/components/primer/button_marketing_component.rb +4 -9
  20. data/app/components/primer/clipboard_copy.html.erb +8 -0
  21. data/app/components/primer/clipboard_copy.rb +26 -0
  22. data/app/components/primer/clipboard_copy_component.d.ts +1 -0
  23. data/app/components/primer/clipboard_copy_component.js +34 -0
  24. data/app/components/primer/clipboard_copy_component.ts +39 -0
  25. data/app/components/primer/close_button.rb +11 -2
  26. data/app/components/primer/component.rb +21 -2
  27. data/app/components/primer/counter_component.rb +6 -1
  28. data/app/components/primer/details_component.rb +1 -1
  29. data/app/components/primer/dropdown/menu_component.rb +1 -1
  30. data/app/components/primer/dropdown_component.rb +1 -1
  31. data/app/components/primer/flash_component.rb +3 -3
  32. data/app/components/primer/flex_component.rb +28 -1
  33. data/app/components/primer/flex_item_component.rb +20 -1
  34. data/app/components/primer/heading_component.rb +25 -4
  35. data/app/components/primer/hidden_text_expander.rb +2 -4
  36. data/app/components/primer/icon_button.rb +65 -0
  37. data/app/components/primer/image.rb +46 -0
  38. data/app/components/primer/image_crop.d.ts +1 -0
  39. data/app/components/primer/image_crop.html.erb +12 -0
  40. data/app/components/primer/image_crop.js +1 -0
  41. data/app/components/primer/image_crop.rb +36 -0
  42. data/app/components/primer/image_crop.ts +1 -0
  43. data/app/components/primer/label_component.rb +7 -3
  44. data/app/components/primer/layout_component.rb +1 -1
  45. data/app/components/primer/link_component.rb +1 -1
  46. data/app/components/primer/local_time.d.ts +1 -0
  47. data/app/components/primer/local_time.js +1 -0
  48. data/app/components/primer/local_time.rb +59 -0
  49. data/app/components/primer/local_time.ts +1 -0
  50. data/app/components/primer/{markdown_component.rb → markdown.rb} +6 -5
  51. data/app/components/primer/menu_component.rb +1 -1
  52. data/app/components/primer/navigation/tab_component.rb +8 -1
  53. data/app/components/primer/octicon_component.html.erb +7 -0
  54. data/app/components/primer/octicon_component.rb +53 -19
  55. data/app/components/primer/octicon_symbols_component.html.erb +3 -0
  56. data/app/components/primer/octicon_symbols_component.rb +61 -0
  57. data/app/components/primer/popover_component.rb +1 -1
  58. data/app/components/primer/primer.d.ts +3 -0
  59. data/app/components/primer/primer.js +3 -0
  60. data/app/components/primer/primer.ts +3 -0
  61. data/app/components/primer/progress_bar_component.rb +1 -1
  62. data/app/components/primer/spinner_component.rb +3 -3
  63. data/app/components/primer/state_component.rb +2 -2
  64. data/app/components/primer/subhead_component.rb +34 -4
  65. data/app/components/primer/tab_container_component.rb +1 -1
  66. data/app/components/primer/tab_nav_component.html.erb +2 -0
  67. data/app/components/primer/tab_nav_component.rb +23 -10
  68. data/app/components/primer/text_component.rb +6 -3
  69. data/app/components/primer/time_ago_component.rb +1 -1
  70. data/app/components/primer/timeline_item_component.rb +1 -1
  71. data/app/components/primer/{tooltip_component.rb → tooltip.rb} +11 -9
  72. data/app/components/primer/truncate.rb +1 -1
  73. data/app/components/primer/underline_nav_component.rb +1 -1
  74. data/app/lib/primer/classify.rb +11 -36
  75. data/app/lib/primer/classify/cache.rb +20 -15
  76. data/app/lib/primer/classify/flex.rb +111 -0
  77. data/app/lib/primer/classify/functional_border_colors.rb +1 -2
  78. data/app/lib/primer/fetch_or_fallback_helper.rb +2 -2
  79. data/app/lib/primer/octicon/cache.rb +42 -0
  80. data/app/lib/primer/view_helper.rb +2 -1
  81. data/lib/primer/view_components.rb +1 -1
  82. data/lib/primer/view_components/version.rb +1 -1
  83. data/lib/tasks/coverage.rake +14 -0
  84. data/lib/tasks/docs.rake +315 -0
  85. data/lib/tasks/statuses.rake +12 -0
  86. data/lib/yard/docs_helper.rb +57 -0
  87. data/static/statuses.json +54 -1
  88. metadata +50 -11
  89. data/app/components/primer/button_group_component.rb +0 -35
  90. data/app/components/primer/foo_bar.d.ts +0 -1
  91. data/app/components/primer/foo_bar.js +0 -1
@@ -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", tag: :img, src: "spinner.gif") %>
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'
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Primer
4
- # Use labels to add contextual metadata to a design.
4
+ # Use `Label` to add contextual metadata to a design.
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",
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Primer
4
- # Use Layout to build a main/sidebar layout.
4
+ # Use `Layout` to build a main/sidebar layout.
5
5
  class LayoutComponent < Primer::Component
6
6
  # The main content
7
7
  #
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Primer
4
- # Use links for moving from one page to another. The Link component styles anchor tags with default blue styling and hover text-decoration.
4
+ # Use `Link` for navigating from one page to another. `Link` styles anchor tags with default blue styling and hover text-decoration.
5
5
  class LinkComponent < Primer::Component
6
6
  status :beta
7
7
 
@@ -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'
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Primer
4
- # Use MarkdownComponent to wrap markdown content
5
- class MarkdownComponent < Primer::Component
4
+ # Use `Markdown` to wrap markdown content
5
+ class Markdown < Primer::Component
6
+ status :beta
6
7
  # @example Default
7
- # <%= render(Primer::MarkdownComponent.new) do %>
8
+ # <%= render(Primer::Markdown.new) do %>
8
9
  # <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
10
  #
10
11
  # <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 +236,11 @@ module Primer
235
236
  #
236
237
  # <p>Code can also use syntax highlighting.</p>
237
238
  #
238
- # <pre><code class="prism-code language-javascript">var foo = "bar";</code></pre>
239
+ # <pre><code>var foo = "bar";</code></pre>
239
240
  #
240
241
  # <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
242
  #
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>
243
+ # <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
244
  #
244
245
  # <p>Inline code inside table cells should still be distinguishable.</p>
245
246
  #
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Primer
4
- # Use menus to create vertical lists of navigational links.
4
+ # Use `Menu` to create vertical lists of navigational links.
5
5
  class MenuComponent < Primer::Component
6
6
  # Optional menu heading
7
7
  #
@@ -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.
@@ -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 %>
@@ -1,46 +1,80 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "octicons"
4
+
3
5
  module Primer
4
- # Renders an [Octicon](https://primer.style/octicons/) with <%= link_to_system_arguments_docs %>.
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.
5
8
  class OcticonComponent < Primer::Component
6
9
  status :beta
7
10
 
8
11
  SIZE_DEFAULT = :small
12
+ SIZE_MEDIUM = :medium
13
+
9
14
  SIZE_MAPPINGS = {
10
15
  SIZE_DEFAULT => 16,
11
- :medium => 32,
12
- :large => 64
16
+ SIZE_MEDIUM => 24
13
17
  }.freeze
14
18
  SIZE_OPTIONS = SIZE_MAPPINGS.keys
15
19
 
16
20
  # @example Default
17
- # <%= render(Primer::OcticonComponent.new("check")) %>
18
- # <%= render(Primer::OcticonComponent.new(icon: "check")) %>
21
+ # <%= render(Primer::OcticonComponent.new(:check)) %>
22
+ # <%= render(Primer::OcticonComponent.new(icon: :check)) %>
19
23
  #
20
24
  # @example Medium
21
- # <%= render(Primer::OcticonComponent.new("people", size: :medium)) %>
25
+ # <%= render(Primer::OcticonComponent.new(:people, size: :medium)) %>
22
26
  #
23
- # @example Large
24
- # <%= render(Primer::OcticonComponent.new("x", size: :large)) %>
27
+ # @example Helper
28
+ # <%= primer_octicon(:check) %>
25
29
  #
26
- # @param icon [String] Name of [Octicon](https://primer.style/octicons/) to use.
30
+ # @param icon [Symbol] Name of <%= link_to_octicons %> to use.
27
31
  # @param size [Symbol] <%= one_of(Primer::OcticonComponent::SIZE_MAPPINGS) %>
32
+ # @param use_symbol [Boolean] EXPERIMENTAL (May change or be removed) - Set to true when using with <%= link_to_component(Primer::OcticonSymbolsComponent) %>.
28
33
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
29
- def initialize(icon_name = nil, icon: nil, size: SIZE_DEFAULT, **system_arguments)
30
- @icon = icon_name || icon
34
+ def initialize(icon_name = nil, icon: nil, size: SIZE_DEFAULT, use_symbol: false, **system_arguments)
35
+ icon_key = icon_name || icon
36
+
37
+ # Don't allow sizes under 16px
38
+ if system_arguments[:height].present? && system_arguments[:height] < 16 || system_arguments[:width].present? && system_arguments[:width] < 16
39
+ system_arguments.delete(:height)
40
+ system_arguments.delete(:width)
41
+ end
42
+
43
+ cache_key = Primer::Octicon::Cache.get_key(symbol: icon_key, size: size, **system_arguments.slice(:height, :width))
44
+
31
45
  @system_arguments = system_arguments
46
+ @system_arguments[:tag] = :svg
47
+ @system_arguments[:aria] ||= {}
48
+ @use_symbol = use_symbol
49
+
50
+ if @system_arguments[:aria][:label] || @system_arguments[:"aria-label"]
51
+ @system_arguments[:role] = "img"
52
+ else
53
+ @system_arguments[:aria][:hidden] = true
54
+ end
55
+
56
+ if (cache_icon = Primer::Octicon::Cache.read(cache_key))
57
+ @icon = cache_icon
58
+ else
59
+ # Filter out classify options to prevent them from becoming invalid html attributes.
60
+ # Note height and width are both classify options and valid html attributes.
61
+ octicon_options = {
62
+ height: SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size, SIZE_DEFAULT)]
63
+ }.merge(@system_arguments.slice(:height, :width))
32
64
 
33
- @system_arguments[:class] = Primer::Classify.call(**@system_arguments)[:class]
34
- @system_arguments[:height] ||= SIZE_MAPPINGS[size]
65
+ @icon = Octicons::Octicon.new(icon_key, octicon_options)
66
+ Primer::Octicon::Cache.set(cache_key, @icon)
67
+ end
35
68
 
36
- # Filter out classify options to prevent them from becoming invalid html attributes.
37
- # Note height and width are both classify options and valid html attributes.
38
- octicon_helper_options = @system_arguments.slice(:height, :width)
39
- @system_arguments = add_test_selector(@system_arguments).except(*Primer::Classify::VALID_KEYS, :classes).merge(octicon_helper_options)
69
+ @system_arguments[:classes] = class_names(
70
+ @icon.options[:class],
71
+ @system_arguments[:classes]
72
+ )
73
+ @system_arguments.merge!(@icon.options.except(:class, :'aria-hidden'))
40
74
  end
41
75
 
42
- def call
43
- octicon(@icon, { **@system_arguments })
76
+ def self._after_compile
77
+ Primer::Octicon::Cache.preload!
44
78
  end
45
79
  end
46
80
  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