primer_view_components 0.1.0 → 0.1.2

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -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 +1 -1
  6. data/app/assets/styles/primer_view_components.css.map +1 -1
  7. data/app/components/primer/alpha/action_list/divider.rb +2 -2
  8. data/app/components/primer/alpha/action_list/heading.html.erb +1 -1
  9. data/app/components/primer/alpha/action_list/heading.rb +11 -5
  10. data/app/components/primer/alpha/action_list/item.rb +19 -15
  11. data/app/components/primer/alpha/action_list.html.erb +7 -8
  12. data/app/components/primer/alpha/action_list.rb +16 -11
  13. data/app/components/primer/alpha/nav_list/{section.rb → group.rb} +5 -5
  14. data/app/components/primer/alpha/nav_list/item.html.erb +1 -1
  15. data/app/components/primer/alpha/nav_list/item.rb +15 -1
  16. data/app/components/primer/alpha/nav_list.d.ts +1 -0
  17. data/app/components/primer/alpha/nav_list.html.erb +8 -8
  18. data/app/components/primer/alpha/nav_list.js +21 -0
  19. data/app/components/primer/alpha/nav_list.rb +30 -34
  20. data/app/components/primer/alpha/nav_list.ts +23 -0
  21. data/app/components/primer/alpha/navigation/tab.rb +168 -0
  22. data/app/components/primer/alpha/overlay/header.html.erb +2 -2
  23. data/app/components/primer/alpha/overlay.rb +29 -9
  24. data/app/components/primer/alpha/tab_nav.rb +10 -3
  25. data/app/components/primer/alpha/tab_panels.rb +2 -2
  26. data/app/components/primer/alpha/underline_nav.css +1 -1
  27. data/app/components/primer/alpha/underline_nav.css.map +1 -1
  28. data/app/components/primer/alpha/underline_nav.pcss +1 -0
  29. data/app/components/primer/alpha/underline_nav.rb +2 -2
  30. data/app/components/primer/alpha/underline_panels.rb +2 -2
  31. data/app/components/primer/beta/button.html.erb +1 -1
  32. data/app/components/primer/beta/button.rb +2 -1
  33. data/app/components/primer/component.rb +34 -0
  34. data/app/components/primer/navigation/tab_component.rb +3 -157
  35. data/app/components/primer/truncate.rb +1 -1
  36. data/lib/primer/deprecations.yml +9 -0
  37. data/lib/primer/forms/dsl/text_field_input.rb +1 -1
  38. data/lib/primer/forms/primer_text_field.js +17 -6
  39. data/lib/primer/forms/primer_text_field.ts +15 -7
  40. data/lib/primer/forms/text_field.html.erb +3 -3
  41. data/lib/primer/view_components/version.rb +1 -1
  42. data/lib/primer/yard/component_manifest.rb +2 -1
  43. data/lib/tasks/docs.rake +1 -1
  44. data/previews/primer/alpha/action_list_preview.rb +41 -29
  45. data/previews/primer/alpha/nav_list_preview/trailing_action.html.erb +19 -0
  46. data/previews/primer/alpha/nav_list_preview.rb +19 -30
  47. data/previews/primer/alpha/overlay_preview.rb +34 -4
  48. data/previews/primer/alpha/tab_nav_preview/with_extra.html.erb +8 -0
  49. data/previews/primer/alpha/tab_nav_preview.rb +5 -0
  50. data/previews/primer/alpha/tab_panels_preview/with_extra.html.erb +17 -0
  51. data/previews/primer/alpha/tab_panels_preview.rb +5 -0
  52. data/static/arguments.json +64 -8
  53. data/static/audited_at.json +2 -1
  54. data/static/constants.json +20 -8
  55. data/static/previews.json +20 -5
  56. data/static/statuses.json +4 -3
  57. metadata +10 -8
  58. data/app/components/primer/alpha/nav_list/section.html.erb +0 -3
  59. data/previews/primer/alpha/action_list_preview/heading.html.erb +0 -4
  60. /data/app/components/primer/{navigation/tab_component.html.erb → alpha/navigation/tab.html.erb} +0 -0
