Skip to content

Commit 322d101

Browse files
committed
Adds DataTable cell placeholder for blank values
Introduces DataTable::CellPlaceholder, a muted span ported from Primer React's Table.CellPlaceholder, and a Column placeholder option that substitutes it for blank cell values. Placeholder text is display-only: blank values still sort as blank. Custom cell renderers can also render the component directly.
1 parent 5545269 commit 322d101

17 files changed

Lines changed: 236 additions & 3 deletions

File tree

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@
2727
<% cell_arguments = { classes: "TableCell", align: cell.column.align } %>
2828
<% cell_arguments[:data] = cell.sort_data if cell.sortable? %>
2929
<% if cell.row_header? %>
30-
<% tr.with_row_header(**cell_arguments) { cell.column.render_cell(row).to_s } %>
30+
<% tr.with_row_header(**cell_arguments) { cell_content(cell, row) } %>
3131
<% else %>
32-
<% tr.with_cell(**cell_arguments) { cell.column.render_cell(row).to_s } %>
32+
<% tr.with_cell(**cell_arguments) { cell_content(cell, row) } %>
3333
<% end %>
3434
<% end %>
3535
<% end %>

app/components/primer/open_project/data_table.pcss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@
114114
border-block-end: var(--borderWidth-thin) solid var(--borderColor-default);
115115
}
116116

