openproject-primer_view_components 0.80.2 → 0.81.0

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -22
  3. data/README.md +20 -1
  4. data/app/assets/javascripts/components/primer/primer.d.ts +1 -0
  5. data/app/assets/javascripts/lib/primer/forms/character_counter.d.ts +41 -0
  6. data/app/assets/javascripts/lib/primer/forms/primer_text_area.d.ts +13 -0
  7. data/app/assets/javascripts/lib/primer/forms/primer_text_field.d.ts +2 -0
  8. data/app/assets/javascripts/primer_view_components.js +1 -1
  9. data/app/assets/javascripts/primer_view_components.js.map +1 -1
  10. data/app/assets/styles/primer_view_components.css +1 -1
  11. data/app/assets/styles/primer_view_components.css.map +1 -1
  12. data/app/components/primer/alpha/action_list/item.rb +2 -1
  13. data/app/components/primer/alpha/auto_complete/auto_complete.html.erb +8 -6
  14. data/app/components/primer/alpha/select_panel.rb +1 -1
  15. data/app/components/primer/alpha/select_panel_element.js +1 -1
  16. data/app/components/primer/alpha/select_panel_element.ts +1 -1
  17. data/app/components/primer/alpha/stack.rb +1 -0
  18. data/app/components/primer/alpha/tab_nav.css +1 -1
  19. data/app/components/primer/alpha/tab_nav.css.json +1 -0
  20. data/app/components/primer/alpha/tab_nav.css.map +1 -1
  21. data/app/components/primer/alpha/tab_nav.pcss +7 -1
  22. data/app/components/primer/alpha/text_area.rb +1 -0
  23. data/app/components/primer/alpha/text_field.rb +1 -0
  24. data/app/components/primer/alpha/toggle_switch.html.erb +2 -2
  25. data/app/components/primer/alpha/tool_tip.js +12 -5
  26. data/app/components/primer/alpha/tool_tip.ts +14 -5
  27. data/app/components/primer/beta/auto_complete/item.html.erb +5 -4
  28. data/app/components/primer/beta/avatar.rb +4 -0
  29. data/app/components/primer/beta/avatar_stack.rb +6 -0
  30. data/app/components/primer/beta/blankslate.css +1 -1
  31. data/app/components/primer/beta/blankslate.css.map +1 -1
  32. data/app/components/primer/beta/blankslate.pcss +2 -0
  33. data/app/components/primer/beta/spinner.html.erb +2 -2
  34. data/app/components/primer/primer.d.ts +1 -0
  35. data/app/components/primer/primer.js +1 -0
  36. data/app/components/primer/primer.ts +1 -0
  37. data/app/controllers/primer/view_components/toggle_switch_controller.rb +2 -2
  38. data/app/forms/check_box_with_nested_form.rb +9 -5
  39. data/app/forms/text_area_with_character_limit_form.rb +13 -0
  40. data/app/forms/text_field_with_character_limit_form.rb +13 -0
  41. data/app/lib/primer/forms/caption.html.erb +16 -9
  42. data/app/lib/primer/forms/character_counter.d.ts +41 -0
  43. data/app/lib/primer/forms/character_counter.js +114 -0
  44. data/app/lib/primer/forms/character_counter.ts +129 -0
  45. data/app/lib/primer/forms/check_box.rb +28 -0
  46. data/app/lib/primer/forms/dsl/input.rb +23 -0
  47. data/app/lib/primer/forms/dsl/multi_input.rb +3 -1
  48. data/app/lib/primer/forms/dsl/text_area_input.rb +12 -1
  49. data/app/lib/primer/forms/dsl/text_field_input.rb +11 -1
  50. data/app/lib/primer/forms/form_control.html.erb +2 -1
  51. data/app/lib/primer/forms/primer_text_area.d.ts +13 -0
  52. data/app/lib/primer/forms/primer_text_area.js +53 -0
  53. data/app/lib/primer/forms/primer_text_area.ts +37 -0
  54. data/app/lib/primer/forms/primer_text_field.d.ts +2 -0
  55. data/app/lib/primer/forms/primer_text_field.js +16 -2
  56. data/app/lib/primer/forms/primer_text_field.ts +16 -3
  57. data/app/lib/primer/forms/text_area.html.erb +6 -4
  58. data/app/lib/primer/forms/text_field.html.erb +1 -1
  59. data/app/lib/primer/forms/text_field.rb +8 -0
  60. data/lib/primer/accessibility.rb +9 -3
  61. data/lib/primer/view_components/engine.rb +1 -4
  62. data/lib/primer/view_components/version.rb +2 -2
  63. data/previews/primer/alpha/action_menu_preview/submitting_forms.html.erb +1 -1
  64. data/previews/primer/alpha/form_control_preview/playground.html.erb +4 -2
  65. data/previews/primer/alpha/octicon_symbols_preview/playground.html.erb +1 -1
  66. data/previews/primer/alpha/overlay_preview.rb +0 -25
  67. data/previews/primer/alpha/text_area_preview.rb +29 -8
  68. data/previews/primer/alpha/text_field_preview.rb +34 -4
  69. data/previews/primer/alpha/toggle_switch_preview.rb +14 -14
  70. data/previews/primer/alpha/tree_view_preview/loading_failure.html.erb +53 -1
  71. data/previews/primer/alpha/tree_view_preview/stress_test.html.erb +43 -0
  72. data/previews/primer/beta/button_preview/all_schemes.html.erb +1 -1
  73. data/previews/primer/beta/button_preview/invisible_all_visuals.html.erb +1 -1
  74. data/previews/primer/beta/button_preview/summary_as_button.html.erb +10 -1
  75. data/previews/primer/forms_preview/text_area_with_character_limit_form.html.erb +3 -0
  76. data/previews/primer/forms_preview/text_field_with_character_limit_form.html.erb +3 -0
  77. data/previews/primer/forms_preview.rb +6 -0
  78. data/static/arguments.json +13 -1
  79. data/static/form_previews.json +10 -0
  80. data/static/info_arch.json +106 -15
  81. data/static/previews.json +93 -14
  82. metadata +15 -2
