diff --git a/.changeset/giant-carrots-explain.md b/.changeset/giant-carrots-explain.md
new file mode 100644
index 0000000000..1ce100f768
--- /dev/null
+++ b/.changeset/giant-carrots-explain.md
@@ -0,0 +1,5 @@
+---
+'@openproject/primer-view-components': minor
+---
+
+Extend page header with parent link, context bar actions and responsiveness
diff --git a/app/components/primer/open_project/page_header.html.erb b/app/components/primer/open_project/page_header.html.erb
index bf35aae4d1..53f3240b25 100644
--- a/app/components/primer/open_project/page_header.html.erb
+++ b/app/components/primer/open_project/page_header.html.erb
@@ -1,9 +1,17 @@
<%= render Primer::BaseComponent.new(**@system_arguments) do %>
- <%= breadcrumbs %>
+ <% if parent_link || breadcrumbs || context_bar_actions %>
+
+ <% end %>
+
+
<%= description %>
<% end %>
diff --git a/app/components/primer/open_project/page_header.pcss b/app/components/primer/open_project/page_header.pcss
index df67b3fc02..4539010093 100644
--- a/app/components/primer/open_project/page_header.pcss
+++ b/app/components/primer/open_project/page_header.pcss
@@ -12,6 +12,13 @@
}
}
+.PageHeader-contextBar {
+ display: flex;
+ flex-flow: row;
+ justify-content: flex-end;
+ align-items: center;
+}
+
.PageHeader-titleBar {
display: flex;
flex-flow: row;
@@ -38,8 +45,9 @@
/* Add 1 or 2 buttons to the right of the heading */
.PageHeader-actions {
- margin: var(--base-size-4) 0 var(--base-size-4) var(--base-size-4);
+ margin: 0 0 0 var(--base-size-4);
justify-content: flex-end;
+ display: flex;
& + .PageHeader-description {
margin-top: var(--base-size-4);
@@ -50,9 +58,19 @@
display: block;
width: 100%;
margin-bottom: var(--base-size-8);
+ padding-bottom: var(--base-size-4);
}
.PageHeader-backButton {
margin-top: 2px; /* to center align with label */
margin-right: var(--base-size-4);
}
+
+.PageHeader-parentLink {
+ flex: 1 1 auto;
+ margin-bottom: var(--base-size-4);
+}
+
+.PageHeader-contextBarActions {
+ margin: 0 0 0 var(--base-size-4);
+}
\ No newline at end of file
diff --git a/app/components/primer/open_project/page_header.rb b/app/components/primer/open_project/page_header.rb
index 4e99187d09..9dbbff1012 100644
--- a/app/components/primer/open_project/page_header.rb
+++ b/app/components/primer/open_project/page_header.rb
@@ -27,6 +27,11 @@ class PageHeader < Primer::Component
"triangle-left"
].freeze
+ DEFAULT_BACK_BUTTON_DISPLAY = [:none, :flex].freeze
+ DEFAULT_BREADCRUMBS_DISPLAY = [:none, :flex].freeze
+ DEFAULT_PARENT_LINK_DISPLAY = [:block, :none].freeze
+ DEFAULT_CONTEXT_BAR_ACTIONS_DISPLAY = [:block, :none].freeze
+
status :open_project
# The title of the page header
@@ -64,7 +69,21 @@ class PageHeader < Primer::Component
Primer::BaseComponent.new(**system_arguments)
}
+ # Context Bar Actions
+ # By default shown on narrow screens. Can be overridden with system_argument: display
+ #
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
+ renders_one :context_bar_actions, lambda { |**system_arguments|
+ deny_tag_argument(**system_arguments)
+ system_arguments[:tag] = :div
+ system_arguments[:classes] = class_names(system_arguments[:classes], "PageHeader-contextBarActions")
+ system_arguments[:display] ||= DEFAULT_CONTEXT_BAR_ACTIONS_DISPLAY
+
+ Primer::BaseComponent.new(**system_arguments)
+ }
+
# Optional back button prepend the title
+ # By default shown on wider screens. Can be overridden with system_argument: display
#
# @param size [Symbol] <%= one_of(Primer::OpenProject::PageHeader::BACK_BUTTON_SIZE_OPTIONS) %>
# @param icon [String] <%= one_of(Primer::OpenProject::PageHeader::BACK_BUTTON_ICON_OPTIONS) %>
@@ -80,16 +99,35 @@ class PageHeader < Primer::Component
system_arguments[:size] = fetch_or_fallback(BACK_BUTTON_SIZE_OPTIONS, size, DEFAULT_BACK_BUTTON_SIZE)
system_arguments[:icon] = fetch_or_fallback(BACK_BUTTON_ICON_OPTIONS, icon, DEFAULT_BACK_BUTTON_ICON)
system_arguments[:classes] = class_names(system_arguments[:classes], "PageHeader-backButton")
+ system_arguments[:display] ||= DEFAULT_BACK_BUTTON_DISPLAY
Primer::Beta::IconButton.new(**system_arguments)
}
+ # Optional parent link in the context area
+ # By default shown on narrow screens. Can be overridden with system_argument: display
+ #
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
+ renders_one :parent_link, lambda { |icon: DEFAULT_BACK_BUTTON_ICON, **system_arguments, &block|
+ deny_tag_argument(**system_arguments)
+ system_arguments[:icon] = fetch_or_fallback(BACK_BUTTON_ICON_OPTIONS, icon, DEFAULT_BACK_BUTTON_ICON)
+ system_arguments[:classes] = class_names(system_arguments[:classes], "PageHeader-parentLink")
+ system_arguments[:display] ||= DEFAULT_PARENT_LINK_DISPLAY
+
+ render(Primer::Beta::Link.new(scheme: :primary, muted: true, **system_arguments)) do
+ render(Primer::Beta::Octicon.new(icon: "arrow-left", "aria-label": "aria_label", mr: 2)) + content_tag(:span, &block)
+ end
+ }
+
# Optional breadcrumbs above the title row
+ # By default shown on wider screens. Can be overridden with system_argument: display
#
# @param items [Array] Items is an array of strings, hash {href, text} or an anchor tag string
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
renders_one :breadcrumbs, lambda { |items, **system_arguments|
system_arguments[:classes] = class_names(system_arguments[:classes], "PageHeader-breadcrumbs")
+ system_arguments[:display] ||= DEFAULT_BREADCRUMBS_DISPLAY
+
render(Primer::Beta::Breadcrumbs.new(**system_arguments)) do |breadcrumbs|
items.each do |item|
item = anchor_string_to_object(item) if anchor_tag_string?(item)
diff --git a/previews/primer/open_project/page_header_preview.rb b/previews/primer/open_project/page_header_preview.rb
index bbf5dea68b..258a17a872 100644
--- a/previews/primer/open_project/page_header_preview.rb
+++ b/previews/primer/open_project/page_header_preview.rb
@@ -21,22 +21,32 @@ def default
# @param with_back_button [Boolean]
# @param back_button_size [Symbol] select [small, medium, large]
# @param with_breadcrumbs [Boolean]
+ # @param with_actions [Boolean]
+ # @param with_context_bar_actions [Boolean]
+ # @param with_parent_link [Boolean]
def playground(
variant: :medium,
title: "Hello",
description: "Last updated 5 minutes ago by XYZ.",
with_back_button: false,
back_button_size: :medium,
- with_breadcrumbs: false
+ with_breadcrumbs: false,
+ with_actions: false,
+ with_context_bar_actions: false,
+ with_parent_link: false
)
breadcrumb_items = [{ href: "/foo", text: "Foo" }, { href: "/bar", text: "Bar" }, "Baz"]
- render(Primer::OpenProject::PageHeader.new) do |header|
- header.with_title(variant: variant) { title }
- header.with_description { description }
- header.with_back_button(href: "#", size: back_button_size, 'aria-label': "Back") if with_back_button
- header.with_breadcrumbs(breadcrumb_items) if with_breadcrumbs
- end
+ render_with_template(locals: { variant: variant,
+ title: title,
+ description: description,
+ with_back_button: with_back_button,
+ back_button_size: back_button_size,
+ with_breadcrumbs: with_breadcrumbs,
+ with_parent_link: with_parent_link,
+ with_actions: with_actions,
+ with_context_bar_actions: with_context_bar_actions,
+ breadcrumb_items: breadcrumb_items })
end
# @label Large
@@ -52,7 +62,11 @@ def actions
render_with_template(locals: {})
end
- # @label With back button
+ # @label With back button (on wide)
+ # **Back button** is only shown on **wider than narrow screens** by default.
+ # If you want to override that behaviour please use the system_argument: **display**
+ # e.g. **component.with\_breadcrumbs(display: [:block, :block])**
+ #
# @param href [String] text
# @param size [Symbol] select [small, medium, large]
# @param icon [String] select ["arrow-left", "chevron-left", "triangle-left"]
@@ -63,7 +77,11 @@ def back_button(href: "#", size: :medium, icon: "arrow-left")
end
end
- # @label With breadcrumbs
+ # @label With breadcrumbs (on wide)
+ # **Breadcrumbs** are only shown on **wider than narrow screens** by default.
+ # If you want to override that behaviour please use the system_argument: **display**
+ # e.g. **component.with\_breadcrumbs(display: [:block, :block])**
+ #
def breadcrumbs
breadcrumb_items = [
{ href: "/foo", text: "Foo" },
@@ -75,6 +93,27 @@ def breadcrumbs
header.with_breadcrumbs(breadcrumb_items)
end
end
+
+ # @label With parent link (on narrow)
+ # **Parent link** is only shown on **narrow screens** by default.
+ # If you want to override that behaviour please use the system_argument: **display**
+ # e.g. **component.with\_parent\_link(display: [:block, :block])**
+ #
+ def parent_link
+ render(Primer::OpenProject::PageHeader.new) do |header|
+ header.with_title { "A title" }
+ header.with_parent_link(href: "test") { "Parent link" }
+ end
+ end
+
+ # @label With context bar actions (on narrow)
+ # **Context bar actions** are only shown on **narrow screens** by default.
+ # If you want to override that behaviour please use the system_argument: **display**
+ # e.g. **component.with\_context\_bar\_actions(display: [:block, :block])**
+ #
+ def context_bar_actions
+ render_with_template(locals: {})
+ end
end
end
end
diff --git a/previews/primer/open_project/page_header_preview/context_bar_actions.html.erb b/previews/primer/open_project/page_header_preview/context_bar_actions.html.erb
new file mode 100644
index 0000000000..b053c79025
--- /dev/null
+++ b/previews/primer/open_project/page_header_preview/context_bar_actions.html.erb
@@ -0,0 +1,25 @@
+<%= render(Primer::OpenProject::PageHeader.new) do |component| %>
+ <% component.with_title(tag: :h1) do %>
+ A title
+ <% end %>
+ <% component.with_description do %>
+ A description with actions
+ <% end %>
+ <% component.with_parent_link(href: "#") do %>
+ Parent link
+ <% end %>
+ <% component.with_context_bar_actions do %>
+ <%= render(Primer::Alpha::ActionMenu.new) do |component| %>
+ <% component.with_show_button { "Menu" } %>
+ <% component.with_item(label: "Item", tag: :button, value: "") %>
+ <% component.with_item(
+ label: "Show dialog",
+ tag: :button,
+ content_arguments: { "data-show-dialog-id": "my-dialog" },
+ value: "",
+ scheme: :danger
+ ) %>
+ <% end %>
+ <% end %>
+<% end %>
+
diff --git a/previews/primer/open_project/page_header_preview/playground.html.erb b/previews/primer/open_project/page_header_preview/playground.html.erb
new file mode 100644
index 0000000000..e7d1a9c275
--- /dev/null
+++ b/previews/primer/open_project/page_header_preview/playground.html.erb
@@ -0,0 +1,32 @@
+<%= render Primer::OpenProject::PageHeader.new do |header| %>
+ <%= header.with_title(variant: variant) { title } %>
+ <%= header.with_description { description } %>
+ <%= header.with_back_button(href: "#", size: back_button_size, 'aria-label': "Back") if with_back_button %>
+ <%= header.with_breadcrumbs(breadcrumb_items) if with_breadcrumbs %>
+ <%= header.with_parent_link(href: "#") { "Parent link" } if with_parent_link %>
+ <% if with_actions %>
+ <% header.with_actions do %>
+ <%= render(Primer::Alpha::ActionMenu.new) do |component| %>
+ <% component.with_show_button { "Menu" } %>
+ <% component.with_item(label: "Item", tag: :button, value: "") %>
+ <% component.with_item(
+ label: "Show dialog",
+ tag: :button,
+ content_arguments: { "data-show-dialog-id": "my-dialog" },
+ value: "",
+ scheme: :danger
+ ) %>
+ <% end %>
+ <% end %>
+ <% end %>
+ <% if with_context_bar_actions %>
+ <% header.with_context_bar_actions do %>
+ <%= render(Primer::Beta::IconButton.new(
+ scheme: :default,
+ size: :small,
+ icon: "pencil",
+ "aria-label": "aria_label"
+ )) %>
+ <% end %>
+ <% end %>
+<% end %>
diff --git a/test/components/primer/open_project/page_header_test.rb b/test/components/primer/open_project/page_header_test.rb
index c9fc6bfd0f..996ee1fbfd 100644
--- a/test/components/primer/open_project/page_header_test.rb
+++ b/test/components/primer/open_project/page_header_test.rb
@@ -82,4 +82,26 @@ def test_renders_breadcrumbs
assert_selector("nav[aria-label='Breadcrumb'].PageHeader-breadcrumbs .breadcrumb-item a[href='/foo/bar']")
assert_selector("nav[aria-label='Breadcrumb'].PageHeader-breadcrumbs .breadcrumb-item a[href='#']")
end
+
+ def test_renders_parent_link
+ render_inline(Primer::OpenProject::PageHeader.new) do |header|
+ header.with_title { "Hello" }
+ header.with_parent_link(href: "test") { "Parent link" }
+ end
+
+ assert_text("Hello")
+ assert_selector(".PageHeader-title")
+ assert_selector(".PageHeader-parentLink")
+ end
+
+ def test_renders_context_bar_actions
+ render_inline(Primer::OpenProject::PageHeader.new) do |header|
+ header.with_title { "Hello" }
+ header.with_context_bar_actions { "An context bar action" }
+ end
+
+ assert_text("Hello")
+ assert_selector(".PageHeader-title")
+ assert_selector(".PageHeader-contextBarActions")
+ end
end