Skip to content

Commit

Permalink
Expose action list builder methods (#2187)
Browse files Browse the repository at this point in the history
  • Loading branch information
camertron authored Aug 2, 2023
1 parent 3d44b06 commit ce2011e
Show file tree
Hide file tree
Showing 6 changed files with 527 additions and 314 deletions.
7 changes: 7 additions & 0 deletions .changeset/curvy-toys-obey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@primer/view-components': minor
---

Expose ActionList's #build_item and #build_avatar_item externally to facilitate parent-less item construction

<!-- Changed components: Primer::Alpha::ActionList, Primer::Alpha::ActionMenu, Primer::Alpha::NavList -->
58 changes: 42 additions & 16 deletions app/components/primer/alpha/action_list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ def custom_element_name
# @!parse
# # Adds an item to the list.
# #
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList::Item) %>.
# # @param component_klass [Class] The class to use instead of the default <%= link_to_component(Primer::Alpha::ActionList::Item) %>
# # @param system_arguments [Hash] These arguments are forwarded to <%= link_to_component(Primer::Alpha::ActionList::Item) %>, or whatever class is passed as the `component_klass` argument.
# def with_item(**system_arguments, &block)
# end

Expand All @@ -78,9 +79,10 @@ def custom_element_name
# # @param username [String] The username associated with the avatar.
# # @param full_name [String] Optional. The user's full name.
# # @param full_name_scheme [Symbol] Optional. How to display the user's full name. <%= one_of(Primer::Alpha::ActionList::Item::DESCRIPTION_SCHEME_OPTIONS) %>
# # @param avatar_arguments [Hash] Optional. The arguments accepted by <%= link_to_component(Primer::Beta::Avatar) %>.
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionList::Item) %>.
# def with_avatar_item(src:, username:, full_name: nil, full_name_scheme: Primer::Alpha::ActionList::Item::DEFAULT_DESCRIPTION_SCHEME, avatar_arguments: {}, **system_arguments, &block)
# # @param component_klass [Class] The class to use instead of the default <%= link_to_component(Primer::Alpha::ActionList::Item) %>
# # @param avatar_arguments [Hash] Optional. The arguments accepted by <%= link_to_component(Primer::Beta::Avatar) %>
# # @param system_arguments [Hash] These arguments are forwarded to <%= link_to_component(Primer::Alpha::ActionList::Item) %>, or whatever class is passed as the `component_klass` argument.
# def with_avatar_item(src:, username:, full_name: nil, full_name_scheme: Primer::Alpha::ActionList::Item::DEFAULT_DESCRIPTION_SCHEME, component_klass: ActionList::Item, avatar_arguments: {}, **system_arguments, &block)
# end

# Items. Items can be individual items, avatar items, or dividers. See the documentation for `#with_item`, `#with_divider`, and `#with_avatar_item` respectively for more information.
Expand All @@ -97,15 +99,8 @@ def custom_element_name
},

