Skip to content

Commit ac9f727

Browse files
feat[#50499]: Add back button and breadcrumbs to page header (#45)
* feat[#50499]: Add back button and breadcrumbs to page header * chore[#50499]: Set renders_one BackButton attributes size and icon defaults * fix[#50499]: Remove I18n from PageHeader * fix[#50499]: Rename CSS class PageHeader-back_button to PageHeader-backButton * fix[#50499]: Fix RuboCop errors and warnings * fix[#50499]: Vertically center back button * fix[#50499]: Add aria-label in preview for with_back_button * test[#50499]: Add renders_back_button test * test[#50499]: Add renders_breadcrumbs test * fix[#50499]: Fix class names in PageHeader * chore[#50499]: Remove show_breadcrumb option in PageHeader.with_breadcrumbs * chore[#50499]: Remove manually added static classes * feat[#50499]: Support **system_arguments in PageHeader breadcrumbs slot * fix[#50499]: Readd @Label With actions in PageHeaderPreview * fix[#50499]: Fix spaces * fix[#50499]: Change PageHeader align-items to baseline * fix[#50499]: Add size and icon options and use fetch_or_fallback * chore[#50499]: Add params options in preview for PageHeader with back button * chore[#50499]: Add params information in back_button slot * chore[#50499]: Add params options in preview for PageHeader with breadcrumbs * chore[#50499]: Add anchor_tag_string helper function * fix[#50499]: Fix back button and title wrapping on small screen * chore[#50499]: Remove long testing title from preview * chore[#50499]: Remove back button in breadcrumb preview * chore[#50499]: Sort PageHeader with-alternatives alphabetically * chore[#50499]: Add toggles for back button and breadcrumbs in playground * chore[#50499]: Add back button size parameter to playground * Center back button in any cases. Unfortunately, there is a two pixel misalignment which we have to cancel out. This is most likely coming from the arrow being thinner than the font --------- Co-authored-by: Henriette Darge <h.darge@openproject.com>
1 parent f4903ea commit ac9f727

6 files changed

Lines changed: 172 additions & 9 deletions

File tree

.changeset/green-bananas-kneel.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@openproject/primer-view-components': minor
3+
---
4+
5+
Add back button and breadcrumbs support to PageHeader
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
<%= render Primer::BaseComponent.new(**@system_arguments) do %>
2-
<%= title %>
2+
<%= breadcrumbs %>
3+
<div class="PageHeader-titleBar">
4+
<%= back_button %>
5+
<%= title %>
6+
<%= actions %>
7+
</div>
38
<%= description %>
4-
<%= actions %>
59
<% end %>

app/components/primer/open_project/page_header.pcss

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,24 @@
55
padding-bottom: var(--stack-padding-condensed);
66
margin-bottom: var(--stack-gap-normal);
77
border-bottom: var(--borderWidth-thin) solid var(--borderColor-muted);
8-
flex-flow: row wrap;
9-
justify-content: flex-end; /* Keep actions right aligned. */
8+
flex-flow: column;
109

1110
@media (max-width: 767.98px) {
1211
border-bottom: 0;
1312
}
1413
}
1514

15+
.PageHeader-titleBar {
16+
display: flex;
17+
flex-flow: row;
18+
justify-content: flex-end;
19+
align-items: center; /* Keep back button vertically aligned. */
20+
}
21+
1622
.PageHeader-title {
1723
font-size: 24px;
1824
font-weight: var(--base-text-weight-normal);
1925
flex: 1 1 auto;
20-
order: 0;
2126
}
2227

2328
.PageHeader-title--large {
@@ -29,17 +34,25 @@
2934
font-size: var(--text-body-size-medium);
3035
color: var(--fgColor-muted);
3136
flex: 1 100%;
32-
order: 2;
3337
}
3438

3539
/* Add 1 or 2 buttons to the right of the heading */
3640
.PageHeader-actions {
3741
margin: var(--base-size-4) 0 var(--base-size-4) var(--base-size-4);
38-
align-self: center;
3942
justify-content: flex-end;
40-
order: 1;
4143

4244
& + .PageHeader-description {
4345
margin-top: var(--base-size-4);
4446
}
4547
}
48+
49+
.PageHeader-breadcrumbs {
50+
display: block;
51+
width: 100%;
52+
margin-bottom: var(--base-size-8);
53+
}
54+
55+
.PageHeader-backButton {
56+
margin-top: 2px; /* to center align with label */
57+
margin-right: var(--base-size-4);
58+
}

app/components/primer/open_project/page_header.rb

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,20 @@ class PageHeader < Primer::Component
1313
DEFAULT_HEADER_VARIANT
1414
].freeze
1515