@@ -80,6 +80,14 @@ module Primer
80
80
  Primer::Beta::Truncate.new(**truncate_arguments).with_content(text)
81
81
  end
82
82
  end
83
+
84
+ def character_limit_validation_arguments
85
+ {
86
+ class: "FormControl-inlineValidation",
87
+ id: @input.character_limit_validation_id,
88
+ hidden: true
89
+ }
90
+ end
83
91
  end
84
92
  end
85
93
  end
@@ -32,6 +32,10 @@ module Primer
32
32
  per_component: {
33
33
  Primer::Alpha::ToggleSwitch => {
34
34
  all_scenarios: %i[button-name]
35
+ },
36
+
37
+ Primer::Alpha::MultiInput => {
38
+ visually_hide_label: %i[label-title-only]
35
39
  }
36
40
  }
37
41
  }
@@ -46,6 +50,8 @@ module Primer
46
50
  end
47
51
 
48
52
  def axe_rules_to_skip(component: nil, scenario_name: nil, flatten: false)
53
+ scenario_key = scenario_name.is_a?(String) ? scenario_name.to_sym : scenario_name
54
+
49
55
  to_skip = {
50
56
  wont_fix: Set.new(AXE_RULES_TO_SKIP.dig(:wont_fix, :global) || []),
51
57
  will_fix: Set.new(AXE_RULES_TO_SKIP.dig(:will_fix, :global) || [])
@@ -55,9 +61,9 @@ module Primer
55
61
  to_skip[:wont_fix].merge(AXE_RULES_TO_SKIP.dig(:wont_fix, :per_component, component, :all_scenarios) || [])
56
62
  to_skip[:will_fix].merge(AXE_RULES_TO_SKIP.dig(:will_fix, :per_component, component, :all_scenarios) || [])
57
63
 
58
- if scenario_name
59
- to_skip[:wont_fix].merge(AXE_RULES_TO_SKIP.dig(:wont_fix, :per_component, component, scenario_name) || [])
60
- to_skip[:will_fix].merge(AXE_RULES_TO_SKIP.dig(:will_fix, :per_component, component, scenario_name) || [])
64
+ if scenario_key
65
+ to_skip[:wont_fix].merge(AXE_RULES_TO_SKIP.dig(:wont_fix, :per_component, component, scenario_key) || [])
66
+ to_skip[:will_fix].merge(AXE_RULES_TO_SKIP.dig(:will_fix, :per_component, component, scenario_key) || [])
61
67
  end
62
68
  end
63
69
 
@@ -48,10 +48,7 @@ module Primer
48
48
 
49
49
  initializer "primer.forms.helpers" do
50
50
  ActiveSupport.on_load :action_controller_base do
51
- begin
52
- require "primer/form_helper"
53
- rescue LoadError
54
- end
51
+ require_relative "../../../app/helpers/primer/form_helper"
55
52
 
56
53
  helper Primer::FormHelper
57
54
 
@@ -5,8 +5,8 @@ module Primer
5
5
  module ViewComponents
6
6
  module VERSION
7
7
  MAJOR = 0
8
- MINOR = 80
9
- PATCH = 2
8
+ MINOR = 81
9
+ PATCH = 0
10
10
 
11
11
  STRING = [MAJOR, MINOR, PATCH].join(".")
12
12
  end
@@ -8,7 +8,7 @@
8
8
  }) do |item| %>
