primer_view_components 0.0.84 → 0.0.87

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  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/auto_complete/item.rb +1 -1
  6. data/app/components/primer/alpha/auto_complete.rb +1 -1
  7. data/app/components/primer/alpha/button_marketing.rb +1 -1
  8. data/app/components/primer/alpha/text_field.rb +105 -0
  9. data/app/components/primer/alpha/tool-tip-element.d.ts +3 -1
  10. data/app/components/primer/alpha/tool-tip-element.js +20 -13
  11. data/app/components/primer/alpha/tool-tip-element.ts +23 -14
  12. data/app/components/primer/alpha/tooltip.rb +1 -1
  13. data/app/components/primer/beta/base_button.rb +47 -0
  14. data/app/components/primer/{alpha → beta}/border_box/header.html.erb +0 -0
  15. data/app/components/primer/{alpha → beta}/border_box/header.rb +6 -6
  16. data/app/components/primer/{border_box_component.html.erb → beta/border_box.html.erb} +0 -0
  17. data/app/components/primer/beta/border_box.rb +147 -0
  18. data/app/components/primer/blankslate_component.html.erb +0 -5
  19. data/app/components/primer/border_box_component.rb +2 -140
  20. data/app/components/primer/button_component.html.erb +12 -4
  21. data/app/components/primer/button_component.rb +2 -2
  22. data/app/components/primer/clipboard_copy.rb +6 -2
  23. data/app/components/primer/close_button.rb +1 -1
  24. data/app/components/primer/hellip_button.rb +1 -1
  25. data/app/components/primer/icon_button.html.erb +6 -0
  26. data/app/components/primer/icon_button.rb +46 -10
  27. data/app/components/primer/link_component.rb +12 -5
  28. data/app/helpers/primer/form_helper.rb +10 -0
  29. data/app/lib/primer/join_style_arguments_helper.rb +1 -1
  30. data/lib/primer/classify/utilities.rb +3 -6
  31. data/lib/primer/form_components.rb +36 -0
  32. data/lib/primer/forms/acts_as_component.rb +118 -0
  33. data/lib/primer/forms/base.html.erb +8 -0
  34. data/lib/primer/forms/base.rb +142 -0
  35. data/lib/primer/forms/base_component.rb +62 -0
  36. data/lib/primer/forms/buffer_rewriter.rb +51 -0
  37. data/lib/primer/forms/builder.rb +48 -0
  38. data/lib/primer/forms/caption.html.erb +10 -0
  39. data/lib/primer/forms/caption.rb +29 -0
  40. data/lib/primer/forms/check_box.html.erb +9 -0
  41. data/lib/primer/forms/check_box.rb +16 -0
  42. data/lib/primer/forms/check_box_group.html.erb +12 -0
  43. data/lib/primer/forms/check_box_group.rb +14 -0
  44. data/lib/primer/forms/dsl/check_box_group_input.rb +41 -0
  45. data/lib/primer/forms/dsl/check_box_input.rb +27 -0
  46. data/lib/primer/forms/dsl/form_object.rb +25 -0
  47. data/lib/primer/forms/dsl/form_reference_input.rb +36 -0
  48. data/lib/primer/forms/dsl/hidden_input.rb +29 -0
  49. data/lib/primer/forms/dsl/input.rb +237 -0
  50. data/lib/primer/forms/dsl/input_group.rb +34 -0
  51. data/lib/primer/forms/dsl/input_methods.rb +86 -0
  52. data/lib/primer/forms/dsl/multi_input.rb +58 -0
  53. data/lib/primer/forms/dsl/radio_button_group_input.rb +38 -0
  54. data/lib/primer/forms/dsl/radio_button_input.rb +37 -0
  55. data/lib/primer/forms/dsl/select_list_input.rb +53 -0
  56. data/lib/primer/forms/dsl/submit_button_input.rb +28 -0
  57. data/lib/primer/forms/dsl/text_area_input.rb +33 -0
  58. data/lib/primer/forms/dsl/text_field_input.rb +65 -0
  59. data/lib/primer/forms/form_control.html.erb +18 -0
  60. data/lib/primer/forms/form_control.rb +23 -0
  61. data/lib/primer/forms/form_list.html.erb +5 -0
  62. data/lib/primer/forms/form_list.rb +21 -0
  63. data/lib/primer/forms/form_reference.html.erb +3 -0
  64. data/lib/primer/forms/form_reference.rb +14 -0
  65. data/lib/primer/forms/group.html.erb +5 -0
  66. data/lib/primer/forms/group.rb +27 -0
  67. data/lib/primer/forms/hidden_field.html.erb +1 -0
  68. data/lib/primer/forms/hidden_field.rb +15 -0
  69. data/lib/primer/forms/multi.html.erb +3 -0
  70. data/lib/primer/forms/multi.rb +14 -0
  71. data/lib/primer/forms/radio_button.html.erb +14 -0
  72. data/lib/primer/forms/radio_button.rb +29 -0
  73. data/lib/primer/forms/radio_button_group.html.erb +12 -0
  74. data/lib/primer/forms/radio_button_group.rb +14 -0
  75. data/lib/primer/forms/select_list.html.erb +5 -0
  76. data/lib/primer/forms/select_list.rb +26 -0
  77. data/lib/primer/forms/separator.html.erb +1 -0
  78. data/lib/primer/forms/separator.rb +8 -0
  79. data/lib/primer/forms/spacing_wrapper.html.erb +3 -0
  80. data/lib/primer/forms/spacing_wrapper.rb +8 -0
  81. data/lib/primer/forms/submit_button.html.erb +4 -0
  82. data/lib/primer/forms/submit_button.rb +50 -0
  83. data/lib/primer/forms/text_area.html.erb +5 -0
  84. data/lib/primer/forms/text_area.rb +16 -0
  85. data/lib/primer/forms/text_field.html.erb +19 -0
  86. data/lib/primer/forms/text_field.rb +14 -0
  87. data/lib/primer/view_components/engine.rb +34 -0
  88. data/lib/primer/view_components/linters/argument_mappers/button.rb +2 -2
  89. data/lib/primer/view_components/linters/button_component_migration_counter.rb +1 -1
  90. data/lib/primer/view_components/linters/helpers/deprecated_components_helpers.rb +2 -8
  91. data/lib/primer/view_components/version.rb +1 -1
  92. data/lib/rubocop/cop/primer/component_name_migration.rb +3 -0
  93. data/lib/rubocop/cop/primer/deprecated_arguments.rb +0 -10
  94. data/lib/tasks/deprecated.rake +22 -0
  95. data/lib/tasks/docs.rake +5 -3
  96. data/static/arguments.yml +148 -39
  97. data/static/audited_at.json +4 -2
  98. data/static/classes.yml +2 -1
  99. data/static/constants.json +44 -39
  100. data/static/statuses.json +7 -5
  101. metadata +69 -8
  102. data/app/components/primer/base_button.rb +0 -43
