primer_view_components 0.49.0 → 0.50.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/README.md +20 -1
  4. data/app/assets/javascripts/primer_view_components.js +1 -1
  5. data/app/assets/javascripts/primer_view_components.js.map +1 -1
  6. data/app/assets/styles/primer_view_components.css +1 -1
  7. data/app/assets/styles/primer_view_components.css.map +1 -1
  8. data/app/components/primer/alpha/action_list/item.rb +2 -1
  9. data/app/components/primer/alpha/select_panel.rb +1 -1
  10. data/app/components/primer/alpha/select_panel_element.js +1 -1
  11. data/app/components/primer/alpha/select_panel_element.ts +1 -1
  12. data/app/components/primer/alpha/stack.rb +1 -0
  13. data/app/components/primer/alpha/tab_nav.css +1 -1
  14. data/app/components/primer/alpha/tab_nav.css.json +1 -0
  15. data/app/components/primer/alpha/tab_nav.css.map +1 -1
  16. data/app/components/primer/alpha/tab_nav.pcss +7 -1
  17. data/app/controllers/primer/view_components/toggle_switch_controller.rb +2 -2
  18. data/app/forms/check_box_with_nested_form.rb +9 -5
  19. data/app/lib/primer/forms/check_box.rb +28 -0
  20. data/app/lib/primer/forms/dsl/multi_input.rb +3 -1
  21. data/app/lib/primer/forms/dsl/text_field_input.rb +2 -1
  22. data/app/lib/primer/forms/form_control.html.erb +2 -1
  23. data/app/lib/primer/forms/text_field.html.erb +1 -1
  24. data/lib/primer/accessibility.rb +9 -3
  25. data/lib/primer/view_components/engine.rb +1 -4
  26. data/lib/primer/view_components/version.rb +1 -1
  27. data/previews/primer/alpha/form_control_preview/playground.html.erb +4 -2
  28. data/previews/primer/alpha/octicon_symbols_preview/playground.html.erb +1 -1
  29. data/previews/primer/alpha/overlay_preview.rb +0 -25
  30. data/previews/primer/alpha/text_area_preview.rb +11 -11
  31. data/previews/primer/alpha/text_field_preview.rb +9 -0
  32. data/previews/primer/alpha/toggle_switch_preview.rb +14 -14
  33. data/previews/primer/beta/button_preview/all_schemes.html.erb +1 -1
  34. data/previews/primer/beta/button_preview/invisible_all_visuals.html.erb +1 -1
  35. data/previews/primer/beta/button_preview/summary_as_button.html.erb +10 -1
  36. data/static/arguments.json +1 -1
  37. data/static/info_arch.json +16 -15
  38. data/static/previews.json +15 -14
  39. metadata +2 -2
@@ -302,7 +302,8 @@ module Primer
302
302
  if @list.allows_selection?
303
303
  @content_arguments[:aria] = merge_aria(
304
304
  @content_arguments,
305
- { aria: @list.aria_selection_variant == :selected ? { selected: active? } : { checked: active? } }
305
+ # Always use aria-selected for listbox (role="option" requires aria-selected per WAI-ARIA 1.2)
306
+ { aria: @list.acts_as_listbox? || @list.aria_selection_variant == :selected ? { selected: active? } : { checked: active? } }
306
307
  )
307
308
  end
308
309
 
@@ -256,7 +256,7 @@ module Primer
256
256
  super(
257
257
  p: 2,
258
258
  role: "listbox",
259
- aria_selection_variant: select_variant == :single ? :selected : :checked,
259
+ aria_selection_variant: :selected,
260
260
  select_variant: select_variant == :multiple ? :multiple_checkbox : :single,
261
261
  **system_arguments
262
262
  )
@@ -84,7 +84,7 @@ let SelectPanelElement = class SelectPanelElement extends HTMLElement {
84
84
  return this.getAttribute('data-select-variant');
85
85
  }
86
86
  get ariaSelectionType() {
87
- return this.selectVariant === 'multiple' ? 'aria-checked' : 'aria-selected';
87
+ return 'aria-selected';
88
88
  }