9
9
  <% if params["time"] %>
10
10
  <% item.with_description.with_content("Last submitted at #{params["time"]}") %>
11
- <% elsif %>
11
+ <% else %>
12
12
  <% item.with_description.with_content("Not yet submitted") %>
13
13
  <% end %>
14
14
  <% end %>
@@ -8,10 +8,12 @@
8
8
 
9
9
  <br>
10
10
 
11
+ <% field_id = "bar-#{SecureRandom.hex}" %>
12
+
11
13
  <%= form_with(url: "/foo") do |f| %>
12
- <%= render(Primer::Alpha::FormControl.new(**system_arguments, label_arguments: { for: "bar" })) do |component| %>
14
+ <%= render(Primer::Alpha::FormControl.new(**system_arguments, label_arguments: { for: field_id })) do |component| %>
13
15
  <% component.with_input do |input_arguments| %>
14
- <%= f.text_field(:bar, **input_arguments) %>
16
+ <%= f.text_field(:bar, id: field_id, **input_arguments) %>
15
17
  <% end %>
16
18
  <% end %>
17
19
  <% end %>
@@ -1,7 +1,7 @@
1
1
  <p style="max-width: 500px">
2
2
  Below is an SVG image containing the chosen octicon surrounded by a green circle. The octicon has been emitted
3
3
  onto the page as an SVG &lt;symbol&gt; element and included into the SVG you see below with the &lt;use&gt; element.
4
- See the <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/symbol" target="_blank">MDN docs</a> for
4
+ See the <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/symbol" target="_blank" style="text-decoration: underline">MDN docs</a> for
5
5
  more information.
6
6
  </p>
7
7
 
@@ -77,31 +77,6 @@ module Primer
77
77
  end
78
78
  end
79
79
 
80
- # @label Menu No Header
81
- #
82
- # @param size [Symbol] select [auto, small, medium, medium_portrait, large, xlarge]
83
- # @param padding [Symbol] select [normal, condensed, none]
84
- # @param anchor_align [Symbol] select [start, center, end]
85
- # @param anchor_side [Symbol] select [inside_top, inside_bottom, inside_left, inside_right, inside_center, outside_top, outside_bottom, outside_left, outside_right]
86
- # @param allow_out_of_bounds [Boolean] toggle
87
- #
88
- # @param button_text [String] text
89
- # @param body_text [String] text
90
- def menu_no_header(size: :auto, padding: :normal, anchor_align: :center, anchor_side: :outside_bottom, allow_out_of_bounds: false, button_text: "Show Overlay Menu", body_text: "This is a menu")
91
- render(Primer::Alpha::Overlay.new(
92
- title: "Menu",
93
- role: :menu,
94
- size: size,
95
- padding: padding,
96
- anchor_align: anchor_align,
97
- anchor_side: anchor_side,
98
- allow_out_of_bounds: allow_out_of_bounds
99
- )) do |d|
100
- d.with_show_button { button_text }
101
- d.with_body { body_text }
102
- end
103
- end
104
-
105
80
  # @label Middle Of Page
106
81
  #
107
82
  # @param title [String] text
@@ -16,6 +16,7 @@ module Primer
16
16
  # @param disabled toggle
17
17
  # @param invalid toggle
18
18
  # @param validation_message text
19
+ # @param character_limit number
19
20
  # @param input_width [Symbol] select [auto, xsmall, small, medium, large, xlarge, xxlarge]
20
21
  def playground(
21
22
  name: "my-text-area",
@@ -28,6 +29,7 @@ module Primer
28
29
  disabled: false,
29
30
  invalid: false,
30
31
  validation_message: nil,
32
+ character_limit: nil,
31
33
  input_width: nil
32
34
  )
33
35
  system_arguments = {
@@ -41,6 +43,7 @@ module Primer
41
43
  disabled: disabled,
42
44
  invalid: invalid,
43
45
  validation_message: validation_message,
46
+ character_limit: character_limit,
44
47
  input_width: input_width
45
48
  }
46
49
 
@@ -50,7 +53,7 @@ module Primer
50
53
  # @label Default
51
54
  # @snapshot
52
55
  def default
53
- render(Primer::Alpha::TextArea.new(name: "my-text-area", label: "Tell me about yourself"))
56
+ render(Primer::Alpha::TextArea.new(id: "my-text-area-default", name: "my-text-area-default", label: "Tell me about yourself"))
54
57
  end