117+
/* TableCellPlaceholder */
118+
.TableCellPlaceholder {
119+
color: var(--fgColor-muted);
120+
}
121+
117122
.TableHeader:where([data-cell-align='end']),
118123
.TableCell:where([data-cell-align='end']) {
119124
display: flex;

app/components/primer/open_project/data_table.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,13 @@ def grid_template_from_columns(columns)
251251
end
252252
end
253253

254+
def cell_content(cell, row)
255+
value = cell.column.render_cell(row).to_s
256+
return value if value.present? || cell.column.placeholder.blank?
257+
258+
render(CellPlaceholder.new) { cell.column.placeholder }
259+
end
260+
254261
def cells_for(row)
255262
headers.map do |header|
256263
sort_metadata = header.sortable? ? header.column.sort_metadata(row) : {}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# frozen_string_literal: true
2+
3+
module Primer
4+
module OpenProject
5+
class DataTable
6+
# Renders muted placeholder text inside a table cell, standing in for
7+
# an empty value. Ported from Primer React's `Table.CellPlaceholder`.
8+
#
9+
# This component is part of `Primer::OpenProject::DataTable` and should
10+
# not be used as a standalone component. Use it inside a custom cell
11+
# renderer, or set `placeholder:` on a column to substitute blank
12+
# values automatically.
13+
class CellPlaceholder < Primer::Component
14+
status :open_project
15+
16+
# @param system_arguments [Hash]
17+
# System arguments passed to the root element
18+
def initialize(**system_arguments)
19+
@system_arguments = system_arguments
20+
@system_arguments[:tag] = :span
21+
@system_arguments[:classes] = class_names(
22+
@system_arguments[:classes],
23+
"TableCellPlaceholder"
24+
)
25+
end
26+
27+
def call
28+
render(Primer::BaseComponent.new(**@system_arguments)) { content }
29+
end
30+
end
31+
end
32+
end
33+
end

app/components/primer/open_project/data_table/column.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class Column < Primer::Component
1010

1111
COLUMN_WIDTH_OPTIONS = %i[grow grow_collapse auto].freeze
1212

13-
attr_reader :id, :align, :field, :max_width, :min_width, :row_header, :sort_by, :width, :cell_block
13+
attr_reader :id, :align, :field, :max_width, :min_width, :placeholder, :row_header, :sort_by, :width, :cell_block
1414

1515
# Provide a custom cell renderer.
1616
#
@@ -36,6 +36,9 @@ def with_header(&block)
3636
# @param sort_value [Proc, nil] Optional `->(row)` returning the value used when sorting this column.
3737
# Enables sorting of computed/fieldless columns. Falls back to `field` when omitted.
3838
# @param row_header [Boolean] Whether this column is a row header (`<th scope="row">`)
39+
# @param placeholder [String, nil] Placeholder text substituted for blank cell values,
40+
# rendered as a muted <%= link_to_component(Primer::OpenProject::DataTable::CellPlaceholder) %>.
41+
# Display-only: blank values still sort as blank.
3942
# @param min_width [Numeric, String, nil] Minimum width
4043
# @param max_width [Numeric, String, nil] Maximum width
4144
def initialize(
@@ -47,12 +50,14 @@ def initialize(
4750
sort_by: nil,
4851
sort_value: nil,
4952
row_header: false,
53+
placeholder: nil,
5054
min_width: nil,
5155
max_width: nil
5256
)
5357
@id = id
5458
@field = field
5559
@sort_value_proc = sort_value
60+
@placeholder = placeholder
5661
@align = align
5762
@header = header
5863

previews/primer/open_project/data_table_preview.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module OpenProject
55
# @component Primer::OpenProject::DataTable
66
class DataTablePreview < ViewComponent::Preview
77
DemoProject = Data.define(:id, :name, :status_code, :created_at)
8+
DemoAssignment = Data.define(:id, :name, :assignee)
89

910
# @label Default
1011
# @snapshot
@@ -50,6 +51,18 @@ def playground(
5051
)
5152
end
5253

54+
# @label With Cell Placeholder
55+
# @snapshot
56+
def with_cell_placeholder
57+
rows = [
58+
DemoAssignment.new(id: 1, name: "Project 1", assignee: "Ada Lovelace"),
59+
DemoAssignment.new(id: 2, name: "Project 2", assignee: nil),
60+
DemoAssignment.new(id: 3, name: "Project 3", assignee: "Grace Hopper")
61+
]
62+
63+
render_with_template(locals: { rows: rows })
64+
end
65+
5366
# @label With Pagination
5467
# @snapshot
5568
# @param page [Integer] number
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<%=
2+
render(
3+
Primer::OpenProject::DataTable.new(rows)
4+
) do |data_table|
5+
data_table.with_title(tag: :h2) { "Projects" }
6+
data_table.with_subtitle { "Blank assignees render a muted placeholder." }
7+
8+
data_table.with_column(field: :name, header: "Name", row_header: true)
9+
data_table.with_column(field: :assignee, header: "Assignee", placeholder: "Unassigned")
10+
end
11+
%>

static/arguments.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6102,6 +6102,22 @@
61026102
}
61036103
]
61046104
},
6105+
{
6106+
"component": "OpenProject::DataTable::CellPlaceholder",
6107+
"status": "open_project",
6108+
"a11y_reviewed": false,
6109+
"short_name": "OpenProjectDataTableCellPlaceholder",
6110+
"source": "https://github.com/primer/view_components/tree/main/app/components/primer/open_project/data_table/cell_placeholder.rb",
6111+
"lookbook": "https://primer.style/view-components/lookbook/inspect/primer/open_project/data_table/cell_placeholder/default/",
6112+
"parameters": [
6113+
{
6114+
"name": "system_arguments",
6115+
"type": "Hash",
6116+
"default": "N/A",
6117+
"description": "System arguments passed to the root element"
6118+
}
6119+
]
6120+
},
61056121
{
61066122
"component": "OpenProject::DataTable::Column",
61076123
"status": "open_project",
@@ -6158,6 +6174,12 @@
61586174
"default": "`false`",
61596175
"description": "Whether this column is a row header (`<th scope=\"row\">`)"
61606176
},
6177+
{
6178+
"name": "placeholder",
6179+
"type": "String, nil",
6180+
"default": "`nil`",
6181+
"description": "Placeholder text substituted for blank cell values, rendered as a muted [OpenProject::DataTable::CellPlaceholder](/components/openprojectdatatablecellplaceholder). Display-only: blank values still sort as blank."
6182+
},
61616183
{
61626184
"name": "min_width",
61636185
"type": "Numeric, String, nil",

static/audited_at.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@
150150
"Primer::OpenProject::DangerDialog::ConfirmationCheckBox": "",
151151
"Primer::OpenProject::DangerDialog::FormWrapper": "",
152152
"Primer::OpenProject::DataTable": "",
153+
"Primer::OpenProject::DataTable::CellPlaceholder": "",
153154
"Primer::OpenProject::DataTable::Column": "",
154155
"Primer::OpenProject::DataTable::PaginationFooter": "",
155156
"Primer::OpenProject::DataTable::SortHeader": "",

static/constants.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1774,6 +1774,7 @@
17741774
"spacious"
17751775
],
17761776
"Cell": "Primer::OpenProject::DataTable::Cell",
1777+
"CellPlaceholder": "Primer::OpenProject::DataTable::CellPlaceholder",
17771778
"Column": "Primer::OpenProject::DataTable::Column",
17781779
"GeneratedSlotMethods": "Primer::OpenProject::DataTable::GeneratedSlotMethods",
17791780
"Header": "Primer::OpenProject::DataTable::Header",
@@ -1788,6 +1789,9 @@
17881789
"Sorting": "Primer::OpenProject::DataTable::Sorting",
17891790
"TITLE_TAG_DEFAULT": "h2"
17901791
},
1792+
"Primer::OpenProject::DataTable::CellPlaceholder": {
1793+
"GeneratedSlotMethods": "Primer::OpenProject::DataTable::CellPlaceholder::GeneratedSlotMethods"
1794+
},
17911795
"Primer::OpenProject::DataTable::Column": {
17921796
"COLUMN_WIDTH_OPTIONS": [
17931797
"grow",

0 commit comments

Comments
 (0)