16+
DEFAULT_BACK_BUTTON_SIZE = :medium
17+
BACK_BUTTON_SIZE_OPTIONS = [
18+
:small,
19+
DEFAULT_HEADER_VARIANT,
20+
:large
21+
].freeze
22+
23+
DEFAULT_BACK_BUTTON_ICON = "arrow-left"
24+
BACK_BUTTON_ICON_OPTIONS = [
25+
DEFAULT_BACK_BUTTON_ICON,
26+
"chevron-left",
27+
"triangle-left"
28+
].freeze
29+
1630
status :open_project
1731

1832
# The title of the page header
@@ -50,6 +64,45 @@ class PageHeader < Primer::Component
5064
Primer::BaseComponent.new(**system_arguments)
5165
}
5266

67+
# Optional back button prepend the title
68+
#
69+
# @param size [Symbol] <%= one_of(Primer::OpenProject::PageHeader::BACK_BUTTON_SIZE_OPTIONS) %>
70+
# @param icon [String] <%= one_of(Primer::OpenProject::PageHeader::BACK_BUTTON_ICON_OPTIONS) %>
71+
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
72+
renders_one :back_button, lambda { |
73+
size: DEFAULT_BACK_BUTTON_SIZE,
74+
icon: DEFAULT_BACK_BUTTON_ICON,
75+
**system_arguments
76+
|
77+
deny_tag_argument(**system_arguments)
78+
system_arguments[:tag] = :a
79+
system_arguments[:scheme] = :invisible
80+
system_arguments[:size] = fetch_or_fallback(BACK_BUTTON_SIZE_OPTIONS, size, DEFAULT_BACK_BUTTON_SIZE)
81+
system_arguments[:icon] = fetch_or_fallback(BACK_BUTTON_ICON_OPTIONS, icon, DEFAULT_BACK_BUTTON_ICON)
82+
system_arguments[:classes] = class_names(system_arguments[:classes], "PageHeader-backButton")
83+
84+
Primer::Beta::IconButton.new(**system_arguments)
85+
}
86+
87+
# Optional breadcrumbs above the title row
88+
#
89+
# @param items [Array<String, Hash>] Items is an array of strings, hash {href, text} or an anchor tag string
90+
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
91+
renders_one :breadcrumbs, lambda { |items, **system_arguments|
92+
system_arguments[:classes] = class_names(system_arguments[:classes], "PageHeader-breadcrumbs")
93+
render(Primer::Beta::Breadcrumbs.new(**system_arguments)) do |breadcrumbs|
94+
items.each do |item|
95+
item = anchor_string_to_object(item) if anchor_tag_string?(item)
96+
97+
if item.is_a?(String)
98+
breadcrumbs.with_item(href: "#") { item }
99+
else
100+
breadcrumbs.with_item(href: item[:href]) { item[:text] }
101+
end
102+
end
103+
end
104+
}
105+
53106
def initialize(**system_arguments)
54107
@system_arguments = deny_tag_argument(**system_arguments)
55108

@@ -64,6 +117,23 @@ def initialize(**system_arguments)
64117
def render?
65118
title?
66119
end
120+
121+
private
122+
123+
# transform anchor tag strings to {href, text} objects
124+
# e.g "\u003ca href=\"/admin\"\u003eAdministration\u003c/a\u003e"
125+
def anchor_string_to_object(html_string)
126+
# Parse the HTML
127+
doc = Nokogiri::HTML.fragment(html_string)
128+
# Extract href and text
129+
anchor = doc.at("a")
130+
{ href: anchor["href"], text: anchor.text }
131+
end
132+
133+
# Check if the item is an anchor tag string e.g "\u003ca href=\"/admin\"\u003eAdministration\u003c/a\u003e"
134+
def anchor_tag_string?(item)
135+
item.is_a?(String) && item.start_with?("\u003c")
136+
end
67137
end
68138
end
69139
end