55
58
 
56
59
  # @!group Options
@@ -58,43 +61,61 @@ module Primer
58
61
  # @label With caption
59
62
  # @snapshot
60
63
  def with_caption
61
- render(Primer::Alpha::TextArea.new(caption: "With a caption", name: "my-text-area", label: "Tell me about yourself"))
64
+ render(Primer::Alpha::TextArea.new(id: "my-text-area-with-caption", name: "my-text-area-with-caption", caption: "With a caption", label: "Tell me about yourself"))
62
65
  end
63
66
 
64
67
  # @label Visually hidden label
65
68
  # @snapshot
66
69
  def visually_hide_label
67
- render(Primer::Alpha::TextArea.new(visually_hide_label: true, name: "my-text-area", label: "Tell me about yourself"))
70
+ render(Primer::Alpha::TextArea.new(id: "my-text-area-visually-hide-label", name: "my-text-area-visually-hide-label", visually_hide_label: true, label: "Tell me about yourself"))
68
71
  end
69
72
 
70
73
  # @label Full width
71
74
  # @snapshot
72
75
  def full_width
73
- render(Primer::Alpha::TextArea.new(full_width: true, name: "my-text-area", label: "Tell me about yourself"))
76
+ render(Primer::Alpha::TextArea.new(id: "my-text-area-full-width", name: "my-text-area-full-width", full_width: true, label: "Tell me about yourself"))
74
77
  end
75
78
 
76
79
  # @label Not full width
77
80
  # @snapshot
78
81
  def not_full_width
79
- render(Primer::Alpha::TextArea.new(full_width: false, name: "my-text-area", label: "Tell me about yourself"))
82
+ render(Primer::Alpha::TextArea.new(id: "my-text-area-not-full-width", name: "my-text-area-not-full-width", full_width: false, label: "Tell me about yourself"))
80
83
  end
81
84
 
82
85
  # @label Disabled
83
86
  # @snapshot
84
87
  def disabled
85
- render(Primer::Alpha::TextArea.new(disabled: true, name: "my-text-area", label: "Tell me about yourself"))
88
+ render(Primer::Alpha::TextArea.new(id: "my-text-area-disabled", name: "my-text-area-disabled", disabled: true, label: "Tell me about yourself"))
86
89
  end
87
90
 
88
91
  # @label Invalid
89
92
  # @snapshot
90
93
  def invalid
91
- render(Primer::Alpha::TextArea.new(invalid: true, name: "my-text-area", label: "Tell me about yourself"))
94
+ render(Primer::Alpha::TextArea.new(id: "my-text-area-invalid", name: "my-text-area-invalid", invalid: true, label: "Tell me about yourself"))
92
95
  end
93
96
 
94
97
  # @label With validation message
95
98
  # @snapshot
96
99
  def with_validation_message
97
- render(Primer::Alpha::TextArea.new(validation_message: "An error occurred!", name: "my-text-area", label: "Tell me about yourself"))
100
+ render(Primer::Alpha::TextArea.new(id: "my-text-area-with-validation-message", name: "my-text-area-with-validation-message", validation_message: "An error occurred!", label: "Tell me about yourself"))
101
+ end
102
+
103
+ # @label With character limit
104
+ # @snapshot interactive
105
+ def with_character_limit
106
+ render(Primer::Alpha::TextArea.new(id: "my-text-area-with-character-limit", name: "my-text-area-with-character-limit", character_limit: 10, label: "Tell me about yourself"))
107
+ end
108
+
109
+ # @label With character limit, over limit
110
+ # @snapshot interactive
111
+ def with_character_limit_over_limit
112
+ render(Primer::Alpha::TextArea.new(id: "my-text-area-with-character-limit-over-limit", name: "my-text-area-with-character-limit-over-limit", character_limit: 10, label: "Tell me about yourself", value: "This text is definitely over the limit."))
113
+ end
114
+
115
+ # @label With character limit and caption
116
+ # @snapshot
117
+ def with_character_limit_and_caption
118
+ render(Primer::Alpha::TextArea.new(id: "my-text-area-with-character-limit-and-caption", name: "my-text-area-with-character-limit-and-caption", character_limit: 100, caption: "With a caption.", label: "Tell me about yourself"))
98
119
  end
99
120
  #
100
121
  # @!endgroup
@@ -15,6 +15,7 @@ module Primer
15
15
  # @param size [Symbol] select [small, medium, large]
16
16
  # @param show_clear_button toggle
17
17
  # @param clear_button_id text
18
+ # @param clear_button_label text
18
19
  # @param full_width toggle
