primer_view_components 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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