@@ -3,7 +3,7 @@
3
3
  module Primer
4
4
  module Alpha
5
5
  class ActionList
6
- # Section heading rendered above the section contents.
6
+ # Group heading rendered above the group contents.
7
7
  class Divider < Primer::Component
8
8
  DEFAULT_SCHEME = :subtle
9
9
  SCHEME_MAPPINGS = {
@@ -17,7 +17,7 @@ module Primer
17
17
  def initialize(scheme: DEFAULT_SCHEME, **system_arguments)
18
18
  @system_arguments = system_arguments
19
19
  @system_arguments[:tag] = :li
20
- @system_arguments[:role] = :separator
20
+ @system_arguments[:role] = :presentation
21
21
  @system_arguments[:'aria-hidden'] = true
22
22
  @scheme = fetch_or_fallback(SCHEME_OPTIONS, scheme, DEFAULT_SCHEME)
23
23
  @system_arguments[:classes] = class_names(
@@ -1,4 +1,4 @@
1
- <%= render(Primer::BaseComponent.new(**@system_arguments)) do %>
1
+ <%= render(Primer::BaseComponent.new(tag: :div, **@system_arguments)) do %>
2
2
  <%= render(Primer::BaseComponent.new(tag: @tag, classes: "ActionList-sectionDivider-title", id: @list_id)) do %>
3
3
  <%= @title %>
4
4
  <% end %>
@@ -11,21 +11,27 @@ module Primer
11
11
  :filled => "ActionList-sectionDivider--filled"
12
12
  }.freeze
13
13
  SCHEME_OPTIONS = SCHEME_MAPPINGS.keys.freeze
14
+ HEADING_MIN = 1
15
+ HEADING_MAX = 6
16
+ HEADING_LEVELS = (HEADING_MIN..HEADING_MAX).to_a.freeze
14
17
 
15
18
  # @param list_id [String] The unique identifier of the sub list the heading belongs to. Used internally.
16
19
  # @param title [String] Sub list title.
20
+ # @param heading_level [Integer] Heading level. Level 2 results in an `<h2>` tag, level 3 an `<h3>` tag, etc.
17
21
  # @param subtitle [String] Optional sub list description.
18
22
  # @param scheme [Symbol] Display a background color if scheme is `filled`.
19
- # @param tag [Symbol] Semantic tag for the heading.
23
+ # @param tag [Integer] Semantic tag for the heading.
20
24
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
21
- def initialize(list_id:, title:, tag: :h3, scheme: DEFAULT_SCHEME, subtitle: nil, **system_arguments)
22
- @tag = tag
23
- @system_arguments = system_arguments
25
+ def initialize(list_id:, title:, heading_level: 3, scheme: DEFAULT_SCHEME, subtitle: nil, **system_arguments)
26
+ raise "Heading level must be between #{HEADING_MIN} and #{HEADING_MAX}" unless HEADING_LEVELS.include?(heading_level)
27
+
28
+ @heading_level = heading_level
29
+ @tag = :"h#{heading_level}"
30
+ @system_arguments = deny_tag_argument(**system_arguments)
24
31
  @list_id = list_id
25
32
  @title = title
26
33
  @subtitle = subtitle
27
34
  @scheme = fetch_or_fallback(SCHEME_OPTIONS, scheme, DEFAULT_SCHEME)
28
- @system_arguments[:tag] = :li
29
35
  @system_arguments[:classes] = class_names(
30
36
  "ActionList-sectionDivider",
31
37
  SCHEME_MAPPINGS[@scheme],
@@ -84,12 +84,16 @@ module Primer
84
84
  # A button rendered after the trailing icon that can be used to show a menu, activate
85
85
  # a dialog, etc.
86
86
  #
87
- # @param show_on_hover [Boolean] Whether or not to show the button when the list item is hovered. If `true`, the button will be invisible until hovered. If `false`, the button will always be visible. Defaults to `false`.
88
87
  # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Beta::IconButton) %>.
89
- renders_one :trailing_action, lambda { |show_on_hover: false, **system_arguments|
90
- @trailing_action_on_hover = show_on_hover
88
+ renders_one :trailing_action, lambda { |**system_arguments|
89
+ Primer::Beta::IconButton.new(
90
+ classes: class_names(
91
+ system_arguments[:classes],
92
+ "ActionListItem-trailingAction"
93
+ ),
91
94
 
92
- Primer::Beta::IconButton.new(scheme: :invisible, classes: ["ActionListItem-trailingAction"], **system_arguments)
95
+ **system_arguments
96
+ )
93
97
  }
94
98
 
95
99
  # `Tooltip` that appears on mouse hover or keyboard focus over the trailing action button. Use tooltips sparingly and as
@@ -112,7 +116,7 @@ module Primer
112
116
  # @private
113
117
  renders_one :private_content
114
118
 
115
- attr_reader :list, :href, :active, :disabled, :parent
119
+ attr_reader :id, :list, :href, :active, :disabled, :parent
116
120
 
117
121
  # Whether or not this item is active.
118
122
  #
@@ -150,7 +154,7 @@ module Primer
150
154
  parent: nil,
151
155
  truncate_label: false,
152
156
  href: nil,
153
- role: :listitem,
157
+ role: nil,
154
158
  size: DEFAULT_SIZE,
155
159
  scheme: DEFAULT_SCHEME,
156
160
  disabled: false,
@@ -167,7 +171,6 @@ module Primer
167
171
  @truncate_label = truncate_label
168
172
  @disabled = disabled
169
173
  @active = active
170
- @trailing_action_on_hover = false
171
174
  @id = id
172
175
  @system_arguments = system_arguments
173
176
  @content_arguments = content_arguments
@@ -184,7 +187,7 @@ module Primer
184
187
  "ActionListItem"
185
188
  )
186
189
 
187
- @system_arguments[:role] = role
190
+ @system_arguments[:role] = role if role
188
191
 
189
192
  @system_arguments[:aria] ||= {}
190
193
  @system_arguments[:aria][:disabled] = "true" if @disabled
@@ -209,12 +212,14 @@ module Primer
209
212
  SIZE_MAPPINGS[@size]
210
213
  )
211
214
 
212
- if @href && !@disabled
213
- @content_arguments[:tag] = :a
214
- @content_arguments[:href] = @href
215
- else
216
- @content_arguments[:tag] = :button
217
- @content_arguments[:onclick] = on_click if on_click
215
+ unless @content_arguments[:tag]
216
+ if @href && !@disabled
217
+ @content_arguments[:tag] = :a
218
+ @content_arguments[:href] = @href
219
+ else
220
+ @content_arguments[:tag] = :button
221
+ @content_arguments[:onclick] = on_click if on_click
222
+ end
218
223
  end
219
224
 
220
225
  @description_wrapper_arguments = {
@@ -231,7 +236,6 @@ module Primer
231
236
  @system_arguments[:classes] = class_names(
232
237
  @system_arguments[:classes],
233
238
  "ActionListItem--withActions" => trailing_action.present?,
234
- "ActionListItem--trailingActionHover" => @trailing_action_on_hover,
235
239
  "ActionListItem--navActive" => active?
236
240
  )
237
241
 
@@ -1,15 +1,14 @@
1
- <%= render(Primer::BaseComponent.new(tag: :ul, role: @role, classes: "ActionListWrap")) do %>
1
+ <%= render(Primer::BaseComponent.new(tag: :div)) do %>
2
2
  <% if heading %>
3
3
  <%= heading %>
4
4
  <% end %>
5
- <%= render(Primer::BaseComponent.new(tag: :li, **@list_wrapper_arguments)) do %>
6
- <%= render(Primer::BaseComponent.new(tag: :ul, **@system_arguments)) do %>
7
- <% items.each_with_index do |item, index| %>
8
- <% if index > 0 && @show_dividers %>
9
- <%= render(Primer::Alpha::ActionList::Divider.new) %>
10
- <% end %>
11
- <%= item %>
5
+ <%= render(Primer::BaseComponent.new(tag: :ul, **@system_arguments)) do %>
6
+ <% items.each_with_index do |item, index| %>
7
+ <%# the conditions here make sure two dividers are never rendered one after the other %>
8
+ <% if index > 0 && @show_dividers && !item.is_a?(Divider) && !items[index - 1].is_a?(Divider) %>
9
+ <%= render(Primer::Alpha::ActionList::Divider.new) %>
12
10
  <% end %>
11
+ <%= item %>
13
12
  <% end %>
14
13
  <% end %>
15
14
  <% end %>
@@ -48,6 +48,15 @@ module Primer
48
48
  end
49
49
  }
50
50
 
51
+ # Adds a divider to the list of items.
52
+ #
53
+ # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList::Divider) %>.
54
+ def with_divider(**system_arguments, &block)
55
+ # This is a giant hack that should be removed when :items can be converted into a polymorphic slot.
56
+ # This feature needs to land in view_component first: https://github.com/ViewComponent/view_component/pull/1652
57
+ set_slot(:items, { renderable: Divider, collection: true }, **system_arguments, &block)
58
+ end
59
+
51
60
  # @param role [Boolean] ARIA role describing the function of the list. listbox and menu are a common values.
52
61
  # @param item_classes [String] Additional CSS classes to attach to items.
53
62
  # @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.
@@ -61,10 +70,10 @@ module Primer
61
70
  **system_arguments
62
71
  )