19
20
  # @param disabled toggle
20
21
  # @param invalid toggle
@@ -24,6 +25,7 @@ module Primer
24
25
  # @param monospace toggle
25
26
  # @param leading_visual_icon octicon
26
27
  # @param leading_spinner toggle
28
+ # @param character_limit number
27
29
  # @param input_width [Symbol] select [auto, xsmall, small, medium, large, xlarge, xxlarge]
28
30
  def playground(
29
31
  name: "my-text-field",
@@ -35,6 +37,7 @@ module Primer
35
37
  size: Primer::Forms::Dsl::Input::DEFAULT_SIZE.to_s,
36
38
  show_clear_button: false,
37
39
  clear_button_id: "my-text-field-clear-button",
40
+ clear_button_label: nil,
38
41
  full_width: true,
39
42
  disabled: false,
40
43
  invalid: false,
@@ -44,6 +47,7 @@ module Primer
44
47
  monospace: false,
45
48
  leading_visual_icon: nil,
46
49
  leading_spinner: false,
50
+ character_limit: nil,
47
51
  input_width: nil
48
52
  )
49
53
  system_arguments = {
@@ -56,6 +60,7 @@ module Primer
56
60
  size: size,
57
61
  show_clear_button: show_clear_button,
58
62
  clear_button_id: clear_button_id,
63
+ clear_button_label: clear_button_label,
59
64
  full_width: full_width,
60
65
  disabled: disabled,
61
66
  invalid: invalid,
@@ -64,6 +69,7 @@ module Primer
64
69
  inset: inset,
65
70
  monospace: monospace,
66
71
  leading_spinner: leading_spinner,
72
+ character_limit: character_limit,
67
73
  input_width: input_width
68
74
  }
69
75
 
@@ -133,6 +139,12 @@ module Primer
133
139
  render(Primer::Alpha::TextField.new(show_clear_button: true, name: "my-text-field-3", label: "My text field"))
134
140
  end
135
141
 
142
+ # @label Show clear button with custom label
143
+ # @snapshot
144
+ def show_clear_button_with_custom_label
145
+ render(Primer::Alpha::TextField.new(show_clear_button: true, clear_button_label: "Effacer", name: "my-text-field-3-custom", label: "Mon champ de texte"))
146
+ end
147
+
136
148
  # @label Full width
137
149
  # @snapshot
138
150
  def full_width
@@ -216,6 +228,24 @@ module Primer
216
228
  def with_validation_message
217
229
  render(Primer::Alpha::TextField.new(validation_message: "An error occurred!", name: "my-text-field-17", label: "My text field"))
218
230
  end
231
+
232
+ # @label With character limit
233
+ # @snapshot interactive
234
+ def with_character_limit
235
+ render(Primer::Alpha::TextField.new(character_limit: 10, name: "my-text-field-18", label: "Username"))
236
+ end
237
+
238
+ # @label With character limit, over limit
239
+ # @snapshot interactive
240
+ def with_character_limit_over_limit
241
+ render(Primer::Alpha::TextField.new(character_limit: 10, name: "my-text-field-19", label: "Tell me about yourself", value: "This text is definitely over the limit."))
242
+ end
243
+
244
+ # @label With character limit and caption
245
+ # @snapshot
246
+ def with_character_limit_and_caption
247
+ render(Primer::Alpha::TextField.new(character_limit: 20, caption: "Choose a unique username.", name: "my-text-field-20", label: "Username"))
248
+ end
219
249
  #
220
250
  # @!endgroup
221
251
 
@@ -223,24 +253,24 @@ module Primer
223
253
  #
224
254
  # @label Auto check request ok
225
255
  def with_auto_check_ok
226
- render(Primer::Alpha::TextField.new(auto_check_src: UrlHelpers.primer_view_components.example_check_ok_path, name: "my-text-field-18", label: "My text field"))
256
+ render(Primer::Alpha::TextField.new(auto_check_src: UrlHelpers.primer_view_components.example_check_ok_path, name: "my-text-field-21", label: "My text field"))
227
257
  end
228
258
 
229
259
  # @label Auto check request accepted
230
260
  def with_auto_check_accepted
231
- render(Primer::Alpha::TextField.new(auto_check_src: UrlHelpers.primer_view_components.example_check_accepted_path, name: "my-text-field-19", label: "My text field"))
261
+ render(Primer::Alpha::TextField.new(auto_check_src: UrlHelpers.primer_view_components.example_check_accepted_path, name: "my-text-field-22", label: "My text field"))
232
262
  end
233
263
 
234
264
  # @label Auto check request error