previews/primer/open_project/page_header_preview.rb

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,24 @@ def default
1818
# @param variant [Symbol] select [medium, large]
1919
# @param title [String] text
2020
# @param description [String] text
21-
def playground(variant: :medium, title: "Hello", description: "Last updated 5 minutes ago by XYZ.")
21+
# @param with_back_button [Boolean]
22+
# @param back_button_size [Symbol] select [small, medium, large]
23+
# @param with_breadcrumbs [Boolean]
24+
def playground(
25+
variant: :medium,
26+
title: "Hello",
27+
description: "Last updated 5 minutes ago by XYZ.",
28+
with_back_button: false,
29+
back_button_size: :medium,
30+
with_breadcrumbs: false
31+
)
32+
breadcrumb_items = [{ href: "/foo", text: "Foo" }, { href: "/bar", text: "Bar" }, "Baz"]
33+
2234
render(Primer::OpenProject::PageHeader.new) do |header|
2335
header.with_title(variant: variant) { title }
2436
header.with_description { description }
37+
header.with_back_button(href: "#", size: back_button_size, 'aria-label': "Back") if with_back_button
38+
header.with_breadcrumbs(breadcrumb_items) if with_breadcrumbs
2539
end
2640
end
2741

@@ -37,6 +51,30 @@ def large_title
3751
def actions
3852
render_with_template(locals: {})
3953
end
54+
55+
# @label With back button
56+
# @param href [String] text
57+
# @param size [Symbol] select [small, medium, large]
58+
# @param icon [String] select ["arrow-left", "chevron-left", "triangle-left"]
59+
def back_button(href: "#", size: :medium, icon: "arrow-left")
60+
render(Primer::OpenProject::PageHeader.new) do |header|
61+
header.with_title { "Hello" }
62+
header.with_back_button(href: href, size: size, icon: icon, 'aria-label': "Back")
63+
end
64+
end
65+
66+
# @label With breadcrumbs
67+
def breadcrumbs
68+
breadcrumb_items = [
69+
{ href: "/foo", text: "Foo" },
70+
"\u003ca href=\"/foo/bar\"\u003eBar\u003c/a\u003e",
71+
"Baz"
72+
]
73+
render(Primer::OpenProject::PageHeader.new) do |header|
74+
header.with_title { "A title" }
75+
header.with_breadcrumbs(breadcrumb_items)
76+
end
77+
end
4078
end
4179
end
4280
end

test/components/primer/open_project/page_header_test.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,37 @@ def test_renders_actions
4949
assert_text("An action")
5050
assert_selector(".PageHeader-actions")
5151
end
52+
53+
def test_renders_back_button
54+
render_inline(Primer::OpenProject::PageHeader.new) do |header|
55+
header.with_title { "Hello" }
56+
header.with_back_button(href: "/link", 'aria-label': "Back")
57+
end
58+
59+
assert_text("Hello")
60+
assert_selector(".PageHeader-title")
61+
assert_selector(".PageHeader-backButton")
62+
assert_selector(".Button-withTooltip a[href='/link']")
63+
end
64+
65+
def test_renders_breadcrumbs
66+
breadcrumb_items = [
67+
{ href: "/foo", text: "Foo" },
68+
"\u003ca href=\"/foo/bar\"\u003eBar\u003c/a\u003e",
69+
"test"
70+
]
71+
72+
render_inline(Primer::OpenProject::PageHeader.new) do |header|
73+
header.with_title { "Hello" }
74+
header.with_breadcrumbs(breadcrumb_items)
75+
end
76+
77+
assert_text("Hello")
78+
assert_selector(".PageHeader-title")
79+
assert_selector(".PageHeader-breadcrumbs")
80+
81+
assert_selector("nav[aria-label='Breadcrumb'].PageHeader-breadcrumbs .breadcrumb-item a[href='/foo']")
82+
assert_selector("nav[aria-label='Breadcrumb'].PageHeader-breadcrumbs .breadcrumb-item a[href='/foo/bar']")
83+
assert_selector("nav[aria-label='Breadcrumb'].PageHeader-breadcrumbs .breadcrumb-item a[href='#']")
84+
end
5285
end

0 commit comments

Comments
 (0)