@@ -1,145 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Primer
4
- # `BorderBox` is a Box component with a border.
5
- class BorderBoxComponent < Primer::Component
6
- status :beta
7
-
8
- DEFAULT_PADDING = :default
9
- PADDING_MAPPINGS = {
10
- DEFAULT_PADDING => "",
11
- :condensed => "Box--condensed",
12
- :spacious => "Box--spacious"
13
- }.freeze
14
- PADDING_SUGGESTION = "Perhaps you could consider using :padding options of #{PADDING_MAPPINGS.keys.to_sentence}?"
15
-
16
- DEFAULT_ROW_SCHEME = :default
17
- ROW_SCHEME_MAPPINGS = {
18
- DEFAULT_ROW_SCHEME => "",
19
- :neutral => "Box-row--gray",
20
- :info => "Box-row--blue",
21
- :warning => "Box-row--yellow"
22
- }.freeze
23
-
24
- # Optional Header.
25
- #
26
- # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
27
- # @accessibility
28
- # When using header.title, the recommended tag is a heading tag, such as h1, h2, h3, etc.
29
- renders_one :header, "Primer::Alpha::BorderBox::Header"
30
-
31
- # Optional Body.
32
- #
33
- # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
34
- renders_one :body, lambda { |**system_arguments|
35
- system_arguments[:tag] = :div
36
- system_arguments[:classes] = class_names(
37
- "Box-body",
38
- system_arguments[:classes]
39
- )
40
-
41
- Primer::BaseComponent.new(**system_arguments)
42
- }
43
-
44
- # Optional Footer.
45
- #
46
- # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
47
- renders_one :footer, lambda { |**system_arguments|
48
- system_arguments[:tag] = :div
49
- system_arguments[:classes] = class_names(
50
- "Box-footer",
51
- system_arguments[:classes]
52
- )
53
-
54
- Primer::BaseComponent.new(**system_arguments)
55
- }
56
-
57
- # Use Rows to add rows with borders and maintain the same padding.
58
- #
59
- # @param scheme [Symbol] Color scheme. <%= one_of(Primer::BorderBoxComponent::ROW_SCHEME_MAPPINGS.keys) %>
60
- # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
61
- renders_many :rows, lambda { |scheme: DEFAULT_ROW_SCHEME, **system_arguments|
62
- system_arguments[:tag] = :li
63
- system_arguments[:classes] = class_names(
64
- "Box-row",
65
- ROW_SCHEME_MAPPINGS[fetch_or_fallback(ROW_SCHEME_MAPPINGS.keys, scheme, DEFAULT_ROW_SCHEME)],
66
- system_arguments[:classes]
67
- )
68
-
69
- Primer::BaseComponent.new(**system_arguments)
70
- }
71
-
72
- # @example Header with title, body, rows, and footer
73
- # <%= render(Primer::BorderBoxComponent.new) do |component| %>
74
- # <% component.header do |h| %>
75
- # <% h.title(tag: :h2) do %>
76
- # Header
77
- # <% end %>
78
- # <% end %>
79
- # <% component.body do %>
80
- # Body
81
- # <% end %>
82
- # <% component.row do %>
83
- # <% if true %>
84
- # Row one
85
- # <% end %>
86
- # <% end %>
87
- # <% component.row do %>
88
- # Row two
89
- # <% end %>
90
- # <% component.footer do %>
91
- # Footer
92
- # <% end %>
93
- # <% end %>
94
- #
95
- # @example Padding density
96
- # <%= render(Primer::BorderBoxComponent.new(padding: :condensed)) do |component| %>
97
- # <% component.header do %>
98
- # Header
99
- # <% end %>
100
- # <% component.body do %>
101
- # Body
102
- # <% end %>
103
- # <% component.row do %>
104
- # Row two
105
- # <% end %>
106
- # <% component.footer do %>
107
- # Footer
108
- # <% end %>
109
- # <% end %>
110
- #
111
- # @example Row colors
112
- # <%= render(Primer::BorderBoxComponent.new) do |component| %>
113
- # <% component.row do %>
114
- # Default
115
- # <% end %>
116
- # <% component.row(scheme: :neutral) do %>
117
- # Neutral
118
- # <% end %>
119
- # <% component.row(scheme: :info) do %>
120
- # Info
121
- # <% end %>
122
- # <% component.row(scheme: :warning) do %>
123
- # Warning
124
- # <% end %>
125
- # <% end %>
126
- #
127
- # @param padding [Symbol] <%= one_of(Primer::BorderBoxComponent::PADDING_MAPPINGS.keys) %>
128
- # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
129
- def initialize(padding: DEFAULT_PADDING, **system_arguments)
130
- @system_arguments = deny_tag_argument(**system_arguments)
131
- @system_arguments[:tag] = :div
132
- @system_arguments[:classes] = class_names(
133
- "Box",
134
- PADDING_MAPPINGS[fetch_or_fallback(PADDING_MAPPINGS.keys, padding, DEFAULT_PADDING)],
135
- system_arguments[:classes]
136
- )
137
-
138
- @system_arguments[:system_arguments_denylist] = { [:p, :pt, :pb, :pr, :pl] => PADDING_SUGGESTION }
139
- end
140
-
141
- def render?
142
- rows.any? || header.present? || body.present? || footer.present?
143
- end
4
+ class BorderBoxComponent < Primer::Beta::BorderBox
5
+ status :deprecated
144
6
  end