63
72
  @id = self.class.generate_id
64
- @role = role
65
73
 
66
74
  @system_arguments = system_arguments
67
75
  @system_arguments[:tag] = :ul
76
+ @system_arguments[:role] = role
68
77
  @item_classes = item_classes
69
78
  @scheme = fetch_or_fallback(SCHEME_OPTIONS, scheme, DEFAULT_SCHEME)
70
79
  @show_dividers = show_dividers
@@ -72,7 +81,6 @@ module Primer
72
81
  SCHEME_MAPPINGS[@scheme],
73
82
  system_arguments[:classes],
74
83
  "ActionListWrap",
75
- "ActionListWrap--subGroup",
76
84
  "ActionListWrap--divided" => @show_dividers
77
85
  )
78
86
 
@@ -81,18 +89,15 @@ module Primer
81
89
 
82
90
  # @private
83
91
  def before_render
92
+ aria_label = aria(:label, @system_arguments)
93
+ aria_labelledby = aria(:labelledby, @system_arguments)
94
+
84
95
  if heading.present?
85
96
  @system_arguments[:"aria-labelledby"] = @id
86
- elsif aria(:label, @system_arguments).blank?
87
- raise ArgumentError, "An aria-label or heading must be provided"
97
+ raise ArgumentError, "An aria-label should not be provided if a heading is present" if aria_label.present?
98
+ elsif aria_label.blank? && aria_labelledby.blank?
99
+ raise ArgumentError, "An aria-label, aria-labelledby, or heading must be provided"
88
100
  end
