primer_view_components 0.0.36 → 0.0.41

Sign up to get free protection for your applications and to get access to all the features.
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,34 @@
1
+ import '@github/clipboard-copy-element';
2
+ const CLIPBOARD_COPY_TIMER_DURATION = 2000;
3
+ function toggleSVG(svg) {
4
+ if (svg.style.display === '' || svg.style.display === 'block') {
5
+ svg.style.display = 'none';
6
+ }
7
+ else {
8
+ svg.style.display = 'block';
9
+ }
10
+ }
11
+ // Toggle a copy button.
12
+ function toggleCopyButton(button) {
13
+ const [clippyIcon, checkIcon] = button.querySelectorAll('.octicon');
14
+ if (!clippyIcon || !checkIcon)
15
+ return;
16
+ toggleSVG(clippyIcon);
17
+ toggleSVG(checkIcon);
18
+ }
19
+ const clipboardCopyElementTimers = new WeakMap();
20
+ document.addEventListener('clipboard-copy', function ({ target }) {
21
+ if (!(target instanceof HTMLElement))
22
+ return;
23
+ if (!target.hasAttribute('data-view-component'))
24
+ return;
25
+ const currentTimeout = clipboardCopyElementTimers.get(target);
26
+ if (currentTimeout) {
27
+ clearTimeout(currentTimeout);
28
+ clipboardCopyElementTimers.delete(target);
29
+ }
30
+ else {
31
+ toggleCopyButton(target);
32
+ }
33
+ clipboardCopyElementTimers.set(target, setTimeout(toggleCopyButton, CLIPBOARD_COPY_TIMER_DURATION, target));
34
+ });
@@ -0,0 +1,39 @@
1
+ import '@github/clipboard-copy-element'
2
+
3
+ const CLIPBOARD_COPY_TIMER_DURATION = 2000
4
+
5
+ function toggleSVG(svg: SVGElement) {
6
+ if (svg.style.display === '' || svg.style.display === 'block') {
7
+ svg.style.display = 'none'
8
+ } else {
9
+ svg.style.display = 'block'
10
+ }
11
+ }
12
+
13
+ // Toggle a copy button.
14
+ function toggleCopyButton(button: HTMLElement) {
15
+ const [clippyIcon, checkIcon] = button.querySelectorAll<SVGElement>('.octicon')
16
+
17
+ if (!clippyIcon || !checkIcon) return
18
+
19
+ toggleSVG(clippyIcon)
20
+ toggleSVG(checkIcon)
21
+ }
22
+
23
+ const clipboardCopyElementTimers = new WeakMap<HTMLElement, number>()
24
+
25
+ document.addEventListener('clipboard-copy', function ({target}) {
26
+ if (!(target instanceof HTMLElement)) return
27
+ if (!target.hasAttribute('data-view-component')) return
28
+
29
+ const currentTimeout = clipboardCopyElementTimers.get(target)
30
+
31
+ if (currentTimeout) {
32
+ clearTimeout(currentTimeout)
33
+ clipboardCopyElementTimers.delete(target)
34
+ } else {
35
+ toggleCopyButton(target)
36
+ }
37
+
38
+ clipboardCopyElementTimers.set(target, setTimeout(toggleCopyButton, CLIPBOARD_COPY_TIMER_DURATION, target))
39
+ })
@@ -1,8 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Primer
4
- # Use CloseButton to render an `×` without default button styles.
4
+ # Use `CloseButton` to render an `×` without default button styles.
5
+ #
6
+ # @accessibility
7
+ # `CloseButton` has a default `aria-label` of "Close" to provides assistive technologies with an accessible label.
8
+ # You may choose to override this label with something more descriptive via [system_arguments][0].
9
+ # [0]: https://primer.style/view-components/system-arguments#html-attributes
5
10
  class CloseButton < Primer::Component
11
+ status :beta
12
+
6
13
  DEFAULT_TYPE = :button
7
14
  TYPE_OPTIONS = [DEFAULT_TYPE, :submit].freeze
8
15
 