145
7
  end
@@ -1,4 +1,12 @@
1
- <%= render Primer::BaseButton.new(**@system_arguments) do -%>
2
- <%= leading_visual %><%= trimmed_content %><%= trailing_visual %><%= primer_octicon("triangle-down", ml: 2, mr: -1) if @dropdown %>
3
- <%= tooltip %>
4
- <% end -%>
1
+ <% if tooltip.present? %>
2
+ <%= render Primer::BaseComponent.new(tag: :div, position: :relative, display: :inline_block) do %>
3
+ <%= render Primer::Beta::BaseButton.new(**@system_arguments) do -%>
4
+ <%= leading_visual %><%= trimmed_content %><%= trailing_visual %><%= primer_octicon("triangle-down", ml: 2, mr: -1) if @dropdown %>
5
+ <% end -%>
6
+ <%= tooltip %>
7
+ <% end -%>
8
+ <% else %>
9
+ <%= render Primer::Beta::BaseButton.new(**@system_arguments) do -%>
10
+ <%= leading_visual %><%= trimmed_content %><%= trailing_visual %><%= primer_octicon("triangle-down", ml: 2, mr: -1) if @dropdown %>
11
+ <% end -%>
12
+ <% end %>
@@ -123,8 +123,8 @@ module Primer
123
123
  # @param scheme [Symbol] <%= one_of(Primer::ButtonComponent::SCHEME_OPTIONS) %>