89
-
90
- return if items.blank?
91
-
92
- @list_wrapper_arguments[:classes] = class_names(
93
- @list_wrapper_arguments[:classes],
94
- "ActionListItem--hasSubItem"
95
- )
96
101
  end
97
102
 
98
103
  # @private
@@ -6,10 +6,10 @@ module Primer
6
6
  # A logical grouping of navigation links with an optional heading.
7
7
  #
8
8
  # See <%= link_to_component(Primer::Alpha::NavList) %> for usage examples.
9
- class Section < ActionList
10
- # A special "show more" list item that appears at the bottom of the section. Clicking
9
+ class Group < ActionList
10
+ # A special "show more" list item that appears at the bottom of the group. Clicking
11
11
  # the item will fetch the next page of results from the URL passed in the `src` argument
12
- # and append the resulting chunk of HTML to the section.
12
+ # and append the resulting chunk of HTML to the group.
13
13
  #
14
14
  # @param src [String] The URL to query for additional pages of list items.
15
15
  # @param pages [Integer] The total number of pages in the result set.
@@ -51,14 +51,14 @@ module Primer
51
51
  super(**@system_arguments)
52
52
  end
53
53
 
54
- # Cause this section to show its list of sub items when rendered.
54
+ # Cause this group to show its list of sub items when rendered.
55
55
  # :nocov:
