Skip to content

Commit

Permalink
Start implementing mobile behavior for border-box table (#17216)
Browse files Browse the repository at this point in the history
* Start implementing mobile behavior for border-box table

* Auto column

* Allow labels

* Hide rows on mobile

* Change order of project and start time

* Add implementation for oidc

* Reimplement wide columns

* Documentation

* Remove custom mobile labels

* Extract heading class

* Fix spacing between rows in mobile BorderBoxTable

* Remove doubled code block

---------

Co-authored-by: Henriette Darge <[email protected]>
  • Loading branch information
oliverguenther and HDinger authored Nov 19, 2024
1 parent 032e5d3 commit 3490947
Show file tree
Hide file tree
Showing 15 changed files with 230 additions and 87 deletions.
10 changes: 8 additions & 2 deletions app/components/op_primer/border_box_row_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,17 @@ See COPYRIGHT and LICENSE files for more details.
classes: "#{table.grid_class} #{row_css_class}",
) do %>
<% columns.each do |column| %>
<%= render(Primer::BaseComponent.new(tag: :div, classes: column_css_class(column), **column_args(column))) { column_value(column) } %>
<%= render(Primer::BaseComponent.new(tag: :div, classes: grid_column_classes(column), **column_args(column))) do %>
<% label = mobile_label(column) %>
<% if label.present? %>
<%= render(Primer::Beta::Text.new(classes: "op-border-box-grid--row-label")) { "#{label}: " } %>
<% end %>
<%= column_value(column) %>
<% end %>
<% end %>

<% if table.has_actions? %>
<%= flex_layout(align_items: :center, justify_content: :flex_end) do |flex| %>
<%= flex_layout(classes: "op-border-box-grid--row-action") do |flex| %>
<% button_links.each_with_index do |link, i| %>
<% args = i == (button_links.count - 1) ? {} : { mr: 1 } %>
<% flex.with_column(**args) { link } %>
Expand Down
19 changes: 19 additions & 0 deletions app/components/op_primer/border_box_row_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,25 @@ module OpPrimer
class BorderBoxRowComponent < RowComponent # rubocop:disable OpenProject/AddPreviewForViewComponent
include ComponentHelpers

def mobile_label(column)
return unless table.mobile_labels.include?(column)

table.column_title(column)
end

def visible_on_mobile?(column)
table.mobile_columns.include?(column)
end

def grid_column_classes(column)
classes = ["op-border-box-grid--row-item"]
classes << column_css_class(column)
classes << "op-border-box-grid--wide-column" if table.wide_column?(column)
classes << "op-border-box-grid--no-mobile" unless visible_on_mobile?(column)

classes.compact.join(" ")
end

def column_args(_column)
{}
end
Expand Down
10 changes: 7 additions & 3 deletions app/components/op_primer/border_box_table_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,17 @@ See COPYRIGHT and LICENSE files for more details.
test_selector:,
)) do |component|
component.with_header(classes: grid_class, color: :muted) do
concat render(Primer::Beta::Text.new(classes: "op-border-box-grid--mobile-heading",
font_weight: :semibold)) { mobile_title }
headers.each do |name, args|
caption = args.delete(:caption)
concat render(Primer::Beta::Text.new(font_weight: :semibold, **header_args(name))) { caption }
concat render(Primer::Beta::Text.new(classes: header_classes(name),
font_weight: :semibold,
**header_args(name))) { args[:caption] }
end

if has_actions?
concat render(Primer::BaseComponent.new(tag: :div))
concat render(Primer::BaseComponent.new(classes: heading_class,
tag: :div))
end
end

Expand Down
65 changes: 65 additions & 0 deletions app/components/op_primer/border_box_table_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,71 @@ module OpPrimer
class BorderBoxTableComponent < TableComponent
include ComponentHelpers

class << self
# Declares columns to be shown in the mobile table
#
# Use it in subclasses like so:
#
# columns :name, :description
#
# mobile_columns :name
#
# This results in the description columns to be hidden on mobile
def mobile_columns(*names)
return @mobile_columns || columns if names.empty?

@mobile_columns = names.map(&:to_sym)
end

# Declares which columns to be rendered with a label
#
# mobile_labels :name
#
# This results in the description columns to be hidden on mobile
def mobile_labels(*names)
return @mobile_labels if names.empty?

@mobile_labels = names.map(&:to_sym)
end

# Declare wide columns, that will result in a grid column span of 3
#
# column_grid_span :title
#
def wide_columns(*names)
return Array(@wide_columns) if names.empty?

@wide_columns = names.map(&:to_sym)
end
end

delegate :mobile_columns, :mobile_labels,
to: :class

def wide_column?(column)
self.class.wide_columns.include?(column)
end

def header_args(_column)
{}
end

def column_title(name)
header = headers.find { |h| h[0] == name }
header ? header[1][:caption] : nil
end

def header_classes(column)
classes = [heading_class]
classes << "op-border-box-grid--wide-column" if wide_column?(column)

classes.join(" ")
end

def heading_class
"op-border-box-grid--heading"
end

# Default grid class with equal weights
def grid_class
"op-border-box-grid"
Expand All @@ -57,6 +118,10 @@ def render_blank_slate
end
end

def mobile_title
raise ArgumentError, "Need to provide a mobile table title"
end