124
124
  # @param variant [Symbol] DEPRECATED. <%= one_of(Primer::ButtonComponent::SIZE_OPTIONS) %>
125
125
  # @param size [Symbol] <%= one_of(Primer::ButtonComponent::SIZE_OPTIONS) %>
126
- # @param tag [Symbol] (Primer::BaseButton::DEFAULT_TAG) <%= one_of(Primer::BaseButton::TAG_OPTIONS) %>
127
- # @param type [Symbol] (Primer::BaseButton::DEFAULT_TYPE) <%= one_of(Primer::BaseButton::TYPE_OPTIONS) %>
126
+ # @param tag [Symbol] (Primer::Beta::BaseButton::DEFAULT_TAG) <%= one_of(Primer::Beta::BaseButton::TAG_OPTIONS) %>
127
+ # @param type [Symbol] (Primer::Beta::BaseButton::DEFAULT_TYPE) <%= one_of(Primer::Beta::BaseButton::TYPE_OPTIONS) %>
128
128
  # @param group_item [Boolean] Whether button is part of a ButtonGroup.
129
129
  # @param block [Boolean] Whether button is full-width with `display: block`.
130
130
  # @param dropdown [Boolean] Whether or not to render a dropdown caret.
@@ -12,7 +12,7 @@ module Primer
12
12
  # <%= render(Primer::ClipboardCopy.new(value: "Text to copy", "aria-label": "Copy text to the system clipboard")) %>
13
13
  #
14
14
  # @example With text instead of icons
15
- # <%= render(Primer::ClipboardCopy.new(value: "Text to copy", "aria-label": "Copy text to the system clipboard")) do %>
15
+ # <%= render(Primer::ClipboardCopy.new(value: "Text to copy")) do %>
16
16
  # Click to copy!
17
17
  # <% end %>
18
18
  #
@@ -34,10 +34,14 @@ module Primer
34
34
  @system_arguments[:value] = value if value.present?
35
35
  end
36
36
 
