Skip to content

Commit

Permalink
Merge pull request #96 from opf/feature/support-dialogs-in-pageheader
Browse files Browse the repository at this point in the history
Support dialogs in pageheader
  • Loading branch information
HDinger authored Apr 4, 2024
2 parents 78c679f + f52bbfa commit d3422da
Show file tree
Hide file tree
Showing 10 changed files with 219 additions and 46 deletions.
6 changes: 6 additions & 0 deletions .changeset/tasty-masks-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@openproject/primer-view-components": minor
---

* Add support for Dialogs inside the Primer::OpenProject::PageHeader component
* Limit the number of allowed actions to 5
43 changes: 16 additions & 27 deletions app/components/primer/open_project/page_header.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,20 @@ class PageHeader < Primer::Component
# Add the options individually to the mobile menu in the template
@desktop_menu_block = block

PageHeaderActionMenu.new(**system_arguments)
Primer::OpenProject::PageHeader::Menu.new(**system_arguments)
},
},
dialog: {
renders: lambda { |mobile_icon:, mobile_label:, **system_arguments|
deny_tag_argument(**system_arguments)

# The id will be automatically calculated for the trigger button, so we have to behave the same, for the mobile click to work
system_arguments[:button_arguments][:id] = "dialog-show-#{system_arguments[:dialog_arguments][:id]}"

system_arguments[:button_arguments] = set_action_arguments(system_arguments[:button_arguments])
add_option_to_mobile_menu(system_arguments[:button_arguments], mobile_icon, mobile_label, :default)

Primer::OpenProject::PageHeader::Dialog.new(**system_arguments)
},
},
}
Expand Down Expand Up @@ -183,6 +196,8 @@ def initialize(mobile_menu_label: I18n.t("label_more"), **system_arguments)

def render?
raise ArgumentError, "PageHeader needs a title and a breadcrumb. Please use the `with_title` and `with_breadcrumbs` slot" unless breadcrumbs? || Rails.env.production?
raise ArgumentError, "PageHeader allows only a maximum of 5 actions" if actions.count > 5

title? && breadcrumbs?
end

Expand Down Expand Up @@ -253,32 +268,6 @@ def anchor_string_to_object(html_string)
def anchor_tag_string?(item)
item.is_a?(String) && item.start_with?("\u003c")
end

# A Helper class to create ActionMenus inside the PageHeader action slot
class PageHeaderActionMenu < Primer::Component
status :open_project

# @param menu_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionMenu) %>.
# @param button_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Beta::Button) %> or <%= link_to_component(Primer::Beta::IconButton) %>, depending on the value of the `icon:` argument.
def initialize(menu_arguments: {}, button_arguments: {})
@menu = Primer::Alpha::ActionMenu.new(**menu_arguments)
@button = @menu.with_show_button(icon: "triangle-down", **button_arguments)
end

def render_in(view_context, &block)
super(view_context) do
block.call(@menu, @button)
end
end

def before_render
content
end

def call
render(@menu)
end
end
end
end
end
38 changes: 38 additions & 0 deletions app/components/primer/open_project/page_header/dialog.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

module Primer
module OpenProject
class PageHeader
# A Helper class to create ActionMenus inside the PageHeader action slot
# Do not use standalone
class Dialog < Primer::Component
status :open_project

# @param dialog_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::Dialog) %>.
# @param button_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Beta::Button) %> or <%= link_to_component(Primer::Beta::IconButton) %>, depending on the value of the `icon:` argument.
def initialize(dialog_arguments: {}, button_arguments: {})
callback = button_arguments.delete(:button_block)

@dialog = Primer::Alpha::Dialog.new(**dialog_arguments)
@button = @dialog.with_show_button(**button_arguments) do |button|
callback&.call(button)
end
end

def render_in(view_context, &block)
super(view_context) do
block&.call(@dialog, @button)
end
end

def before_render
content
end

def call
render(@dialog)
end
end
end
end
end
38 changes: 38 additions & 0 deletions app/components/primer/open_project/page_header/menu.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

module Primer
module OpenProject
class PageHeader
# A Helper class to create ActionMenus inside the PageHeader action slot
# It should not be used standalone
class Menu < Primer::Component
status :open_project

