primer_view_components 0.1.0 → 0.1.1
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.
- 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
|