Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 0 additions & 54 deletions app/components/admin/departments/blankslate_component.rb

This file was deleted.

109 changes: 59 additions & 50 deletions app/components/admin/departments/detail_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -29,68 +29,77 @@ See COPYRIGHT and LICENSE files for more details.

<%=
component_wrapper(tag: "turbo-frame", target: "_top") do
render(Primer::Beta::BorderBox.new) do |box|
box.with_header do
flex_layout(align_items: :center, justify_content: :space_between) do |header|
header.with_column do
render(Primer::Beta::Breadcrumbs.new) do |loaf|
breadcrumb_items.each do |item|
loaf.with_item(href: item[:href] || "#", target: nil) { item[:label] }
end
end
end

if group&.ldap_managed?
header.with_column do
render(Primer::Beta::Label.new(scheme: :accent)) { I18n.t(:label_managed_by_ldap) }
end
elsif group
header.with_column do
render(
Primer::Beta::IconButton.new(
tag: :a,
href: edit_admin_department_path(group),
icon: :pencil,
scheme: :invisible,
"aria-label": I18n.t(:button_edit)
)
)
end
render(OpenProject::Common::BorderBoxListComponent.new(container: "admin-departments-detail")) do |list|
list.with_header(title: header_title) do |header|
header.with_breadcrumbs do |crumbs|
breadcrumb_items.each do |item|
crumbs.with_item(href: item[:href] || "#", target: nil) { item[:label] }
end
end
end

if show_global_empty_state? && !add_subgroup?
box.with_row do
render(Admin::Departments::BlankslateComponent.new)
if group&.ldap_managed?
header.with_label(scheme: :accent) { I18n.t(:label_managed_by_ldap) }
end
elsif show_department_empty_state? && !add_user? && !add_subgroup?
box.with_row do
render(Admin::Departments::DetailBlankslateComponent.new(group:))

if group && !group.ldap_managed?
header.with_action_icon_button(
tag: :a,
href: edit_admin_department_path(group),
icon: :pencil,
scheme: :invisible,
aria: { label: I18n.t(:button_edit) }
)
end
end

if group.blank?
list.with_empty_state(
title: t("departments.blankslate.heading"),
description: t("departments.blankslate.description"),
icon: :people,
action_label: t("departments.blankslate.add_button"),
action_icon: :plus,
action_arguments: {
href: new_department_admin_departments_path,
scheme: :primary,
data: { turbo_frame: Admin::Departments::DetailComponent.wrapper_key }
}
)
elsif group.ldap_managed?
list.with_empty_state(
title: t("departments.detail_blankslate.managed_heading"),
description: t("departments.detail_blankslate.managed_description"),
icon: :lock
)
else
child_groups.each do |child|
box.with_row do
render(Admin::Departments::DepartmentRowComponent.new(department: child))
end
list.with_empty_state(
title: t("departments.detail_blankslate.heading"),
description: t("departments.detail_blankslate.description"),
icon: :people
)
end

child_groups.each do |child|
list.with_item do
render(Admin::Departments::DepartmentRowComponent.new(department: child))
end
end

if add_subgroup?
box.with_row do
render(Admin::Departments::AddDepartmentComponent.new(group:))
end
if add_subgroup?
list.with_item do
render(Admin::Departments::AddDepartmentComponent.new(group:))
end
end

users.each do |user|
box.with_row do
render(Admin::Departments::UserRowComponent.new(user:, group:))
end
users.each do |user|
list.with_item do
render(Admin::Departments::UserRowComponent.new(user:, group:))
end
end

if add_user?
box.with_row do
render(Admin::Departments::AddUserComponent.new(group:))
end
if add_user?
list.with_item do
render(Admin::Departments::AddUserComponent.new(group:))
end
end
end
Expand Down
8 changes: 2 additions & 6 deletions app/components/admin/departments/detail_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,8 @@ def breadcrumb_items
items
end

def show_global_empty_state?
group.blank? && child_groups.empty?
end