37
+ # :nodoc:
38
+ def before_render
39
+ validate_aria_label if content.blank?
40
+ end
41
+
37
42
  private
38
43
 
39
44
  def validate!
40
- validate_aria_label
41
45
  raise ArgumentError, "Must provide either `value` or `for`" if @value.nil? && @system_arguments[:for].nil?
42
46
  end
43
47
  end
@@ -31,7 +31,7 @@ module Primer
31
31
  end
32
32
 
33
33
  def call
34
- render(Primer::BaseButton.new(**@system_arguments)) do
34
+ render(Primer::Beta::BaseButton.new(**@system_arguments)) do
35
35
  render(Primer::OcticonComponent.new("x"))
36
36
  end
37
37
  end
@@ -33,7 +33,7 @@ module Primer
33
33
  end
34
34
 
35
35
  def call
36
- render(Primer::BaseButton.new(**@system_arguments)) { "&hellip;".html_safe }
36
+ render(Primer::Beta::BaseButton.new(**@system_arguments)) { "&hellip;".html_safe }
37
37
  end
38
38
  end
39
39
  end
@@ -0,0 +1,6 @@
1
+ <%= render Primer::BaseComponent.new(tag: :div, position: :relative, display: :inline_block) do %>
2
+ <%= render Primer::Beta::BaseButton.new(**@system_arguments) do -%>
3
+ <%= render Primer::OcticonComponent.new(icon: @icon) %>
4
+ <% end -%>
5
+ <%= render Primer::Alpha::Tooltip.new(**@tooltip_arguments) %>
6
+ <% end %>
@@ -8,6 +8,7 @@ module Primer
8
8
  # The `aria-label` should describe the action to be invoked rather than the icon itself. For instance,
9
9
  # if your `IconButton` renders a magnifying glass icon and invokes a search action, the `aria-label` should be
10
10
  # `"Search"` instead of `"Magnifying glass"`.
11
+ # Either `aria-label` or `aria-description` will be used for the `Tooltip` text, depending on which one is present.
11
12
  # [Learn more about best functional image practices (WAI Images)](https://www.w3.org/WAI/tutorials/images/functional)
12
13
  class IconButton < Primer::Component
13
14
  status :beta
@@ -18,9 +19,10 @@ module Primer
18
19
  :danger => "btn-octicon-danger"
19
20
  }.freeze
20
21
  SCHEME_OPTIONS = SCHEME_MAPPINGS.keys
22
+
21
23
  # @example Default
22
24
  #
23
- # <%= render(Primer::IconButton.new(icon: :search, "aria-label": "Search")) %>
25
+ # <%= render(Primer::IconButton.new(icon: :search, "aria-label": "Search", id: "search-button")) %>
24
26
  #
25
27
  # @example Schemes
26
28
  #
@@ -29,23 +31,41 @@ module Primer
29
31
  #
30
32
  # @example In a BorderBox
31
33
  #
32
- # <%= render(Primer::BorderBoxComponent.new) do |component| %>
34
+ # <%= render(Primer::Beta::BorderBox.new) do |component| %>
33
35
  # <% component.body do %>
34
36
  # <%= render(Primer::Beta::Text.new(pr: 2)) { "Body" } %>
35
37
  # <%= render(Primer::IconButton.new(icon: :pencil, box: true, "aria-label": "Edit")) %>
36
38
  # <% end %>
37
39
  # <% end %>
38
40
  #
41
+ # @example With an `aria-description`
42
+ # @description
43
+ # If you need to have a longer description for the icon button, use both the `aria-label` and `aria-description`
44
+ # attributes. A label should be short and concise, while the description can be longer as it is intended to provide
45
+ # more context and information. See the accessibility section for more information.
46
+ # @code
47
+ # <%= render(Primer::IconButton.new(icon: :bold, "aria-label": "Bold", "aria-description": "Add bold text, Cmd+b")) %>
48
+ #
49
+ # @example Custom tooltip direction
50
+ #
51
+ # <%= render(Primer::IconButton.new(icon: :search, "aria-label": "Search", tooltip_direction: :e)) %>
52
+ #
39
53
  # @param scheme [Symbol] <%= one_of(Primer::IconButton::SCHEME_OPTIONS) %>