89
89
  set selectVariant(variant) {
90
90
  if (variant) {
@@ -97,7 +97,7 @@ export class SelectPanelElement extends HTMLElement {
97
97
  }
98
98
 
99
99
  get ariaSelectionType(): string {
100
- return this.selectVariant === 'multiple' ? 'aria-checked' : 'aria-selected'
100
+ return 'aria-selected'
101
101
  }
102
102
 
103
103
  set selectVariant(variant: SelectVariant) {
@@ -117,6 +117,7 @@ module Primer
117
117
  DEFAULT = nil
118
118
  OPTIONS = [
119
119
  DEFAULT,
120
+ :none,
120
121
  :condensed,
121
122
  :normal,
122
123
  :spacious
@@ -1 +1 @@
1
- .tabnav{border-bottom:var(--borderWidth-thin) solid var(--borderColor-default);margin-bottom:var(--stack-gap-normal);margin-top:0}.tabnav-tabs{display:flex;margin-bottom:calc(var(--borderWidth-thin)*-1);overflow:hidden}.tabnav-tab{background-color:initial;border:var(--borderWidth-thin) solid #0000;border-bottom:0;color:var(--fgColor-muted);display:inline-block;flex-shrink:0;font-size:var(--text-body-size-medium);line-height:23px;padding:var(--base-size-8) var(--control-medium-paddingInline-spacious);-webkit-text-decoration:none;text-decoration:none;transition:color .2s cubic-bezier(.3,0,.5,1)}.tabnav-tab.selected,.tabnav-tab[aria-current]:not([aria-current=false]),.tabnav-tab[aria-selected=true]{background-color:var(--bgColor-default);border-color:var(--borderColor-default);border-radius:var(--borderRadius-medium) var(--borderRadius-medium) 0 0;color:var(--fgColor-default)}:is(.tabnav-tab.selected,.tabnav-tab[aria-selected=true],.tabnav-tab[aria-current]:not([aria-current=false])) .octicon{color:inherit}.tabnav-tab:hover{color:var(--fgColor-default);-webkit-text-decoration:none;text-decoration:none;transition-duration:.1s}.tabnav-tab:focus,.tabnav-tab:focus-visible{border-radius:var(--borderRadius-medium) var(--borderRadius-medium) 0 0!important;outline-offset:-6px}.tabnav-tab .octicon,.tabnav-tab:active{color:var(--fgColor-muted)}.tabnav-tab .octicon{margin-right:var(--control-small-gap)}.tabnav-tab .Counter{color:inherit;margin-left:var(--control-small-gap)}.tabnav-extra{color:var(--fgColor-muted);display:inline-block;font-size:var(--text-body-size-small);margin-left:10px;padding-top:10px}.tabnav-extra>.octicon{margin-right:var(--base-size-2)}a.tabnav-extra:hover{color:var(--fgColor-accent);-webkit-text-decoration:none;text-decoration:none}.tabnav-btn{margin-left:var(--controlStack-medium-gap-condensed)}
1
+ .tabnav{border-bottom:var(--borderWidth-thin) solid var(--borderColor-default);margin-bottom:var(--stack-gap-normal);margin-top:0}.tabnav-tabs{display:flex;margin-bottom:calc(var(--borderWidth-thin)*-1);overflow-x:auto;overflow-y:hidden}.tabnav-tab,.tabnav-tabs>li{flex-shrink:0}.tabnav-tab{background-color:initial;border:var(--borderWidth-thin) solid #0000;border-bottom:0;color:var(--fgColor-muted);display:inline-block;font-size:var(--text-body-size-medium);line-height:23px;padding:var(--base-size-8) var(--control-medium-paddingInline-spacious);-webkit-text-decoration:none;text-decoration:none;transition:color .2s cubic-bezier(.3,0,.5,1)}.tabnav-tab.selected,.tabnav-tab[aria-current]:not([aria-current=false]),.tabnav-tab[aria-selected=true]{background-color:var(--bgColor-default);border-color:var(--borderColor-default);border-radius:var(--borderRadius-medium) var(--borderRadius-medium) 0 0;color:var(--fgColor-default)}:is(.tabnav-tab.selected,.tabnav-tab[aria-selected=true],.tabnav-tab[aria-current]:not([aria-current=false])) .octicon{color:inherit}.tabnav-tab:hover{color:var(--fgColor-default);-webkit-text-decoration:none;text-decoration:none;transition-duration:.1s}.tabnav-tab:focus,.tabnav-tab:focus-visible{border-radius:var(--borderRadius-medium) var(--borderRadius-medium) 0 0!important;outline-offset:-6px}.tabnav-tab .octicon,.tabnav-tab:active{color:var(--fgColor-muted)}.tabnav-tab .octicon{margin-right:var(--control-small-gap)}.tabnav-tab .Counter{color:inherit;margin-left:var(--control-small-gap)}.tabnav-extra{color:var(--fgColor-muted);display:inline-block;font-size:var(--text-body-size-small);margin-left:10px;padding-top:10px}.tabnav-extra>.octicon{margin-right:var(--base-size-2)}a.tabnav-extra:hover{color:var(--fgColor-accent);-webkit-text-decoration:none;text-decoration:none}.tabnav-btn{margin-left:var(--controlStack-medium-gap-condensed)}
@@ -4,6 +4,7 @@
4
4
  ".tabnav",
5
5
  ".tabnav-tabs",
6
6
  ".tabnav-tab",
7
+ ".tabnav-tabs>li",
7
8
  ".tabnav-tab.selected",
8
9
  ".tabnav-tab[aria-current]:not([aria-current=false])",
9
10
  ".tabnav-tab[aria-selected=true]",
@@ -1 +1 @@
1
- {"version":3,"sources":["tab_nav.pcss"],"names":[],"mappings":"AAGA,QAIE,sEAAuE,CADvE,qCAAsC,CAFtC,YAIF,CAEA,aACE,YAAa,CAEb,8CAAiD,CACjD,eACF,CAEA,YAUE,wBAA6B,CAE7B,0CAAgB,CAAhB,eAAgB,CAJhB,0BAA2B,CAP3B,oBAAqB,CACrB,aAAc,CAGd,sCAAuC,CAEvC,gBAAiB,CAHjB,uEAAwE,CAKxE,4BAAqB,CAArB,oBAAqB,CAIrB,4CA0CF,CAxCE,yGAIE,uCAAwC,CACxC,uCAAwC,CACxC,uEAAwE,CAHxE,4BAQF,CAHE,uHACE,aACF,CAGF,kBACE,4BAA6B,CAC7B,4BAAqB,CAArB,oBAAqB,CACrB,uBACF,CAEA,4CAEE,iFAAmF,CACnF,mBACF,CAMA,wCAHE,0BAOF,CAJA,qBAEE,qCAEF,CAEA,qBAGE,aAAc,CADd,oCAEF,CAQF,cAOE,0BAA2B,CAN3B,oBAAqB,CAKrB,qCAAsC,CADtC,gBAAiB,CAFjB,gBASF,CAHE,uBACE,+BACF,CAKF,qBACE,2BAA4B,CAC5B,4BAAqB,CAArB,oBACF,CAOA,YAEE,oDACF","file":"tab_nav.css","sourcesContent":["/* tabnav */\n\n/* Outer wrapper */\n.tabnav {\n margin-top: 0;\n /* stylelint-disable-next-line primer/spacing */\n margin-bottom: var(--stack-gap-normal);\n border-bottom: var(--borderWidth-thin) solid var(--borderColor-default);\n}\n\n.tabnav-tabs {\n display: flex;\n /* stylelint-disable-next-line primer/spacing */\n margin-bottom: calc(var(--borderWidth-thin) * -1);\n overflow: hidden;\n}\n\n.tabnav-tab {\n display: inline-block;\n flex-shrink: 0;\n /* stylelint-disable-next-line primer/spacing */\n padding: var(--base-size-8) var(--control-medium-paddingInline-spacious);\n font-size: var(--text-body-size-medium);\n /* stylelint-disable-next-line primer/typography */\n line-height: 23px;\n color: var(--fgColor-muted);\n text-decoration: none;\n background-color: transparent;\n border: var(--borderWidth-thin) solid transparent;\n border-bottom: 0;\n transition: color 0.2s cubic-bezier(0.3, 0, 0.5, 1);\n\n &.selected,\n &[aria-selected='true'],\n &[aria-current]:not([aria-current='false']) {\n color: var(--fgColor-default);\n background-color: var(--bgColor-default); /* cover bottom border */\n border-color: var(--borderColor-default);\n border-radius: var(--borderRadius-medium) var(--borderRadius-medium) 0 0;\n\n & .octicon {\n color: inherit;\n }\n }\n\n &:hover {\n color: var(--fgColor-default);\n text-decoration: none;\n transition-duration: 0.1s;\n }\n\n &:focus,\n &:focus-visible {\n border-radius: var(--borderRadius-medium) var(--borderRadius-medium) 0 0 !important;\n outline-offset: -6px;\n }\n\n &:active {\n color: var(--fgColor-muted);\n }\n\n & .octicon {\n /* stylelint-disable-next-line primer/spacing */\n margin-right: var(--control-small-gap);\n color: var(--fgColor-muted);\n }\n\n & .Counter {\n /* stylelint-disable-next-line primer/spacing */\n margin-left: var(--control-small-gap);\n color: inherit;\n }\n}\n\n/* Tabnav extras\n**\n** Tabnav extras are non-tab elements that sit in the tabnav. Usually they're\n** inline text or links. */\n\n.tabnav-extra {\n display: inline-block;\n /* stylelint-disable-next-line primer/spacing */\n padding-top: 10px;\n /* stylelint-disable-next-line primer/spacing */\n margin-left: 10px;\n font-size: var(--text-body-size-small);\n color: var(--fgColor-muted);\n\n & > .octicon {\n margin-right: var(--base-size-2);\n }\n}\n\n/* When tabnav-extra are anchors */\n/* stylelint-disable-next-line selector-no-qualifying-type, selector-max-type */\na.tabnav-extra:hover {\n color: var(--fgColor-accent);\n text-decoration: none;\n}\n\n/* Tabnav buttons\n**\n** For when there are multiple buttons, space them out appropriately. Requires\n** the buttons to be floated or inline-block. */\n\n.tabnav-btn {\n /* stylelint-disable-next-line primer/spacing */\n margin-left: var(--controlStack-medium-gap-condensed);\n}\n"]}
1
+ {"version":3,"sources":["tab_nav.pcss"],"names":[],"mappings":"AAGA,QAIE,sEAAuE,CADvE,qCAAsC,CAFtC,YAIF,CAEA,aACE,YAAa,CAEb,8CAAiD,CACjD,eAAgB,CAChB,iBAMF,CAEA,4BAJI,aA2DJ,CAvDA,YAUE,wBAA6B,CAE7B,0CAAgB,CAAhB,eAAgB,CAJhB,0BAA2B,CAP3B,oBAAqB,CAIrB,sCAAuC,CAEvC,gBAAiB,CAHjB,uEAAwE,CAKxE,4BAAqB,CAArB,oBAAqB,CAIrB,4CA0CF,CAxCE,yGAIE,uCAAwC,CACxC,uCAAwC,CACxC,uEAAwE,CAHxE,4BAQF,CAHE,uHACE,aACF,CAGF,kBACE,4BAA6B,CAC7B,4BAAqB,CAArB,oBAAqB,CACrB,uBACF,CAEA,4CAEE,iFAAmF,CACnF,mBACF,CAMA,wCAHE,0BAOF,CAJA,qBAEE,qCAEF,CAEA,qBAGE,aAAc,CADd,oCAEF,CAQF,cAOE,0BAA2B,CAN3B,oBAAqB,CAKrB,qCAAsC,CADtC,gBAAiB,CAFjB,gBASF,CAHE,uBACE,+BACF,CAKF,qBACE,2BAA4B,CAC5B,4BAAqB,CAArB,oBACF,CAOA,YAEE,oDACF","file":"tab_nav.css","sourcesContent":["/* tabnav */\n\n/* Outer wrapper */\n.tabnav {\n margin-top: 0;\n /* stylelint-disable-next-line primer/spacing */\n margin-bottom: var(--stack-gap-normal);\n border-bottom: var(--borderWidth-thin) solid var(--borderColor-default);\n}\n\n.tabnav-tabs {\n display: flex;\n /* stylelint-disable-next-line primer/spacing */\n margin-bottom: calc(var(--borderWidth-thin) * -1);\n overflow-x: auto;\n overflow-y: hidden;\n\n /* stylelint-disable-next-line selector-max-type */\n & > li {\n flex-shrink: 0;\n }\n}\n\n.tabnav-tab {\n display: inline-block;\n flex-shrink: 0;\n /* stylelint-disable-next-line primer/spacing */\n padding: var(--base-size-8) var(--control-medium-paddingInline-spacious);\n font-size: var(--text-body-size-medium);\n /* stylelint-disable-next-line primer/typography */\n line-height: 23px;\n color: var(--fgColor-muted);\n text-decoration: none;\n background-color: transparent;\n border: var(--borderWidth-thin) solid transparent;\n border-bottom: 0;\n transition: color 0.2s cubic-bezier(0.3, 0, 0.5, 1);\n\n &.selected,\n &[aria-selected='true'],\n &[aria-current]:not([aria-current='false']) {\n color: var(--fgColor-default);\n background-color: var(--bgColor-default); /* cover bottom border */\n border-color: var(--borderColor-default);\n border-radius: var(--borderRadius-medium) var(--borderRadius-medium) 0 0;\n\n & .octicon {\n color: inherit;\n }\n }\n\n &:hover {\n color: var(--fgColor-default);\n text-decoration: none;\n transition-duration: 0.1s;\n }\n\n &:focus,\n &:focus-visible {\n border-radius: var(--borderRadius-medium) var(--borderRadius-medium) 0 0 !important;\n outline-offset: -6px;\n }\n\n &:active {\n color: var(--fgColor-muted);\n }\n\n & .octicon {\n /* stylelint-disable-next-line primer/spacing */\n margin-right: var(--control-small-gap);\n color: var(--fgColor-muted);\n }\n\n & .Counter {\n /* stylelint-disable-next-line primer/spacing */\n margin-left: var(--control-small-gap);\n color: inherit;\n }\n}\n\n/* Tabnav extras\n**\n** Tabnav extras are non-tab elements that sit in the tabnav. Usually they're\n** inline text or links. */\n\n.tabnav-extra {\n display: inline-block;\n /* stylelint-disable-next-line primer/spacing */\n padding-top: 10px;\n /* stylelint-disable-next-line primer/spacing */\n margin-left: 10px;\n font-size: var(--text-body-size-small);\n color: var(--fgColor-muted);\n\n & > .octicon {\n margin-right: var(--base-size-2);\n }\n}\n\n/* When tabnav-extra are anchors */\n/* stylelint-disable-next-line selector-no-qualifying-type, selector-max-type */\na.tabnav-extra:hover {\n color: var(--fgColor-accent);\n text-decoration: none;\n}\n\n/* Tabnav buttons\n**\n** For when there are multiple buttons, space them out appropriately. Requires\n** the buttons to be floated or inline-block. */\n\n.tabnav-btn {\n /* stylelint-disable-next-line primer/spacing */\n margin-left: var(--controlStack-medium-gap-condensed);\n}\n"]}
@@ -12,7 +12,13 @@
12
12
  display: flex;
13
13
  /* stylelint-disable-next-line primer/spacing */
14
14
  margin-bottom: calc(var(--borderWidth-thin) * -1);
15
- overflow: hidden;
15
+ overflow-x: auto;
16
+ overflow-y: hidden;
17
+
18
+ /* stylelint-disable-next-line selector-max-type */
19
+ & > li {
20
+ flex-shrink: 0;
21
+ }
16
22
  }
17
23
 
18
24
  .tabnav-tab {
@@ -9,7 +9,7 @@ module Primer
9
9
  attr_accessor :last_request
10
10
  end
11
11
 
12
- rescue_from ActionController::InvalidAuthenticityToken, with: :handle_invalid_authenticity_token
12
+ rescue_from ActionController::InvalidCrossOriginRequest, with: :handle_invalid_cross_origin_request
13
13
 
14
14
  before_action :reject_non_ajax_request
15
15
 
@@ -29,7 +29,7 @@ module Primer
29
29
 
30
30
  private
31
31
 
32
- def handle_invalid_authenticity_token
32
+ def handle_invalid_cross_origin_request
33
33
  render status: :unauthorized, plain: "Bad CSRF token."
34
34
  end
35
35
 
@@ -8,27 +8,31 @@ class CheckBoxWithNestedForm < ApplicationForm
8
8
  custom_cities_form.text_field(
9
9
  name: :custom_cities,
10
10
  label: "Custom cities",
11
- description: "A space-separated list of cities"
11
+ caption: "A space-separated list of cities"
12
12
  )
13
13
  end
14
14
  end
15
15
 
16
16
  form do |check_form|
17
- check_form.check_box_group(name: :city_categories) do |check_group|
17
+ check_form.check_box_group(
18
+ name: :city_categories,
19
+ label: "City categories",
20
+ caption: "Select all that apply."
21
+ ) do |check_group|
18
22
  check_group.check_box(
19
23
  value: "capital",
20
24
  label: "Capital",
21
- description: "The capital city"
25
+ caption: "The capital city"
22
26
  )
23
27
  check_group.check_box(
24
28
  value: "populous",
25
29
  label: "Most-populous",
26
- description: "The five most-populous cities"
30
+ caption: "The five most-populous cities"
27
31
  )
28
32
  check_group.check_box(
29
33
  value: "custom",
30
34
  label: "Custom",
31
- description: "A custom list of cities"
35
+ caption: "A custom list of cities"
32
36
  ) do |custom_check_box|
33
37
  custom_check_box.nested_form do |builder|
34
38
  CustomCitiesForm.new(builder)
@@ -11,6 +11,13 @@ module Primer
11
11
  @input.add_label_classes("FormControl-label")
12
12
  @input.add_input_classes("FormControl-checkbox")
13
13
 
14
+ # Generate custom ID that preserves brackets from the name
15
+ unless @input.input_arguments[:id].present?
16
+ generate_custom_id
17
+ # Update the label's for attribute to match the new ID
18
+ @input.label_arguments[:for] = @input.input_arguments[:id]
19
+ end
20
+
14
21
  return unless @input.scheme == :array
15
22
 
16
23
  @input.input_arguments[:multiple] = true
@@ -32,6 +39,27 @@ module Primer
32
39
 
33
40
  private
34
41
 
42
+ def generate_custom_id
43
+ # Generate an ID from the name that preserves special characters like brackets
44
+ # For array scheme: name + "_" + value (e.g., "permissions[3]_foo")
45
+ # For boolean scheme: just the name (e.g., "long_o")
46
+ base_name = @input.name.to_s
47
+
48
+ # For array scheme, Rails appends [] to the name, so we remove it for ID generation
49
+ # but only the trailing [] that Rails adds, not brackets that are part of the original name
50
+ # Regex /\[\]$/ matches literal "[]" at the end of the string
51
+ base_name = base_name.sub(/\[\]$/, "")
52
+
53
+ # For array scheme, append the value to make IDs unique
54
+ # For boolean scheme, just use the base name
55
+ # Note: Rails automatically escapes HTML attributes, so special characters are safe
56
+ if @input.scheme == :array && @input.value.present?
57
+ @input.input_arguments[:id] = "#{base_name}_#{@input.value}"
58
+ else
59
+ @input.input_arguments[:id] = base_name
60
+ end
61
+ end
62
+
35
63
  def checked_value
36
64
  @input.value || "1"
37
65
  end
@@ -39,7 +39,9 @@ module Primer
39
39
  new_options[:data] ||= {}
40
40
  new_options[:data][:name] = name
41
41
  new_options[:data][:targets] = "primer-multi-input.fields"
42
- new_options[:id] = nil if options[:hidden]
42
+ new_options[:id] = "#{@name}_#{name}"
43
+ new_options[:aria] ||= {}
44
+ new_options[:aria][:labelledby] = "label-#{base_id}"
43
45
  new_options[:disabled] = true if options[:hidden] # disable to avoid submitting to server
44
46
  new_options
45
47
  end
@@ -6,7 +6,7 @@ module Primer
6
6
  attr_reader(
7
7
  *%i[
8
8
  name label show_clear_button leading_visual leading_spinner trailing_visual clear_button_id
9
- visually_hide_label inset monospace field_wrap_classes auto_check_src character_limit
9
+ clear_button_label visually_hide_label inset monospace field_wrap_classes auto_check_src character_limit
10
10
  ]
11
11
  )
12
12
 
@@ -21,6 +21,7 @@ module Primer
21
21
  @trailing_visual = system_arguments.delete(:trailing_visual)
22
22
  @leading_spinner = !!system_arguments.delete(:leading_spinner)
23
23
  @clear_button_id = system_arguments.delete(:clear_button_id) || SecureRandom.uuid
24
+ @clear_button_label = system_arguments.delete(:clear_button_label)
24
25
  @inset = system_arguments.delete(:inset)
25
26
  @monospace = system_arguments.delete(:monospace)
26
27
  @auto_check_src = system_arguments.delete(:auto_check_src)
@@ -1,7 +1,8 @@
1
1
  <% if @input.form_control? %>
2
2
  <%= content_tag(@tag, **@form_group_arguments) do %>
3
3
  <% if @input.label %>
4
- <%= builder.label(@input.name, **@input.label_arguments) do %>
4
+ <% label_id = @input.label_arguments[:id] || "label-#{@input.base_id}" %>
5
+ <%= builder.label(@input.name, **@input.label_arguments.merge(id: label_id)) do %>
5
6
  <%= @input.label %>
6
7
  <% if @input.required? %>
7
8
  <span aria-hidden="true">*</span>
@@ -13,7 +13,7 @@
13
13
  <%= builder.text_field(@input.name, **@input.input_arguments) %>
14
14
  <% end %>
15
15
  <% if @input.show_clear_button? %>
16
- <button type="button" id="<%= @input.clear_button_id %>" class="FormControl-input-trailingAction" aria-label="Clear" data-action="click:primer-text-field#clearContents">
16
+ <button type="button" id="<%= @input.clear_button_id %>" class="FormControl-input-trailingAction" aria-label="<%= @input.clear_button_label || 'Clear' %>" data-action="click:primer-text-field#clearContents">
17
17
  <%= render(Primer::Beta::Octicon.new(icon: :"x-circle-fill")) %>
18
18
  </button>
19
19
  <% 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,7 +5,7 @@ module Primer
5
5
  module ViewComponents
6
6
  module VERSION
7
7
  MAJOR = 0
8
- MINOR = 49
8
+ MINOR = 50
9
9
  PATCH = 0
10
10
 
11
11
  STRING = [MAJOR, MINOR, PATCH].join(".")
@@ -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
@@ -50,7 +50,7 @@ module Primer
50
50
  # @label Default
51
51
  # @snapshot
52
52
  def default
53
- render(Primer::Alpha::TextArea.new(name: "my-text-area", label: "Tell me about yourself"))
53
+ render(Primer::Alpha::TextArea.new(id: "my-text-area-default", name: "my-text-area-default", label: "Tell me about yourself"))
54
54
  end
55
55
 
56
56
  # @!group Options
@@ -58,61 +58,61 @@ module Primer
58
58
  # @label With caption
59
59
  # @snapshot
60
60
  def with_caption
61
- render(Primer::Alpha::TextArea.new(caption: "With a caption", name: "my-text-area", label: "Tell me about yourself"))
61
+ 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
62
  end
63
63
 
64
64
  # @label Visually hidden label
65
65
  # @snapshot
66
66
  def visually_hide_label
67
- render(Primer::Alpha::TextArea.new(visually_hide_label: true, name: "my-text-area", label: "Tell me about yourself"))
67
+ 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
68
  end
69
69
 
70
70
  # @label Full width
71
71
  # @snapshot
72
72
  def full_width
73
- render(Primer::Alpha::TextArea.new(full_width: true, name: "my-text-area", label: "Tell me about yourself"))
73
+ 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
74
  end
75
75
 
76
76
  # @label Not full width
77
77
  # @snapshot
78
78
  def not_full_width
79
- render(Primer::Alpha::TextArea.new(full_width: false, name: "my-text-area", label: "Tell me about yourself"))
79
+ 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
80
  end
81
81
 
82
82
  # @label Disabled
83
83
  # @snapshot
84
84
  def disabled
85
- render(Primer::Alpha::TextArea.new(disabled: true, name: "my-text-area", label: "Tell me about yourself"))
85
+ render(Primer::Alpha::TextArea.new(id: "my-text-area-disabled", name: "my-text-area-disabled", disabled: true, label: "Tell me about yourself"))
86
86
  end
87
87
 
88
88
  # @label Invalid
89
89
  # @snapshot
90
90
  def invalid
91
- render(Primer::Alpha::TextArea.new(invalid: true, name: "my-text-area", label: "Tell me about yourself"))
91
+ render(Primer::Alpha::TextArea.new(id: "my-text-area-invalid", name: "my-text-area-invalid", invalid: true, label: "Tell me about yourself"))
92
92
  end
93
93
 
94
94
  # @label With validation message
95
95
  # @snapshot
96
96
  def with_validation_message
97
- render(Primer::Alpha::TextArea.new(validation_message: "An error occurred!", name: "my-text-area", label: "Tell me about yourself"))
97
+ 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"))
98
98
  end
99
99
 
100
100
  # @label With character limit
101
101
  # @snapshot interactive
102
102
  def with_character_limit
103
- render(Primer::Alpha::TextArea.new(character_limit: 10, name: "my-text-area", label: "Tell me about yourself"))
103
+ 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"))
104
104
  end
105
105
 
106
106
  # @label With character limit, over limit
107
107
  # @snapshot interactive
108
108
  def with_character_limit_over_limit
109
- render(Primer::Alpha::TextArea.new(character_limit: 10, name: "my-text-area", label: "Tell me about yourself", value: "This text is definitely over the limit."))
109
+ 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."))
110
110
  end
111
111
 
112
112
  # @label With character limit and caption
113
113
  # @snapshot
114
114
  def with_character_limit_and_caption
115
- render(Primer::Alpha::TextArea.new(character_limit: 100, caption: "With a caption.", name: "my-text-area", label: "Tell me about yourself"))
115
+ 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"))
116
116
  end
117
117
  #
118
118
  # @!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
@@ -35,6 +36,7 @@ module Primer
35
36
  size: Primer::Forms::Dsl::Input::DEFAULT_SIZE.to_s,
36
37
  show_clear_button: false,
37
38
  clear_button_id: "my-text-field-clear-button",
39
+ clear_button_label: nil,
38
40
  full_width: true,
39
41
  disabled: false,
40
42
  invalid: false,
@@ -56,6 +58,7 @@ module Primer
56
58
  size: size,
57
59
  show_clear_button: show_clear_button,
58
60
  clear_button_id: clear_button_id,
61
+ clear_button_label: clear_button_label,
59
62
  full_width: full_width,
60
63
  disabled: disabled,
61
64
  invalid: invalid,
@@ -133,6 +136,12 @@ module Primer
133
136
  render(Primer::Alpha::TextField.new(show_clear_button: true, name: "my-text-field-3", label: "My text field"))
134
137
  end
135
138
 
139
+ # @label Show clear button with custom label
140
+ # @snapshot
141
+ def show_clear_button_with_custom_label
142
+ render(Primer::Alpha::TextField.new(show_clear_button: true, clear_button_label: "Effacer", name: "my-text-field-3-custom", label: "Mon champ de texte"))
143
+ end
144
+
136
145
  # @label Full width
137
146
  # @snapshot
138
147
  def full_width
@@ -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
@@ -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>
@@ -2616,7 +2616,7 @@
2616
2616
  "name": "gap",
2617
2617
  "type": "Symbol",
2618
2618
  "default": "`GapArg::DEFAULT`",
2619
- "description": "Specify the gap between children elements in the stack. One of `nil`, `:condensed`, `:normal`, or `:spacious`."
2619
+ "description": "Specify the gap between children elements in the stack. One of `nil`, `:condensed`, `:none`, `:normal`, or `:spacious`."
2620
2620
  },
2621
2621
  {
2622
2622
  "name": "direction",