primer_view_components 0.1.6 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -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/assets/styles/primer_view_components.css +3 -3
  6. data/app/assets/styles/primer_view_components.css.map +1 -1
  7. data/app/components/primer/alpha/action_list/divider.rb +1 -1
  8. data/app/components/primer/alpha/action_list/form_wrapper.html.erb +10 -0
  9. data/app/components/primer/alpha/action_list/form_wrapper.rb +61 -0
  10. data/app/components/primer/alpha/action_list/heading.html.erb +2 -2
  11. data/app/components/primer/alpha/action_list/heading.rb +9 -5
  12. data/app/components/primer/alpha/action_list/item.html.erb +41 -36
  13. data/app/components/primer/alpha/action_list/item.rb +16 -2
  14. data/app/components/primer/alpha/action_list.css +1 -1
  15. data/app/components/primer/alpha/action_list.css.json +1 -0
  16. data/app/components/primer/alpha/action_list.css.map +1 -1
  17. data/app/components/primer/alpha/action_list.html.erb +6 -0
  18. data/app/components/primer/alpha/action_list.pcss +42 -37
  19. data/app/components/primer/alpha/action_list.rb +31 -11
  20. data/app/components/primer/alpha/action_menu/action_menu_element.d.ts +7 -1
  21. data/app/components/primer/alpha/action_menu/action_menu_element.js +55 -3
  22. data/app/components/primer/alpha/action_menu/action_menu_element.ts +70 -2
  23. data/app/components/primer/alpha/action_menu/list.rb +9 -11
  24. data/app/components/primer/alpha/action_menu.rb +50 -12
  25. data/app/components/primer/alpha/auto_complete.css +1 -1
  26. data/app/components/primer/alpha/auto_complete.css.map +1 -1
  27. data/app/components/primer/alpha/auto_complete.pcss +2 -2
  28. data/app/components/primer/alpha/banner.css +1 -1
  29. data/app/components/primer/alpha/banner.css.map +1 -1
  30. data/app/components/primer/alpha/banner.pcss +7 -7
  31. data/app/components/primer/alpha/dialog.css +1 -1
  32. data/app/components/primer/alpha/dialog.css.map +1 -1
  33. data/app/components/primer/alpha/dialog.pcss +33 -32
  34. data/app/components/primer/alpha/dialog.rb +4 -0
  35. data/app/components/primer/alpha/dropdown.css +1 -1
  36. data/app/components/primer/alpha/dropdown.css.map +1 -1
  37. data/app/components/primer/alpha/dropdown.pcss +12 -11
  38. data/app/components/primer/alpha/layout.css +1 -1
  39. data/app/components/primer/alpha/layout.css.map +1 -1
  40. data/app/components/primer/alpha/layout.pcss +4 -4
  41. data/app/components/primer/alpha/menu.css +1 -1
  42. data/app/components/primer/alpha/menu.css.map +1 -1
  43. data/app/components/primer/alpha/menu.pcss +20 -20
  44. data/app/components/primer/alpha/nav_list/divider.rb +14 -0
  45. data/app/components/primer/alpha/nav_list/group.html.erb +7 -0
  46. data/app/components/primer/alpha/nav_list/group.rb +24 -11
  47. data/app/components/primer/alpha/nav_list/item.rb +4 -0
  48. data/app/components/primer/alpha/nav_list.d.ts +0 -1
  49. data/app/components/primer/alpha/nav_list.html.erb +9 -4
  50. data/app/components/primer/alpha/nav_list.js +3 -4
  51. data/app/components/primer/alpha/nav_list.rb +87 -10
  52. data/app/components/primer/alpha/nav_list.ts +3 -2
  53. data/app/components/primer/alpha/segmented_control.css +1 -1
  54. data/app/components/primer/alpha/segmented_control.css.json +2 -2
  55. data/app/components/primer/alpha/segmented_control.css.map +1 -1
  56. data/app/components/primer/alpha/segmented_control.pcss +34 -43
  57. data/app/components/primer/alpha/tab_nav.css +1 -1
  58. data/app/components/primer/alpha/tab_nav.css.map +1 -1
  59. data/app/components/primer/alpha/tab_nav.pcss +12 -12
  60. data/app/components/primer/alpha/text_field.css +3 -3
  61. data/app/components/primer/alpha/text_field.css.json +11 -11
  62. data/app/components/primer/alpha/text_field.css.map +1 -1
  63. data/app/components/primer/alpha/text_field.pcss +76 -90
  64. data/app/components/primer/alpha/toggle_switch.css +1 -1
  65. data/app/components/primer/alpha/toggle_switch.css.map +1 -1
  66. data/app/components/primer/alpha/toggle_switch.pcss +9 -9
  67. data/app/components/primer/alpha/underline_nav.css +1 -1
  68. data/app/components/primer/alpha/underline_nav.css.map +1 -1
  69. data/app/components/primer/alpha/underline_nav.pcss +7 -7
  70. data/app/components/primer/beta/auto_complete/item.html.erb +9 -9
  71. data/app/components/primer/beta/auto_complete/item.rb +17 -13
  72. data/app/components/primer/beta/auto_complete.rb +1 -1
  73. data/app/components/primer/beta/avatar.css +1 -1
  74. data/app/components/primer/beta/avatar.css.map +1 -1
  75. data/app/components/primer/beta/avatar.pcss +2 -2
  76. data/app/components/primer/beta/avatar_stack.css +1 -1
  77. data/app/components/primer/beta/avatar_stack.css.map +1 -1
  78. data/app/components/primer/beta/avatar_stack.pcss +5 -5
  79. data/app/components/primer/beta/blankslate.css +1 -1
  80. data/app/components/primer/beta/blankslate.css.map +1 -1
  81. data/app/components/primer/beta/blankslate.pcss +13 -13
  82. data/app/components/primer/beta/border_box.css +1 -1
  83. data/app/components/primer/beta/border_box.css.json +1 -1
  84. data/app/components/primer/beta/border_box.css.map +1 -1
  85. data/app/components/primer/beta/border_box.pcss +41 -39
  86. data/app/components/primer/beta/button.css +1 -1
  87. data/app/components/primer/beta/button.css.json +2 -0
  88. data/app/components/primer/beta/button.css.map +1 -1
  89. data/app/components/primer/beta/button.pcss +34 -25
  90. data/app/components/primer/beta/counter.css +1 -1
  91. data/app/components/primer/beta/counter.css.map +1 -1
  92. data/app/components/primer/beta/counter.pcss +3 -3
  93. data/app/components/primer/beta/flash.css +1 -1
  94. data/app/components/primer/beta/flash.css.map +1 -1
  95. data/app/components/primer/beta/flash.pcss +10 -11
  96. data/app/components/primer/beta/label.css +1 -1
  97. data/app/components/primer/beta/label.css.map +1 -1
  98. data/app/components/primer/beta/label.pcss +2 -2
  99. data/app/components/primer/beta/popover.css +1 -1
  100. data/app/components/primer/beta/popover.css.map +1 -1
  101. data/app/components/primer/beta/popover.pcss +4 -4
  102. data/app/components/primer/beta/state.css +1 -1
  103. data/app/components/primer/beta/state.css.map +1 -1
  104. data/app/components/primer/beta/state.pcss +5 -5
  105. data/app/components/primer/beta/subhead.css +1 -1
  106. data/app/components/primer/beta/subhead.css.map +1 -1
  107. data/app/components/primer/beta/subhead.pcss +4 -4
  108. data/app/components/primer/beta/timeline_item.css +1 -1
  109. data/app/components/primer/beta/timeline_item.css.map +1 -1
  110. data/app/components/primer/beta/timeline_item.pcss +13 -13
  111. data/app/components/primer/beta/truncate.css +1 -1
  112. data/app/components/primer/beta/truncate.css.map +1 -1
  113. data/app/components/primer/beta/truncate.pcss +1 -1
  114. data/app/components/primer/component.rb +3 -96
  115. data/app/lib/primer/attributes_helper.rb +105 -0
  116. data/lib/postcss_mixins/activeIndicatorLine.pcss +1 -1
  117. data/lib/primer/forms/text_field.rb +6 -0
  118. data/lib/primer/view_components/linters/disallow_component_css_counter.rb +1 -1
  119. data/lib/primer/view_components/version.rb +1 -1
  120. data/lib/primer/yard/component_manifest.rb +1 -0
  121. data/previews/primer/alpha/action_menu_preview/multiple_select_form.html.erb +13 -0
  122. data/previews/primer/alpha/action_menu_preview/single_select_form.html.erb +13 -0
  123. data/previews/primer/alpha/action_menu_preview/submitting_forms.html.erb +15 -0
  124. data/previews/primer/alpha/action_menu_preview.rb +46 -2
  125. data/previews/primer/alpha/nav_list_preview/trailing_action.html.erb +10 -10
  126. data/previews/primer/alpha/nav_list_preview.rb +37 -16
  127. data/previews/primer/beta/button_preview/trailing_counter.html.erb +11 -0
  128. data/previews/primer/beta/button_preview.rb +15 -0
  129. data/previews/primer/box_preview.rb +28 -0
  130. data/static/arguments.json +59 -14
  131. data/static/audited_at.json +2 -0
  132. data/static/classes.json +3 -0
  133. data/static/constants.json +21 -1
  134. data/static/info_arch.json +238 -36
  135. data/static/previews.json +58 -0
  136. data/static/statuses.json +2 -0
  137. metadata +12 -2
