mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
Merge pull request #18508 from opf/feature/62577-borderBoxCollapsible-lookbook-docs
LookBook docs for Border Box
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
The Border Box element is used to display structured items in a list, each with a set of actions.
|
||||
|
||||
<%= embed OpPrimer::BorderBoxComponentPreview, :default %>
|
||||
|
||||
## Anatomy
|
||||
|
||||
The Border Box element is made up of
|
||||
|
||||
1. The **header**, which acts as a container for the items below and helps distinguish between sections. It is optional.
|
||||
2. Individual **items** below can vary in what information they show.
|
||||
|
||||
If a page uses a Border Box with a header, all sections must use headers.
|
||||
|
||||
## Header
|
||||
|
||||
The header can either be static or collapsible.
|
||||
|
||||
The CollapsibleHeader brings additional functionality:
|
||||
|
||||
- an **arrow indicator** next to the title indicates if the section is collapsed or expanded.
|
||||
- a **header description** (optional) to give additional context to the content of that section. Hidden when collapsed.
|
||||
- a **counter** (optional) displays the number of items that that particular section contains.
|
||||
|
||||
When collapsed, the bottom border is slightly thicker to indicate something is hidden.
|
||||
|
||||
Both the collapsible and static headers can accept additional actions. For example, a **more button** can offer additional contextual actions such as edit, delete and move (move up, move to top, move down, move to the end).
|
||||
|
||||
<%= embed OpPrimer::BorderBoxComponentPreview, :collapsible %>
|
||||
|
||||
## Uses
|
||||
|
||||
Some places in which we already use the Border Box:
|
||||
|
||||
- to display individual meeting agenda items in a meeting
|
||||
- to display the a list of project attributes in Project settings
|
||||
- to display a list of configured OAuth applications
|
||||
|
||||
If the content needs to be more structured (with columns and column headers), please use the [Border Box Table](./tables/border_box_table) component instead.
|
||||
|
||||
## Best practices
|
||||
|
||||
**Do**
|
||||
|
||||
- Use sections (with headers) to better organise content if they have logical categories (eg. Open and Planned meetings).
|
||||
- Do make the header and items draggable if the user should be able to drag individual items up and down and into other sections. The sections themselves can also be dragged up and down to change order.
|
||||
|
||||
**Don't**
|
||||
|
||||
- Don't mix sections with and without headers. If one section has a header, they must all have headers.
|
||||
- Don't make sections collapsible if they do not need to be.
|
||||
|
||||
## Technical notes
|
||||
|
||||
<%= embed OpPrimer::BorderBoxComponentPreview, :collapsible, panels: %i[source] %>
|
||||
|
||||
For detailed examples have a look at the other [border box preview examples](/lookbook/inspect/primer/beta/border_box/playground) or the [collapsible header preview examples](/lookbook/inspect/primer/open_project/border_box/collapsible_header/playground).
|
||||
+30
-40
@@ -1,41 +1,41 @@
|
||||
## Overview
|
||||
|
||||
### Border Box Table
|
||||
|
||||
<%= embed OpPrimer::BorderBoxTableComponentPreview, :default %>
|
||||
|
||||
### Sortable table
|
||||
## Anatomy
|
||||
|
||||
<%= embed OpenProject::Deprecated::TablePreview, :default %>
|
||||
**Note:** The BorderBoxTable is based on the `Primer::Beta::BorderBox` with a CSS grid applied inside to align the individual rows and make it look like a table. Please note, that it is technically not a table and has therefore none of the benefits a normal HTML table would have (e.g automatic column width adjustments or accessibility short cuts).
|
||||
|
||||
## Usage
|
||||
If you want or need that, please use the [TableComponent docs](./tables)
|
||||
|
||||
To use either table implementation, you need to subclass into your own namespace:
|
||||
## Best practices
|
||||
|
||||
- **Regular table:** `TableComponent` and `RowComponent`
|
||||
- **BorderBox table:** `OpPrimer::BorderBoxTableComponent` and `OpPrimer::BorderBoxRowComponent`
|
||||
**Do**
|
||||
|
||||
**Columns:** Define the columns of the table as class method `columns :a, :b, :c`
|
||||
- Use a BorderBoxTable if you have a *few* entries of unsorted data that does not require pagination
|
||||
- Use up to two actions using the `:secondary` scheme of `Primer::Beta::Button` and `Primer::Beta::IconButton`.
|
||||
- OR: Use an action menu with `:invisible` scheme
|
||||
|
||||
**Headers:** Define the headers of the component like so
|
||||
**Don't**
|
||||
|
||||
```ruby
|
||||
def headers
|
||||
[
|
||||
[:a, { caption: 'Column A' }],
|
||||
[:b, { caption: 'Column B' }],
|
||||
[:c, { caption: 'Column C' }]
|
||||
]
|
||||
end
|
||||
```
|
||||
- Don't use the BorderBoxTable when the table consists of a large number of items that require sorting or pagination
|
||||
|
||||
**Actions:** Define the actions in the row component as `button_links`. See the example code
|
||||
## Uses
|
||||
|
||||
<%= embed OpPrimer::BorderBoxTableComponentPreview, :default, panels: %i[source] %>
|
||||
Some examples of where we already use the Border Box table:
|
||||
|
||||
### Border Box Table specifics
|
||||
- to display a list of meetings
|
||||
- to display a list of configured SAML providers
|
||||
- to display a list of configured OpenID providers
|
||||
- to display a list of configured SCIM clients
|
||||
|
||||
#### Mobile behavior
|
||||
If the content does not need a table structure with column headers and grids, please use the simpler [Border Box](../border_box) component instead.
|
||||
|
||||
## Technical notes
|
||||
|
||||
Since the `BorderBoxTable` is a sub-class of our normal `TableComponent` it follows the same architecture. For the basic usage please visit the [TableComponent docs](./tables). The following docs are only about the additional specifics of the `BorderBoxTable`.
|
||||
|
||||
### Mobile behavior
|
||||
|
||||
On mobile, the BorderBoxTable does not scroll, but collapse into two columns (content columns and actions). You can control which columns are shown using the `mobile_columns` method.
|
||||
|
||||
@@ -52,15 +52,15 @@ mobile_columns :title, :users
|
||||
mobile_labels :users
|
||||
```
|
||||
|
||||
#### Mobile headers
|
||||
### Mobile headers
|
||||
|
||||
On mobile, the usual table headers are replaced with a single `mobile_title` property that you have to set on the table.
|
||||
|
||||
#### Text wrapping behaviour
|
||||
### Text wrapping behaviour
|
||||
|
||||
By default, text longer than the column width will truncate with ellipsis. Only the main column has text that wraps around to display the full string.
|
||||
|
||||
#### Main column
|
||||
### Main column
|
||||
|
||||
Use the `main_column` helper to make a column 2 times as wide as the others and also display the full text:
|
||||
|
||||
@@ -71,9 +71,9 @@ columns :title, :users, :created_at
|
||||
# Make title column wider than the others (factor 2 using grid span)
|
||||
main_column :title
|
||||
```
|
||||
Note: Ideally, one one main column will be present for each table.
|
||||
Note: Ideally, only one main column will be present for each table.
|
||||
|
||||
#### Footer
|
||||
### Footer
|
||||
|
||||
Set `has_footer?` to true to add a footer to the table, defining the component/content to be rendered with the `footer` method.
|
||||
|
||||
@@ -87,18 +87,8 @@ def footer
|
||||
end
|
||||
```
|
||||
|
||||
## Best practices
|
||||
### Examples
|
||||
|
||||
**Do**
|
||||
|
||||
- Use a BorderBox table if you have a *few* entries of unsorted data, that does not require pagination
|
||||
- Use up to two actions using the `:secondary` scheme of `Primer::Beta::Button` and `Primer::Beta::IconButton`.
|
||||
- OR: Use an action menu with `:invisible` scheme
|
||||
|
||||
**Don't**
|
||||
|
||||
- Use the BorderBox table for a lot of items that require sorting or pagination
|
||||
|
||||
## Examples
|
||||
<%= embed OpPrimer::BorderBoxTableComponentPreview, :default, panels: %i[source] %>
|
||||
|
||||
For detailed examples have a look at the other [preview examples](/lookbook/inspect/OpenProject/Primer/border_box_table/default) of the component.
|
||||
@@ -0,0 +1,79 @@
|
||||
## Overview
|
||||
|
||||
We currently have two components for implementing a table view:
|
||||
|
||||
* a (sortable) `TableComponent`
|
||||
* a (unsortable, primer based) `BorderBoxTableComponent`
|
||||
|
||||
**Sortable table**
|
||||
|
||||
<%= embed OpenProject::Deprecated::TablePreview, :default %>
|
||||
|
||||
**Border Box Table**
|
||||
|
||||
<%= embed OpPrimer::BorderBoxTableComponentPreview, :default %>
|
||||
|
||||
For the specifics of that component please have a look at the [BorderBoxTableComponent docs](./border_box_table).
|
||||
|
||||
## Anatomy
|
||||
|
||||
**Sortable table**
|
||||
|
||||
* The Sortable Table results in a standard HTML table with some classes applied for styling.
|
||||
* If `sortable` is set to true, all sortable column headers are clickable to change the sort order. When a sort is applied, that is indicated via an arrow.
|
||||
|
||||
**Border Box Table**
|
||||
|
||||
* The BorderBoxTable is based on the `Primer::Beta::BorderBox` with a CSS grid applied inside to align the individual rows and make it look like a table.
|
||||
|
||||
## Technical notes
|
||||
|
||||
The `BorderBoxTableComponent` is a subclass of the `TableComponent`, so the following notes apply to both components. There are however some additional things to consider, for that please visit the [BorderBoxTableComponent docs](./border_box_table).
|
||||
|
||||
To use either table implementation, you need to subclass the following classes onto your own namespace:
|
||||
|
||||
- **Regular table:** `TableComponent` and `RowComponent`
|
||||
- **BorderBox table:** `OpPrimer::BorderBoxTableComponent` and `OpPrimer::BorderBoxRowComponent`
|
||||
|
||||
**Columns**
|
||||
|
||||
Define the columns of the table as class method:
|
||||
|
||||
```ruby
|
||||
module ModuleA
|
||||
class TableComponent < ::TableComponent
|
||||
columns :name, :description, :sort
|
||||
# ...
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
**Headers**
|
||||
|
||||
Define the headers of the component as a method:
|
||||
|
||||
```ruby
|
||||
module ModuleA
|
||||
class TableComponent < ::TableComponent
|
||||
# ...
|
||||
|
||||
def headers
|
||||
[
|
||||
[:a, { caption: 'Column A' }],
|
||||
[:b, { caption: 'Column B' }],
|
||||
[:c, { caption: 'Column C' }]
|
||||
]
|
||||
end
|
||||
|
||||
# ...
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
**Actions**
|
||||
|
||||
Define the actions in the row component as `button_links`. See the example code:
|
||||
|
||||
### Full example
|
||||
|
||||
<%= embed OpenProject::Deprecated::TablePreview, :default, panels: %i[source] %>
|
||||
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module OpPrimer
|
||||
# @hidden
|
||||
# @display min_height 400px
|
||||
class BorderBoxComponentPreview < Lookbook::Preview
|
||||
def default
|
||||
render(Primer::Beta::BorderBox.new) do |component|
|
||||
component.with_header { "Header" }
|
||||
component.with_body { "Body" }
|
||||
component.with_row { "Row one" }
|
||||
component.with_row { "Row two" }
|
||||
component.with_row { "Row three" }
|
||||
component.with_footer { "Footer" }
|
||||
end
|
||||
end
|
||||
|
||||
def collapsible
|
||||
render_with_template
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,49 @@
|
||||
<%= render(Primer::Beta::BorderBox.new) do |component| %>
|
||||
<% component.with_header do %>
|
||||
<%= render(
|
||||
Primer::OpenProject::BorderBox::CollapsibleHeader.new(
|
||||
box: component,
|
||||
title: "Collapsible header",
|
||||
count: 3
|
||||
)
|
||||
) %>
|
||||
<% end %>
|
||||
<% component.with_body { "Body" } %>
|
||||
<% component.with_row do %>
|
||||
<%= render(Primer::OpenProject::FlexLayout.new(justify_content: :space_between, align_items: :center)) do |flex| %>
|
||||
<%= flex.with_column { "Row 1" } %>
|
||||
<%= flex.with_column do %>
|
||||
<%= render(Primer::Alpha::ActionMenu.new) do |menu|
|
||||
menu.with_show_button(icon: "kebab-horizontal", "aria-label": "More", scheme: :invisible)
|
||||
menu.with_item(
|
||||
tag: :a,
|
||||
label: "Edit",
|
||||
href: "#"
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: :pencil)
|
||||
"foo"
|
||||
end
|
||||
end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% component.with_row do %>
|
||||
<%= render(Primer::OpenProject::FlexLayout.new(justify_content: :space_between, align_items: :center)) do |flex| %>
|
||||
<%= flex.with_column { "Row 2" } %>
|
||||
<%= flex.with_column do %>
|
||||
<%= render(Primer::Alpha::ActionMenu.new) do |menu|
|
||||
menu.with_show_button(icon: "kebab-horizontal", "aria-label": "More", scheme: :invisible)
|
||||
menu.with_item(
|
||||
tag: :a,
|
||||
label: "Edit",
|
||||
href: "#"
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: :pencil)
|
||||
"foo"
|
||||
end
|
||||
end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% component.with_footer { "Footer" } %>
|
||||
<% end %>
|
||||
@@ -4,19 +4,24 @@ module OpPrimer
|
||||
# @logical_path OpenProject/Primer
|
||||
# @display min_height 300px
|
||||
class BorderBoxTableComponentPreview < Lookbook::Preview
|
||||
# See the [component documentation](/lookbook/pages/components/tables) for more details.
|
||||
# See the [component documentation](/lookbook/pages/components/tables/border_box_table) for more details.
|
||||
def default
|
||||
render_with_template
|
||||
end
|
||||
|
||||
# See the [component documentation](/lookbook/pages/components/tables) for more details.
|
||||
# See the [component documentation](/lookbook/pages/components/tables/border_box_table) for more details.
|
||||
def custom_column_widths
|
||||
render_with_template
|
||||
end
|
||||
|
||||
# See the [component documentation](/lookbook/pages/components/tables) for more details.
|
||||
# See the [component documentation](/lookbook/pages/components/tables/border_box_table) for more details.
|
||||
def with_action_menu
|
||||
render_with_template
|
||||
end
|
||||
|
||||
# See the [component documentation](/lookbook/pages/components/tables/border_box_table) for more details.
|
||||
def with_footer
|
||||
render_with_template
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<%=
|
||||
table = Class.new(OpPrimer::BorderBoxTableComponent) do
|
||||
columns :foo, :bar
|
||||
|
||||
mobile_labels :bar
|
||||
|
||||
def self.name
|
||||
"MyTable"
|
||||
end
|
||||
|
||||
def mobile_title
|
||||
"Mobile foo"
|
||||
end
|
||||
|
||||
def row_class
|
||||
@row_class ||= Class.new(OpPrimer::BorderBoxRowComponent) do
|
||||
def self.name
|
||||
"MyRow"
|
||||
end
|
||||
|
||||
def button_links
|
||||
[
|
||||
edit_button,
|
||||
delete_button
|
||||
]
|
||||
end
|
||||
|
||||
def edit_button
|
||||
render(Primer::Beta::Button.new(scheme: :secondary, tag: :a, href: "#")) { "Edit" }
|
||||
end
|
||||
|
||||
def delete_button
|
||||
render(Primer::Beta::IconButton.new(icon: :trash, scheme: :danger, tag: :a, href: "#", "aria-label": "Delete"))
|
||||
end
|
||||
|
||||
def foo
|
||||
"foo"
|
||||
end
|
||||
|
||||
def bar
|
||||
"bar"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def has_footer?
|
||||
true
|
||||
end
|
||||
|
||||
def footer
|
||||
render(Primer::BaseComponent.new(tag: :span)) do
|
||||
concat render(Primer::Beta::Text.new) { "This can be a custom footer component " }
|
||||
concat render(Primer::Beta::Link.new(href: "#")) { "with all sorts of things" }
|
||||
end
|
||||
end
|
||||
|
||||
def headers
|
||||
[
|
||||
[:foo, { caption: "Foo column" }],
|
||||
[:bar, { caption: "Bar column" }]
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
render(table.new(rows: [1, 2]))
|
||||
%>
|
||||
@@ -1,100 +1,58 @@
|
||||
<div class="generic-table--container">
|
||||
<div class="generic-table--results-container" style="max-height: 340px;">
|
||||
<table class="generic-table" data-controller="table-highlighting">
|
||||
<colgroup>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<div class="generic-table--sort-header-outer">
|
||||
<div class="generic-table--sort-header">
|
||||
<span class="sort asc">
|
||||
First
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
<th>
|
||||
<div class="generic-table--sort-header-outer">
|
||||
<div class="generic-table--sort-header">
|
||||
<span class="sort asc">
|
||||
Second
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
<th>
|
||||
<div class="generic-table--sort-header-outer">
|
||||
<div class="generic-table--sort-header">
|
||||
<span class="sort desc">
|
||||
Third
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
<th>
|
||||
<div class="generic-table--sort-header-outer">
|
||||
<div class="generic-table--sort-header">
|
||||
<span class="sort desc">
|
||||
Fourth
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
<th>
|
||||
<div class="generic-table--sort-header-outer">
|
||||
<div class="generic-table--sort-header">
|
||||
<span class="sort desc">
|
||||
Fifth
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Lorem ipsum dolor sit amet, consectetur adipiscing elit</td>
|
||||
<td>Nullam a sem et metus congue placerat.</td>
|
||||
<td>Mauris ut augue viverra, consequat eros eu, maximus quam.</td>
|
||||
<td>Maecenas elementum orci a varius suscipit.</td>
|
||||
<td>Nunc molestie neque sit amet eros semper dapibus.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nullam a sem et metus congue placerat.</td>
|
||||
<td>Lorem ipsum dolor sit amet, consectetur adipiscing elit</td>
|
||||
<td>Mauris ut augue viverra, consequat eros eu, maximus quam.</td>
|
||||
<td>Maecenas elementum orci a varius suscipit.</td>
|
||||
<td>Nunc molestie neque sit amet eros semper dapibus.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nullam a sem et metus congue placerat.</td>
|
||||
<td>Mauris ut augue viverra, consequat eros eu, maximus quam.</td>
|
||||
<td>Lorem ipsum dolor sit amet, consectetur adipiscing elit</td>
|
||||
<td>Maecenas elementum orci a varius suscipit.</td>
|
||||
<td>Nunc molestie neque sit amet eros semper dapibus.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nullam a sem et metus congue placerat.</td>
|
||||
<td>Mauris ut augue viverra, consequat eros eu, maximus quam.</td>
|
||||
<td>Maecenas elementum orci a varius suscipit.</td>
|
||||
<td>Lorem ipsum dolor sit amet, consectetur adipiscing elit</td>
|
||||
<td>Nunc molestie neque sit amet eros semper dapibus.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nullam a sem et metus congue placerat.</td>
|
||||
<td>Mauris ut augue viverra, consequat eros eu, maximus quam.</td>
|
||||
<td>Maecenas elementum orci a varius suscipit.</td>
|
||||
<td>Lorem ipsum dolor sit amet, consectetur adipiscing elit</td>
|
||||
<td>Nunc molestie neque sit amet eros semper dapibus.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<%=
|
||||
table = Class.new(TableComponent) do
|
||||
columns :foo, :bar
|
||||
|
||||
</div>
|
||||
</div>
|
||||
def self.name
|
||||
"MyTable"
|
||||
end
|
||||
|
||||
## This method is just a hack used for the preview
|
||||
## Create your components under your namespace like so instead
|
||||
## MyNamespace::TableComponent
|
||||
## MyNamespace::RowComponent
|
||||
## and the other class will be autoloaded
|
||||
def row_class
|
||||
@row_class ||= Class.new(RowComponent) do
|
||||
def self.name
|
||||
"Row"
|
||||
end
|
||||
|
||||
def button_links
|
||||
[
|
||||
delete_button
|
||||
]
|
||||
end
|
||||
|
||||
def delete_button
|
||||
render(Primer::Beta::IconButton.new(icon: :trash, scheme: :danger, tag: :a, href: "#", "aria-label": "Delete"))
|
||||
end
|
||||
|
||||
def foo
|
||||
render(Primer::Beta::Text.new(tag: :p)) { "Lorem ipsum dolor sit amet, consetetur sadipscing elitr" }
|
||||
end
|
||||
|
||||
def bar
|
||||
"At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def has_actions?
|
||||
true
|
||||
end
|
||||
|
||||
def headers
|
||||
[
|
||||
[:foo, { caption: "Foo column" }],
|
||||
[:bar, { caption: "Bar column" }]
|
||||
]
|
||||
end
|
||||
|
||||
def sortable?
|
||||
# True per default but for this preview disabled
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
render(table.new(rows: [1, 2]))
|
||||
%>
|
||||
|
||||
Reference in New Issue
Block a user