40
54
  # @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.
55
+ # @param tag [Symbol] <%= one_of(Primer::Beta::BaseButton::TAG_OPTIONS) %>
56
+ # @param type [Symbol] <%= one_of(Primer::Beta::BaseButton::TYPE_OPTIONS) %>
57
+ # @param aria-label [String] String that can be read by assistive technology. A label should be short and concise. See the accessibility section for more information.
58
+ # @param aria-description [String] String that can be read by assistive technology. A description can be longer as it is intended to provide more context and information. See the accessibility section for more information.
59
+ # @param tooltip_direction [Symbol] (Primer::Alpha::Tooltip::DIRECTION_DEFAULT) <%= one_of(Primer::Alpha::Tooltip::DIRECTION_OPTIONS) %>
60
+ # @param box [Boolean] Whether the button is in a <%= link_to_component(Primer::Beta::BorderBox) %>. If `true`, the button will have the `Box-btn-octicon` class.
44
61
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
45
- def initialize(icon:, scheme: DEFAULT_SCHEME, box: false, **system_arguments)
62
+ def initialize(icon:, scheme: DEFAULT_SCHEME, box: false, tooltip_direction: Primer::Alpha::Tooltip::DIRECTION_DEFAULT, **system_arguments)
46
63
  @icon = icon
47
64
 
48
65
  @system_arguments = system_arguments
66
+
67
+ @system_arguments[:id] ||= "icon-button-#{SecureRandom.hex(4)}"
68
+
49
69
  @system_arguments[:classes] = class_names(
50
70
  "btn-octicon",
51
71
  SCHEME_MAPPINGS[fetch_or_fallback(SCHEME_OPTIONS, scheme, DEFAULT_SCHEME)],
@@ -54,11 +74,27 @@ module Primer
54
74
  )
55
75
 
56
76
  validate_aria_label
57
- end
58
77
 
59
- def call
60
- render(Primer::BaseButton.new(**@system_arguments)) do
61
- render(Primer::OcticonComponent.new(icon: @icon))
78
+ @aria_label = aria("label", @system_arguments)
79
+ @aria_description = aria("description", @system_arguments)
80
+
81
+ @tooltip_arguments = {
82
+ for_id: @system_arguments[:id],
83
+ direction: tooltip_direction
84
+ }
85
+
86
+ # If we have both an `aria-label` and a `aria-description`, we create a `Tooltip` with the description type and keep the `aria-label` in the button.
87
+ # Otherwise, the `aria-label` is used as the tooltip text, which is the `aria-labelled-by` of the button, so we don't set it in the button.
88
+ if @aria_label.present? && @aria_description.present?
89
+ @system_arguments.delete(:"aria-description")
90
+ @system_arguments[:aria].delete(:description) if @system_arguments.include?(:aria)
91
+ @tooltip_arguments[:text] = @aria_description
92
+ @tooltip_arguments[:type] = :description
93
+ else
94
+ @system_arguments.delete(:"aria-label")
95
+ @system_arguments[:aria].delete(:label) if @system_arguments.include?(:aria)
96
+ @tooltip_arguments[:text] = @aria_label
97
+ @tooltip_arguments[:type] = :label
62
98
  end
63
99
  end
64
100
  end
@@ -17,6 +17,8 @@ module Primer
17
17
 
18
18
  # `Tooltip` that appears on mouse hover or keyboard focus over the link. Use tooltips sparingly and as a last resort.
19
19
  # **Important:** This tooltip defaults to `type: :description`. In a few scenarios, `type: :label` may be more appropriate.
20
+ # The tooltip will appear adjacent to the anchor element. Both the tooltip and the anchor will be nested
21
+ # under a positioning wrapper.
20
22
  # Consult the <%= link_to_component(Primer::Alpha::Tooltip) %> documentation for more information.