235
265
  def with_auto_check_error
236
- render(Primer::Alpha::TextField.new(auto_check_src: UrlHelpers.primer_view_components.example_check_error_path, name: "my-text-field-20", label: "My text field"))
266
+ render(Primer::Alpha::TextField.new(auto_check_src: UrlHelpers.primer_view_components.example_check_error_path, name: "my-text-field-23", label: "My text field"))
237
267
  end
238
268
  #
239
269
  # @!endgroup
240
270
 
241
271
  # @label With data target attribute
242
272
  def with_data_target
243
- render(Primer::Alpha::TextField.new(name: "my-text-field", label: "My text field", data: { target: "custom-component.inputElement" }))
273
+ render(Primer::Alpha::TextField.new(name: "my-text-field-24", label: "My text field", data: { target: "custom-component.inputElement" }))
244
274
  end
245
275
  #
246
276
  # @!endgroup
@@ -9,67 +9,67 @@ module Primer
9
9
  include ActionView::Helpers::FormTagHelper
10
10
 
11
11
  def playground
12
- render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path))
12
+ render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path, "aria-label": "Toggle Switch"))
13
13
  end
14
14
 
15
15
  # @snapshot
16
16
  def default
17
- render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path))
17
+ render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path, "aria-label": "Default Toggle Switch"))
18
18
  end
19
19
 
20
20
  # @snapshot
21
21
  def checked
22
- render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path, checked: true))
22
+ render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path, checked: true, "aria-label": "Checked Toggle Switch"))
23
23
  end
24
24
 
25
25
  # @snapshot
26
26
  def disabled
27
- render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path, enabled: false))
27
+ render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path, enabled: false, "aria-label": "Disabled Toggle Switch"))
28
28
  end
29
29
 
30
30
  # @snapshot
31
31
  def checked_disabled
32
- render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path, checked: true, enabled: false))
32
+ render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path, checked: true, enabled: false, "aria-label": "Checked Disabled Toggle Switch"))
33
33
  end
34
34
 
35
35
  # @snapshot
36
36
  def small
37
- render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path, size: :small))
37
+ render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path, size: :small, "aria-label": "Small Toggle Switch"))
38
38
  end
39
39
 
40
40
  # @snapshot
41
41
  def with_status_label_position_end
42
- render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path, status_label_position: :end))
42
+ render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path, status_label_position: :end, "aria-label": "Toggle Switch with Status"))
43
43
  end
44
44
 
45
45
  # @snapshot
46
46
  def with_a_bad_src
47
- render(Primer::Alpha::ToggleSwitch.new(src: "/foo"))
47
+ render(Primer::Alpha::ToggleSwitch.new(src: "/foo", "aria-label": "Toggle Switch"))
48
48
  end
49
49
 
50
50
  def with_no_src
51
- render(Primer::Alpha::ToggleSwitch.new)
51
+ render(Primer::Alpha::ToggleSwitch.new("aria-label": "Toggle Switch"))
52
52
  end
53
53
 
54
54
  def with_csrf_token
55
- render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path))
55
+ render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path, "aria-label": "Toggle Switch"))
56
56
  end
57
57
 
58
58
  def with_bad_csrf_token
59
- render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path, csrf_token: "i_am_a_criminal"))
59
+ render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path, csrf_token: "i_am_a_criminal", "aria-label": "Toggle Switch"))
60
60
  end
61
61
 
62
62
  def with_turbo
63
- render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path, turbo: true))
63
+ render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path, turbo: true, "aria-label": "Toggle Switch"))
64
64
  end
65
65
 
66
66
  def with_autofocus
67
- render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path, autofocus: true))
67
+ render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path, autofocus: true, "aria-label": "Toggle Switch"))
68
68
  end
69
69
  end
70
70
 
71
71
  def with_custom_status_label
72
- render Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path, on_label: "Enabled", off_label: "Disabled")
72
+ render Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.primer_view_components.toggle_switch_index_path, on_label: "Enabled", off_label: "Disabled", "aria-label": "Toggle Switch")
73
73
  end
74
74
  end
75
75
  end
@@ -29,8 +29,60 @@
29
29
  const includeFragment = subject.querySelector('tree-view-include-fragment')
30
30
  if (!includeFragment) return
31
31
 