def show_department_empty_state?
group.present? && child_groups.empty? && users.empty?
def header_title
group&.name || organization_name
end

private
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,15 @@
.Box-title
min-width: 0

// Let an embedded breadcrumb trail shrink instead of overflowing the row
> nav
min-width: 0

// The current crumb stands in for the title, so it carries the
// title's weight
.breadcrumb-item-selected
font-weight: var(--base-text-weight-semibold, 600)

&--description
margin-top: var(--op-border-box-list-header-row-gap)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,15 @@ See COPYRIGHT and LICENSE files for more details.
<% else %>
<% grid.with_area(:heading) do %>
<%= render(Primer::BaseComponent.new(tag: :div, classes: "op-border-box-list-header--heading-line")) do %>
<%= render(Primer::Beta::Truncate.new(**title_arguments, tag: title_tag, classes: title_classes)) do %>
<%= title_content %>
<% if breadcrumbs? %>
<%= render(Primer::BaseComponent.new(**title_arguments, tag: title_tag, classes: hidden_title_classes)) do %>
<%= title_content %>
<% end %>
<%= breadcrumbs %>
<% else %>
<%= render(Primer::Beta::Truncate.new(**title_arguments, tag: title_tag, classes: title_classes)) do %>
<%= title_content %>
<% end %>
<% end %>
<% if render_count? %>
<%= render(Primer::Beta::Counter.new(**counter_arguments)) %>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,24 @@ class Header < ApplicationComponent
# end
renders_one :title

# @!parse
# # Renders a breadcrumb trail in the title position.
# #
# # The trail replaces the visible title: `title:` stays mandatory
# # and renders as a visually hidden heading, so the list keeps its
# # accessible name and heading navigation. Compose crumbs on the
# # yielded `Primer::Beta::Breadcrumbs`; the last crumb is marked as
# # the current page automatically. Not available on collapsible
# # headers.
# #
# # @param system_arguments [Hash] forwarded to `Primer::Beta::Breadcrumbs`.
# # @return [ViewComponent::Slot]
# def with_breadcrumbs(**system_arguments, &block)
# end
renders_one :breadcrumbs, ->(**system_arguments) do
Primer::Beta::Breadcrumbs.new(**system_arguments)
end

# @!parse
# # Adds secondary content below the header title.
# #
Expand Down Expand Up @@ -210,6 +228,7 @@ def title_content

def before_render
raise ArgumentError, "A header title is required: pass `title:` or use the `with_title` slot." unless title?
raise ArgumentError, "Breadcrumbs are not supported on collapsible headers." if breadcrumbs? && collapsible?
end

# Resolves inferred counts after the list slots have been captured.
Expand Down Expand Up @@ -248,6 +267,12 @@ def title_classes
class_names("Box-title", title_arguments[:classes])
end

# @return [String] classes for the visually hidden title rendered
# alongside header breadcrumbs.
def hidden_title_classes
class_names(title_classes, "sr-only")
end

# @return [String, nil] ids controlled by the collapsible header.
def collapsible_id
@collapsible_id.presence
Expand Down
54 changes: 23 additions & 31 deletions app/components/work_package_relations_tab/index_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,45 +118,37 @@ def should_render_create_button?
end

def render_relation_group(title:, relation_group:, &)
render(border_box_container(
padding: :condensed,
data: { test_selector: "op-relation-group-#{relation_group.type}" }
)) do |border_box|
render(
OpenProject::Common::BorderBoxListComponent.new(
container: "op-relation-group-#{relation_group.type}",
padding: :condensed,
test_selector: "op-relation-group-#{relation_group.type}"
)
) do |list|
if relation_group.type.child? && should_render_add_child?
render_children_header(border_box, title, relation_group.count)
render_children_header(list, title, relation_group.count)
else
render_header(border_box, title, relation_group.count)
render_header(list, title, relation_group.count)
end

render_items(border_box, relation_group.all_relation_items, &)
render_items(list, relation_group.all_relation_items, &)
end
end