21
23
  #
22
24
  # @param type [Symbol] (:description) <%= one_of(Primer::Alpha::Tooltip::TYPE_OPTIONS) %>
@@ -43,9 +45,6 @@ module Primer
43
45
  # @example Without underline
44
46
  # <%= render(Primer::LinkComponent.new(href: "#", underline: false)) { "Link" } %>
45
47
  #
46
- # @example Span as link
47
- # <%= render(Primer::LinkComponent.new(tag: :span)) { "Span as a link" } %>
48
- #
49
48
  # @example With tooltip
50
49
  # @description
51
50
  # Use tooltips sparingly and as a last resort. Consult the <%= link_to_component(Primer::Alpha::Tooltip) %> documentation for more information.
@@ -82,8 +81,16 @@ module Primer
82
81
  end
83
82
 
84
83
  def call
85
- render(Primer::BaseComponent.new(**@system_arguments)) do
86
- content.to_s + tooltip.to_s
84
+ if tooltip.present?
85
+ render Primer::BaseComponent.new(tag: :span, position: :relative) do
86
+ render(Primer::BaseComponent.new(**@system_arguments)) do
87
+ content
88
+ end.to_s + tooltip.to_s
89
+ end
90
+ else
91
+ render(Primer::BaseComponent.new(**@system_arguments)) do
92
+ content
93
+ end
87
94
  end
88
95
  end
89
96
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ # :nodoc:
5
+ module FormHelper
6
+ def primer_form_with(**kwargs, &block)
7
+ form_with(**kwargs, skip_default_ids: false, builder: Primer::Forms::Builder, &block)
8
+ end
9
+ end
10
+ end
@@ -8,7 +8,7 @@ module Primer
8
8
  # join_style_arguments("width: 100%", "height: 100%") =>
9
9
  # "width: 100%;height: 100%"
10
10
  def join_style_arguments(*args)
11
- args.compact.join(";")
11
+ args.compact.map { |a| a.strip.chomp(";") }.join(";")
12
12
  end
13
13
  end
14
14
  end
@@ -7,15 +7,12 @@ module Primer
7
7
  class Classify
8
8
  # Handler for PrimerCSS utility classes loaded from utilities.rake
9
9
  class Utilities
10
- # Load the utilities.yml file.
11
- # Disabling because we want to load symbols, strings, and integers from the .yml file
12
- # rubocop:disable Security/YAMLLoad
13
- UTILITIES = YAML.load(
10
+ UTILITIES = YAML.safe_load(
14
11
  File.read(
15
12
  File.join(File.dirname(__FILE__), "./utilities.yml")
16
- )
13
+ ),
14
+ permitted_classes: [Symbol]
17
15
  ).freeze
18
- # rubocop:enable Security/YAMLLoad
19
16
 
20
17
  BREAKPOINTS = ["", "-sm", "-md", "-lg", "-xl"].freeze