@@ -14,15 +21,17 @@ module Primer
14
21
  def initialize(type: DEFAULT_TYPE, **system_arguments)
15
22
  @system_arguments = system_arguments
16
23
  @system_arguments[:tag] = :button
24
+ @system_arguments[:block] = false
17
25
  @system_arguments[:type] = fetch_or_fallback(TYPE_OPTIONS, type, DEFAULT_TYPE)
18
26
  @system_arguments[:classes] = class_names(
19
27
  "close-button",
20
28
  system_arguments[:classes]
21
29
  )
30
+ @system_arguments[:"aria-label"] ||= "Close"
22
31
  end
23
32
 
24
33
  def call
25
- render(Primer::BaseComponent.new(**@system_arguments)) do
34
+ render(Primer::BaseButton.new(**@system_arguments)) do
26
35
  render(Primer::OcticonComponent.new("x"))
27
36
  end
28
37
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "octicons_helper/helper"
4
3
  require "view_component/version"
5
4
 
6
5
  module Primer
@@ -9,10 +8,30 @@ module Primer
9
8
  include ViewComponent::SlotableV2 unless ViewComponent::VERSION::STRING.to_f >= 2.28
10
9
  include ClassNameHelper
11
10
  include FetchOrFallbackHelper
12
- include OcticonsHelper
13
11
  include TestSelectorHelper
14
12
  include JoinStyleArgumentsHelper
15
13
  include ViewHelper
16
14
  include Status::Dsl
15
+
16
+ private
17
+
18
+ def deprecated_component_warning(new_class: nil, version: nil)
19
+ return if Rails.env.production? || silence_deprecations?
20
+
21
+ message = "#{self.class.name} is deprecated"
22
+ message += " and will be removed in v#{version}." if version
23
+ message += " Use #{new_class.name} instead." if new_class
24
+
25
+ ActiveSupport::Deprecation.warn(message)
26
+ end
27
+
28
+ def validate_aria_label
29
+ aria_label = @system_arguments[:"aria-label"] || @system_arguments.dig(:aria, :label)
30
+ raise ArgumentError, "`aria-label` is required." if aria_label.nil? && !Rails.env.production?
31
+ end
32
+
33
+ def silence_deprecations?
34
+ Rails.application.config.primer_view_components.silence_deprecations
35
+ end
17
36
  end
18
37
  end
@@ -1,7 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Primer
4
- # Use Primer::CounterComponent to add a count to navigational elements and buttons.
4
+ # Use `Counter` to add a count to navigational elements and buttons.
5
+ #
6
+ # @accessibility
7
+ # Always use `Counter` with adjacent text that provides supplementary information regarding what the count is for. For instance, `Counter`
8
+ # should be accompanied with text such as `issues` or `pull requests`.
9
+ #
5
10
  class CounterComponent < Primer::Component
6
11
  status :beta
7
12
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Primer
4
- # Use DetailsComponent to reveal content after clicking a button.
4
+ # Use `DetailsComponent` to reveal content after clicking a button.
5
5
  class DetailsComponent < Primer::Component
6
6
  status :beta
7
7
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Primer
4
4
  module Dropdown
5
- # This component is part of `Primer::DropdownComponent` and should not be
5
+ # This component is part of `Dropdown` and should not be
6
6
  # used as a standalone component.
7
7
  class MenuComponent < Primer::Component
8
8
  SCHEME_DEFAULT = :default
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Primer
4
- # Dropdowns are lightweight context menus for housing navigation and actions.
4
+ # `Dropdown` is a lightweight context menu for housing navigation and actions.
5
5
  # They're great for instances where you don't need the full power (and code) of the select menu.
6
6
  class DropdownComponent < Primer::Component
7
7
  # Required trigger for the dropdown. Only accepts a content.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Primer
4
- # Use the Flash component to inform users of successful or pending actions.
4
+ # Use `Flash` to inform users of successful or pending actions.
5
5
  class FlashComponent < Primer::Component
6
6
  status :beta
7
7
 
@@ -35,7 +35,7 @@ module Primer
35
35
  # <%= render(Primer::FlashComponent.new(dismissible: true)) { "This is a dismissible flash message!" } %>