# @param menu_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::ActionMenu) %>.
# @param button_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Beta::Button) %> or <%= link_to_component(Primer::Beta::IconButton) %>, depending on the value of the `icon:` argument.
def initialize(menu_arguments: {}, button_arguments: {})
callback = button_arguments.delete(:button_block)

@menu = Primer::Alpha::ActionMenu.new(**menu_arguments)
@button = @menu.with_show_button(**button_arguments) do |button|
callback&.call(button)
end
end

def render_in(view_context, &block)
super(view_context) do
block&.call(@menu, @button)
end
end

def before_render
content
end

def call
render(@menu)
end
end
end
end
end
48 changes: 48 additions & 0 deletions previews/primer/open_project/page_header_preview.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,54 @@ def actions
render_with_template(locals: {})
end

# @label With a menu inside the actions
def menu_actions
callback = lambda do |button|
button.with_leading_visual_icon(icon: :alert)
"Click me"
end

render(Primer::OpenProject::PageHeader.new) do |component|
component.with_title { "Great news" }
component.with_breadcrumbs([{ href: "/foo", text: "Foo" }, { href: "/bar", text: "Bar" }, "Baz"])

component.with_action_button(mobile_icon: "star", mobile_label: "Star") do |button|
button.with_leading_visual_icon(icon: "star")
"Star"
end
component.with_action_menu(menu_arguments: { anchor_align: :end },
button_arguments: { button_block: callback }) do |menu|
menu.with_item(label: "Subitem 1") do |item|
item.with_leading_visual_icon(icon: :paste)
end
menu.with_item(label: "Subitem 2") do |item|
item.with_leading_visual_icon(icon: :log)
end
end
end
end

# @label With a dialog inside the actions
def dialog_actions
callback = lambda do |button|
button.with_leading_visual_icon(icon: :plus)
"Open dialog"
end

render(Primer::OpenProject::PageHeader.new) do |component|
component.with_title { "Great news" }
component.with_breadcrumbs([{ href: "/foo", text: "Foo" }, { href: "/bar", text: "Bar" }, "Baz"])

component.with_action_icon_button(icon: :trash, mobile_icon: :trash, label: "Delete", scheme: :danger)

component.with_action_dialog(mobile_icon: :plus, mobile_label: "Open dialog",
dialog_arguments: { id: "my_dialog", title: "A great dialog" },
button_arguments: { button_block: callback }) do |d|
d.with_body { "Hello" }
end
end
end

# @label With leading action (on wide)
# **Leading action** is only shown on **wide screens** by default.
# If you want to override that behaviour please use the system_argument: **display**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,4 @@
<% end %>
<% component.with_action_button(mobile_icon: nil, mobile_label: nil) { "Hide on mobile" } %>
<% component.with_action_icon_button(icon: "trash", mobile_icon: "trash", label: "Delete", scheme: :danger) %>
<% component.with_action_menu(menu_arguments: { anchor_align: :end }, button_arguments: { icon: "op-kebab-vertical", "aria-label": "Some actions" }) do |menu, button| %>
<% menu.with_item(label: "Subitem 1") do |item| %>
<% item.with_leading_visual_icon(icon: :paste) %>
<% end %>
<% menu.with_item(label: "Subitem 2") do |item| %>
<% item.with_leading_visual_icon(icon: :log) %>
<% end %>
<% end %>
<% end %>
4 changes: 3 additions & 1 deletion test/components/component_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,9 @@ def test_registered_components
"Primer::Content",
"Primer::Navigation::TabComponent",
"Primer::OpenProject::BorderGrid::Cell",
"Primer::OpenProject::GridLayout::Area"
"Primer::OpenProject::GridLayout::Area",
"Primer::OpenProject::PageHeader::Menu",
"Primer::OpenProject::PageHeader::Dialog"
]

primer_component_files_count = Dir["app/components/**/*.rb"].count { |p| p.exclude?("/experimental/") }
Expand Down
14 changes: 14 additions & 0 deletions test/components/primer/open_project/page_header/dialog_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

require "components/test_helper"

