diff --git a/app/components/open_project/common/work_package_card_component.html.erb b/app/components/open_project/common/work_package_card_component.html.erb index cb2bcf0b5ba..38d68255cdc 100644 --- a/app/components/open_project/common/work_package_card_component.html.erb +++ b/app/components/open_project/common/work_package_card_component.html.erb @@ -39,13 +39,17 @@ See COPYRIGHT and LICENSE files for more details. <% end %> <% grid.with_area(:menu) do %> - <%= render( - OpenProject::Common::WorkPackageCardComponent::Menu.new( - work_package:, - src: menu_src, - button_aria_label: t(".menu.label_actions") - ) - ) %> + <% if menu? %> + <%= menu %> + <% else %> + <%= render( + OpenProject::Common::WorkPackageCardComponent::Menu.new( + work_package:, + src: menu_src, + button_aria_label: t(".menu.label_actions") + ) + ) %> + <% end %> <% end %> <% grid.with_area(:subject) do %> diff --git a/app/components/open_project/common/work_package_card_component.rb b/app/components/open_project/common/work_package_card_component.rb index bcbea5c8e56..12bb0fb8430 100644 --- a/app/components/open_project/common/work_package_card_component.rb +++ b/app/components/open_project/common/work_package_card_component.rb @@ -35,11 +35,20 @@ module OpenProject include OpPrimer::ComponentHelpers renders_one :metric, Primer::Content + renders_one :menu, ->(src: nil, button_aria_label: nil, **system_arguments) { + Menu.new( + work_package:, + src:, + button_aria_label:, + **system_arguments + ) + } attr_reader :work_package, :menu_src # @param work_package [WorkPackage] the work package this card represents. - # @param menu_src [String, NilClass] optional lazy menu source. + # @param menu_src [String, NilClass] optional lazy menu source. Prefer the + # `with_menu(src:)` slot for new call sites. def initialize(work_package:, menu_src: nil) super() diff --git a/lookbook/previews/open_project/common/work_package_card_component_preview.rb b/lookbook/previews/open_project/common/work_package_card_component_preview.rb index 023d64a9667..cb15d220239 100644 --- a/lookbook/previews/open_project/common/work_package_card_component_preview.rb +++ b/lookbook/previews/open_project/common/work_package_card_component_preview.rb @@ -54,6 +54,19 @@ module OpenProject end end + def with_menu + work_package = WorkPackage.first + return preview_message("No work packages in the database.") unless work_package + + render OpenProject::Common::WorkPackageCardComponent.new( + work_package: + ) do |card| + card.with_menu do |menu| + menu.with_item(label: "Open", href: "/work_packages/#{work_package.id}") + end + end + end + private def preview_message(text) diff --git a/modules/backlogs/app/components/backlogs/work_package_card_component.rb b/modules/backlogs/app/components/backlogs/work_package_card_component.rb index 76590b0d2f0..9f431f7b980 100644 --- a/modules/backlogs/app/components/backlogs/work_package_card_component.rb +++ b/modules/backlogs/app/components/backlogs/work_package_card_component.rb @@ -32,6 +32,8 @@ module Backlogs class WorkPackageCardComponent < ApplicationComponent attr_reader :work_package, :menu_src + delegate :with_menu, to: :card + def initialize(work_package:, menu_src: nil) super() @@ -40,11 +42,21 @@ module Backlogs end def call - render(OpenProject::Common::WorkPackageCardComponent.new(work_package:, menu_src:)) do |card| - card.with_metric do + render(card) do |common_card| + common_card.with_metric do render(Backlogs::StoryPointsComponent.new(work_package:)) end end end + + private + + def card + @card ||= OpenProject::Common::WorkPackageCardComponent.new(work_package:, menu_src:) + end + + def before_render + content + end end end diff --git a/modules/backlogs/spec/components/backlogs/work_package_card_component_spec.rb b/modules/backlogs/spec/components/backlogs/work_package_card_component_spec.rb index 6612181934d..793f1afe317 100644 --- a/modules/backlogs/spec/components/backlogs/work_package_card_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/work_package_card_component_spec.rb @@ -60,4 +60,20 @@ RSpec.describe Backlogs::WorkPackageCardComponent, type: :component do expect(rendered_component).to have_element "include-fragment", src: menu_src end + + it "supports inline menu items through the menu slot" do + rendered = render_inline(described_class.new(work_package:, menu_src:)) do |card| + card.with_menu(button_aria_label: "Backlogs card actions") do |menu| + menu.with_item(label: "Open", href: "/work_packages/#{work_package.id}") + end + end + + expect(rendered).to have_link "Open", href: "/work_packages/#{work_package.id}" + expect(rendered).to have_button( + "work_package_#{work_package.id}_menu-button", + accessible_name: "Backlogs card actions" + ) + expect(rendered).to have_no_element "include-fragment" + expect(rendered).to have_text("5 points", normalize_ws: true) + end end diff --git a/spec/components/open_project/common/work_package_card_component_spec.rb b/spec/components/open_project/common/work_package_card_component_spec.rb index d9034a3ea84..66778329f50 100644 --- a/spec/components/open_project/common/work_package_card_component_spec.rb +++ b/spec/components/open_project/common/work_package_card_component_spec.rb @@ -96,5 +96,34 @@ RSpec.describe OpenProject::Common::WorkPackageCardComponent, type: :component d it "uses the provided menu src" do expect(rendered_component).to have_element "include-fragment", src: menu_src end + + it "supports inline menu items through the menu slot" do + rendered = render_inline(component) do |card| + card.with_menu(button_aria_label: "Card actions") do |menu| + menu.with_item(label: "Open", href: "/work_packages/#{work_package.id}") + end + end + + expect(rendered).to have_link "Open", href: "/work_packages/#{work_package.id}" + expect(rendered).to have_button(menu_button_id, accessible_name: "Card actions") + expect(rendered).to have_no_element "include-fragment" + end + + it "supports deferred menu loading through the menu slot" do + rendered = render_inline(described_class.new(work_package:)) do |card| + card.with_menu(src: menu_src) + end + + expect(rendered).to have_element "include-fragment", src: menu_src + end + + it "uses the menu slot before the initializer menu source" do + rendered = render_inline(component) do |card| + card.with_menu(src: "/slot-menu") + end + + expect(rendered).to have_element "include-fragment", src: "/slot-menu" + expect(rendered).to have_no_element "include-fragment", src: menu_src + end end end