36
36
  #
37
37
  # @example Icon
38
- # <%= render(Primer::FlashComponent.new(icon: "people")) { "This is a flash message with an icon!" } %>
38
+ # <%= render(Primer::FlashComponent.new(icon: :people)) { "This is a flash message with an icon!" } %>
39
39
  #
40
40
  # @example With actions
41
41
  # <%= render(Primer::FlashComponent.new) do |component| %>
@@ -48,7 +48,7 @@ module Primer
48
48
  # @param full [Boolean] Whether the component should take up the full width of the screen.
49
49
  # @param spacious [Boolean] Whether to add margin to the bottom of the component.
50
50
  # @param dismissible [Boolean] Whether the component can be dismissed with an X button.
51
- # @param icon [String] Name of Octicon icon to use.
51
+ # @param icon [Symbol] Name of Octicon icon to use.
52
52
  # @param scheme [Symbol] <%= one_of(Primer::FlashComponent::SCHEME_MAPPINGS.keys) %>
53
53
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
54
54
  def initialize(full: false, spacious: false, dismissible: false, icon: nil, scheme: DEFAULT_SCHEME, **system_arguments)
@@ -1,11 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Primer
4
- # Use FlexComponent to make an element lay out its content using the flexbox model.
4
+ # Use `Flex` to make an element lay out its content using the flexbox model.
5
5
  # Before using these utilities, you should be familiar with CSS3 Flexible Box
6
6
  # spec. If you are not, check out MDN's guide [Using CSS Flexible
7
7
  # Boxes](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox).
8
+ #
9
+ # @deprecated
10
+ # Use <%= link_to_component(Primer::BoxComponent) %> instead.
11
+ #
12
+ # **Before**:
13
+ #
14
+ # ```erb
15
+ # <%%= render Primer::FlexComponent.new(justify_content: :center) %>
16
+ # <%%= render Primer::FlexComponent.new(inline: true) %>
17
+ # <%%= render Primer::FlexComponent.new(flex_wrap: true) %>
18
+ # <%%= render Primer::FlexComponent.new(align_items: :start) %>
19
+ # <%%= render Primer::FlexComponent.new(direction: :column) %>
20
+ # ```
21
+ #
22
+ # **After**:
23
+ #
24
+ # ```erb
25
+ # <%%= render Primer::BoxComponent.new(display: :flex, justify_content: :center) %>
26
+ # <%%= render Primer::BoxComponent.new(display: :inline_flex) %>
27
+ # <%%= render Primer::BoxComponent.new(display: :flex, flex_wrap: :wrap) %>
28
+ # <%%= render Primer::BoxComponent.new(display: :flex, align_items: :start) %>
29
+ # <%%= render Primer::BoxComponent.new(display: :flex, direction: :column) %>
30
+ # ```
8
31
  class FlexComponent < Primer::Component
32
+ status :deprecated
33
+
9
34
  JUSTIFY_CONTENT_DEFAULT = nil
10
35
  JUSTIFY_CONTENT_MAPPINGS = {
11
36
  flex_start: "flex-justify-start",
@@ -77,6 +102,8 @@ module Primer
77
102
  direction: nil,
78
103
  **system_arguments
79
104
  )
105
+ deprecated_component_warning(new_class: Primer::BoxComponent, version: "0.0.40")
106
+
80
107
  @align_items = fetch_or_fallback(ALIGN_ITEMS_OPTIONS, align_items, ALIGN_ITEMS_DEFAULT)
81
108
  @justify_content = fetch_or_fallback(JUSTIFY_CONTENT_OPTIONS, justify_content, JUSTIFY_CONTENT_DEFAULT)
82
109
  @flex_wrap = fetch_or_fallback(FLEX_WRAP_OPTIONS, flex_wrap, FLEX_WRAP_DEFAULT)
@@ -1,9 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Primer
4
- # Use FlexItemComponent to specify the ability of a flex item to alter its
4
+ # Use `FlexItem` to specify the ability of a flex item to alter its
5
5
  # dimensions to fill available space
