Skip to content
3 changes: 1 addition & 2 deletions app/components/_index.sass
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@import "enterprise_edition/banner_component"
@import "filter/filters_component"
@import "open_project/common/border_box_list_component"
@import "op_primer/border_box_table_component"
@import "op_primer/full_page_prompt_component"
@import "op_primer/form_helpers"
Expand All @@ -11,8 +12,6 @@
@import "open_project/common/inplace_edit_fields/index"
@import "open_project/common/submenu_component"
@import "open_project/common/main_menu_toggle_component"
@import "open_project/common/work_package_card_list_component"
@import "open_project/common/work_package_card_list_component/header"
@import "open_project/common/work_package_card_component"
@import "portfolios/details_component"
@import "projects/row_component"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,25 +29,25 @@ See COPYRIGHT and LICENSE files for more details.

<%= render(Primer::Beta::BorderBox.new(**@system_arguments)) do |border_box| %>
<% if header? %>
<% border_box.with_header(id: header_id) do %>
<% border_box.with_header(**header.row_args) do %>
<%= header %>
<% end %>
<% end %>

<% if items.empty? %>
<% border_box.with_row(data: { empty_list_item: true }) do %>
<%= empty_state %>
<% end %>
<% else %>
<% if items.any? %>
<% items.each do |item| %>
<% border_box.with_row(**item.row_args) do %>
<%= render(item.card) %>
<%= item %>
<% end %>
<% end %>
<% elsif empty_state? %>
<% border_box.with_row(data: { empty_list_item: true }) do %>
<%= empty_state %>
<% end %>
<% end %>

<% if footer? %>
<% border_box.with_row(scheme: :neutral) do %>
<% border_box.with_footer(**footer.footer_args) do %>
<%= footer %>
<% end %>
<% end %>
Expand Down
119 changes: 119 additions & 0 deletions app/components/open_project/common/border_box_list_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# frozen_string_literal: true

#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

module OpenProject
module Common
class BorderBoxListComponent < ApplicationComponent
include OpPrimer::ComponentHelpers

Comment thread
myabc marked this conversation as resolved.
renders_one :header, ->(**system_arguments) {
system_arguments[:list_id] ||= list_id

Header.new(**system_arguments)
}

renders_many :items, types: {
item: {
renders: ->(**system_arguments) {
Item.new(**system_arguments)
},
as: :item
},
work_package_item: {
renders: ->(work_package:, project: nil, params: {}, component_klass: WorkPackageItem, **item_arguments) {
project ||= work_package.project
item_arguments[:container] = container unless item_arguments.key?(:container)
item_arguments[:current_user] = current_user unless item_arguments.key?(:current_user)

component_klass.new(
work_package:,
project:,
params:,
**item_arguments
)
},
as: :work_package_item
}
}

renders_one :empty_state, ->(title:, description: nil, icon: nil, **system_arguments) {
EmptyState.new(title:, description:, icon:, **system_arguments)
}

renders_one :footer, ->(**system_arguments) {
system_arguments[:id] ||= dom_target(list_id, :footer) if list_id

Footer.new(**system_arguments)
}

attr_reader :container, :current_user

def initialize(container:, current_user: User.current, **system_arguments)
super()

@container = container
@current_user = current_user
@system_arguments = system_arguments

apply_container_defaults!
end

def before_render
content
apply_header_defaults!
end

def render?
header? || items.any? || empty_state? || footer?
end

private

def apply_container_defaults!
@system_arguments[:id] ||= dom_target(*Array(container))
@system_arguments[:list_id] ||= dom_target(*Array(container), :list)
end

def apply_header_defaults!
return unless header?

header.collapsible_id = [list_id, footer_id].compact.join(" ")
end

def list_id
@system_arguments[:list_id]
end

def footer_id
footer&.id
end
end
end
end
22 changes: 22 additions & 0 deletions app/components/open_project/common/border_box_list_component.sass
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// See COPYRIGHT and LICENSE files for more details.
//++

.op-border-box-list-header
display: grid
grid-template-columns: 1fr minmax(5rem, max-content) auto
grid-template-areas: "collapsible actions menu"
align-items: center