56
56
  def expand!
57
57
  @expanded = true
58
58
  end
59
59
  # :nocov:
60
60
 
61
- # The items contained within this section.
61
+ # The items contained within this group.
62
62
  #
63
63
  # @return [Array<Primer::Alpha::ActionList::Item>]
64
64
  def items
@@ -1,7 +1,7 @@
1
1
  <% with_private_content do %>
2
2
  <% unless items.empty? %>
3
3
  <% capture do %>
4
- <%= render(Primer::BaseComponent.new(tag: :ul, **@sub_list_arguments)) do %>
4
+ <%= render(Primer::BaseComponent.new(tag: :ul, role: :list, **@sub_list_arguments)) do %>
5
5
  <% items.each do |item| %>
6
6
  <%= item %>
7
7
  <% end %>
@@ -47,6 +47,10 @@ module Primer
47
47
  )
48
48
  }
49
49
 
50
+ @list = system_arguments[:list]
51
+
52
+ @sub_list_arguments["data-action"] = "keydown:#{@list.custom_element_name}#handleItemWithSubItemKeydown" if @list
53
+
50
54
  overrides = { "data-item-id": @selected_by_ids.join(" ") }
51
55
 
52
56
  super(**system_arguments, **overrides)
@@ -79,9 +83,19 @@ module Primer
79
83
 
80
84
  return if items.blank?
81
85
 
86
+ @sub_list_arguments[:aria] = merge_aria(
87
+ @sub_list_arguments,
88
+ { aria: { labelledby: id } }
89
+ )
90
+
91
+ raise ArgumentError, "Items with sub-items cannot have hrefs" if href.present?
92
+
82
93
  @content_arguments[:tag] = :button
83
94
  @content_arguments[:"aria-expanded"] = @expanded.to_s
84
- @content_arguments[:"data-action"] = "click:#{@list.custom_element_name}#handleItemWithSubItemClick"
95
+ @content_arguments[:"data-action"] = "
96
+ click:#{@list.custom_element_name}#handleItemWithSubItemClick
97
+ keydown:#{@list.custom_element_name}#handleItemWithSubItemKeydown
98
+ "
85
99
 
86
100
  with_private_trailing_action_icon(:"chevron-down", classes: "ActionListItem-collapseIcon")
87
101
 
@@ -18,6 +18,7 @@ export declare class NavListElement extends HTMLElement {
18
18
  collapseItem(item: HTMLElement): void;
19
19
  itemIsExpanded(item: HTMLElement | null): boolean;
20
20
  handleItemWithSubItemClick(e: Event): void;
21
+ handleItemWithSubItemKeydown(e: KeyboardEvent): void;
21
22
  private showMore;
22
23
  private setShowMoreItemState;
23
24
  }
@@ -1,10 +1,10 @@
1
- <%= render(Primer::BaseComponent.new(tag: :ul, **@system_arguments)) do %>
2
- <% sections.each_with_index do |section, index| %>
3
- <% if index > 0 %>
4
- <%= render(Primer::Alpha::ActionList::Divider.new) %>
1
+ <%= render(Primer::BaseComponent.new(tag: :nav, **@system_arguments)) do %>
2
+ <nav-list>
3
+ <% groups.each_with_index do |group, index| %>
4
+ <% if index > 0 %>
5
+ <%= render(Primer::Alpha::ActionList::Divider.new) %>
6
+ <% end %>
7
+ <%= group %>
5
8
  <% end %>