32
- includeFragment.addEventListener('loadend', (event) => {
32
+ function retryButton() {
33
+ return subject.querySelector("[data-target='tree-view-sub-tree-node.retryButton']")
34
+ }
35
+
36
+ function focusTreeItem() {
37
+ const treeItem = subject.querySelector("[role='treeitem']")
38
+ if (treeItem) treeItem.focus()
39
+ }
40
+
41
+ function makeRetryTabbable() {
42
+ const retry = retryButton()
43
+ if (!retry) return
44
+ // Remove tabindex="-1" so the button can be reached by keyboard.
45
+ retry.removeAttribute('tabindex')
46
+ }
47
+
48
+ function addRetryFocusHandler() {
49
+ const retry = retryButton()
50
+ if (!retry) return
51
+
52
+ if (retry.dataset.focusHandlerAttached === "true") return
53
+ retry.dataset.focusHandlerAttached = "true"
54
+
55
+ retry.addEventListener('click', () => {
56
+ focusTreeItem()
57
+ })
58
+ }
59
+
60
+ // When the failure UI loads, ensure Retry is tabbable
61
+ includeFragment.addEventListener('loadend', (_event) => {
62
+ makeRetryTabbable()
63
+ addRetryFocusHandler()
33
64
  subject.setAttribute('data-ready', 'true')
34
65
  })
66
+
67
+ // If include-fragment swaps in new markup on re-render, re-apply tabindex removal.
68
+ includeFragment.addEventListener('include-fragment-replaced', () => {
69
+ makeRetryTabbable()
70
+ addRetryFocusHandler()
71
+ })
72
+
73
+ // If Retry exists and the user presses Tab while focused inside this TreeView,
74
+ // redirect focus to Retry so it is reachable without requiring a mouse click.
75
+ subject.addEventListener('keydown', (event) => {
76
+ if (event.key !== 'Tab' || event.shiftKey) return
77
+ const active = document.activeElement
78
+ if (!(active instanceof HTMLElement)) return
79
+ if (!subject.contains(active)) return
80
+ if (active.getAttribute('role') !== 'treeitem') return
81
+ const retry = retryButton()
82
+ if (!retry) return
83
+ makeRetryTabbable()
84
+ event.preventDefault()
85
+ retry.focus()
86
+ })
35
87
  })
36
88
  </script>