avatar_item: {
renders: lambda { |src:, username:, full_name: nil, full_name_scheme: Primer::Alpha::ActionList::Item::DEFAULT_DESCRIPTION_SCHEME, avatar_arguments: {}, **system_arguments|
build_item(label: username, description_scheme: full_name_scheme, **system_arguments).tap do |item|
item.with_leading_visual_raw_content do
# no alt text necessary for presentational item
render(Primer::Beta::Avatar.new(src: src, **avatar_arguments, role: :presentation, size: 16))
end

item.with_description_content(full_name) if full_name

renders: lambda { |**system_arguments|
build_avatar_item(**system_arguments).tap do |item|
will_add_item(item)
end
},
Expand Down Expand Up @@ -175,8 +170,14 @@ def before_render
@system_arguments[:"aria-describedby"] = heading.subtitle_id if heading.subtitle?
end

# @private
def build_item(**system_arguments)
# Builds a new item but does not add it to the list. Use this method
# instead of the `#with_item` slot if you need to render an item outside
# the context of a list, eg. if rendering additional items to append to
# an existing list, perhaps via a separate HTTP request.
#
# @param component_klass [Class] The class to use instead of the default <%= link_to_component(Primer::Alpha::ActionList::Item) %>
# @param system_arguments [Hash] These arguments are forwarded to <%= link_to_component(Primer::Alpha::ActionList::Item) %>, or whatever class is passed as the `component_klass` argument.
def build_item(component_klass: ActionList::Item, **system_arguments)
# rubocop:disable Style/IfUnlessModifier
if single_select? && system_arguments[:active] && items.count(&:active?).positive?
raise ArgumentError, "only a single item may be active when select_variant is set to :single"
Expand All @@ -188,7 +189,32 @@ def build_item(**system_arguments)
system_arguments[:classes]
)

ActionList::Item.new(list: self, **system_arguments)
component_klass.new(list: self, **system_arguments)
end

# Builds a new avatar item but does not add it to the list. Avatar items
# are a convenient way to accessibly add an item with a leading avatar
# image. Use this method instead of the `#with_avatar_item` slot if you
# need to render an avatar item outside the context of a list, eg. if
# rendering additional items to append to an existing list, perhaps via
# a separate HTTP request.
#
# @param src [String] The source url of the avatar image.
# @param username [String] The username associated with the avatar.
# @param full_name [String] Optional. The user's full name.
# @param full_name_scheme [Symbol] Optional. How to display the user's full name. <%= one_of(Primer::Alpha::ActionList::Item::DESCRIPTION_SCHEME_OPTIONS) %>
# @param component_klass [Class] The class to use instead of the default <%= link_to_component(Primer::Alpha::ActionList::Item) %>
# @param avatar_arguments [Hash] Optional. The arguments accepted by <%= link_to_component(Primer::Beta::Avatar) %>
# @param system_arguments [Hash] These arguments are forwarded to <%= link_to_component(Primer::Alpha::ActionList::Item) %>, or whatever class is passed as the `component_klass` argument.
def build_avatar_item(src:, username:, full_name: nil, full_name_scheme: Primer::Alpha::ActionList::Item::DEFAULT_DESCRIPTION_SCHEME, component_klass: ActionList::Item, avatar_arguments: {}, **system_arguments)
build_item(label: username, description_scheme: full_name_scheme, component_klass: component_klass, **system_arguments).tap do |item|
item.with_leading_visual_raw_content do
# no alt text necessary for presentational item
item.render(Primer::Beta::Avatar.new(src: src, **avatar_arguments, role: :presentation, size: 16))
end

item.with_description_content(full_name) if full_name
end
end

def single_select?
Expand Down
4 changes: 2 additions & 2 deletions app/components/primer/alpha/action_menu/list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class List < Primer::Alpha::ActionList
# Adds a new item to the list.
#
# @param data [Hash] When the menu is used as a form input (see the <%= link_to_component(Primer::Alpha::ActionMenu) %> docs), the label is submitted to the server by default. However, if the `data: { value: }` or `"data-value":` attribute is provided, it will be sent to the server instead.
# @param system_arguments [Hash] The same arguments accepted by <%= link_to_component(Primer::Alpha::ActionList::Item) %>.
# @param system_arguments [Hash] These arguments are forwarded to <%= link_to_component(Primer::Alpha::ActionList::Item) %>, or whatever class is passed as the `component_klass` argument.
def with_item(data: {}, **system_arguments, &block)
system_arguments = organize_arguments(data: data, **system_arguments)

Expand All @@ -30,7 +30,7 @@ def with_item(data: {}, **system_arguments, &block)
# @param full_name_scheme [Symbol] Optional. How to display the user's full name. <%= one_of(Primer::Alpha::ActionList::Item::DESCRIPTION_SCHEME_OPTIONS) %>
# @param data [Hash] When the menu is used as a form input (see the <%= link_to_component(Primer::Alpha::ActionMenu) %> docs), the label is submitted to the server by default. However, if the `data: { value: }` or `"data-value":` attribute is provided, it will be sent to the server instead.
# @param avatar_arguments [Hash] Optional. The arguments accepted by <%= link_to_component(Primer::Beta::Avatar) %>.
# @param system_arguments [Hash] The same arguments accepted by <%= link_to_component(Primer::Alpha::ActionList::Item) %>.
# @param system_arguments [Hash] These arguments are forwarded to <%= link_to_component(Primer::Alpha::ActionList::Item) %>, or whatever class is passed as the `component_klass` argument.
def with_avatar_item(src:, username:, full_name: nil, full_name_scheme: Primer::Alpha::ActionList::Item::DEFAULT_DESCRIPTION_SCHEME, data: {}, avatar_arguments: {}, **system_arguments, &block)
system_arguments = organize_arguments(data: data, **system_arguments)

Expand Down
89 changes: 58 additions & 31 deletions app/components/primer/alpha/nav_list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ def self.custom_element_name
# @!parse
# # Adds an item to the list.
# #
# # @param component_klass [Class] The component class to use. Defaults to `Primer::Alpha::NavList::Item`.
# # @param system_arguments [Hash] The arguments accepted by the `component_klass` class.
# def with_item(**system_arguments, &block)
# # @param component_klass [Class] The class to use instead of the default <%= link_to_component(Primer::Alpha::NavList::Item) %>
# # @param system_arguments [Hash] These arguments are forwarded to <%= link_to_component(Primer::Alpha::NavList::Item) %>, or whatever class is passed as the `component_klass` argument.
# def with_item(component_klass: Primer::Alpha::NavList::Item, **system_arguments, &block)
# end

# @!parse
Expand All @@ -42,10 +42,10 @@ def self.custom_element_name
# # @param username [String] The username associated with the avatar.
# # @param full_name [String] Optional. The user's full name.
# # @param full_name_scheme [Symbol] Optional. How to display the user's full name. <%= one_of(Primer::Alpha::ActionList::Item::DESCRIPTION_SCHEME_OPTIONS) %>
# # @param component_klass [Class] The component class to use. Defaults to `Primer::Alpha::NavList::Item`.
# # @param avatar_arguments [Hash] Optional. The arguments accepted by <%= link_to_component(Primer::Beta::Avatar) %>.
# # @param system_arguments [Hash] The arguments accepted by the `component_klass` class.
# def with_avatar_item(src:, username:, full_name: nil, full_name_scheme: Primer::Alpha::ActionList::Item::DEFAULT_DESCRIPTION_SCHEME, avatar_arguments: {}, **system_arguments, &block)
# # @param component_klass [Class] The class to use instead of the default <%= link_to_component(Primer::Alpha::NavList::Item) %>
# # @param avatar_arguments [Hash] Optional. The arguments accepted by <%= link_to_component(Primer::Beta::Avatar) %>
# # @param system_arguments [Hash] These arguments are forwarded to <%= link_to_component(Primer::Alpha::NavList::Item) %>, or whatever class is passed as the `component_klass` argument.
# def with_avatar_item(src:, username:, full_name: nil, full_name_scheme: Primer::Alpha::ActionList::Item::DEFAULT_DESCRIPTION_SCHEME, component_klass: Primer::Alpha::NavList::Item, avatar_arguments: {}, **system_arguments, &block)
# end

# @!parse
Expand All @@ -66,36 +66,16 @@ def self.custom_element_name
#
renders_many :items, types: {
item: {
renders: lambda { |component_klass: Primer::Alpha::NavList::Item, **system_arguments, &block|
component_klass.new(
list: top_level_group,
selected_item_id: @selected_item_id,
**system_arguments,
&block
)
renders: lambda { |**system_arguments, &block|
build_item(**system_arguments, &block)
},

as: :item
},

avatar_item: {
renders: lambda { |src:, username:, full_name: nil, full_name_scheme: Primer::Alpha::ActionList::Item::DEFAULT_DESCRIPTION_SCHEME, component_klass: Primer::Alpha::NavList::Item, avatar_arguments: {}, **system_arguments|
item = component_klass.new(
list: top_level_group,
selected_item_id: @selected_item_id,
label: username,
description_scheme: full_name_scheme,
**system_arguments
)

item.with_leading_visual_raw_content do
# no alt text necessary
render(Primer::Beta::Avatar.new(src: src, **avatar_arguments, role: :presentation, size: 16))
end

item.with_description_content(full_name) if full_name

item
renders: lambda { |**system_arguments|
build_avatar_item(**system_arguments)
},

as: :avatar_item
Expand Down Expand Up @@ -195,6 +175,53 @@ def initialize(selected_item_id: nil, **system_arguments)
@selected_item_id = selected_item_id
end

# Builds a new item but does not add it to the list. Use this method
# instead of the `#with_item` slot if you need to render an item outside
# the context of a list, eg. if rendering additional items to append to
# an existing list, perhaps via a separate HTTP request.
#
# @param component_klass [Class] The class to use instead of the default <%= link_to_component(Primer::Alpha::NavList::Item) %>
# @param system_arguments [Hash] These arguments are forwarded to <%= link_to_component(Primer::Alpha::NavList::Item) %>, or whatever class is passed as the `component_klass` argument.
def build_item(component_klass: Primer::Alpha::NavList::Item, **system_arguments, &block)
component_klass.new(
list: top_level_group,
selected_item_id: @selected_item_id,
**system_arguments,
&block
)
end

# Builds a new avatar item but does not add it to the list. Avatar items
# are a convenient way to accessibly add an item with a leading avatar
# image. Use this method instead of the `#with_avatar_item` slot if you
# need to render an avatar item outside the context of a list, eg. if
# rendering additional items to append to an existing list, perhaps via
# a separate HTTP request.
#
# @param src [String] The source url of the avatar image.
# @param username [String] The username associated with the avatar.
# @param full_name [String] Optional. The user's full name.
# @param full_name_scheme [Symbol] Optional. How to display the user's full name. <%= one_of(Primer::Alpha::ActionList::Item::DESCRIPTION_SCHEME_OPTIONS) %>
# @param component_klass [Class] The class to use instead of the default <%= link_to_component(Primer::Alpha::NavList::Item) %>
# @param avatar_arguments [Hash] Optional. The arguments accepted by <%= link_to_component(Primer::Beta::Avatar) %>
# @param system_arguments [Hash] These arguments are forwarded to <%= link_to_component(Primer::Alpha::NavList::Item) %>, or whatever class is passed as the `component_klass` argument.
def build_avatar_item(src:, username:, full_name: nil, full_name_scheme: Primer::Alpha::ActionList::Item::DEFAULT_DESCRIPTION_SCHEME, component_klass: Primer::Alpha::NavList::Item, avatar_arguments: {}, **system_arguments)
component_klass.new(
list: top_level_group,
selected_item_id: @selected_item_id,
label: username,
description_scheme: full_name_scheme,
**system_arguments
).tap do |item|
item.with_leading_visual_raw_content do
# no alt text necessary
item.render(Primer::Beta::Avatar.new(src: src, **avatar_arguments, role: :presentation, size: 16))
end

item.with_description_content(full_name) if full_name
end
end

private

def before_render
Expand Down
15 changes: 12 additions & 3 deletions app/components/primer/alpha/nav_list/group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,19 @@ def expand!

# @private
def build_item(component_klass: NavList::Item, **system_arguments)
component_klass.new(
**system_arguments,
super(
component_klass: component_klass,
selected_item_id: @selected_item_id,
list: self
**system_arguments
)
end

# @private
def build_avatar_item(component_klass: NavList::Item, **system_arguments)
super(
component_klass: component_klass,
selected_item_id: @selected_item_id,
**system_arguments
)
end

Expand Down
Loading

0 comments on commit ce2011e

Please sign in to comment.