diff --git a/playbook/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_headers.html.erb b/playbook/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_headers.html.erb
new file mode 100644
index 0000000000..9e9d6c515c
--- /dev/null
+++ b/playbook/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_headers.html.erb
@@ -0,0 +1,43 @@
+<% column_definitions = [
+ {
+ accessor: "year",
+ label: "Year",
+ cellAccessors: ["quarter", "month", "day"],
+ },
+ {
+ label: "Enrollment Data",
+ columns: [
+ {
+ accessor: "newEnrollments",
+ label: "New Enrollments",
+ },
+ {
+ accessor: "scheduledMeetings",
+ label: "Scheduled Meetings",
+ },
+ ],
+ },
+ {
+ label: "Performance Data",
+ columns: [
+ {
+ accessor: "attendanceRate",
+ label: "Attendance Rate",
+ },
+ {
+ accessor: "completedClasses",
+ label: "Completed Classes",
+ },
+ {
+ accessor: "classCompletionRate",
+ label: "Class Completion Rate",
+ },
+ {
+ accessor: "graduatedStudents",
+ label: "Graduated Students",
+ },
+ ],
+ },
+] %>
+
+<%= pb_rails("advanced_table", props: { id: "beta_table_with_headers", table_data: @table_data, column_definitions: column_definitions }) %>
diff --git a/playbook/app/pb_kits/playbook/pb_advanced_table/docs/example.yml b/playbook/app/pb_kits/playbook/pb_advanced_table/docs/example.yml
index 1387c03272..c01df055be 100644
--- a/playbook/app/pb_kits/playbook/pb_advanced_table/docs/example.yml
+++ b/playbook/app/pb_kits/playbook/pb_advanced_table/docs/example.yml
@@ -5,6 +5,7 @@ examples:
- advanced_table_collapsible_trail_rails: Collapsible Trail
- advanced_table_beta_sort: Enable Sorting
- advanced_table_custom_cell_rails: Custom Components for Cells
+ - advanced_table_column_headers: Multi-Header Columns
react:
- advanced_table_default: Default (Required Props)
diff --git a/playbook/app/pb_kits/playbook/pb_advanced_table/table_body.rb b/playbook/app/pb_kits/playbook/pb_advanced_table/table_body.rb
index 0143ebaa90..64f0cd41e9 100644
--- a/playbook/app/pb_kits/playbook/pb_advanced_table/table_body.rb
+++ b/playbook/app/pb_kits/playbook/pb_advanced_table/table_body.rb
@@ -17,13 +17,32 @@ class TableBody < Playbook::KitBase
prop :collapsible_trail, type: Playbook::Props::Boolean,
default: true
+ def flatten_columns(columns)
+ columns.flat_map do |col|
+ if col[:columns]
+ flatten_columns(col[:columns])
+ elsif col[:accessor].present?
+ col
+ end
+ end.compact
+ end
+
def render_row_and_children(row, column_definitions, current_depth, first_parent_child)
+ leaf_columns = flatten_columns(column_definitions)
+
output = ActiveSupport::SafeBuffer.new
is_first_child_of_subrow = current_depth.positive? && first_parent_child && subrow_headers[current_depth - 1].present?
output << pb_rails("advanced_table/table_subrow_header", props: { row: row, column_definitions: column_definitions, depth: current_depth, subrow_header: subrow_headers[current_depth - 1], collapsible_trail: collapsible_trail }) if is_first_child_of_subrow && enable_toggle_expansion == "all"
- output << pb_rails("advanced_table/table_row", props: { id: id, row: row, column_definitions: column_definitions, depth: current_depth, collapsible_trail: collapsible_trail })
+ # Pass only leaf_columns to table_row to account for multiple nested columns
+ output << pb_rails("advanced_table/table_row", props: {
+ id: id,
+ row: row,
+ column_definitions: leaf_columns,
+ depth: current_depth,
+ collapsible_trail: collapsible_trail,
+ })
if row[:children].present?
output << content_tag(:div, class: "toggle-content", data: { advanced_table_content: row.object_id.to_s + id }) do
diff --git a/playbook/app/pb_kits/playbook/pb_advanced_table/table_header.html.erb b/playbook/app/pb_kits/playbook/pb_advanced_table/table_header.html.erb
index dd001da180..8542074ead 100644
--- a/playbook/app/pb_kits/playbook/pb_advanced_table/table_header.html.erb
+++ b/playbook/app/pb_kits/playbook/pb_advanced_table/table_header.html.erb
@@ -1,18 +1,24 @@
<%= pb_content_tag do %>
- <%= pb_rails("table/table_row", props: { tag: "div" }) do %>
- <% object.column_definitions.each_with_index do |item, index| %>
- <%= pb_rails("table/table_header", props: { tag: "div", id: item[:accessor], classname: object.th_classname, sort_menu: item[:sort_menu] }) do %>
- <%= pb_rails("flex", props:{ align: "center", justify: index.zero? ? "start" : "end", text_align: "end" }) do %>
- <% if index.zero? && (object.enable_toggle_expansion == "header" || object.enable_toggle_expansion == "all") %>
-
- <% end %>
- <%= item[:label] %>
+ <% object.header_rows.each_with_index do |header_row, row_index| %>
+ <%= pb_rails("table/table_row", props: { tag: "div" }) do %>
+ <% header_row.each_with_index do |cell, cell_index| %>
+
+ <% header_id = cell[:accessor].present? ? cell[:accessor] : "header_#{row_index}_#{cell_index}" %>
+
+ <%= pb_rails("table/table_header", props: { tag: "div", id: header_id, classname: object.th_classname, sort_menu: cell[:accessor] ? cell[:sort_menu] : nil }) do %>
+ <%= pb_rails("flex", props:{ align: "center", justify: row_index.zero? ? "start" : "end", text_align: "end" }) do %>
+ <% if cell_index.zero? && (object.enable_toggle_expansion == "header" || object.enable_toggle_expansion == "all") %>
+
<% end %>
+ <%= cell[:label] %>
<% end %>
- <% end %>
<% end %>
+
+ <% end %>
+ <% end %>
+ <% end %>
<% end %>
diff --git a/playbook/app/pb_kits/playbook/pb_advanced_table/table_header.rb b/playbook/app/pb_kits/playbook/pb_advanced_table/table_header.rb
index 00b6b628e7..39245498d8 100644
--- a/playbook/app/pb_kits/playbook/pb_advanced_table/table_header.rb
+++ b/playbook/app/pb_kits/playbook/pb_advanced_table/table_header.rb
@@ -16,6 +16,53 @@ def classname
def th_classname
generate_classname("table-header-cells", separator: " ")
end
+
+ def header_rows
+ rows = []
+ max_depth = compute_max_depth(column_definitions)
+
+ max_depth.times { rows << [] }
+
+ process_columns(column_definitions, rows, 0, max_depth)
+
+ rows
+ end
+
+ private
+
+ def compute_max_depth(columns)
+ columns.map do |col|
+ col[:columns] ? 1 + compute_max_depth(col[:columns]) : 1
+ end.max || 1
+ end
+
+ def process_columns(columns, rows, current_depth, max_depth)
+ columns.each do |col|
+ if col[:columns]
+ colspan = compute_leaf_columns(col[:columns])
+ rowspan = 1
+ rows[current_depth] << { label: col[:label], colspan: colspan, rowspan: rowspan }
+
+ process_columns(col[:columns], rows, current_depth + 1, max_depth)
+ else
+ colspan = 1
+ rowspan = max_depth - current_depth
+ rows[current_depth] << {
+ label: col[:label],
+ colspan: colspan,
+ rowspan: rowspan,
+ accessor: col[:accessor],
+ sort_menu: col[:sort_menu],
+ }
+ end
+ end
+ end
+
+ def compute_leaf_columns(columns)
+ columns.reduce(0) do |sum, col|
+ col[:columns] ? sum + compute_leaf_columns(col[:columns]) : sum + 1
+ end
+ end
end
end
end
diff --git a/playbook/app/pb_kits/playbook/pb_advanced_table/table_row.html.erb b/playbook/app/pb_kits/playbook/pb_advanced_table/table_row.html.erb
index 66268817ad..7ca978bcae 100644
--- a/playbook/app/pb_kits/playbook/pb_advanced_table/table_row.html.erb
+++ b/playbook/app/pb_kits/playbook/pb_advanced_table/table_row.html.erb
@@ -1,5 +1,6 @@
<%= pb_content_tag do %>
<% object.column_definitions.each_with_index do |column, index| %>
+ <% next unless column[:accessor].present? %>
<%= pb_rails("table/table_cell", props: { tag:"div", classname:object.td_classname}) do %>
<%= pb_rails("flex", props:{ align: "center", justify: index.zero? ? "start" : "end" }) do %>
<% if collapsible_trail && index.zero? %>
diff --git a/playbook/app/pb_kits/playbook/pb_advanced_table/table_row.rb b/playbook/app/pb_kits/playbook/pb_advanced_table/table_row.rb
index 90dd238322..9c40da5bba 100644
--- a/playbook/app/pb_kits/playbook/pb_advanced_table/table_row.rb
+++ b/playbook/app/pb_kits/playbook/pb_advanced_table/table_row.rb
@@ -29,6 +29,8 @@ def depth_accessors
private
def custom_renderer_value(column, index)
+ return nil unless column[:accessor].present?
+
if index.zero?
if depth.zero?
row[column[:accessor].to_sym]
@@ -37,6 +39,7 @@ def custom_renderer_value(column, index)
key = item.to_sym
return row[key] if depth - 1 == accessor_index
end
+ nil
end
else
row[column[:accessor].to_sym]