def blank_title
I18n.t(:label_nothing_display)
end
Expand Down
44 changes: 39 additions & 5 deletions app/components/op_primer/border_box_table_component.sass
Original file line number Diff line number Diff line change
@@ -1,7 +1,41 @@
@import "helpers"

.op-border-box-grid
display: grid
justify-content: flex-start
align-items: center
// Distribute columns evenly by default
grid-auto-columns: minmax(0, 1fr)
grid-auto-flow: column

&--row-action
align-items: center
justify-content: flex-end

@media screen and (min-width: $breakpoint-md)
.op-border-box-grid
// Distribute columns evenly on desktop
grid-auto-columns: minmax(0, 1fr)
grid-auto-flow: column
justify-content: flex-start
align-items: center

&--mobile-heading,
&--row-label
display: none

&--wide-column
grid-column: span 3

@media screen and (max-width: $breakpoint-md)
.op-border-box-grid
grid-template-columns: 1fr auto
grid-auto-flow: row

&--heading,
&--no-mobile
display: none

&--row-item
grid-column: 1
// Match the height of the action button on the right. Otherwise the spacing between the lines will be diverging
line-height: var(--control-medium-size)

&--row-action
grid-column: 2
grid-row: 1
43 changes: 43 additions & 0 deletions lookbook/docs/patterns/12-tables.md.erb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,49 @@ end

<%= embed OpPrimer::BorderBoxTableComponentPreview, :default, panels: %i[source] %>



### Border Box Table specific options

#### 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.

As the columns are collapsed, some information might need an additional label, which you can prepend with `mobile_labels`.

```ruby
# On desktop, show these columns
columns :title, :users, :created_at

# On mobile, only two columns are visible
mobile_columns :title, :users

# For users, show the label using the header caption
mobile_labels :users
```



#### Mobile headers

On mobile, the usual table headers are replaced with a single `mobile_title` property that you have to set on the table.

#### Wide columns

To use wider columns on desktop, use the `wide_columns` helper:

```ruby
# On desktop, show these columns
columns :title, :users, :created_at

# Make title column wider than the others (factor 3 using grid span)
wide_columns :title
```





## Best practices

**Do**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
<%=
table = Class.new(OpPrimer::BorderBoxTableComponent) do
columns :foo, :bar

wide_columns :foo

def self.name
"MyTable"
end

def header_args(column)
if column == :foo
{ style: "grid-column: span 3" }
else
super
end
def mobile_title
"Mobile foo"
end

def row_class
Expand All @@ -19,14 +18,6 @@
"MyRow"
end

def column_args(column)
if column == :foo
{ style: "grid-column: span 3" }
else
super
end
end

def foo
"foo"
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
<%=
table = Class.new(OpPrimer::BorderBoxTableComponent) do
columns :foo, :bar

mobile_labels :bar

def self.name
"MyTable"
end

def mobile_title
"Mobile foo"
end

## This method is just a hack used for the preview
## Create your components under your namespace like so instead
## MyNamespace::TableComponent
Expand Down Expand Up @@ -33,7 +40,7 @@

def foo
concat render(Primer::Beta::Link.new(scheme: :primary, href: "#")) { "Some link" }
concat render(Primer::Beta::Text.new(tag: :p, font_size: :small, color: :subtle)) { "Hello there" }
render(Primer::Beta::Text.new(tag: :p, font_size: :small, color: :subtle)) { "Hello there" }
end

def bar
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
<%=
table = Class.new(OpPrimer::BorderBoxTableComponent) do
columns :foo, :bar

def self.name
"MyTable"
end

def mobile_title
"Mobile foo"
end

def row_class
@clazz ||= Class.new(OpPrimer::BorderBoxRowComponent) do
def self.name
Expand Down Expand Up @@ -33,7 +38,8 @@

def foo
concat render(Primer::Beta::Link.new(scheme: :primary, href: "#")) { "Some link" }
concat render(Primer::Beta::Text.new(tag: :p, font_size: :small, color: :subtle)) { "Hello there" }

render(Primer::Beta::Text.new(tag: :p, font_size: :small, color: :subtle)) { "Hello there" }
end

def bar
Expand Down
22 changes: 1 addition & 21 deletions modules/auth_saml/app/components/saml/providers/row_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,18 @@ def provider
model
end

def column_args(column)
if column == :name
{ style: "grid-column: span 3" }
else
super
end
end

def name
concat render(Primer::Beta::Link.new(
font_weight: :bold,
href: url_for(action: :show, id: provider.id)
)) { provider.display_name || provider.name }

render_availability_label
render_idp_sso_service_url
end

def render_availability_label
unless provider.available?
concat render(Primer::Beta::Label.new(ml: 2, scheme: :attention, size: :medium)) { t(:label_incomplete) }
end
end

def render_idp_sso_service_url
if provider.idp_sso_service_url
concat render(Primer::Beta::Text.new(
tag: :p,
classes: "-break-word",
font_size: :small,
color: :subtle
)) { provider.idp_sso_service_url }
render(Primer::Beta::Label.new(ml: 2, scheme: :attention, size: :medium)) { t(:label_incomplete) }
end
end

Expand Down
Loading

0 comments on commit 3490947

Please sign in to comment.