openproject-primer_view_components 0.68.0 → 0.69.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 +12 -0
- data/app/assets/javascripts/components/primer/alpha/action_menu/action_menu_element.d.ts +5 -0
- data/app/assets/javascripts/components/primer/alpha/action_menu/action_menu_focus_zone_stack.d.ts +17 -0
- data/app/assets/javascripts/primer_view_components.js +1 -1
- data/app/assets/javascripts/primer_view_components.js.map +1 -1
- data/app/components/primer/alpha/action_menu/action_menu_element.d.ts +5 -0
- data/app/components/primer/alpha/action_menu/action_menu_element.js +111 -16
- data/app/components/primer/alpha/action_menu/action_menu_element.ts +136 -23
- data/app/components/primer/alpha/action_menu/action_menu_focus_zone_stack.d.ts +17 -0
- data/app/components/primer/alpha/action_menu/action_menu_focus_zone_stack.js +62 -0
- data/app/components/primer/alpha/action_menu/action_menu_focus_zone_stack.ts +67 -0
- data/app/components/primer/alpha/action_menu/list.rb +3 -1
- data/app/components/primer/alpha/action_menu/list_wrapper.rb +31 -0
- data/app/components/primer/alpha/action_menu/menu.html.erb +24 -0
- data/app/components/primer/alpha/action_menu/menu.rb +136 -0
- data/app/components/primer/alpha/action_menu/primary_menu.rb +86 -0
- data/app/components/primer/alpha/action_menu/sub_menu.rb +74 -0
- data/app/components/primer/alpha/action_menu/sub_menu_item.html.erb +5 -0
- data/app/components/primer/alpha/action_menu/sub_menu_item.rb +53 -0
- data/app/components/primer/alpha/action_menu.html.erb +1 -26
- data/app/components/primer/alpha/action_menu.rb +44 -118
- data/app/components/primer/alpha/select_panel.rb +3 -3
- data/lib/primer/view_components/version.rb +2 -2
- data/previews/primer/alpha/action_menu_preview/multiple_select_form.html.erb +13 -4
- data/previews/primer/alpha/action_menu_preview/opens_dialog.html.erb +20 -11
- data/previews/primer/alpha/action_menu_preview/single_select_form_items.html.erb +13 -2
- data/previews/primer/alpha/action_menu_preview/sub_menus.html.erb +19 -0
- data/previews/primer/alpha/action_menu_preview/with_actions.html.erb +20 -11
- data/previews/primer/alpha/action_menu_preview/with_deferred_content.html.erb +24 -0
- data/previews/primer/alpha/action_menu_preview.rb +93 -29
- data/static/arguments.json +169 -68
- data/static/audited_at.json +4 -0
- data/static/constants.json +27 -7
- data/static/info_arch.json +797 -199
- data/static/previews.json +13 -0
- data/static/statuses.json +4 -0
- metadata +14 -2
@@ -0,0 +1,67 @@
|
|
1
|
+
import AnchoredPositionElement from '../../anchored_position'
|
2
|
+
import {FocusKeys, focusTrap, focusZone} from '@primer/behaviors'
|
3
|
+
import {ActionMenuElement} from './action_menu_element'
|
4
|
+
|
5
|
+
type StackEntry = {
|
6
|
+
element: AnchoredPositionElement
|
7
|
+
abortController?: AbortController
|
8
|
+
}
|
9
|
+
|
10
|
+
export class ActionMenuFocusZoneStack {
|
11
|
+
#stack: StackEntry[]
|
12
|
+
|
13
|
+
constructor() {
|
14
|
+
this.#stack = []
|
15
|
+
}
|
16
|
+
|
17
|
+
get current(): StackEntry | undefined {
|
18
|
+
return this.#stack[this.#stack.length - 1]
|
19
|
+
}
|
20
|
+
|
21
|
+
push(next: AnchoredPositionElement, options: {trapFocus: boolean} = {trapFocus: true}) {
|
22
|
+
const {trapFocus} = options
|
23
|
+
this.#stack.push({element: next, abortController: this.#setupFocusZone(next, trapFocus)})
|
24
|
+
}
|
25
|
+
|
26
|
+
pop(target?: AnchoredPositionElement) {
|
27
|
+
if (target) {
|
28
|
+
while (this.#stack.length > 0 && this.current?.element !== target) {
|
29
|
+
const entry = this.#stack.pop()
|
30
|
+
entry?.abortController?.abort()
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
const entry = this.#stack.pop()
|
35
|
+
entry?.abortController?.abort()
|
36
|
+
}
|
37
|
+
|
38
|
+
#setupFocusZone(containerEl: AnchoredPositionElement, trapFocus: boolean): AbortController | undefined {
|
39
|
+
const focusZoneAbortController = focusZone(containerEl, {
|
40
|
+
bindKeys: FocusKeys.ArrowVertical | FocusKeys.ArrowHorizontal | FocusKeys.HomeAndEnd | FocusKeys.Backspace,
|
41
|
+
focusOutBehavior: 'wrap',
|
42
|
+
|
43
|
+
focusableElementFilter: (element: HTMLElement): boolean => {
|
44
|
+
return this.elementIsMenuItem(element) && element.closest('anchored-position') === containerEl
|
45
|
+
},
|
46
|
+
})
|
47
|
+
|
48
|
+
if (trapFocus) {
|
49
|
+
const {signal: focusZoneSignal} = focusZoneAbortController
|
50
|
+
return focusTrap(containerEl, undefined, focusZoneSignal)
|
51
|
+
} else {
|
52
|
+
return focusZoneAbortController
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
elementIsMenuItem(element: HTMLElement): boolean {
|
57
|
+
return this.#validItemRoles.includes(element.getAttribute('role') || '')
|
58
|
+
}
|
59
|
+
|
60
|
+
get #validItemRoles(): string[] {
|
61
|
+
return ActionMenuElement.validItemRoles
|
62
|
+
}
|
63
|
+
|
64
|
+
get isEmpty(): boolean {
|
65
|
+
return this.#stack.length === 0
|
66
|
+
}
|
67
|
+
}
|
@@ -11,7 +11,9 @@ module Primer
|
|
11
11
|
|
12
12
|
attr_reader :items
|
13
13
|
|
14
|
-
|
14
|
+
delegate :acts_as_form_input?, to: :@list
|
15
|
+
|
16
|
+
# @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionMenu::ListWrapper) %>
|
15
17
|
def initialize(**system_arguments)
|
16
18
|
@items = []
|
17
19
|
@has_group = false
|
@@ -6,6 +6,37 @@ module Primer
|
|
6
6
|
# This component is part of <%= link_to_component(Primer::Alpha::ActionMenu) %> and should not be
|
7
7
|
# used as a standalone component.
|
8
8
|
class ListWrapper < Primer::Alpha::ActionList
|
9
|
+
# @!parse
|
10
|
+
# # Adds an item to the menu.
|
11
|
+
# #
|
12
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList) %>'s `item` slot.
|
13
|
+
# def with_item(**system_arguments)
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # Adds an avatar item to the menu.
|
17
|
+
# #
|
18
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList) %>'s `item` slot.
|
19
|
+
# def with_avatar_item(**system_arguments)
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# # Adds a divider to the list. Dividers visually separate items.
|
23
|
+
# #
|
24
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList::Divider) %>.
|
25
|
+
# def with_divider(**system_arguments)
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# # Adds a group to the menu. Groups are a logical set of items with a header.
|
29
|
+
# #
|
30
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionMenu::Group) %>.
|
31
|
+
# def with_group(**system_arguments)
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# # Gets the list of configured menu items, which includes regular items, avatar items, groups, and dividers.
|
35
|
+
# #
|
36
|
+
# # @return [Array<ViewComponent::Slot>]
|
37
|
+
# def items
|
38
|
+
# end
|
39
|
+
|
9
40
|
add_polymorphic_slot_type(
|
10
41
|
slot_name: :items,
|
11
42
|
type: :group,
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<%= render(@overlay) do |overlay| %>
|
2
|
+
<% overlay.with_body(padding: :none) do %>
|
3
|
+
<% if @src.present? %>
|
4
|
+
<%= render(Primer::Alpha::IncludeFragment.new(src: @src, loading: preload? ? :eager : :lazy, "data-target": "action-menu.includeFragment")) do %>
|
5
|
+
<%= render(Primer::Alpha::ActionMenu::List.new(id: "#{@menu_id}-list", menu_id: @menu_id)) do |list| %>
|
6
|
+
<% list.with_item(
|
7
|
+
aria: { disabled: true },
|
8
|
+
content_arguments: {
|
9
|
+
display: :flex,
|
10
|
+
align_items: :center,
|
11
|
+
justify_content: :center,
|
12
|
+
text_align: :center,
|
13
|
+
autofocus: true
|
14
|
+
}
|
15
|
+
) do %>
|
16
|
+
<%= render Primer::Beta::Spinner.new(aria: { label: "Loading content..." }) %>
|
17
|
+
<% end %>
|
18
|
+
<% end %>
|
19
|
+
<% end %>
|
20
|
+
<% else %>
|
21
|
+
<%= render(@list) %>
|
22
|
+
<% end %>
|
23
|
+
<% end %>
|
24
|
+
<% end %>
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# typed: false
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Primer
|
5
|
+
module Alpha
|
6
|
+
class ActionMenu
|
7
|
+
# This component is part of <%= link_to_component(Primer::Alpha::ActionMenu) %> and should not be
|
8
|
+
# used as a standalone component.
|
9
|
+
class Menu < Primer::Component
|
10
|
+
DEFAULT_PRELOAD = false
|
11
|
+
|
12
|
+
DEFAULT_SELECT_VARIANT = :none
|
13
|
+
SELECT_VARIANT_OPTIONS = [
|
14
|
+
:single,
|
15
|
+
:multiple,
|
16
|
+
DEFAULT_SELECT_VARIANT
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
attr_reader :menu_id, :anchor_side, :anchor_align, :list, :preload, :src, :select_variant, :form_arguments
|
20
|
+
|
21
|
+
delegate :with_item, :with_avatar_item, :with_divider, :with_group, :items, :acts_as_form_input?, to: :@list
|
22
|
+
|
23
|
+
alias preload? preload
|
24
|
+
|
25
|
+
# @!parse
|
26
|
+
# # Adds an item to the menu.
|
27
|
+
# #
|
28
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList) %>'s `item` slot.
|
29
|
+
# def with_item(**system_arguments)
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# # Adds an avatar item to the menu.
|
33
|
+
# #
|
34
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList) %>'s `item` slot.
|
35
|
+
# def with_avatar_item(**system_arguments)
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# # Adds a divider to the list. Dividers visually separate items.
|
39
|
+
# #
|
40
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList::Divider) %>.
|
41
|
+
# def with_divider(**system_arguments)
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# # Adds a group to the menu. Groups are a logical set of items with a header.
|
45
|
+
# #
|
46
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionMenu::Group) %>.
|
47
|
+
# def with_group(**system_arguments)
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# # Gets the list of configured menu items, which includes regular items, avatar items, groups, and dividers.
|
51
|
+
# #
|
52
|
+
# # @return [Array<ViewComponent::Slot>]
|
53
|
+
# def items
|
54
|
+
# end
|
55
|
+
|
56
|
+
# @param anchor_align [Symbol] <%= one_of(Primer::Alpha::Overlay::ANCHOR_ALIGN_OPTIONS) %>
|
57
|
+
# @param anchor_side [Symbol] <%= one_of(Primer::Alpha::Overlay::ANCHOR_SIDE_OPTIONS) %>
|
58
|
+
# @param menu_id [String] Id of the menu.
|
59
|
+
# @param size [Symbol] <%= one_of(Primer::Alpha::Overlay::SIZE_OPTIONS) %>
|
60
|
+
# @param src [String] Used with an `include-fragment` element to load menu content from the given source URL.
|
61
|
+
# @param preload [Boolean] When true, and src is present, loads the `include-fragment` on trigger hover.
|
62
|
+
# @param select_variant [Symbol] <%= one_of(Primer::Alpha::ActionMenu::Menu::SELECT_VARIANT_OPTIONS) %>
|
63
|
+
# @param form_arguments [Hash] Allows an `ActionMenu` to act as a select list in multi- and single-select modes. Pass the `builder:` and `name:` options to this hash. `builder:` should be an instance of `ActionView::Helpers::FormBuilder`, which are created by the standard Rails `#form_with` and `#form_for` helpers. The `name:` option is the desired name of the field that will be included in the params sent to the server on form submission.
|
64
|
+
# @param overlay_arguments [Hash] Arguments to pass to the underlying <%= link_to_component(Primer::Alpha::Overlay) %>
|
65
|
+
# @param list_arguments [Hash] Arguments to pass to the underlying <%= link_to_component(Primer::Alpha::ActionMenu::List) %>
|
66
|
+
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>.
|
67
|
+
def initialize(
|
68
|
+
anchor_align:,
|
69
|
+
anchor_side:,
|
70
|
+
menu_id: self.class.generate_id,
|
71
|
+
size: Primer::Alpha::Overlay::DEFAULT_SIZE,
|
72
|
+
src: nil,
|
73
|
+
preload: DEFAULT_PRELOAD,
|
74
|
+
select_variant: DEFAULT_SELECT_VARIANT,
|
75
|
+
form_arguments: {},
|
76
|
+
overlay_arguments: {},
|
77
|
+
list_arguments: {},
|
78
|
+
**system_arguments
|
79
|
+
)
|
80
|
+
@menu_id = menu_id
|
81
|
+
@src = src
|
82
|
+
@preload = fetch_or_fallback_boolean(preload, DEFAULT_PRELOAD)
|
83
|
+
@anchor_side = anchor_side
|
84
|
+
@anchor_align = anchor_align
|
85
|
+
|
86
|
+
@select_variant = fetch_or_fallback(SELECT_VARIANT_OPTIONS, select_variant, DEFAULT_SELECT_VARIANT)
|
87
|
+
@form_arguments = form_arguments
|
88
|
+
|
89
|
+
overlay_arguments[:data] = merge_data(
|
90
|
+
overlay_arguments, data: {
|
91
|
+
target: "action-menu.overlay"
|
92
|
+
}
|
93
|
+
)
|
94
|
+
|
95
|
+
@overlay = Primer::Alpha::Overlay.new(
|
96
|
+
id: "#{@menu_id}-overlay",
|
97
|
+
title: "Menu",
|
98
|
+
visually_hide_title: true,
|
99
|
+
anchor_align: anchor_align,
|
100
|
+
anchor_side: anchor_side,
|
101
|
+
size: size,
|
102
|
+
**overlay_arguments
|
103
|
+
)
|
104
|
+
|
105
|
+
@list = Primer::Alpha::ActionMenu::List.new(
|
106
|
+
menu_id: @menu_id,
|
107
|
+
select_variant: select_variant,
|
108
|
+
form_arguments: form_arguments,
|
109
|
+
**list_arguments
|
110
|
+
)
|
111
|
+
|
112
|
+
system_arguments # rubocop:disable Lint/Void
|
113
|
+
end
|
114
|
+
|
115
|
+
# Adds a sub-menu to the menu.
|
116
|
+
#
|
117
|
+
# @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionMenu::SubMenuItem) %>.
|
118
|
+
def with_sub_menu_item(**system_arguments, &block)
|
119
|
+
@list.with_item(
|
120
|
+
component_klass: SubMenuItem,
|
121
|
+
select_variant: select_variant,
|
122
|
+
form_arguments: form_arguments,
|
123
|
+
**system_arguments,
|
124
|
+
&block
|
125
|
+
)
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def before_render
|
131
|
+
raise ArgumentError, "`items` cannot be set when `src` is specified" if src.present? && items.any?
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# typed: false
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Primer
|
5
|
+
module Alpha
|
6
|
+
class ActionMenu
|
7
|
+
# This component is part of <%= link_to_component(Primer::Alpha::ActionMenu) %> and should not be
|
8
|
+
# used as a standalone component.
|
9
|
+
class PrimaryMenu < Menu
|
10
|
+
DEFAULT_ANCHOR_ALIGN = :start
|
11
|
+
DEFAULT_ANCHOR_SIDE = :outside_bottom
|
12
|
+
|
13
|
+
attr_reader :dynamic_label, :dynamic_label_prefix
|
14
|
+
|
15
|
+
# @!parse
|
16
|
+
# # Adds an item to the menu.
|
17
|
+
# #
|
18
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList) %>'s `item` slot.
|
19
|
+
# def with_item(**system_arguments)
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# # Adds an avatar item to the menu.
|
23
|
+
# #
|
24
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList) %>'s `item` slot.
|
25
|
+
# def with_avatar_item(**system_arguments)
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# # Adds a divider to the list. Dividers visually separate items.
|
29
|
+
# #
|
30
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList::Divider) %>.
|
31
|
+
# def with_divider(**system_arguments)
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# # Adds a group to the menu. Groups are a logical set of items with a header.
|
35
|
+
# #
|
36
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionMenu::Group) %>.
|
37
|
+
# def with_group(**system_arguments)
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# # Gets the list of configured menu items, which includes regular items, avatar items, groups, and dividers.
|
41
|
+
# #
|
42
|
+
# # @return [Array<ViewComponent::Slot>]
|
43
|
+
# def items
|
44
|
+
# end
|
45
|
+
|
46
|
+
# @param anchor_align [Symbol] <%= one_of(Primer::Alpha::Overlay::ANCHOR_ALIGN_OPTIONS) %>
|
47
|
+
# @param anchor_side [Symbol] <%= one_of(Primer::Alpha::Overlay::ANCHOR_SIDE_OPTIONS) %>
|
48
|
+
# @param dynamic_label [Boolean] Whether or not to display the text of the currently selected item in the show button.
|
49
|
+
# @param dynamic_label_prefix [String] If provided, the prefix is prepended to the dynamic label and displayed in the show button.
|
50
|
+
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>.
|
51
|
+
def initialize(
|
52
|
+
anchor_align: DEFAULT_ANCHOR_ALIGN,
|
53
|
+
anchor_side: DEFAULT_ANCHOR_SIDE,
|
54
|
+
dynamic_label: false,
|
55
|
+
dynamic_label_prefix: nil,
|
56
|
+
**system_arguments
|
57
|
+
)
|
58
|
+
@dynamic_label = dynamic_label
|
59
|
+
@dynamic_label_prefix = dynamic_label_prefix
|
60
|
+
|
61
|
+
system_arguments[:list_arguments] ||= {}
|
62
|
+
|
63
|
+
system_arguments[:list_arguments][:data] = merge_data(
|
64
|
+
system_arguments[:list_arguments],
|
65
|
+
{ data: { target: "action-menu.list" } }
|
66
|
+
)
|
67
|
+
|
68
|
+
super(
|
69
|
+
anchor_align: anchor_align,
|
70
|
+
anchor_side: anchor_side,
|
71
|
+
**system_arguments
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Button to activate the menu.
|
76
|
+
#
|
77
|
+
# @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::Overlay) %>'s `show_button` slot.
|
78
|
+
def with_show_button(**system_arguments, &block)
|
79
|
+
@overlay.with_show_button(**system_arguments, id: "#{@menu_id}-button", controls: "#{@menu_id}-list") do |button|
|
80
|
+
evaluate_block(button, &block)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# typed: false
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Primer
|
5
|
+
module Alpha
|
6
|
+
class ActionMenu
|
7
|
+
# This component is part of <%= link_to_component(Primer::Alpha::ActionMenu) %> and should not be
|
8
|
+
# used as a standalone component.
|
9
|
+
class SubMenu < Menu
|
10
|
+
DEFAULT_ANCHOR_ALIGN = :start
|
11
|
+
DEFAULT_ANCHOR_SIDE = :outside_right
|
12
|
+
|
13
|
+
# @!parse
|
14
|
+
# # Adds an item to the menu.
|
15
|
+
# #
|
16
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList) %>'s `item` slot.
|
17
|
+
# def with_item(**system_arguments)
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # Adds an avatar item to the menu.
|
21
|
+
# #
|
22
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList) %>'s `item` slot.
|
23
|
+
# def with_avatar_item(**system_arguments)
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# # Adds a divider to the list. Dividers visually separate items.
|
27
|
+
# #
|
28
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList::Divider) %>.
|
29
|
+
# def with_divider(**system_arguments)
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# # Adds a group to the menu. Groups are a logical set of items with a header.
|
33
|
+
# #
|
34
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionMenu::Group) %>.
|
35
|
+
# def with_group(**system_arguments)
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# # Gets the list of configured menu items, which includes regular items, avatar items, groups, and dividers.
|
39
|
+
# #
|
40
|
+
# # @return [Array<ViewComponent::Slot>]
|
41
|
+
# def items
|
42
|
+
# end
|
43
|
+
|
44
|
+
# @param menu_id [String] Id of the menu.
|
45
|
+
# @param anchor_align [Symbol] <%= one_of(Primer::Alpha::Overlay::ANCHOR_ALIGN_OPTIONS) %>
|
46
|
+
# @param anchor_side [Symbol] <%= one_of(Primer::Alpha::Overlay::ANCHOR_SIDE_OPTIONS) %>
|
47
|
+
# @param overlay_arguments [Hash] Arguments to pass to the underlying <%= link_to_component(Primer::Alpha::Overlay) %>
|
48
|
+
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>.
|
49
|
+
def initialize(
|
50
|
+
menu_id: self.class.generate_id,
|
51
|
+
anchor_align: DEFAULT_ANCHOR_ALIGN,
|
52
|
+
anchor_side: DEFAULT_ANCHOR_SIDE,
|
53
|
+
overlay_arguments: {},
|
54
|
+
**system_arguments
|
55
|
+
)
|
56
|
+
overlay_arguments[:anchor] = "#{menu_id}-button"
|
57
|
+
super
|
58
|
+
end
|
59
|
+
|
60
|
+
# Adds a sub-menu to the menu.
|
61
|
+
#
|
62
|
+
# @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionMenu::SubMenuItem) %>.
|
63
|
+
def with_sub_menu_item(**system_arguments, &block)
|
64
|
+
super(
|
65
|
+
anchor_align: anchor_align, # carry over to sub-menus
|
66
|
+
anchor_side: anchor_side, # carry over to sub-menus
|
67
|
+
**system_arguments,
|
68
|
+
&block
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Primer
|
4
|
+
module Alpha
|
5
|
+
class ActionMenu
|
6
|
+
# This component is part of <%= link_to_component(Primer::Alpha::ActionMenu) %> and should not be
|
7
|
+
# used as a standalone component.
|
8
|
+
class SubMenuItem < ::Primer::Alpha::ActionList::Item
|
9
|
+
def initialize(content_arguments: {}, form_arguments: {}, **system_arguments)
|
10
|
+
# We extract form_arguments from system_arguments here to avoid passing them to the
|
11
|
+
# ActionList::Item base class or to the SubMenu instance. Doing so prevents a form
|
12
|
+
# input from being emitted for sub-menu items, which prevents an extra empty value
|
13
|
+
# from being sent to the server on form submit.
|
14
|
+
@sub_menu = SubMenu.allocate
|
15
|
+
system_arguments = @sub_menu.send(:initialize, **system_arguments)
|
16
|
+
system_arguments[:id] = "#{@sub_menu.menu_id}-button"
|
17
|
+
|
18
|
+
@form_arguments = form_arguments
|
19
|
+
|
20
|
+
content_arguments[:tag] = :button
|
21
|
+
content_arguments[:popovertarget] = "#{@sub_menu.menu_id}-overlay"
|
22
|
+
|
23
|
+
content_arguments[:aria] = merge_aria(
|
24
|
+
content_arguments, {
|
25
|
+
aria: {
|
26
|
+
controls: "#{@sub_menu.menu_id}-list",
|
27
|
+
haspopup: true
|
28
|
+
}
|
29
|
+
}
|
30
|
+
)
|
31
|
+
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
def with_item(**system_arguments, &block)
|
36
|
+
@sub_menu.with_item(form_arguments: @form_arguments, **system_arguments, &block)
|
37
|
+
end
|
38
|
+
|
39
|
+
def with_avatar_item(**system_arguments, &block)
|
40
|
+
@sub_menu.with_avatar_item(form_arguments: @form_arguments, **system_arguments, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def with_sub_menu_item(**system_arguments, &block)
|
44
|
+
@sub_menu.with_sub_menu_item(form_arguments: @form_arguments, **system_arguments, &block)
|
45
|
+
end
|
46
|
+
|
47
|
+
def with_group(**system_arguments, &block)
|
48
|
+
@sub_menu.with_group(form_arguments: @form_arguments, **system_arguments, &block)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -1,28 +1,3 @@
|
|
1
1
|
<%= render Primer::BaseComponent.new(**@system_arguments) do %>
|
2
|
-
|
3
|
-
<%= render(@overlay) do |overlay| %>
|
4
|
-
<% overlay.with_body(padding: :none) do %>
|
5
|
-
<% if @src.present? %>
|
6
|
-
<%= render(Primer::Alpha::IncludeFragment.new(src: @src, loading: preload? ? :eager : :lazy, "data-target": "action-menu.includeFragment")) do %>
|
7
|
-
<%= render(Primer::Alpha::ActionMenu::List.new(id: "#{@menu_id}-list", menu_id: @menu_id)) do |list| %>
|
8
|
-
<% list.with_item(
|
9
|
-
aria: { disabled: true },
|
10
|
-
content_arguments: {
|
11
|
-
display: :flex,
|
12
|
-
align_items: :center,
|
13
|
-
justify_content: :center,
|
14
|
-
text_align: :center,
|
15
|
-
autofocus: true
|
16
|
-
}
|
17
|
-
) do %>
|
18
|
-
<%= render Primer::Beta::Spinner.new(aria: { label: "Loading content..." }) %>
|
19
|
-
<% end %>
|
20
|
-
<% end %>
|
21
|
-
<% end %>
|
22
|
-
<% else %>
|
23
|
-
<%= render(@list) %>
|
24
|
-
<% end %>
|
25
|
-
<% end %>
|
26
|
-
<% end %>
|
27
|
-
</focus-group>
|
2
|
+
<%= render(@primary_menu) %>
|
28
3
|
<% end %>
|