6
+ #
7
+ # @deprecated
8
+ # Use <%= link_to_component(Primer::BoxComponent) %> instead.
9
+ #
10
+ # **Before**:
11
+ #
12
+ # ```erb
13
+ # <%%= render Primer::FlexItemComponent.new(flex_auto: :auto) %>
14
+ # ```
15
+ #
16
+ # **After**:
17
+ #
18
+ # ```erb
19
+ # <%%= render Primer::BoxComponent.new(flex: :auto) %>
20
+ # ```
6
21
  class FlexItemComponent < Primer::Component
22
+ status :deprecated
23
+
7
24
  FLEX_AUTO_DEFAULT = false
8
25
  FLEX_AUTO_ALLOWED_VALUES = [FLEX_AUTO_DEFAULT, true].freeze
9
26
 
@@ -21,6 +38,8 @@ module Primer
21
38
  # @param flex_auto [Boolean] Fills available space and auto-sizes based on the content. Defaults to <%= Primer::FlexItemComponent::FLEX_AUTO_DEFAULT %>
22
39
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
23
40
  def initialize(flex_auto: FLEX_AUTO_DEFAULT, **system_arguments)
41
+ deprecated_component_warning(new_class: Primer::BoxComponent, version: "0.0.40")
42
+
24
43
  @system_arguments = system_arguments
25
44
  @system_arguments[:classes] =
26
45
  class_names(
@@ -1,19 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Primer
4
- # Use the Heading component to wrap a component that will create a heading element
4
+ # `Heading` should be used to communicate page organization and hierarchy.
5
+ #
6
+ # - Set tag to one of `:h1`, `:h2`, `:h3`, `:h4`, `:h5`, `:h6` based on what is appropriate for the page context.
7
+ # - Use `Heading` as the title of a section or sub section.
8
+ # - Do not use `Heading` for styling alone. For simply styling text, consider using <%= link_to_component(Primer::TextComponent) %> with relevant <%= link_to_typography_docs %>
9
+ # such as `font_size` and `font_weight`.
10
+ # - Do not jump heading levels. For instance, do not follow a `<h1>` with an `<h3>`. Heading levels should increase by one in ascending order.
11
+ #
12
+ # @accessibility
13
+ # While sighted users rely on visual cues such as font size changes to determine what the heading is, assistive technology users rely on programatic cues that can be read out.
14
+ # When text on a page is visually implied to be a heading, ensure that it is coded as a heading. Additionally, visually implied heading level and coded heading level should be
15
+ # consistent. [See WCAG success criteria: 1.3.1: Info and Relationships](https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships.html)
16
+ #
17
+ # Headings allow assistive technology users to quickly navigate around a page. Navigation to text that is not meant to be a heading can be a confusing experience.
18
+ # <%= link_to_heading_practices %>
5
19
  class HeadingComponent < Primer::Component
6
20
  status :beta
7
21
 
22
+ TAG_FALLBACK = :h2
23
+ TAG_OPTIONS = [:h1, TAG_FALLBACK, :h3, :h4, :h5, :h6].freeze
24
+
8
25
  # @example Default
9
- # <%= render(Primer::HeadingComponent.new) { "H1 Text" } %>
26
+ # <%= render(Primer::HeadingComponent.new(tag: :h1)) { "H1 Text" } %>
10
27
  # <%= render(Primer::HeadingComponent.new(tag: :h2)) { "H2 Text" } %>
11
28
  # <%= render(Primer::HeadingComponent.new(tag: :h3)) { "H3 Text" } %>
29
+ # <%= render(Primer::HeadingComponent.new(tag: :h4)) { "H4 Text" } %>
30
+ # <%= render(Primer::HeadingComponent.new(tag: :h5)) { "H5 Text" } %>
31
+ # <%= render(Primer::HeadingComponent.new(tag: :h6)) { "H6 Text" } %>
12
32
  #
33
+ # @param tag [String] <%= one_of(Primer::HeadingComponent::TAG_OPTIONS) %>
13
34
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
14
- def initialize(**system_arguments)
35
+ def initialize(tag:, **system_arguments)
15
36
  @system_arguments = system_arguments
16
- @system_arguments[:tag] ||= :h1
37
+ @system_arguments[:tag] = fetch_or_fallback(TAG_OPTIONS, tag, TAG_FALLBACK)
17
38
  end
18
39
 
19
40
  def call
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Primer
4
- # Use HiddenTextExpander to indicate and toggle hidden text.
4
+ # Use `HiddenTextExpander` to indicate and toggle hidden text.
5
5
  class HiddenTextExpander < Primer::Component
6
6
  # @example Default
7
7
  # <%= render(Primer::HiddenTextExpander.new) %>
@@ -25,8 +25,6 @@ module Primer
25
25
  )
