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
@@ -5,7 +5,7 @@ module Primer
5
5
  class AutoComplete
6
6
  # Use `AutoCompleteItem` to list results of an auto-completed search.
7
7
  class Item < Primer::Component
8
- status :alpha
8
+ status :deprecated
9
9
 
10
10
  # @example Default
11
11
  # <%= render(Primer::Alpha::AutoComplete.new(label_text: "Fruits", src: "/auto_complete", input_id: "fruits-input--custom-results", list_id: "fruits-popup--custom-results")) do |c| %>
@@ -12,7 +12,7 @@ module Primer
12
12
  # However, please note that a visible label should almost always
13
13
  # be used unless there is compelling reason not to. A placeholder is not a label.
14
14
  class AutoComplete < Primer::Component
15
- status :alpha
15
+ status :deprecated
16
16
 
17
17
  # Customizable results list.
18
18
  #
@@ -63,7 +63,7 @@ module Primer
63
63
  end
64
64
 
65
65
  def call
66
- render(Primer::BaseButton.new(**@system_arguments)) { content }
66
+ render(Primer::Beta::BaseButton.new(**@system_arguments)) { content }
67
67
  end
68
68
  end
69
69
  end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Alpha
5
+ TextField = Primer::FormComponents.from_input(Primer::Forms::Dsl::TextFieldInput)
6
+
7
+ # A text field suitable for use outside a form. For a text field input suitable for use
8
+ # within an HTML form, see the `Primer::Forms` namespace.
9
+ class TextField < Primer::Component
10
+ status :alpha
11
+
12
+ # @!method initialize
13
+ #
14
+ # @example Default
15
+ # <%= render(Primer::Alpha::TextField.new(name: :first_name, label: "First name")) %>
16
+ #
17
+ # @example With a clear button
18
+ # <%= render(
19
+ # Primer::Alpha::TextField.new(
20
+ # name: :first_name,
21
+ # label: "First name",
22
+ # show_clear_button: true
23
+ # )
24
+ # ) %>
25
+ #
26
+ # @example Full width
27
+ # <%= render(
28
+ # Primer::Alpha::TextField.new(
29
+ # name: :first_name,
30
+ # label: "First name",
31
+ # full_width: true
32
+ # )
33
+ # ) %>
34
+ #
35
+ # @example Disabled
36
+ # <%= render(
37
+ # Primer::Alpha::TextField.new(
38
+ # name: :first_name,
39
+ # label: "First name",
40
+ # disabled: true
41
+ # )
42
+ # ) %>
43
+ #
44
+ # @example Invalid
45
+ # <%= render(
46
+ # Primer::Alpha::TextField.new(
47
+ # name: :first_name,
48
+ # label: "First name",
49
+ # invalid: true
50
+ # )
51
+ # ) %>
52
+ #
53
+ # @example With a leading visual
54
+ # <%= render(
55
+ # Primer::Alpha::TextField.new(
56
+ # name: :first_name,
57
+ # label: "First name",
58
+ # leading_visual: {
59
+ # icon: :person
60
+ # }
61
+ # )
62
+ # ) %>
63
+ #
64
+ # @example With a caption
65
+ # <%= render(
66
+ # Primer::Alpha::TextField.new(
67
+ # name: :first_name,
68
+ # label: "First name",
69
+ # caption: "What your friends call you"
70
+ # )
71
+ # ) %>
72
+ #
73
+ # @example With a validation message
74
+ # <%= render(
75
+ # Primer::Alpha::TextField.new(
76
+ # name: :first_name,
77
+ # label: "First name",
78
+ # validation_message: "Hmm, that doesn't look right"
79
+ # )
80
+ # ) %>
81
+ #
82
+ # @param name [String] Value for the HTML name attribute.
83
+ # @param id [String] Value for the HTML id attribute.
84
+ # @param class [String] A list of CSS classes to add to the input. Exists for compatibility with Rails form builders.
85
+ # @param classes [String] A list of CSS classes to add to the input. Combined with the `:class` argument.
86
+ # @param caption [String] Caption text to render below the input.
87
+ # @param label [String] Label text displayed above the input.
88
+ # @param visually_hide_label [Boolean] Whether or not to visually hide the label. If `true` the label will be hidden visually but still available to screen readers.
89
+ # @param size [Symbol] The size of the field. <%= one_of(Primer::Forms::Dsl::Input::SIZE_OPTIONS) %>
90
+ # @param show_clear_button [Boolean] Whether or not to include a clear button inside the input that clears the input's contents when clicked.
91
+ # @param clear_button_id [String] The HTML id attribute of the clear button.
92
+ # @param full_width [Boolean] Controls whether or not the input takes the full width of its container.
93
+ # @param disabled [Boolean] Whether or not the input should accept keyboard and mouse input.
94
+ # @param invalid [Boolean] If `true`, renders the input in a visually invalid state.
95
+ # @param placeholder [String] Placeholder text.
96
+ # @param inset [Boolean] If `true`, renders the input in a visually inset state.
97
+ # @param monospace [Boolean] If `true`, uses a monospace font for the input field.
98
+ # @param leading_visual [Hash] Renders a leading visual icon before the text field's cursor. The hash will be passed to Primer's [Octicon component](https://primer.style/view-components/components/octicon).
99
+ # @param validation_message [String] A validation message to display beneath the input. Implicitly sets `invalid` to `true`.
100
+ # @param label_arguments [Hash] System arugments passed to the Rails builder's `#label` method. These arguments will appear as HTML attributes on the `<label>` tag.
101
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
102
+ # @param block [Proc] Unused.
103
+ end
104
+ end
105
+ end
@@ -9,11 +9,13 @@ declare class ToolTipElement extends HTMLElement {
9
9
  get direction(): Direction;
10
10
  set direction(value: Direction);
11
11
  get control(): HTMLElement | null;
12
+ set visibilityHidden(value: true | false);
13
+ get visibilityHidden(): true | false;
12
14
  connectedCallback(): void;
13
15
  disconnectedCallback(): void;
14
16
  handleEvent(event: Event): void;
15
17
  static observedAttributes: string[];
16
- attributeChangedCallback(name: string): void;
18
+ attributeChangedCallback(name: string, oldValue: string, newValue: string): void;
17
19
  }