21
18
 
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ # :nodoc:
5
+ class FormComponents
6
+ def self.from_input(input_klass)
7
+ Class.new(Primer::Component) do
8
+ @input_klass = input_klass
9
+
10
+ class << self
11
+ attr_reader :input_klass
12
+ end
13
+
14
+ def initialize(**system_arguments, &block)
15
+ @system_arguments = system_arguments
16
+ @block = block
17
+ end
18
+
19
+ def call
20
+ builder = Primer::Forms::Builder.new(
21
+ nil, nil, __vc_original_view_context, {}
22
+ )
23
+
24
+ input = self.class.input_klass.new(
25
+ builder: builder,
26
+ form: nil,
27
+ **@system_arguments,
28
+ &@block
29
+ )
30
+
31
+ input.to_component.render_in(__vc_original_view_context) { content }
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ module Primer
6
+ module Forms
7
+ # :nodoc:
8
+ module ActsAsComponent
9
+ # :nodoc:
10
+ module InstanceMethods
11
+ delegate :render, :content_tag, :output_buffer, :capture, to: :@view_context
12
+
13
+ def render_in(view_context, &block)
14
+ @view_context = view_context
15
+ before_render
16
+ perform_render(&block)
17
+ end
18
+
19
+ # :nocov:
20
+ def perform_render(&_block)
21
+ raise NotImplementedError, "subclasses must implement ##{__method__}."
22
+ end
23
+ # :nocov:
24
+
25
+ def before_render; end
26
+
27
+ # :nocov:
28
+ # rubocop:disable Naming/AccessorMethodName
29
+ def set_original_view_context(view_context)
30
+ @view_context = view_context
31
+ end
32
+ # rubocop:enable Naming/AccessorMethodName
33
+ # :nocov:
34
+ end
35
+
36
+ def self.extended(base)
37
+ base.include(InstanceMethods)
38
+ end
39
+
40
+ TemplateGlob = Struct.new(:glob_pattern, :method_name, :on_compile_callback)
41
+ TemplateParams = Struct.new(:source, :identifier, :type, :format)
42
+
43
+ attr_accessor :template_root_path
44
+
45
+ def renders_templates(glob_pattern, method_name = nil, &block)
46
+ template_globs << TemplateGlob.new(glob_pattern, method_name, block)
47
+ end
48
+ alias renders_template renders_templates
49
+
50
+ def compile!
51
+ # always recompile in dev
52
+ return if defined?(@compiled) && @compiled && !Rails.env.development?
53
+
54
+ template_globs.each do |template_glob|
55
+ compile_templates_in(template_glob)
56
+ end
57
+
58
+ @compiled = true
59
+ end
60
+
61
+ private
62
+
63
+ def template_globs
64
+ @template_globs ||= []
65
+ end
66
+
67
+ def compile_templates_in(template_glob)
68
+ pattern = if Pathname(template_glob.glob_pattern).absolute?
69
+ template_glob.glob_pattern
70
+ else
71
+ # skip compilation for anonymous form classes, as in tests
72
+ return unless template_root_path
73
+
74
+ File.join(template_root_path, template_glob.glob_pattern)
75
+ end
76
+
77
+ template_paths = Dir.glob(pattern)
78
+
79
+ raise "Cannot compile multiple templates with the same method name." if template_paths.size > 1 && template_glob.method_name
80
+
81
+ template_paths.each do |template_path|
82
+ method_name = template_glob.method_name
83
+ method_name ||= "render_#{File.basename(template_path).chomp('.html.erb')}"
84
+ define_template_method(template_path, method_name)
85
+ template_glob&.on_compile_callback&.call(template_path)
86
+ end
87
+ end
88
+
89
+ def define_template_method(template_path, method_name)
90
+ # rubocop:disable Style/DocumentDynamicEvalDefinition
91
+ # rubocop:disable Style/EvalWithLocation
92
+ class_eval <<-RUBY, template_path, 0
93
+ private def #{method_name}
94
+ capture { #{compile_template(template_path)} }
95
+ end
96
+ RUBY
97
+ # rubocop:enable Style/EvalWithLocation
98
+ # rubocop:enable Style/DocumentDynamicEvalDefinition
99
+ end
100
+
101
+ def compile_template(path)
102
+ handler = ActionView::Template.handler_for_extension("erb")
103
+ template = File.read(path)
104
+ template_params = TemplateParams.new({
105
+ source: template,
106
+ identifier: __FILE__,
107
+ type: "text/html",
108
+ format: "text/html"
109
+ })
110
+
111
+ # change @output_buffer ivar to output_buffer method call
112
+ BufferRewriter.rewrite(
113
+ handler.call(template_params, template)
114
+ )
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,8 @@
1
+ <%= render(SpacingWrapper.new) do %>
2
+ <% inputs.each do |input| %>
3
+ <%= render(input.to_component) %>
4
+ <% end %>
5
+ <% end %>
6
+ <% if after_content? %>
7
+ <%= render_after_content %>
8
+ <% end %>