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 %> +
+ <%= parent_link %> + <%= breadcrumbs %> + <%= context_bar_actions %> +
+ <% end %> +
<%= back_button %> <%= title %> <%= actions %>
+ <%= 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