class PrimerOpenProjectPageHeaderDialogTest < Minitest::Test
include Primer::ComponentTestHelpers

def test_renders
render_inline(Primer::OpenProject::PageHeader::Dialog.new(dialog_arguments: { id: "my_dialog", title: "A great dialog"},
button_arguments: { button_block: lambda { |b| "I AM A BUTTON" } }))

assert_text("I AM A BUTTON")
end
end
17 changes: 17 additions & 0 deletions test/components/primer/open_project/page_header/menu_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

require "components/test_helper"

class PrimerOpenProjectPageHeaderMenuTest < Minitest::Test
include Primer::ComponentTestHelpers

def test_renders
render_inline(Primer::OpenProject::PageHeader::Menu.new(menu_arguments: { anchor_align: :end },
button_arguments: { button_block: lambda { |b| "I AM A BUTTON" } })) do |menu|
menu.with_item(label: "Subitem 1")
end

assert_text("I AM A BUTTON")
assert_text("Subitem 1")
end
end
49 changes: 39 additions & 10 deletions test/components/primer/open_project/page_header_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,6 @@ def test_renders_actions
header.with_action_text { "An additional hint" }
header.with_action_icon_button(icon: "trash", mobile_icon: "trash", label: "Delete") { "Delete" }
header.with_action_link(mobile_icon: "link", mobile_label: "Link to", href: "https://community.openproject.com") { "Link to.." }
header.with_action_menu(menu_arguments: { anchor_align: :end }, button_arguments: { icon: "op-kebab-vertical", "aria-label": "Some actions" }) do |menu, button|
menu.with_item(label: "Subitem 1") do |item|
item.with_leading_visual_icon(icon: :paste)
end
end
end

# Renders the actions
Expand All @@ -80,9 +75,6 @@ def test_renders_actions
assert_selector(".PageHeader-actions")
assert_text("An action")
assert_text("An additional hint")
assert_selector(".PageHeader-actions .ActionListItem-label") do
assert_text("Subitem 1")
end

# The mobile variant of the actions is already rendered, but hidden
assert_selector(".PageHeader-contextBar")
Expand All @@ -96,12 +88,49 @@ def test_renders_actions
assert_selector(".PageHeader-contextBar .ActionListItem-label") do
assert_text("Link to")
end

# The text is hidden on mobile
assert_selector("span.d-none.d-sm-flex")
end

def test_renders_a_menu_as_action
render_inline(Primer::OpenProject::PageHeader.new) do |header|
header.with_title { "Hello" }
header.with_breadcrumbs(breadcrumb_elements)

header.with_action_link(mobile_icon: "link", mobile_label: "Link to", href: "https://community.openproject.com") { "Link to.." }
header.with_action_menu(menu_arguments: { anchor_align: :end }, button_arguments: { icon: "op-kebab-vertical", "aria-label": "Some actions" }) do |menu, button|
menu.with_item(label: "Subitem 1") do |item|
item.with_leading_visual_icon(icon: :paste)
end
end
end

assert_selector(".PageHeader-actions .ActionListItem-label") do
assert_text("Subitem 1")
end
assert_selector(".PageHeader-contextBar .ActionListItem-label") do
assert_text("Link to")
end
assert_selector(".PageHeader-contextBar .ActionListItem-label") do
assert_text("Subitem 1")
end
end

# The text is hidden on mobile
assert_selector("span.d-none.d-sm-flex")
def test_renders_a_dialog_as_action
render_inline(Primer::OpenProject::PageHeader.new) do |header|
header.with_title { "Hello" }
header.with_breadcrumbs(breadcrumb_elements)
header.with_action_dialog(mobile_icon: "alert",
mobile_label: "Open a dialog",
dialog_arguments: { id: "my-dialog", title: "A great dialog" },
button_arguments: { icon: "alert", "aria-label": "Some dialog" }) do |dialog|
dialog.with_body { "Hello" }
end
end

assert_selector(".PageHeader-actions #dialog-show-my-dialog")
assert_selector("dialog#my-dialog")
end

def test_renders_single_action
Expand Down

0 comments on commit d3422da

Please sign in to comment.