6
- <li>
7
- <%= section %>
8
- </li>
9
- <% end %>
9
+ </nav-list>
10
10
  <% end %>
@@ -82,6 +82,7 @@ let NavListElement = class NavListElement extends HTMLElement {
82
82
  var _a;
83
83
  (_a = item.nextElementSibling) === null || _a === void 0 ? void 0 : _a.setAttribute('data-hidden', '');
84
84
  item.setAttribute('aria-expanded', 'false');
85
+ item.focus();
85
86
  }
86
87
  itemIsExpanded(item) {
87
88
  if ((item === null || item === void 0 ? void 0 : item.tagName) === 'A') {
@@ -105,6 +106,26 @@ let NavListElement = class NavListElement extends HTMLElement {
105
106
  }
106
107
  e.stopPropagation();
107
108
  }
109
+ // collapse item
110
+ handleItemWithSubItemKeydown(e) {
111
+ const el = e.currentTarget;
112
+ if (!(el instanceof HTMLElement))
113
+ return;
114
+ let button = el.closest('button');
115
+ if (!button) {
116
+ const button_id = el.getAttribute('aria-labelledby');
117
+ if (button_id) {
118
+ button = document.getElementById(button_id);
119
+ }
120
+ else {
121
+ return;
122
+ }
123
+ }
124
+ if (this.itemIsExpanded(button) && e.key === 'Escape') {
125
+ this.collapseItem(button);
126
+ }
127
+ e.stopPropagation();
128
+ }
108
129
  async showMore(e) {
109
130
  var _a, _b;
110
131
  e.preventDefault();
@@ -3,13 +3,13 @@
3
3
  module Primer
4
4
  module Alpha
5
5
  # `NavList` provides a simple way to render side navigation, i.e. navigation
6
- # that appears to the left or right side of some main content. Each section in a
6
+ # that appears to the left or right side of some main content. Each group in a
7
7
  # nav list is a list of links.
8
8
  #
9
- # Nav list sections can contain sub items. Rather than navigating to a URL, sections
9
+ # Nav list groups can contain sub items. Rather than navigating to a URL, groups
10
10
  # with sub items expand and collapse on click. To indicate this functionality, the
11
- # section will automatically render with a trailing chevron icon that changes direction
12
- # when the section expands and collapses.
11
+ # group will automatically render with a trailing chevron icon that changes direction
12
+ # when the group expands and collapses.
13
13
  #
14
14
  # Nav list items appear visually active when selected. Each nav item must have one
15
15
  # or more ID values that determine which item will appear selected. Use the
@@ -22,40 +22,40 @@ module Primer
22
22
  "nav-list"
23
23
  end
24
24
 
25
- # Sections. Each section is a list of links and an optional heading.
25
+ # Groups. Each group is a list of links and an optional heading.
26
26
  #
27
- # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::NavList::Section) %>.
28
- renders_many :sections, lambda { |**system_arguments|
29
- Primer::Alpha::NavList::Section.new(selected_item_id: @selected_item_id, **system_arguments)
27
+ # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::NavList::Group) %>.
28
+ renders_many :groups, lambda { |**system_arguments|
29
+ Primer::Alpha::NavList::Group.new(selected_item_id: @selected_item_id, **system_arguments)
30
30
  }
31
31
 
32
32
  # @example Items and headings
33
33
  #
34
34
  # <%= render(Primer::Alpha::NavList.new(selected_item_id: :personal_info)) do |component| %>
35
- # <% component.with_section(aria: { label: "Settings" }) do |section| %>
36
- # <% section.with_item(label: "General", selected_by_ids: :general, href: "/settings/general") %>
35
+ # <% component.with_group(aria: { label: "Settings" }) do |group| %>
36
+ # <% group.with_item(label: "General", selected_by_ids: :general, href: "/settings/general") %>
37
37
  # <% end %>
38
- # <% component.with_section(aria: { label: "Account settings" }) do |section| %>
39
- # <% section.with_heading(title: "Account Settings") %>
40
- # <% section.with_item(label: "Personal Information", selected_by_ids: :personal_info, href: "/account/info") %>
41
- # <% section.with_item(label: "Password", selected_by_ids: :password, href: "/account/password") %>
42
- # <% section.with_item(label: "Billing info", selected_by_ids: :billing, href: "/account/billing") %>
38
+ # <% component.with_group do |group| %>
39
+ # <% group.with_heading(title: "Account Settings") %>
40
+ # <% group.with_item(label: "Personal Information", selected_by_ids: :personal_info, href: "/account/info") %>
41
+ # <% group.with_item(label: "Password", selected_by_ids: :password, href: "/account/password") %>
42
+ # <% group.with_item(label: "Billing info", selected_by_ids: :billing, href: "/account/billing") %>
43
43
  # <% end %>
44
44
  # <% end %>
45
45
  #
46
46
  # @example Leading and trailing visuals
47
47
  #
48
48
  # <%= render(Primer::Alpha::NavList.new(selected_item_id: :personal_info)) do |component| %>
49
- # <% component.with_section(aria: { label: "Account settings" }) do |section| %>
50
- # <% section.with_heading(title: "Account Settings") %>
51
- # <% section.with_item(label: "Personal Information", selected_by_ids: :personal_info, href: "/account/info") do |item| %>
49
+ # <% component.with_group do |group| %>
50
+ # <% group.with_heading(title: "Account Settings") %>
51
+ # <% group.with_item(label: "Personal Information", selected_by_ids: :personal_info, href: "/account/info") do |item| %>
52
52
  # <% item.with_leading_visual_avatar(src: "https://github.com/github.png", alt: "GitHub") %>
53
53
  # <% end %>
54
- # <% section.with_item(label: "Notifications", selected_by_ids: :notifications, href: "/account/notifications") do |item| %>
54
+ # <% group.with_item(label: "Notifications", selected_by_ids: :notifications, href: "/account/notifications") do |item| %>
55
55
  # <% item.with_leading_visual_icon(icon: :bell) %>
56
56
  # <% item.with_trailing_visual_counter(count: 15) %>
57
57
  # <% end %>
58
- # <% section.with_item(label: "Billing info", selected_by_ids: :billing, href: "/account/billing") do |item| %>
58
+ # <% group.with_item(label: "Billing info", selected_by_ids: :billing, href: "/account/billing") do |item| %>
59
59
  # <% item.with_leading_visual_icon(icon: :package) %>
60
60
  # <% item.with_trailing_visual_icon(icon: :"dot-fill", color: :attention) %>
61
61
  # <% end %>
@@ -65,9 +65,9 @@ module Primer
65
65
  # @example Expandable sub items
66
66
  #
67
67
  # <%= render(Primer::Alpha::NavList.new(selected_item_id: :email_notifications)) do |component| %>
68
- # <% component.with_section(aria: { label: "Account settings" }) do |section| %>
69
- # <% section.with_heading(title: "Account Settings") %>
70
- # <% section.with_item(label: "Notification settings") do |item| %>
68
+ # <% component.with_group do |group| %>
69
+ # <% group.with_heading(title: "Account Settings") %>
70
+ # <% group.with_item(label: "Notification settings") do |item| %>
71
71
  # <% item.with_leading_visual_icon(icon: :bell) %>
72
72
  # <% item.with_item(label: "Email", selected_by_ids: :email_notifications, href: "/account/notifications/email") do |subitem| %>
73
73
  # <% subitem.with_trailing_visual_icon(icon: :mail) %>
@@ -76,7 +76,7 @@ module Primer
76
76
  # <% subitem.with_trailing_visual_icon(icon: :"device-mobile") %>
77
77
  # <% end %>
78
78
  # <% end %>
79
- # <% section.with_item(label: "Messages") do |item| %>
79
+ # <% group.with_item(label: "Messages") do |item| %>
80
80
  # <% item.with_leading_visual_icon(icon: :bookmark) %>
81
81
  # <% item.with_item(label: "Inbox", href: "/account/messages/inbox") do |subitem| %>
82
82
  # <% subitem.with_trailing_visual_counter(count: 10) %>
@@ -91,13 +91,13 @@ module Primer
91
91
  # @example Trailing action
92
92
  #
93
93
  # <%= render(Primer::Alpha::NavList.new) do |component| %>
94
- # <% component.with_section(aria: { label: "Favorite foods" }) do |section| %>
95
- # <% section.with_heading(title: "My Favorite Foods") %>
96
- # <% section.with_item(label: "Popplers", selected_by_ids: :popplers, href: "/foods/popplers") do |item| %>
97
- # <% item.with_trailing_action(show_on_hover: false, icon: "plus", "aria-label": "Add new food", size: :medium) %>
94
+ # <% component.with_group do |group| %>
95
+ # <% group.with_heading(title: "My Favorite Foods") %>
96
+ # <% group.with_item(label: "Popplers", selected_by_ids: :popplers, href: "/foods/popplers") do |item| %>
97
+ # <% item.with_trailing_action(icon: "plus", "aria-label": "Add new food", size: :medium) %>
98
98
  # <% end %>
99
- # <% section.with_item(label: "Slurm", selected_by_ids: :slurm, href: "/foods/slurm") do |item| %>
100
- # <% item.with_trailing_action(show_on_hover: true, icon: "plus", "aria-label": "Add new food", size: :medium) %>
99
+ # <% group.with_item(label: "Slurm", selected_by_ids: :slurm, href: "/foods/slurm") do |item| %>
100
+ # <% item.with_trailing_action(icon: "plus", "aria-label": "Add new food", size: :medium) %>
101
101
  # <% end %>
102
102
  # <% end %>
103
103
  # <% end %>
@@ -106,10 +106,6 @@ module Primer
106
106
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
107
107
  def initialize(selected_item_id: nil, **system_arguments)
108
108
  @system_arguments = system_arguments
109
- @system_arguments[:classes] = class_names(
110
- @system_arguments[:classes],
111
- "ActionListWrap"
112
- )
113
109
  @selected_item_id = selected_item_id
114
110
  end
115
111
  end
@@ -87,6 +87,7 @@ export class NavListElement extends HTMLElement {
87
87
  collapseItem(item: HTMLElement) {
88
88
  item.nextElementSibling?.setAttribute('data-hidden', '')
89
89
  item.setAttribute('aria-expanded', 'false')
90
+ item.focus()
90
91
  }
91
92
 
92
93
  itemIsExpanded(item: HTMLElement | null) {
@@ -112,6 +113,28 @@ export class NavListElement extends HTMLElement {
112
113
  e.stopPropagation()
113
114
  }
114
115
 
116
+ // collapse item
117
+ handleItemWithSubItemKeydown(e: KeyboardEvent) {
118
+ const el = e.currentTarget
119
+ if (!(el instanceof HTMLElement)) return
120
+
121
+ let button = el.closest<HTMLButtonElement>('button')
122
+ if (!button) {
123
+ const button_id = el.getAttribute('aria-labelledby')
124
+ if (button_id) {
125
+ button = document.getElementById(button_id) as HTMLButtonElement
126
+ } else {
127
+ return
128
+ }
129
+ }
130
+
131
+ if (this.itemIsExpanded(button) && e.key === 'Escape') {
132
+ this.collapseItem(button)
133
+ }
134
+
135
+ e.stopPropagation()
136
+ }
137
+
115
138
  private async showMore(e: Event) {
116
139
  e.preventDefault()
117
140
  if (this.showMoreDisabled) return