Skip to content

Commit a347183

Browse files
committed
Adds DataTable actions slot and divider
Ports Primer React's Table.Actions and Table.Divider. Typed action slots (button, icon_button, menu) render at the end of the title row via a new 'actions' grid area; an optional divider kwarg draws a presentational rule below the title row. Generalizes the table spacing selector since header elements may now sit between the subtitle and the overflow wrapper.
1 parent 322d101 commit a347183

9 files changed

Lines changed: 159 additions & 6 deletions

File tree

app/components/primer/open_project/data_table.html.erb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
<%= render(Primer::Box.new(**@container_arguments)) do %>
22
<%= title %>
3+
<% if actions.any? %>
4+
<div class="TableActions">
5+
<% actions.each do |action| %>
6+
<%= action %>
7+
<% end %>
8+
</div>
9+
<% end %>
10+
<% if @divider %>
11+
<div class="TableDivider" role="presentation"></div>
12+
<% end %>
313
<%= subtitle %>
414
<%= render(Primer::BaseComponent.new(**@wrapper_arguments)) do %>
515
<data-table>

app/components/primer/open_project/data_table.pcss

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@
2121
/* Container ---------------------------------------------------------------- */
2222
.TableContainer {
2323
display: grid;
24+
grid-template-columns: 1fr auto;
2425
grid-template-areas:
25-
'title'
26-
'subtitle'
27-
'table'
28-
'footer';
26+
'title actions'
27+
'divider divider'
28+
'subtitle subtitle'
29+
'table table'
30+
'footer footer';
2931
}
3032

3133
/* TableTitle */
@@ -49,9 +51,25 @@
4951
grid-area: subtitle;
5052
}
5153

54+
/* TableActions */
55+
.TableActions {
56+
display: flex;
57+
align-self: center;
58+
justify-self: end;
59+
column-gap: var(--base-size-8);
60+
grid-area: actions;
61+
}
62+
63+
/* TableDivider */
64+
.TableDivider {
65+
height: var(--borderWidth-thin);
66+
margin-block: var(--base-size-16) var(--base-size-8);
67+
background-color: var(--borderColor-default);
68+
grid-area: divider;
69+
}
70+
5271
/* Spacing before the table */
53-
.TableTitle + .TableOverflowWrapper,
54-
.TableSubtitle + .TableOverflowWrapper {
72+
.TableContainer > .TableOverflowWrapper:not(:first-child) {
5573
margin-block-start: var(--base-size-8);
5674
}
5775

app/components/primer/open_project/data_table.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,20 @@ def sort_data
6969
column
7070
}
7171

72+
# Actions rendered at the end of the table's title row, ported from
73+
# Primer React's `Table.Actions`.
74+
#
75+
# Use:
76+
#
77+
# - `button` for a labelled <%= link_to_component(Primer::Beta::Button) %>.
78+
# - `icon_button` for an icon-only <%= link_to_component(Primer::Beta::IconButton) %>.
79+
# - `menu` for a <%= link_to_component(Primer::Alpha::ActionMenu) %>.
80+
renders_many :actions, types: {
81+
button: Primer::Beta::Button,
82+
icon_button: Primer::Beta::IconButton,
83+
menu: Primer::Alpha::ActionMenu
84+
}
85+
7286
renders_one :pagination, Primer::OpenProject::DataTable::PaginationFooter
7387

7488
# @param data [Array, ActiveRecord::Relation]
@@ -81,6 +95,9 @@ def sort_data
8195
# @param initial_sort_direction [Symbol, nil]
8296
# Sort direction for the initially sorted column.
8397
# Options: :ASC, :DESC
98+
# @param divider [Boolean]
99+
# Whether to render a presentational divider line below the title row,
100+
# ported from Primer React's `Table.Divider`
84101
# @param html_data [Hash]
85102
# HTML data attributes to be passed to the table
86103
# @param system_arguments [Hash]
@@ -90,13 +107,15 @@ def initialize(
90107
cell_padding: CELL_PADDING_DEFAULT,
91108
initial_sort_column: nil,
92109
initial_sort_direction: nil,
110+
divider: false,
93111
html_data: {},
94112
**system_arguments
95113
)
96114
@rows = data
97115
@cell_padding = fetch_or_fallback(CELL_PADDING_OPTIONS, cell_padding, CELL_PADDING_DEFAULT)
98116
@initial_sort_column = initial_sort_column
99117
@initial_sort_direction = initial_sort_direction
118+
@divider = fetch_or_fallback_boolean(divider, false)
100119
@id = system_arguments[:id] ||= self.class.generate_id(base_name: "data-table")
101120

102121
@container_arguments = {}

previews/primer/open_project/data_table_preview.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ def playground(
5151
)
5252
end
5353

54+
# @label With Actions
55+
# @snapshot
56+
def with_actions
57+
render_with_template(locals: { projects: sample_rows.first(3) })
58+
end
59+
5460
# @label With Cell Placeholder
5561
# @snapshot
5662
def with_cell_placeholder
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<%=
2+
render(
3+
Primer::OpenProject::DataTable.new(projects, divider: true)
4+
) do |data_table|
5+
data_table.with_title(tag: :h2) { "Projects" }
6+
data_table.with_subtitle { "Actions render at the end of the title row." }
7+
8+
data_table.with_action_icon_button(icon: :download, "aria-label": "Download")
9+
data_table.with_action_button(leading_visual_icon: :plus) { "New project" }
10+
data_table.with_action_menu(anchor_align: :end) do |menu|
11+
menu.with_show_button(icon: :"kebab-horizontal", "aria-label": "More actions")
12+
menu.with_item(label: "Import projects")
13+
menu.with_item(label: "Manage columns")
14+
end
15+
16+
data_table.with_column(field: :name, header: "Name", row_header: true)
17+
data_table.with_column(field: :status_code, header: "Status")
18+
end
19+
%>