18
20
  declare global {
19
21
  interface Window {
@@ -176,6 +176,12 @@ class ToolTipElement extends HTMLElement {
176
176
  get control() {
177
177
  return this.ownerDocument.getElementById(this.htmlFor);
178
178
  }
179
+ set visibilityHidden(value) {
180
+ this.style.visibility = value ? 'hidden' : '';
181
+ }
182
+ get visibilityHidden() {
183
+ return this.style.visibility === 'hidden';
184
+ }
179
185
  connectedCallback() {
180
186
  var _a;
181
187
  if (!this.shadowRoot) {
@@ -188,7 +194,7 @@ class ToolTipElement extends HTMLElement {
188
194
  <slot></slot>
189
195
  `;
190
196
  }
191
- this.hidden = true;
197
+ this.visibilityHidden = true;
192
198
  __classPrivateFieldSet(this, _ToolTipElement_allowUpdatePosition, true, "f");
193
199
  if (!this.id) {
194
200
  this.id = `tooltip-${Date.now()}-${(Math.random() * 10000).toFixed(0)}`;
@@ -216,27 +222,28 @@ class ToolTipElement extends HTMLElement {
216
222
  return;
217
223
  // Ensures that tooltip stays open when hovering between tooltip and element
218
224
  // WCAG Success Criterion 1.4.13 Hoverable
219
- if ((event.type === 'mouseenter' || event.type === 'focus') && this.hidden) {
220
- this.hidden = false;
225
+ if ((event.type === 'mouseenter' || event.type === 'focus') && this.visibilityHidden) {
226
+ this.visibilityHidden = false;
221
227
  }
222
228
  else if (event.type === 'blur') {
223
- this.hidden = true;
229
+ this.visibilityHidden = true;
224
230
  }
225
231
  else if (event.type === 'mouseleave' &&
226
232
  event.relatedTarget !== this.control &&
227
233
  event.relatedTarget !== this) {
228
- this.hidden = true;
234
+ this.visibilityHidden = true;
229
235
  }
230
- else if (event.type === 'keydown' && event.key === 'Escape' && !this.hidden) {
231
- this.hidden = true;
236
+ else if (event.type === 'keydown' && event.key === 'Escape' && !this.visibilityHidden) {
237
+ this.visibilityHidden = true;
232
238
  }
233
239
  }
234
- attributeChangedCallback(name) {
240
+ attributeChangedCallback(name, oldValue, newValue) {
235
241
  if (name === 'id' || name === 'data-type') {
236
242
  if (!this.id || !this.control)
237
243
  return;
238
244
  if (this.type === 'label') {
239
245
  this.control.setAttribute('aria-labelledby', this.id);
246
+ this.setAttribute('aria-hidden', 'true');
240
247
  }
241
248
  else {
242
249
  let describedBy = this.control.getAttribute('aria-describedby');
@@ -244,7 +251,7 @@ class ToolTipElement extends HTMLElement {
244
251
  this.control.setAttribute('aria-describedby', describedBy);
245
252
  }
246
253
  }
247
- else if (this.isConnected && name === 'hidden') {
254
+ else if (this.isConnected && name === 'style' && (oldValue && oldValue.includes('visibility')) || (newValue && newValue.includes('visibility'))) {
248
255
  __classPrivateFieldGet(this, _ToolTipElement_instances, "m", _ToolTipElement_update).call(this);
249
256
  }
250
257
  else if (name === 'data-direction') {
@@ -286,21 +293,21 @@ class ToolTipElement extends HTMLElement {
286
293
  }
287
294
  }
288
295
  _ToolTipElement_abortController = new WeakMap(), _ToolTipElement_align = new WeakMap(), _ToolTipElement_side = new WeakMap(), _ToolTipElement_allowUpdatePosition = new WeakMap(), _ToolTipElement_instances = new WeakSet(), _ToolTipElement_update = function _ToolTipElement_update() {
289
- if (this.hidden) {
296
+ if (this.visibilityHidden) {
290
297
  this.classList.remove(TOOLTIP_OPEN_CLASS, ...DIRECTION_CLASSES);
291
298
  }
292
299
  else {
293
300
  this.classList.add(TOOLTIP_OPEN_CLASS);
294
301
  for (const tooltip of this.ownerDocument.querySelectorAll(this.tagName)) {
295
302
  if (tooltip !== this)
296
- tooltip.hidden = true;
303
+ tooltip.visibilityHidden = true;
297
304
  }
298
305
  __classPrivateFieldGet(this, _ToolTipElement_instances, "m", _ToolTipElement_updatePosition).call(this);
299
306
  }
300
307
  }, _ToolTipElement_updatePosition = function _ToolTipElement_updatePosition() {
301
308
  if (!this.control)
302
309
  return;
303
- if (!__classPrivateFieldGet(this, _ToolTipElement_allowUpdatePosition, "f") || this.hidden)
310
+ if (!__classPrivateFieldGet(this, _ToolTipElement_allowUpdatePosition, "f") || this.visibilityHidden)
304
311
  return;
305
312
  const TOOLTIP_OFFSET = 10;
306
313
  this.style.left = `0px`; // Ensures we have reliable tooltip width in `getAnchoredPosition`
@@ -344,7 +351,7 @@ _ToolTipElement_abortController = new WeakMap(), _ToolTipElement_align = new Wea
344
351
  }
345
352
  this.classList.add(`tooltip-${direction}`);
346
353
  };
347
- ToolTipElement.observedAttributes = ['data-type', 'data-direction', 'id', 'hidden'];
354
+ ToolTipElement.observedAttributes = ['data-type', 'data-direction', 'id', 'style'];
348
355
  if (!window.customElements.get('tool-tip')) {
349
356
  window.ToolTipElement = ToolTipElement;
350
357
  window.customElements.define('tool-tip', ToolTipElement);
@@ -176,6 +176,14 @@ class ToolTipElement extends HTMLElement {
176
176
  return this.ownerDocument.getElementById(this.htmlFor)
177
177
  }
178
178
 
179
+ set visibilityHidden(value: true | false) {
180
+ this.style.visibility = value ? 'hidden' : ''
181
+ }
182
+
183
+ get visibilityHidden() {
184
+ return this.style.visibility === 'hidden'
185
+ }
186
+
179
187
  connectedCallback() {
180
188
  if (!this.shadowRoot) {
181
189
  const shadow = this.attachShadow({mode: 'open'})
@@ -187,7 +195,7 @@ class ToolTipElement extends HTMLElement {
187
195
  <slot></slot>
188
196
  `
189
197
  }
190
- this.hidden = true
198
+ this.visibilityHidden = true
191
199
  this.#allowUpdatePosition = true
192
200
 
193
201
  if (!this.id) {
@@ -220,46 +228,47 @@ class ToolTipElement extends HTMLElement {
220
228
 
221
229
  // Ensures that tooltip stays open when hovering between tooltip and element
222
230
  // WCAG Success Criterion 1.4.13 Hoverable
223
- if ((event.type === 'mouseenter' || event.type === 'focus') && this.hidden) {
224
- this.hidden = false
231
+ if ((event.type === 'mouseenter' || event.type === 'focus') && this.visibilityHidden) {
232
+ this.visibilityHidden = false
225
233
  } else if (event.type === 'blur') {
226
- this.hidden = true
234
+ this.visibilityHidden = true
227
235
  } else if (
228
236
  event.type === 'mouseleave' &&
229
237
  (event as MouseEvent).relatedTarget !== this.control &&
230
238
  (event as MouseEvent).relatedTarget !== this
231
239
  ) {
232
- this.hidden = true
233
- } else if (event.type === 'keydown' && (event as KeyboardEvent).key === 'Escape' && !this.hidden) {
234
- this.hidden = true
240
+ this.visibilityHidden = true
241
+ } else if (event.type === 'keydown' && (event as KeyboardEvent).key === 'Escape' && !this.visibilityHidden) {
242
+ this.visibilityHidden = true
235
243
  }
236
244
  }
237
245
 
238
- static observedAttributes = ['data-type', 'data-direction', 'id', 'hidden']
246
+ static observedAttributes = ['data-type', 'data-direction', 'id', 'style']
239
247
 
240
248
  #update() {
241
- if (this.hidden) {
249
+ if (this.visibilityHidden) {
242
250
  this.classList.remove(TOOLTIP_OPEN_CLASS, ...DIRECTION_CLASSES)
243
251
  } else {
244
252
  this.classList.add(TOOLTIP_OPEN_CLASS)
245
- for (const tooltip of this.ownerDocument.querySelectorAll<HTMLElement>(this.tagName)) {
246
- if (tooltip !== this) tooltip.hidden = true
253
+ for (const tooltip of this.ownerDocument.querySelectorAll<ToolTipElement>(this.tagName)) {
254
+ if (tooltip !== this) tooltip.visibilityHidden = true
247
255
  }
248
256
  this.#updatePosition()
249
257
  }
250
258
  }
251
259
 
252
- attributeChangedCallback(name: string) {
260
+ attributeChangedCallback(name: string, oldValue: string, newValue: string) {
253
261
  if (name === 'id' || name === 'data-type') {
254
262
  if (!this.id || !this.control) return
255
263
  if (this.type === 'label') {
256
264
  this.control.setAttribute('aria-labelledby', this.id)
265
+ this.setAttribute('aria-hidden', 'true')
257
266
  } else {
258
267
  let describedBy = this.control.getAttribute('aria-describedby')
259
268
  describedBy ? (describedBy = `${describedBy} ${this.id}`) : (describedBy = this.id)
260
269
  this.control.setAttribute('aria-describedby', describedBy)
261
270
  }
262
- } else if (this.isConnected && name === 'hidden') {
271
+ } else if (this.isConnected && name === 'style' && (oldValue && oldValue.includes('visibility')) || (newValue && newValue.includes('visibility'))) {
263
272
  this.#update()
264
273
  } else if (name === 'data-direction') {
265
274
  this.classList.remove(...DIRECTION_CLASSES)
@@ -294,7 +303,7 @@ class ToolTipElement extends HTMLElement {
294
303
 
295
304
  #updatePosition() {
296
305
  if (!this.control) return
297
- if (!this.#allowUpdatePosition || this.hidden) return
306
+ if (!this.#allowUpdatePosition || this.visibilityHidden) return
298
307
 
299
308
  const TOOLTIP_OFFSET = 10
300
309
 
@@ -88,8 +88,8 @@ module Primer
88
88
 
89
89
  @text = text
90
90
  @system_arguments = system_arguments
91
- @system_arguments[:hidden] = true
92
91
  @system_arguments[:tag] = :"tool-tip"
92
+ @system_arguments[:style] = join_style_arguments(@system_arguments[:style], "visibility: hidden")
93
93
  @system_arguments[:for] = for_id
94
94
  @system_arguments[:"data-direction"] = fetch_or_fallback(DIRECTION_OPTIONS, direction, DIRECTION_DEFAULT).to_s
95
95
  @system_arguments[:"data-type"] = fetch_or_fallback(TYPE_OPTIONS, type, TYPE_FALLBACK).to_s
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Beta
5
+ # Use `BaseButton` to render an unstyled `<button>` tag that can be customized.
6
+ class BaseButton < Primer::Component
7
+ status :beta
8
+
9
+ DEFAULT_TAG = :button
10
+ TAG_OPTIONS = [DEFAULT_TAG, :a, :summary].freeze
11
+
12
+ DEFAULT_TYPE = :button
13
+ TYPE_OPTIONS = [DEFAULT_TYPE, :reset, :submit].freeze
14
+
15
+ # @example Block
16
+ # <%= render(Primer::Beta::BaseButton.new(block: :true)) { "Block" } %>
17
+ # <%= render(Primer::Beta::BaseButton.new(block: :true, scheme: :primary)) { "Primary block" } %>
18
+ #
19
+ # @param tag [Symbol] <%= one_of(Primer::Beta::BaseButton::TAG_OPTIONS) %>
20
+ # @param type [Symbol] <%= one_of(Primer::Beta::BaseButton::TYPE_OPTIONS) %>
21
+ # @param block [Boolean] Whether button is full-width with `display: block`.
22
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
23
+ def initialize(
24
+ tag: DEFAULT_TAG,
25
+ type: DEFAULT_TYPE,
26
+ block: false,
27
+ **system_arguments
28
+ )
29
+ @system_arguments = system_arguments
30
+ @system_arguments[:tag] = fetch_or_fallback(TAG_OPTIONS, tag, DEFAULT_TAG)
31
+
32
+ @system_arguments[:type] = fetch_or_fallback(TYPE_OPTIONS, type, DEFAULT_TYPE) if @system_arguments[:tag] == :button
33
+
34
+ @system_arguments[:classes] = class_names(
35
+ system_arguments[:classes],
36
+ "btn-block" => block
37
+ )
38
+ end
39
+
40
+ def call
41
+ render(Primer::BaseComponent.new(**@system_arguments)) { content }
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ Primer::BaseButton = Primer::Beta::BaseButton
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Primer
4
- module Alpha
5
- module BorderBox
4
+ module Beta
5
+ class BorderBox
6
6
  # `BorderBox::Header` is used inside `BorderBox` to render its header slot.
7
7
  #
8
8
  # @accessibility When using `header.title`, set `tag` to one of `h1`, `h2`, `h3`, etc. based on what is appropriate for the page context. <%= link_to_heading_practices %>
@@ -12,7 +12,7 @@ module Primer
12
12
 
13
13
  # Optional Title.
14
14
  #
15
- # @param tag [Symbol] <%= one_of(Primer::Alpha::BorderBox::Header::TITLE_TAG_OPTIONS) %>
15
+ # @param tag [Symbol] <%= one_of(Primer::Beta::BorderBox::Header::TITLE_TAG_OPTIONS) %>
16
16
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
17
17
  renders_one :title, lambda { |tag:, **system_arguments|
18
18
  system_arguments[:tag] = fetch_or_fallback(TITLE_TAG_OPTIONS, tag, TITLE_TAG_FALLBACK)
@@ -24,14 +24,14 @@ module Primer
24
24
  Primer::BaseComponent.new(**system_arguments)
25
25
  }
26
26
 
27
- # @example default use case
27
+ # @example Default
28
28
  #
29
- # <%= render(Primer::Alpha::BorderBox::Header.new) do %>
29
+ # <%= render(Primer::Beta::BorderBox::Header.new) do %>
30
30
  # Header
31
31
  # <% end %>
32
32
  #
33
33
  # @example with title
34
- # <%= render(Primer::Alpha::BorderBox::Header.new) do |h| %>
34
+ # <%= render(Primer::Beta::BorderBox::Header.new) do |h| %>
35
35
  # <% h.title(tag: :h3) do %>I am a title<% end %>
36
36
  # <% end %>
37
37
  #
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Beta
5
+ # `BorderBox` is a Box component with a border.
6
+ class BorderBox < Primer::Component
7
+ status :beta
8
+
9
+ DEFAULT_PADDING = :default
10
+ PADDING_MAPPINGS = {
11
+ DEFAULT_PADDING => "",
12
+ :condensed => "Box--condensed",
13
+ :spacious => "Box--spacious"
14
+ }.freeze
15
+ PADDING_SUGGESTION = "Perhaps you could consider using :padding options of #{PADDING_MAPPINGS.keys.to_sentence}?"
16
+
17
+ DEFAULT_ROW_SCHEME = :default
18
+ ROW_SCHEME_MAPPINGS = {
19
+ DEFAULT_ROW_SCHEME => "",
20
+ :neutral => "Box-row--gray",
21
+ :info => "Box-row--blue",
22
+ :warning => "Box-row--yellow"
23
+ }.freeze
24
+
25
+ # Optional Header.
26
+ #
27
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
28
+ # @accessibility
29
+ # When using header.title, the recommended tag is a heading tag, such as h1, h2, h3, etc.
30
+ renders_one :header, "Primer::Beta::BorderBox::Header"
31
+
32
+ # Optional Body.
33
+ #
34
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
35
+ renders_one :body, lambda { |**system_arguments|
36
+ system_arguments[:tag] = :div
37
+ system_arguments[:classes] = class_names(
38
+ "Box-body",
39
+ system_arguments[:classes]
40
+ )
41
+
42
+ Primer::BaseComponent.new(**system_arguments)
43
+ }
44
+
45
+ # Optional Footer.
46
+ #
47
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
48
+ renders_one :footer, lambda { |**system_arguments|
49
+ system_arguments[:tag] = :div
50
+ system_arguments[:classes] = class_names(
51
+ "Box-footer",
52
+ system_arguments[:classes]
53
+ )
54
+
55
+ Primer::BaseComponent.new(**system_arguments)
56
+ }
57
+
58
+ # Use Rows to add rows with borders and maintain the same padding.
59
+ #
60
+ # @param scheme [Symbol] Color scheme. <%= one_of(Primer::Beta::BorderBox::ROW_SCHEME_MAPPINGS.keys) %>
61
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
62
+ renders_many :rows, lambda { |scheme: DEFAULT_ROW_SCHEME, **system_arguments|
63
+ system_arguments[:tag] = :li
64
+ system_arguments[:classes] = class_names(
65
+ "Box-row",
66
+ ROW_SCHEME_MAPPINGS[fetch_or_fallback(ROW_SCHEME_MAPPINGS.keys, scheme, DEFAULT_ROW_SCHEME)],
67
+ system_arguments[:classes]
68
+ )
69
+
70
+ Primer::BaseComponent.new(**system_arguments)
71
+ }
72
+
73
+ # @example Header with title, body, rows, and footer
74
+ # <%= render(Primer::Beta::BorderBox.new) do |component| %>
75
+ # <% component.header do |h| %>
76
+ # <% h.title(tag: :h2) do %>
77
+ # Header
78
+ # <% end %>
79
+ # <% end %>
80
+ # <% component.body do %>
81
+ # Body
82
+ # <% end %>
83
+ # <% component.row do %>
84
+ # <% if true %>
85
+ # Row one
86
+ # <% end %>
87
+ # <% end %>
88
+ # <% component.row do %>
89
+ # Row two
90
+ # <% end %>
91
+ # <% component.footer do %>
92
+ # Footer
93
+ # <% end %>
94
+ # <% end %>
95
+ #
96
+ # @example Padding density
97
+ # <%= render(Primer::Beta::BorderBox.new(padding: :condensed)) do |component| %>
98
+ # <% component.header do %>
99
+ # Header
100
+ # <% end %>
101
+ # <% component.body do %>
102
+ # Body
103
+ # <% end %>
104
+ # <% component.row do %>
105
+ # Row two
106
+ # <% end %>
107
+ # <% component.footer do %>
108
+ # Footer
109
+ # <% end %>
110
+ # <% end %>
111
+ #
112
+ # @example Row colors
113
+ # <%= render(Primer::Beta::BorderBox.new) do |component| %>
114
+ # <% component.row do %>
115
+ # Default
116
+ # <% end %>
117
+ # <% component.row(scheme: :neutral) do %>
118
+ # Neutral
119
+ # <% end %>
120
+ # <% component.row(scheme: :info) do %>
121
+ # Info
122
+ # <% end %>
123
+ # <% component.row(scheme: :warning) do %>
124
+ # Warning
125
+ # <% end %>
126
+ # <% end %>
127
+ #
128
+ # @param padding [Symbol] <%= one_of(Primer::Beta::BorderBox::PADDING_MAPPINGS.keys) %>
129
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
130
+ def initialize(padding: DEFAULT_PADDING, **system_arguments)
131
+ @system_arguments = deny_tag_argument(**system_arguments)
132
+ @system_arguments[:tag] = :div
133
+ @system_arguments[:classes] = class_names(
134
+ "Box",
135
+ PADDING_MAPPINGS[fetch_or_fallback(PADDING_MAPPINGS.keys, padding, DEFAULT_PADDING)],
136
+ system_arguments[:classes]
137
+ )
138
+
139
+ @system_arguments[:system_arguments_denylist] = { [:p, :pt, :pb, :pr, :pl] => PADDING_SUGGESTION }
140
+ end
141
+
142
+ def render?
143
+ rows.any? || header.present? || body.present? || footer.present?
144
+ end
145
+ end
146
+ end
147
+ end
@@ -7,21 +7,16 @@
7
7
  <% elsif @image_src.present? && @image_alt.present? %>
8
8
  <%= image_tag @image_src.to_s, class: "mb-3", size: "56x56", alt: @image_alt.to_s %>
9
9
  <% end %>
10
-
11
10
  <% if @title.present? %>
12
11
  <%= render Primer::BaseComponent.new(tag: @title_tag, mb: 1) do %><%= @title %><% end %>
13
12
  <% end %>
14
-
15
13
  <% if @description.present? %>
16
14
  <p><%= @description %></p>
17
15
  <% end %>
18
-
19
16
  <%= content %>
20
-
21
17
  <% if @button_text.present? && @button_url.present? %>
22
18
  <a class="btn <%= @button_classes %>" href="<%= @button_url %>"><%= @button_text %></a>
23
19
  <% end %>
24
-
25
20
  <% if @link_text.present? && @link_url.present? %>
26
21
  <p>
27
22
  <%= link_to @link_url.to_s do %><%= @link_text %><% end %>