&--actions,
&--menu
margin-left: var(--stack-gap-normal)
align-self: flex-start
// Unfortunately, the invisible button style bites us here again.
margin-top: -6px
Original file line number Diff line number Diff line change
Expand Up @@ -23,33 +23,42 @@
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

require "rails_helper"
module OpenProject
module Common
class BorderBoxListComponent
class EmptyState < ApplicationComponent
include Primer::AttributesHelper

RSpec.describe OpenProject::Common::WorkPackageCardListComponent::EmptyItem, type: :component do
describe "#row_args" do
it "marks the row as an empty list item by default" do
item = described_class.new
def initialize(title:, description: nil, icon: nil, **system_arguments)
super()

expect(item.row_args[:data]).to include(empty_list_item: true)
end
@title = title
@description = description
@icon = icon
@system_arguments = system_arguments
end

def call
system_arguments = @system_arguments.deep_dup
system_arguments[:role] = "status"
system_arguments[:aria] = merge_aria(
system_arguments,
aria: { live: "polite" }
)

it "lets caller-supplied data override the default empty item data" do
item = described_class.new(
data: {
empty_list_item: false,
test_selector: "custom-empty-row"
}
)
blankslate = Primer::Beta::Blankslate.new(**system_arguments)
blankslate.with_heading(tag: :h4).with_content(@title)
blankslate.with_description { @description } if @description
blankslate.with_visual_icon(icon: @icon) if @icon

expect(item.row_args[:data]).to include(
empty_list_item: false,
test_selector: "custom-empty-row"
)
render(blankslate)
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,31 @@
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

module OpenProject
module Common
class WorkPackageCardListComponent
# Row bridge for caller-provided empty content.
class EmptyItem < ContentItem
include Primer::AttributesHelper
class BorderBoxListComponent
class Footer < ApplicationComponent
attr_reader :id

def row_args
system_arguments = @system_arguments.deep_dup
system_arguments[:data] = merge_data(
{ data: { empty_list_item: true } },
system_arguments
)
system_arguments
def initialize(**system_arguments)
super()

@id = system_arguments[:id]
@system_arguments = system_arguments
end

def footer_args
@system_arguments.deep_dup
end

def empty_item? = true
def call
content
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,55 +23,58 @@
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

module OpenProject
module Common
class WorkPackageCardListComponent
class Header < ApplicationComponent
include OpPrimer::ComponentHelpers
class BorderBoxListComponent
# Adds the standard list action menu slot used by list headers and items.
module HasMenu
extend ActiveSupport::Concern
include Primer::ClassNameHelper

renders_one :description

renders_many :actions, types: {
button: ->(**system_arguments) do
Primer::Beta::Button.new(**system_arguments)
included do
# @!parse
# # Adds a trailing action menu.
# #
# # @param menu_id [String, nil] id prefix for the Primer action menu.
# # @param button_aria_label [String, nil] accessible label for the menu button.
# # @param system_arguments [Hash] forwarded to `Primer::Alpha::ActionMenu`.
# # @return [ViewComponent::Slot]
# def with_menu(menu_id: nil, button_aria_label: nil, **system_arguments, &block)
# end
renders_one :menu, ->(menu_id: nil, button_aria_label: nil, **system_arguments) do
build_menu(menu_id:, button_aria_label:, **system_arguments)
end
}
end

private

renders_one :menu, ->(menu_id: nil, button_aria_label: nil, **system_arguments) do
def build_menu(menu_id: nil, button_aria_label: nil, **system_arguments)
system_arguments[:classes] = class_names(
system_arguments[:classes],
"hide-when-print"
)

menu = Primer::Alpha::ActionMenu.new(
menu_id: menu_id || dom_target(container, :menu),
menu_id: menu_id || default_menu_id,
anchor_align: :end,
**system_arguments
)
menu.with_show_button(
scheme: :invisible,
icon: :"kebab-horizontal",
"aria-label": button_aria_label || t(".label_actions"),
"aria-label": button_aria_label || I18n.t(:label_actions),
tooltip_direction: :se
)
menu
end

attr_reader :title, :container, :list_id, :collapsed, :count

def initialize(title:, container:, list_id:, collapsed: false, count: nil)
super()

@title = title
@container = container
@list_id = list_id
@collapsed = collapsed
@count = count
def default_menu_id
self.class.generate_id
end
end
end
Expand Down
Loading
Loading