def render_header(border_box, title, count)
border_box.with_header(py: 3) do
concat render(Primer::Beta::Text.new(mr: 2, font_size: :normal, font_weight: :bold)) { title }
concat render(Primer::Beta::Counter.new(count:, round: true, scheme: :primary))
end
def render_header(list, title, count)
list.with_header(title:, count:, py: 3, title_arguments: { font_size: :normal })
end

def render_children_header(border_box, title, count) # rubocop:disable Metrics/AbcSize
border_box.with_header(py: 3) do
flex_layout(justify_content: :space_between, align_items: :center) do |header|
header.with_column(mr: 2) do
concat render(Primer::Beta::Text.new(mr: 2, font_size: :normal, font_weight: :bold)) { title }
concat render(Primer::Beta::Counter.new(count:, round: true, scheme: :primary))
end
header.with_column do
render(Primer::Alpha::ActionMenu.new(menu_id: ADD_CHILD_ACTION_MENU)) do |menu|
menu.with_show_button do |button|
button.with_leading_visual_icon(icon: :plus)
button.with_trailing_action_icon(icon: :"triangle-down")
t("work_package_relations_tab.label_add_child_button")
end

render_add_relations_menu_items(menu, ADD_CHILD_MENU_TYPES)
end
def render_children_header(list, title, count)
list.with_header(title:, count:, py: 3, title_arguments: { font_size: :normal }) do |header|
header.with_menu(menu_id: ADD_CHILD_ACTION_MENU) do |menu|
menu.with_show_button do |button|
button.with_leading_visual_icon(icon: :plus)
button.with_trailing_action_icon(icon: :"triangle-down")
t("work_package_relations_tab.label_add_child_button")
end

render_add_relations_menu_items(menu, ADD_CHILD_MENU_TYPES)
end
end
end
Expand Down Expand Up @@ -215,11 +207,11 @@ def description(relation_type)
I18n.t("#{I18N_NAMESPACE}.relations.#{relation_type}_description")
end

def render_items(border_box, relation_items)
def render_items(list, relation_items)
relation_items.each do |relation_item|
relation = relation_item.relation || relation_item.related
visibility = relation_item.visibility
border_box.with_row(
list.with_item(
test_selector: row_test_selector(relation, visibility),
data: data_attribute(relation)
) do
Expand Down
12 changes: 12 additions & 0 deletions lookbook/docs/components/border-box-list.md.erb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ comparable lists should use headers.
| Slot | Description |
|---|---|
| `with_title` | Block-based alternative to `title:`, for titles that need more than plain text. Takes precedence over `title:` |
| `with_breadcrumbs` | Breadcrumb trail (`Primer::Beta::Breadcrumbs`) rendered in the title position. The title stays mandatory and renders visually hidden, keeping the list's accessible name. Not available on collapsible headers |
| `with_description` | Secondary content below the title, wrapped in `Primer::Beta::Text` with muted color by default. Accepts Primer system arguments |
| `with_label` | Informational status label (`Primer::Beta::Label`) shown on the title row. Right-aligned, truncated when long, and hidden on small screens, so use it only for supplementary status, never as the sole affordance for an action |
| `with_action_button` | Button rendered in the header actions area |
Expand Down Expand Up @@ -127,6 +128,17 @@ enough for the list action.

<%= embed OpenProject::Common::BorderBoxListComponentPreview, :custom_header_content, panels: %i[source] %>

### Header breadcrumbs

Use `with_breadcrumbs` when a list header should show a hierarchy trail in the
title position, such as a department's place in the organization structure.
The breadcrumbs replace the visible title; `title:` remains mandatory and
renders as a visually hidden heading, keeping the list's accessible name and
heading navigation intact. The last crumb is marked as the current page
automatically. Breadcrumbs are not available on collapsible headers.

<%= embed OpenProject::Common::BorderBoxListComponentPreview, :header_breadcrumbs, panels: %i[source] %>

### Empty state

When no items are present, the component renders a generic
Expand Down
Loading
Loading