@@ -1,9 +1,14 @@
1
1
  :root {
2
- --primer-actionListContent-paddingBlock: var(--primer-control-medium-paddingBlock, 6px);
2
+ --actionListContent-paddingBlock: var(--control-medium-paddingBlock, 6px);
3
3
  }
4
4
 
5
5
  /* ActionList */
6
6
 
7
+ .ActionListHeader {
8
+ margin-left: var(--base-size-8, 8px);
9
+ margin-bottom: var(--base-size-16, 16px);
10
+ }
11
+
7
12
  /* <ul> */
8
13
  .ActionListWrap {
9
14
  list-style: none;
@@ -18,7 +23,7 @@
18
23
  .ActionListWrap--divided {
19
24
  & .ActionListItem-label::before {
20
25
  position: absolute;
21
- top: calc(-1 * var(--primer-actionListContent-paddingBlock));
26
+ top: calc(-1 * var(--actionListContent-paddingBlock));
22
27
  display: block;
23
28
  width: 100%;
24
29
  height: 1px;
@@ -30,10 +35,10 @@
30
35
  & .ActionListItem-descriptionWrap--inline {
31
36
  &::before {
32
37
  position: absolute;
33
- top: calc(-1 * var(--primer-actionListContent-paddingBlock));
38
+ top: calc(-1 * var(--actionListContent-paddingBlock));
34
39
  display: block;
35
40
  width: 100%;
36
- height: var(--primer-borderWidth-thin, 1px);
41
+ height: var(--borderWidth-thin, 1px);
37
42
  content: '';
38
43
  background: var(--color-action-list-item-inline-divider);
39
44
  }
@@ -71,7 +76,7 @@
71
76
  position: relative;
72
77
  list-style: none;
73
78
  background-color: transparent;
74
- border-radius: var(--primer-borderRadius-medium, 6px);
79
+ border-radius: var(--borderRadius-medium, 6px);
75
80
 
76
81
  /* state */
77
82
 
@@ -129,9 +134,9 @@
129
134
 
130
135
  &:not(.ActionListItem--navActive, :focus-visible) {
131
136
  /* Support for "Windows high contrast mode" */
132
- outline: solid var(--primer-borderWidth-thin, 1px) transparent;
133
- outline-offset: calc(-1 * var(--primer-borderWidth-thin, 1px));
134
- box-shadow: var(--primer-borderInset-thin, 1px) var(--color-action-list-item-default-active-border);
137
+ outline: solid var(--borderWidth-thin, 1px) transparent;
138
+ outline-offset: calc(-1 * var(--borderWidth-thin, 1px));
139
+ box-shadow: var(--borderInset-thin, 1px) var(--color-action-list-item-default-active-border);
135
140
  }
136
141
  }
137
142
  }
@@ -141,9 +146,9 @@
141
146
 
142
147
  &:not(.ActionListItem--navActive) {
143
148
  /* Support for "Windows high contrast mode" https:sarahmhigley.com/writing/whcm-quick-tips/ */
144
- outline: solid var(--primer-borderWidth-thin, 1px) transparent;
145
- outline-offset: calc(-1 * var(--primer-borderWidth-thin, 1px));
146
- box-shadow: var(--primer-borderInset-thin, 1px) var(--color-action-list-item-default-active-border);
149
+ outline: solid var(--borderWidth-thin, 1px) transparent;
150
+ outline-offset: calc(-1 * var(--borderWidth-thin, 1px));
151
+ box-shadow: var(--borderInset-thin, 1px) var(--color-action-list-item-default-active-border);
147
152
  }
148
153
 
149
154
  & .ActionListItem-label::before,
@@ -189,7 +194,7 @@
189
194
  & .ActionListItem-multiSelectIconRect {
190
195
  fill: var(--color-accent-fg);
191
196
  stroke: var(--color-accent-fg);
192
- stroke-width: var(--primer-borderWidth-thin, 1px);
197
+ stroke-width: var(--borderWidth-thin, 1px);
193
198
  }
194
199
 
195
200
  & .ActionListItem-multiSelectCheckmark {
@@ -232,13 +237,13 @@
232
237
  & .ActionListItem-multiSelectIconRect {
233
238
  fill: var(--color-canvas-default);
234
239
  stroke: var(--color-border-default);
235
- stroke-width: var(--primer-borderWidth-thin, 1px);
240
+ stroke-width: var(--borderWidth-thin, 1px);
236
241
  }
237
242
  }
238
243
 
239
244
  & .ActionListItem-multiSelectIconRect {
240
245
  fill: var(--color-canvas-default);
241
- border: var(--primer-borderWidth-thin, 1px) solid var(--color-border-default);
246
+ border: var(--borderWidth-thin, 1px) solid var(--color-border-default);
242
247
  }
243
248
  }
244
249
 
@@ -360,14 +365,14 @@
360
365
  position: relative;
361
366
  display: grid;
362
367
  width: 100%;
363
- padding-block: var(--primer-actionListContent-paddingBlock);
364
- padding-inline: var(--primer-control-medium-paddingInline-condensed, 8px);
368
+ padding-block: var(--actionListContent-paddingBlock);
369
+ padding-inline: var(--control-medium-paddingInline-condensed, 8px);
365
370
  color: var(--color-fg-default);
366
371
  text-align: left;
367
372
  user-select: none;
368
373
  background-color: transparent;
369
374
  border: none;
370
- border-radius: var(--primer-borderRadius-medium, 6px);
375
+ border-radius: var(--borderRadius-medium, 6px);
371
376
  transition: background 33.333ms linear;
372
377
  touch-action: manipulation;
373
378
  -webkit-tap-highlight-color: transparent;
@@ -378,7 +383,7 @@
378
383
 
379
384
  /* column-gap persists with empty grid-areas, margin applies only when children exist */
380
385
  & > :not(:last-child) {
381
- margin-right: var(--primer-control-medium-gap, 8px);
386
+ margin-right: var(--control-medium-gap, 8px);
382
387
  }
383
388
 
384
389
  /* state */
@@ -501,16 +506,16 @@
501
506
  /* sizes */
502
507
 
503
508
  &.ActionListContent--sizeLarge {
504
- --primer-actionListContent-paddingBlock: var(--primer-control-large-paddingBlock, calc((2.5rem - 1.25rem) / 2));
509
+ --actionListContent-paddingBlock: var(--control-large-paddingBlock, calc((2.5rem - 1.25rem) / 2));
505
510
  }
506
511
 
507
512
  &.ActionListContent--sizeXLarge {
508
- --primer-actionListContent-paddingBlock: var(--primer-control-xlarge-paddingBlock, calc((3rem - 1.25rem) / 2));
513
+ --actionListContent-paddingBlock: var(--control-xlarge-paddingBlock, calc((3rem - 1.25rem) / 2));
509
514
  }
510
515
 
511
516
  /* On pointer:coarse (mobile), all list items are large */
512
517
  @media (pointer: coarse) {
513
- --primer-actionListContent-paddingBlock: var(--primer-control-large-paddingBlock, calc((2.5rem - 1.25rem) / 2));
518
+ --actionListContent-paddingBlock: var(--control-large-paddingBlock, calc((2.5rem - 1.25rem) / 2));
514
519
  }
515
520
 
516
521
  &.ActionListContent--blockDescription {
@@ -562,9 +567,9 @@
562
567
 
563
568
  /* description */
564
569
  .ActionListItem-description {
565
- font-size: var(--primer-text-body-size-small, 12px);
570
+ font-size: var(--text-body-size-small, 12px);
566
571
  font-weight: var(--base-text-weight-normal, 400);
567
- line-height: var(--primer-text-body-lineHeight-small, calc(20 / 12));
572
+ line-height: var(--text-body-lineHeight-small, calc(20 / 12));
568
573
  color: var(--color-fg-muted);
569
574
  }
570
575
 
@@ -573,7 +578,7 @@
573
578
  .ActionListItem-visual,
574
579
  .ActionListItem-action {
575
580
  display: flex;
576
- min-height: var(--primer-control-medium-lineBoxHeight, 20px);
581
+ min-height: var(--control-medium-lineBoxHeight, 20px);
577
582
  color: var(--color-fg-muted);
578
583
  pointer-events: none;
579
584
  fill: var(--color-fg-muted);
@@ -583,9 +588,9 @@
583
588
  /* text */
584
589
  .ActionListItem-label {
585
590
  position: relative;
586
- font-size: var(--primer-text-body-size-medium, 14px);
591
+ font-size: var(--text-body-size-medium, 14px);
587
592
  font-weight: var(--base-text-weight-normal, 400);
588
- line-height: var(--primer-text-body-lineHeight-medium, calc(20 / 14));
593
+ line-height: var(--text-body-lineHeight-medium, calc(20 / 14));
589
594
  color: var(--color-fg-default);
590
595
  grid-area: label;
591
596
  }
@@ -600,8 +605,8 @@
600
605
  target ActionListItem--subItem for padding-left to maintain :active :after state */
601
606
 
602
607
  .ActionListItem--subItem > .ActionListContent > .ActionListItem-label {
603
- font-size: var(--primer-text-body-size-small, 12px);
604
- line-height: var(--primer-text-body-lineHeight-small, calc(20 / 12));
608
+ font-size: var(--text-body-size-small, 12px);
609
+ line-height: var(--text-body-lineHeight-small, calc(20 / 12));
605
610
  }
606
611
 
607
612
  /* trailing action icon button */
@@ -638,10 +643,10 @@
638
643
  /* with children */
639
644
  &:not(:empty) {
640
645
  display: flex;
641
- padding-inline: var(--primer-actionListContent-paddingBlock);
646
+ padding-inline: var(--actionListContent-paddingBlock);
642
647
  padding-block: var(--base-size-8, 8px);
643
- font-size: var(--primer-text-body-size-small, 12px);
644
- line-height: var(--primer-text-body-lineHeight-small, calc(20 / 12));
648
+ font-size: var(--text-body-size-small, 12px);
649
+ line-height: var(--text-body-lineHeight-small, calc(20 / 12));
645
650
  font-weight: var(--base-text-weight-semibold, 600);
646
651
  color: var(--color-fg-muted);
647
652
  flex-direction: column;
@@ -650,9 +655,9 @@
650
655
  /* no children */
651
656
  &:empty {
652
657
  display: block;
653
- height: var(--primer-borderWidth-thin, 1px);
658
+ height: var(--borderWidth-thin, 1px);
654
659
  padding: 0;
655
- margin-block-start: calc(var(--base-size-8, 8px) - var(--primer-borderWidth-thin, 1px));
660
+ margin-block-start: calc(var(--base-size-8, 8px) - var(--borderWidth-thin, 1px));
656
661
  margin-block-end: var(--base-size-8, 8px);
657
662
  margin-inline: calc(-1 * var(--base-size-8, 8px));
658
663
  list-style: none;
@@ -661,19 +666,19 @@
661
666
  }
662
667
 
663
668
  & .ActionList-sectionDivider-title {
664
- font-size: var(--primer-text-body-size-small, 12px);
669
+ font-size: var(--text-body-size-small, 12px);
665
670
  font-weight: var(--base-text-weight-semibold, 600);
666
671
  color: var(--color-fg-muted);
667
672
  }
668
673
  }
669
674
 
670
675
  .ActionList-sectionDivider--filled {
671
- margin-block-start: calc(var(--base-size-8, 8px) - var(--primer-borderWidth-thin, 1px));
676
+ margin-block-start: calc(var(--base-size-8, 8px) - var(--borderWidth-thin, 1px));
672
677
  margin-block-end: var(--base-size-8, 8px);
673
678
  margin-inline: calc(-1 * var(--base-size-8, 8px));
674
679
  background: var(--color-canvas-subtle);
675
- border-top: solid var(--primer-borderWidth-thin, 1px) var(--color-action-list-item-inline-divider);
676
- border-bottom: solid var(--primer-borderWidth-thin, 1px) var(--color-action-list-item-inline-divider);
680
+ border-top: solid var(--borderWidth-thin, 1px) var(--color-action-list-item-inline-divider);
681
+ border-bottom: solid var(--borderWidth-thin, 1px) var(--color-action-list-item-inline-divider);
677
682
 
678
683
  /* if no children, treat as divider */
679
684
  &:empty {
@@ -53,7 +53,7 @@ module Primer
53
53
  #
54
54
  # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList::Heading) %>.
55
55
  renders_one :heading, lambda { |**system_arguments|
56
- Heading.new(list_id: @id, **system_arguments)
56
+ Heading.new(**system_arguments)
57
57
  }
58
58
 
59
59
  # Items.
@@ -74,7 +74,7 @@ module Primer
74
74
  set_slot(:items, { renderable: Divider, collection: true }, **system_arguments, &block)
75
75
  end
76
76
 
77
- attr_reader :select_variant, :role
77
+ attr_reader :id, :select_variant, :role
78
78
 
79
79
  # @param id [String] HTML ID value.
80
80
  # @param role [Boolean] ARIA role describing the function of the list. listbox and menu are a common values.
@@ -82,6 +82,7 @@ module Primer
82
82
  # @param scheme [Symbol] <%= one_of(Primer::Alpha::ActionList::SCHEME_OPTIONS) %> `inset` children are offset (vertically and horizontally) from list edges. `full` (default) children are flush (vertically and horizontally) with list edges.
83
83
  # @param show_dividers [Boolean] Display a divider above each item in the list when it does not follow a header or divider.
84
84
  # @param select_variant [Symbol] How items may be selected in the list. <%= one_of(Primer::Alpha::ActionList::SELECT_VARIANT_OPTIONS) %>
85
+ # @param form_arguments [Hash] Allows an `ActionList` to act as a select list in multi- and single-select modes. Pass the `builder:` and `name:` options to this hash. `builder:` should be an instance of `ActionView::Helpers::FormBuilder`, which are created by the standard Rails `#form_with` and `#form_for` helpers. The `name:` option is the desired name of the field that will be included in the params sent to the server on form submission. *NOTE*: Consider using an <%= link_to_component(Primer::Alpha::ActionMenu) %> instead of using this feature directly.
85
86
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
86
87
  def initialize(
87
88
  id: self.class.generate_id,
@@ -90,6 +91,7 @@ module Primer
90
91
  scheme: DEFAULT_SCHEME,
91
92
  show_dividers: false,
92
93
  select_variant: DEFAULT_SELECT_VARIANT,
94
+ form_arguments: {},
93
95
  **system_arguments
94
96
  )
95
97
  @system_arguments = system_arguments
@@ -111,19 +113,21 @@ module Primer
111
113
  @system_arguments[:role] = @role
112
114
 
113
115
  @list_wrapper_arguments = {}
116
+
117
+ @form_builder = form_arguments[:builder]
118
+ @input_name = form_arguments[:name]
119
+
120
+ return unless required_form_arguments_given? && !allows_selection?
121
+
122
+ raise ArgumentError, "lists/menus that act as form inputs must also allow item selection (please pass the `select_variant:` option)"
114
123
  end
115
124
 
116
125
  # @private
117
126
  def before_render
118
- aria_label = aria(:label, @system_arguments)
119
- aria_labelledby = aria(:labelledby, @system_arguments)
120
-
121
- if heading.present?
122
- @system_arguments[:"aria-labelledby"] = heading.id unless aria_labelledby
123
- raise ArgumentError, "An aria-label should not be provided if a heading is present" if aria_label.present?
124
- elsif aria_label.blank? && aria_labelledby.blank?
125
- raise ArgumentError, "An aria-label, aria-labelledby, or heading must be provided"
126
- end
127
+ return unless heading?
128
+
129
+ @system_arguments[:"aria-labelledby"] = heading.title_id
130
+ @system_arguments[:"aria-describedby"] = heading.subtitle_id if heading.subtitle?
127
131
  end
128
132
 
129
133
  # @private
@@ -158,8 +162,24 @@ module Primer
158
162
  @system_arguments[:role] == :menu
159
163
  end
160
164
 
165
+ def required_form_arguments_given?
166
+ @form_builder && @input_name
167
+ end
168
+
169
+ def acts_as_form_input?
170
+ required_form_arguments_given? && allows_selection?
171
+ end
172
+
161
173
  # @private
162
174
  def will_add_item(_item); end
175
+
176
+ private
177
+
178
+ def with_post_list_content(&block)
179
+ @post_list_content_block = block
180
+ end
181
+
182
+ attr_reader :post_list_content_block
163
183
  end
164
184
  end
165
185
  end
@@ -1,5 +1,10 @@
1
1
  import '@github/include-fragment-element';
2
- declare type SelectVariant = 'single' | 'multiple' | null;
2
+ declare type SelectVariant = 'none' | 'single' | 'multiple' | null;
3
+ declare type SelectedItem = {
4
+ label: string | null | undefined;
5
+ value: string | null | undefined;
6
+ element: Element;
7
+ };
3
8
  export declare class ActionMenuElement extends HTMLElement {
4
9
  #private;
5
10
  get selectVariant(): SelectVariant;
@@ -11,6 +16,7 @@ export declare class ActionMenuElement extends HTMLElement {
11
16
  get popoverElement(): HTMLElement | null;
12
17
  get invokerElement(): HTMLElement | null;
13
18
  get invokerLabel(): HTMLElement | null;
19
+ get selectedItems(): SelectedItem[];
14
20
  connectedCallback(): void;
15
21
  disconnectedCallback(): void;
16
22
  handleEvent(event: Event): void;
@@ -9,7 +9,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _ActionMenuElement_instances, _ActionMenuElement_abortController, _ActionMenuElement_originalLabel, _ActionMenuElement_setDynamicLabel, _ActionMenuElement_isEnterKeydown, _ActionMenuElement_firstItem_get;
12
+ var _ActionMenuElement_instances, _ActionMenuElement_abortController, _ActionMenuElement_originalLabel, _ActionMenuElement_inputName, _ActionMenuElement_setDynamicLabel, _ActionMenuElement_updateInput, _ActionMenuElement_isEnterKeydown, _ActionMenuElement_firstItem_get;
13
13
  import '@github/include-fragment-element';
14
14
  const popoverSelector = (() => {
15
15
  try {
@@ -27,6 +27,7 @@ export class ActionMenuElement extends HTMLElement {
27
27
  _ActionMenuElement_instances.add(this);
28
28
  _ActionMenuElement_abortController.set(this, void 0);
29
29
  _ActionMenuElement_originalLabel.set(this, '');
30
+ _ActionMenuElement_inputName.set(this, '');
30
31
  }
31
32
  get selectVariant() {
32
33
  return this.getAttribute('data-select-variant');
@@ -73,6 +74,19 @@ export class ActionMenuElement extends HTMLElement {
73
74
  return null;
74
75
  return this.invokerElement.querySelector('.Button-label');
75
76
  }
77
+ get selectedItems() {
78
+ const selectedItems = this.querySelectorAll('[aria-checked=true]');
79
+ const results = [];
80
+ for (const selectedItem of selectedItems) {
81
+ const labelEl = selectedItem.querySelector('.ActionListItem-label');
82
+ results.push({
83
+ label: labelEl === null || labelEl === void 0 ? void 0 : labelEl.textContent,
84
+ value: selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.getAttribute('data-value'),
85
+ element: selectedItem
86
+ });
87
+ }
88
+ return results;
89
+ }
76
90
  connectedCallback() {
77
91
  const { signal } = (__classPrivateFieldSet(this, _ActionMenuElement_abortController, new AbortController(), "f"));
78
92
  this.addEventListener('keydown', this, { signal });
@@ -80,6 +94,7 @@ export class ActionMenuElement extends HTMLElement {
80
94
  this.addEventListener('mouseover', this, { signal });
81
95
  this.addEventListener('focusout', this, { signal });
82
96
  __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_setDynamicLabel).call(this);
97
+ __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_updateInput).call(this);
83
98
  }
84
99
  disconnectedCallback() {
85
100
  __classPrivateFieldGet(this, _ActionMenuElement_abortController, "f").abort();
@@ -116,6 +131,7 @@ export class ActionMenuElement extends HTMLElement {
116
131
  }
117
132
  __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_setDynamicLabel).call(this);
118
133
  }
134
+ __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_updateInput).call(this);
119
135
  if (event instanceof KeyboardEvent && event.target instanceof HTMLButtonElement) {
120
136
  // prevent buttons from being clicked twice
121
137
  event.preventDefault();
@@ -132,15 +148,15 @@ export class ActionMenuElement extends HTMLElement {
132
148
  }
133
149
  }
134
150
  }
135
- _ActionMenuElement_abortController = new WeakMap(), _ActionMenuElement_originalLabel = new WeakMap(), _ActionMenuElement_instances = new WeakSet(), _ActionMenuElement_setDynamicLabel = function _ActionMenuElement_setDynamicLabel() {
151
+ _ActionMenuElement_abortController = new WeakMap(), _ActionMenuElement_originalLabel = new WeakMap(), _ActionMenuElement_inputName = new WeakMap(), _ActionMenuElement_instances = new WeakSet(), _ActionMenuElement_setDynamicLabel = function _ActionMenuElement_setDynamicLabel() {
136
152
  if (!this.dynamicLabel)
137
153
  return;
138
154
  const invokerLabel = this.invokerLabel;
139
155
  if (!invokerLabel)
140
156
  return;
157
+ __classPrivateFieldSet(this, _ActionMenuElement_originalLabel, __classPrivateFieldGet(this, _ActionMenuElement_originalLabel, "f") || (invokerLabel.textContent || ''), "f");
141
158
  const itemLabel = this.querySelector('[aria-checked=true] .ActionListItem-label');
142
159
  if (itemLabel && this.dynamicLabel) {
143
- __classPrivateFieldSet(this, _ActionMenuElement_originalLabel, __classPrivateFieldGet(this, _ActionMenuElement_originalLabel, "f") || (invokerLabel.textContent || ''), "f");
144
160
  const prefixSpan = document.createElement('span');
145
161
  prefixSpan.classList.add('color-fg-muted');
146
162
  const contentSpan = document.createElement('span');
@@ -151,6 +167,42 @@ _ActionMenuElement_abortController = new WeakMap(), _ActionMenuElement_originalL
151
167
  else {
152
168
  invokerLabel.textContent = __classPrivateFieldGet(this, _ActionMenuElement_originalLabel, "f");
153
169
  }
170
+ }, _ActionMenuElement_updateInput = function _ActionMenuElement_updateInput() {
171
+ if (this.selectVariant === 'single') {
172
+ const input = this.querySelector(`[data-list-inputs=true] input`);
173
+ if (!input)
174
+ return;
175
+ const selectedItem = this.selectedItems[0];
176
+ if (selectedItem) {
177
+ input.value = (selectedItem.value || selectedItem.label || '').trim();
178
+ input.removeAttribute('disabled');
179
+ }
180
+ else {
181
+ input.setAttribute('disabled', 'disabled');
182
+ }
183
+ }
184
+ else if (this.selectVariant !== 'none') {
185
+ // multiple select variant
186
+ const inputList = this.querySelector('[data-list-inputs=true]');
187
+ if (!inputList)
188
+ return;
189
+ const inputs = inputList.querySelectorAll('input');
190
+ if (inputs.length > 0) {
191
+ __classPrivateFieldSet(this, _ActionMenuElement_inputName, __classPrivateFieldGet(this, _ActionMenuElement_inputName, "f") || inputs[0].name, "f");
192
+ }
193
+ for (const selectedItem of this.selectedItems) {
194
+ const newInput = document.createElement('input');
195
+ newInput.setAttribute('data-list-input', 'true');
196
+ newInput.type = 'hidden';
197
+ newInput.autocomplete = 'off';
198
+ newInput.name = __classPrivateFieldGet(this, _ActionMenuElement_inputName, "f");
199
+ newInput.value = (selectedItem.value || selectedItem.label || '').trim();
200
+ inputList.append(newInput);
201
+ }
202
+ for (const input of inputs) {
203
+ input.remove();
204
+ }
205
+ }
154
206
  }, _ActionMenuElement_isEnterKeydown = function _ActionMenuElement_isEnterKeydown(event) {
155
207
  return (event instanceof KeyboardEvent &&
156
208
  event.type === 'keydown' &&
@@ -9,13 +9,19 @@ const popoverSelector = (() => {
9
9
  }
10
10
  })()
11
11
 
12
- type SelectVariant = 'single' | 'multiple' | null
12
+ type SelectVariant = 'none' | 'single' | 'multiple' | null
13
+ type SelectedItem = {
14
+ label: string | null | undefined
15
+ value: string | null | undefined
16
+ element: Element
17
+ }
13
18
 
14
19
  const menuItemSelectors = ['[role="menuitem"]', '[role="menuitemcheckbox"]', '[role="menuitemradio"]']
15
20
 
16
21
  export class ActionMenuElement extends HTMLElement {
17
22
  #abortController: AbortController
18
23
  #originalLabel = ''
24
+ #inputName = ''
19
25
 
20
26
  get selectVariant(): SelectVariant {
21
27
  return this.getAttribute('data-select-variant') as SelectVariant
@@ -65,6 +71,23 @@ export class ActionMenuElement extends HTMLElement {
65
71
  return this.invokerElement.querySelector('.Button-label')
66
72
  }
67
73
 
74
+ get selectedItems(): SelectedItem[] {
75
+ const selectedItems = this.querySelectorAll('[aria-checked=true]')
76
+ const results: SelectedItem[] = []
77
+
78
+ for (const selectedItem of selectedItems) {
79
+ const labelEl = selectedItem.querySelector('.ActionListItem-label')
80
+
81
+ results.push({
82
+ label: labelEl?.textContent,
83
+ value: selectedItem?.getAttribute('data-value'),
84
+ element: selectedItem
85
+ })
86
+ }
87
+
88
+ return results
89
+ }
90
+
68
91
  connectedCallback() {
69
92
  const {signal} = (this.#abortController = new AbortController())
70
93
  this.addEventListener('keydown', this, {signal})
@@ -72,6 +95,7 @@ export class ActionMenuElement extends HTMLElement {
72
95
  this.addEventListener('mouseover', this, {signal})
73
96
  this.addEventListener('focusout', this, {signal})
74
97
  this.#setDynamicLabel()
98
+ this.#updateInput()
75
99
  }
76
100
 
77
101
  disconnectedCallback() {
@@ -108,6 +132,9 @@ export class ActionMenuElement extends HTMLElement {
108
132
  }
109
133
  this.#setDynamicLabel()
110
134
  }
135
+
136
+ this.#updateInput()
137
+
111
138
  if (event instanceof KeyboardEvent && event.target instanceof HTMLButtonElement) {
112
139
  // prevent buttons from being clicked twice
113
140
  event.preventDefault()
@@ -128,9 +155,9 @@ export class ActionMenuElement extends HTMLElement {
128
155
  if (!this.dynamicLabel) return
129
156
  const invokerLabel = this.invokerLabel
130
157
  if (!invokerLabel) return
158
+ this.#originalLabel ||= invokerLabel.textContent || ''
131
159
  const itemLabel = this.querySelector('[aria-checked=true] .ActionListItem-label')
132
160
  if (itemLabel && this.dynamicLabel) {
133
- this.#originalLabel ||= invokerLabel.textContent || ''
134
161
  const prefixSpan = document.createElement('span')
135
162
  prefixSpan.classList.add('color-fg-muted')
136
163
  const contentSpan = document.createElement('span')
@@ -142,6 +169,47 @@ export class ActionMenuElement extends HTMLElement {
142
169
  }
143
170
  }
144
171
 
172
+ #updateInput() {
173
+ if (this.selectVariant === 'single') {
174
+ const input = this.querySelector(`[data-list-inputs=true] input`) as HTMLInputElement | null
175
+ if (!input) return
176
+
177
+ const selectedItem = this.selectedItems[0]
178
+
179
+ if (selectedItem) {
180
+ input.value = (selectedItem.value || selectedItem.label || '').trim()
181
+ input.removeAttribute('disabled')
182
+ } else {
183
+ input.setAttribute('disabled', 'disabled')
184
+ }
185
+ } else if (this.selectVariant !== 'none') {
186
+ // multiple select variant
187
+ const inputList = this.querySelector('[data-list-inputs=true]')
188
+ if (!inputList) return
189
+
190
+ const inputs = inputList.querySelectorAll('input')
191
+
192
+ if (inputs.length > 0) {
193
+ this.#inputName ||= (inputs[0] as HTMLInputElement).name
194
+ }
195
+
196
+ for (const selectedItem of this.selectedItems) {
197
+ const newInput = document.createElement('input')
198
+ newInput.setAttribute('data-list-input', 'true')
199
+ newInput.type = 'hidden'
200
+ newInput.autocomplete = 'off'
201
+ newInput.name = this.#inputName
202
+ newInput.value = (selectedItem.value || selectedItem.label || '').trim()
203
+
204
+ inputList.append(newInput)
205
+ }
206
+
207
+ for (const input of inputs) {
208
+ input.remove()
209
+ }
210
+ }
211
+ }
212
+
145
213
  #isEnterKeydown(event: Event): boolean {
146
214
  return (
147
215
  event instanceof KeyboardEvent &&
@@ -12,18 +12,16 @@ module Primer
12
12
 
13
13
  # Adds a new item to the list.
14
14
  #
15
+ # @param data [Hash] When the menu is used as a form input (see the <%= link_to_component(Primer::Alpha::ActionMenu) %> docs), the label is submitted to the server by default. However, if the `data: { value: }` or `"data-value":` attribute is provided, it will be sent to the server instead.
15
16
  # @param system_arguments [Hash] The same arguments accepted by <%= link_to_component(Primer::Alpha::ActionList::Item) %>.
16
- def with_item(**system_arguments, &block)
17
+ def with_item(data: {}, **system_arguments, &block)
17
18
  content_arguments = system_arguments.delete(:content_arguments) || {}
18
19
 
19
- content_arguments[:tag] =
20
- if system_arguments[:tag] && ITEM_TAG_OPTIONS.include?(system_arguments[:tag])
21
- system_arguments[:tag]
22
- elsif system_arguments[:href] && !system_arguments[:disabled]
23
- :a
24
- else
25
- DEFAULT_ITEM_TAG
26
- end
20
+ # rubocop:disable Style/IfUnlessModifier
21
+ if system_arguments[:tag] && ITEM_TAG_OPTIONS.include?(system_arguments[:tag])
22
+ content_arguments[:tag] = system_arguments[:tag]
23
+ end
24
+ # rubocop:enable Style/IfUnlessModifier
27
25
 
28
26
  # disallow setting item's tag
29
27
  system_arguments.delete(:tag)
@@ -51,7 +49,7 @@ module Primer
51
49
  content_arguments[:disabled] = "" if content_arguments[:tag] == :button
52
50
  end
53
51
 
54
- super(**system_arguments, content_arguments: content_arguments) do |item|
52
+ super(data: data, **system_arguments, content_arguments: content_arguments) do |item|
55
53
  # Prevent double renders by using the capture method on the component
56
54
  # that originally received the block.
57
55
  #
@@ -70,7 +68,7 @@ module Primer
70
68
  end
71
69
 
72
70
  # @param menu_id [String] ID of the parent menu.
73
- # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
71
+ # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList) %>
74
72
  def initialize(menu_id:, **system_arguments, &block)
75
73
  @menu_id = menu_id
76
74