primer_view_components 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/app/assets/javascripts/primer_view_components.js +1 -1
- data/app/assets/javascripts/primer_view_components.js.map +1 -1
- data/app/assets/styles/primer_view_components.css +1 -1
- data/app/assets/styles/primer_view_components.css.map +1 -1
- data/app/components/primer/alpha/action_list/divider.rb +2 -2
- data/app/components/primer/alpha/action_list/heading.html.erb +1 -1
- data/app/components/primer/alpha/action_list/heading.rb +10 -4
- data/app/components/primer/alpha/action_list/item.rb +3 -3
- data/app/components/primer/alpha/action_list.html.erb +6 -8
- data/app/components/primer/alpha/action_list.rb +5 -10
- data/app/components/primer/alpha/nav_list/{section.rb → group.rb} +5 -5
- data/app/components/primer/alpha/nav_list/item.html.erb +1 -1
- data/app/components/primer/alpha/nav_list/item.rb +15 -1
- data/app/components/primer/alpha/nav_list.d.ts +1 -0
- data/app/components/primer/alpha/nav_list.html.erb +8 -8
- data/app/components/primer/alpha/nav_list.js +21 -0
- data/app/components/primer/alpha/nav_list.rb +28 -32
- data/app/components/primer/alpha/nav_list.ts +23 -0
- data/app/components/primer/alpha/navigation/tab.rb +168 -0
- data/app/components/primer/alpha/overlay.rb +19 -6
- data/app/components/primer/alpha/tab_nav.rb +10 -3
- data/app/components/primer/alpha/tab_panels.rb +2 -2
- data/app/components/primer/alpha/underline_nav.css +1 -1
- data/app/components/primer/alpha/underline_nav.css.map +1 -1
- data/app/components/primer/alpha/underline_nav.pcss +1 -0
- data/app/components/primer/alpha/underline_nav.rb +2 -2
- data/app/components/primer/alpha/underline_panels.rb +2 -2
- data/app/components/primer/beta/button.html.erb +1 -1
- data/app/components/primer/beta/button.rb +2 -1
- data/app/components/primer/component.rb +34 -0
- data/app/components/primer/navigation/tab_component.rb +3 -157
- data/lib/primer/deprecations.yml +4 -0
- data/lib/primer/view_components/version.rb +1 -1
- data/lib/primer/yard/component_manifest.rb +2 -1
- data/lib/tasks/docs.rake +1 -1
- data/previews/primer/alpha/action_list_preview.rb +6 -14
- data/previews/primer/alpha/nav_list_preview/trailing_action.html.erb +19 -0
- data/previews/primer/alpha/nav_list_preview.rb +19 -30
- data/previews/primer/alpha/overlay_preview.rb +7 -2
- data/previews/primer/alpha/tab_nav_preview/with_extra.html.erb +8 -0
- data/previews/primer/alpha/tab_nav_preview.rb +5 -0
- data/previews/primer/alpha/tab_panels_preview/with_extra.html.erb +17 -0
- data/previews/primer/alpha/tab_panels_preview.rb +5 -0
- data/static/arguments.json +63 -7
- data/static/audited_at.json +2 -1
- data/static/constants.json +20 -8
- data/static/previews.json +10 -0
- data/static/statuses.json +3 -2
- metadata +8 -6
- data/app/components/primer/alpha/nav_list/section.html.erb +0 -3
- data/previews/primer/alpha/action_list_preview/heading.html.erb +0 -4
- /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
|
-
#
|
6
|
+
# Group heading rendered above the group contents.
|
7
7
|
class Divider < Primer::Component
|
8
8
|
DEFAULT_SCHEME = :subtle
|
9
9
|
SCHEME_MAPPINGS = {
|
@@ -16,7 +16,7 @@ module Primer
|
|
16
16
|
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
|
17
17
|
def initialize(scheme: DEFAULT_SCHEME, **system_arguments)
|
18
18
|
@system_arguments = system_arguments
|
19
|
-
@system_arguments[:tag] = :
|
19
|
+
@system_arguments[:tag] = :div
|
20
20
|
@system_arguments[:role] = :separator
|
21
21
|
@system_arguments[:'aria-hidden'] = true
|
22
22
|
@scheme = fetch_or_fallback(SCHEME_OPTIONS, scheme, DEFAULT_SCHEME)
|
@@ -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 [
|
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:,
|
22
|
-
|
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}"
|
23
30
|
@system_arguments = 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],
|
@@ -112,7 +112,7 @@ module Primer
|
|
112
112
|
# @private
|
113
113
|
renders_one :private_content
|
114
114
|
|
115
|
-
attr_reader :list, :href, :active, :disabled, :parent
|
115
|
+
attr_reader :id, :list, :href, :active, :disabled, :parent
|
116
116
|
|
117
117
|
# Whether or not this item is active.
|
118
118
|
#
|
@@ -150,7 +150,7 @@ module Primer
|
|
150
150
|
parent: nil,
|
151
151
|
truncate_label: false,
|
152
152
|
href: nil,
|
153
|
-
role:
|
153
|
+
role: nil,
|
154
154
|
size: DEFAULT_SIZE,
|
155
155
|
scheme: DEFAULT_SCHEME,
|
156
156
|
disabled: false,
|
@@ -184,7 +184,7 @@ module Primer
|
|
184
184
|
"ActionListItem"
|
185
185
|
)
|
186
186
|
|
187
|
-
@system_arguments[:role] = role
|
187
|
+
@system_arguments[:role] = role if role
|
188
188
|
|
189
189
|
@system_arguments[:aria] ||= {}
|
190
190
|
@system_arguments[:aria][:disabled] = "true" if @disabled
|
@@ -1,15 +1,13 @@
|
|
1
|
-
<%= render(Primer::BaseComponent.new(tag: :
|
1
|
+
<%= render(Primer::BaseComponent.new(tag: :div)) do %>
|
2
2
|
<% if heading %>
|
3
3
|
<%= heading %>
|
4
4
|
<% end %>
|
5
|
-
<%= render(Primer::BaseComponent.new(tag: :
|
6
|
-
|
7
|
-
<%
|
8
|
-
|
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
|
+
<% if index > 0 && @show_dividers %>
|
8
|
+
<%= render(Primer::Alpha::ActionList::Divider.new) %>
|
12
9
|
<% end %>
|
10
|
+
<%= item %>
|
13
11
|
<% end %>
|
14
12
|
<% end %>
|
15
13
|
<% end %>
|
@@ -61,10 +61,10 @@ module Primer
|
|
61
61
|
**system_arguments
|
62
62
|
)
|
63
63
|
@id = self.class.generate_id
|
64
|
-
@role = role
|
65
64
|
|
66
65
|
@system_arguments = system_arguments
|
67
66
|
@system_arguments[:tag] = :ul
|
67
|
+
@system_arguments[:role] = role
|
68
68
|
@item_classes = item_classes
|
69
69
|
@scheme = fetch_or_fallback(SCHEME_OPTIONS, scheme, DEFAULT_SCHEME)
|
70
70
|
@show_dividers = show_dividers
|
@@ -72,7 +72,6 @@ module Primer
|
|
72
72
|
SCHEME_MAPPINGS[@scheme],
|
73
73
|
system_arguments[:classes],
|
74
74
|
"ActionListWrap",
|
75
|
-
"ActionListWrap--subGroup",
|
76
75
|
"ActionListWrap--divided" => @show_dividers
|
77
76
|
)
|
78
77
|
|
@@ -81,18 +80,14 @@ module Primer
|
|
81
80
|
|
82
81
|
# @private
|
83
82
|
def before_render
|
83
|
+
aria_label = aria(:label, @system_arguments)
|
84
|
+
|
84
85
|
if heading.present?
|
85
86
|
@system_arguments[:"aria-labelledby"] = @id
|
86
|
-
|
87
|
+
raise ArgumentError, "An aria-label should not be provided if a heading is present" if aria_label.present?
|
88
|
+
elsif aria_label.blank?
|
87
89
|
raise ArgumentError, "An aria-label or heading must be provided"
|
88
90
|
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
91
|
end
|
97
92
|
|
98
93
|
# @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
|
10
|
-
# A special "show more" list item that appears at the bottom of the
|
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
|
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
|
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
|
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"] = "
|
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: :
|
2
|
-
|
3
|
-
<%
|
4
|
-
|
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
|
-
|
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
|
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
|
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
|
-
#
|
12
|
-
# when the
|
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
|
-
#
|
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::
|
28
|
-
renders_many :
|
29
|
-
Primer::Alpha::NavList::
|
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.
|
36
|
-
# <%
|
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.
|
39
|
-
# <%
|
40
|
-
# <%
|
41
|
-
# <%
|
42
|
-
# <%
|
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.
|
50
|
-
# <%
|
51
|
-
# <%
|
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
|
-
# <%
|
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
|
-
# <%
|
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.
|
69
|
-
# <%
|
70
|
-
# <%
|
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
|
-
# <%
|
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,12 +91,12 @@ module Primer
|
|
91
91
|
# @example Trailing action
|
92
92
|
#
|
93
93
|
# <%= render(Primer::Alpha::NavList.new) do |component| %>
|
94
|
-
# <% component.
|
95
|
-
# <%
|
96
|
-
# <%
|
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
97
|
# <% item.with_trailing_action(show_on_hover: false, icon: "plus", "aria-label": "Add new food", size: :medium) %>
|
98
98
|
# <% end %>
|
99
|
-
# <%
|
99
|
+
# <% group.with_item(label: "Slurm", selected_by_ids: :slurm, href: "/foods/slurm") do |item| %>
|
100
100
|
# <% item.with_trailing_action(show_on_hover: true, icon: "plus", "aria-label": "Add new food", size: :medium) %>
|
101
101
|
# <% end %>
|
102
102
|
# <% 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
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Primer
|
4
|
+
module Alpha
|
5
|
+
module Navigation
|
6
|
+
# This component is part of navigation components such as `Primer::Alpha::TabNav`
|
7
|
+
# and `Primer::Alpha::UnderlineNav` and should not be used by itself.
|
8
|
+
#
|
9
|
+
# @accessibility
|
10
|
+
# `Tab` renders the selected anchor tab with `aria-current="page"` by default.
|
11
|
+
# When the selected tab does not correspond to the current page, such as in a nested inner tab, make sure to use aria-current="true"
|
12
|
+
class Tab < Primer::Component
|
13
|
+
status :alpha
|
14
|
+
|
15
|
+
DEFAULT_ARIA_CURRENT_FOR_ANCHOR = :page
|
16
|
+
ARIA_CURRENT_OPTIONS_FOR_ANCHOR = [true, DEFAULT_ARIA_CURRENT_FOR_ANCHOR].freeze
|
17
|
+
# Panel controlled by the Tab. This will not render anything in the tab itself.
|
18
|
+
# It will provide a accessor for the Tab's parent to call and render the panel
|
19
|
+
# content in the appropriate place.
|
20
|
+
# Refer to `UnderlineNav` and `TabNav` implementations for examples.
|
21
|
+
#
|
22
|
+
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
|
23
|
+
renders_one :panel, lambda { |**system_arguments|
|
24
|
+
return unless @with_panel
|
25
|
+
|
26
|
+
deny_tag_argument(**system_arguments)
|
27
|
+
system_arguments[:id] = @panel_id
|
28
|
+
system_arguments[:tag] = :div
|
29
|
+
system_arguments[:role] ||= :tabpanel
|
30
|
+
system_arguments[:tabindex] = 0
|
31
|
+
system_arguments[:hidden] = true unless @selected
|
32
|
+
|
33
|
+
label_present = aria("label", system_arguments) || aria("labelledby", system_arguments)
|
34
|
+
unless label_present
|
35
|
+
if @id.present?
|
36
|
+
system_arguments[:"aria-labelledby"] = @id
|
37
|
+
elsif !Rails.env.production?
|
38
|
+
raise ArgumentError, "Panels must be labelled. Either set a unique `id` on the tab, or set an `aria-label` directly on the panel"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
Primer::BaseComponent.new(**system_arguments)
|
43
|
+
}
|
44
|
+
|
45
|
+
# Icon to be rendered in the Tab left.
|
46
|
+
#
|
47
|
+
# @param kwargs [Hash] The same arguments as <%= link_to_component(Primer::Beta::Octicon) %>.
|
48
|
+
renders_one :icon, lambda { |icon = nil, **system_arguments|
|
49
|
+
system_arguments[:classes] = class_names(
|
50
|
+
@icon_classes,
|
51
|
+
system_arguments[:classes]
|
52
|
+
)
|
53
|
+
Primer::Beta::Octicon.new(icon, **system_arguments)
|
54
|
+
}
|
55
|
+
|
56
|
+
# The Tab's text.
|
57
|
+
#
|
58
|
+
# @param kwargs [Hash] The same arguments as <%= link_to_component(Primer::Beta::Text) %>.
|
59
|
+
renders_one :text, Primer::Beta::Text
|
60
|
+
|
61
|
+
# Counter to be rendered in the Tab right.
|
62
|
+
#
|
63
|
+
# @param kwargs [Hash] The same arguments as <%= link_to_component(Primer::Beta::Counter) %>.
|
64
|
+
renders_one :counter, Primer::Beta::Counter
|
65
|
+
|
66
|
+
attr_reader :selected
|
67
|
+
|
68
|
+
# @example Default
|
69
|
+
# <%= render(Primer::Alpha::Navigation::Tab.new(selected: true)) do |component| %>
|
70
|
+
# <% component.with_text { "Selected" } %>
|
71
|
+
# <% end %>
|
72
|
+
# <%= render(Primer::Alpha::Navigation::Tab.new) do |component| %>
|
73
|
+
# <% component.with_text { "Not selected" } %>
|
74
|
+
# <% end %>
|
75
|
+
#
|
76
|
+
# @example With icons and counters
|
77
|
+
# <%= render(Primer::Alpha::Navigation::Tab.new) do |component| %>
|
78
|
+
# <% component.with_icon(:star) %>
|
79
|
+
# <% component.with_text { "Tab" } %>
|
80
|
+
# <% end %>
|
81
|
+
# <%= render(Primer::Alpha::Navigation::Tab.new) do |component| %>
|
82
|
+
# <% component.with_icon(:star) %>
|
83
|
+
# <% component.with_text { "Tab" } %>
|
84
|
+
# <% component.with_counter(count: 10) %>
|
85
|
+
# <% end %>
|
86
|
+
# <%= render(Primer::Alpha::Navigation::Tab.new) do |component| %>
|
87
|
+
# <% component.with_text { "Tab" } %>
|
88
|
+
# <% component.with_counter(count: 10) %>
|
89
|
+
# <% end %>
|
90
|
+
#
|
91
|
+
# @example Inside a list
|
92
|
+
# <%= render(Primer::Alpha::Navigation::Tab.new(list: true)) do |component| %>
|
93
|
+
# <% component.with_text { "Tab" } %>
|
94
|
+
# <% end %>
|
95
|
+
#
|
96
|
+
# @example With custom HTML
|
97
|
+
# <%= render(Primer::Alpha::Navigation::Tab.new) do %>
|
98
|
+
# <div>
|
99
|
+
# This is my <strong>custom HTML</strong>
|
100
|
+
# </div>
|
101
|
+
# <% end %>
|
102
|
+
#
|
103
|
+
# @param list [Boolean] Whether the Tab is an item in a `<ul>` list.
|
104
|
+
# @param selected [Boolean] Whether the Tab is selected or not.
|
105
|
+
# @param with_panel [Boolean] Whether the Tab has an associated panel.
|
106
|
+
# @param panel_id [String] Only applies if `with_panel` is `true`. Unique id of panel.
|
107
|
+
# @param icon_classes [Boolean] Classes that must always be applied to icons.
|
108
|
+
# @param wrapper_arguments [Hash] <%= link_to_system_arguments_docs %> to be used in the `<li>` wrapper when the tab is an item in a list.
|
109
|
+
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
|
110
|
+
def initialize(list: false, selected: false, with_panel: false, panel_id: "", icon_classes: "", wrapper_arguments: {}, **system_arguments)
|
111
|
+
@selected = selected
|
112
|
+
@icon_classes = icon_classes
|
113
|
+
@list = list
|
114
|
+
@with_panel = with_panel
|
115
|
+
|
116
|
+
@system_arguments = system_arguments
|
117
|
+
@id = @system_arguments[:id]
|
118
|
+
@wrapper_arguments = wrapper_arguments
|
119
|
+
|
120
|
+
if with_panel || @system_arguments[:tag] == :button
|
121
|
+
@system_arguments[:tag] = :button
|
122
|
+
@system_arguments[:type] = :button
|
123
|
+
@system_arguments[:role] = :tab
|
124
|
+
panel_id(panel_id)
|
125
|
+
# https://www.w3.org/TR/wai-aria-practices/#presentation_role
|
126
|
+
@wrapper_arguments[:role] = :presentation
|
127
|
+
else
|
128
|
+
@system_arguments[:tag] = :a
|
129
|
+
end
|
130
|
+
|
131
|
+
@wrapper_arguments[:tag] = :li
|
132
|
+
@wrapper_arguments[:display] ||= :inline_flex
|
133
|
+
|
134
|
+
return unless @selected
|
135
|
+
|
136
|
+
if @system_arguments[:tag] == :a
|
137
|
+
aria_current = aria("current", system_arguments) || DEFAULT_ARIA_CURRENT_FOR_ANCHOR
|
138
|
+
@system_arguments[:"aria-current"] = fetch_or_fallback(ARIA_CURRENT_OPTIONS_FOR_ANCHOR, aria_current, DEFAULT_ARIA_CURRENT_FOR_ANCHOR)
|
139
|
+
else
|
140
|
+
@system_arguments[:"aria-selected"] = true
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def wrapper
|
145
|
+
unless @list
|
146
|
+
yield
|
147
|
+
return # returning `yield` caused a double render
|
148
|
+
end
|
149
|
+
|
150
|
+
render(Primer::BaseComponent.new(**@wrapper_arguments)) do
|
151
|
+
yield if block_given?
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def panel_id(panel_id)
|
158
|
+
if panel_id.blank?
|
159
|
+
raise ArgumentError, "`panel_id` is required" unless Rails.env.production?
|
160
|
+
else
|
161
|
+
@panel_id = panel_id
|
162
|
+
@system_arguments[:"aria-controls"] = @panel_id
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|