static/arguments.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6088,6 +6088,12 @@
60886088
"default": "`nil`",
60896089
"description": "Sort direction for the initially sorted column. Options: :ASC, :DESC"
60906090
},
6091+
{
6092+
"name": "divider",
6093+
"type": "Boolean",
6094+
"default": "`false`",
6095+
"description": "Whether to render a presentational divider line below the title row, ported from Primer React's `Table.Divider`"
6096+
},
60916097
{
60926098
"name": "html_data",
60936099
"type": "Hash",

static/info_arch.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20005,6 +20005,12 @@
2000520005
"default": "`nil`",
2000620006
"description": "Sort direction for the initially sorted column. Options: :ASC, :DESC"
2000720007
},
20008+
{
20009+
"name": "divider",
20010+
"type": "Boolean",
20011+
"default": "`false`",
20012+
"description": "Whether to render a presentational divider line below the title row, ported from Primer React's `Table.Divider`"
20013+
},
2000820014
{
2000920015
"name": "html_data",
2001020016
"type": "Hash",
@@ -20034,6 +20040,11 @@
2003420040
"description": null,
2003520041
"parameters": []
2003620042
},
20043+
{
20044+
"name": "actions",
20045+
"description": "Actions rendered at the end of the table's title row, ported from\nPrimer React's `Table.Actions`.\n\nUse:\n\n- `button` for a labelled {{#link_to_component}}Primer::Beta::Button{{/link_to_component}}.\n- `icon_button` for an icon-only {{#link_to_component}}Primer::Beta::IconButton{{/link_to_component}}.\n- `menu` for a {{#link_to_component}}Primer::Alpha::ActionMenu{{/link_to_component}}.",
20046+
"parameters": []
20047+
},
2003720048
{
2003820049
"name": "pagination",
2003920050
"description": null,
@@ -20094,6 +20105,19 @@
2009420105
]
2009520106
}
2009620107
},
20108+
{
20109+
"preview_path": "primer/open_project/data_table/with_actions",
20110+
"name": "with_actions",
20111+
"snapshot": "true",
20112+
"skip_rules": {
20113+
"wont_fix": [
20114+
"region"
20115+
],
20116+
"will_fix": [
20117+
"color-contrast"
20118+
]
20119+
}
20120+
},
2009720121
{
2009820122
"preview_path": "primer/open_project/data_table/with_cell_placeholder",
2009920123
"name": "with_cell_placeholder",

static/previews.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3666,6 +3666,19 @@
36663666
]
36673667
}
36683668
},
3669+
{
3670+
"preview_path": "primer/open_project/data_table/with_actions",
3671+
"name": "with_actions",
3672+
"snapshot": "true",
3673+
"skip_rules": {
3674+
"wont_fix": [
3675+
"region"
3676+
],
3677+
"will_fix": [
3678+
"color-contrast"
3679+
]
3680+
}
3681+
},
36693682
{
36703683
"preview_path": "primer/open_project/data_table/with_cell_placeholder",
36713684
"name": "with_cell_placeholder",

test/components/primer/open_project/data_table/data_table_test.rb

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,44 @@ def test_does_not_mutate_caller_html_data
422422
assert_equal({ test_selector: "my-table" }, html_data)
423423
end
424424

425+
def test_renders_actions_in_title_area
426+
render_component(@data) do |data_table|
427+
data_table.with_column(field: :subject, header: "Subject")
428+
data_table.with_title { "Projects" }
429+
data_table.with_action_button { "Export" }
430+
data_table.with_action_icon_button(icon: :pencil, "aria-label": "Edit")
431+
end
432+
433+
assert_selector(".TableContainer .TableActions button", text: "Export")
434+
assert_selector(".TableActions button .octicon-pencil")
435+
assert_selector(".TableActions tool-tip", text: "Edit", visible: :all)
436+
end
437+
438+
def test_renders_no_actions_container_without_actions
439+
render_component(@data) do |data_table|
440+
data_table.with_column(field: :subject, header: "Subject")
441+
end
442+
443+
assert_no_selector(".TableActions")
444+
end
445+
446+
def test_renders_divider_when_enabled
447+
render_component(@data, divider: true) do |data_table|
448+
data_table.with_column(field: :subject, header: "Subject")
449+
data_table.with_title { "Projects" }
450+
end
451+
452+
assert_selector(".TableContainer .TableDivider[role='presentation']")
453+
end
454+
455+
def test_renders_no_divider_by_default
456+
render_component(@data) do |data_table|
457+
data_table.with_column(field: :subject, header: "Subject")
458+
end
459+
460+
assert_no_selector(".TableDivider")
461+
end
462+
425463
def test_renders_placeholder_for_blank_cell_values
426464
row_klass = Data.define(:subject, :assignee)
427465
data = [row_klass.new(subject: "First", assignee: nil)]

0 commit comments

Comments
 (0)