Skip to content
2 changes: 1 addition & 1 deletion 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 "op_primer/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 @@ -12,7 +13,6 @@
@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,19 @@ 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 %>
<% items.each do |item| %>
<% border_box.with_row(**item.row_args) do %>
<%= render(item.card) %>
<% end %>
<% items.each do |item| %>
<% border_box.with_row(**item.row_args) do %>
<%= item %>
<% end %>
<% end %>

<% if footer? %>
<% border_box.with_row(scheme: :neutral) do %>
<% border_box.with_footer(**footer.footer_args) do %>
<%= footer %>
<% end %>
<% end %>
Expand Down
146 changes: 146 additions & 0 deletions app/components/op_primer/border_box_list_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# 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 OpPrimer
# Static BorderBox-backed list shell for callers that need ordered header,
# item, empty-item, and footer content without list behavior such as sorting
# or reordering.
class BorderBoxListComponent < ApplicationComponent
# @!parse
# # Adds a structured header rendered through the underlying BorderBox
# # header slot, with optional count, description, actions, and menu.
# #
# # @param title [String] header title.
# # @param count [Integer, nil] optional count badge.
# # @param list_id [String, nil] id of the collapsible BorderBox list body.
# # @param collapsed [Boolean] whether the collapsible header starts closed.
# # @param count_aria_label [String, nil] accessible label for the count badge.
# # @param system_arguments [Hash] forwarded to `Header`, whose `row_args`
# # are forwarded to `Primer::Beta::BorderBox#with_header`.
# def with_header(title:, **system_arguments, &block)
# end
renders_one :header, ->(**system_arguments) {
system_arguments[:list_id] ||= list_id

Header.new(**system_arguments)
}

# @!parse
# # Adds an arbitrary content row.
# #
# # @param component_klass [Class] class to use instead of the default
# # `Item`. The class must implement `row_args` for BorderBox row
# # arguments and render its own content.
# # @param system_arguments [Hash] forwarded to `component_klass` or
# # `Primer::Beta::BorderBox#with_row`.
# def with_item(component_klass: Item, **system_arguments, &block)
# end
#
# @!parse
# # Adds an empty-state content row with `data-empty-list-item="true"`.
# #
# # @param system_arguments [Hash] forwarded to `Primer::Beta::BorderBox#with_row`.
# def with_empty_item(**system_arguments, &block)
# end
renders_many :items, types: {
item: {
renders: ->(component_klass: Item, **system_arguments) {
component_klass.new(**system_arguments)
},
as: :item
},
empty_item: {
renders: ->(**system_arguments) {
EmptyItem.new(**system_arguments)
},
as: :empty_item
}
}

# @!parse
# # Gets the configured list items.
# #
# # This is intentionally public so wrapper components can delegate item
# # access while keeping underlying BorderBox rows as an internal detail.
# #
# # @return [Array<ViewComponent::Slot>]
# def items
# end

# @!parse
# # Adds a footer below the list body.
# #
# # When the parent BorderBox has a `list_id`, the footer receives a
# # stable default id derived from that list id unless an explicit id is
# # provided.
# #
# # @param system_arguments [Hash] forwarded to `Footer`, whose
# # `footer_args` are forwarded to `Primer::Beta::BorderBox#with_footer`.
# def with_footer(**system_arguments, &block)
# end
renders_one :footer, ->(**system_arguments) {
system_arguments[:id] ||= dom_target(list_id, :footer) if list_id

Footer.new(**system_arguments)
}

# @param system_arguments [Hash] forwarded to `Primer::Beta::BorderBox`.
def initialize(**system_arguments)
super()

@system_arguments = system_arguments
end

def before_render
content
apply_header_defaults!
end

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

private

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
34 changes: 34 additions & 0 deletions app/components/op_primer/border_box_list_component.sass
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//-- 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

.op-border-box-list-item
display: grid
grid-template-columns: minmax(0, 1fr) auto
align-items: start
gap: var(--stack-gap-normal)

&--content
min-width: 0

&--menu
margin-top: -6px
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,33 @@
#
# 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
module OpPrimer
class BorderBoxListComponent
class EmptyItem < ApplicationComponent
include Primer::AttributesHelper

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

@system_arguments = system_arguments
end

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

def empty_item? = true
def call
content
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,35 +23,29 @@
#
# 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
# Item bridge for caller-provided content.
class ContentItem < ApplicationComponent
def initialize(**system_arguments)
super()
module OpPrimer
class BorderBoxListComponent
class Footer < ApplicationComponent
attr_reader :id

@system_arguments = system_arguments
end
def initialize(**system_arguments)
super()

def row_args
@system_arguments.deep_dup
end

def card
self
end
@id = system_arguments[:id]
@system_arguments = system_arguments
end

def empty_item? = false
def footer_args
@system_arguments.deep_dup
end

def call
content
end
def call
content
end
end
end
Expand Down
Loading
Loading