26
26
 
27
27
  @button_arguments = button_arguments
28
- @button_arguments[:tag] = :button
29
- @button_arguments[:type] = :button
30
28
  @button_arguments[:"aria-expanded"] = false
31
29
  @button_arguments[:classes] = class_names(
32
30
  "ellipsis-expander",
@@ -36,7 +34,7 @@ module Primer
36
34
 
37
35
  def call
38
36
  render(Primer::BaseComponent.new(**@system_arguments)) do
39
- render(Primer::BaseComponent.new(**@button_arguments)) { "&hellip;" }
37
+ render(Primer::BaseButton.new(**@button_arguments)) { "&hellip;".html_safe }
40
38
  end
41
39
  end
42
40
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ # Use `IconButton` to render Icon-only buttons without the default button styles.
5
+ #
6
+ # @accessibility
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)
12
+ class IconButton < Primer::Component
13
+ status :beta
14
+
15
+ DEFAULT_SCHEME = :default
16
+ SCHEME_MAPPINGS = {
17
+ DEFAULT_SCHEME => "",
18
+ :danger => "btn-octicon-danger"
19
+ }.freeze
20
+ SCHEME_OPTIONS = SCHEME_MAPPINGS.keys
21
+ # @example Default
22
+ #
23
+ # <%= render(Primer::IconButton.new(icon: :search, "aria-label": "Search")) %>
24
+ #
25
+ # @example Schemes
26
+ #
27
+ # <%= render(Primer::IconButton.new(icon: :search, "aria-label": "Search")) %>
28
+ # <%= render(Primer::IconButton.new(icon: :trash, "aria-label": "Delete", scheme: :danger)) %>
29
+ #
30
+ # @example In a BorderBox
31
+ #
32
+ # <%= render(Primer::BorderBoxComponent.new) do |component| %>
33
+ # <% component.body do %>
34
+ # <%= render(Primer::TextComponent.new(pr: 2)) { "Body" } %>
35
+ # <%= render(Primer::IconButton.new(icon: :pencil, box: true, "aria-label": "Edit")) %>
36
+ # <% end %>
37
+ # <% end %>
38
+ #
39
+ # @param scheme [Symbol] <%= one_of(Primer::IconButton::SCHEME_OPTIONS) %>
40
+ # @param icon [String] Name of <%= link_to_octicons %> to use.
41
+ # @param tag [Symbol] <%= one_of(Primer::BaseButton::TAG_OPTIONS) %>
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.
44
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
45
+ def initialize(scheme: DEFAULT_SCHEME, icon:, box: false, **system_arguments)
46
+ @icon = icon
47
+
48
+ @system_arguments = system_arguments
49
+ @system_arguments[:classes] = class_names(
50
+ "btn-octicon",
51
+ SCHEME_MAPPINGS[fetch_or_fallback(SCHEME_OPTIONS, scheme, DEFAULT_SCHEME)],
52
+ system_arguments[:classes],
53
+ "Box-btn-octicon" => box
54
+ )
55
+
56
+ validate_aria_label
57
+ end
58
+
59
+ def call
60
+ render(Primer::BaseButton.new(**@system_arguments)) do
61
+ render(Primer::OcticonComponent.new(icon: @icon))
62
+ end
63
+ end
64
+ end
65
+ end