@@ -0,0 +1,43 @@
1
+ <div style="max-width: 400px">
2
+ <%= render(Primer::Alpha::TreeView.new) do |tree_view| %>
3
+ <% (1..100).each do %>
4
+ <% tree_view.with_sub_tree(label: "src", expanded: expanded, disabled: disabled, select_variant: select_variant, select_strategy: select_strategy) do |sub_tree| %>
5
+ <% sub_tree.with_leading_visual_icons(label: "Foobar") do |icons| %>
6
+ <% icons.with_expanded_icon(icon: :"file-directory-open-fill", color: :accent) %>
7
+ <% icons.with_collapsed_icon(icon: :"file-directory-fill", color: :accent) %>
8
+ <% end %>
9
+
10
+ <% sub_tree.with_sub_tree(label: "src", expanded: expanded, disabled: disabled, select_variant: select_variant, select_strategy: select_strategy) do |sub_tree2| %>
11
+ <% sub_tree2.with_leading_visual_icons(label: "Foobar") do |icons| %>
12
+ <% icons.with_expanded_icon(icon: :"file-directory-open-fill", color: :accent) %>
13
+ <% icons.with_collapsed_icon(icon: :"file-directory-fill", color: :accent) %>
14
+ <% end %>
15
+
16
+ <% sub_tree2.with_trailing_visual_icon(icon: :"diff-modified") unless select_variant == :single %>
17
+
18
+ <% sub_tree2.with_leaf(label: "button.rb", disabled: disabled, select_variant: select_variant) do |item| %>
19
+ <% item.with_leading_visual_icon(icon: :file) %>
20
+ <% end %>
21
+ <% end %>
22
+
23
+ <% tree_view.with_leaf(label: "action_menu.rb", disabled: disabled, select_variant: select_variant) do |item| %>
24
+ <% item.with_leading_visual_icon(icon: :file) %>
25
+ <% end %>
26
+
27
+ <% sub_tree.with_trailing_visual_icon(icon: :"diff-modified") unless select_variant == :single %>
28
+
29
+ <% sub_tree.with_leaf(label: "button.rb", disabled: disabled, select_variant: select_variant) do |item| %>
30
+ <% item.with_leading_visual_icon(icon: :file) %>
31
+ <% end %>
32
+
33
+ <% sub_tree.with_leaf(label: "icon_button.rb", current: true, disabled: disabled, select_variant: select_variant) do |item| %>
34
+ <% item.with_leading_visual_icon(icon: :file) %>
35
+ <% end %>
36
+ <% end %>
37
+
38
+ <% tree_view.with_leaf(label: "action_menu.rb", disabled: disabled, select_variant: select_variant) do |item| %>
39
+ <% item.with_leading_visual_icon(icon: :file) %>
40
+ <% end %>
41
+ <% end %>
42
+ <% end %>
43
+ </div>
@@ -1,4 +1,4 @@
1
- <div style="display: flex; gap: 1rem;">
1
+ <div style="display: flex; flex-wrap: wrap; gap: 1rem;">
2
2
  <%= render(Primer::Beta::Button.new(
3
3
  scheme: :secondary,
4
4
  size: :medium,
@@ -1,4 +1,4 @@
1
- <div style="display: flex; gap: 1rem;">
1
+ <div style="display: flex; flex-wrap: wrap; gap: 1rem;">
2
2
  <%= render(Primer::Beta::Button.new(
3
3
  scheme: :invisible,
4
4
  size: :medium,
@@ -1,4 +1,12 @@
1
- <details>
1
+ <span
2
+ id="button-summary-live"
3
+ class="sr-only"
4
+ role="status"
5
+ aria-live="polite"
6
+ aria-atomic="true"></span>
7
+
8
+ <details
9
+ ontoggle="document.getElementById('button-summary-live').textContent = this.open ? 'A wrapping `details` tag is required when using the button with the `:summary` tag' : ''">
2
10
  <%= render(Primer::Beta::Button.new(
3
11
  scheme: scheme,
4
12
  size: size,
@@ -9,5 +17,6 @@
9
17
  )) do %>
10
18
  Button
11
19
  <% end %>
20
+
12
21
  <p>A wrapping `details` tag is required when using the button with the `:summary` tag</p>
13
22
  </details>
@@ -0,0 +1,3 @@
1
+ <%= primer_form_with(url: "/foo") do |f| %>
2
+ <%= render(TextAreaWithCharacterLimitForm.new(f)) %>
3
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <%= primer_form_with(url: "/foo") do |f| %>
2
+ <%= render(TextFieldWithCharacterLimitForm.new(f)) %>
3
+ <% end %>
@@ -72,5 +72,11 @@ module Primer
72
72
 
73
73
  # @snapshot
74
74
  def auto_complete_form; end
75
+
76
+ # @snapshot
77
+ def text_area_with_character_limit_form; end
78
+
79
+ # @snapshot
80
+ def text_field_with_character_limit_form; end
75
81
  end
76
82
  end
@@ -2721,7 +2721,7 @@
2721
2721
  "name": "gap",
2722
2722
  "type": "Symbol",
2723
2723
  "default": "`GapArg::DEFAULT`",
2724
- "description": "Specify the gap between children elements in the stack. One of `nil`, `:condensed`, `:normal`, or `:spacious`."
2724
+ "description": "Specify the gap between children elements in the stack. One of `nil`, `:condensed`, `:none`, `:normal`, or `:spacious`."
2725
2725
  },
2726
2726
  {
2727
2727
  "name": "direction",
@@ -2951,6 +2951,12 @@
2951
2951
  "default": "N/A",
2952
2952
  "description": "When set to `true`, the field will take up all the horizontal space allowed by its container. Defaults to `true`."
2953
2953
  },
2954
+ {
2955
+ "name": "character_limit",
2956
+ "type": "Number",
2957
+ "default": "N/A",
2958
+ "description": "Optional character limit for the input. If provided, a character counter will be displayed below the input."
2959
+ },
2954
2960
  {
2955
2961
  "name": "name",
2956
2962
  "type": "String",
@@ -3087,6 +3093,12 @@
3087
3093
  "default": "N/A",
3088
3094
  "description": "When set to `true`, the field will take up all the horizontal space allowed by its container. Defaults to `true`."
3089
3095
  },
3096
+ {
3097
+ "name": "character_limit",
3098
+ "type": "Number",
3099
+ "default": "N/A",
3100
+ "description": "Optional character limit for the input. If provided, a character counter will be displayed below the input."
3101
+ },
3090
3102
  {
3091
3103
  "name": "name",
3092
3104
  "type": "String",
@@ -117,6 +117,16 @@
117
117
  "preview_path": "primer/forms/auto_complete_form",
118
118
  "name": "auto_complete_form",
119
119
  "snapshot": "true"
120
+ },
121
+ {
122
+ "preview_path": "primer/forms/text_area_with_character_limit_form",
123
+ "name": "text_area_with_character_limit_form",
124
+ "snapshot": "true"
125
+ },
126
+ {
127
+ "preview_path": "primer/forms/text_field_with_character_limit_form",
128
+ "name": "text_field_with_character_limit_form",
129
+ "snapshot": "true"
120
130
  }
121
131
  ]
122
132
  }